Linux Btrfs filesystem development
 help / color / mirror / Atom feed
From: Qu Wenruo <quwenruo.btrfs@gmx.com>
To: Boris Burkov <boris@bur.io>, Qu Wenruo <wqu@suse.com>
Cc: linux-btrfs@vger.kernel.org
Subject: Re: [PATCH v3] btrfs: introduce a new ioctl to force updating the device name
Date: Wed, 16 Sep 2026 07:35:06 +0930	[thread overview]
Message-ID: <5a3895c8-57a7-4ffc-87f5-7dedf758dce9@gmx.com> (raw)
In-Reply-To: <20260915191659.GB910335@zen.localdomain>



在 2026/9/16 04:46, Boris Burkov 写道:
> On Fri, Sep 11, 2026 at 05:10:28PM +0930, Qu Wenruo wrote:
>> Btrfs has a very long history fighting to determine what is the proper
>> name to show in btrfs_show_devname().
>>
>> The long history includes:
>>
>> - 7e06de7c83a7 ("btrfs: canonicalize the device path before adding it")
>>    To address a report that a very weird path can be utilized to do the
>>    mount, and btrfs saves that weird path as the device name.
>>    But such a name cannot be accessed outside of a certain pid, so the end
>>    result is that the device shown in btrfs is completely meaningless.
>>
>>    That commit tries to canonicalize the path, but it is not good enough.
>>
>> - 2e8b6bc0ab41 ("btrfs: avoid unnecessary device path update for the same device")
>>    This is to address a lot of unnecessary device path updates caused by
>>    different soft links of the same block device.
>>
>> - 8fb1dcbbcc1f ("Revert "btrfs: canonicalize the device path before adding it"")
>>    This reverts the first patch.
>>
>>    The reason is that the "/dev/" directory is not guaranteed to be a
>>    devfs, it can be a regular directory with manually populated special
>>    files, and it can even be in a different name space, which means other
>>    namespaces may not see the same special file at all.
>>
>> - 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan")
>>    This one completely gets rid of the path based device check, to avoid
>>    deadlock.
>>
>>    Since the "/dev/" is not ensured to be a devfs, one can put "/dev/"
>>    into a btrfs, and path resolution will lock the involved inode,
>>    meanwhile device scan also needs to lock that inode, which can lead to
>>    deadlock.
>>
>> - A recent report that the device name shown in mountinfo is incorrect
>>    This is another corner case where the end user is booting the kernel
>>    without an initramfs.
>>    So that at rootfs mount time, a special path "/dev/root" is passed as
>>    the device for rootfs.
>>
>>    And a later btrfs device rescan won't update the device name since the
>>    newer name points to the same devt.
>>
>> In short, unlike LVM which uses device-mapper and has a dedicated dm-*
>> device, btrfs has to directly manage the device names during mount/rescan
>> and all kinds of corner cases inside btrfs itself.
>>
>> Instead of handling all those hassles inside btrfs, push the
>> responsibility to end users, and provide a new ioctl to force updating
>> the btrfs device path.
>>
>> Now all the decisions are made by end users, they have every right to do
>> stupid things (including namespace related hassles) or to replace
>> outdated device paths with a correct one.
>>
>> With this new ioctl and corresponding btrfs-progs changes, end users
>> finally have the proper way to update the weird names shown in
>> btrfs_show_devname():
>>
>>   # cd /dev
>>   # mknod weird b 253 3
>>   # mount /dev/weird /mnt/btrfs
>>   # mount | grep weird
>>   /dev/weird on /mnt/btrfs type btrfs (rw,relatime,discard=async,space_cache=v2,subvolid=5,subvol=/)
>>   ^^^^^^^^^^ The weird name is still utilized here.
>>   # btrfs dev scan -r /dev/test/scratch1
> 
> One thing that is slightly unclear to me is what the required connection
> between /dev/weird and /dev/test/scratch1 is. I would hope that they
> must resolve to the same device for this to work / make sense.

Yes, /dev/test/scratch1 points to dm-2, which has the same major:minor 
number as /dev/weird

