diff options
| -rw-r--r-- | fs/btrfs/btrfs.c | 98 | ||||
| -rw-r--r-- | fs/btrfs/ctree.h | 2 | ||||
| -rw-r--r-- | fs/btrfs/dir-item.c | 73 | ||||
| -rw-r--r-- | fs/fs.c | 4 | ||||
| -rw-r--r-- | include/btrfs.h | 5 |
5 files changed, 181 insertions, 1 deletions
diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index f3087f690fa..6f034861da9 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -8,7 +8,9 @@ #include <config.h> #include <malloc.h> #include <u-boot/uuid.h> +#include <linux/kernel.h> #include <linux/time.h> +#include <fs.h> #include "btrfs.h" #include "crypto/hash.h" #include "disk-io.h" @@ -159,6 +161,102 @@ int btrfs_ls(const char *path) return 0; } +/* + * The fs layer closes and re-probes btrfs between readdir() calls (see + * fs_readdir() in fs/fs.c), freeing and reallocating fs_info, so root cannot + * be stored directly. The subvolume id and inode number are stable though, so + * re-resolve the root from the current fs_info by subvolume id, which avoids + * a full path walk and is much faster. + */ +struct btrfs_dir_stream { + struct fs_dir_stream parent; + struct fs_dirent dirent; + u64 subvolid; + u64 ino; + u64 offset; +}; + +int btrfs_opendir(const char *dirname, struct fs_dir_stream **dirsp) +{ + struct btrfs_fs_info *fs_info = current_fs_info; + struct btrfs_dir_stream *dirs; + struct btrfs_root *root; + u64 ino; + u8 type; + int ret; + + *dirsp = NULL; + ASSERT(fs_info); + + ret = btrfs_lookup_path(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID, + dirname, &root, &ino, &type, 40); + if (ret < 0) + return ret; + if (type != BTRFS_FT_DIR) + return -ENOTDIR; + + dirs = calloc(1, sizeof(*dirs)); + if (!dirs) + return -ENOMEM; + dirs->subvolid = root->root_key.objectid; + dirs->ino = ino; + + *dirsp = &dirs->parent; + return 0; +} + +static unsigned int btrfs_dirent_type_to_fs_type(u8 dirent_type) +{ + switch (dirent_type) { + case BTRFS_FT_DIR: + return FS_DT_DIR; + case BTRFS_FT_SYMLINK: + return FS_DT_LNK; + default: + return FS_DT_REG; + } +} + +int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) +{ + struct btrfs_dir_stream *dirs = container_of(fs_dirs, struct btrfs_dir_stream, parent); + struct btrfs_fs_info *fs_info = current_fs_info; + struct fs_dirent *dent = &dirs->dirent; + struct btrfs_root *root; + struct btrfs_key key; + u8 type; + int ret; + + *dentp = NULL; + ASSERT(fs_info); + + key.objectid = dirs->subvolid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root(fs_info, &key); + if (IS_ERR(root)) + return PTR_ERR(root); + + memset(dent, 0, sizeof(*dent)); + ret = btrfs_next_dir_entry(root, dirs->ino, &dirs->offset, dent->name, + sizeof(dent->name), &type); + if (ret < 0) + return ret; + if (ret > 0) + return -ENOENT; + + dent->type = btrfs_dirent_type_to_fs_type(type); + *dentp = dent; + return 0; +} + +void btrfs_closedir(struct fs_dir_stream *fs_dirs) +{ + struct btrfs_dir_stream *dirs = container_of(fs_dirs, struct btrfs_dir_stream, parent); + + free(dirs); +} + int btrfs_exists(const char *file) { struct btrfs_fs_info *fs_info = current_fs_info; diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index b7be09d5df8..7de35aa7efd 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -1225,6 +1225,8 @@ typedef int (*btrfs_iter_dir_callback_t)(struct btrfs_root *root, struct btrfs_dir_item *di); int btrfs_iter_dir(struct btrfs_root *root, u64 ino, btrfs_iter_dir_callback_t callback); +int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset, + char *namebuf, int namebuf_len, u8 *ftype); /* inode.c */ int btrfs_lookup_path(struct btrfs_root *root, u64 ino, const char *filename, struct btrfs_root **root_ret, u64 *ino_ret, diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c index 5f81d6414f0..b12298e20c4 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -166,3 +166,76 @@ out: btrfs_release_path(&path); return ret; } + +/* + * btrfs_next_dir_entry() - read one directory entry at or after a cursor + * + * Streaming counterpart to btrfs_iter_dir() for the fs-layer readdir, which + * is re-entered once per entry: returns the first real entry whose + * BTRFS_DIR_INDEX_KEY offset is >= *offset and advances *offset past it. + * + * @root, @ino: directory to read + * @offset: in/out cursor; DIR_INDEX offset to resume from + * @namebuf: caller buffer that receives the NUL-terminated name + * @namebuf_len: size of @namebuf in bytes + * @ftype: receives the BTRFS_FT_* type of the entry + * + * Return: 0 if an entry was returned, 1 when the directory is exhausted, + * -ve on error. + */ +int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset, + char *namebuf, int namebuf_len, u8 *ftype) +{ + struct btrfs_path path; + struct btrfs_key key; + struct btrfs_dir_item *di; + int name_len; + int ret; + + btrfs_init_path(&path); + key.objectid = ino; + key.type = BTRFS_DIR_INDEX_KEY; + key.offset = *offset; + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + goto out; + + if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) { + ret = btrfs_next_leaf(root, &path); + if (ret < 0) + goto out; + if (ret > 0) { /* end of tree */ + ret = 1; + goto out; + } + } + + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + if (key.objectid != ino || key.type != BTRFS_DIR_INDEX_KEY) { + ret = 1; /* no more entries for this dir */ + goto out; + } + + di = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_dir_item); + if (verify_dir_item(root, path.nodes[0], di)) { + ret = -EUCLEAN; + goto out; + } + + *offset = key.offset + 1; + + name_len = btrfs_dir_name_len(path.nodes[0], di); + if (name_len > namebuf_len - 1) + name_len = namebuf_len - 1; + read_extent_buffer(path.nodes[0], namebuf, + (unsigned long)(di + 1), name_len); + namebuf[name_len] = '\0'; + *ftype = btrfs_dir_type(path.nodes[0], di); + ret = 0; + +out: + btrfs_release_path(&path); + return ret; +} @@ -326,7 +326,9 @@ static struct fstype_info fstypes[] = { .read = btrfs_read, .write = fs_write_unsupported, .uuid = btrfs_uuid, - .opendir = fs_opendir_unsupported, + .opendir = btrfs_opendir, + .readdir = btrfs_readdir, + .closedir = btrfs_closedir, .unlink = fs_unlink_unsupported, .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, diff --git a/include/btrfs.h b/include/btrfs.h index 2d73add18e0..6fff45a497e 100644 --- a/include/btrfs.h +++ b/include/btrfs.h @@ -10,6 +10,8 @@ struct blk_desc; struct disk_partition; +struct fs_dir_stream; +struct fs_dirent; int btrfs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition); @@ -20,5 +22,8 @@ int btrfs_read(const char *, void *, loff_t, loff_t, loff_t *); void btrfs_close(void); int btrfs_uuid(char *); void btrfs_list_subvols(void); +int btrfs_opendir(const char *filename, struct fs_dir_stream **dirsp); +int btrfs_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp); +void btrfs_closedir(struct fs_dir_stream *dirs); #endif /* __U_BOOT_BTRFS_H__ */ |
