]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
btrfs: allow mounting filesystems with remap-tree incompat flag
authorMark Harmstone <mark@harmstone.com>
Wed, 7 Jan 2026 14:09:08 +0000 (14:09 +0000)
committerDavid Sterba <dsterba@suse.com>
Tue, 3 Feb 2026 06:54:35 +0000 (07:54 +0100)
If we encounter a filesystem with the remap-tree incompat flag set,
validate its compatibility with the other flags, and load the remap tree
using the values that have been added to the superblock.

The remap-tree feature depends on the free-space-tree, but no-holes and
block-group-tree have been made dependencies to reduce the testing
matrix. Similarly I'm not aware of any reason why mixed-bg and zoned would be
incompatible with remap-tree, but this is blocked for the time being
until it can be fully tested.

Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Mark Harmstone <mark@harmstone.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/Kconfig
fs/btrfs/accessors.h
fs/btrfs/disk-io.c
fs/btrfs/extent-tree.c
fs/btrfs/fs.h
fs/btrfs/transaction.c
include/uapi/linux/btrfs_tree.h

index 423122786a93466793fb58f0fb287b5d27c77f0a..ede184b6eda161e65ee606a679e9f522c8bef6e2 100644 (file)
@@ -116,4 +116,6 @@ config BTRFS_EXPERIMENTAL
 
          - asynchronous checksum generation for data writes
 
+         - remap-tree - logical address remapping tree
+
          If unsure, say N.
index 9797f9e8d4e5c2c2c0f5af907dade2face87fdbf..8938357fcb40b677cfcc656b99261a0d476859ce 100644 (file)
@@ -883,6 +883,12 @@ BTRFS_SETGET_STACK_FUNCS(super_uuid_tree_generation, struct btrfs_super_block,
                         uuid_tree_generation, 64);
 BTRFS_SETGET_STACK_FUNCS(super_nr_global_roots, struct btrfs_super_block,
                         nr_global_roots, 64);
+BTRFS_SETGET_STACK_FUNCS(super_remap_root, struct btrfs_super_block,
+                        remap_root, 64);
+BTRFS_SETGET_STACK_FUNCS(super_remap_root_generation, struct btrfs_super_block,
+                        remap_root_generation, 64);
+BTRFS_SETGET_STACK_FUNCS(super_remap_root_level, struct btrfs_super_block,
+                        remap_root_level, 8);
 
 /* struct btrfs_file_extent_item */
 BTRFS_SETGET_STACK_FUNCS(stack_file_extent_type, struct btrfs_file_extent_item,
index cd46b9d85880a4b68db54b69b11fb4a2b1a56dba..c69734c74c26ba6003c1b8550a1da4c7770903ef 100644 (file)
@@ -1136,6 +1136,8 @@ static struct btrfs_root *btrfs_get_global_root(struct btrfs_fs_info *fs_info,
                return btrfs_grab_root(btrfs_global_root(fs_info, &key));
        case BTRFS_RAID_STRIPE_TREE_OBJECTID:
                return btrfs_grab_root(fs_info->stripe_root);
+       case BTRFS_REMAP_TREE_OBJECTID:
+               return btrfs_grab_root(fs_info->remap_root);
        default:
                return NULL;
        }
@@ -1226,6 +1228,7 @@ void btrfs_free_fs_info(struct btrfs_fs_info *fs_info)
        btrfs_put_root(fs_info->data_reloc_root);
        btrfs_put_root(fs_info->block_group_root);
        btrfs_put_root(fs_info->stripe_root);
+       btrfs_put_root(fs_info->remap_root);
        btrfs_check_leaked_roots(fs_info);
        btrfs_extent_buffer_leak_debug_check(fs_info);
        kfree(fs_info->super_copy);
@@ -1778,6 +1781,7 @@ static void free_root_pointers(struct btrfs_fs_info *info, bool free_chunk_root)
        free_root_extent_buffers(info->data_reloc_root);
        free_root_extent_buffers(info->block_group_root);
        free_root_extent_buffers(info->stripe_root);
+       free_root_extent_buffers(info->remap_root);
        if (free_chunk_root)
                free_root_extent_buffers(info->chunk_root);
 }
@@ -2191,21 +2195,44 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info)
        if (ret)
                goto out;
 
