| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * BTRFS filesystem implementation for U-Boot |
| * |
| * 2017 Marek Behun, CZ.NIC, marek.behun@nic.cz |
| */ |
| |
| #include <malloc.h> |
| #include "ctree.h" |
| #include "btrfs.h" |
| #include "disk-io.h" |
| |
| /* |
| * Resolve the path of ino inside subvolume @root into @path_ret. |
| * |
| * @path_ret must be at least PATH_MAX size. |
| */ |
| static int get_path_in_subvol(struct btrfs_root *root, u64 ino, char *path_ret) |
| { |
| struct btrfs_path path; |
| struct btrfs_key key; |
| char *tmp; |
| u64 cur = ino; |
| int ret = 0; |
| |
| tmp = malloc(PATH_MAX); |
| if (!tmp) |
| return -ENOMEM; |
| tmp[0] = '\0'; |
| |
| btrfs_init_path(&path); |
| while (cur != BTRFS_FIRST_FREE_OBJECTID) { |
| struct btrfs_inode_ref *iref; |
| int name_len; |
| |
| btrfs_release_path(&path); |
| key.objectid = cur; |
| key.type = BTRFS_INODE_REF_KEY; |
| key.offset = (u64)-1; |
| |
| ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); |
| /* Impossible */ |
| if (ret == 0) |
| ret = -EUCLEAN; |
| if (ret < 0) |
| goto out; |
| ret = btrfs_previous_item(root, &path, cur, |
| BTRFS_INODE_REF_KEY); |
| if (ret > 0) |
| ret = -ENOENT; |
| if (ret < 0) |
| goto out; |
| |
| strncpy(tmp, path_ret, PATH_MAX); |
| iref = btrfs_item_ptr(path.nodes[0], path.slots[0], |
| struct btrfs_inode_ref); |
| name_len = btrfs_inode_ref_name_len(path.nodes[0], |
| iref); |
| if (name_len > BTRFS_NAME_LEN) { |
| ret = -ENAMETOOLONG; |
| goto out; |
| } |
| read_extent_buffer(path.nodes[0], path_ret, |
| (unsigned long)(iref + 1), name_len); |
| path_ret[name_len] = '/'; |
| path_ret[name_len + 1] = '\0'; |
| strncat(path_ret, tmp, PATH_MAX); |
| |
| btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); |
| cur = key.offset; |
| } |
| out: |
| btrfs_release_path(&path); |
| free(tmp); |
| return ret; |
| } |
| |
| static int list_one_subvol(struct btrfs_root *root, char *path_ret) |
| { |
| struct btrfs_fs_info *fs_info = root->fs_info; |
| struct btrfs_root *tree_root = fs_info->tree_root; |
| struct btrfs_path path; |
| struct btrfs_key key; |
| char *tmp; |
| u64 cur = root->root_key.objectid; |
| int ret = 0; |
| |
| tmp = malloc(PATH_MAX); |
| if (!tmp) |
| return -ENOMEM; |
| tmp[0] = '\0'; |
| path_ret[0] = '\0'; |
| btrfs_init_path(&path); |
| while (cur != BTRFS_FS_TREE_OBJECTID) { |
| struct btrfs_root_ref *rr; |
| struct btrfs_key location; |
| int name_len; |
| u64 ino; |
| |
| key.objectid = cur; |
| key.type = BTRFS_ROOT_BACKREF_KEY; |
| key.offset = (u64)-1; |
| btrfs_release_path(&path); |
| |
| ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0); |
| if (ret == 0) |
| ret = -EUCLEAN; |
| if (ret < 0) |
| goto out; |
| ret = btrfs_previous_item(tree_root, &path, cur, |
| BTRFS_ROOT_BACKREF_KEY); |
| if (ret > 0) |
| ret = -ENOENT; |
| if (ret < 0) |
| goto out; |
| |
| /* Get the subvolume name */ |
| rr = btrfs_item_ptr(path.nodes[0], path.slots[0], |
| struct btrfs_root_ref); |
| strncpy(tmp, path_ret, PATH_MAX); |
| name_len = btrfs_root_ref_name_len(path.nodes[0], rr); |
| if (name_len > BTRFS_NAME_LEN) { |
| ret = -ENAMETOOLONG; |
| goto out; |
| } |
| ino = btrfs_root_ref_dirid(path.nodes[0], rr); |
| read_extent_buffer(path.nodes[0], path_ret, |
| (unsigned long)(rr + 1), name_len); |
| path_ret[name_len] = '/'; |
| path_ret[name_len + 1] = '\0'; |
| strncat(path_ret, tmp, PATH_MAX); |
| |
| /* Get the path inside the parent subvolume */ |
| btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); |
| location.objectid = key.offset; |
| location.type = BTRFS_ROOT_ITEM_KEY; |
| location.offset = (u64)-1; |
| root = btrfs_read_fs_root(fs_info, &location); |
| if (IS_ERR(root)) { |
| ret = PTR_ERR(root); |
| goto out; |
| } |
| ret = get_path_in_subvol(root, ino, path_ret); |
| if (ret < 0) |
| goto out; |
| cur = key.offset; |
| } |
| /* Add the leading '/' */ |
| strncpy(tmp, path_ret, PATH_MAX); |
| strncpy(path_ret, "/", PATH_MAX); |
| strncat(path_ret, tmp, PATH_MAX); |
| out: |
| btrfs_release_path(&path); |
| free(tmp); |
| return ret; |
| } |
| |
| static int list_subvolums(struct btrfs_fs_info *fs_info) |
| { |
| struct btrfs_root *tree_root = fs_info->tree_root; |
| struct btrfs_root *root; |
| struct btrfs_path path; |
| struct btrfs_key key; |
| char *result; |
| int ret = 0; |
| |
| result = malloc(PATH_MAX); |
| if (!result) |
| return -ENOMEM; |
| |
| ret = list_one_subvol(fs_info->fs_root, result); |
| if (ret < 0) |
| goto out; |
| root = fs_info->fs_root; |
| printf("ID %llu gen %llu path %.*s\n", |
| root->root_key.objectid, btrfs_root_generation(&root->root_item), |
| PATH_MAX, result); |
| |
| key.objectid = BTRFS_FIRST_FREE_OBJECTID; |
| key.type = BTRFS_ROOT_ITEM_KEY; |
| key.offset = 0; |
| btrfs_init_path(&path); |
| ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0); |
| if (ret < 0) |
| goto out; |
| while (1) { |
| if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) |
| goto next; |
| |
| btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); |
| if (key.objectid > BTRFS_LAST_FREE_OBJECTID) |
| break; |
| if (key.objectid < BTRFS_FIRST_FREE_OBJECTID || |
| key.type != BTRFS_ROOT_ITEM_KEY) |
| goto next; |
| key.offset = (u64)-1; |
| root = btrfs_read_fs_root(fs_info, &key); |
| if (IS_ERR(root)) { |
| ret = PTR_ERR(root); |
| if (ret == -ENOENT) |
| goto next; |
| } |
| ret = list_one_subvol(root, result); |
| if (ret < 0) |
| goto out; |
| printf("ID %llu gen %llu path %.*s\n", |
| root->root_key.objectid, |
| btrfs_root_generation(&root->root_item), |
| PATH_MAX, result); |
| next: |
| ret = btrfs_next_item(tree_root, &path); |
| if (ret < 0) |
| goto out; |
| if (ret > 0) { |
| ret = 0; |
| break; |
| } |
| } |
| out: |
| free(result); |
| return ret; |
| } |
| |
| void btrfs_list_subvols(void) |
| { |
| struct btrfs_fs_info *fs_info = current_fs_info; |
| int ret; |
| |
| if (!fs_info) |
| return; |
| ret = list_subvolums(fs_info); |
| if (ret < 0) |
| error("failed to list subvolume: %d", ret); |
| } |