diff options
Diffstat (limited to 'roms/u-boot/fs/btrfs/inode.c')
-rw-r--r-- | roms/u-boot/fs/btrfs/inode.c | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/roms/u-boot/fs/btrfs/inode.c b/roms/u-boot/fs/btrfs/inode.c new file mode 100644 index 000000000..2c2379303 --- /dev/null +++ b/roms/u-boot/fs/btrfs/inode.c @@ -0,0 +1,767 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * BTRFS filesystem implementation for U-Boot + * + * 2017 Marek Behun, CZ.NIC, marek.behun@nic.cz + */ + +#include <linux/kernel.h> +#include <malloc.h> +#include <memalign.h> +#include "btrfs.h" +#include "disk-io.h" +#include "volumes.h" + +/* + * Read the content of symlink inode @ino of @root, into @target. + * NOTE: @target will not be \0 termiated, caller should handle it properly. + * + * Return the number of read data. + * Return <0 for error. + */ +int btrfs_readlink(struct btrfs_root *root, u64 ino, char *target) +{ + struct btrfs_path path; + struct btrfs_key key; + struct btrfs_file_extent_item *fi; + int ret; + + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = 0; + btrfs_init_path(&path); + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return ret; + if (ret > 0) { + ret = -ENOENT; + goto out; + } + fi = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_file_extent_item); + if (btrfs_file_extent_type(path.nodes[0], fi) != + BTRFS_FILE_EXTENT_INLINE) { + ret = -EUCLEAN; + error("Extent for symlink %llu must be INLINE type!", ino); + goto out; + } + if (btrfs_file_extent_compression(path.nodes[0], fi) != + BTRFS_COMPRESS_NONE) { + ret = -EUCLEAN; + error("Extent for symlink %llu must not be compressed!", ino); + goto out; + } + if (btrfs_file_extent_ram_bytes(path.nodes[0], fi) >= + root->fs_info->sectorsize) { + ret = -EUCLEAN; + error("Symlink %llu extent data too large (%llu)!\n", + ino, btrfs_file_extent_ram_bytes(path.nodes[0], fi)); + goto out; + } + read_extent_buffer(path.nodes[0], target, + btrfs_file_extent_inline_start(fi), + btrfs_file_extent_ram_bytes(path.nodes[0], fi)); + ret = btrfs_file_extent_ram_bytes(path.nodes[0], fi); +out: + btrfs_release_path(&path); + return ret; +} + +static int lookup_root_ref(struct btrfs_fs_info *fs_info, + u64 rootid, u64 *root_ret, u64 *dir_ret) +{ + struct btrfs_root *root = fs_info->tree_root; + struct btrfs_root_ref *root_ref; + struct btrfs_path path; + struct btrfs_key key; + int ret; + + btrfs_init_path(&path); + key.objectid = rootid; + key.type = BTRFS_ROOT_BACKREF_KEY; + key.offset = (u64)-1; + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return ret; + /* Should not happen */ + if (ret == 0) { + ret = -EUCLEAN; + goto out; + } + ret = btrfs_previous_item(root, &path, rootid, BTRFS_ROOT_BACKREF_KEY); + if (ret < 0) + goto out; + if (ret > 0) { + ret = -ENOENT; + goto out; + } + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + root_ref = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_root_ref); + *root_ret = key.offset; + *dir_ret = btrfs_root_ref_dirid(path.nodes[0], root_ref); +out: + btrfs_release_path(&path); + return ret; +} + +/* + * To get the parent inode of @ino of @root. + * + * @root_ret and @ino_ret will be filled. + * + * NOTE: This function is not reliable. It can only get one parent inode. + * The get the proper parent inode, we need a full VFS inodes stack to + * resolve properly. + */ +static int get_parent_inode(struct btrfs_root *root, u64 ino, + struct btrfs_root **root_ret, u64 *ino_ret) +{ + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_path path; + struct btrfs_key key; + int ret; + + if (ino == BTRFS_FIRST_FREE_OBJECTID) { + u64 parent_root = -1; + + /* It's top level already, no more parent */ + if (root->root_key.objectid == BTRFS_FS_TREE_OBJECTID) { + *root_ret = fs_info->fs_root; + *ino_ret = BTRFS_FIRST_FREE_OBJECTID; + return 0; + } + + ret = lookup_root_ref(fs_info, root->root_key.objectid, + &parent_root, ino_ret); + if (ret < 0) + return ret; + + key.objectid = parent_root; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + *root_ret = btrfs_read_fs_root(fs_info, &key); + if (IS_ERR(*root_ret)) + return PTR_ERR(*root_ret); + + return 0; + } + + btrfs_init_path(&path); + key.objectid = ino; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return ret; + /* Should not happen */ + if (ret == 0) { + ret = -EUCLEAN; + goto out; + } + ret = btrfs_previous_item(root, &path, ino, BTRFS_INODE_REF_KEY); + if (ret < 0) + goto out; + if (ret > 0) { + ret = -ENOENT; + goto out; + } + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + *root_ret = root; + *ino_ret = key.offset; +out: + btrfs_release_path(&path); + return ret; +} + +static inline int next_length(const char *path) +{ + int res = 0; + while (*path != '\0' && *path != '/') { + ++res; + ++path; + if (res > BTRFS_NAME_LEN) + break; + } + 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; +} + +/* + * Resolve one filename of @ino of @root. + * + * key_ret: The child key (either INODE_ITEM or ROOT_ITEM type) + * type_ret: BTRFS_FT_* of the child inode. + * + * Return 0 with above members filled. + * Return <0 for error. + */ +static int resolve_one_filename(struct btrfs_root *root, u64 ino, + const char *name, int namelen, + struct btrfs_key *key_ret, u8 *type_ret) +{ + struct btrfs_dir_item *dir_item; + struct btrfs_path path; + int ret = 0; + + btrfs_init_path(&path); + + dir_item = btrfs_lookup_dir_item(NULL, root, &path, ino, name, + namelen, 0); + if (IS_ERR(dir_item)) { + ret = PTR_ERR(dir_item); + goto out; + } + + btrfs_dir_item_key_to_cpu(path.nodes[0], dir_item, key_ret); + *type_ret = btrfs_dir_type(path.nodes[0], dir_item); +out: + btrfs_release_path(&path); + return ret; +} + +/* + * Resolve a full path @filename. The start point is @ino of @root. + * + * The result will be filled into @root_ret, @ino_ret and @type_ret. + */ +int btrfs_lookup_path(struct btrfs_root *root, u64 ino, const char *filename, + struct btrfs_root **root_ret, u64 *ino_ret, + u8 *type_ret, int symlink_limit) +{ + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_root *next_root; + struct btrfs_key key; + const char *cur = filename; + u64 next_ino; + u8 next_type; + u8 type = BTRFS_FT_UNKNOWN; + int len; + int ret = 0; + + /* If the path is absolute path, also search from fs root */ + if (*cur == '/') { + root = fs_info->fs_root; + ino = btrfs_root_dirid(&root->root_item); + type = BTRFS_FT_DIR; + } + + while (*cur != '\0') { + cur = skip_current_directories(cur); + + len = next_length(cur); + if (len > BTRFS_NAME_LEN) { + error("%s: Name too long at \"%.*s\"", __func__, + BTRFS_NAME_LEN, cur); + return -ENAMETOOLONG; + } + + if (len == 1 && cur[0] == '.') + break; + + if (len == 2 && cur[0] == '.' && cur[1] == '.') { + /* Go one level up */ + ret = get_parent_inode(root, ino, &next_root, &next_ino); + if (ret < 0) + return ret; + root = next_root; + ino = next_ino; + goto next; + } + + if (!*cur) + break; + + ret = resolve_one_filename(root, ino, cur, len, &key, &type); + if (ret < 0) + return ret; + + if (key.type == BTRFS_ROOT_ITEM_KEY) { + /* Child inode is a subvolume */ + + next_root = btrfs_read_fs_root(fs_info, &key); + if (IS_ERR(next_root)) + return PTR_ERR(next_root); + root = next_root; + ino = btrfs_root_dirid(&root->root_item); + } else if (type == BTRFS_FT_SYMLINK && symlink_limit >= 0) { + /* Child inode is a symlink */ + + char *target; + + if (symlink_limit == 0) { + error("%s: Too much symlinks!", __func__); + return -EMLINK; + } + target = malloc(fs_info->sectorsize); + if (!target) + return -ENOMEM; + ret = btrfs_readlink(root, key.objectid, target); + if (ret < 0) { + free(target); + return ret; + } + target[ret] = '\0'; + + ret = btrfs_lookup_path(root, ino, target, &next_root, + &next_ino, &next_type, + symlink_limit); + if (ret < 0) + return ret; + root = next_root; + ino = next_ino; + type = next_type; + } else { + /* Child inode is an inode */ + ino = key.objectid; + } +next: + cur += len; + } + + /* We haven't found anything, but still get no error? */ + if (type == BTRFS_FT_UNKNOWN && !ret) + ret = -EUCLEAN; + + if (!ret) { + *root_ret = root; + *ino_ret = ino; + *type_ret = type; + } + + return ret; +} + +/* + * Read out inline extent. + * + * Since inline extent should only exist for offset 0, no need for extra + * parameters. + * Truncating should be handled by the caller. + * + * Return the number of bytes read. + * Return <0 for error. + */ +int btrfs_read_extent_inline(struct btrfs_path *path, + struct btrfs_file_extent_item *fi, char *dest) +{ + struct extent_buffer *leaf = path->nodes[0]; + int slot = path->slots[0]; + char *cbuf = NULL; + char *dbuf = NULL; + u32 csize; + u32 dsize; + int ret; + + csize = btrfs_file_extent_inline_item_len(leaf, btrfs_item_nr(slot)); + if (btrfs_file_extent_compression(leaf, fi) == BTRFS_COMPRESS_NONE) { + /* Uncompressed, just read it out */ + read_extent_buffer(leaf, dest, + btrfs_file_extent_inline_start(fi), + csize); + return csize; + } + + /* Compressed extent, prepare the compressed and data buffer */ + dsize = btrfs_file_extent_ram_bytes(leaf, fi); + cbuf = malloc(csize); + dbuf = malloc(dsize); + if (!cbuf || !dbuf) { + ret = -ENOMEM; + goto out; + } + read_extent_buffer(leaf, cbuf, btrfs_file_extent_inline_start(fi), + csize); + ret = btrfs_decompress(btrfs_file_extent_compression(leaf, fi), + cbuf, csize, dbuf, dsize); + if (ret == (u32)-1) { + ret = -EIO; + goto out; + } + /* + * The compressed part ends before sector boundary, the remaining needs + * to be zeroed out. + */ + if (ret < dsize) + memset(dbuf + ret, 0, dsize - ret); + memcpy(dest, dbuf, dsize); + ret = dsize; +out: + free(cbuf); + free(dbuf); + return ret; +} + +/* + * Read out regular extent. + * + * Truncating should be handled by the caller. + * + * @offset and @len should not cross the extent boundary. + * Return the number of bytes read. + * Return <0 for error. + */ +int btrfs_read_extent_reg(struct btrfs_path *path, + struct btrfs_file_extent_item *fi, u64 offset, + int len, char *dest) +{ + struct extent_buffer *leaf = path->nodes[0]; + struct btrfs_fs_info *fs_info = leaf->fs_info; + struct btrfs_key key; + u64 extent_num_bytes; + u64 disk_bytenr; + u64 read; + char *cbuf = NULL; + char *dbuf = NULL; + u32 csize; + u32 dsize; + bool finished = false; + int num_copies; + int i; + int slot = path->slots[0]; + int ret; + + btrfs_item_key_to_cpu(leaf, &key, slot); + extent_num_bytes = btrfs_file_extent_num_bytes(leaf, fi); + ASSERT(IS_ALIGNED(offset, fs_info->sectorsize) && + IS_ALIGNED(len, fs_info->sectorsize)); + ASSERT(offset >= key.offset && + offset + len <= key.offset + extent_num_bytes); + + /* Preallocated or hole , fill @dest with zero */ + if (btrfs_file_extent_type(leaf, fi) == BTRFS_FILE_EXTENT_PREALLOC || + btrfs_file_extent_disk_bytenr(leaf, fi) == 0) { + memset(dest, 0, len); + return len; + } + + if (btrfs_file_extent_compression(leaf, fi) == BTRFS_COMPRESS_NONE) { + u64 logical; + + logical = btrfs_file_extent_disk_bytenr(leaf, fi) + + btrfs_file_extent_offset(leaf, fi) + + offset - key.offset; + read = len; + + num_copies = btrfs_num_copies(fs_info, logical, len); + for (i = 1; i <= num_copies; i++) { + ret = read_extent_data(fs_info, dest, logical, &read, i); + if (ret < 0 || read != len) + continue; + finished = true; + break; + } + if (!finished) + return -EIO; + return len; + } + + csize = btrfs_file_extent_disk_num_bytes(leaf, fi); + dsize = btrfs_file_extent_ram_bytes(leaf, fi); + disk_bytenr = btrfs_file_extent_disk_bytenr(leaf, fi); + num_copies = btrfs_num_copies(fs_info, disk_bytenr, csize); + + cbuf = malloc_cache_aligned(csize); + dbuf = malloc_cache_aligned(dsize); + if (!cbuf || !dbuf) { + ret = -ENOMEM; + goto out; + } + /* For compressed extent, we must read the whole on-disk extent */ + for (i = 1; i <= num_copies; i++) { + read = csize; + ret = read_extent_data(fs_info, cbuf, disk_bytenr, + &read, i); + if (ret < 0 || read != csize) + continue; + finished = true; + break; + } + if (!finished) { + ret = -EIO; + goto out; + } + + ret = btrfs_decompress(btrfs_file_extent_compression(leaf, fi), cbuf, + csize, dbuf, dsize); + if (ret == (u32)-1) { + ret = -EIO; + goto out; + } + /* + * The compressed part ends before sector boundary, the remaining needs + * to be zeroed out. + */ + if (ret < dsize) + memset(dbuf + ret, 0, dsize - ret); + /* Then copy the needed part */ + memcpy(dest, dbuf + btrfs_file_extent_offset(leaf, fi), len); + ret = len; +out: + free(cbuf); + free(dbuf); + return ret; +} + +/* + * Get the first file extent that covers bytenr @file_offset. + * + * @file_offset must be aligned to sectorsize. + * + * return 0 for found, and path points to the file extent. + * return >0 for not found, and fill @next_offset. + * @next_offset can be 0 if there is no next file extent. + * return <0 for error. + */ +static int lookup_data_extent(struct btrfs_root *root, struct btrfs_path *path, + u64 ino, u64 file_offset, u64 *next_offset) +{ + struct btrfs_key key; + struct btrfs_file_extent_item *fi; + u8 extent_type; + int ret = 0; + + ASSERT(IS_ALIGNED(file_offset, root->fs_info->sectorsize)); + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = file_offset; + + ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + /* Error or we're already at the file extent */ + if (ret <= 0) + return ret; + if (ret > 0) { + /* Check previous file extent */ + ret = btrfs_previous_item(root, path, ino, + BTRFS_EXTENT_DATA_KEY); + if (ret < 0) + return ret; + if (ret > 0) + goto check_next; + } + /* Now the key.offset must be smaller than @file_offset */ + btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + if (key.objectid != ino || + key.type != BTRFS_EXTENT_DATA_KEY) + goto check_next; + + fi = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_file_extent_item); + extent_type = btrfs_file_extent_type(path->nodes[0], fi); + if (extent_type == BTRFS_FILE_EXTENT_INLINE) { + if (file_offset == 0) + return 0; + /* Inline extent should be the only extent, no next extent. */ + *next_offset = 0; + return 1; + } + + /* This file extent covers @file_offset */ + if (key.offset <= file_offset && key.offset + + btrfs_file_extent_num_bytes(path->nodes[0], fi) > file_offset) + return 0; +check_next: + ret = btrfs_next_item(root, path); + if (ret < 0) + return ret; + if (ret > 0) { + *next_offset = 0; + return 1; + } + + btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + fi = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_file_extent_item); + /* Next next data extent */ + if (key.objectid != ino || + key.type != BTRFS_EXTENT_DATA_KEY) { + *next_offset = 0; + return 1; + } + /* Current file extent already beyond @file_offset */ + if (key.offset > file_offset) { + *next_offset = key.offset; + return 1; + } + /* This file extent covers @file_offset */ + if (key.offset <= file_offset && key.offset + + btrfs_file_extent_num_bytes(path->nodes[0], fi) > file_offset) + return 0; + /* This file extent ends before @file_offset, check next */ + ret = btrfs_next_item(root, path); + if (ret < 0) + return ret; + if (ret > 0) { + *next_offset = 0; + return 1; + } + btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + if (key.type != BTRFS_EXTENT_DATA_KEY || key.objectid != ino) { + *next_offset = 0; + return 1; + } + *next_offset = key.offset; + return 1; +} + +static int read_and_truncate_page(struct btrfs_path *path, + struct btrfs_file_extent_item *fi, + int start, int len, char *dest) +{ + struct extent_buffer *leaf = path->nodes[0]; + struct btrfs_fs_info *fs_info = leaf->fs_info; + u64 aligned_start = round_down(start, fs_info->sectorsize); + u8 extent_type; + char *buf; + int page_off = start - aligned_start; + int page_len = fs_info->sectorsize - page_off; + int ret; + + ASSERT(start + len <= aligned_start + fs_info->sectorsize); + buf = malloc_cache_aligned(fs_info->sectorsize); + if (!buf) + return -ENOMEM; + + extent_type = btrfs_file_extent_type(leaf, fi); + if (extent_type == BTRFS_FILE_EXTENT_INLINE) { + ret = btrfs_read_extent_inline(path, fi, buf); + memcpy(dest, buf + page_off, min(page_len, ret)); + free(buf); + return len; + } + + ret = btrfs_read_extent_reg(path, fi, + round_down(start, fs_info->sectorsize), + fs_info->sectorsize, buf); + if (ret < 0) { + free(buf); + return ret; + } + memcpy(dest, buf + page_off, page_len); + free(buf); + return len; +} + +int btrfs_file_read(struct btrfs_root *root, u64 ino, u64 file_offset, u64 len, + char *dest) +{ + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_file_extent_item *fi; + struct btrfs_path path; + struct btrfs_key key; + u64 aligned_start = round_down(file_offset, fs_info->sectorsize); + u64 aligned_end = round_down(file_offset + len, fs_info->sectorsize); + u64 next_offset; + u64 cur = aligned_start; + int ret = 0; + + btrfs_init_path(&path); + + /* Set the whole dest all zero, so we won't need to bother holes */ + memset(dest, 0, len); + + /* Read out the leading unaligned part */ + if (aligned_start != file_offset) { + ret = lookup_data_extent(root, &path, ino, aligned_start, + &next_offset); + if (ret < 0) + goto out; + if (ret == 0) { + /* Read the unaligned part out*/ + fi = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_file_extent_item); + ret = read_and_truncate_page(&path, fi, file_offset, + round_up(file_offset, fs_info->sectorsize) - + file_offset, dest); + if (ret < 0) + goto out; + cur += fs_info->sectorsize; + } else { + /* The whole file is a hole */ + if (!next_offset) { + memset(dest, 0, len); + return len; + } + cur = next_offset; + } + } + + /* Read the aligned part */ + while (cur < aligned_end) { + u64 extent_num_bytes; + u8 type; + + btrfs_release_path(&path); + ret = lookup_data_extent(root, &path, ino, cur, &next_offset); + if (ret < 0) + goto out; + if (ret > 0) { + /* No next, direct exit */ + if (!next_offset) { + ret = 0; + goto out; + } + } + fi = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_file_extent_item); + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + type = btrfs_file_extent_type(path.nodes[0], fi); + if (type == BTRFS_FILE_EXTENT_INLINE) { + ret = btrfs_read_extent_inline(&path, fi, dest); + goto out; + } + /* Skip holes, as we have zeroed the dest */ + if (type == BTRFS_FILE_EXTENT_PREALLOC || + btrfs_file_extent_disk_bytenr(path.nodes[0], fi) == 0) { + cur = key.offset + btrfs_file_extent_num_bytes( + path.nodes[0], fi); + continue; + } + + /* Read the remaining part of the extent */ + extent_num_bytes = btrfs_file_extent_num_bytes(path.nodes[0], + fi); + ret = btrfs_read_extent_reg(&path, fi, cur, + min(extent_num_bytes, aligned_end - cur), + dest + cur - file_offset); + if (ret < 0) + goto out; + cur += min(extent_num_bytes, aligned_end - cur); + } + + /* Read the tailing unaligned part*/ + if (file_offset + len != aligned_end) { + btrfs_release_path(&path); + ret = lookup_data_extent(root, &path, ino, aligned_end, + &next_offset); + /* <0 is error, >0 means no extent */ + if (ret) + goto out; + fi = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_file_extent_item); + ret = read_and_truncate_page(&path, fi, aligned_end, + file_offset + len - aligned_end, + dest + aligned_end - file_offset); + } +out: + btrfs_release_path(&path); + if (ret < 0) + return ret; + return len; +} |