* [PATCH 1/5] btrfs: don't drop the error of the last split bio
2026-10-30 14:38 [PATCH 0/5] btrfs: data integrity fixes Josef Bacik
@ 2026-10-30 14:38 ` Josef Bacik
2026-10-30 21:03 ` Qu Wenruo
2026-10-30 14:38 ` [PATCH 2/5] btrfs: clear dirty blocks after a delalloc failure Josef Bacik
` (3 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Josef Bacik @ 2026-10-30 14:38 UTC (permalink / raw)
To: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
Qu Wenruo, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable, Josef Bacik
When a btrfs_bio had to be split, btrfs_bio_end_io() saves the first error
of any of its parts in the status field of the original btrfs_bio. The
part that completes last is supposed to load it into bi_status of the
original bio before calling the end_io handler, but that is only done if
that last part was successful:
if (atomic_dec_and_test(&bbio->pending_ios)) {
/* Load split bio's error which might be set above. */
if (status == BLK_STS_OK)
bbio->bio.bi_status = READ_ONCE(bbio->status);
If the last part failed, the assumption is that bi_status was set at the
top of the function. That is true if the last part is what is left of the
original bio. If it is a split bio, the assignment was done to the split
bio, which was freed right after. If the original bio has completed before
without an error, its bi_status is BLK_STS_OK and stays like that:
original bio (tail) split bio (head)
btrfs_bio_end_io(orig, OK)
orig->bio.bi_status = OK
pending_ios 2 -> 1
btrfs_bio_end_io(split, IOERR)
split->bio.bi_status = IOERR
bio_put(split)
cmpxchg(&orig->status, OK, IOERR)
pending_ios 1 -> 0
orig->end_io(), bi_status is OK
The end_io handler covers the whole range, so for a buffered read all the
folios are marked uptodate, including the ones that were never filled, and
read(2) returns their content instead of -EIO. For a write the ordered
extent is completed as if all the data had reached the device.
This can be reproduced with raid0 data on two devices, one of them slower
than the other and with a bad block, by reading a file whose last stripe
is on the fast device.
The saved status has the first error of all the parts, or BLK_STS_OK if
there was none, so always use it.
Fixes: d48e1dea3931 ("btrfs: fix error propagation of split bios")
CC: stable@vger.kernel.org # 6.12+
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
fs/btrfs/bio.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 2c6234f6182f..fc0b021cdefb 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -127,9 +127,12 @@ void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status)
cmpxchg(&bbio->status, BLK_STS_OK, status);
if (atomic_dec_and_test(&bbio->pending_ios)) {
- /* Load split bio's error which might be set above. */
- if (status == BLK_STS_OK)
- bbio->bio.bi_status = READ_ONCE(bbio->status);
+ /*
+ * Load the first error, if any. It may come from a split bio,
+ * including the one we were called for, in which case nothing
+ * stored it in the original bio yet.
+ */
+ bbio->bio.bi_status = READ_ONCE(bbio->status);
if (bbio_has_ordered_extent(bbio)) {
struct btrfs_ordered_extent *ordered = bbio->ordered;
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH 1/5] btrfs: don't drop the error of the last split bio
2026-10-30 14:38 ` [PATCH 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
@ 2026-10-30 21:03 ` Qu Wenruo
0 siblings, 0 replies; 10+ messages in thread
From: Qu Wenruo @ 2026-10-30 21:03 UTC (permalink / raw)
To: Josef Bacik, David Sterba, Chris Mason, Naohiro Aota,
Johannes Thumshirn, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable
在 2026/10/31 01:08, Josef Bacik 写道:
> When a btrfs_bio had to be split, btrfs_bio_end_io() saves the first error
> of any of its parts in the status field of the original btrfs_bio. The
> part that completes last is supposed to load it into bi_status of the
> original bio before calling the end_io handler, but that is only done if
> that last part was successful:
>
> if (atomic_dec_and_test(&bbio->pending_ios)) {
> /* Load split bio's error which might be set above. */
> if (status == BLK_STS_OK)
> bbio->bio.bi_status = READ_ONCE(bbio->status);
>
> If the last part failed, the assumption is that bi_status was set at the
> top of the function. That is true if the last part is what is left of the
> original bio. If it is a split bio, the assignment was done to the split
> bio, which was freed right after. If the original bio has completed before
> without an error, its bi_status is BLK_STS_OK and stays like that:
>
> original bio (tail) split bio (head)
>
> btrfs_bio_end_io(orig, OK)
> orig->bio.bi_status = OK
> pending_ios 2 -> 1
> btrfs_bio_end_io(split, IOERR)
> split->bio.bi_status = IOERR
> bio_put(split)
> cmpxchg(&orig->status, OK, IOERR)
> pending_ios 1 -> 0
> orig->end_io(), bi_status is OK
>
> The end_io handler covers the whole range, so for a buffered read all the
> folios are marked uptodate, including the ones that were never filled, and
> read(2) returns their content instead of -EIO. For a write the ordered
> extent is completed as if all the data had reached the device.
>
> This can be reproduced with raid0 data on two devices, one of them slower
> than the other and with a bad block, by reading a file whose last stripe
> is on the fast device.
>
> The saved status has the first error of all the parts, or BLK_STS_OK if
> there was none, so always use it.
>
> Fixes: d48e1dea3931 ("btrfs: fix error propagation of split bios")
> CC: stable@vger.kernel.org # 6.12+
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Thanks,
Qu
> ---
> fs/btrfs/bio.c | 9 ++++++---
> 1 file changed, 6 insertions(+), 3 deletions(-)
>
> diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
> index 2c6234f6182f..fc0b021cdefb 100644
> --- a/fs/btrfs/bio.c
> +++ b/fs/btrfs/bio.c
> @@ -127,9 +127,12 @@ void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status)
> cmpxchg(&bbio->status, BLK_STS_OK, status);
>
> if (atomic_dec_and_test(&bbio->pending_ios)) {
> - /* Load split bio's error which might be set above. */
> - if (status == BLK_STS_OK)
> - bbio->bio.bi_status = READ_ONCE(bbio->status);
> + /*
> + * Load the first error, if any. It may come from a split bio,
> + * including the one we were called for, in which case nothing
> + * stored it in the original bio yet.
> + */
> + bbio->bio.bi_status = READ_ONCE(bbio->status);
>
> if (bbio_has_ordered_extent(bbio)) {
> struct btrfs_ordered_extent *ordered = bbio->ordered;
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 2/5] btrfs: clear dirty blocks after a delalloc failure
2026-10-30 14:38 [PATCH 0/5] btrfs: data integrity fixes Josef Bacik
2026-10-30 14:38 ` [PATCH 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
@ 2026-10-30 14:38 ` Josef Bacik
2026-10-30 20:57 ` Qu Wenruo
2026-10-30 14:38 ` [PATCH 3/5] btrfs: make fsync wait after pre-submission errors Josef Bacik
` (2 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Josef Bacik @ 2026-10-30 14:38 UTC (permalink / raw)
To: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
Qu Wenruo, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable, Josef Bacik
If writepage_delalloc() runs the first delalloc ranges of a folio and
then btrfs_run_delalloc_range() fails, the blocks that already got an
ordered extent are finished with an error through
btrfs_mark_ordered_io_finished(), but their dirty bits are left set. The
next writeback then submits blocks whose ordered extent is gone, or is
about to be completed a second time.
Fix by clearing the dirty bits and cycling the writeback state for those
blocks, as submit_write_sector() does for a block it fails to submit.
Fixes: 72dad8e377af ("btrfs: fix double accounting race when btrfs_run_delalloc_range() failed")
CC: stable@vger.kernel.org # 6.14+
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
fs/btrfs/extent_io.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 5ff630630490..3283835ccb5e 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -1746,6 +1746,16 @@ 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;
+ /*
+ * The blocks are not going to be submitted, so they
+ * must not stay dirty. Otherwise they are written back
+ * later without an ordered extent, or worse, with this
+ * one if it is still around, and invalidating the folio
+ * would account for them again.
+ */
+ btrfs_folio_clear_dirty(fs_info, folio, start, len);
+ btrfs_folio_set_writeback(fs_info, folio, start, len);
+ btrfs_folio_clear_writeback(fs_info, folio, start, len);
btrfs_mark_ordered_io_finished(inode, start, len, false);
}
return ret;
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH 2/5] btrfs: clear dirty blocks after a delalloc failure
2026-10-30 14:38 ` [PATCH 2/5] btrfs: clear dirty blocks after a delalloc failure Josef Bacik
@ 2026-10-30 20:57 ` Qu Wenruo
0 siblings, 0 replies; 10+ messages in thread
From: Qu Wenruo @ 2026-10-30 20:57 UTC (permalink / raw)
To: Josef Bacik, David Sterba, Chris Mason, Naohiro Aota,
Johannes Thumshirn, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable
在 2026/10/31 01:08, Josef Bacik 写道:
> If writepage_delalloc() runs the first delalloc ranges of a folio and
> then btrfs_run_delalloc_range() fails, the blocks that already got an
> ordered extent are finished with an error through
> btrfs_mark_ordered_io_finished(), but their dirty bits are left set. The
> next writeback then submits blocks whose ordered extent is gone, or is
> about to be completed a second time.
>
> Fix by clearing the dirty bits and cycling the writeback state for those
> blocks, as submit_write_sector() does for a block it fails to submit.
>
> Fixes: 72dad8e377af ("btrfs: fix double accounting race when btrfs_run_delalloc_range() failed")
> CC: stable@vger.kernel.org # 6.14+
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Thanks,
Qu
> ---
> fs/btrfs/extent_io.c | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
> index 5ff630630490..3283835ccb5e 100644
> --- a/fs/btrfs/extent_io.c
> +++ b/fs/btrfs/extent_io.c
> @@ -1746,6 +1746,16 @@ 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;
>
> + /*
> + * The blocks are not going to be submitted, so they
> + * must not stay dirty. Otherwise they are written back
> + * later without an ordered extent, or worse, with this
> + * one if it is still around, and invalidating the folio
> + * would account for them again.
> + */
> + btrfs_folio_clear_dirty(fs_info, folio, start, len);
> + btrfs_folio_set_writeback(fs_info, folio, start, len);
> + btrfs_folio_clear_writeback(fs_info, folio, start, len);
> btrfs_mark_ordered_io_finished(inode, start, len, false);
> }
> return ret;
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 3/5] btrfs: make fsync wait after pre-submission errors
2026-10-30 14:38 [PATCH 0/5] btrfs: data integrity fixes Josef Bacik
2026-10-30 14:38 ` [PATCH 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
2026-10-30 14:38 ` [PATCH 2/5] btrfs: clear dirty blocks after a delalloc failure Josef Bacik
@ 2026-10-30 14:38 ` Josef Bacik
2026-10-30 21:06 ` Qu Wenruo
2026-10-30 14:38 ` [PATCH 4/5] btrfs: don't report success for a skipped log sync Josef Bacik
2026-10-30 14:38 ` [PATCH 5/5] btrfs: don't trim the target of a device replace Josef Bacik
4 siblings, 1 reply; 10+ messages in thread
From: Josef Bacik @ 2026-10-30 14:38 UTC (permalink / raw)
To: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
Qu Wenruo, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable, Josef Bacik
Commit f13e01b89daf ("btrfs: ensure fast fsync waits for ordered extents
after a write failure") made btrfs_finish_ordered_extent() set
BTRFS_INODE_COW_WRITE_ERROR in the inode when a COW write fails. The flag
makes the next fast fsync wait for the completion of the ordered extents,
which is where the extent maps that point to unwritten extents are
dropped. Otherwise the fsync logs file extent items for them, which is a
corruption if the log is replayed.
As the comment there says, this is also needed for errors that happen
before a bio is submitted. But for buffered writes those don't go through
btrfs_finish_ordered_extent(). When running delalloc fails for a folio
that already got ordered extents, or a block can't be submitted,
writepage_delalloc(), submit_write_sector(), extent_write_locked_range()
and btrfs_cleanup_ordered_extents() call btrfs_mark_ordered_io_finished(),
which marks the ordered extent with BTRFS_ORDERED_IOERR and queues its
completion without setting the flag:
writeback (or a first fsync) fsync
writepage_delalloc()
cow_file_range() for range A
creates extent map and ordered
extent
cow_file_range() for range B fails
btrfs_mark_ordered_io_finished(A)
queues the completion
no dirty folios, no writeback
flag is not set, does not wait
btrfs_log_changed_extents()
logs the extent map of A
btrfs_sync_log()
btrfs_finish_one_ordered()
drops the extent map
frees the reserved extent
If the error was already reported, for example to a previous fsync that
triggered the writeback, this fsync even returns success. After a power
failure, replaying the log replaces data that was durable before with the
unwritten extent, for which there are no checksums:
BTRFS warning: csum hole found for disk bytenr range [14434304, 14438400)
BTRFS warning: csum failed root 5 ino 267 off 0 csum 0x8941f998 expected
csum 0x00000000 mirror 1
Both functions share can_finish_ordered_extent(), so set the flag there.
Fixes: f13e01b89daf ("btrfs: ensure fast fsync waits for ordered extents after a write failure")
CC: stable@vger.kernel.org # 6.10+
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
fs/btrfs/ordered-data.c | 62 ++++++++++++++++++++++++-------------------------
1 file changed, 31 insertions(+), 31 deletions(-)
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index df74c75d6c29..6543e99055f2 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -401,6 +401,37 @@ static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
if (!uptodate)
btrfs_mark_ordered_extent_error(ordered);
+ /*
+ * If this is a COW write it means we created new extent maps for the
+ * range and they point to unwritten locations if we got an error either
+ * before submitting a bio or during IO.
+ *
+ * We have marked the ordered extent with BTRFS_ORDERED_IOERR, and we
+ * are queuing its completion once all its IO is done. During completion,
+ * at btrfs_finish_one_ordered(), we will drop the extent maps for the
+ * unwritten extents.
+ *
+ * However because completion runs in a work queue we can end up having
+ * a fast fsync running before that. In the case of direct IO, once we
+ * unlock the inode the fsync might start, and we queue the completion
+ * before unlocking the inode. In the case of buffered IO when writeback
+ * finishes (end_bbio_data_write()) we queue the completion, so if the
+ * writeback was triggered by a fast fsync, the fsync might start
+ * logging before ordered extent completion runs in the work queue.
+ *
+ * The fast fsync will log file extent items based on the extent maps it
+ * finds, so if by the time it collects extent maps the ordered extent
+ * completion didn't happen yet, it will log file extent items that
+ * point to unwritten extents, resulting in a corruption if a crash
+ * happens and the log tree is replayed. Note that a fast fsync does not
+ * wait for completion of ordered extents in order to reduce latency.
+ *
+ * Set a flag in the inode so that the next fast fsync will wait for
+ * ordered extents to complete before starting to log.
+ */
+ if (!uptodate && !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags))
+ set_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
+
if (ordered->bytes_left)
return false;
@@ -436,37 +467,6 @@ void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
uptodate);
spin_unlock(&inode->ordered_tree_lock);
- /*
- * If this is a COW write it means we created new extent maps for the
- * range and they point to unwritten locations if we got an error either
- * before submitting a bio or during IO.
- *
- * We have marked the ordered extent with BTRFS_ORDERED_IOERR, and we
- * are queuing its completion below. During completion, at
- * btrfs_finish_one_ordered(), we will drop the extent maps for the
- * unwritten extents.
- *
- * However because completion runs in a work queue we can end up having
- * a fast fsync running before that. In the case of direct IO, once we
- * unlock the inode the fsync might start, and we queue the completion
- * before unlocking the inode. In the case of buffered IO when writeback
- * finishes (end_bbio_data_write()) we queue the completion, so if the
- * writeback was triggered by a fast fsync, the fsync might start
- * logging before ordered extent completion runs in the work queue.
- *
- * The fast fsync will log file extent items based on the extent maps it
- * finds, so if by the time it collects extent maps the ordered extent
- * completion didn't happen yet, it will log file extent items that
- * point to unwritten extents, resulting in a corruption if a crash
- * happens and the log tree is replayed. Note that a fast fsync does not
- * wait for completion of ordered extents in order to reduce latency.
- *
- * Set a flag in the inode so that the next fast fsync will wait for
- * ordered extents to complete before starting to log.
- */
- if (!uptodate && !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags))
- set_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
-
if (ret)
btrfs_queue_ordered_fn(ordered);
}
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH 3/5] btrfs: make fsync wait after pre-submission errors
2026-10-30 14:38 ` [PATCH 3/5] btrfs: make fsync wait after pre-submission errors Josef Bacik
@ 2026-10-30 21:06 ` Qu Wenruo
0 siblings, 0 replies; 10+ messages in thread
From: Qu Wenruo @ 2026-10-30 21:06 UTC (permalink / raw)
To: Josef Bacik, David Sterba, Chris Mason, Naohiro Aota,
Johannes Thumshirn, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable
在 2026/10/31 01:08, Josef Bacik 写道:
> Commit f13e01b89daf ("btrfs: ensure fast fsync waits for ordered extents
> after a write failure") made btrfs_finish_ordered_extent() set
> BTRFS_INODE_COW_WRITE_ERROR in the inode when a COW write fails. The flag
> makes the next fast fsync wait for the completion of the ordered extents,
> which is where the extent maps that point to unwritten extents are
> dropped. Otherwise the fsync logs file extent items for them, which is a
> corruption if the log is replayed.
>
> As the comment there says, this is also needed for errors that happen
> before a bio is submitted. But for buffered writes those don't go through
> btrfs_finish_ordered_extent(). When running delalloc fails for a folio
> that already got ordered extents, or a block can't be submitted,
> writepage_delalloc(), submit_write_sector(), extent_write_locked_range()
> and btrfs_cleanup_ordered_extents() call btrfs_mark_ordered_io_finished(),
> which marks the ordered extent with BTRFS_ORDERED_IOERR and queues its
> completion without setting the flag:
>
> writeback (or a first fsync) fsync
>
> writepage_delalloc()
> cow_file_range() for range A
> creates extent map and ordered
> extent
> cow_file_range() for range B fails
> btrfs_mark_ordered_io_finished(A)
> queues the completion
> no dirty folios, no writeback
> flag is not set, does not wait
> btrfs_log_changed_extents()
> logs the extent map of A
> btrfs_sync_log()
> btrfs_finish_one_ordered()
> drops the extent map
> frees the reserved extent
>
> If the error was already reported, for example to a previous fsync that
> triggered the writeback, this fsync even returns success. After a power
> failure, replaying the log replaces data that was durable before with the
> unwritten extent, for which there are no checksums:
>
> BTRFS warning: csum hole found for disk bytenr range [14434304, 14438400)
> BTRFS warning: csum failed root 5 ino 267 off 0 csum 0x8941f998 expected
> csum 0x00000000 mirror 1
>
> Both functions share can_finish_ordered_extent(), so set the flag there.
Sashiko reported a critical level problem, you may want to check it:
https://sashiko.dev/#/patchset/20261030-b4-btrfs-data-integrity-v1-0-f4d4196731e6%40toxicpanda.com
Thanks,
Qu
>
> Fixes: f13e01b89daf ("btrfs: ensure fast fsync waits for ordered extents after a write failure")
> CC: stable@vger.kernel.org # 6.10+
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> ---
> fs/btrfs/ordered-data.c | 62 ++++++++++++++++++++++++-------------------------
> 1 file changed, 31 insertions(+), 31 deletions(-)
>
> diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
> index df74c75d6c29..6543e99055f2 100644
> --- a/fs/btrfs/ordered-data.c
> +++ b/fs/btrfs/ordered-data.c
> @@ -401,6 +401,37 @@ static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
> if (!uptodate)
> btrfs_mark_ordered_extent_error(ordered);
>
> + /*
> + * If this is a COW write it means we created new extent maps for the
> + * range and they point to unwritten locations if we got an error either
> + * before submitting a bio or during IO.
> + *
> + * We have marked the ordered extent with BTRFS_ORDERED_IOERR, and we
> + * are queuing its completion once all its IO is done. During completion,
> + * at btrfs_finish_one_ordered(), we will drop the extent maps for the
> + * unwritten extents.
> + *
> + * However because completion runs in a work queue we can end up having
> + * a fast fsync running before that. In the case of direct IO, once we
> + * unlock the inode the fsync might start, and we queue the completion
> + * before unlocking the inode. In the case of buffered IO when writeback
> + * finishes (end_bbio_data_write()) we queue the completion, so if the
> + * writeback was triggered by a fast fsync, the fsync might start
> + * logging before ordered extent completion runs in the work queue.
> + *
> + * The fast fsync will log file extent items based on the extent maps it
> + * finds, so if by the time it collects extent maps the ordered extent
> + * completion didn't happen yet, it will log file extent items that
> + * point to unwritten extents, resulting in a corruption if a crash
> + * happens and the log tree is replayed. Note that a fast fsync does not
> + * wait for completion of ordered extents in order to reduce latency.
> + *
> + * Set a flag in the inode so that the next fast fsync will wait for
> + * ordered extents to complete before starting to log.
> + */
> + if (!uptodate && !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags))
> + set_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
> +
> if (ordered->bytes_left)
> return false;
>
> @@ -436,37 +467,6 @@ void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
> uptodate);
> spin_unlock(&inode->ordered_tree_lock);
>
> - /*
> - * If this is a COW write it means we created new extent maps for the
> - * range and they point to unwritten locations if we got an error either
> - * before submitting a bio or during IO.
> - *
> - * We have marked the ordered extent with BTRFS_ORDERED_IOERR, and we
> - * are queuing its completion below. During completion, at
> - * btrfs_finish_one_ordered(), we will drop the extent maps for the
> - * unwritten extents.
> - *
> - * However because completion runs in a work queue we can end up having
> - * a fast fsync running before that. In the case of direct IO, once we
> - * unlock the inode the fsync might start, and we queue the completion
> - * before unlocking the inode. In the case of buffered IO when writeback
> - * finishes (end_bbio_data_write()) we queue the completion, so if the
> - * writeback was triggered by a fast fsync, the fsync might start
> - * logging before ordered extent completion runs in the work queue.
> - *
> - * The fast fsync will log file extent items based on the extent maps it
> - * finds, so if by the time it collects extent maps the ordered extent
> - * completion didn't happen yet, it will log file extent items that
> - * point to unwritten extents, resulting in a corruption if a crash
> - * happens and the log tree is replayed. Note that a fast fsync does not
> - * wait for completion of ordered extents in order to reduce latency.
> - *
> - * Set a flag in the inode so that the next fast fsync will wait for
> - * ordered extents to complete before starting to log.
> - */
> - if (!uptodate && !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags))
> - set_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
> -
> if (ret)
> btrfs_queue_ordered_fn(ordered);
> }
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 4/5] btrfs: don't report success for a skipped log sync
2026-10-30 14:38 [PATCH 0/5] btrfs: data integrity fixes Josef Bacik
` (2 preceding siblings ...)
2026-10-30 14:38 ` [PATCH 3/5] btrfs: make fsync wait after pre-submission errors Josef Bacik
@ 2026-10-30 14:38 ` Josef Bacik
2026-10-30 14:38 ` [PATCH 5/5] btrfs: don't trim the target of a device replace Josef Bacik
4 siblings, 0 replies; 10+ messages in thread
From: Josef Bacik @ 2026-10-30 14:38 UTC (permalink / raw)
To: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
Qu Wenruo, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable, Josef Bacik
When a commit of the log root tree gives up, because the transaction
needs a full commit or writing the log failed, btrfs_sync_log() still
advances log_transid_committed and hands the result to the contexts on
the list. Unlike a successful commit it does not advance log_transid, so
the log transaction it just retired is still the one new syncs join.
A sync that gets to the log root tree after that adds its context to
the emptied list, sees log_transid_committed >= its log_transid and
returns its log_ret, which is still 0:
fsync A (subvol 1) rename fsync B (subvol 2)
btrfs_need_log_full_commit()
-> false
btrfs_set_log_full_commit()
btrfs_need_log_full_commit()
-> true
btrfs_remove_all_log_ctxs()
log_transid_committed = N
log_transid stays N
log_transid = N, log_ret = 0
log_transid_committed >= N
return 0
btrfs_sync_file() then ends the transaction without committing it, and a
crash before A's transaction commit loses what B was told is durable.
This was reproduced with delays added to btrfs_sync_log().
Fix by advancing log_transid when a commit of the log root tree gives
up, so a later sync joins the next log transaction and either commits it
or gives up itself. The check for an already committed log transaction
can then no longer be true, so assert that it isn't, and still force a
full commit if it is.
Fixes: d1433debe7f4 ("Btrfs: just wait or commit our own log sub-transaction")
CC: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
fs/btrfs/tree-log.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 1d9dfb63fc5a..b542b1d97989 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -3437,11 +3437,20 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans,
goto out;
}
- if (log_root_tree->log_transid_committed >= root_log_ctx.log_transid) {
+ /*
+ * A commit of the log root tree moves log_transid on even when it
+ * gives up, so the log transaction we just joined can't be done.
+ */
+ if (unlikely(log_root_tree->log_transid_committed >=
+ root_log_ctx.log_transid)) {
+ ASSERT(0, "log_transid_committed=%d log_transid=%d",
+ log_root_tree->log_transid_committed,
+ root_log_ctx.log_transid);
blk_finish_plug(&plug);
list_del_init(&root_log_ctx.list);
+ btrfs_wait_tree_log_extents(log, mark);
mutex_unlock(&log_root_tree->log_mutex);
- ret = root_log_ctx.log_ret;
+ ret = BTRFS_LOG_FORCE_COMMIT;
trace_btrfs_sync_log_exit(trans, root, ctx, ret);
goto out;
}
@@ -3575,6 +3584,13 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans,
mutex_lock(&log_root_tree->log_mutex);
btrfs_remove_all_log_ctxs(log_root_tree, index2, ret);
+ /*
+ * If the commit gave up, log_transid still names the log transaction
+ * we are retiring. Move it on, so that nobody joins a log transaction
+ * whose result has already been handed out.
+ */
+ if (log_root_tree->log_transid == root_log_ctx.log_transid)
+ log_root_tree->log_transid++;
log_root_tree->log_transid_committed++;
log_root_tree->log_commit[index2] = false;
mutex_unlock(&log_root_tree->log_mutex);
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 5/5] btrfs: don't trim the target of a device replace
2026-10-30 14:38 [PATCH 0/5] btrfs: data integrity fixes Josef Bacik
` (3 preceding siblings ...)
2026-10-30 14:38 ` [PATCH 4/5] btrfs: don't report success for a skipped log sync Josef Bacik
@ 2026-10-30 14:38 ` Josef Bacik
2026-10-30 21:02 ` Qu Wenruo
4 siblings, 1 reply; 10+ messages in thread
From: Josef Bacik @ 2026-10-30 14:38 UTC (permalink / raw)
To: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
Qu Wenruo, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable, Josef Bacik
FITRIM also discards the space of every device that is not allocated to a
chunk. btrfs_trim_free_extents() goes over fs_devices->devices and, for
each device that is writeable, discards the ranges that have neither
CHUNK_ALLOCATED nor CHUNK_TRIMMED set in device->alloc_state.
The target device of a running replace operation is on that list and it is
writeable. But its alloc_state is empty. The allocated ranges of the
source device are only copied to it by btrfs_set_target_alloc_state(),
when the replace finishes. Until then the whole device looks unallocated,
so an fstrim discards everything that was copied to it so far, and what is
being written to it in parallel to the source device.
The replace finishes without any error and the source device is dropped.
With a single device filesystem, 700M of data and fstrim called in a loop
while the replace runs:
BTRFS info: dev_replace from /dev/vdb (devid 1) to /dev/vdc finished
BTRFS error: bad tree block start, mirror 1 want 30638080 have 0
BTRFS error: bad tree block start, mirror 2 want 30638080 have 0
BTRFS info: scrub: not finished on devid 1 with status: -5
Skip the target device. The flag is cleared with the device list mutex
held, after the alloc_state was set up, and the trimming code holds that
mutex while it works on a device.
Fixes: 499f377f49f0 ("btrfs: iterate over unused chunk space in FITRIM")
CC: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
fs/btrfs/extent-tree.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c
index 9b0a5c6b96f3..22155507f905 100644
--- a/fs/btrfs/extent-tree.c
+++ b/fs/btrfs/extent-tree.c
@@ -6641,6 +6641,14 @@ static int btrfs_trim_free_extents_throttle(struct btrfs_device *device,
if (!test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state) || !device->bdev)
return 0;
+ /*
+ * The target of a running device replace has nothing in its alloc_state
+ * until the replace finishes, all of it would look unallocated and we
+ * would discard what was already copied to it.
+ */
+ if (test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state))
+ return 0;
+
/* Discard not supported = nothing to do. */
if (!bdev_max_discard_sectors(device->bdev))
return 0;
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH 5/5] btrfs: don't trim the target of a device replace
2026-10-30 14:38 ` [PATCH 5/5] btrfs: don't trim the target of a device replace Josef Bacik
@ 2026-10-30 21:02 ` Qu Wenruo
0 siblings, 0 replies; 10+ messages in thread
From: Qu Wenruo @ 2026-10-30 21:02 UTC (permalink / raw)
To: Josef Bacik, David Sterba, Chris Mason, Naohiro Aota,
Johannes Thumshirn, Boris Burkov, Filipe Manana
Cc: linux-btrfs, linux-kernel, stable
在 2026/10/31 01:08, Josef Bacik 写道:
> FITRIM also discards the space of every device that is not allocated to a
> chunk. btrfs_trim_free_extents() goes over fs_devices->devices and, for
> each device that is writeable, discards the ranges that have neither
> CHUNK_ALLOCATED nor CHUNK_TRIMMED set in device->alloc_state.
>
> The target device of a running replace operation is on that list and it is
> writeable. But its alloc_state is empty. The allocated ranges of the
> source device are only copied to it by btrfs_set_target_alloc_state(),
> when the replace finishes. Until then the whole device looks unallocated,
> so an fstrim discards everything that was copied to it so far, and what is
> being written to it in parallel to the source device.
>
> The replace finishes without any error and the source device is dropped.
> With a single device filesystem, 700M of data and fstrim called in a loop
> while the replace runs:
>
> BTRFS info: dev_replace from /dev/vdb (devid 1) to /dev/vdc finished
> BTRFS error: bad tree block start, mirror 1 want 30638080 have 0
> BTRFS error: bad tree block start, mirror 2 want 30638080 have 0
> BTRFS info: scrub: not finished on devid 1 with status: -5
>
> Skip the target device. The flag is cleared with the device list mutex
> held, after the alloc_state was set up, and the trimming code holds that
> mutex while it works on a device.
>
> Fixes: 499f377f49f0 ("btrfs: iterate over unused chunk space in FITRIM")
> CC: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Thanks,
Qu
> ---
> fs/btrfs/extent-tree.c | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c
> index 9b0a5c6b96f3..22155507f905 100644
> --- a/fs/btrfs/extent-tree.c
> +++ b/fs/btrfs/extent-tree.c
> @@ -6641,6 +6641,14 @@ static int btrfs_trim_free_extents_throttle(struct btrfs_device *device,
> if (!test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state) || !device->bdev)
> return 0;
>
> + /*
> + * The target of a running device replace has nothing in its alloc_state
> + * until the replace finishes, all of it would look unallocated and we
> + * would discard what was already copied to it.
> + */
> + if (test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state))
> + return 0;
> +
> /* Discard not supported = nothing to do. */
> if (!bdev_max_discard_sectors(device->bdev))
> return 0;
>
^ permalink raw reply [flat|nested] 10+ messages in thread