Files
u-boot-krane/fs/btrfs/btrfs.c
T
Cole MunzandTom Rini a11f8659f4 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 <Munzzyy1@proton.me>
Reviewed-by: Qu Wenruo <wqu@suse.com>
2026-08-10 12:38:57 -06:00

286 lines
6.1 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* BTRFS filesystem implementation for U-Boot
*
* 2017 Marek Behún, CZ.NIC, kabel@kernel.org
*/
#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;
int btrfs_probe(struct blk_desc *fs_dev_desc,
struct disk_partition *fs_partition)
{
struct btrfs_fs_info *fs_info;
int ret = -1;
btrfs_hash_init();
fs_info = open_ctree_fs_info(fs_dev_desc, fs_partition);
if (fs_info) {
current_fs_info = fs_info;
ret = 0;
}
return ret;
}
/*
* 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;
}
}
/*
* 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_root *root;
struct btrfs_key location;
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, &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;
}
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;
struct btrfs_root *root;
u64 ino;
u8 type;
int ret;
ASSERT(fs_info);
ret = btrfs_lookup_path(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID,
file, &root, &ino, &type, 40);
if (ret < 0)
return 0;
if (type == BTRFS_FT_REG_FILE)
return 1;
return 0;
}
int btrfs_size(const char *file, loff_t *size)
{
struct btrfs_fs_info *fs_info = current_fs_info;
struct btrfs_root *root;
u64 isize;
u64 ino;
u8 type;
int ret;
ret = btrfs_lookup_path(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID,
file, &root, &ino, &type, 40);
if (ret < 0) {
debug("Cannot lookup file %s\n", file);
return ret;
}
if (type != BTRFS_FT_REG_FILE) {
printf("Not a regular file: %s\n", file);
return -ENOENT;
}
ret = btrfs_get_inode_size(root, ino, &isize);
if (ret) {
printf("Cannot read size of ino %llu\n", ino);
return ret;
}
*size = isize;
return 0;
}
int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len,
loff_t *actread)
{
struct btrfs_fs_info *fs_info = current_fs_info;
struct btrfs_root *root;
loff_t real_size;
u64 ino;
u8 type;
int ret;
ASSERT(fs_info);
ret = btrfs_lookup_path(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID,
file, &root, &ino, &type, 40);
if (ret < 0) {
error("Cannot lookup file %s", file);
return ret;
}
if (type != BTRFS_FT_REG_FILE) {
error("Not a regular file: %s", file);
return -EINVAL;
}
ret = btrfs_size(file, &real_size);
if (ret < 0) {
error("Failed to get inode size: %s", file);
return ret;
}
if (!len || len > real_size - offset)
len = real_size - offset;
ret = btrfs_file_read(root, ino, offset, len, buf);
if (ret < 0) {
error("An error occurred while reading file %s", file);
return ret;
}
*actread = len;
return 0;
}
void btrfs_close(void)
{
if (current_fs_info) {
close_ctree_fs_info(current_fs_info);
current_fs_info = NULL;
}
}
int btrfs_uuid(char *uuid_str)
{
#ifdef CONFIG_LIB_UUID
if (current_fs_info)
uuid_bin_to_str(current_fs_info->super_copy->fsid, uuid_str,
UUID_STR_FORMAT_STD);
return 0;
#endif
return -ENOSYS;
}