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: 1135704ed2 ("exfat: fix valid_size extension over a shared writable mapping")
Cc: stable@vger.kernel.org
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Chi Zhiling 2026-10-04 18:19:32 +08:00 committed by Namjae Jeon
parent 646df471c3
commit 03ac97285b

View File

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