* FAILED: patch "[PATCH] btrfs: fix incorrect buffered IO fallback for append direct" failed to apply to 6.18-stable tree
@ 2026-07-15 10:13 gregkh
2026-07-20 16:51 ` [PATCH 6.18.y 1/5] btrfs: concentrate the error handling of submit_one_sector() Sasha Levin
0 siblings, 1 reply; 6+ messages in thread
From: gregkh @ 2026-07-15 10:13 UTC (permalink / raw)
To: wqu; +Cc: stable
The patch below does not apply to the 6.18-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 <stable@vger.kernel.org>.
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.18.y
git checkout FETCH_HEAD
git cherry-pick -x ff66fe6662330226b3f486014c375538d91c44aa
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026071509-driveway-had-c0ca@gregkh' --subject-prefix 'PATCH 6.18.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 <wqu@suse.com>
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/20260528111659.87113-1-wqu@suse.com/
[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.
CC: stable@vger.kernel.org # 5.15+
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 88cb2e82a507..412309825d6f 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -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
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 6.18.y 1/5] btrfs: concentrate the error handling of submit_one_sector()
2026-07-15 10:13 FAILED: patch "[PATCH] btrfs: fix incorrect buffered IO fallback for append direct" failed to apply to 6.18-stable tree gregkh
@ 2026-07-20 16:51 ` Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 2/5] btrfs: replace for_each_set_bit() with for_each_set_bitmap() Sasha Levin
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Sasha Levin @ 2026-07-20 16:51 UTC (permalink / raw)
To: stable; +Cc: Qu Wenruo, Boris Burkov, David Sterba, Sasha Levin
From: Qu Wenruo <wqu@suse.com>
[ Upstream commit 44820d80026e0b509007d41c83d42f1213ee8589 ]
Currently submit_one_sector() has only one failure path from
btrfs_get_extent().
However the error handling is split into two parts, one inside
submit_one_sector(), which clears the dirty flag and finishes the
writeback for the fs block.
The other part is to submit any remaining bio inside bio_ctrl and mark
the ordered extent finished for the fs block.
There is no special reason that we must split the error handling, let's
just concentrate all the error handling into submit_one_sector().
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Stable-dep-of: ff66fe666233 ("btrfs: fix incorrect buffered IO fallback for append direct writes")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/extent_io.c | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 4b2bd3cc3ed33e..b8e2b605d52c1e 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -1599,7 +1599,7 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
/*
* Return 0 if we have submitted or queued the sector for submission.
- * Return <0 for critical errors, and the sector will have its dirty flag cleared.
+ * Return <0 for critical errors, and the involved sector will be cleaned up.
*
* Caller should make sure filepos < i_size and handle filepos >= i_size case.
*/
@@ -1623,6 +1623,13 @@ static int submit_one_sector(struct btrfs_inode *inode,
em = btrfs_get_extent(inode, NULL, filepos, sectorsize);
if (IS_ERR(em)) {
+ /*
+ * bio_ctrl may contain a bio crossing several folios.
+ * Submit it immediately so that the bio has a chance
+ * to finish normally, other than marked as error.
+ */
+ submit_one_bio(bio_ctrl);
+
/*
* When submission failed, we should still clear the folio dirty.
* Or the folio will be written back again but without any
@@ -1631,6 +1638,13 @@ static int submit_one_sector(struct btrfs_inode *inode,
btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize);
btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize);
btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize);
+
+ /*
+ * Since there is no bio submitted to finish the ordered
+ * extent, we have to manually finish this sector.
+ */
+ btrfs_mark_ordered_io_finished(inode, folio, filepos,
+ fs_info->sectorsize, false);
return PTR_ERR(em);
}
@@ -1756,19 +1770,6 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode,
}
ret = submit_one_sector(inode, folio, cur, bio_ctrl, i_size);
if (unlikely(ret < 0)) {
- /*
- * bio_ctrl may contain a bio crossing several folios.
- * Submit it immediately so that the bio has a chance
- * to finish normally, other than marked as error.
- */
- submit_one_bio(bio_ctrl);
- /*
- * Failed to grab the extent map which should be very rare.
- * Since there is no bio submitted to finish the ordered
- * extent, we have to manually finish this sector.
- */
- btrfs_mark_ordered_io_finished(inode, folio, cur,
- fs_info->sectorsize, false);
if (!found_error)
found_error = ret;
continue;
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 6.18.y 2/5] btrfs: replace for_each_set_bit() with for_each_set_bitmap()
2026-07-20 16:51 ` [PATCH 6.18.y 1/5] btrfs: concentrate the error handling of submit_one_sector() Sasha Levin
@ 2026-07-20 16:51 ` Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 3/5] btrfs: remove folio parameter from ordered io related functions Sasha Levin
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-07-20 16:51 UTC (permalink / raw)
To: stable; +Cc: Qu Wenruo, Boris Burkov, David Sterba, Sasha Levin
From: Qu Wenruo <wqu@suse.com>
[ Upstream commit e6698b34fab33867ef3faeeea6feb165f31aae24 ]
Inside extent_io.c, there are several simple call sites doing things
like:
for_each_set_bit(bit, bitmap, bitmap_size) {
/* handle one fs block */
}
The workload includes:
- set_bit()
Inside extent_writepage_io().
This can be replaced with a bitmap_set().
- btrfs_folio_set_lock()
- btrfs_mark_ordered_io_finished()
Inside writepage_delalloc().
Instead of calling it multiple times, we can pass a range into the
function with one call.
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Stable-dep-of: ff66fe666233 ("btrfs: fix incorrect buffered IO fallback for append direct writes")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/extent_io.c | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index b8e2b605d52c1e..88b5581df7dc16 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -1427,8 +1427,9 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
u64 delalloc_start = page_start;
u64 delalloc_end = page_end;
u64 delalloc_to_write = 0;
+ unsigned int start_bit;
+ unsigned int end_bit;
int ret = 0;
- int bit;
/* Save the dirty bitmap as our submission bitmap will be a subset of it. */
if (btrfs_is_subpage(fs_info, folio)) {
@@ -1438,10 +1439,12 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
bio_ctrl->submit_bitmap = 1;
}
- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, blocks_per_folio) {
- u64 start = page_start + (bit << fs_info->sectorsize_bits);
+ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap,
+ blocks_per_folio) {
+ u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
+ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits;
- btrfs_folio_set_lock(fs_info, folio, start, fs_info->sectorsize);
+ btrfs_folio_set_lock(fs_info, folio, start, len);
}
/* Lock all (subpage) delalloc ranges inside the folio first. */
@@ -1558,10 +1561,13 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
fs_info->sectorsize_bits,
blocks_per_folio);
- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, bitmap_size)
- btrfs_mark_ordered_io_finished(inode, folio,
- page_start + (bit << fs_info->sectorsize_bits),
- fs_info->sectorsize, false);
+ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap,
+ bitmap_size) {
+ u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
+ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits;
+
+ btrfs_mark_ordered_io_finished(inode, folio, start, len, false);
+ }
return ret;
}
out:
@@ -1727,8 +1733,8 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode,
return ret;
}
- for (cur = start; cur < end; cur += fs_info->sectorsize)
- set_bit((cur - folio_start) >> fs_info->sectorsize_bits, &range_bitmap);
+ bitmap_set(&range_bitmap, (start - folio_pos(folio)) >> fs_info->sectorsize_bits,
+ len >> fs_info->sectorsize_bits);
bitmap_and(&bio_ctrl->submit_bitmap, &bio_ctrl->submit_bitmap, &range_bitmap,
blocks_per_folio);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 6.18.y 3/5] btrfs: remove folio parameter from ordered io related functions
2026-07-20 16:51 ` [PATCH 6.18.y 1/5] btrfs: concentrate the error handling of submit_one_sector() Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 2/5] btrfs: replace for_each_set_bit() with for_each_set_bitmap() Sasha Levin
@ 2026-07-20 16:51 ` Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 4/5] btrfs: fix false IO failure after falling back to buffered write Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 5/5] btrfs: fix incorrect buffered IO fallback for append direct writes Sasha Levin
3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-07-20 16:51 UTC (permalink / raw)
To: stable; +Cc: Qu Wenruo, Filipe Manana, David Sterba, Sasha Levin
From: Qu Wenruo <wqu@suse.com>
[ Upstream commit a11d6912fdd9e57aff889ec97256b1d6b4e5bf06 ]
Both functions btrfs_finish_ordered_extent() and
btrfs_mark_ordered_io_finished() are accepting an optional folio
parameter.
That @folio is passed into can_finish_ordered_extent(), which later will
test and clear the ordered flag for the involved range.
However I do not think there is any other call site that can clear
ordered flags of an page cache folio and can affect
can_finish_ordered_extent().
There are limited *_clear_ordered() callers out of
can_finish_ordered_extent() function:
- btrfs_migrate_folio()
This is completely unrelated, it's just migrating the ordered flag to
the new folio.
- btrfs_cleanup_ordered_extents()
We manually clean the ordered flags of all involved folios, then call
btrfs_mark_ordered_io_finished() without a @folio parameter.
So it doesn't need and didn't pass a @folio parameter in the first
place.
- btrfs_writepage_fixup_worker()
This function is going to be removed soon, and we should not hit that
function anymore.
- btrfs_invalidate_folio()
This is the real call site we need to bother with.
If we already have a bio running, btrfs_finish_ordered_extent() in
end_bbio_data_write() will be executed first, as
btrfs_invalidate_folio() will wait for the writeback to finish.
Thus if there is a running bio, it will not see the range has
ordered flags, and just skip to the next range.
If there is no bio running, meaning the ordered extent is created but
the folio is not yet submitted.
In that case btrfs_invalidate_folio() will manually clear the folio
ordered range, but then manually finish the ordered extent with
btrfs_dec_test_ordered_pending() without bothering the folio ordered
flags.
Meaning if the OE range with folio ordered flags will be finished
manually without the need to call can_finish_ordered_extent().
This means all can_finish_ordered_extent() call sites should get a range
that has folio ordered flag set, thus the old "return false" branch
should never be triggered.
Now we can:
- Remove the @folio parameter from involved functions
* btrfs_mark_ordered_io_finished()
* btrfs_finish_ordered_extent()
For call sites passing a @folio into those functions, let them
manually clear the ordered flag of involved folios.
- Move btrfs_finish_ordered_extent() out of the loop in
end_bbio_data_write()
We only need to call btrfs_finish_ordered_extent() once per bbio,
not per folio.
- Add an ASSERT() to make sure all folio ranges have ordered flags
It's only for end_bbio_data_write().
And we already have enough safe nets to catch over-accounting of ordered
extents.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Stable-dep-of: ff66fe666233 ("btrfs: fix incorrect buffered IO fallback for append direct writes")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/compression.c | 2 +-
fs/btrfs/direct-io.c | 9 ++++-----
fs/btrfs/extent_io.c | 23 ++++++++++++++---------
fs/btrfs/inode.c | 10 ++++++----
fs/btrfs/ordered-data.c | 29 +++++------------------------
fs/btrfs/ordered-data.h | 6 ++----
6 files changed, 32 insertions(+), 47 deletions(-)
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index 8c3899832a1aa1..42ba417acc28d0 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -324,7 +324,7 @@ static void btrfs_finish_compressed_write_work(struct work_struct *work)
struct compressed_bio *cb =
container_of(work, struct compressed_bio, write_end_work);
- btrfs_finish_ordered_extent(cb->bbio.ordered, NULL, cb->start, cb->len,
+ btrfs_finish_ordered_extent(cb->bbio.ordered, cb->start, cb->len,
cb->bbio.bio.bi_status == BLK_STS_OK);
if (cb->writeback)
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 3836414cbe3716..3460a8bcbad76d 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -625,7 +625,7 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length,
pos += submitted;
length -= submitted;
if (write)
- btrfs_finish_ordered_extent(dio_data->ordered, NULL,
+ btrfs_finish_ordered_extent(dio_data->ordered,
pos, length, false);
else
btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos,
@@ -657,9 +657,8 @@ static void btrfs_dio_end_io(struct btrfs_bio *bbio)
}
if (btrfs_op(bio) == BTRFS_MAP_WRITE) {
- btrfs_finish_ordered_extent(bbio->ordered, NULL,
- dip->file_offset, dip->bytes,
- !bio->bi_status);
+ btrfs_finish_ordered_extent(bbio->ordered, dip->file_offset,
+ dip->bytes, !bio->bi_status);
} else {
btrfs_unlock_dio_extent(&inode->io_tree, dip->file_offset,
dip->file_offset + dip->bytes - 1, NULL);
@@ -735,7 +734,7 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
ret = btrfs_extract_ordered_extent(bbio, dio_data->ordered);
if (ret) {
- btrfs_finish_ordered_extent(dio_data->ordered, NULL,
+ btrfs_finish_ordered_extent(dio_data->ordered,
file_offset, dip->bytes,
!ret);
bio->bi_status = errno_to_blk_status(ret);
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 88b5581df7dc16..f955b4003d4246 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -523,6 +523,7 @@ static void end_bbio_data_write(struct btrfs_bio *bbio)
int error = blk_status_to_errno(bio->bi_status);
struct folio_iter fi;
const u32 sectorsize = fs_info->sectorsize;
+ u32 bio_size = 0;
ASSERT(!bio_flagged(bio, BIO_CLONED));
bio_for_each_folio_all(fi, bio) {
@@ -530,6 +531,7 @@ static void end_bbio_data_write(struct btrfs_bio *bbio)
u64 start = folio_pos(folio) + fi.offset;
u32 len = fi.length;
+ bio_size += len;
/* Our read/write should always be sector aligned. */
if (!IS_ALIGNED(fi.offset, sectorsize))
btrfs_err(fs_info,
@@ -540,13 +542,15 @@ static void end_bbio_data_write(struct btrfs_bio *bbio)
"incomplete page write with offset %zu and length %zu",
fi.offset, fi.length);
- btrfs_finish_ordered_extent(bbio->ordered, folio, start, len,
- !error);
if (error)
mapping_set_error(folio->mapping, error);
+
+ ASSERT(btrfs_folio_test_ordered(fs_info, folio, start, len));
+ btrfs_folio_clear_ordered(fs_info, folio, start, len);
btrfs_folio_clear_writeback(fs_info, folio, start, len);
}
+ btrfs_finish_ordered_extent(bbio->ordered, bbio->file_offset, bio_size, !error);
bio_put(bio);
}
@@ -1566,7 +1570,8 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits;
- btrfs_mark_ordered_io_finished(inode, folio, start, len, false);
+ btrfs_folio_clear_ordered(fs_info, folio, start, len);
+ btrfs_mark_ordered_io_finished(inode, start, len, false);
}
return ret;
}
@@ -1642,6 +1647,7 @@ static int submit_one_sector(struct btrfs_inode *inode,
* ordered extent.
*/
btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize);
+ btrfs_folio_clear_ordered(fs_info, folio, filepos, sectorsize);
btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize);
btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize);
@@ -1649,8 +1655,8 @@ static int submit_one_sector(struct btrfs_inode *inode,
* Since there is no bio submitted to finish the ordered
* extent, we have to manually finish this sector.
*/
- btrfs_mark_ordered_io_finished(inode, folio, filepos,
- fs_info->sectorsize, false);
+ btrfs_mark_ordered_io_finished(inode, filepos, fs_info->sectorsize,
+ false);
return PTR_ERR(em);
}
@@ -1761,8 +1767,8 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode,
spin_unlock_irqrestore(&inode->ordered_tree_lock, flags);
btrfs_put_ordered_extent(ordered);
- btrfs_mark_ordered_io_finished(inode, folio, cur,
- fs_info->sectorsize, true);
+ btrfs_folio_clear_ordered(fs_info, folio, cur, fs_info->sectorsize);
+ btrfs_mark_ordered_io_finished(inode, cur, fs_info->sectorsize, true);
/*
* This range is beyond i_size, thus we don't need to
* bother writing back.
@@ -2640,8 +2646,7 @@ void extent_write_locked_range(struct inode *inode, const struct folio *locked_f
if (IS_ERR(folio)) {
cur_end = min(round_down(cur, PAGE_SIZE) + PAGE_SIZE - 1, end);
cur_len = cur_end + 1 - cur;
- btrfs_mark_ordered_io_finished(BTRFS_I(inode), NULL,
- cur, cur_len, false);
+ btrfs_mark_ordered_io_finished(BTRFS_I(inode), cur, cur_len, false);
mapping_set_error(mapping, PTR_ERR(folio));
cur = cur_end;
continue;
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index b972d4cba98023..25b4a8fd31133f 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -424,7 +424,7 @@ static inline void btrfs_cleanup_ordered_extents(struct btrfs_inode *inode,
folio_put(folio);
}
- return btrfs_mark_ordered_io_finished(inode, NULL, offset, bytes, false);
+ return btrfs_mark_ordered_io_finished(inode, offset, bytes, false);
}
static int btrfs_dirty_inode(struct btrfs_inode *inode);
@@ -2864,7 +2864,9 @@ static void btrfs_writepage_fixup_worker(struct btrfs_work *work)
* to reflect the errors and clean the page.
*/
mapping_set_error(folio->mapping, ret);
- btrfs_mark_ordered_io_finished(inode, folio, page_start,
+ btrfs_folio_clear_ordered(fs_info, folio, page_start,
+ folio_size(folio));
+ btrfs_mark_ordered_io_finished(inode, page_start,
folio_size(folio), !ret);
folio_clear_dirty_for_io(folio);
}
@@ -7679,11 +7681,11 @@ 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);
+ spin_lock(&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);
+ spin_unlock(&inode->ordered_tree_lock);
/*
* If the ordered extent has finished, we're safe to delete all
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index 8a8aa6ed405bd4..89f6b6f8cc5312 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -348,30 +348,13 @@ static void finish_ordered_fn(struct btrfs_work *work)
}
static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
- struct folio *folio, u64 file_offset,
- u64 len, bool uptodate)
+ u64 file_offset, u64 len, bool uptodate)
{
struct btrfs_inode *inode = ordered->inode;
struct btrfs_fs_info *fs_info = inode->root->fs_info;
lockdep_assert_held(&inode->ordered_tree_lock);
- if (folio) {
- ASSERT(folio->mapping);
- ASSERT(folio_pos(folio) <= file_offset);
- ASSERT(file_offset + len <= folio_end(folio));
-
- /*
- * Ordered flag indicates whether we still have
- * pending io unfinished for the ordered extent.
- *
- * If it's not set, we need to skip to next range.
- */
- if (!btrfs_folio_test_ordered(fs_info, folio, file_offset, len))
- return false;
- btrfs_folio_clear_ordered(fs_info, folio, file_offset, len);
- }
-
/* Now we're fine to update the accounting. */
if (WARN_ON_ONCE(len > ordered->bytes_left)) {
btrfs_crit(fs_info,
@@ -413,8 +396,7 @@ static void btrfs_queue_ordered_fn(struct btrfs_ordered_extent *ordered)
}
void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
- struct folio *folio, u64 file_offset, u64 len,
- bool uptodate)
+ u64 file_offset, u64 len, bool uptodate)
{
struct btrfs_inode *inode = ordered->inode;
unsigned long flags;
@@ -423,7 +405,7 @@ void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
trace_btrfs_finish_ordered_extent(inode, file_offset, len, uptodate);
spin_lock_irqsave(&inode->ordered_tree_lock, flags);
- ret = can_finish_ordered_extent(ordered, folio, file_offset, len,
+ ret = can_finish_ordered_extent(ordered, file_offset, len,
uptodate);
spin_unlock_irqrestore(&inode->ordered_tree_lock, flags);
@@ -476,8 +458,7 @@ void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
* extent(s) covering it.
*/
void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode,
- struct folio *folio, u64 file_offset,
- u64 num_bytes, bool uptodate)
+ u64 file_offset, u64 num_bytes, bool uptodate)
{
struct rb_node *node;
struct btrfs_ordered_extent *entry = NULL;
@@ -540,7 +521,7 @@ void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode,
ASSERT(end + 1 - cur < U32_MAX);
len = end + 1 - cur;
- if (can_finish_ordered_extent(entry, folio, cur, len, uptodate)) {
+ if (can_finish_ordered_extent(entry, cur, len, uptodate)) {
spin_unlock_irqrestore(&inode->ordered_tree_lock, flags);
btrfs_queue_ordered_fn(entry);
spin_lock_irqsave(&inode->ordered_tree_lock, flags);
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index 1e6b0b182b2954..ab71c164fb0898 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -164,11 +164,9 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry);
void btrfs_remove_ordered_extent(struct btrfs_inode *btrfs_inode,
struct btrfs_ordered_extent *entry);
void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
- struct folio *folio, u64 file_offset, u64 len,
- bool uptodate);
+ u64 file_offset, u64 len, bool uptodate);
void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode,
- struct folio *folio, u64 file_offset,
- u64 num_bytes, bool uptodate);
+ u64 file_offset, u64 num_bytes, bool uptodate);
bool btrfs_dec_test_ordered_pending(struct btrfs_inode *inode,
struct btrfs_ordered_extent **cached,
u64 file_offset, u64 io_size);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 6.18.y 4/5] btrfs: fix false IO failure after falling back to buffered write
2026-07-20 16:51 ` [PATCH 6.18.y 1/5] btrfs: concentrate the error handling of submit_one_sector() Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 2/5] btrfs: replace for_each_set_bit() with for_each_set_bitmap() Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 3/5] btrfs: remove folio parameter from ordered io related functions Sasha Levin
@ 2026-07-20 16:51 ` Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 5/5] btrfs: fix incorrect buffered IO fallback for append direct writes Sasha Levin
3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-07-20 16:51 UTC (permalink / raw)
To: stable; +Cc: Qu Wenruo, Boris Burkov, David Sterba, Sasha Levin
From: Qu Wenruo <wqu@suse.com>
[ 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/20260528111659.87113-1-wqu@suse.com/
[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.
CC: stable@vger.kernel.org # 5.15+
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Stable-dep-of: ff66fe666233 ("btrfs: fix incorrect buffered IO fallback for append direct writes")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/direct-io.c | 17 ++++++++++++++---
fs/btrfs/inode.c | 6 +-----
fs/btrfs/ordered-data.c | 12 ++++++++++++
fs/btrfs/ordered-data.h | 2 ++
4 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 3460a8bcbad76d..8434674e6e5b29 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -624,12 +624,23 @@ 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) {
+ /*
+ * We have a short write, if there is any range
+ * that is submitted properly, that part will have
+ * its own OE split from the original one.
+ *
+ * So for the OE at dio_data->ordered, it's the part
+ * that is not submitted, and should be marked
+ * as fully truncated.
+ */
+ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0);
btrfs_finish_ordered_extent(dio_data->ordered,
- pos, length, false);
- else
+ pos, length, true);
+ } else {
btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos,
pos + length - 1, NULL);
+ }
ret = -ENOTBLK;
}
if (write) {
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 25b4a8fd31133f..331ae08a3c84e0 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7681,11 +7681,7 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset,
EXTENT_LOCKED | EXTENT_DO_ACCOUNTING |
EXTENT_DEFRAG, &cached_state);
- spin_lock(&inode->ordered_tree_lock);
- set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
- ordered->truncated_len = min(ordered->truncated_len,
- cur - ordered->file_offset);
- spin_unlock(&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
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index 89f6b6f8cc5312..b3ed4212bfecd6 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -339,6 +339,18 @@ void btrfs_mark_ordered_extent_error(struct btrfs_ordered_extent *ordered)
mapping_set_error(ordered->inode->vfs_inode.i_mapping, -EIO);
}
+void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
+ u64 truncate_len)
+{
+ struct btrfs_inode *inode = ordered->inode;
+
+ ASSERT(truncate_len <= ordered->num_bytes);
+ spin_lock(&inode->ordered_tree_lock);
+ set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
+ ordered->truncated_len = min(ordered->truncated_len, truncate_len);
+ spin_unlock(&inode->ordered_tree_lock);
+}
+
static void finish_ordered_fn(struct btrfs_work *work)
{
struct btrfs_ordered_extent *ordered_extent;
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index ab71c164fb0898..2e7879c6cbc779 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -220,6 +220,8 @@ bool btrfs_try_lock_ordered_range(struct btrfs_inode *inode, u64 start, u64 end,
struct btrfs_ordered_extent *btrfs_split_ordered_extent(
struct btrfs_ordered_extent *ordered, u64 len);
void btrfs_mark_ordered_extent_error(struct btrfs_ordered_extent *ordered);
+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);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 6.18.y 5/5] btrfs: fix incorrect buffered IO fallback for append direct writes
2026-07-20 16:51 ` [PATCH 6.18.y 1/5] btrfs: concentrate the error handling of submit_one_sector() Sasha Levin
` (2 preceding siblings ...)
2026-07-20 16:51 ` [PATCH 6.18.y 4/5] btrfs: fix false IO failure after falling back to buffered write Sasha Levin
@ 2026-07-20 16:51 ` Sasha Levin
3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-07-20 16:51 UTC (permalink / raw)
To: stable; +Cc: Qu Wenruo, Boris Burkov, David Sterba, Sasha Levin
From: Qu Wenruo <wqu@suse.com>
[ 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/20260528111659.87113-1-wqu@suse.com/
[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.
CC: stable@vger.kernel.org # 5.15+
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/direct-io.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 8434674e6e5b29..3091132190b6b6 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -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
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-20 16:51 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 10:13 FAILED: patch "[PATCH] btrfs: fix incorrect buffered IO fallback for append direct" failed to apply to 6.18-stable tree gregkh
2026-07-20 16:51 ` [PATCH 6.18.y 1/5] btrfs: concentrate the error handling of submit_one_sector() Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 2/5] btrfs: replace for_each_set_bit() with for_each_set_bitmap() Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 3/5] btrfs: remove folio parameter from ordered io related functions Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 4/5] btrfs: fix false IO failure after falling back to buffered write Sasha Levin
2026-07-20 16:51 ` [PATCH 6.18.y 5/5] btrfs: fix incorrect buffered IO fallback for append direct writes Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).