[PATCH v2 1/2] fs: btrfs: implement opendir(), readdir() and closedir()

Qu Wenruo quwenruo.btrfs at gmx.com
Sat Jun 27 00:26:51 CEST 2026



在 2026/6/27 00:48, Alexey Charkov 写道:
> Add support for generic directory iteration with opendir(), readdir() and
> closedir() in the btrfs filesystem driver.
> 
> Signed-off-by: Alexey Charkov <alchark at flipper.net>
> ---
>   fs/btrfs/btrfs.c    | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>   fs/btrfs/ctree.h    |  2 ++
>   fs/btrfs/dir-item.c | 73 +++++++++++++++++++++++++++++++++++++++
>   fs/fs.c             |  4 ++-
>   include/btrfs.h     |  5 +++
>   5 files changed, 181 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c
> index f3087f690fa4..6f034861da90 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);

You can save the root pointer into btrfs_dir_stream, so we can avoid 
subvolume root lookup for each dentry.

You do not need to bother the lifespan of subvolume roots either, they 
are properly released during the close of the fs.

Otherwise looks good to me, except an unrelated question just lines below.

> +	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;

I'm not sure what is the proper/preferred/sane handling of end of directory.

Ext4/fat returns -ENOENT, squashfs returned -1, erofs return 1, exfat 
returns 0 but without populating @dentp.

Personally I found the exfat behavior more sane, but considering the 
caller fs_ls_generic() doesn't really bother the return value but only 
cares if @dent is populated, it should be fine either way.

But still, returning -ENOENT will populate errno, which may be confusing 
for debugging.

Anyway it's an unrelated nitpick.

Thanks,
Qu

> +
> +	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 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..b12298e20c41 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;
> +}
> 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__ */
> 



More information about the U-Boot mailing list