mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/5] btrfs: data integrity fixes
@ 2026-11-03 15:05 Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Josef Bacik @ 2026-11-03 15:05 UTC (permalink / raw)
  To: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
	Qu Wenruo, Boris Burkov, Filipe Manana
  Cc: linux-btrfs, linux-kernel, stable, Zygo Blaxell, Josef Bacik

v1: https://lore.kernel.org/all/20261030-b4-btrfs-data-integrity-v1-0-f4d4196731e6@toxicpanda.com/

v1->v2:
- Rework patch 3, now "btrfs: wait for ordered extent IO in fast fsync".
  BTRFS_INODE_COW_WRITE_ERROR is set after the end IO handler clears
  writeback, so a fast fsync could still miss it, for plain write errors
  as well.  Fast fsync now waits for BTRFS_ORDERED_IO_DONE on the ordered
  extents it collected and checks BTRFS_ORDERED_IOERR, and the inode flag
  is gone (Sashiko, Qu, Filipe).
- Patch 4: use DEBUG_WARN() instead of ASSERT() for the check that can no
  longer be true (Filipe).
- Collect tags.

--- Original email (v1) ---

Hello,

I've been doing some model checking of our trickier paths with Claude's
help, and these are the fixes for the bugs it turned up that actually
lose data, or tell userspace that something is safe when it isn't.
Every one of them was reproduced in a VM on an unpatched kernel and goes
away with the fix, though the fsync and log sync ones needed a delay
injected to hit the window.

The worst one is the fstrim one.  If you run fstrim while a device
replace is running we'll discard everything that has already been copied
to the target device, because the target's alloc_state is empty until
the replace finishes, so the whole device looks unallocated.  The
replace then finishes happily, the source device goes away, and you're
left with zeroes where your metadata used to be.  This has been around
since FITRIM learned to trim unallocated space back in 4.3.

The split bio one is similarly nasty.  If the last piece of a split bio
to complete is one of the split bios and it failed, we drop the error on
the floor, and a buffered read returns success with whatever was sitting
in the page.

The rest are in our error handling.  A delalloc failure in the middle of
a folio leaves blocks dirty that no longer have an ordered extent, a
fast fsync after an error that happened before we submitted the bio can
log extents that were never written (which log replay then happily puts
in place of the data that was there before), and btrfs_sync_log() can
return success for a log transaction that never got committed.

I have a cleanup series for btrfs_sync_log coming, as I had to spend a
lot of time grokking that fix, which is a good indication that the code
is too complicated.

They're all independent of each other and tagged for stable.  This is
against for-next, and it also applies cleanly on top of Qu's
extent-by-extent writeback series.  Thanks,

Josef

---
Changes in v2:
- Link to v1: https://patch.msgid.link/20261030-b4-btrfs-data-integrity-v1-0-f4d4196731e6@toxicpanda.com

---
Josef Bacik (5):
      btrfs: don't drop the error of the last split bio
      btrfs: clear dirty blocks after a delalloc failure
      btrfs: wait for ordered extent IO in fast fsync
      btrfs: don't report success for a skipped log sync
      btrfs: don't trim the target of a device replace

 fs/btrfs/bio.c          |  9 ++++++---
 fs/btrfs/btrfs_inode.h  | 10 ----------
 fs/btrfs/extent-tree.c  |  8 ++++++++
 fs/btrfs/extent_io.c    | 10 ++++++++++
 fs/btrfs/file.c         | 52 +++++++++++++++++++++++++++++++++++++++----------
 fs/btrfs/ordered-data.c | 31 -----------------------------
 fs/btrfs/tree-log.c     | 20 +++++++++++++++++--
 7 files changed, 84 insertions(+), 56 deletions(-)
---
base-commit: 2789b2c034d7c0c8687355921a38c5ac41a565fa
change-id: 20261029-b4-btrfs-data-integrity-eb753b57300f


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v2 1/5] btrfs: don't drop the error of the last split bio
  2026-11-03 15:05 [PATCH v2 0/5] btrfs: data integrity fixes Josef Bacik