-       /*
-        * This tree can share blocks with some other fs tree during relocation
-        * and we need a proper setup by btrfs_get_fs_root
-        */
-       root = btrfs_get_fs_root(tree_root->fs_info,
-                                BTRFS_DATA_RELOC_TREE_OBJECTID, true);
-       if (IS_ERR(root)) {
-               if (!btrfs_test_opt(fs_info, IGNOREBADROOTS)) {
-                       location.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID;
-                       ret = PTR_ERR(root);
-                       goto out;
+       if (btrfs_fs_incompat(fs_info, REMAP_TREE)) {
+               /* The remap_root has already been loaded in load_important_roots(). */
+               root = fs_info->remap_root;
+
+               set_bit(BTRFS_ROOT_TRACK_DIRTY, &root->state);
+
+               root->root_key.objectid = BTRFS_REMAP_TREE_OBJECTID;
+               root->root_key.type = BTRFS_ROOT_ITEM_KEY;
+               root->root_key.offset = 0;
+
+               /* Check that data reloc tree doesn't also exist. */
+               location.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID;
+               root = btrfs_read_tree_root(fs_info->tree_root, &location);
+               if (!IS_ERR(root)) {
+                       btrfs_err(fs_info, "data reloc tree exists when remap-tree enabled");
+                       btrfs_put_root(root);
+                       return -EIO;
+               } else if (PTR_ERR(root) != -ENOENT) {
+                       btrfs_warn(fs_info, "error %ld when checking for data reloc tree",
+                                  PTR_ERR(root));
                }
        } else {
-               set_bit(BTRFS_ROOT_TRACK_DIRTY, &root->state);
-               fs_info->data_reloc_root = root;
+               /*
+                * This tree can share blocks with some other fs tree during
+                * relocation and we need a proper setup by btrfs_get_fs_root().
+                */
+               root = btrfs_get_fs_root(tree_root->fs_info,
+                                        BTRFS_DATA_RELOC_TREE_OBJECTID, true);
+               if (IS_ERR(root)) {
+                       if (!btrfs_test_opt(fs_info, IGNOREBADROOTS)) {
+                               location.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID;
+                               ret = PTR_ERR(root);
+                               goto out;
+                       }
+               } else {
+                       set_bit(BTRFS_ROOT_TRACK_DIRTY, &root->state);
+                       fs_info->data_reloc_root = root;
+               }
        }
 
        location.objectid = BTRFS_QUOTA_TREE_OBJECTID;
@@ -2445,6 +2472,35 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info,
                ret = -EINVAL;
        }
 
+       if (btrfs_fs_incompat(fs_info, REMAP_TREE)) {
+               /*
+                * Reduce test matrix for remap tree by requiring block-group-tree
+                * and no-holes. Free-space-tree is a hard requirement.
+                */
+               if (!btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID) ||
+                   !btrfs_fs_incompat(fs_info, NO_HOLES) ||
+                   !btrfs_fs_compat_ro(fs_info, BLOCK_GROUP_TREE)) {
+                       btrfs_err(fs_info,
+"remap-tree feature requires free-space-tree, no-holes, and block-group-tree");
+                       ret = -EINVAL;
+               }
+
+               if (btrfs_fs_incompat(fs_info, MIXED_GROUPS)) {
+                       btrfs_err(fs_info, "remap-tree not supported with mixed-bg");
+                       ret = -EINVAL;
+               }
+
+               if (btrfs_fs_incompat(fs_info, ZONED)) {
+                       btrfs_err(fs_info, "remap-tree not supported with zoned devices");
+                       ret = -EINVAL;
+               }
+
+               if (sectorsize > PAGE_SIZE) {
+                       btrfs_err(fs_info, "remap-tree not supported when block size > page size");
+                       ret = -EINVAL;
+               }
+       }
+
        /*
         * Hint to catch really bogus numbers, bitflips or so, more exact checks are
         * done later
@@ -2603,6 +2659,18 @@ static int load_important_roots(struct btrfs_fs_info *fs_info)
                btrfs_warn(fs_info, "couldn't read tree root");
                return ret;
        }
+
+       if (btrfs_fs_incompat(fs_info, REMAP_TREE)) {
+               bytenr = btrfs_super_remap_root(sb);
+               gen = btrfs_super_remap_root_generation(sb);
+               level = btrfs_super_remap_root_level(sb);
+               ret = load_super_root(fs_info->remap_root, bytenr, gen, level);
+               if (ret) {
+                       btrfs_warn(fs_info, "couldn't read remap root");
+                       return ret;
+               }
+       }
+
        return 0;
 }
 
@@ -3231,6 +3299,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device
        struct btrfs_fs_info *fs_info = btrfs_sb(sb);
        struct btrfs_root *tree_root;
        struct btrfs_root *chunk_root;
+       struct btrfs_root *remap_root;
        int ret;
        int level;
 
@@ -3365,6 +3434,16 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device
        if (ret < 0)
                goto fail_alloc;
 
+       if (btrfs_super_incompat_flags(disk_super) & BTRFS_FEATURE_INCOMPAT_REMAP_TREE) {
+               remap_root = btrfs_alloc_root(fs_info, BTRFS_REMAP_TREE_OBJECTID,
+                                             GFP_KERNEL);
+               fs_info->remap_root = remap_root;
+               if (!remap_root) {
+                       ret = -ENOMEM;
+                       goto fail_alloc;
+               }
+       }
+
        /*
         * At this point our mount options are validated, if we set ->max_inline
         * to something non-standard make sure we truncate it to sectorsize.
index 48a453fa3063c7c39c314a6ce76120e43411caed..ce4bda1f37adbfc57d1914ce779e93a59fd12629 100644 (file)
@@ -2593,6 +2593,8 @@ static u64 get_alloc_profile_by_root(struct btrfs_root *root, int data)
                flags = BTRFS_BLOCK_GROUP_DATA;
        else if (root == fs_info->chunk_root)
                flags = BTRFS_BLOCK_GROUP_SYSTEM;
+       else if (root == fs_info->remap_root)
+               flags = BTRFS_BLOCK_GROUP_METADATA_REMAP;
        else
                flags = BTRFS_BLOCK_GROUP_METADATA;
 
index 195428ecfd75d7f624295361b5383b346fe63f15..13b0aa0b9da972629afc0765c0b8c3c1fef817d3 100644 (file)
@@ -315,7 +315,8 @@ enum {
 #define BTRFS_FEATURE_INCOMPAT_SUPP            \
        (BTRFS_FEATURE_INCOMPAT_SUPP_STABLE |   \
         BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE | \
-        BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2)
+        BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 | \
+        BTRFS_FEATURE_INCOMPAT_REMAP_TREE)
 
 #else
 
@@ -475,6 +476,7 @@ struct btrfs_fs_info {
        struct btrfs_root *data_reloc_root;
        struct btrfs_root *block_group_root;
        struct btrfs_root *stripe_root;
+       struct btrfs_root *remap_root;
 
        /* The log root tree is a directory of all the other log roots */
        struct btrfs_root *log_root_tree;
