FAILED: patch "[PATCH] btrfs: fix incorrect buffered IO fallback for append direct" failed to apply to 6.1-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.1-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.1.y
git checkout FETCH_HEAD
git cherry-pick -x ff66fe6662330226b3f486014c375538d91c44aa
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026071510-legend-handled-035f@gregkh' --subject-prefix 'PATCH 6.1.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From ff66fe6662330226b3f486014c375538d91c44aa Mon Sep 17 00:00:00 2001
From: Qu Wenruo <[email protected]>
Date: Thu, 4 Jun 2026 09:59:47 +0930
Subject: [PATCH] btrfs: fix incorrect buffered IO fallback for append direct
writes
[BUG]
With the previous bug of short direct writes fixed, test case generic/362 (*) still fails with the following error with nodatasum mount option:
generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
- output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
--- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930
+++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:13:09.072485767 +0930
@@ -1,2 +1,3 @@
QA output created by 362
+Wrong file size after first write, got 8192 expected 4096
Silence is golden
...*: If the test case has been executed before with default data checksum, the failure will not reproduce. Need the following fix to make it reliably reproducible:
https://lore.kernel.org/linux-btrfs/[email protected]/
[CAUSE]
Inside btrfs_dio_iomap_begin() for a direct write, we increase the isize if it's beyond the current isize.
But if the direct io finished short, we do not revert the isize to the previous value nor to the short write end.
Then if we need to fall back to buffered writes, and the write has IOCB_APPEND flag, then the buffered write will be positioned at the incorrect isize.
The call chain looks like this:
btrfs_direct_write(pos=0, length=4K)
|- __iomap_dio_rw()
| |- iomap_iter()
| | |- btrfs_dio_iomap_begin()
| | |- btrfs_get_blocks_direct_write()
| | |- i_size_write()
| | Which updates the isize to the write end (4K).
| |
| |- iomap_dio_iter()
| | Failed with -EFAULT on the first page.
| |
| |- iomap_iter()
| | |- btrfs_dio_iomap_end()
| | Detects a short write, return -ENOTBLK
| |- if (ret == -ENOTBLK) { ret = 0;}
| Which resets the return value.
|
|- ret = iomap_dio_complet()
| Which returns 0.
|
|- btrfs_buffered_write(iocb, from); |- generic_write_checks()
|- iocb->ki_pos = i_size_read()
Which is still the new size (4K), other than the original isize 0.
[FIX]
Introduce the following btrfs_dio_data members:
- old_isize
- updated_isize
If the direct write has enlarged the isize.
Then if we got a short write, and btrfs_dio_data::updated_isize is set, revert to the correct isize based on old_isize and current file position.
And here we call i_size_write() without holding an extent lock, which is a very special case that we're safe to do:
- Only a single writer can be enlarging isize Enlarging isize will take the exclusive inode lock.
- Buffered readers need to wait for the OE we're holding Buffered readers will lock extent and wait for OE of the folio range. Sometimes we can skip the OE wait, but since all page cache is invalidated, the OE wait can not be skipped.
But I do not think this is the most elegant solution, nor covers all cases. E.g. if the bio is submitted but IO failed, we are unable to do the revert.
I believe the more elegant one would be extend the EXTENT_DIO_LOCKED lifespan for direct writes, so that we can update the isize when a write beyond EOF finished successfully.
However that change is too huge for a small bug fix. So only implement the minimal partial fix for now.
[REASON FOR NO FIXES TAG]
The bug is again very old, before commit f85781fb505e ("btrfs: switch to iomap for direct IO") we are already increasing isize without a proper rollback for short writes.
Thus only a CC to stable.
fs/btrfs/direct-io.c +42 -1
@@ -15,10 +15,12 @@struct btrfs_dio_data {ssize_t submitted; +loff_t old_isize;struct extent_changeset *data_reserved;struct btrfs_ordered_extent *ordered;bool data_space_reserved;bool nocow_done; +bool updated_isize; };struct btrfs_dio_private {
@@ -228,6 +230,7 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,bool space_reserved = false;u64 len = *lenp;u64 prev_len; +loff_t old_isize;int ret =0 ;/*
@@ -341,8 +344,14 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,* Need to update the i_size under the extent lock so buffered * readers will get the updated i_size when we unlock. */ -if (start + len >i_size_read (inode)) + old_isize =i_size_read (inode); +if (start + len > old_isize) { +if (!dio_data->updated_isize) { + dio_data->old_isize = old_isize; + dio_data->updated_isize = true; + }i_size_write (inode, start + len); + } out:if (ret && space_reserved) {btrfs_delalloc_release_extents (BTRFS_I (inode), len);
@@ -625,6 +634,38 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length, pos += submitted; length -= submitted;if (write) { +/* +* Got a short write and have updated the isize, need to +* revert the isize change. +* +* Normally we need to update isize with extent lock hold, +* but we're safe due to the following factors: +* +* - Only a single writer can be enlarging isize +* Enlarging isize will take the exclusive inode lock. +* +* - Buffered readers need to wait for the OE we're holding +* Buffered readers will lock extent and wait for OE +* of the folio range, and since page cache is invalidated +* the OE wait can not be skipped. +* +* So here we are safe to revert the isize before +* finishing the OE, and no reader of the remaining range +* can see the enlarged size. +* +* TODO: Extend the DIO_LOCKED lifespan for direct writes, +* and only enlarge isize after a successful write. +*/ +if (dio_data->updated_isize) { +u64 new_isize; + +if (submitted ==0 ) + new_isize = dio_data->old_isize; +else + new_isize =max (dio_data->old_isize, pos); +i_size_write (inode, new_isize); + dio_data->updated_isize = false; + }/* * We have a short write, if there is any range * that is submitted properly, that part will have
Sasha Levin
maintainerPATCH 1/2 btrfs: fix false IO failure after falling back to buffered write
From: Qu Wenruo <[email protected]>
[ Upstream commit 66ff4d366e7eb4d31813d2acabf3af512ce03aa5 ]
[BUG]
The test case generic/362 will fail with "nodatasum" mount option (*):
MOUNT_OPTIONS -- -o nodatasum /dev/mapper/test-scratch1 /mnt/scratch
generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
--- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930
+++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:21:17.574771567 +0930
@@ -1,2 +1,3 @@
QA output created by 362
+First write failed: Input/output error
Silence is golden
...*: If the test case has been executed before with default data checksum, the failure will not reproduce. Need the following fix to make it reliably reproducible:
https://lore.kernel.org/linux-btrfs/[email protected]/
[CAUSE]
Inside __iomap_dio_rw(), the -EFAULT/-ENOTBLK error is not directly returned. Thus we never got an error pointer from __iomap_dio_rw().
The call chain looks like this:
btrfs_direct_write()
|- btrfs_dio_write()
|- __iomap_dio_rw()
| |- iomap_iter()
| | |- btrfs_dio_iomap_begin()
| | Now an ordered extent is allocated for the 4K write.
| |
| |- iomi.status = iomap_dio_iter()
| | Where iomap_dio_iter() returned -EFAULT.
| |
| |- ret = iomap_iter()
| | |- btrfs_dio_iomap_end()
| | | |- btrfs_finish_ordered_extent(uptodate = false)
| | | | |- can_finish_ordered_extent()
| | | | |- btrfs_mark_ordered_extent_error()
| | | | |- mapping_set_error()
| | | | Now the address space is marked error.
| | | | return -ENOTBLK
| | |- return -ENOTBLK
| |- if (ret == -ENOTBLK) { ret = 0; }
| Now the return value is reset to 0.
| Thus no error pointer will be returned.
|
|- ret = iomap_dio_complete()
| Since no byte is submitted, @ret is 0.
|
|- Fallback to buffered IO
| And the buffered write finished without error
|
|- filemap_fdatawait_range() |- filemap_check_errors()
The previous error is recorded, thus an error is returned
However the buffered write is properly submitted and finished, the error is from the btrfs_finish_ordered_extent() call with @uptodate = false.
[FIX]
When a short dio write happened, any range that is submitted will have btrfs_extract_ordered_extent() to be called, thus the submitted range will always have an OE just covering the submitted range.
The remaining OE range is never submitted, thus they should be treated as truncated, not an error. So that we can properly reclaim and not insert an unnecessary file extent item, without marking the mapping as error.
Extract a helper, btrfs_mark_ordered_extent_truncated(), and utilize that helper to mark the direct IO ordered extent as truncated, so it won't cause failure for the later buffered fallback.
[REASON FOR NO FIXES TAG]
The bug itself is pretty old, at commit f85781fb505e ("btrfs: switch to iomap for direct IO") we're already passing @uptodate=false finishing the OE.
But at that time OE with IOERR won't call mapping_set_error(), so it's not exposed.
Later commit d61bec08b904 ("btrfs: mark ordered extent and inode with error if we fail to finish") finally exposed the bug, but that commit is doing a correct job, not the root cause.
Anyway the bug is very old, dating back to 5.1x days, thus only CC to stable.
Stable-dep-of: ff66fe666233 ("btrfs: fix incorrect buffered IO fallback for append direct writes")
--- fs/btrfs/inode.c | 33 +++++++++++++++++++++++++-------- fs/btrfs/ordered-data.c | 12 ++++++++++++ fs/btrfs/ordered-data.h | 2 ++ 3 files changed, 39 insertions(+), 8 deletions(-)
fs/btrfs/inode.c +25 -8
@@ -7987,12 +7987,33 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length,if (submitted < length) { pos += submitted; length -= submitted; -if (write) +if (write) { +struct btrfs_ordered_extent *ordered; + +/* +* We have a short write, and the ordered extent is +* not split at submission time on this tree, so it +* may cover both the submitted range and the range +* that was never submitted. +* +* Truncate the ordered extent to the part that was +* submitted properly, so the never submitted range +* is reclaimed without inserting a file extent item +* for it and without marking it as an IO error. +*/ + ordered =btrfs_lookup_ordered_extent (BTRFS_I (inode), + pos); +if (ordered) { +btrfs_mark_ordered_extent_truncated (ordered, + pos - ordered->file_offset); +btrfs_put_ordered_extent (ordered); + }btrfs_mark_ordered_io_finished (BTRFS_I (inode),NULL , - pos, length, false); -else + pos, length, true); + }else {unlock_extent (&BTRFS_I (inode)->io_tree, pos, pos + length -1 ,NULL ); + } ret = -ENOTBLK ; }
@@ -8516,11 +8537,7 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset,EXTENT_LOCKED |EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, &cached_state); -spin_lock_irq (&inode->ordered_tree.lock); -set_bit (BTRFS_ORDERED_TRUNCATED , &ordered->flags); - ordered->truncated_len =min (ordered->truncated_len, - cur - ordered->file_offset); -spin_unlock_irq (&inode->ordered_tree.lock); +btrfs_mark_ordered_extent_truncated (ordered, cur - ordered->file_offset);/* * If the ordered extent has finished, we're safe to delete all
fs/btrfs/ordered-data.c +12 -0
@@ -279,6 +279,18 @@ void btrfs_add_ordered_sum(struct btrfs_ordered_extent *entry,spin_unlock_irq (&tree->lock); } +void btrfs_mark_ordered_extent_truncated (struct btrfs_ordered_extent *ordered, +u64 truncate_len) +{ +struct btrfs_inode *inode =BTRFS_I (ordered->inode); + +ASSERT (truncate_len <= ordered->num_bytes); +spin_lock_irq (&inode->ordered_tree.lock); +set_bit (BTRFS_ORDERED_TRUNCATED , &ordered->flags); + ordered->truncated_len =min (ordered->truncated_len, truncate_len); +spin_unlock_irq (&inode->ordered_tree.lock); +} +static void finish_ordered_fn (struct btrfs_work *work) {struct btrfs_ordered_extent *ordered_extent;
fs/btrfs/ordered-data.h +2 -0
@@ -209,6 +209,8 @@ void btrfs_lock_and_flush_ordered_range(struct btrfs_inode *inode, u64 start,bool btrfs_try_lock_ordered_range (struct btrfs_inode *inode,u64 start,u64 end);int btrfs_split_ordered_extent (struct btrfs_ordered_extent *ordered,u64 pre,u64 post); +void btrfs_mark_ordered_extent_truncated (struct btrfs_ordered_extent *ordered, +u64 truncate_len);int __init ordered_data_init (void );void __cold ordered_data_exit (void );
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 btrfs: fix incorrect buffered IO fallback for append direct writes
From: Qu Wenruo <[email protected]>
[ Upstream commit ff66fe6662330226b3f486014c375538d91c44aa ]
[BUG]
With the previous bug of short direct writes fixed, test case generic/362 (*) still fails with the following error with nodatasum mount option:
generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
- output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
--- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930
+++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:13:09.072485767 +0930
@@ -1,2 +1,3 @@
QA output created by 362
+Wrong file size after first write, got 8192 expected 4096
Silence is golden
...*: If the test case has been executed before with default data checksum, the failure will not reproduce. Need the following fix to make it reliably reproducible:
https://lore.kernel.org/linux-btrfs/[email protected]/
[CAUSE]
Inside btrfs_dio_iomap_begin() for a direct write, we increase the isize if it's beyond the current isize.
But if the direct io finished short, we do not revert the isize to the previous value nor to the short write end.
Then if we need to fall back to buffered writes, and the write has IOCB_APPEND flag, then the buffered write will be positioned at the incorrect isize.
The call chain looks like this:
btrfs_direct_write(pos=0, length=4K)
|- __iomap_dio_rw()
| |- iomap_iter()
| | |- btrfs_dio_iomap_begin()
| | |- btrfs_get_blocks_direct_write()
| | |- i_size_write()
| | Which updates the isize to the write end (4K).
| |
| |- iomap_dio_iter()
| | Failed with -EFAULT on the first page.
| |
| |- iomap_iter()
| | |- btrfs_dio_iomap_end()
| | Detects a short write, return -ENOTBLK
| |- if (ret == -ENOTBLK) { ret = 0;}
| Which resets the return value.
|
|- ret = iomap_dio_complet()
| Which returns 0.
|
|- btrfs_buffered_write(iocb, from); |- generic_write_checks()
|- iocb->ki_pos = i_size_read()
Which is still the new size (4K), other than the original isize 0.
[FIX]
Introduce the following btrfs_dio_data members:
- old_isize
- updated_isize
If the direct write has enlarged the isize.
Then if we got a short write, and btrfs_dio_data::updated_isize is set, revert to the correct isize based on old_isize and current file position.
And here we call i_size_write() without holding an extent lock, which is a very special case that we're safe to do:
- Only a single writer can be enlarging isize Enlarging isize will take the exclusive inode lock.
- Buffered readers need to wait for the OE we're holding Buffered readers will lock extent and wait for OE of the folio range. Sometimes we can skip the OE wait, but since all page cache is invalidated, the OE wait can not be skipped.
But I do not think this is the most elegant solution, nor covers all cases. E.g. if the bio is submitted but IO failed, we are unable to do the revert.
I believe the more elegant one would be extend the EXTENT_DIO_LOCKED lifespan for direct writes, so that we can update the isize when a write beyond EOF finished successfully.
However that change is too huge for a small bug fix. So only implement the minimal partial fix for now.
[REASON FOR NO FIXES TAG]
The bug is again very old, before commit f85781fb505e ("btrfs: switch to iomap for direct IO") we are already increasing isize without a proper rollback for short writes.
Thus only a CC to stable.
--- fs/btrfs/inode.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-)
fs/btrfs/inode.c +42 -1
@@ -63,9 +63,11 @@ struct btrfs_iget_args {struct btrfs_dio_data {ssize_t submitted; +loff_t old_isize;struct extent_changeset *data_reserved;bool data_space_reserved;bool nocow_done; +bool updated_isize; };struct btrfs_dio_private {
@@ -7591,6 +7593,7 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,bool space_reserved = false;u64 len = *lenp;u64 prev_len; +loff_t old_isize;int ret =0 ;/*
@@ -7706,8 +7709,14 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,* Need to update the i_size under the extent lock so buffered * readers will get the updated i_size when we unlock. */ -if (start + len >i_size_read (inode)) + old_isize =i_size_read (inode); +if (start + len > old_isize) { +if (!dio_data->updated_isize) { + dio_data->old_isize = old_isize; + dio_data->updated_isize = true; + }i_size_write (inode, start + len); + } out:if (ret && space_reserved) {btrfs_delalloc_release_extents (BTRFS_I (inode), len);
@@ -7990,6 +7999,38 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length,if (write) {struct btrfs_ordered_extent *ordered; +/* +* Got a short write and have updated the isize, need to +* revert the isize change. +* +* Normally we need to update isize with extent lock hold, +* but we're safe due to the following factors: +* +* - Only a single writer can be enlarging isize +* Enlarging isize will take the exclusive inode lock. +* +* - Buffered readers need to wait for the OE we're holding +* Buffered readers will lock extent and wait for OE +* of the folio range, and since page cache is invalidated +* the OE wait can not be skipped. +* +* So here we are safe to revert the isize before +* finishing the OE, and no reader of the remaining range +* can see the enlarged size. +* +* TODO: Extend the DIO_LOCKED lifespan for direct writes, +* and only enlarge isize after a successful write. +*/ +if (dio_data->updated_isize) { +u64 new_isize; + +if (submitted ==0 ) + new_isize = dio_data->old_isize; +else + new_isize =max (dio_data->old_isize, pos); +i_size_write (inode, new_isize); + dio_data->updated_isize = false; + }/* * We have a short write, and the ordered extent is * not split at submission time on this tree, so it
Signature · 1 line
2.53.0