* [PATCH v2 0/7] exfat: fix valid_size update races
@ 2026-10-13 3:38 Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 1/7] exfat: advance valid_size under the folio lock for buffered writes Chi Zhiling
` (7 more replies)
0 siblings, 8 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
exFAT uses valid_size (ValidDataLength) as the on-disk boundary of
initialised data, but the current code advances it too late or without
proper serialisation. As a result, buffered writes can silently lose
data reclaimed before valid_size covers it, page faults and direct I/O
can race with writes, and a failed direct write still grows the file
on disk.
This series advances valid_size under the folio lock for buffered
writes, revalidates cached iomaps, drops i_rwsem from
exfat_page_mkwrite(), and publishes i_size/valid_size only on
successful direct I/O completion.
V1: https://lore.kernel.org/exfat/59056131-e3bd-4003-8c30-0a356cf0bd82@163.com/
Link: https://lore.kernel.org/exfat/10029d6f-13cc-4d17-a2f9-b093a8339dde/T/#t
Chi Zhiling (7):
exfat: advance valid_size under the folio lock for buffered writes
exfat: advance valid_size while zeroing new ranges
exfat: validate cached iomaps during buffered writes
exfat: drop i_rwsem from exfat_page_mkwrite
exfat: do not re-zero the valid range in exfat_page_mkwrite
exfat: complete direct writes synchronously when they update metadata
exfat: publish i_size and valid_size on direct I/O completion
fs/exfat/file.c | 69 ++++++++++++++++++++++---------------
fs/exfat/iomap.c | 88 +++++++++++++++++++++++++++++++++++++++---------
fs/exfat/iomap.h | 1 +
3 files changed, 114 insertions(+), 44 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/7] exfat: advance valid_size under the folio lock for buffered writes
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
@ 2026-10-13 3:38 ` Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 2/7] exfat: advance valid_size while zeroing new ranges Chi Zhiling
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
Buffered writes currently advance valid_size in
exfat_write_iomap_end(), which runs after the folio has been unlocked.
As a result, a folio beyond valid_size can become uptodate and dirty,
and be observed by writeback or reclaim while valid_size still points
to an earlier position.
If reclaim drops such a folio before valid_size covers its range, a
subsequent read treats the range as unwritten and zero-fills it without
reading from disk. When this zero-filled page is later written back,
the zeros overwrite the data that was just written, silently losing
the write even if valid_size has since been advanced.
Advance valid_size from the buffered write path via an iomap
put_folio callback while the folio is still locked. This ensures
valid_size is updated before the folio becomes visible to writeback,
preventing the written data from being lost. Mark the inode dirty
whenever valid_size advances.
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
fs/exfat/file.c | 8 ++++++--
fs/exfat/iomap.c | 16 ++++++++++++++++
fs/exfat/iomap.h | 1 +
3 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index f106db945d8d..dc6df0b73373 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -816,7 +816,7 @@ static ssize_t exfat_fallback_buffered_write(struct kiocb *iocb,
inode_dio_wait(file_inode(iocb->ki_filp));
written = iomap_file_buffered_write(iocb, from, &exfat_write_iomap_ops,
- NULL, NULL);
+ &exfat_iomap_write_ops, NULL);
if (written < 0)
return written;
@@ -907,11 +907,15 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
*/
inode_dio_wait(inode);
ret = iomap_file_buffered_write(iocb, iter,
- &exfat_write_iomap_ops, NULL, NULL);
+ &exfat_write_iomap_ops, &exfat_iomap_write_ops,
+ NULL);
}
if (ret < 0)
goto unlock;
+ if (exfat_get_valid_size(ei) > valid_size)
+ mark_inode_dirty(inode);
+
inode_unlock(inode);
if (iocb->ki_pos > pos) {
diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index e50ecbfbfbe0..f3816509c6e5 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -252,6 +252,22 @@ const struct iomap_ops exfat_write_iomap_ops = {
.iomap_next = exfat_write_iomap_next,
};
+static void exfat_iomap_put_folio(struct inode *inode, loff_t pos,
+ unsigned int copied, struct folio *folio)
+{
+ struct exfat_inode_info *ei = EXFAT_I(inode);
+
+ if (copied)
+ exfat_advance_valid_size(ei, pos + copied);
+
+ folio_unlock(folio);
+ folio_put(folio);
+}
+
+const struct iomap_write_ops exfat_iomap_write_ops = {
+ .put_folio = exfat_iomap_put_folio,
+};
+
/*
* exfat_writeback_range - Map folio during writeback
*
diff --git a/fs/exfat/iomap.h b/fs/exfat/iomap.h
index fd8a913f7794..39c93f8cd790 100644
--- a/fs/exfat/iomap.h
+++ b/fs/exfat/iomap.h
@@ -9,6 +9,7 @@
extern const struct iomap_dio_ops exfat_write_dio_ops;
extern const struct iomap_ops exfat_iomap_ops;
extern const struct iomap_ops exfat_write_iomap_ops;
+extern const struct iomap_write_ops exfat_iomap_write_ops;
extern const struct iomap_writeback_ops exfat_writeback_ops;
extern const struct iomap_read_ops exfat_iomap_bio_read_ops;
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 2/7] exfat: advance valid_size while zeroing new ranges
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 1/7] exfat: advance valid_size under the folio lock for buffered writes Chi Zhiling
@ 2026-10-13 3:38 ` Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 3/7] exfat: validate cached iomaps during buffered writes Chi Zhiling
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
valid_size is currently updated only after the range has been zeroed, so
it lags the folios that were just marked uptodate and dirty.
Advance it from exfat_prepare_valid_range() page by page, under the folio
lock and right after each folio is marked dirty; the same helper advances
zeroed_size and dirties the inode once the range is prepared.
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
fs/exfat/file.c | 34 +++++++++++++++++++---------------
1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index dc6df0b73373..6f7b50c7a075 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -724,6 +724,7 @@ static int exfat_prepare_valid_range(struct inode *inode, loff_t start,
loff_t end)
{
struct address_space *mapping = inode->i_mapping;
+ struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t next, pos = start;
struct folio *folio;
pgoff_t index;
@@ -744,12 +745,21 @@ static int exfat_prepare_valid_range(struct inode *inode, loff_t start,
folio_lock(folio);
if (folio->mapping == mapping) {
folio_mark_dirty(folio);
+ exfat_advance_valid_size(ei, next);
pos = next;
}
folio_unlock(folio);
folio_put(folio);
}
+ /*
+ * Track zeroed_size by block, not page, because writeback stops
+ * at i_size recording blocks wholly beyond it could skip a
+ * later required zeroing.
+ */
+ exfat_advance_zeroed_size(ei, round_up(next, i_blocksize(inode)));
+ mark_inode_dirty(inode);
+
return 0;
}
@@ -794,11 +804,6 @@ static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size)
filemap_invalidate_unlock(inode->i_mapping);
return ret;
}
-
- exfat_set_valid_size(ei, new_valid_size);
- exfat_advance_zeroed_size(ei,
- round_up(new_valid_size, i_blocksize(inode)));
- mark_inode_dirty(inode);
}
return ret;
@@ -962,6 +967,7 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
struct exfat_inode_info *ei = EXFAT_I(inode);
vm_fault_t ret;
loff_t new_valid_size, mmap_valid_size, fault_page_start;
+ bool dirty = false;
if (!inode_trylock(inode))
return VM_FAULT_RETRY;
@@ -988,16 +994,7 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
return vmf_fs_error(err);
}
}
-
- /*
- * Track zeroed_size by block, not page, because writeback stops
- * at i_size recording blocks wholly beyond it could skip a
- * later required zeroing.
- */
- exfat_advance_zeroed_size(ei,
- round_up(new_valid_size, i_blocksize(inode)));
- exfat_set_valid_size(ei, new_valid_size);
- mark_inode_dirty(inode);
+ dirty = true;
}
sb_start_pagefault(inode->i_sb);
@@ -1005,6 +1002,13 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
filemap_invalidate_lock_shared(inode->i_mapping);
ret = iomap_page_mkwrite(vmf, &exfat_iomap_ops, NULL);
+ if (ret == VM_FAULT_LOCKED && dirty) {
+ exfat_advance_valid_size(ei, new_valid_size);
+ exfat_advance_zeroed_size(ei,
+ round_up(new_valid_size, i_blocksize(inode)));
+ mark_inode_dirty(inode);
+ }
+
filemap_invalidate_unlock_shared(inode->i_mapping);
sb_end_pagefault(inode->i_sb);
inode_unlock(inode);
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 3/7] exfat: validate cached iomaps during buffered writes
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 1/7] exfat: advance valid_size under the folio lock for buffered writes Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 2/7] exfat: advance valid_size while zeroing new ranges Chi Zhiling
@ 2026-10-13 3:38 ` Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 4/7] exfat: drop i_rwsem from exfat_page_mkwrite Chi Zhiling
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
The next patch, "exfat: drop i_rwsem from exfat_page_mkwrite()", allows a
fault to advance valid_size under the folio lock without serializing
against an in-flight buffered write. As a result, valid_size may advance
into a folio covered by a cached iomap. Reusing that iomap would cause
__iomap_write_begin() to zero the wrong part of the block.
Add an ->iomap_valid callback that compares ->validity_cookie against the
current valid_size and forces a remap if valid_size has advanced into the
locked folio, whose pointer is stored in ->private by ->get_folio.
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
fs/exfat/iomap.c | 38 +++++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index f3816509c6e5..26d216ef9c44 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -57,6 +57,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
iomap->addr = IOMAP_NULL_ADDR;
iomap->offset = offset;
iomap->length = length;
+ iomap->validity_cookie = valid_size;
return 0;
}
@@ -145,7 +146,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
}
}
- iomap->flags |= IOMAP_F_MERGED;
+ iomap->validity_cookie = valid_size;
out:
mutex_unlock(&sbi->s_lock);
return err;
@@ -252,6 +253,17 @@ const struct iomap_ops exfat_write_iomap_ops = {
.iomap_next = exfat_write_iomap_next,
};
+static struct folio *exfat_iomap_get_folio(struct iomap_iter *iter, loff_t pos,
+ unsigned int len)
+{
+ struct folio *folio = iomap_get_folio(iter, pos, len);
+
+ if (!IS_ERR(folio))
+ iter->iomap.private = folio;
+
+ return folio;
+}
+
static void exfat_iomap_put_folio(struct inode *inode, loff_t pos,
unsigned int copied, struct folio *folio)
{
@@ -264,8 +276,32 @@ static void exfat_iomap_put_folio(struct inode *inode, loff_t pos,
folio_put(folio);
}
+static bool exfat_iomap_valid(struct inode *inode, const struct iomap *iomap)
+{
+ struct folio *folio = iomap->private;
+ loff_t old_valid_size = iomap->validity_cookie;
+ loff_t valid_size = exfat_get_valid_size(EXFAT_I(inode));
+
+ if (valid_size == old_valid_size)
+ return true;
+
+ if (!folio)
+ return false;
+
+ /* Still valid for this folio. */
+ if (old_valid_size >= folio_next_pos(folio))
+ return true;
+
+ if (valid_size <= folio_pos(folio))
+ return true;
+
+ return false;
+}
+
const struct iomap_write_ops exfat_iomap_write_ops = {
+ .get_folio = exfat_iomap_get_folio,
.put_folio = exfat_iomap_put_folio,
+ .iomap_valid = exfat_iomap_valid,
};
/*
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 4/7] exfat: drop i_rwsem from exfat_page_mkwrite
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
` (2 preceding siblings ...)
2026-10-13 3:38 ` [PATCH v2 3/7] exfat: validate cached iomaps during buffered writes Chi Zhiling
@ 2026-10-13 3:38 ` Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 5/7] exfat: do not re-zero the valid range in exfat_page_mkwrite Chi Zhiling
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
exfat_page_mkwrite() takes i_rwsem with inode_trylock() and returns
VM_FAULT_RETRY when that fails. The MM subsystem does not propagate
VM_FAULT_RETRY from ->page_mkwrite(), so the fault still completes and
the page becomes writable without exfat_page_mkwrite() ever advancing
valid_size.
Blocking on i_rwsem is not an option either: ->page_mkwrite() runs under
mmap_lock, so waiting for i_rwsem would create an mmap_lock -> i_rwsem
ordering that can deadlock.
Now that valid_size is advanced under the folio lock, i_rwsem is no
longer needed here. Take mapping->invalidate_lock shared, which
serializes the fault against truncate, and advance valid_size after
iomap_page_mkwrite() has returned the locked folio.
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
fs/exfat/file.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 6f7b50c7a075..a8c1f4757678 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -969,8 +969,9 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
loff_t new_valid_size, mmap_valid_size, fault_page_start;
bool dirty = false;
- if (!inode_trylock(inode))
- return VM_FAULT_RETRY;
+ sb_start_pagefault(inode->i_sb);
+ file_update_time(vmf->vma->vm_file);
+ filemap_invalidate_lock_shared(inode->i_mapping);
mmap_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
fault_page_start = ((loff_t)vmf->pgoff) << PAGE_SHIFT;
@@ -990,17 +991,13 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
err = exfat_prepare_valid_range(inode, zeroed_size,
fault_page_start);
if (err < 0) {
- inode_unlock(inode);
- return vmf_fs_error(err);
+ ret = vmf_fs_error(err);
+ goto out;
}
}
dirty = true;
}
- sb_start_pagefault(inode->i_sb);
- file_update_time(vmf->vma->vm_file);
-
- filemap_invalidate_lock_shared(inode->i_mapping);
ret = iomap_page_mkwrite(vmf, &exfat_iomap_ops, NULL);
if (ret == VM_FAULT_LOCKED && dirty) {
exfat_advance_valid_size(ei, new_valid_size);
@@ -1008,10 +1005,9 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
round_up(new_valid_size, i_blocksize(inode)));
mark_inode_dirty(inode);
}
-
+out:
filemap_invalidate_unlock_shared(inode->i_mapping);
sb_end_pagefault(inode->i_sb);
- inode_unlock(inode);
return ret;
}
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 5/7] exfat: do not re-zero the valid range in exfat_page_mkwrite
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
` (3 preceding siblings ...)
2026-10-13 3:38 ` [PATCH v2 4/7] exfat: drop i_rwsem from exfat_page_mkwrite Chi Zhiling
@ 2026-10-13 3:38 ` Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 6/7] exfat: complete direct writes synchronously when they update metadata Chi Zhiling
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
valid_size is advanced before zeroed_size, and exfat_page_mkwrite() no
longer holds i_rwsem, so a fault can observe a zeroed_size that lags
valid_size. Zeroing the gap from zeroed_size then reads back and dirties
folios below valid_size that already hold valid data.
Start from max(valid_size, zeroed_size) instead, as
exfat_extend_valid_size() already does. The skipped range is either
already zeroed or already valid, so nothing that needs zeroing is missed.
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
fs/exfat/file.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index a8c1f4757678..6209a991a118 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -965,8 +965,9 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
struct exfat_inode_info *ei = EXFAT_I(inode);
+ loff_t mmap_valid_size, fault_page_start;
+ loff_t old_valid_size, new_valid_size;
vm_fault_t ret;
- loff_t new_valid_size, mmap_valid_size, fault_page_start;
bool dirty = false;
sb_start_pagefault(inode->i_sb);
@@ -975,12 +976,14 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
mmap_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
fault_page_start = ((loff_t)vmf->pgoff) << PAGE_SHIFT;
+ old_valid_size = exfat_get_valid_size(ei);
new_valid_size = min(mmap_valid_size, i_size_read(inode));
- if (exfat_get_valid_size(ei) < new_valid_size) {
+ if (old_valid_size < new_valid_size) {
loff_t zeroed_size = exfat_get_zeroed_size(ei);
if (zeroed_size < fault_page_start) {
+ loff_t gap_start = max(old_valid_size, zeroed_size);
int err;
/*
@@ -988,7 +991,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_prepare_valid_range(inode, zeroed_size,
+ err = exfat_prepare_valid_range(inode, gap_start,
fault_page_start);
if (err < 0) {
ret = vmf_fs_error(err);
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 6/7] exfat: complete direct writes synchronously when they update metadata
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
` (4 preceding siblings ...)
2026-10-13 3:38 ` [PATCH v2 5/7] exfat: do not re-zero the valid range in exfat_page_mkwrite Chi Zhiling
@ 2026-10-13 3:38 ` Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 7/7] exfat: publish i_size and valid_size on direct I/O completion Chi Zhiling
2026-10-13 4:04 ` [PATCH v2 0/7] exfat: fix valid_size update races Namjae Jeon
7 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
The inode metadata for a direct write is updated in ->end_io(). That
handler runs without holding i_rwsem. As a result, the i_size update can
race with other operations.
Whenever a write extends beyond the current valid_size — that is,
whenever ->end_io() will have metadata to update — force synchronous
completion. This guarantees that the i_size update happens under the
inode lock.
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
fs/exfat/file.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 6209a991a118..efcfeab750c0 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -840,10 +840,16 @@ static ssize_t exfat_fallback_buffered_write(struct kiocb *iocb,
static ssize_t exfat_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
+ struct inode *inode = file_inode(iocb->ki_filp);
+ loff_t end = iocb->ki_pos + iov_iter_count(from);
+ unsigned int dio_flags = 0;
ssize_t ret;
+ if (end > exfat_get_valid_size(EXFAT_I(inode)))
+ dio_flags |= IOMAP_DIO_FORCE_WAIT;
+
ret = iomap_dio_rw(iocb, from, &exfat_write_iomap_ops,
- &exfat_write_dio_ops, 0, NULL, 0);
+ &exfat_write_dio_ops, dio_flags, NULL, 0);
if (ret == -ENOTBLK)
ret = 0;
else if (ret < 0)
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 7/7] exfat: publish i_size and valid_size on direct I/O completion
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
` (5 preceding siblings ...)
2026-10-13 3:38 ` [PATCH v2 6/7] exfat: complete direct writes synchronously when they update metadata Chi Zhiling
@ 2026-10-13 3:38 ` Chi Zhiling
2026-10-13 4:04 ` [PATCH v2 0/7] exfat: fix valid_size update races Namjae Jeon
7 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 3:38 UTC (permalink / raw)
To: exfat, linux-kernel
Cc: Yuezhang.Mo, dxdt, linkinjeon, sj1557.seo, Chi Zhiling
From: Chi Zhiling <chizhiling@kylinos.cn>
exfat_write_iomap_end() advances valid_size from the submission path.
A direct write that later fails or completes short has therefore already
marked its range valid, and __exfat_write_inode() records
max(i_size, valid_size) as DataLength, growing the file on disk even
though no data was written. Advancing valid_size early also lets a
racing mmap fault map the range and pull the not-yet-overwritten contents
of the recycled cluster into the page cache.
Publish both fields from ->end_io() instead, once the transfer has
completed successfully, and stop advancing valid_size from ->iomap_end();
buffered writes still publish valid_size under the folio lock via
->put_folio().
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
fs/exfat/iomap.c | 34 +++++++++++++++++++---------------
1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index 26d216ef9c44..9e25c18c148a 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -15,22 +15,31 @@
/*
* exfat_file_write_dio_end_io - Direct I/O write completion handler
*
- * Updates i_size if the write extended the file. Called from the dio layer
- * after I/O completion.
+ * Updates i_size and valid_size after the transfer has completed
+ * successfully. The updating of i_size and valid_size runs in process
+ * context and under i_rwsem.
*/
static int exfat_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
int error, unsigned int flags)
{
struct inode *inode = file_inode(iocb->ki_filp);
+ loff_t end = iocb->ki_pos + size;
+ bool dirtied = false;
- if (error)
+ if (error || !size)
return error;
- if (size && i_size_read(inode) < iocb->ki_pos + size) {
- i_size_write(inode, iocb->ki_pos + size);
- mark_inode_dirty(inode);
+ if (i_size_read(inode) < end) {
+ i_size_write(inode, end);
+ dirtied = true;
}
+ if (exfat_advance_valid_size(EXFAT_I(inode), end))
+ dirtied = true;
+
+ if (dirtied)
+ mark_inode_dirty(inode);
+
return 0;
}
@@ -213,15 +222,11 @@ static const struct iomap_ops exfat_swap_iomap_ops = {
/*
* exfat_write_iomap_end - Update the state after write
- *
- * Extends ->valid_size to cover the newly written range.
- * Marks the inode dirty if metadata was changed.
*/
static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
ssize_t written, unsigned int flags, struct iomap *iomap)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
- bool dirtied = false;
loff_t end;
if (!written)
@@ -229,18 +234,17 @@ static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
end = pos + written;
- if (exfat_advance_valid_size(ei, end))
- dirtied = true;
-
/*
* IOMAP_F_ZERO_TAIL zeroes the remainder of the last block. Track that
* block as zeroed so later valid_size extensions do not zero it again.
*/
if (iomap->flags & IOMAP_F_ZERO_TAIL)
end = round_up(end, i_blocksize(inode));
- exfat_advance_zeroed_size(ei, end);
- if (dirtied || iomap->flags & IOMAP_F_SIZE_CHANGED)
+ if (!(flags & IOMAP_DIRECT))
+ exfat_advance_zeroed_size(ei, end);
+
+ if (iomap->flags & IOMAP_F_SIZE_CHANGED)
mark_inode_dirty(inode);
return written;
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 0/7] exfat: fix valid_size update races
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
` (6 preceding siblings ...)
2026-10-13 3:38 ` [PATCH v2 7/7] exfat: publish i_size and valid_size on direct I/O completion Chi Zhiling
@ 2026-10-13 4:04 ` Namjae Jeon
2026-10-13 6:02 ` Chi Zhiling
7 siblings, 1 reply; 10+ messages in thread
From: Namjae Jeon @ 2026-10-13 4:04 UTC (permalink / raw)
To: Chi Zhiling
Cc: exfat, linux-kernel, Yuezhang.Mo, dxdt, sj1557.seo, Chi Zhiling
On Tue, Oct 13, 2026 at 12:39 PM Chi Zhiling <chizhiling@163.com> wrote:
>
> From: Chi Zhiling <chizhiling@kylinos.cn>
Hi Chi,
>
> exFAT uses valid_size (ValidDataLength) as the on-disk boundary of
> initialised data, but the current code advances it too late or without
> proper serialisation. As a result, buffered writes can silently lose
> data reclaimed before valid_size covers it, page faults and direct I/O
> can race with writes, and a failed direct write still grows the file
> on disk.
>
> This series advances valid_size under the folio lock for buffered
> writes, revalidates cached iomaps, drops i_rwsem from
> exfat_page_mkwrite(), and publishes i_size/valid_size only on
> successful direct I/O completion.
>
> V1: https://lore.kernel.org/exfat/59056131-e3bd-4003-8c30-0a356cf0bd82@163.com/
> Link: https://lore.kernel.org/exfat/10029d6f-13cc-4d17-a2f9-b093a8339dde/T/#t
>
> Chi Zhiling (7):
> exfat: advance valid_size under the folio lock for buffered writes
> exfat: advance valid_size while zeroing new ranges
> exfat: validate cached iomaps during buffered writes
> exfat: drop i_rwsem from exfat_page_mkwrite
> exfat: do not re-zero the valid range in exfat_page_mkwrite
> exfat: complete direct writes synchronously when they update metadata
> exfat: publish i_size and valid_size on direct I/O completion
Can you add fixes tags to the patches ?
Thanks.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 0/7] exfat: fix valid_size update races
2026-10-13 4:04 ` [PATCH v2 0/7] exfat: fix valid_size update races Namjae Jeon
@ 2026-10-13 6:02 ` Chi Zhiling
0 siblings, 0 replies; 10+ messages in thread
From: Chi Zhiling @ 2026-10-13 6:02 UTC (permalink / raw)
To: Namjae Jeon
Cc: exfat, linux-kernel, Yuezhang.Mo, dxdt, sj1557.seo, Chi Zhiling
On 10/13/2026 12:04 PM, Namjae Jeon wrote:
> On Tue, Oct 13, 2026 at 12:39 PM Chi Zhiling <chizhiling@163.com> wrote:
>>
>> From: Chi Zhiling <chizhiling@kylinos.cn>
> Hi Chi,
Hi Namjae,
>>
>> exFAT uses valid_size (ValidDataLength) as the on-disk boundary of
>> initialised data, but the current code advances it too late or without
>> proper serialisation. As a result, buffered writes can silently lose
>> data reclaimed before valid_size covers it, page faults and direct I/O
>> can race with writes, and a failed direct write still grows the file
>> on disk.
>>
>> This series advances valid_size under the folio lock for buffered
>> writes, revalidates cached iomaps, drops i_rwsem from
>> exfat_page_mkwrite(), and publishes i_size/valid_size only on
>> successful direct I/O completion.
>>
>> V1: https://lore.kernel.org/exfat/59056131-e3bd-4003-8c30-0a356cf0bd82@163.com/
>> Link: https://lore.kernel.org/exfat/10029d6f-13cc-4d17-a2f9-b093a8339dde/T/#t
>>
>> Chi Zhiling (7):
>> exfat: advance valid_size under the folio lock for buffered writes
>> exfat: advance valid_size while zeroing new ranges
>> exfat: validate cached iomaps during buffered writes
>> exfat: drop i_rwsem from exfat_page_mkwrite
>> exfat: do not re-zero the valid range in exfat_page_mkwrite
>> exfat: complete direct writes synchronously when they update metadata
>> exfat: publish i_size and valid_size on direct I/O completion
> Can you add fixes tags to the patches ?
Sure, I'll add Fixes tag in the next version.
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-13 6:03 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-13 3:38 [PATCH v2 0/7] exfat: fix valid_size update races Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 1/7] exfat: advance valid_size under the folio lock for buffered writes Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 2/7] exfat: advance valid_size while zeroing new ranges Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 3/7] exfat: validate cached iomaps during buffered writes Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 4/7] exfat: drop i_rwsem from exfat_page_mkwrite Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 5/7] exfat: do not re-zero the valid range in exfat_page_mkwrite Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 6/7] exfat: complete direct writes synchronously when they update metadata Chi Zhiling
2026-10-13 3:38 ` [PATCH v2 7/7] exfat: publish i_size and valid_size on direct I/O completion Chi Zhiling
2026-10-13 4:04 ` [PATCH v2 0/7] exfat: fix valid_size update races Namjae Jeon
2026-10-13 6:02 ` Chi Zhiling
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®