> 
> Can you make that relationship a little more clear in this worked
> example by including some commands defining/printing /dev/test/scratch1?
> 
>>   # dmesg -t | tail -n1
>>   BTRFS info: devid 1 device path /dev/weird changed to /dev/mapper/test-scratch1 scanned by btrfs (4068)
>>   # mount | tail -n1
>>   /dev/mapper/test-scratch1 on /mnt/btrfs type btrfs (rw,relatime,discard=async,space_cache=v2,subvolid=5,subvol=/)
>>
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>> Changelog:
>> v3:
>> - Remove the DEVICE_READY ioctl change
>>    That will break the kernel ABI, and there is really no special meaning
>>    for using _IOW other than a different ioctl number.
>>
>> v2:
>> - Add a new patch to fix an existing bug in ioctl read/write direction
>>
>> - Use the correct _IOW for the new ioctl
>>    Exposed by Sashiko, and the older wrong direction was copied from the
>>    only exception.
>> ---
>>   fs/btrfs/super.c           | 16 +++++++++++-----
>>   fs/btrfs/volumes.c         | 24 +++++++++++++++++-------
>>   fs/btrfs/volumes.h         |  6 +++++-
>>   include/uapi/linux/btrfs.h |  2 ++
>>   4 files changed, 35 insertions(+), 13 deletions(-)
>>
>> diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
>> index 464129b1b0d4..76c58a2e6d40 100644
>> --- a/fs/btrfs/super.c
>> +++ b/fs/btrfs/super.c
>> @@ -374,7 +374,7 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
>>   		struct btrfs_device *device;
>>   
>>   		mutex_lock(&uuid_mutex);
>> -		device = btrfs_scan_one_device(param->string, false);
>> +		device = btrfs_scan_one_device(param->string, 0);
>>   		mutex_unlock(&uuid_mutex);
>>   		if (IS_ERR(device))
>>   			return PTR_ERR(device);
>> @@ -1872,10 +1872,10 @@ static int btrfs_get_tree_super(struct fs_context *fc)
>>   	mutex_lock(&uuid_mutex);
>>   
>>   	/*
>> -	 * With 'true' passed to btrfs_scan_one_device() (mount time) we expect
>> +	 * With BTRFS_SCAN_DEV_MOUNT passed to btrfs_scan_one_device() we expect
>>   	 * either a valid device or an error.
>>   	 */
>> -	device = btrfs_scan_one_device(fc->source, true);
>> +	device = btrfs_scan_one_device(fc->source, BTRFS_SCAN_DEV_MOUNT);
>>   	ASSERT(device != NULL);
>>   	if (IS_ERR(device)) {
>>   		mutex_unlock(&uuid_mutex);
>> @@ -2270,7 +2270,7 @@ static long btrfs_control_ioctl(struct file *file, unsigned int cmd,
>>   		 * Scanning outside of mount can return NULL which would turn
>>   		 * into 0 error code.
>>   		 */
>> -		device = btrfs_scan_one_device(vol->name, false);
>> +		device = btrfs_scan_one_device(vol->name, 0);
>>   		ret = PTR_ERR_OR_ZERO(device);
>>   		mutex_unlock(&uuid_mutex);
>>   		break;
>> @@ -2288,7 +2288,7 @@ static long btrfs_control_ioctl(struct file *file, unsigned int cmd,
>>   		 * Scanning outside of mount can return NULL which would turn
>>   		 * into 0 error code.
>>   		 */
>> -		device = btrfs_scan_one_device(vol->name, false);
>> +		device = btrfs_scan_one_device(vol->name, 0);
>>   		if (IS_ERR_OR_NULL(device)) {
>>   			mutex_unlock(&uuid_mutex);
>>   			ret = PTR_ERR_OR_ZERO(device);
>> @@ -2298,6 +2298,12 @@ static long btrfs_control_ioctl(struct file *file, unsigned int cmd,
>>   			device->fs_devices->total_devices);
>>   		mutex_unlock(&uuid_mutex);
>>   		break;
>> +	case BTRFS_IOC_RENAME_DEV:
>> +		mutex_lock(&uuid_mutex);
> 
> What privileges does this require? We don't check CAP_SYS_ADMIN or
> anything like it, but maybe being able to read the /dev/ files implies
> sufficient privilege? Somehow this feels similar enough to ADD_DEVICE
> that it ought to require CAP_SYS_ADMIN?

Makes sense.

Rename should have a higher privileges requirement, thus CAP_SYS_ADMIN 
looks very reasonable.

> 
>> +		device = btrfs_scan_one_device(vol->name, BTRFS_SCAN_DEV_RENAME);
>> +		ret = PTR_ERR_OR_ZERO(device);
>> +		mutex_unlock(&uuid_mutex);
>> +		break;
>>   	case BTRFS_IOC_GET_SUPPORTED_FEATURES:
>>   		ret = btrfs_ioctl_get_supported_features((void __user*)arg);
>>   		break;
>> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
>> index 4ddabadc9188..268bf10ab181 100644
>> --- a/fs/btrfs/volumes.c
>> +++ b/fs/btrfs/volumes.c
>> @@ -758,7 +758,7 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb)
>>    */
>>   static noinline struct btrfs_device *device_list_add(const char *path,
>>   			   struct btrfs_super_block *disk_super,
>> -			   bool *new_device_added)
>> +			   bool *new_device_added, bool force_rename)
>>   {
>>   	struct btrfs_device *device;
>>   	struct btrfs_fs_devices *fs_devices = NULL;
>> @@ -869,7 +869,7 @@ static noinline struct btrfs_device *device_list_add(const char *path,
>>   				MAJOR(path_devt), MINOR(path_devt),
>>   				current->comm, task_pid_nr(current));
>>   
>> -	} else if (!device->name || device->devt != path_devt) {
> 
> Going back to my question above, just to be sure I undertand:
> 
> My understanding is that we get here when we found fs_devices by
> path_devt and then found device by devid. So the new force_rename is to
> use the new name when we find the device and the name is present and the
> devt matches.

Yes.

> 
> So basically we use the new name for the same maj:min by force, which is
> the intended behavior.
> 
> The part that is less clear is whether it is possible to hit any of the
> other scanning paths that modify the device by attempting the force rename
> with some different device. Like if I craft a device with the right
> UUID, devid but wrong devt,

There is an existing branch, "if (device->devt != path_devt)" for that 
situation, and we just error out without adding the new device.

Thanks,
Qu

> then call this ioctl or something. If it
> gets to *this* code, it is probably ok anyway, but if it takes some
> other path, it could be messing with the core volume logic without
> enough permissions.
> 
> Thanks,
> Boris
> 
>> +	} else if (!device->name || device->devt != path_devt || force_rename) {
>>   		const char *old_name;
>>   
>>   		/*
>> @@ -882,9 +882,14 @@ static noinline struct btrfs_device *device_list_add(const char *path,
>>   		 *         different name. or
>>   		 *      b. The missing-disk-which-was-replaced, has
>>   		 *         reappeared now.
>> +		 *      c. A device scan is triggered on different soft links
>> +		 *         of the block device.
>>   		 *
>> -		 * We must allow 1 and 2a above. But 2b would be a spurious
>> -		 * and unintentional.
>> +		 * We must allow 1 and 2a above.
>> +		 * For 2c, we should only allow it when it's triggered from
>> +		 * BTRFS_IOC_RENAME_DEV.
>> +		 *
>> +		 * But 2b would be a spurious and unintentional.
>>   		 *
>>   		 * Further in case of 1 and 2a above, the disk at 'path'
>>   		 * would have missed some transaction when it was away and
>> @@ -1443,14 +1448,19 @@ static bool btrfs_skip_registration(struct btrfs_super_block *disk_super,
>>    * and we are not allowed to call set_blocksize during the scan. The superblock
>>    * is read via pagecache.
>>    *
>> - * With @mount_arg_dev it's a scan during mount time that will always register
>> + * With BTRFS_SCAN_DEV_MOUNT it's a scan during mount time that will always register
>>    * the device or return an error. Multi-device and seeding devices are registered
>>    * in both cases.
>> + *
>> + * With BTRFS_SCAN_DEV_RENAME it's to force a device name update for a mounted
>> + * device.
>>    */
>>   struct btrfs_device *btrfs_scan_one_device(const char *path,
>> -					   bool mount_arg_dev)
>> +					   unsigned int flags)
>>   {
>>   	struct btrfs_super_block *disk_super;
>> +	bool mount_arg_dev = flags & BTRFS_SCAN_DEV_MOUNT;
>> +	bool force_rename = flags & BTRFS_SCAN_DEV_RENAME;
>>   	bool new_device_added = false;
>>   	struct btrfs_device *device = NULL;
>>   	struct file *bdev_file;
>> @@ -1489,7 +1499,7 @@ struct btrfs_device *btrfs_scan_one_device(const char *path,
>>   		goto free_disk_super;
>>   	}
>>   
>> -	device = device_list_add(path, disk_super, &new_device_added);
>> +	device = device_list_add(path, disk_super, &new_device_added, force_rename);
>>   	if (!IS_ERR(device) && new_device_added)
>>   		btrfs_free_stale_devices(device->devt, device);
>>   
>> diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h
>> index 337d7007d9e2..509e1c0ef67c 100644
>> --- a/fs/btrfs/volumes.h
>> +++ b/fs/btrfs/volumes.h
>> @@ -747,7 +747,11 @@ struct btrfs_block_group *btrfs_create_chunk(struct btrfs_trans_handle *trans,
>>   void btrfs_mapping_tree_free(struct btrfs_fs_info *fs_info);
>>   int btrfs_open_devices(struct btrfs_fs_devices *fs_devices,
>>   		       blk_mode_t flags, void *holder);
>> -struct btrfs_device *btrfs_scan_one_device(const char *path, bool mount_arg_dev);
>> +
>> +#define BTRFS_SCAN_DEV_MOUNT	(1U << 0)
>> +#define BTRFS_SCAN_DEV_RENAME	(1U << 1)
>> +struct btrfs_device *btrfs_scan_one_device(const char *path, unsigned int flags);
>> +
>>   int btrfs_forget_devices(dev_t devt);
>>   void btrfs_close_devices(struct btrfs_fs_devices *fs_devices);
>>   void btrfs_release_device_allow_freeze(struct file *bdev_file);
>> diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
>> index 0a13baf3d8d1..ea95470b1347 100644
>> --- a/include/uapi/linux/btrfs.h
>> +++ b/include/uapi/linux/btrfs.h
>> @@ -1226,6 +1226,8 @@ struct btrfs_ioctl_get_csums_args {
>>   #define BTRFS_IOC_QUOTA_RESCAN_STATUS _IOR(BTRFS_IOCTL_MAGIC, 45, \
>>   			       struct btrfs_ioctl_quota_rescan_args)
>>   #define BTRFS_IOC_QUOTA_RESCAN_WAIT _IO(BTRFS_IOCTL_MAGIC, 46)
>> +#define BTRFS_IOC_RENAME_DEV _IOW(BTRFS_IOCTL_MAGIC, 47, \
>> +				  struct btrfs_ioctl_vol_args)
>>   #define BTRFS_IOC_GET_FSLABEL 	FS_IOC_GETFSLABEL
>>   #define BTRFS_IOC_SET_FSLABEL	FS_IOC_SETFSLABEL
>>   #define BTRFS_IOC_GET_DEV_STATS _IOWR(BTRFS_IOCTL_MAGIC, 52, \
>> -- 
>> 2.55.0
>>
> 


      reply	other threads:[~2026-09-15 22:05 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-11  7:40 [PATCH v3] btrfs: introduce a new ioctl to force updating the device name Qu Wenruo
2026-09-15 19:16 ` Boris Burkov
2026-09-15 22:05   ` Qu Wenruo [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5a3895c8-57a7-4ffc-87f5-7dedf758dce9@gmx.com \
    --to=quwenruo.btrfs@gmx.com \
    --cc=boris@bur.io \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=wqu@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox