From 1cf825afd0d7ebb4857002833658574efbef6626 Mon Sep 17 00:00:00 2001 From: Cole Munz Date: Sun, 2 Aug 2026 09:35:19 +0000 Subject: [PATCH 1/3] fs: btrfs: report file sizes from readdir btrfs_readdir() zeroes the dirent and fills in only the name and the type, so dent->size stays 0 and every file is listed as zero bytes: => ls host 0 / 0 f_192k.bin 0 small_3k.bin Reads themselves are fine, since btrfs_read() takes the size from btrfs_size(), which does its own inode item lookup. It affects EFI too: dir_read() in lib/efi_loader/efi_file.c copies dent->size into both file_size and physical_size, so an EFI application enumerating a directory on btrfs sees every file as empty, which is the generic-code path Alexey's readdir series moves btrfs onto. The custom listing that fs_ls_generic() replaced looked the inode item up and printed the real size, and every other filesystem in the tree fills dent->size in its own readdir: ext4fs.c:327, exfat io.c:805, erofs fs.c:186, squashfs sqfs.c:1095 and fat.c:1555. btrfs_next_dir_entry() already has the dir item mapped, so read the key it points at while we are there and hand it back to the caller, and use that to reach the inode item. A subvolume entry points at a root item instead and has no size of its own, so leave that one at 0. => ls host 0 / 196608 f_192k.bin 3000 small_3k.bin Fixes: 31cf3f177823 ("fs: btrfs: use fs_ls_generic() and drop custom implementation") Signed-off-by: Cole Munz Reviewed-by: Qu Wenruo --- fs/btrfs/btrfs.c | 24 +++++++++++++++++++++++- fs/btrfs/ctree.h | 3 ++- fs/btrfs/dir-item.c | 7 ++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index e663dda12e8..b2856be0662 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -93,7 +93,10 @@ 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_inode_item *ii; struct btrfs_root *root; + struct btrfs_path path; + struct btrfs_key location; struct btrfs_key key; u8 type; int ret; @@ -110,13 +113,32 @@ int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) memset(dent, 0, sizeof(*dent)); ret = btrfs_next_dir_entry(root, dirs->ino, &dirs->offset, dent->name, - sizeof(dent->name), &type); + sizeof(dent->name), &type, &location); if (ret < 0) return ret; if (ret > 0) return -ENOENT; dent->type = btrfs_dirent_type_to_fs_type(type); + + /* + * A subvolume entry points at a root item rather than an inode, and + * has no size of its own. Everything else carries one, and the fs + * layer prints it, so look it up. + */ + if (location.type == BTRFS_INODE_ITEM_KEY) { + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, &location, &path, 0, 0); + if (ret == 0) { + ii = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_inode_item); + dent->size = btrfs_inode_size(path.nodes[0], ii); + } + btrfs_release_path(&path); + if (ret < 0) + return ret; + } + *dentp = dent; return 0; } diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index 3fa9a8c9c02..cd3fd669f9a 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -1221,7 +1221,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, const char *name, int name_len, int mod); int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset, - char *namebuf, int namebuf_len, u8 *ftype); + char *namebuf, int namebuf_len, u8 *ftype, + struct btrfs_key *location); /* 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 c7b87d60d98..6edda34818b 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -126,12 +126,16 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, * @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 + * @location: receives the key the entry points at, so the caller can + * reach the inode item without searching for the name + * again * * 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) + char *namebuf, int namebuf_len, u8 *ftype, + struct btrfs_key *location) { struct btrfs_path path; struct btrfs_key key; @@ -180,6 +184,7 @@ int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset, (unsigned long)(di + 1), name_len); namebuf[name_len] = '\0'; *ftype = btrfs_dir_type(path.nodes[0], di); + btrfs_dir_item_key_to_cpu(path.nodes[0], di, location); ret = 0; out: From 1a5c8af2d4e4b4029739eb8787ce259d74977dfd Mon Sep 17 00:00:00 2001 From: Cole Munz Date: Sun, 2 Aug 2026 09:35:22 +0000 Subject: [PATCH 2/3] fs: btrfs: release the path when btrfs_search_slot() fails The U-Boot copy of btrfs_search_slot() returns on error with the nodes it has descended through still attached to the path. The kernel one releases the path on any error unless p->skip_release_on_error is set, and callers written against that convention treat a failed search as owning nothing. btrfs_size() is one: it returns straight away on a search error and never reaches its btrfs_release_path() call, so the attached extent buffer references leak. Route both error exits through a release of the path. The error returns of read_node_slot() carry no extra reference, so the path is the only thing to clean up. Suggested-by: Qu Wenruo Signed-off-by: Cole Munz Reviewed-by: Qu Wenruo --- fs/btrfs/ctree.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c index 8e932adc425..48c50e556b1 100644 --- a/fs/btrfs/ctree.c +++ b/fs/btrfs/ctree.c @@ -425,8 +425,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, level = btrfs_header_level(b); p->nodes[level] = b; ret = check_block(fs_info, p, level); - if (ret) - return -1; + if (ret) { + ret = -1; + goto err; + } ret = btrfs_bin_search(b, key, &slot); if (level != 0) { if (ret && slot > 0) @@ -461,8 +463,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, break; b = read_node_slot(fs_info, b, slot); - if (!extent_buffer_uptodate(b)) - return -EIO; + if (!extent_buffer_uptodate(b)) { + ret = -EIO; + goto err; + } } else { p->slots[level] = slot; /* @@ -479,6 +483,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, } } return 1; + +err: + btrfs_release_path(p); + return ret; } /* From a11f8659f451c5601bac6fdf5b48594d347db38d Mon Sep 17 00:00:00 2001 From: Cole Munz Date: Sun, 2 Aug 2026 09:35:26 +0000 Subject: [PATCH 3/3] fs: btrfs: deduplicate the inode size lookup btrfs_readdir() and btrfs_size() both open code the same search for an inode item to read its size field. Move it into one helper. Signed-off-by: Cole Munz Reviewed-by: Qu Wenruo --- fs/btrfs/btrfs.c | 75 +++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index b2856be0662..f5f6d638ffd 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -88,14 +88,42 @@ static unsigned int btrfs_dirent_type_to_fs_type(u8 dirent_type) } } +/* + * Read the size stored in an inode item. A missing item is -ENOENT and + * leaves *size untouched. + */ +static int btrfs_get_inode_size(struct btrfs_root *root, u64 ino, u64 *size) +{ + struct btrfs_inode_item *ii; + struct btrfs_path path; + struct btrfs_key key; + int ret; + + key.objectid = ino; + key.type = BTRFS_INODE_ITEM_KEY; + key.offset = 0; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return ret; + if (ret > 0) + ret = -ENOENT; + if (!ret) { + ii = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_inode_item); + *size = btrfs_inode_size(path.nodes[0], ii); + } + btrfs_release_path(&path); + return ret; +} + 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_inode_item *ii; struct btrfs_root *root; - struct btrfs_path path; struct btrfs_key location; struct btrfs_key key; u8 type; @@ -127,16 +155,13 @@ int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) * layer prints it, so look it up. */ if (location.type == BTRFS_INODE_ITEM_KEY) { - btrfs_init_path(&path); - ret = btrfs_search_slot(NULL, root, &location, &path, 0, 0); - if (ret == 0) { - ii = btrfs_item_ptr(path.nodes[0], path.slots[0], - struct btrfs_inode_item); - dent->size = btrfs_inode_size(path.nodes[0], ii); - } - btrfs_release_path(&path); - if (ret < 0) + u64 size; + + ret = btrfs_get_inode_size(root, location.objectid, &size); + if (ret < 0 && ret != -ENOENT) return ret; + if (!ret) + dent->size = size; } *dentp = dent; @@ -173,10 +198,8 @@ int btrfs_exists(const char *file) int btrfs_size(const char *file, loff_t *size) { struct btrfs_fs_info *fs_info = current_fs_info; - struct btrfs_inode_item *ii; struct btrfs_root *root; - struct btrfs_path path; - struct btrfs_key key; + u64 isize; u64 ino; u8 type; int ret; @@ -191,27 +214,13 @@ int btrfs_size(const char *file, loff_t *size) printf("Not a regular file: %s\n", file); return -ENOENT; } - btrfs_init_path(&path); - key.objectid = ino; - key.type = BTRFS_INODE_ITEM_KEY; - key.offset = 0; - - ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); - if (ret < 0) { - printf("Cannot lookup ino %llu\n", ino); + ret = btrfs_get_inode_size(root, ino, &isize); + if (ret) { + printf("Cannot read size of ino %llu\n", ino); return ret; } - if (ret > 0) { - printf("Ino %llu does not exist\n", ino); - ret = -ENOENT; - goto out; - } - ii = btrfs_item_ptr(path.nodes[0], path.slots[0], - struct btrfs_inode_item); - *size = btrfs_inode_size(path.nodes[0], ii); -out: - btrfs_release_path(&path); - return ret; + *size = isize; + return 0; } int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len,