@ 2026-11-03 15:05 ` Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 2/5] btrfs: clear dirty blocks after a delalloc failure Josef Bacik
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Josef Bacik @ 2026-11-03 15:05 UTC (permalink / raw)
  To: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
	Qu Wenruo, Boris Burkov, Filipe Manana
  Cc: linux-btrfs, linux-kernel, stable, Zygo Blaxell, 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+
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Tested-by: Zygo Blaxell <ce3g8jdj@umail.furryterror.org>
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] 7+ messages in thread

* [PATCH v2 2/5] btrfs: clear dirty blocks after a delalloc failure
  2026-11-03 15:05 [PATCH v2 0/5] btrfs: data integrity fixes Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
@ 2026-11-03 15:05 ` Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync Josef Bacik
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Josef Bacik @ 2026-11-03 15:05 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+
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
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] 7+ messages in thread

* [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync
  2026-11-03 15:05 [PATCH v2 0/5] btrfs: data integrity fixes Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 2/5] btrfs: clear dirty blocks after a delalloc failure Josef Bacik
@ 2026-11-03 15:05 ` Josef Bacik
  2026-11-03 18:10   ` Filipe Manana
  2026-11-03 15:05 ` [PATCH v2 4/5] btrfs: don't report success for a skipped log sync Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 5/5] btrfs: don't trim the target of a device replace Josef Bacik
  4 siblings, 1 reply; 7+ messages in thread
From: Josef Bacik @ 2026-11-03 15:05 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

A fast fsync waits for writeback and then checks
BTRFS_INODE_COW_WRITE_ERROR to see whether a COW write failed. If one
did, it must wait for the ordered extents to complete before logging,
since until then the extent maps of the failed range point to extents
that were never written. Logging them puts unwritten extents in place
of the data that was there before when the log is replayed.

The flag can be set too late for that check. end_bbio_data_write()
clears writeback before it finishes the ordered extent:

  end_bbio_data_write()            fsync

  btrfs_folio_clear_writeback()
                                   filemap_fdatawait_range()
                                   BTRFS_INODE_COW_WRITE_ERROR clear
                                   btrfs_log_changed_extents()
  btrfs_finish_ordered_extent()
    set BTRFS_INODE_COW_WRITE_ERROR

The write paths that fail before submitting a bio, writepage_delalloc()
and submit_write_sector(), clear writeback and finish the ordered
extent through btrfs_mark_ordered_io_finished(), which never sets the
flag at all.

Fix by dropping the flag and having a fast fsync wait for
BTRFS_ORDERED_IO_DONE on the ordered extents it collected for logging.
can_finish_ordered_extent() sets it only after recording any error with
BTRFS_ORDERED_IOERR. The wait costs little, because IO_DONE is set
right after writeback is cleared, not when the ordered extent completes.

Fixes: f13e01b89daf ("btrfs: ensure fast fsync waits for ordered extents after a write failure")
Fixes: a11d6912fdd9 ("btrfs: remove folio parameter from ordered io related functions")
CC: stable@vger.kernel.org # 6.10+
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 fs/btrfs/btrfs_inode.h  | 10 ----------
 fs/btrfs/file.c         | 52 +++++++++++++++++++++++++++++++++++++++----------
 fs/btrfs/ordered-data.c | 31 -----------------------------
 3 files changed, 42 insertions(+), 51 deletions(-)

diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index f7aa0d145185..65ba8ec23258 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -89,16 +89,6 @@ enum {
 	BTRFS_INODE_FREE_SPACE_INODE,
 	/* Set when there are no capabilities in XATTs for the inode. */
 	BTRFS_INODE_NO_CAP_XATTR,
-	/*
-	 * Set if an error happened when doing a COW write before submitting a
-	 * bio or during writeback. Used for both buffered writes and direct IO
-	 * writes. This is to signal a fast fsync that it has to wait for
-	 * ordered extents to complete and therefore not log extent maps that
-	 * point to unwritten extents (when an ordered extent completes and it
-	 * has the BTRFS_ORDERED_IOERR flag set, it drops extent maps in its
-	 * range).
-	 */
-	BTRFS_INODE_COW_WRITE_ERROR,
 	/*
 	 * Set by reflink while it holds the source's VFS inode lock shared.
 	 * Direct IO writes also take that lock shared; one that finds this bit
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index f978c6524aa0..4ae9aed8993f 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1512,6 +1512,42 @@ static inline bool skip_inode_logging(const struct btrfs_log_ctx *ctx)
 	return false;
 }
 
+/*
+ * Wait for the IO of the ordered extents collected for logging to be done, and
+ * return true if a COW write among them failed.
+ *
+ * Waiting for writeback is not enough to know that: the end IO handlers clear
+ * writeback before they account the IO in the ordered extent. An ordered
+ * extent gets BTRFS_ORDERED_IO_DONE only once all of its IO is accounted, and
+ * can_finish_ordered_extent() sets BTRFS_ORDERED_IOERR before that.
+ */
+static bool cow_write_failed(struct btrfs_inode *inode,
+			     struct list_head *ordered_extents)
+{
+	struct btrfs_ordered_extent *ordered;
+	bool failed = false;
+
+	list_for_each_entry(ordered, ordered_extents, log_list)
+		wait_event(ordered->wait,
+			   test_bit(BTRFS_ORDERED_IO_DONE, &ordered->flags));
+
+	/*
+	 * can_finish_ordered_extent() records a write error before it sets
+	 * BTRFS_ORDERED_IO_DONE, both under this lock.
+	 */
+	spin_lock(&inode->ordered_tree_lock);
+	list_for_each_entry(ordered, ordered_extents, log_list) {
+		if (test_bit(BTRFS_ORDERED_IOERR, &ordered->flags) &&
+		    !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags)) {
+			failed = true;
+			break;
+		}
+	}
+	spin_unlock(&inode->ordered_tree_lock);
+
+	return failed;
+}
+
 /*
  * fsync call for both files and directories.  This logs the inode into
  * the tree log instead of forcing full commits whenever possible.
@@ -1615,7 +1651,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
 	 * IO of a lower priority task while holding a transaction open.
 	 *
 	 * For a full fsync we wait for the ordered extents to complete while
-	 * for a fast fsync we wait just for writeback to complete, and then
+	 * for a fast fsync we wait just for their IO to complete, and then
 	 * attach the ordered extents to the transaction so that a transaction
 	 * commit waits for their completion, to avoid data loss if we fsync,
 	 * the current transaction commits before the ordered extents complete
@@ -1627,7 +1663,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
 	 */
 	if (full_sync || btrfs_is_zoned(fs_info)) {
 		ret = btrfs_wait_ordered_range(inode, start, len);
-		clear_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
 	} else {
 		/*
 		 * Get our ordered extents as soon as possible to avoid doing
@@ -1640,15 +1675,12 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
 			goto out_release_extents;
 
 		/*
-		 * Check and clear the BTRFS_INODE_COW_WRITE_ERROR now after
-		 * starting and waiting for writeback, because for buffered IO
-		 * it may have been set during the end IO callback
-		 * (end_bbio_data_write() -> btrfs_finish_ordered_extent()) in
-		 * case an error happened and we need to wait for ordered
-		 * extents to complete so that any extent maps that point to
-		 * unwritten locations are dropped and we don't log them.
+		 * If a COW write failed, before submitting a bio or during IO,
+		 * the extent maps of its range point to unwritten locations
+		 * until the ordered extent completes and drops them. Wait for
+		 * that, so that we don't log them.
 		 */
-		if (test_and_clear_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags))
+		if (cow_write_failed(inode, &ctx.ordered_extents))
 			ret = btrfs_wait_ordered_range(inode, start, len);
 	}
 
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index df74c75d6c29..27aca31866fd 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -436,37 +436,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] 7+ messages in thread

* [PATCH v2 4/5] btrfs: don't report success for a skipped log sync
  2026-11-03 15:05 [PATCH v2 0/5] btrfs: data integrity fixes Josef Bacik
                   ` (2 preceding siblings ...)
  2026-11-03 15:05 ` [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync Josef Bacik
@ 2026-11-03 15:05 ` Josef Bacik
  2026-11-03 15:05 ` [PATCH v2 5/5] btrfs: don't trim the target of a device replace Josef Bacik
  4 siblings, 0 replies; 7+ messages in thread
From: Josef Bacik @ 2026-11-03 15:05 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 warn on debug builds if it is, and force
a full commit.

Fixes: d1433debe7f4 ("Btrfs: just wait or commit our own log sub-transaction")
CC: stable@vger.kernel.org
Reviewed-by: Filipe Manana <fdmanana@suse.com>
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..3de7757d168d 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)) {
+		DEBUG_WARN("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] 7+ messages in thread

* [PATCH v2 5/5] btrfs: don't trim the target of a device replace
  2026-11-03 15:05 [PATCH v2 0/5] btrfs: data integrity fixes Josef Bacik
                   ` (3 preceding siblings ...)
  2026-11-03 15:05 ` [PATCH v2 4/5] btrfs: don't report success for a skipped log sync Josef Bacik
@ 2026-11-03 15:05 ` Josef Bacik
  4 siblings, 0 replies; 7+ messages in thread
