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.
The inode item is a separate key, so reaching it still takes a second
search, but btrfs_size() already open-codes that exact search, so pull
it into a helper both callers share. That also closes a path leak in
btrfs_size(), which returned without releasing on a search error. 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 <[email protected]>
---
The second search itself cannot go away, since the dir index item and
the inode item it points at are separate keys, and the old custom
lister did the same two lookups per entry. What can go away is the
open-coded machinery: btrfs_size() carries an identical search, so v2
moves it into one helper both callers use. That also fixes btrfs_size()
returning without releasing the path when the search errors out.
Changes in v2:
- pull the inode item search into a helper shared with btrfs_size()
instead of open-coding a second copy (Alexey)
Still passes the btrfs suite from the pending test series on top of
this patch plus the readdir series: 5 passed.
fs/btrfs/btrfs.c | 75 +++++++++++++++++++++++++++++++--------------
fs/btrfs/ctree.h | 3 +-
fs/btrfs/dir-item.c | 7 ++++-
3 files changed, 60 insertions(+), 25 deletions(-)
diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c
index e663dda12e80..dea155bd150e 100644
--- a/fs/btrfs/btrfs.c
+++ b/fs/btrfs/btrfs.c
@@ -88,12 +88,41 @@ 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)
+ 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_root *root;
+ struct btrfs_key location;
struct btrfs_key key;
u8 type;
int ret;
@@ -110,13 +139,29 @@ 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) {
+ 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;
return 0;
}
@@ -151,10 +196,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;
@@ -169,27 +212,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,
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 3fa9a8c9c020..cd3fd669f9ae 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 c7b87d60d986..6edda34818b8 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:
--
2.55.0