Add support for generic directory iteration with opendir(), readdir() and closedir() in the btrfs filesystem driver, following the ext4fs implementation for opendir()/closedir() and the btrfs_iter_dir() function for readdir().
Signed-off-by: Alexey Charkov <[email protected]> --- fs/btrfs/btrfs.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/btrfs/ctree.h | 2 ++ fs/btrfs/dir-item.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fs.c | 4 ++- include/btrfs.h | 5 +++ 5 files changed, 193 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index f3087f690fa4..c647c8dedf4e 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -9,6 +9,7 @@ #include <malloc.h> #include <u-boot/uuid.h> #include <linux/time.h> +#include <fs.h> #include "btrfs.h" #include "crypto/hash.h" #include "disk-io.h" @@ -159,6 +160,97 @@ int btrfs_ls(const char *path) return 0; } +struct btrfs_dir_stream { + struct fs_dir_stream parent; + struct fs_dirent dirent; + char *dirname; + 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->dirname = strdup(dirname); + if (!dirs->dirname) { + free(dirs); + return -ENOMEM; + } + + *dirsp = (struct fs_dir_stream *)dirs; + return 0; +} + +int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) +{ + struct btrfs_dir_stream *dirs = (struct btrfs_dir_stream *)fs_dirs; + struct btrfs_fs_info *fs_info = current_fs_info; + struct fs_dirent *dent = &dirs->dirent; + struct btrfs_root *root; + u64 ino; + u8 type; + int ret; + + *dentp = NULL; + ASSERT(fs_info); + + ret = btrfs_lookup_path(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID, + dirs->dirname, &root, &ino, &type, 40); + if (ret < 0) + return ret; + if (type != BTRFS_FT_DIR) + return -ENOTDIR; + + memset(dent, 0, sizeof(*dent)); + ret = btrfs_next_dir_entry(root, ino, &dirs->offset, dent->name, + sizeof(dent->name), &type); + if (ret < 0) + return ret; + if (ret > 0) + return -ENOENT; + + switch (type) { + case BTRFS_FT_DIR: + dent->type = FS_DT_DIR; + break; + case BTRFS_FT_SYMLINK: + dent->type = FS_DT_LNK; + break; + default: + dent->type = FS_DT_REG; + break; + } + + *dentp = dent; + return 0; +} + +void btrfs_closedir(struct fs_dir_stream *fs_dirs) +{ + struct btrfs_dir_stream *dirs = (struct btrfs_dir_stream *)fs_dirs; + + free(dirs->dirname); + 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 b7be09d5df83..7de35aa7efd5 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 5f81d6414f06..8cf5be3790e7 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -166,3 +166,94 @@ 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; + 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; + + while (1) { + struct btrfs_dir_item *di; + int name_len; + u8 type; + + 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; + type = btrfs_dir_type(path.nodes[0], di); + + /* XATTRs share the key space but are not directory entries. */ + if (type == BTRFS_FT_XATTR) { + ret = btrfs_next_item(root, &path); + if (ret < 0) + goto out; + if (ret > 0) { + ret = 1; + goto out; + } + continue; + } + + 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 = type; + ret = 0; + goto out; + } + +out: + btrfs_release_path(&path); + return ret; +} diff --git a/fs/fs.c b/fs/fs.c index 8ea50a6c13c4..4694a88f776b 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -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 2d73add18e09..6fff45a497ee 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__ */ -- 2.53.0