From: Josef Bacik @ 2026-11-03 15:05 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
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
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] 7+ messages in thread

* Re: [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync
  2026-11-03 15:05 ` [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync Josef Bacik
@ 2026-11-03 18:10   ` Filipe Manana
  0 siblings, 0 replies; 7+ messages in thread
From: Filipe Manana @ 2026-11-03 18:10 UTC (permalink / raw)
  To: Josef Bacik
  Cc: David Sterba, Chris Mason, Naohiro Aota, Johannes Thumshirn,
	Qu Wenruo, Boris Burkov, linux-btrfs, linux-kernel, stable

On Tue, Nov 3, 2026 at 3:57 PM Josef Bacik <josef@toxicpanda.com> wrote:
>
> A fast fsync waits for writeback and then checks
> BTRFS_INODE_COW_WRITE_ERROR to see whether a COW write failed. If one
> did, it must wait for the ordered extents to complete before logging,
> since until then the extent maps of the failed range point to extents
> that were never written. Logging them puts unwritten extents in place
> of the data that was there before when the log is replayed.
>
> The flag can be set too late for that check. end_bbio_data_write()
> clears writeback before it finishes the ordered extent:
>
>   end_bbio_data_write()            fsync
>
>   btrfs_folio_clear_writeback()
>                                    filemap_fdatawait_range()
>                                    BTRFS_INODE_COW_WRITE_ERROR clear
>                                    btrfs_log_changed_extents()
>   btrfs_finish_ordered_extent()
>     set BTRFS_INODE_COW_WRITE_ERROR
>
> The write paths that fail before submitting a bio, writepage_delalloc()
> and submit_write_sector(), clear writeback and finish the ordered
> extent through btrfs_mark_ordered_io_finished(), which never sets the
> flag at all.
>
> Fix by dropping the flag and having a fast fsync wait for
> BTRFS_ORDERED_IO_DONE on the ordered extents it collected for logging.
> can_finish_ordered_extent() sets it only after recording any error with
> BTRFS_ORDERED_IOERR. The wait costs little, because IO_DONE is set
> right after writeback is cleared, not when the ordered extent completes.
>
> Fixes: f13e01b89daf ("btrfs: ensure fast fsync waits for ordered extents after a write failure")
> Fixes: a11d6912fdd9 ("btrfs: remove folio parameter from ordered io related functions")
> CC: stable@vger.kernel.org # 6.10+
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

The idea looks sound to me.

Sashiko found something and from a quick glance it seems valid to me:

https://sashiko.dev/#/patchset/20261103-b4-btrfs-data-integrity-v2-0-577dc2690061%40toxicpanda.com

Thanks.


> ---
>  fs/btrfs/btrfs_inode.h  | 10 ----------
>  fs/btrfs/file.c         | 52 +++++++++++++++++++++++++++++++++++++++----------
>  fs/btrfs/ordered-data.c | 31 -----------------------------
>  3 files changed, 42 insertions(+), 51 deletions(-)
>
> diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
> index f7aa0d145185..65ba8ec23258 100644
> --- a/fs/btrfs/btrfs_inode.h
> +++ b/fs/btrfs/btrfs_inode.h
> @@ -89,16 +89,6 @@ enum {
>         BTRFS_INODE_FREE_SPACE_INODE,
>         /* Set when there are no capabilities in XATTs for the inode. */
>         BTRFS_INODE_NO_CAP_XATTR,
> -       /*
> -        * Set if an error happened when doing a COW write before submitting a
> -        * bio or during writeback. Used for both buffered writes and direct IO
> -        * writes. This is to signal a fast fsync that it has to wait for
> -        * ordered extents to complete and therefore not log extent maps that
> -        * point to unwritten extents (when an ordered extent completes and it
> -        * has the BTRFS_ORDERED_IOERR flag set, it drops extent maps in its
> -        * range).
> -        */
> -       BTRFS_INODE_COW_WRITE_ERROR,
>         /*
>          * Set by reflink while it holds the source's VFS inode lock shared.
>          * Direct IO writes also take that lock shared; one that finds this bit
> diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> index f978c6524aa0..4ae9aed8993f 100644
> --- a/fs/btrfs/file.c
> +++ b/fs/btrfs/file.c
> @@ -1512,6 +1512,42 @@ static inline bool skip_inode_logging(const struct btrfs_log_ctx *ctx)
>         return false;
>  }
>
> +/*
> + * Wait for the IO of the ordered extents collected for logging to be done, and
> + * return true if a COW write among them failed.
> + *
> + * Waiting for writeback is not enough to know that: the end IO handlers clear
> + * writeback before they account the IO in the ordered extent. An ordered
> + * extent gets BTRFS_ORDERED_IO_DONE only once all of its IO is accounted, and
> + * can_finish_ordered_extent() sets BTRFS_ORDERED_IOERR before that.
> + */
> +static bool cow_write_failed(struct btrfs_inode *inode,
> +                            struct list_head *ordered_extents)
> +{
> +       struct btrfs_ordered_extent *ordered;
> +       bool failed = false;
> +
> +       list_for_each_entry(ordered, ordered_extents, log_list)
> +               wait_event(ordered->wait,
> +                          test_bit(BTRFS_ORDERED_IO_DONE, &ordered->flags));
> +
> +       /*
> +        * can_finish_ordered_extent() records a write error before it sets
> +        * BTRFS_ORDERED_IO_DONE, both under this lock.
> +        */
> +       spin_lock(&inode->ordered_tree_lock);
> +       list_for_each_entry(ordered, ordered_extents, log_list) {
> +               if (test_bit(BTRFS_ORDERED_IOERR, &ordered->flags) &&
> +                   !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags)) {
> +                       failed = true;
> +                       break;
> +               }
> +       }
> +       spin_unlock(&inode->ordered_tree_lock);
> +
> +       return failed;
> +}
> +
>  /*
>   * fsync call for both files and directories.  This logs the inode into
>   * the tree log instead of forcing full commits whenever possible.
> @@ -1615,7 +1651,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
>          * IO of a lower priority task while holding a transaction open.
>          *
>          * For a full fsync we wait for the ordered extents to complete while
> -        * for a fast fsync we wait just for writeback to complete, and then
> +        * for a fast fsync we wait just for their IO to complete, and then
>          * attach the ordered extents to the transaction so that a transaction
>          * commit waits for their completion, to avoid data loss if we fsync,
>          * the current transaction commits before the ordered extents complete
> @@ -1627,7 +1663,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
>          */
>         if (full_sync || btrfs_is_zoned(fs_info)) {
>                 ret = btrfs_wait_ordered_range(inode, start, len);
> -               clear_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
>         } else {
>                 /*
>                  * Get our ordered extents as soon as possible to avoid doing
> @@ -1640,15 +1675,12 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
>                         goto out_release_extents;
>
>                 /*
> -                * Check and clear the BTRFS_INODE_COW_WRITE_ERROR now after
> -                * starting and waiting for writeback, because for buffered IO
> -                * it may have been set during the end IO callback
> -                * (end_bbio_data_write() -> btrfs_finish_ordered_extent()) in
> -                * case an error happened and we need to wait for ordered
> -                * extents to complete so that any extent maps that point to
> -                * unwritten locations are dropped and we don't log them.
> +                * If a COW write failed, before submitting a bio or during IO,
> +                * the extent maps of its range point to unwritten locations
> +                * until the ordered extent completes and drops them. Wait for
> +                * that, so that we don't log them.
>                  */
> -               if (test_and_clear_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags))
> +               if (cow_write_failed(inode, &ctx.ordered_extents))
>                         ret = btrfs_wait_ordered_range(inode, start, len);
>         }
>
> diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
> index df74c75d6c29..27aca31866fd 100644
> --- a/fs/btrfs/ordered-data.c
> +++ b/fs/btrfs/ordered-data.c
> @@ -436,37 +436,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] 7+ messages in thread

end of thread, other threads:[~2026-11-03 18:11 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-03 15:05 [PATCH v2 0/5] btrfs: data integrity fixes Josef Bacik
2026-11-03 15:05 ` [PATCH v2 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
2026-11-03 15:05 ` [PATCH v2 2/5] btrfs: clear dirty blocks after a delalloc failure Josef Bacik
2026-11-03 15:05 ` [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync Josef Bacik
2026-11-03 18:10   ` Filipe Manana
2026-11-03 15:05 ` [PATCH v2 4/5] btrfs: don't report success for a skipped log sync Josef Bacik
2026-11-03 15:05 ` [PATCH v2 5/5] btrfs: don't trim the target of a device replace Josef Bacik

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®