mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
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:
parent
646df471c3
commit
03ac97285b
106
fs/exfat/file.c
106
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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user