| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * BTRFS filesystem implementation for U-Boot |
| * |
| * 2017 Marek Behun, CZ.NIC, marek.behun@nic.cz |
| */ |
| |
| #include <config.h> |
| #include <malloc.h> |
| #include <uuid.h> |
| #include <linux/time.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] = "FILE", |
| [BTRFS_FT_DIR] = "DIR", |
| [BTRFS_FT_CHRDEV] = "CHRDEV", |
| [BTRFS_FT_BLKDEV] = "BLKDEV", |
| [BTRFS_FT_FIFO] = "FIFO", |
| [BTRFS_FT_SOCK] = "SOCK", |
| [BTRFS_FT_SYMLINK] = "SYMLINK", |
| [BTRFS_FT_XATTR] = "XATTR" |
| }; |
| u8 type = btrfs_dir_type(eb, di); |
| char namebuf[BTRFS_NAME_LEN]; |
| char *target = NULL; |
| char filetime[32]; |
| time_t mtime; |
| int ret = 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("DIR_ITEM.%u", 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) |
| { |
| 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; |
| } |
| |
| int btrfs_ls(const char *path) |
| { |
| struct btrfs_fs_info *fs_info = current_fs_info; |
| struct btrfs_root *root = fs_info->fs_root; |
| u64 ino = BTRFS_FIRST_FREE_OBJECTID; |
| u8 type; |
| int ret; |
| |
| 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); |
| return ret; |
| } |
| |
| if (type != BTRFS_FT_DIR) { |
| error("Not a directory: %s", path); |
| return -ENOENT; |
| } |
| ret = btrfs_iter_dir(root, ino, show_dir); |
| if (ret < 0) { |
| error("An error occurred while listing directory %s", path); |
| return ret; |
| } |
| return 0; |
| } |
| |
| 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_inode_item *ii; |
| struct btrfs_root *root; |
| struct btrfs_path path; |
| struct btrfs_key key; |
| 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) { |
| printf("Cannot lookup file %s\n", file); |
| return ret; |
| } |
| if (type != BTRFS_FT_REG_FILE) { |
| 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); |
| 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; |
| } |
| |
| 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 = 0; |
| 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; |
| } |
| |
| if (!len) { |
| ret = btrfs_size(file, &real_size); |
| if (ret < 0) { |
| error("Failed to get inode size: %s", file); |
| return ret; |
| } |
| len = real_size; |
| } |
| |
| if (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; |
| } |