From 03ac97285b699954fa7bc24f8b6cdc1830c94d28 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Sun, 4 Oct 2026 18:19:32 +0800 Subject: [PATCH] exfat: dirty all new pages when extending valid_size In the current code, exfat_zero_new_range() does not mark non-uptodate pages as dirty, which may cause some blocks within a page to not be written back. This commit no longer checks each block in a non-uptodate page individually. Instead, it reads the entire page back into the cache and then writes it back as a whole. This allows concurrent writes and zeroing operations to be synchronized using the folio lock. Fixes: 1135704ed22f ("exfat: fix valid_size extension over a shared writable mapping") Cc: stable@vger.kernel.org Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/file.c | 106 ++++++++++++------------------------------------ 1 file changed, 25 insertions(+), 81 deletions(-) diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 871011750990..1aad24fd313f 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -656,98 +656,42 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) } /* - * exfat_zero_new_range - zero [start, end) without overwriting uptodate blocks + * exfat_prepare_valid_range - populate and dirty folios covering [start, end) * - * Uptodate blocks may contain data written through a shared mapping beyond - * valid_size. + * Populate missing blocks via the read path, which zero-fills data + * beyond the current valid_size and skips uptodate blocks. + * Mark entire folios dirty so the newly valid range is written back. + * + * Call before advancing valid_size. */ -static int exfat_zero_new_range(struct inode *inode, loff_t start, loff_t end) +static int exfat_prepare_valid_range(struct inode *inode, loff_t start, + loff_t end) { struct address_space *mapping = inode->i_mapping; - unsigned int blocksize = i_blocksize(inode); - loff_t pos = start; - int err; + loff_t next, pos = start; + struct folio *folio; + pgoff_t index; + + if (pos >= end) + return 0; while (pos < end) { - loff_t next = min_t(loff_t, - round_down(pos, PAGE_SIZE) + PAGE_SIZE, end); - struct folio *folio; - loff_t bpos; + index = pos >> PAGE_SHIFT; + next = min(((loff_t)index + 1) << PAGE_SHIFT, end); - folio = filemap_get_folio(mapping, pos >> PAGE_SHIFT); - if (IS_ERR(folio)) { - err = iomap_zero_range(inode, pos, next - pos, NULL, - &exfat_iomap_ops, NULL, NULL); - if (err < 0) - return err; - pos = next; - continue; - } + balance_dirty_pages_ratelimited(mapping); - if (folio_test_uptodate(folio)) { - folio_lock(folio); - if (folio->mapping == mapping) - folio_mark_dirty(folio); - folio_unlock(folio); - folio_put(folio); - pos = next; - continue; - } + folio = read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) + return PTR_ERR(folio); - /* - * Zero not-uptodate block runs. iomap_zero_range() requires an - * unlocked folio, so recheck ->mapping after each call. - */ folio_lock(folio); - bpos = pos; - while (bpos < next) { - loff_t rstart, rend; - - if (folio->mapping != mapping) { - folio_unlock(folio); - err = iomap_zero_range(inode, bpos, next - bpos, - NULL, &exfat_iomap_ops, NULL, NULL); - if (err < 0) { - folio_put(folio); - return err; - } - folio_lock(folio); - break; - } - - if (iomap_is_partially_uptodate(folio, - offset_in_folio(folio, bpos), blocksize)) { - bpos += blocksize; - continue; - } - - rstart = bpos; - rend = min_t(loff_t, bpos + blocksize, next); - while (rend < next && - !iomap_is_partially_uptodate(folio, - offset_in_folio(folio, rend), blocksize)) - rend = min_t(loff_t, rend + blocksize, next); - - folio_unlock(folio); - err = iomap_zero_range(inode, rstart, rend - rstart, - NULL, &exfat_iomap_ops, NULL, NULL); - if (err < 0) { - folio_put(folio); - return err; - } - folio_lock(folio); - bpos = rend; - } - - /* - * Dirty only a fully uptodate folio. Dirtying a partial folio could - * write uninitialised cache contents over valid on-disk blocks. - */ - if (folio->mapping == mapping && folio_test_uptodate(folio)) + if (folio->mapping == mapping) { folio_mark_dirty(folio); + pos = next; + } folio_unlock(folio); folio_put(folio); - pos = next; } return 0; @@ -783,7 +727,7 @@ static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size) if (gap_start < new_valid_size) unmap_mapping_range(inode->i_mapping, gap_start, new_valid_size - gap_start, 0); - ret = exfat_zero_new_range(inode, gap_start, new_valid_size); + ret = exfat_prepare_valid_range(inode, gap_start, new_valid_size); filemap_invalidate_unlock(inode->i_mapping); if (ret) { filemap_invalidate_lock(inode->i_mapping); @@ -965,7 +909,7 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) * fault populated its folio and iomap_page_mkwrite() * will dirty it. */ - err = exfat_zero_new_range(inode, ei->zeroed_size, + err = exfat_prepare_valid_range(inode, ei->zeroed_size, fault_page_start); if (err < 0) { inode_unlock(inode);