[PATCH v4 0/4] mm/truncate: fix data loss when truncating straddling large folios

Zhang Yi posted 4 patches 2 days, 4 hours ago
mm/internal.h |   4 +-
mm/shmem.c    |  13 ++---
mm/truncate.c | 128 +++++++++++++++++++++++++++++++++-----------------
3 files changed, 91 insertions(+), 54 deletions(-)
[PATCH v4 0/4] mm/truncate: fix data loss when truncating straddling large folios
Posted by Zhang Yi 2 days, 4 hours ago
From: Zhang Yi <yi.zhang@huawei.com>

Hello,

This is the fourth version fixing data loss when truncating straddling
large folios caught on the upcomming ext4 + iomap buffered I/O
conversion.

When truncate_inode_pages_range() punches a hole or truncates a file,
truncate_inode_partial_folio() splits a large folio so that the caller
can drop the in-range sub-folios while keeping the out-of-range tail
intact.  This series fixes three distinct problems in that path that
can each lose the valid out-of-range tail of a straddling folio, plus a
follow-up that clarifies the return value semantics.

Patch 01 aligns the truncation boundaries inwards to the mapping minimum
folio order in truncate_inode_pages_range().  With a non-zero min_order,
folio_split() stops at min_order instead of order 0, so a boundary
computed at page granularity can land inside a min-order-aligned
sub-folio and the truncate loop drops that whole chunk, valid tail
included, causing data loss.

Patch 02 looks the end-edge straddler up by its page index through
__filemap_get_folio() in truncate_inode_partial_folio().  After the
first split the straddler is unlocked and only transiently ref'd in the
page cache, so the page pointer derived from the original folio can be
freed and reallocated as a different folio in the same mapping, and the
mapping check cannot catch it, which may cause incorrect splitting and
potential data loss.

Patch 03 reworks the contract between truncate_inode_partial_folio() and
its callers.  If the second split of the straddler fails, the function
reported success unconditionally, and the leftover incorrect end
position could cause the truncate loop to drop that valid tail.  After
rework, it tells the caller the exact page range safe to discard via new
pstart/pend out-parameters, so the truncate loop never touches a
straddling folio that still holds valid out-of-range data.

Patch 04 clarifies the return value semantics to "at least one split
succeeded", which is all the shmem caller needs to decide whether to
reset its scan loop.


The second patch fixes a pre-existing race issue that is reachable
today, so it is Cc'd to stable.  Patches 01 and 03 require a dirty large
folio that carries no filesystem private data, so they are not reachable
on current filesystems.  They were found while developing the upcoming
ext4 iomap buffered I/O path[1].

Thanks,
Yi.

[1] https://lore.kernel.org/linux-fsdevel/a638a8fb-c184-4069-ae33-379ec12cd514@huaweicloud.com/


v3->v4:
 - Move the patch that fixes data loss when min_order is non-zero to the
   first patch position, aligning start and end in
   truncate_inode_pages_range(). (Zi Yan)
 - Add patch 2, fixing the invalid folio2 issue under concurrency when
   truncate_inode_partial_folio() splits at the end position. Use
   __filemap_get_folio() to obtain a reliable folio2. (Jan Kara)

v2->v3:
 - Rework the folio2 validity check logic to fix the invalid
   folio->index issue. (sashiko)
 - Clarify the pstart and pend setting logic and the corresponding
   comments to make it more readable. (Brian, Joanne)
 - Split the patch into 3 small patches. (Zi Yan)

v1->v2:
 - Export pstart as a new parameter so that the generic and shmem
   truncate paths don't need to recompute the start value from the
   return value. (Brian)
 - When min_order is non-zero, align [pstart, pend] to the inner
   boundaries of the folio to ensure they do not point into the middle
   of a large folio, which could otherwise cause valid data within the
   folio to be incorrectly cleared. (Joanne)

v3: https://lore.kernel.org/linux-mm/20260916092450.654408-1-yi.zhang@huaweicloud.com/
v2: https://lore.kernel.org/linux-mm/20260909062339.473816-1-yi.zhang@huaweicloud.com/
v1: https://lore.kernel.org/linux-mm/20260903115018.2034541-1-yi.zhang@huaweicloud.com/


Zhang Yi (4):
  mm/truncate: align truncation boundaries to mapping minimum folio
    order
  mm/truncate: look up the end-edge straddler by index
  mm/truncate: fix data loss when splitting straddling large folios
    fails
  mm/truncate: clarify return value of truncate_inode_partial_folio()

 mm/internal.h |   4 +-
 mm/shmem.c    |  13 ++---
 mm/truncate.c | 128 +++++++++++++++++++++++++++++++++-----------------
 3 files changed, 91 insertions(+), 54 deletions(-)

