All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2] btrfs: properly cleanup replace_task when the replace failed to start
@ 2026-08-12 23:14 Qu Wenruo
  2026-08-14 10:16 ` Johannes Thumshirn
  2026-08-19 12:28 ` Jeff Layton
  0 siblings, 2 replies; 5+ messages in thread
From: Qu Wenruo @ 2026-08-12 23:14 UTC (permalink / raw)
  To: linux-btrfs

In the function btrfs_dev_replace_start(), we have several error paths
that assigns replace_task without reverting it back to NULL.

There are two involved error paths:

- There is already a running dev-replace
  Then replace_task is over-written to the current task.
  This is the one with long running effect.

- The btrfs_start_transaction() call failed
  This is much harder to hit though.

This can result the replace_task check inside btrfs_map_block() to be
incorrectly triggered, not taking dev_replace->rwsem, and may get an
incorrect/stale view on replace related structures.

Thankfully this bug is very hard to hit.

As dev-replace is an exclusive operation, thus if there is already
a running replace, a new one will be rejected early without reaching
btrfs_dev_replace_start().

The only remaining case is a suspended replace, which is much harder to
hit, e.g. requiring async dev-replace conflicting with another exclusive
operation, then a new replace is started.

Fix the problem by:

- Moving the replace_task assignment after replace_state check in
  btrfs_dev_replace_start()

- Reset replace_task to NULL if btrfs_start_transaction() failed
  in btrfs_dev_replace_start()

- Reset replace_task to NULL for all paths of
  btrfs_dev_replace_finishing()

This is reported by Sashiko, which found the existing bug during review
of another patch, and since the bug is an existing one, it's not shown in
the summary, but only in the detail page.

Link: https://sashiko.dev/#/patchset/tencent_853134544C3CE88A219EEB21346E2510D308%40qq.com
Fixes: 8cca35cb29f8 ("btrfs: don't take dev_replace rwsem on task already holding it")
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
Changelog:
v2:
- Fix incorrect member names in the commit message

- Update the commit message to show that the bug is very hard to hit
  Mostly rejected by the exclusive operation, which rejects new replace
  early.

- Update btrfs_dev_replace_finishing() to reset replace_task
---
 fs/btrfs/dev-replace.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c
index 72cba7fed942..c5e67524b417 100644
--- a/fs/btrfs/dev-replace.c
+++ b/fs/btrfs/dev-replace.c
@@ -633,7 +633,6 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
 		goto leave;
 
 	down_write(&dev_replace->rwsem);
-	dev_replace->replace_task = current;
 	switch (dev_replace->replace_state) {
 	case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
 	case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
@@ -647,6 +646,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
 		goto leave;
 	}
 
+	dev_replace->replace_task = current;
 	dev_replace->cont_reading_from_srcdev_mode = read_src;
 	dev_replace->srcdev = src_device;
 	dev_replace->tgtdev = tgt_device;
@@ -693,6 +693,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
 			BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED;
 		dev_replace->srcdev = NULL;
 		dev_replace->tgtdev = NULL;
+		dev_replace->replace_task = NULL;
 		up_write(&dev_replace->rwsem);
 		goto leave;
 	}
@@ -874,18 +875,20 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
 	/* don't allow cancel or unmount to disturb the finishing procedure */
 	mutex_lock(&dev_replace->lock_finishing_cancel_unmount);
 
-	down_read(&dev_replace->rwsem);
+	down_write(&dev_replace->rwsem);
+	dev_replace->replace_task = NULL;
+
 	/* was the operation canceled, or is it finished? */
 	if (dev_replace->replace_state !=
 	    BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
-		up_read(&dev_replace->rwsem);
+		up_write(&dev_replace->rwsem);
 		mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
 		return 0;
 	}
 
 	tgt_device = dev_replace->tgtdev;
 	src_device = dev_replace->srcdev;
-	up_read(&dev_replace->rwsem);
+	up_write(&dev_replace->rwsem);
 
 	/*
 	 * flush all outstanding I/O and inode extent mappings before the
@@ -986,8 +989,6 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
 
 	list_add(&tgt_device->dev_alloc_list, &fs_devices->alloc_list);
 	fs_devices->rw_devices++;
-
-	dev_replace->replace_task = NULL;
 	up_write(&dev_replace->rwsem);
 	btrfs_rm_dev_replace_blocked(fs_info);
 
-- 
2.54.0


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

end of thread, other threads:[~2026-08-20 11:28 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 23:14 [PATCH v2] btrfs: properly cleanup replace_task when the replace failed to start Qu Wenruo
2026-08-14 10:16 ` Johannes Thumshirn
2026-08-19 12:28 ` Jeff Layton
2026-08-19 22:14   ` Qu Wenruo
2026-08-20 11:28     ` Jeff Layton

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.