index e2f993b1783fad2b16c923669bc9e8dbd5594ae1..f4cc9e1a1b93fa24e0cff65324286d213aef28f3 100644 (file)
@@ -1967,6 +1967,13 @@ static void update_super_roots(struct btrfs_fs_info *fs_info)
                super->cache_generation = 0;
        if (test_bit(BTRFS_FS_UPDATE_UUID_TREE_GEN, &fs_info->flags))
                super->uuid_tree_generation = root_item->generation;
+
+       if (btrfs_fs_incompat(fs_info, REMAP_TREE)) {
+               root_item = &fs_info->remap_root->root_item;
+               super->remap_root = root_item->bytenr;
+               super->remap_root_generation = root_item->generation;
+               super->remap_root_level = root_item->level;
+       }
 }
 
 int btrfs_transaction_blocked(struct btrfs_fs_info *info)
index 86820a9644e87ba73b2c9be85fbd31904f80511d..f7843e6bb978dcc93972de57f5140e2965d6f719 100644 (file)
@@ -721,9 +721,12 @@ struct btrfs_super_block {
        __u8 metadata_uuid[BTRFS_FSID_SIZE];
 
        __u64 nr_global_roots;
+       __le64 remap_root;
+       __le64 remap_root_generation;
+       __u8 remap_root_level;
 
        /* Future expansion */
-       __le64 reserved[27];
+       __u8 reserved[199];
        __u8 sys_chunk_array[BTRFS_SYSTEM_CHUNK_ARRAY_SIZE];
        struct btrfs_root_backup super_roots[BTRFS_NUM_BACKUP_ROOTS];