// SPDX-License-Identifier: GPL-2.0+ /* * BTRFS filesystem implementation for U-Boot * * 2017 Marek Behun, CZ.NIC, marek.behun@nic.cz */ #include "btrfs.h" #include u64 btrfs_lookup_inode_ref(struct btrfs_root *root, u64 inr, struct btrfs_inode_ref *refp, char *name) { struct btrfs_path path; struct btrfs_key *key; struct btrfs_inode_ref *ref; u64 res = -1ULL; key = btrfs_search_tree_key_type(root, inr, BTRFS_INODE_REF_KEY, &path); if (!key) return -1ULL; ref = btrfs_path_item_ptr(&path, struct btrfs_inode_ref); btrfs_inode_ref_to_cpu(ref); if (refp) *refp = *ref; if (name) { if (ref->name_len > BTRFS_NAME_MAX) { printf("%s: inode name too long: %u\n", __func__, ref->name_len); goto out; } memcpy(name, ref + 1, ref->name_len); } res = key->offset; out: btrfs_free_path(&path); return res; } int btrfs_lookup_inode(const struct btrfs_root *root, struct btrfs_key *location, struct btrfs_inode_item *item, struct btrfs_root *new_root) { struct btrfs_root tmp_root = *root; struct btrfs_path path; int res = -1; if (location->type == BTRFS_ROOT_ITEM_KEY) { if (btrfs_find_root(location->objectid, &tmp_root, NULL)) return -1; location->objectid = tmp_root.root_dirid; location->type = BTRFS_INODE_ITEM_KEY; location->offset = 0; } if (btrfs_search_tree(&tmp_root, location, &path)) return res; if (btrfs_comp_keys(location, btrfs_path_leaf_key(&path))) goto out; if (item) { *item = *btrfs_path_item_ptr(&path, struct btrfs_inode_item); btrfs_inode_item_to_cpu(item); } if (new_root) *new_root = tmp_root; res = 0; out: btrfs_free_path(&path); return res; } int btrfs_readlink(const struct btrfs_root *root, u64 inr, char *target) { struct btrfs_path path; struct btrfs_key key; struct btrfs_file_extent_item *extent; const char *data_ptr; int res = -1; key.objectid = inr; key.type = BTRFS_EXTENT_DATA_KEY; key.offset = 0; if (btrfs_search_tree(root, &key, &path)) return -1; if (btrfs_comp_keys(&key, btrfs_path_leaf_key(&path))) goto out; extent = btrfs_path_item_ptr(&path, struct btrfs_file_extent_item); if (extent->type != BTRFS_FILE_EXTENT_INLINE) { printf("%s: Extent for symlink %llu not of INLINE type\n", __func__, inr); goto out; } btrfs_file_extent_item_to_cpu_inl(extent); if (extent->compression != BTRFS_COMPRESS_NONE) { printf("%s: Symlink %llu extent data compressed!\n", __func__, inr); goto out; } else if (extent->encryption != 0) { printf("%s: Symlink %llu extent data encrypted!\n", __func__, inr); goto out; } else if (extent->ram_bytes >= btrfs_info.sb.sectorsize) { printf("%s: Symlink %llu extent data too long (%llu)!\n", __func__, inr, extent->ram_bytes); goto out; } data_ptr = (const char *) extent + offsetof(struct btrfs_file_extent_item, disk_bytenr); memcpy(target, data_ptr, extent->ram_bytes); target[extent->ram_bytes] = '\0'; res = 0; out: btrfs_free_path(&path); return res; } /* inr must be a directory (for regular files with multiple hard links this function returns only one of the parents of the file) */ static u64 get_parent_inode(struct btrfs_root *root, u64 inr, struct btrfs_inode_item *inode_item) { struct btrfs_key key; u64 res; if (inr == BTRFS_FIRST_FREE_OBJECTID) { if (root->objectid != btrfs_info.fs_root.objectid) { u64 parent; struct btrfs_root_ref ref; parent = btrfs_lookup_root_ref(root->objectid, &ref, NULL); if (parent == -1ULL) return -1ULL; if (btrfs_find_root(parent, root, NULL)) return -1ULL; inr = ref.dirid; } if (inode_item) { key.objectid = inr; key.type = BTRFS_INODE_ITEM_KEY; key.offset = 0; if (btrfs_lookup_inode(root, &key, inode_item, NULL)) return -1ULL; } return inr; } res = btrfs_lookup_inode_ref(root, inr, NULL, NULL); if (res == -1ULL) return -1ULL; if (inode_item) { key.objectid = res; key.type = BTRFS_INODE_ITEM_KEY; key.offset = 0; if (btrfs_lookup_inode(root, &key, inode_item, NULL)) return -1ULL; } return res; } static inline int next_length(const char *path) { int res = 0; while (*path != '\0' && *path != '/' && res <= BTRFS_NAME_LEN) ++res, ++path; return res; } static inline const char *skip_current_directories(const char *cur) { while (1) { if (cur[0] == '/') ++cur; else if (cur[0] == '.' && cur[1] == '/') cur += 2; else break; } return cur; } u64 btrfs_lookup_path(struct btrfs_root *root, u64 inr, const char *path, u8 *type_p, struct btrfs_inode_item *inode_item_p, int symlink_limit) { struct btrfs_dir_item item; struct btrfs_inode_item inode_item; u8 type = BTRFS_FT_DIR; int len, have_inode = 0; const char *cur = path; if (*cur == '/') { ++cur; inr = root->root_dirid; } do { cur = skip_current_directories(cur); len = next_length(cur); if (len > BTRFS_NAME_LEN) { printf("%s: Name too long at \"%.*s\"\n", __func__, BTRFS_NAME_LEN, cur); return -1ULL; } if (len == 1 && cur[0] == '.') break; if (len == 2 && cur[0] == '.' && cur[1] == '.') { cur += 2; inr = get_parent_inode(root, inr, &inode_item); if (inr == -1ULL) return -1ULL; type = BTRFS_FT_DIR; continue; } if (!*cur) break; if (btrfs_lookup_dir_item(root, inr, cur, len, &item)) return -1ULL; type = item.type; have_inode = 1; if (btrfs_lookup_inode(root, &item.location, &inode_item, root)) return -1ULL; if (item.type == BTRFS_FT_SYMLINK && symlink_limit >= 0) { char *target; if (!symlink_limit) { printf("%s: Too much symlinks!\n", __func__); return -1ULL; } target = malloc(min(inode_item.size + 1, (u64) btrfs_info.sb.sectorsize)); if (!target) return -1ULL; if (btrfs_readlink(root, item.location.objectid, target)) { free(target); return -1ULL; } inr = btrfs_lookup_path(root, inr, target, &type, &inode_item, symlink_limit - 1); free(target); if (inr == -1ULL) return -1ULL; } else if (item.type != BTRFS_FT_DIR && cur[len]) { printf("%s: \"%.*s\" not a directory\n", __func__, (int) (cur - path + len), path); return -1ULL; } else { inr = item.location.objectid; } cur += len; } while (*cur); if (type_p) *type_p = type; if (inode_item_p) { if (!have_inode) { struct btrfs_key key; key.objectid = inr; key.type = BTRFS_INODE_ITEM_KEY; key.offset = 0; if (btrfs_lookup_inode(root, &key, &inode_item, NULL)) return -1ULL; } *inode_item_p = inode_item; } return inr; } u64 btrfs_file_read(const struct btrfs_root *root, u64 inr, u64 offset, u64 size, char *buf) { struct btrfs_path path; struct btrfs_key key; struct btrfs_file_extent_item *extent; int res = 0; u64 rd, rd_all = -1ULL; key.objectid = inr; key.type = BTRFS_EXTENT_DATA_KEY; key.offset = offset; if (btrfs_search_tree(root, &key, &path)) return -1ULL; if (btrfs_comp_keys(&key, btrfs_path_leaf_key(&path)) < 0) { if (btrfs_prev_slot(&path)) goto out; if (btrfs_comp_keys_type(&key, btrfs_path_leaf_key(&path))) goto out; } rd_all = 0; do { if (btrfs_comp_keys_type(&key, btrfs_path_leaf_key(&path))) break; extent = btrfs_path_item_ptr(&path, struct btrfs_file_extent_item); if (extent->type == BTRFS_FILE_EXTENT_INLINE) { btrfs_file_extent_item_to_cpu_inl(extent); rd = btrfs_read_extent_inline(&path, extent, offset, size, buf); } else { btrfs_file_extent_item_to_cpu(extent); rd = btrfs_read_extent_reg(&path, extent, offset, size, buf); } if (rd == -1ULL) { printf("%s: Error reading extent\n", __func__); rd_all = -1; goto out; } offset = 0; buf += rd; rd_all += rd; size -= rd; if (!size) break; } while (!(res = btrfs_next_slot(&path))); if (res) return -1ULL; out: btrfs_free_path(&path); return rd_all; }