diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index e8b056c3e4cc..bb8641103d26 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -637,9 +637,10 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) /* * The volume must be marked dirty before the modification is made, - * without an unlocked check of the in-memory flag: ntfs_sync_fs() - * can clear the bit concurrently and the modification would then - * land on a volume that is clean on disk. + * without an unlocked check of the in-memory flag: the dirty bit + * is only cleared at the quiescent transitions, under the same + * $Volume mrec_lock this call takes, so an unlocked skip could + * lose the set to one of them. */ ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 42d895776f32..a08523457fe0 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -493,8 +493,9 @@ int ntfs_set_volume_flags(struct ntfs_volume *vol, __le16 flags) * This is the single point that persists the in-memory error state to disk. * The runtime error paths only record NVolErrors() because they run under a * variety of ntfs locks the dirty-bit write cannot be taken under (runlist - * locks, vol->lcnbmp_lock, vol->mftbmp_lock, mrec_locks); the first - * ntfs_sync_fs(), a remount, or the unmount then persists the flag here. + * locks, vol->lcnbmp_lock, vol->mftbmp_lock, mrec_locks); a sync of a + * volume with recorded errors, a remount to read-only, or the unmount, + * then persists the flag here. * * A hibernated volume is not written from these persistence paths: * resuming Windows from a modified image corrupts it, so the dirty bit @@ -2023,7 +2024,7 @@ static void ntfs_shutdown(struct super_block *sb) static int ntfs_sync_fs(struct super_block *sb, int wait) { struct ntfs_volume *vol = NTFS_SB(sb); - int err = 0; + int ret, err = 0; if (NVolShutdown(vol)) return -EIO; @@ -2031,14 +2032,30 @@ static int ntfs_sync_fs(struct super_block *sb, int wait) if (!wait) return 0; - /* If there are some dirty buffers in the bdev inode */ - if (ntfs_sync_volume_dirty_state(vol)) { + /* + * The volume dirty bit is deliberately not cleared here: a sync + * running concurrently with an in-flight modification could clear + * and persist a bit that was just set, leaving the modification + * on a volume that is clean on disk. The bit is only cleared at + * the quiescent state transitions, remounting read-only and clean + * unmount. A recorded error state, however, is persisted right + * away so that it is not lost to a crash on a volume that has + * seen no modification; with NVolErrors() set this can only set + * the bit, never clear it. + */ + if (NVolErrors(vol) && + ntfs_sync_volume_dirty_state(vol)) { ntfs_warning(sb, "Failed to sync dirty bit in volume information flags. Run chkdsk."); err = -EIO; } sync_inodes_sb(sb); - sync_blockdev(sb->s_bdev); - blkdev_issue_flush(sb->s_bdev); + ret = sync_blockdev(sb->s_bdev); + if (ret && !err) + err = ret; + + ret = blkdev_issue_flush(sb->s_bdev); + if (ret && !err) + err = ret; return err; }