| Message ID | 20260922110703.468389-2-yi.zhang@huaweicloud.com |
|---|---|
| State | Not Applicable |
| Headers | show |
| Series | mm/truncate: fix data loss when truncating straddling large folios | expand |
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Critical] mm: integer overflow in truncate_inode_pages_range causing data loss -- commit e346e0885d48b84feb918a7c5125a3b206d6bba7 Author: Zhang Yi <yi.zhang@huawei.com> mm/truncate: align truncation boundaries to mapping minimum folio order This commit aligns the start and end truncation boundaries to the minimum folio order of the mapping. This prevents valid out-of-range tail sub-folios from being incorrectly discarded when a mapping has a non-zero min_order. Link: https://lore.kernel.org/linux-mm/CAJnrk1bQYUe6+1ryyJur5EEnZYrC+_5AYsy=OWzVRgD4202y1g@mail.gmail.com/ > diff --git a/mm/truncate.c b/mm/truncate.c > index b58ba940be474..f9625bb4916f9 100644 > --- a/mm/truncate.c > +++ b/mm/truncate.c [ ... ] > @@ -374,14 +376,14 @@ void truncate_inode_pages_range(struct address_space *mapping, [ ... ] > start = (lstart + PAGE_SIZE - 1) >> PAGE_SHIFT; [ ... ] > else > end = (lend + 1) >> PAGE_SHIFT; > > + start = round_up(start, min_nrpages); [Severity: Critical] Can this integer overflow on 32-bit architectures? In truncate_inode_pages_range(), pgoff_t is a 32-bit integer on 32-bit systems. If a hole punch or truncate operation occurs at an offset near MAX_LFS_FILESIZE, the start page index will be close to 0xFFFFFFFF. When min_nrpages > 1 (e.g., on filesystems supporting min_order > 0 like Ext4 with bigalloc), rounding up this index wraps it to 0. Would this wrap-around cause the entire page cache for the file to be truncated instead of just the intended tail, leading to data loss? > + if (end != (pgoff_t)-1) > + end = round_down(end, min_nrpages); > + > folio_batch_init(&fbatch);
On Tue 22-09-26 19:07:00, Zhang Yi wrote: > From: Zhang Yi <yi.zhang@huawei.com> > > When the mapping has a non-zero minimum folio order (min_order), > folio_split() in truncate_inode_partial_folio() 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 in truncate_inode_pages_range() 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@gmail.com> > Link: https://lore.kernel.org/linux-mm/CAJnrk1bQYUe6+1ryyJur5EEnZYrC+_5AYsy=OWzVRgD4202y1g@mail.gmail.com/ > Fixes: e220917fa5077 ("mm: split a folio in minimum folio order chunks") > Suggested-by: Zi Yan <ziy@nvidia.com> > Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Looks good. Feel free to add: Reviewed-by: Jan Kara <jack@suse.cz> Honza > --- > mm/truncate.c | 18 ++++++++++++------ > 1 file changed, 12 insertions(+), 6 deletions(-) > > diff --git a/mm/truncate.c b/mm/truncate.c > index b58ba940be47..f9625bb4916f 100644 > --- a/mm/truncate.c > +++ b/mm/truncate.c > @@ -345,9 +345,11 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio) > * @lstart: offset from which to truncate > * @lend: offset to which to truncate (inclusive) > * > - * Truncate the page cache, removing the pages that are between > - * specified offsets (and zeroing out partial pages > - * if lstart or lend + 1 is not page aligned). > + * Truncate the page cache, removing the folios that are between specified > + * offsets (and zeroing out partial folios if lstart or lend + 1 is not > + * folio aligned). For mappings with a non-zero minimum folio order, the > + * boundaries are aligned inwards to 1 << min_order so the edge sub-folio > + * straddling the range is kept. > * > * Truncate takes two passes - the first pass is nonblocking. It will not > * block on page locks and it will not block on writeback. The second pass > @@ -374,14 +376,14 @@ 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; > > /* > - * 'start' and 'end' always covers the range of pages to be fully > - * truncated. Partial pages are covered with 'partial_start' at the > - * start of the range and 'partial_end' at the end of the range. > + * 'start' and 'end' always covers the range of folios to be fully > + * truncated, with both boundaries aligned inwards to 1 << min_order. > * Note that 'end' is exclusive while 'lend' is inclusive. > */ > start = (lstart + PAGE_SIZE - 1) >> PAGE_SHIFT; > @@ -395,6 +397,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, > -- > 2.52.0 >
On Tue Sep 22, 2026 at 7:07 AM EDT, Zhang Yi wrote: > From: Zhang Yi <yi.zhang@huawei.com> > > When the mapping has a non-zero minimum folio order (min_order), > folio_split() in truncate_inode_partial_folio() 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 in truncate_inode_pages_range() 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@gmail.com> > Link: https://lore.kernel.org/linux-mm/CAJnrk1bQYUe6+1ryyJur5EEnZYrC+_5AYsy=OWzVRgD4202y1g@mail.gmail.com/ > Fixes: e220917fa5077 ("mm: split a folio in minimum folio order chunks") > Suggested-by: Zi Yan <ziy@nvidia.com> > Signed-off-by: Zhang Yi <yi.zhang@huawei.com> > --- > mm/truncate.c | 18 ++++++++++++------ > 1 file changed, 12 insertions(+), 6 deletions(-) LGTM. Just a nit below. Reviewed-by: Zi Yan <ziy@nvidia.com> > > diff --git a/mm/truncate.c b/mm/truncate.c > index b58ba940be47..f9625bb4916f 100644 > --- a/mm/truncate.c > +++ b/mm/truncate.c > @@ -345,9 +345,11 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio) > * @lstart: offset from which to truncate > * @lend: offset to which to truncate (inclusive) > * > - * Truncate the page cache, removing the pages that are between > - * specified offsets (and zeroing out partial pages > - * if lstart or lend + 1 is not page aligned). > + * Truncate the page cache, removing the folios that are between specified > + * offsets (and zeroing out partial folios if lstart or lend + 1 is not > + * folio aligned). For mappings with a non-zero minimum folio order, the > + * boundaries are aligned inwards to 1 << min_order so the edge sub-folio > + * straddling the range is kept. > * > * Truncate takes two passes - the first pass is nonblocking. It will not > * block on page locks and it will not block on writeback. The second pass > @@ -374,14 +376,14 @@ 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); > It is better to put it at the top (reverse christmas tree).
On 9/22/2026 11:29 PM, Zi Yan wrote: > On Tue Sep 22, 2026 at 7:07 AM EDT, Zhang Yi wrote: >> From: Zhang Yi <yi.zhang@huawei.com> >> >> When the mapping has a non-zero minimum folio order (min_order), >> folio_split() in truncate_inode_partial_folio() 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 in truncate_inode_pages_range() 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@gmail.com> >> Link: https://lore.kernel.org/linux-mm/CAJnrk1bQYUe6+1ryyJur5EEnZYrC+_5AYsy=OWzVRgD4202y1g@mail.gmail.com/ >> Fixes: e220917fa5077 ("mm: split a folio in minimum folio order chunks") >> Suggested-by: Zi Yan <ziy@nvidia.com> >> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> >> --- >> mm/truncate.c | 18 ++++++++++++------ >> 1 file changed, 12 insertions(+), 6 deletions(-) > > LGTM. Just a nit below. > > Reviewed-by: Zi Yan <ziy@nvidia.com> >> >> diff --git a/mm/truncate.c b/mm/truncate.c >> index b58ba940be47..f9625bb4916f 100644 >> --- a/mm/truncate.c >> +++ b/mm/truncate.c >> @@ -345,9 +345,11 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio) >> * @lstart: offset from which to truncate >> * @lend: offset to which to truncate (inclusive) >> * >> - * Truncate the page cache, removing the pages that are between >> - * specified offsets (and zeroing out partial pages >> - * if lstart or lend + 1 is not page aligned). >> + * Truncate the page cache, removing the folios that are between specified >> + * offsets (and zeroing out partial folios if lstart or lend + 1 is not >> + * folio aligned). For mappings with a non-zero minimum folio order, the >> + * boundaries are aligned inwards to 1 << min_order so the edge sub-folio >> + * straddling the range is kept. >> * >> * Truncate takes two passes - the first pass is nonblocking. It will not >> * block on page locks and it will not block on writeback. The second pass >> @@ -374,14 +376,14 @@ 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); >> > > It is better to put it at the top (reverse christmas tree). > > Sure, will move it, thanks for the review. Yi.
On 9/22/2026 7:07 PM, Zhang Yi wrote: > From: Zhang Yi <yi.zhang@huawei.com> > > When the mapping has a non-zero minimum folio order (min_order), > folio_split() in truncate_inode_partial_folio() 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 in truncate_inode_pages_range() 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@gmail.com> > Link: https://lore.kernel.org/linux-mm/CAJnrk1bQYUe6+1ryyJur5EEnZYrC+_5AYsy=OWzVRgD4202y1g@mail.gmail.com/ > Fixes: e220917fa5077 ("mm: split a folio in minimum folio order chunks") > Suggested-by: Zi Yan <ziy@nvidia.com> > Signed-off-by: Zhang Yi <yi.zhang@huawei.com> > --- > mm/truncate.c | 18 ++++++++++++------ > 1 file changed, 12 insertions(+), 6 deletions(-) > > diff --git a/mm/truncate.c b/mm/truncate.c > index b58ba940be47..f9625bb4916f 100644 > --- a/mm/truncate.c > +++ b/mm/truncate.c > @@ -345,9 +345,11 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio) > * @lstart: offset from which to truncate > * @lend: offset to which to truncate (inclusive) > * > - * Truncate the page cache, removing the pages that are between > - * specified offsets (and zeroing out partial pages > - * if lstart or lend + 1 is not page aligned). > + * Truncate the page cache, removing the folios that are between specified > + * offsets (and zeroing out partial folios if lstart or lend + 1 is not > + * folio aligned). For mappings with a non-zero minimum folio order, the > + * boundaries are aligned inwards to 1 << min_order so the edge sub-folio > + * straddling the range is kept. > * > * Truncate takes two passes - the first pass is nonblocking. It will not > * block on page locks and it will not block on writeback. The second pass > @@ -374,14 +376,14 @@ 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; > > /* > - * 'start' and 'end' always covers the range of pages to be fully > - * truncated. Partial pages are covered with 'partial_start' at the > - * start of the range and 'partial_end' at the end of the range. > + * 'start' and 'end' always covers the range of folios to be fully > + * truncated, with both boundaries aligned inwards to 1 << min_order. > * Note that 'end' is exclusive while 'lend' is inclusive. > */ > start = (lstart + PAGE_SIZE - 1) >> PAGE_SHIFT; > @@ -395,6 +397,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); > + Hi, all The sashiko pointed out an issue on this patch: > [Severity: Critical] > Can this integer overflow on 32-bit architectures? > > In truncate_inode_pages_range(), pgoff_t is a 32-bit integer on 32-bit > systems. If a hole punch or truncate operation occurs at an offset near > MAX_LFS_FILESIZE, the start page index will be close to 0xFFFFFFFF. > > When min_nrpages > 1 (e.g., on filesystems supporting min_order > 0 like > Ext4 with bigalloc), rounding up this index wraps it to 0. > > Would this wrap-around cause the entire page cache for the file to be > truncated instead of just the intended tail, leading to data loss? This is a pre-existing issue, and it is not specific to the truncate path. On 32-bit systems, for a mapping with a non-zero minimum folio order, the last min_order-aligned block of a file of MAX_LFS_FILESIZE is also the last block of the page index space, so the folio covering it ends at or past the last representable page index. All places that do offset calculations on such end folios carry an overflow risk, e.g., folio_next_index() wraps to 0 for that folio, and the values derived from it are wrong. For example, on a 32-bit environment I create an ext4 filesystem with a 16KB blocksize, where the maximum file size is 0xFFFFFFFF000 (MAX_LFS_FILESIZE). If we run: xfs_io -f -c "pwrite -b 0x10000 0xFFFFFFFC000 0x3000" /mnt/foo The last folio starts at 0xFFFFFFFC000, but its length is 0x4000, so calling folio_next_index() on it will overflow, leading to unpredictable errors. I think the root cause is that sb->s_maxbytes is set to MAX_LFS_FILESIZE, which on 32-bit is (loff_t)ULONG_MAX << PAGE_SHIFT. A folio is at least min_order pages and min_order aligned, so the file must have at most ULONG_MAX + 1 - (1 << min_order) pages for the last folio's next index to stay representable. I suppose filesystems that use a non-zero minimum folio order should cap sb->s_maxbytes instead of using MAX_LFS_FILESIZE directly, something like this: static inline loff_t max_lfs_filesize(unsigned int min_order) { #if BITS_PER_LONG == 32 return ((loff_t)ULONG_MAX + 1 - (1UL << min_order)) << PAGE_SHIFT; #else return MAX_LFS_FILESIZE; #endif } Any thoughts? Besides, I understand that since this is a pre-existing issue, it shouldn't block the merging of this series? Thanks, Yi.
On Thu 24-09-26 17:31:40, Zhang Yi wrote: > > @@ -395,6 +397,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); > > + > Hi, all > > The sashiko pointed out an issue on this patch: > > > [Severity: Critical] > > Can this integer overflow on 32-bit architectures? > > > > In truncate_inode_pages_range(), pgoff_t is a 32-bit integer on 32-bit > > systems. If a hole punch or truncate operation occurs at an offset near > > MAX_LFS_FILESIZE, the start page index will be close to 0xFFFFFFFF. > > > > When min_nrpages > 1 (e.g., on filesystems supporting min_order > 0 like > > Ext4 with bigalloc), rounding up this index wraps it to 0. > > > > Would this wrap-around cause the entire page cache for the file to be > > truncated instead of just the intended tail, leading to data loss? > > This is a pre-existing issue, and it is not specific to the truncate > path. > > On 32-bit systems, for a mapping with a non-zero minimum folio order, > the last min_order-aligned block of a file of MAX_LFS_FILESIZE is also > the last block of the page index space, so the folio covering it ends at > or past the last representable page index. All places that do offset > calculations on such end folios carry an overflow risk, e.g., > folio_next_index() wraps to 0 for that folio, and the values derived > from it are wrong. > > For example, on a 32-bit environment I create an ext4 filesystem with a > 16KB blocksize, where the maximum file size is 0xFFFFFFFF000 > (MAX_LFS_FILESIZE). If we run: > > xfs_io -f -c "pwrite -b 0x10000 0xFFFFFFFC000 0x3000" /mnt/foo > > The last folio starts at 0xFFFFFFFC000, but its length is 0x4000, > so calling folio_next_index() on it will overflow, leading to > unpredictable errors. > > > I think the root cause is that sb->s_maxbytes is set to > MAX_LFS_FILESIZE, which on 32-bit is (loff_t)ULONG_MAX << PAGE_SHIFT. A > folio is at least min_order pages and min_order aligned, so the file > must have at most ULONG_MAX + 1 - (1 << min_order) pages for the last > folio's next index to stay representable. > > I suppose filesystems that use a non-zero minimum folio order should cap > sb->s_maxbytes instead of using MAX_LFS_FILESIZE directly, something > like this: > > static inline loff_t max_lfs_filesize(unsigned int min_order) > { > #if BITS_PER_LONG == 32 > return ((loff_t)ULONG_MAX + 1 - (1UL << min_order)) << PAGE_SHIFT; > #else > return MAX_LFS_FILESIZE; > #endif > } > > Any thoughts? Yeah, I guess it makes sense. > Besides, I understand that since this is a pre-existing issue, it > shouldn't block the merging of this series? Right, I don't think this belongs to this patchset. Honza
diff --git a/mm/truncate.c b/mm/truncate.c index b58ba940be47..f9625bb4916f 100644 --- a/mm/truncate.c +++ b/mm/truncate.c @@ -345,9 +345,11 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio) * @lstart: offset from which to truncate * @lend: offset to which to truncate (inclusive) * - * Truncate the page cache, removing the pages that are between - * specified offsets (and zeroing out partial pages - * if lstart or lend + 1 is not page aligned). + * Truncate the page cache, removing the folios that are between specified + * offsets (and zeroing out partial folios if lstart or lend + 1 is not + * folio aligned). For mappings with a non-zero minimum folio order, the + * boundaries are aligned inwards to 1 << min_order so the edge sub-folio + * straddling the range is kept. * * Truncate takes two passes - the first pass is nonblocking. It will not * block on page locks and it will not block on writeback. The second pass @@ -374,14 +376,14 @@ 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; /* - * 'start' and 'end' always covers the range of pages to be fully - * truncated. Partial pages are covered with 'partial_start' at the - * start of the range and 'partial_end' at the end of the range. + * 'start' and 'end' always covers the range of folios to be fully + * truncated, with both boundaries aligned inwards to 1 << min_order. * Note that 'end' is exclusive while 'lend' is inclusive. */ start = (lstart + PAGE_SIZE - 1) >> PAGE_SHIFT; @@ -395,6 +397,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,