diff options
| author | Tom Rini <[email protected]> | 2026-07-10 15:55:23 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2026-07-10 15:55:23 -0600 |
| commit | 6741b0dfb41dc82a284ab1cff4c58af6ef2f3f9c (patch) | |
| tree | 52be30716d160497ac62760403b2346ba77f3008 | |
| parent | e0d10a4b85cb3adfff152494afe74d3a0cac1090 (diff) | |
| parent | 31cf3f177823362a42bf4630ec85370ea768ddf1 (diff) | |
Alexey Charkov <[email protected]> says:
Btrfs in U-boot currently uses a custom callback for ls and doesn't
expose the standard opendir/readdir/closedir interface, making it harder
to use in generic code. One area where this would be useful is in
discovering BLS type 1 entries [1] on a Btrfs filesystem.
Add support for the standard interface, and implement ls in terms of it.
[1] https://lore.kernel.org/u-boot/[email protected]/
Link: https://lore.kernel.org/r/[email protected]
| -rw-r--r-- | fs/btrfs/btrfs.c | 199 | ||||
| -rw-r--r-- | fs/btrfs/ctree.h | 7 | ||||
| -rw-r--r-- | fs/btrfs/dir-item.c | 74 | ||||
| -rw-r--r-- | fs/fs.c | 6 | ||||
| -rw-r--r-- | include/btrfs.h | 6 |
5 files changed, 142 insertions, 150 deletions
diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index f3087f690fa..e663dda12e8 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -8,114 +8,15 @@ #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" struct btrfs_fs_info *current_fs_info; -static int show_dir(struct btrfs_root *root, struct extent_buffer *eb, - struct btrfs_dir_item *di) -{ - struct btrfs_fs_info *fs_info = root->fs_info; - struct btrfs_inode_item ii; - struct btrfs_key key; - static const char* dir_item_str[] = { - [BTRFS_FT_REG_FILE] = " ", - [BTRFS_FT_DIR] = "DIR", - [BTRFS_FT_CHRDEV] = "CHR", - [BTRFS_FT_BLKDEV] = "BLK", - [BTRFS_FT_FIFO] = "FIF", - [BTRFS_FT_SOCK] = "SCK", - [BTRFS_FT_SYMLINK] = "SYM", - }; - u8 type = btrfs_dir_type(eb, di); - char namebuf[BTRFS_NAME_LEN]; - char *target = NULL; - char filetime[32]; - time_t mtime; - int ret = 0; - - /* skip XATTRs in directory listing */ - if (type == BTRFS_FT_XATTR) - return 0; - - btrfs_dir_item_key_to_cpu(eb, di, &key); - - if (key.type == BTRFS_ROOT_ITEM_KEY) { - struct btrfs_root *subvol; - - /* It's a subvolume, get its mtime from root item */ - subvol = btrfs_read_fs_root(fs_info, &key); - if (IS_ERR(subvol)) { - ret = PTR_ERR(subvol); - error("Can't find root %llu", key.objectid); - return ret; - } - mtime = btrfs_stack_timespec_sec(&subvol->root_item.otime); - } else { - struct btrfs_path path; - - /* It's regular inode, get its mtime from inode item */ - btrfs_init_path(&path); - ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); - if (ret > 0) - ret = -ENOENT; - if (ret < 0) { - error("Can't find inode %llu", key.objectid); - btrfs_release_path(&path); - return ret; - } - read_extent_buffer(path.nodes[0], &ii, - btrfs_item_ptr_offset(path.nodes[0], path.slots[0]), - sizeof(ii)); - btrfs_release_path(&path); - mtime = btrfs_stack_timespec_sec(&ii.mtime); - } - ctime_r(&mtime, filetime); - - if (type == BTRFS_FT_SYMLINK) { - target = malloc(fs_info->sectorsize); - if (!target) { - error("Can't alloc memory for symlink %llu", - key.objectid); - return -ENOMEM; - } - ret = btrfs_readlink(root, key.objectid, target); - if (ret < 0) { - error("Failed to read symlink %llu", key.objectid); - goto out; - } - target[ret] = '\0'; - } - - if (type < ARRAY_SIZE(dir_item_str) && dir_item_str[type]) - printf("<%s> ", dir_item_str[type]); - else - printf("?%3u? ", type); - if (type == BTRFS_FT_CHRDEV || type == BTRFS_FT_BLKDEV) { - ASSERT(key.type == BTRFS_INODE_ITEM_KEY); - printf("%4llu,%5llu ", btrfs_stack_inode_rdev(&ii) >> 20, - btrfs_stack_inode_rdev(&ii) & 0xfffff); - } else { - if (key.type == BTRFS_INODE_ITEM_KEY) - printf("%10llu ", btrfs_stack_inode_size(&ii)); - else - printf("%10llu ", 0ULL); - } - - read_extent_buffer(eb, namebuf, (unsigned long)(di + 1), - btrfs_dir_name_len(eb, di)); - printf("%24.24s %.*s", filetime, btrfs_dir_name_len(eb, di), namebuf); - if (type == BTRFS_FT_SYMLINK) - printf(" -> %s", target ? target : "?"); - printf("\n"); -out: - free(target); - return ret; -} - int btrfs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { @@ -131,34 +32,102 @@ int btrfs_probe(struct blk_desc *fs_dev_desc, return ret; } -int btrfs_ls(const char *path) +/* + * 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_root *root = fs_info->fs_root; - u64 ino = BTRFS_FIRST_FREE_OBJECTID; + 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, - path, &root, &ino, &type, 40); - if (ret < 0) { - printf("Cannot lookup path %s\n", path); + dirname, &root, &ino, &type, 40); + if (ret < 0) return ret; - } + if (type != BTRFS_FT_DIR) + return -ENOTDIR; - if (type != BTRFS_FT_DIR) { - error("Not a directory: %s", path); - return -ENOENT; + 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; } - ret = btrfs_iter_dir(root, ino, show_dir); - if (ret < 0) { - error("An error occurred while listing directory %s", path); +} + +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..3fa9a8c9c02 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -1220,11 +1220,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, struct btrfs_path *path, u64 dir, const char *name, int name_len, int mod); -typedef int (*btrfs_iter_dir_callback_t)(struct btrfs_root *root, - struct extent_buffer *eb, - 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..c7b87d60d98 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -114,54 +114,74 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, return btrfs_match_dir_item_name(root, path, name, name_len); } -int btrfs_iter_dir(struct btrfs_root *root, u64 ino, - btrfs_iter_dir_callback_t callback) +/* + * btrfs_next_dir_entry() - read one directory entry at or after a cursor + * + * Iterator 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 = 0; + key.offset = *offset; 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; - } + if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) { ret = btrfs_next_leaf(root, &path); if (ret < 0) goto out; - if (ret > 0) { - ret = 0; + if (ret > 0) { /* end of tree */ + ret = 1; goto out; } } - do { - struct btrfs_dir_item *di; - btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); - if (key.objectid != ino || key.type != BTRFS_DIR_INDEX_KEY) - break; - 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; - } - ret = callback(root, path.nodes[0], di); - if (ret < 0) - goto out; - } while (!(ret = btrfs_next_item(root, &path))); + 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; - if (ret > 0) - ret = 0; out: btrfs_release_path(&path); return ret; @@ -320,13 +320,15 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = false, .probe = btrfs_probe, .close = btrfs_close, - .ls = btrfs_ls, + .ls = fs_ls_generic, .exists = btrfs_exists, .size = btrfs_size, .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..3878b7817ea 100644 --- a/include/btrfs.h +++ b/include/btrfs.h @@ -10,15 +10,19 @@ 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); -int btrfs_ls(const char *); int btrfs_exists(const char *); int btrfs_size(const char *, loff_t *); 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__ */ |
