aboutsummaryrefslogtreecommitdiffstats
path: root/roms/u-boot/fs/btrfs/inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/u-boot/fs/btrfs/inode.c')
-rw-r--r--roms/u-boot/fs/btrfs/inode.c767
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;
+}