btrfs: support cloned-device mount capability
authorAnand Jain <anand.jain@oracle.com>
Thu, 28 Sep 2023 01:09:47 +0000 (09:09 +0800)
committerDavid Sterba <dsterba@suse.com>
Thu, 12 Oct 2023 14:44:17 +0000 (16:44 +0200)
Guilherme's previous work [1] aimed at the mounting of cloned devices
using a superblock flag SINGLE_DEV during mkfs.
 [1] https://lore.kernel.org/linux-btrfs/20230831001544.3379273-1-gpiccoli@igalia.com/

Building upon this work, here is in memory only approach. As it mounts
we determine if the same fsid is already mounted if then we generate a
random temp fsid which shall be used the mount, in memory only not
written to the disk. We distinguish devices by devt.

Example:
  $ fallocate -l 300m ./disk1.img
  $ mkfs.btrfs -f ./disk1.img
  $ cp ./disk1.img ./disk2.img
  $ cp ./disk1.img ./disk3.img
  $ mount -o loop ./disk1.img /btrfs
  $ mount -o ./disk2.img /btrfs1
  $ mount -o ./disk3.img /btrfs2

  $ btrfs fi show -m
  Label: none  uuid: 4a212b48-1bec-46a5-938a-783c8c1f0b02
Total devices 1 FS bytes used 144.00KiB
devid    1 size 300.00MiB used 88.00MiB path /dev/loop0

  Label: none  uuid: adabf2fe-5515-4ad0-95b4-7b1609218c16
Total devices 1 FS bytes used 144.00KiB
devid    1 size 300.00MiB used 88.00MiB path /dev/loop1

  Label: none  uuid: 1d77d0df-7d92-439e-adbd-20b9b86fdedb
Total devices 1 FS bytes used 144.00KiB
devid    1 size 300.00MiB used 88.00MiB path /dev/loop2

Co-developed-by: Guilherme G. Piccoli <gpiccoli@igalia.com>
Signed-off-by: Anand Jain <anand.jain@oracle.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/disk-io.c
fs/btrfs/volumes.c
fs/btrfs/volumes.h

index 178312c2efc8b27f78c38ba5ab4605356d112e8d..ff3802986b3efd0821a4cbb8bd2e51c8990cacd3 100644 (file)
@@ -2399,7 +2399,8 @@ int btrfs_validate_super(struct btrfs_fs_info *fs_info,
                ret = -EINVAL;
        }
 
-       if (memcmp(fs_info->fs_devices->fsid, sb->fsid, BTRFS_FSID_SIZE) != 0) {
+       if (!fs_info->fs_devices->temp_fsid &&
+           memcmp(fs_info->fs_devices->fsid, sb->fsid, BTRFS_FSID_SIZE) != 0) {
                btrfs_err(fs_info,
                "superblock fsid doesn't match fsid of fs_devices: %pU != %pU",
                          sb->fsid, fs_info->fs_devices->fsid);
index 44fd866772e2afae16c769fe8ef264fab303a4e1..81b735f4efc1a41e6f28d5cb2e8ffe8612e90f8c 100644 (file)
@@ -554,17 +554,63 @@ static int btrfs_free_stale_devices(dev_t devt, struct btrfs_device *skip_device
 }
 
 static struct btrfs_fs_devices *find_fsid_by_device(
-                                       struct btrfs_super_block *disk_super)
+                                       struct btrfs_super_block *disk_super,
+                                       dev_t devt, bool *same_fsid_diff_dev)
 {
        struct btrfs_fs_devices *fsid_fs_devices;
+       struct btrfs_fs_devices *devt_fs_devices;
        const bool has_metadata_uuid = (btrfs_super_incompat_flags(disk_super) &
                                        BTRFS_FEATURE_INCOMPAT_METADATA_UUID);
+       bool found_by_devt = false;
 
        /* Find the fs_device by the usual method, if found use it. */
        fsid_fs_devices = find_fsid(disk_super->fsid,
                    has_metadata_uuid ? disk_super->metadata_uuid : NULL);
 
-       return fsid_fs_devices;
+       /* The temp_fsid feature is supported only with single device filesystem. */
+       if (btrfs_super_num_devices(disk_super) != 1)
+               return fsid_fs_devices;
+
+       /* Try to find a fs_devices by matching devt. */
+       list_for_each_entry(devt_fs_devices, &fs_uuids, fs_list) {
+               struct btrfs_device *device;
+
+               list_for_each_entry(device, &devt_fs_devices->devices, dev_list) {
+                       if (device->devt == devt) {
+                               found_by_devt = true;
+                               break;
+                       }
+               }
+               if (found_by_devt)
+                       break;
+       }
+
+       if (found_by_devt) {
+               /* Existing device. */
+               if (fsid_fs_devices == NULL) {
+                       if (devt_fs_devices->opened == 0) {
+                               /* Stale device. */
+                               return NULL;
+                       } else {
+                               /* temp_fsid is mounting a subvol. */
+                               return devt_fs_devices;
+                       }
+               } else {
+                       /* Regular or temp_fsid device mounting a subvol. */
+                       return devt_fs_devices;
+               }
+       } else {
+               /* New device. */
+               if (fsid_fs_devices == NULL) {
+                       return NULL;
+               } else {
+                       /* sb::fsid is already used create a new temp_fsid. */
+                       *same_fsid_diff_dev = true;
+                       return NULL;
+               }
+       }
+
+       /* Not reached. */
 }
 
 /*
@@ -670,6 +716,7 @@ static noinline struct btrfs_device *device_list_add(const char *path,
        u64 devid = btrfs_stack_device_id(&disk_super->dev_item);
        dev_t path_devt;
        int error;
+       bool same_fsid_diff_dev = false;
        bool has_metadata_uuid = (btrfs_super_incompat_flags(disk_super) &
                BTRFS_FEATURE_INCOMPAT_METADATA_UUID);
 
@@ -687,7 +734,7 @@ static noinline struct btrfs_device *device_list_add(const char *path,
                return ERR_PTR(error);
        }
 
-       fs_devices = find_fsid_by_device(disk_super);
+       fs_devices = find_fsid_by_device(disk_super, path_devt, &same_fsid_diff_dev);
 
        if (!fs_devices) {
                fs_devices = alloc_fs_devices(disk_super->fsid);
@@ -698,6 +745,13 @@ static noinline struct btrfs_device *device_list_add(const char *path,
                if (IS_ERR(fs_devices))
                        return ERR_CAST(fs_devices);
 
+               if (same_fsid_diff_dev) {
+                       generate_random_uuid(fs_devices->fsid);
+                       fs_devices->temp_fsid = true;
+                       pr_info("BTRFS: device %s using temp-fsid %pU\n",
+                               path, fs_devices->fsid);
+               }
+
                mutex_lock(&fs_devices->device_list_mutex);
                list_add(&fs_devices->fs_list, &fs_uuids);
 
index e485e6a3e52cccefff8cb814a2a575cd9187d83d..fef46c0dbf19611ccd733255311ae3901448572c 100644 (file)
@@ -364,6 +364,8 @@ struct btrfs_fs_devices {
        bool discardable;
        /* The filesystem is a seed filesystem. */
        bool seeding;
+       /* The mount needs to use a randomly generated fsid. */
+       bool temp_fsid;
 
        struct btrfs_fs_info *fs_info;
        /* sysfs kobjects */