--
2.54.0
Re: [PATCH v4 0/4] mm/truncate: fix data loss when truncating straddling large folios
Posted by Brian Foster 2 days, 1 hour ago
On Tue, Sep 22, 2026 at 07:06:59PM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
> 
> Hello,
> 
> This is the fourth version fixing data loss when truncating straddling
> large folios caught on the upcomming ext4 + iomap buffered I/O
> conversion.
> 
> When truncate_inode_pages_range() punches a hole or truncates a file,
> truncate_inode_partial_folio() splits a large folio so that the caller
> can drop the in-range sub-folios while keeping the out-of-range tail
> intact.  This series fixes three distinct problems in that path that
> can each lose the valid out-of-range tail of a straddling folio, plus a
> follow-up that clarifies the return value semantics.
> 
> Patch 01 aligns the truncation boundaries inwards to the mapping minimum
> folio order in truncate_inode_pages_range().  With a non-zero min_order,
> folio_split() stops at min_order instead of order 0, so a boundary
> computed at page granularity can land inside a min-order-aligned
> sub-folio and the truncate loop drops that whole chunk, valid tail
> included, causing data loss.
> 
> Patch 02 looks the end-edge straddler up by its page index through
> __filemap_get_folio() in truncate_inode_partial_folio().  After the
> first split the straddler is unlocked and only transiently ref'd in the
> page cache, so the page pointer derived from the original folio can be
> freed and reallocated as a different folio in the same mapping, and the
> mapping check cannot catch it, which may cause incorrect splitting and
> potential data loss.
> 
> Patch 03 reworks the contract between truncate_inode_partial_folio() and
> its callers.  If the second split of the straddler fails, the function
> reported success unconditionally, and the leftover incorrect end
> position could cause the truncate loop to drop that valid tail.  After
> rework, it tells the caller the exact page range safe to discard via new
> pstart/pend out-parameters, so the truncate loop never touches a
> straddling folio that still holds valid out-of-range data.
> 
> Patch 04 clarifies the return value semantics to "at least one split
> succeeded", which is all the shmem caller needs to decide whether to
> reset its scan loop.
> 
> 
> The second patch fixes a pre-existing race issue that is reachable
> today, so it is Cc'd to stable.  Patches 01 and 03 require a dirty large
> folio that carries no filesystem private data, so they are not reachable
> on current filesystems.  They were found while developing the upcoming
> ext4 iomap buffered I/O path[1].
> 
> Thanks,
> Yi.
> 

Hi Yi,

Modulo Jan's comment on patch 4, this series looks good to me. FWIW:

Reviewed-by: Brian Foster <bfoster@redhat.com>

Brian

> [1] https://lore.kernel.org/linux-fsdevel/a638a8fb-c184-4069-ae33-379ec12cd514@huaweicloud.com/
> 
> 
> v3->v4:
>  - Move the patch that fixes data loss when min_order is non-zero to the
>    first patch position, aligning start and end in
>    truncate_inode_pages_range(). (Zi Yan)
>  - Add patch 2, fixing the invalid folio2 issue under concurrency when
>    truncate_inode_partial_folio() splits at the end position. Use
>    __filemap_get_folio() to obtain a reliable folio2. (Jan Kara)
> 
> v2->v3:
>  - Rework the folio2 validity check logic to fix the invalid
>    folio->index issue. (sashiko)
>  - Clarify the pstart and pend setting logic and the corresponding
>    comments to make it more readable. (Brian, Joanne)
>  - Split the patch into 3 small patches. (Zi Yan)
> 
> v1->v2:
>  - Export pstart as a new parameter so that the generic and shmem
>    truncate paths don't need to recompute the start value from the
>    return value. (Brian)
>  - When min_order is non-zero, align [pstart, pend] to the inner
>    boundaries of the folio to ensure they do not point into the middle
>    of a large folio, which could otherwise cause valid data within the
>    folio to be incorrectly cleared. (Joanne)
> 
> v3: https://lore.kernel.org/linux-mm/20260916092450.654408-1-yi.zhang@huaweicloud.com/
> v2: https://lore.kernel.org/linux-mm/20260909062339.473816-1-yi.zhang@huaweicloud.com/
> v1: https://lore.kernel.org/linux-mm/20260903115018.2034541-1-yi.zhang@huaweicloud.com/
> 
> 
> Zhang Yi (4):
>   mm/truncate: align truncation boundaries to mapping minimum folio
>     order
>   mm/truncate: look up the end-edge straddler by index
>   mm/truncate: fix data loss when splitting straddling large folios
>     fails
>   mm/truncate: clarify return value of truncate_inode_partial_folio()
> 
>  mm/internal.h |   4 +-
>  mm/shmem.c    |  13 ++---
>  mm/truncate.c | 128 +++++++++++++++++++++++++++++++++-----------------
>  3 files changed, 91 insertions(+), 54 deletions(-)
> 
> --
> 2.54.0
>
Re: [PATCH v4 0/4] mm/truncate: fix data loss when truncating straddling large folios
Posted by Zhang Yi 1 day, 1 hour ago
On 9/22/2026 9:47 PM, Brian Foster wrote:
> On Tue, Sep 22, 2026 at 07:06:59PM +0800, Zhang Yi wrote:
>> From: Zhang Yi <yi.zhang@huawei.com>
>>
>> Hello,
>>
>> This is the fourth version fixing data loss when truncating straddling
>> large folios caught on the upcomming ext4 + iomap buffered I/O
>> conversion.
>>
>> When truncate_inode_pages_range() punches a hole or truncates a file,
>> truncate_inode_partial_folio() splits a large folio so that the caller
>> can drop the in-range sub-folios while keeping the out-of-range tail
>> intact.  This series fixes three distinct problems in that path that
>> can each lose the valid out-of-range tail of a straddling folio, plus a
>> follow-up that clarifies the return value semantics.
>>
>> Patch 01 aligns the truncation boundaries inwards to the mapping minimum
>> folio order in truncate_inode_pages_range().  With a non-zero min_order,
>> folio_split() stops at min_order instead of order 0, so a boundary
>> computed at page granularity can land inside a min-order-aligned
>> sub-folio and the truncate loop drops that whole chunk, valid tail
>> included, causing data loss.
>>
>> Patch 02 looks the end-edge straddler up by its page index through
>> __filemap_get_folio() in truncate_inode_partial_folio().  After the
>> first split the straddler is unlocked and only transiently ref'd in the
>> page cache, so the page pointer derived from the original folio can be
>> freed and reallocated as a different folio in the same mapping, and the
>> mapping check cannot catch it, which may cause incorrect splitting and
>> potential data loss.
>>
>> Patch 03 reworks the contract between truncate_inode_partial_folio() and
>> its callers.  If the second split of the straddler fails, the function
>> reported success unconditionally, and the leftover incorrect end
>> position could cause the truncate loop to drop that valid tail.  After
>> rework, it tells the caller the exact page range safe to discard via new
>> pstart/pend out-parameters, so the truncate loop never touches a
>> straddling folio that still holds valid out-of-range data.
>>
>> Patch 04 clarifies the return value semantics to "at least one split
>> succeeded", which is all the shmem caller needs to decide whether to
>> reset its scan loop.
>>
>>
>> The second patch fixes a pre-existing race issue that is reachable
>> today, so it is Cc'd to stable.  Patches 01 and 03 require a dirty large
>> folio that carries no filesystem private data, so they are not reachable
>> on current filesystems.  They were found while developing the upcoming
>> ext4 iomap buffered I/O path[1].
>>
>> Thanks,
>> Yi.
>>
> 
> Hi Yi,
> 
> Modulo Jan's comment on patch 4, this series looks good to me. FWIW:
> 
> Reviewed-by: Brian Foster <bfoster@redhat.com>
> 
> Brian

Thank you for the review!

Yi.

> 
>> [1] https://lore.kernel.org/linux-fsdevel/a638a8fb-c184-4069-ae33-379ec12cd514@huaweicloud.com/
>>
>>
>> v3->v4:
>>   - Move the patch that fixes data loss when min_order is non-zero to the
>>     first patch position, aligning start and end in
>>     truncate_inode_pages_range(). (Zi Yan)
>>   - Add patch 2, fixing the invalid folio2 issue under concurrency when
>>     truncate_inode_partial_folio() splits at the end position. Use
>>     __filemap_get_folio() to obtain a reliable folio2. (Jan Kara)
>>
>> v2->v3:
>>   - Rework the folio2 validity check logic to fix the invalid
>>     folio->index issue. (sashiko)
>>   - Clarify the pstart and pend setting logic and the corresponding
>>     comments to make it more readable. (Brian, Joanne)
>>   - Split the patch into 3 small patches. (Zi Yan)
>>
>> v1->v2:
>>   - Export pstart as a new parameter so that the generic and shmem
>>     truncate paths don't need to recompute the start value from the
>>     return value. (Brian)
>>   - When min_order is non-zero, align [pstart, pend] to the inner
>>     boundaries of the folio to ensure they do not point into the middle
>>     of a large folio, which could otherwise cause valid data within the
>>     folio to be incorrectly cleared. (Joanne)
>>
>> v3: https://lore.kernel.org/linux-mm/20260916092450.654408-1-yi.zhang@huaweicloud.com/
>> v2: https://lore.kernel.org/linux-mm/20260909062339.473816-1-yi.zhang@huaweicloud.com/
>> v1: https://lore.kernel.org/linux-mm/20260903115018.2034541-1-yi.zhang@huaweicloud.com/
>>
>>
>> Zhang Yi (4):
>>    mm/truncate: align truncation boundaries to mapping minimum folio
>>      order
>>    mm/truncate: look up the end-edge straddler by index
>>    mm/truncate: fix data loss when splitting straddling large folios
>>      fails
>>    mm/truncate: clarify return value of truncate_inode_partial_folio()
>>
>>   mm/internal.h |   4 +-
>>   mm/shmem.c    |  13 ++---
>>   mm/truncate.c | 128 +++++++++++++++++++++++++++++++++-----------------
>>   3 files changed, 91 insertions(+), 54 deletions(-)
>>
>> --
>> 2.54.0
>>
>