Re: [PATCH v3 2/3] mm/truncate: align truncation boundaries to mapping minimum folio order

From: Zi Yan

Date: Wed Sep 16 2026 - 17:17:32 EST


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.

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,

--
Best Regards,
Yan, Zi