Re: [PATCH v3 2/3] mm/truncate: align truncation boundaries to mapping minimum folio order
From: Zhang Yi
Date: Thu Sep 17 2026 - 09:19:40 EST
On 9/17/2026 5:14 AM, Zi Yan wrote:
On Wed Sep 16, 2026 at 5:24 AM EDT, Zhang Yi wrote:
From: Zhang Yi <yi.zhang@xxxxxxxxxx>
When the mapping has a non-zero minimum folio order (min_order),
folio_split() stops at min_order instead of order 0, so the sub-folio
containing a split point stays aligned to 1 << min_order rather than to
a single page. The original boundaries were based on page granularity,
so either boundary could land inside the min_order chunk at its edge,
and the truncation loop would drop that whole chunk, valid out-of-range
tail included.
For example, a 64K (order-4) folio with min_order = 2 (16K) punched from
offset 0 to 36K:
split @p0 -> [p0-p3, p4-p7, p8-p15] # non-uniform, min_order
folio2 = p8-p15 # straddles: p8 in range, p9-p15 tail valid
2nd split of folio2 -> [p8-p11, p12-p15] # success
end(old) = p9 # BUG: p9 inside [p8-p11]
loop truncates ... p8-p11 # p9-p11's valid tail is lost
It has gone unnoticed so far for two reasons. A non-zero min_order is
only used by filesystems with a block or sector size larger than the
page size, and those either always write back the affected range before
punching a hole or truncating, or they carry filesystem private data on
dirty folios (e.g. buffer_head), which makes filemap_release_folio()
fail and folio_split() abort with -EBUSY, so the folio is never split
and the old start/end boundaries remain valid. The bug only becomes
reachable on paths that truncate dirty large folios without prior
writeback and without filesystem private data, such as the upcoming ext4
iomap buffered I/O path.
Align both start (rounded up) and end (rounded down) to the mapping
minimum folio order so they always fall on a folio boundary.
Reported-by: Joanne Koong <joannelkoong@xxxxxxxxx>
Link: https://lore.kernel.org/linux-mm/CAJnrk1bQYUe6+1ryyJur5EEnZYrC+_5AYsy=OWzVRgD4202y1g@xxxxxxxxxxxxxx/
Fixes: e220917fa5077 ("mm: split a folio in minimum folio order chunks")
Signed-off-by: Zhang Yi <yi.zhang@xxxxxxxxxx>
---
mm/truncate.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
This should be the first patch, since Fixes is older than the one in
patch 1.
It can adjust start and end in truncate_inode_pages_range() instead,
since shmem's min_order is always 0.
Something like below.
Am I missing anything? Thanks.
Yeah, this makes sense to me, thanks for your suggestion.
Yi.
diff --git a/mm/truncate.c b/mm/truncate.c
index b58ba940be474..803ee61ebf624 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -374,6 +374,7 @@ void truncate_inode_pages_range(struct address_space *mapping,
int i;
struct folio *folio;
bool same_folio;
+ pgoff_t min_nrpages = mapping_min_folio_nrpages(mapping);
if (mapping_empty(mapping))
return;
@@ -395,6 +396,10 @@ void truncate_inode_pages_range(struct address_space *mapping,
else
end = (lend + 1) >> PAGE_SHIFT;
+ start = round_up(start, min_nrpages);
+ if (end != (pgoff_t)-1)
+ end = round_down(end, min_nrpages);
+
folio_batch_init(&fbatch);
index = start;
while (index < end && find_lock_entries(mapping, &index, end - 1,