summaryrefslogtreecommitdiff
path: root/fs/btrfs/inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/btrfs/inode.c')
-rw-r--r--fs/btrfs/inode.c884
1 files changed, 626 insertions, 258 deletions
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 991c2f68c3b..ff330280e02 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -5,193 +5,187 @@
* 2017 Marek Behun, CZ.NIC, marek.behun@nic.cz
*/
-#include "btrfs.h"
+#include <linux/kernel.h>
#include <malloc.h>
+#include <memalign.h>
+#include "btrfs.h"
+#include "disk-io.h"
+#include "volumes.h"
-u64 btrfs_lookup_inode_ref(struct btrfs_root *root, u64 inr,
- struct btrfs_inode_ref *refp, char *name)
+/*
+ * 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_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;
+ struct btrfs_key key;
+ struct btrfs_file_extent_item *fi;
+ int ret;
- if (name) {
- if (ref->name_len > BTRFS_NAME_MAX) {
- printf("%s: inode name too long: %u\n", __func__,
- ref->name_len);
- goto out;
- }
+ key.objectid = ino;
+ key.type = BTRFS_EXTENT_DATA_KEY;
+ key.offset = 0;
+ btrfs_init_path(&path);
- memcpy(name, ref + 1, ref->name_len);
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ if (ret < 0)
+ return ret;
+ if (ret > 0) {
+ ret = -ENOENT;
+ goto out;
}
-
- 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;
+ 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_search_tree(&tmp_root, location, &path))
- return res;
-
- if (btrfs_comp_keys(location, btrfs_path_leaf_key(&path)))
+ 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 (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;
-
+ 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_free_path(&path);
- return res;
+ btrfs_release_path(&path);
+ return ret;
}
-int btrfs_readlink(const struct btrfs_root *root, u64 inr, char *target)
+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;
- 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);
+ 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;
}
-
- btrfs_file_extent_item_to_cpu_inl(extent);
-
- if (extent->compression != BTRFS_COMPRESS_NONE) {
- printf("%s: Symlink %llu extent data compressed!\n", __func__,
- inr);
+ ret = btrfs_previous_item(root, &path, rootid, BTRFS_ROOT_BACKREF_KEY);
+ if (ret < 0)
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);
+ if (ret > 0) {
+ ret = -ENOENT;
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;
+ 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_free_path(&path);
- return res;
+ btrfs_release_path(&path);
+ return ret;
}
-/* 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)
+/*
+ * 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;
- 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;
+ int ret;
- if (btrfs_find_root(parent, root, NULL))
- return -1ULL;
+ if (ino == BTRFS_FIRST_FREE_OBJECTID) {
+ u64 parent_root = -1;
- inr = ref.dirid;
+ /* 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;
}
- if (inode_item) {
- key.objectid = inr;
- key.type = BTRFS_INODE_ITEM_KEY;
- key.offset = 0;
+ ret = lookup_root_ref(fs_info, root->root_key.objectid,
+ &parent_root, ino_ret);
+ if (ret < 0)
+ return ret;
- if (btrfs_lookup_inode(root, &key, inode_item, NULL))
- return -1ULL;
- }
+ 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 inr;
+ return 0;
}
- 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;
+ 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;
}
-
- return res;
+ 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 <= BTRFS_NAME_LEN)
- ++res, ++path;
+ while (*path != '\0' && *path != '/') {
+ ++res;
+ ++path;
+ if (res > BTRFS_NAME_LEN)
+ break;
+ }
return res;
}
@@ -209,175 +203,549 @@ static inline const char *skip_current_directories(const char *cur)
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)
+/*
+ * 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 item;
- struct btrfs_inode_item inode_item;
- u8 type = BTRFS_FT_DIR;
- int len, have_inode = 0;
- const char *cur = path;
+ 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;
+ int len;
+ int ret = 0;
+
+ /* If the path is absolute path, also search from fs root */
if (*cur == '/') {
- ++cur;
- inr = root->root_dirid;
+ root = fs_info->fs_root;
+ ino = btrfs_root_dirid(&root->root_item);
+ type = BTRFS_FT_DIR;
}
- do {
+ while (*cur != '\0') {
cur = skip_current_directories(cur);
len = next_length(cur);
if (len > BTRFS_NAME_LEN) {
- printf("%s: Name too long at \"%.*s\"\n", __func__,
+ error("%s: Name too long at \"%.*s\"", __func__,
BTRFS_NAME_LEN, cur);
- return -1ULL;
+ return -ENAMETOOLONG;
}
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;
+ /* 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;
-
- 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;
+ 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 */
- if (item.type == BTRFS_FT_SYMLINK && symlink_limit >= 0) {
char *target;
- if (!symlink_limit) {
- printf("%s: Too much symlinks!\n", __func__);
- return -1ULL;
+ if (symlink_limit == 0) {
+ error("%s: Too much symlinks!", __func__);
+ return -EMLINK;
}
-
- target = malloc(min(inode_item.size + 1,
- (u64) btrfs_info.sb.sectorsize));
+ target = malloc(fs_info->sectorsize);
if (!target)
- return -1ULL;
-
- if (btrfs_readlink(root, item.location.objectid,
- target)) {
+ return -ENOMEM;
+ ret = btrfs_readlink(root, key.objectid, target);
+ if (ret < 0) {
free(target);
- return -1ULL;
+ 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;
+ }
- inr = btrfs_lookup_path(root, inr, target, &type,
- &inode_item, symlink_limit - 1);
+ if (!ret) {
+ *root_ret = root;
+ *ino_ret = ino;
+ *type_ret = type;
+ }
- free(target);
+ return ret;
+}
- 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;
- }
+/*
+ * 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;
+ }
- cur += len;
- } while (*cur);
+ /* 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 < 0 || ret != dsize) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(dest, dbuf, dsize);
+ ret = dsize;
+out:
+ free(cbuf);
+ free(dbuf);
+ return ret;
+}
- if (type_p)
- *type_p = type;
+/*
+ * 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 (inode_item_p) {
- if (!have_inode) {
- struct btrfs_key key;
+ if (btrfs_file_extent_compression(leaf, fi) == BTRFS_COMPRESS_NONE) {
+ u64 logical;
- key.objectid = inr;
- key.type = BTRFS_INODE_ITEM_KEY;
- key.offset = 0;
+ logical = btrfs_file_extent_disk_bytenr(leaf, fi) +
+ btrfs_file_extent_offset(leaf, fi) +
+ offset - key.offset;
+ read = len;
- if (btrfs_lookup_inode(root, &key, &inode_item, NULL))
- return -1ULL;
+ 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);
- *inode_item_p = inode_item;
+ 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;
}
- return inr;
+ ret = btrfs_decompress(btrfs_file_extent_compression(leaf, fi), cbuf,
+ csize, dbuf, dsize);
+ if (ret != dsize) {
+ ret = -EIO;
+ goto out;
+ }
+ /* Then copy the needed part */
+ memcpy(dest, dbuf + btrfs_file_extent_offset(leaf, fi), len);
+ ret = len;
+out:
+ free(cbuf);
+ free(dbuf);
+ return ret;
}
-u64 btrfs_file_read(const struct btrfs_root *root, u64 inr, u64 offset,
- u64 size, char *buf)
+/*
+ * 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_path path;
struct btrfs_key key;
- struct btrfs_file_extent_item *extent;
- int res = 0;
- u64 rd, rd_all = -1ULL;
+ struct btrfs_file_extent_item *fi;
+ u8 extent_type;
+ int ret = 0;
- key.objectid = inr;
+ ASSERT(IS_ALIGNED(file_offset, root->fs_info->sectorsize));
+ key.objectid = ino;
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;
+ 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;
+ }
- if (btrfs_comp_keys_type(&key, btrfs_path_leaf_key(&path)))
- goto out;
+ /* 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;
}
- rd_all = 0;
+ 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;
+}
- do {
- if (btrfs_comp_keys_type(&key, btrfs_path_leaf_key(&path)))
- break;
+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;
+ }
- extent = btrfs_path_item_ptr(&path,
- struct btrfs_file_extent_item);
+ 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;
+}
- 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);
+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 {
- btrfs_file_extent_item_to_cpu(extent);
- rd = btrfs_read_extent_reg(&path, extent, offset, size,
- buf);
+ /* 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;
- if (rd == -1ULL) {
- printf("%s: Error reading extent\n", __func__);
- rd_all = -1;
+ 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;
}
- offset = 0;
- buf += rd;
- rd_all += rd;
- size -= rd;
-
- if (!size)
- break;
- } while (!(res = btrfs_next_slot(&path)));
-
- if (res)
- return -1ULL;
+ /* 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_free_path(&path);
- return rd_all;
+ btrfs_release_path(&path);
+ if (ret < 0)
+ return ret;
+ return len;
}