ntfs: do not clear the volume dirty bit during sync

ntfs_sync_fs() clears VOLUME_IS_DIRTY while the volume is still mounted
read-write, so a sync running concurrently with an in-flight metadata
modification can clear and persist a bit that was just set: the
modification then lands on a volume that is clean on disk, and a crash
does not run chkdsk.

Stop clearing the bit in ntfs_sync_fs() and leave the clearing to the
remount-to-read-only path, which the VFS reaches only after
sb_prepare_remount_readonly() has drained in-flight writers, and to
ntfs_put_super(), which runs after evict_inodes() on a quiesced
filesystem.  A mounted read-write volume now keeps the dirty bit until
it is dismounted cleanly, which matches the NTFS semantics; the cost is
a needless chkdsk if the machine crashes between a sync and the unmount.

A recorded error state is still persisted right away when the
filesystem is synced: with NVolErrors() set,
ntfs_sync_volume_dirty_state() can only set the bit, never clear it,
so it cannot lose a modification the way the old unconditional call
did.  This keeps the error report from being lost to a crash on a
volume that has seen no modification; an error recorded after the
last sync is still only persisted at the next quiescent transition.

sync_blockdev() and blkdev_issue_flush() are both called and the first
error is returned, so a writeback failure neither hides a flush failure
nor skips it.

A RWF_NOWAIT write still blocks in the marking when the volume looks
clean, as it already did on the base; a non-blocking marking is
follow-up work.

Reported-by: Baolin Liu <liubaolin@kylinos.cn>
Cc: stable@vger.kernel.org
Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Reviewed-by: Baolin Liu <liubaolin@kylinos.cn>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Hongling Zeng 2026-09-15 16:32:50 +08:00 committed by Namjae Jeon
parent 5d0e57ea8e
commit 4a1bb54f57
2 changed files with 28 additions and 10 deletions

View File

@ -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);

View File

@ -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;
}