]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
btrfs: derive f_fsid from on-disk fsid and dev_t
authorAnand Jain <asj@kernel.org>
Mon, 27 Apr 2026 10:18:04 +0000 (18:18 +0800)
committerJohannes Thumshirn <johannes.thumshirn@wdc.com>
Tue, 9 Jun 2026 16:22:45 +0000 (18:22 +0200)
The f_fsid was originally derived from fs_devices->fsid and the
subvolume root ID. However, when temp_fsid is active, fs_devices->fsid
is randomized, making the standard derivation inconsistent.

Since metadata_uuid is optional, it is not a reliable alternative.  This
patch instead retrieves the on-disk UUID from fs_info->super_copy->fsid.

To prevent f_fsid collisions between original and cloned filesystems,
this implementation hashes the dev_t for single-device btrfs filesystems
to ensure uniqueness. This is limited to single-device filesystems as
cloned mounts are currently only supported for that configuration. Note
that f_fsid will change if the device is replaced.

Additionally, since the kernel cannot distinguish between the original
and the cloned filesystem, this new f_fsid derivation is applied to
both.

Link: https://lore.kernel.org/linux-btrfs/cover.1772095546.git.asj@kernel.org/
Link: https://lore.kernel.org/linux-btrfs/cover.1774092915.git.asj@kernel.org/
Signed-off-by: Anand Jain <asj@kernel.org>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/super.c

index faa4777119ac23f831ed5aa9e3b6ab8ae0c191cf..1a5d1c126dfd55cba3bf5f62d282193391c3747b 100644 (file)
@@ -1732,12 +1732,13 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
        u64 total_free_data = 0;
        u64 total_free_meta = 0;
        u32 bits = fs_info->sectorsize_bits;
-       __be32 *fsid = (__be32 *)fs_info->fs_devices->fsid;
+       __be32 *fsid;
        unsigned factor = 1;
        struct btrfs_block_rsv *block_rsv = &fs_info->global_block_rsv;
        int ret;
        u64 thresh = 0;
        int mixed = 0;
+       __kernel_fsid_t f_fsid;
 
        list_for_each_entry(found, &fs_info->space_info, list) {
                if (found->flags & BTRFS_BLOCK_GROUP_DATA &&
@@ -1819,14 +1820,38 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
        buf->f_bsize = fs_info->sectorsize;
        buf->f_namelen = BTRFS_NAME_LEN;
 
-       /* We treat it as constant endianness (it doesn't matter _which_)
-          because we want the fsid to come out the same whether mounted
-          on a big-endian or little-endian host */
-       buf->f_fsid.val[0] = be32_to_cpu(fsid[0]) ^ be32_to_cpu(fsid[2]);
-       buf->f_fsid.val[1] = be32_to_cpu(fsid[1]) ^ be32_to_cpu(fsid[3]);
+       /*
+        * fs_devices->fsid is dynamically generated when temp_fsid is active
+        * to support cloned filesystems. Use the original on-disk fsid instead,
+        * as it remains consistent across mount cycles.
+        */
+       if (fs_info->fs_devices->temp_fsid)
+               fsid = (__be32 *)fs_info->super_copy->fsid;
+       else
+               fsid = (__be32 *)fs_info->fs_devices->fsid;
+
+       /*
+        * We treat it as constant endianness (it doesn't matter _which_)
+        * because we want the fsid to come out the same whether mounted
+        * on a big-endian or little-endian host.
+        */
+       f_fsid.val[0] = be32_to_cpu(fsid[0]) ^ be32_to_cpu(fsid[2]);
+       f_fsid.val[1] = be32_to_cpu(fsid[1]) ^ be32_to_cpu(fsid[3]);
+
        /* Mask in the root object ID too, to disambiguate subvols */
-       buf->f_fsid.val[0] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root) >> 32;
-       buf->f_fsid.val[1] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root);
+       f_fsid.val[0] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root) >> 32;
+       f_fsid.val[1] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root);
+
+       /* Hash dev_t to avoid f_fsid collision with cloned filesystems. */
+       if (fs_info->fs_devices->total_devices == 1) {
+               __kernel_fsid_t dev_fsid =
+                       u64_to_fsid(huge_encode_dev(fs_info->fs_devices->latest_dev->bdev->bd_dev));
+
+               f_fsid.val[0] ^= dev_fsid.val[1];
+               f_fsid.val[1] ^= dev_fsid.val[0];
+       }
+
+       memcpy(&buf->f_fsid, &f_fsid, sizeof(f_fsid));
 
        return 0;
 }