/* bfs.c - The Bee File System. */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2010,2011 Free Software Foundation, Inc. * * VAS_EBOOT is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VAS_EBOOT is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VAS_EBOOT. If not, see . */ /* Based on the book "Practical File System Design by Dominic Giampaolo with corrections and completitions based on Haiku code. */ #include #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); #ifdef MODE_AFS #define BTREE_ALIGN 4 #define SUPERBLOCK 2 #else #define BTREE_ALIGN 8 #define SUPERBLOCK 1 #endif #define VasEBoot_bfs_to_cpu16 VasEBoot_le_to_cpu16 #define VasEBoot_bfs_to_cpu32 VasEBoot_le_to_cpu32 #define VasEBoot_bfs_to_cpu64 VasEBoot_le_to_cpu64 #define VasEBoot_cpu_to_bfs32_compile_time VasEBoot_cpu_to_le32_compile_time #ifdef MODE_AFS #define VasEBoot_bfs_to_cpu_treehead VasEBoot_bfs_to_cpu32 #else #define VasEBoot_bfs_to_cpu_treehead VasEBoot_bfs_to_cpu16 #endif #ifdef MODE_AFS #define SUPER_BLOCK_MAGIC1 0x41465331 #else #define SUPER_BLOCK_MAGIC1 0x42465331 #endif #define SUPER_BLOCK_MAGIC2 0xdd121031 #define SUPER_BLOCK_MAGIC3 0x15b6830e #define POINTER_INVALID 0xffffffffffffffffULL #define ATTR_TYPE 0160000 #define ATTR_REG 0100000 #define ATTR_DIR 0040000 #define ATTR_LNK 0120000 #define DOUBLE_INDIRECT_SHIFT 2 #define LOG_EXTENT_SIZE 3 struct VasEBoot_bfs_extent { VasEBoot_uint32_t ag; VasEBoot_uint16_t start; VasEBoot_uint16_t len; } VAS_EBOOT_PACKED; struct VasEBoot_bfs_superblock { char label[32]; VasEBoot_uint32_t magic1; VasEBoot_uint32_t unused1; VasEBoot_uint32_t bsize; VasEBoot_uint32_t log2_bsize; VasEBoot_uint8_t unused[20]; VasEBoot_uint32_t magic2; VasEBoot_uint32_t unused2; VasEBoot_uint32_t log2_ag_size; VasEBoot_uint8_t unused3[32]; VasEBoot_uint32_t magic3; struct VasEBoot_bfs_extent root_dir; } VAS_EBOOT_PACKED; struct VasEBoot_bfs_inode { VasEBoot_uint8_t unused[20]; VasEBoot_uint32_t mode; VasEBoot_uint32_t flags; #ifdef MODE_AFS VasEBoot_uint8_t unused2[12]; #else VasEBoot_uint8_t unused2[8]; #endif VasEBoot_uint64_t mtime; VasEBoot_uint8_t unused3[8]; struct VasEBoot_bfs_extent attr; VasEBoot_uint8_t unused4[12]; union { struct { struct VasEBoot_bfs_extent direct[12]; VasEBoot_uint64_t max_direct_range; struct VasEBoot_bfs_extent indirect; VasEBoot_uint64_t max_indirect_range; struct VasEBoot_bfs_extent double_indirect; VasEBoot_uint64_t max_double_indirect_range; VasEBoot_uint64_t size; VasEBoot_uint32_t pad[4]; } VAS_EBOOT_PACKED; char inplace_link[144]; } VAS_EBOOT_PACKED; VasEBoot_uint8_t small_data[0]; } VAS_EBOOT_PACKED; enum { LONG_SYMLINK = 0x40 }; struct VasEBoot_bfs_small_data_element_header { VasEBoot_uint32_t type; VasEBoot_uint16_t name_len; VasEBoot_uint16_t value_len; } VAS_EBOOT_PACKED; struct VasEBoot_bfs_btree_header { VasEBoot_uint32_t magic; #ifdef MODE_AFS VasEBoot_uint64_t root; VasEBoot_uint32_t level; VasEBoot_uint32_t node_size; VasEBoot_uint32_t unused; #else VasEBoot_uint32_t node_size; VasEBoot_uint32_t level; VasEBoot_uint32_t unused; VasEBoot_uint64_t root; #endif VasEBoot_uint32_t unused2[2]; } VAS_EBOOT_PACKED; struct VasEBoot_bfs_btree_node { VasEBoot_uint64_t unused; VasEBoot_uint64_t right; VasEBoot_uint64_t overflow; #ifdef MODE_AFS VasEBoot_uint32_t count_keys; VasEBoot_uint32_t total_key_len; #else VasEBoot_uint16_t count_keys; VasEBoot_uint16_t total_key_len; #endif } VAS_EBOOT_PACKED; struct VasEBoot_bfs_data { struct VasEBoot_bfs_superblock sb; struct VasEBoot_bfs_inode ino; }; /* Context for VasEBoot_bfs_dir. */ struct VasEBoot_bfs_dir_ctx { VasEBoot_device_t device; VasEBoot_fs_dir_hook_t hook; void *hook_data; struct VasEBoot_bfs_superblock sb; }; static VasEBoot_err_t read_extent (VasEBoot_disk_t disk, const struct VasEBoot_bfs_superblock *sb, const struct VasEBoot_bfs_extent *in, VasEBoot_off_t off, VasEBoot_off_t byteoff, void *buf, VasEBoot_size_t len) { #ifdef MODE_AFS return VasEBoot_disk_read (disk, ((VasEBoot_bfs_to_cpu32 (in->ag) << (VasEBoot_bfs_to_cpu32 (sb->log2_ag_size) - VAS_EBOOT_DISK_SECTOR_BITS)) + ((VasEBoot_bfs_to_cpu16 (in->start) + off) << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - VAS_EBOOT_DISK_SECTOR_BITS))), byteoff, len, buf); #else return VasEBoot_disk_read (disk, (((VasEBoot_bfs_to_cpu32 (in->ag) << VasEBoot_bfs_to_cpu32 (sb->log2_ag_size)) + VasEBoot_bfs_to_cpu16 (in->start) + off) << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - VAS_EBOOT_DISK_SECTOR_BITS)), byteoff, len, buf); #endif } #ifdef MODE_AFS #define RANGE_SHIFT VasEBoot_bfs_to_cpu32 (sb->log2_bsize) #else #define RANGE_SHIFT 0 #endif static VasEBoot_err_t read_bfs_file (VasEBoot_disk_t disk, const struct VasEBoot_bfs_superblock *sb, const struct VasEBoot_bfs_inode *ino, VasEBoot_off_t off, void *buf, VasEBoot_size_t len, VasEBoot_disk_read_hook_t read_hook, void *read_hook_data) { if (len == 0) return VAS_EBOOT_ERR_NONE; if (off + len > VasEBoot_bfs_to_cpu64 (ino->size)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("attempt to read past the end of file")); if (off < (VasEBoot_bfs_to_cpu64 (ino->max_direct_range) << RANGE_SHIFT)) { unsigned i; VasEBoot_uint64_t pos = 0; for (i = 0; i < ARRAY_SIZE (ino->direct); i++) { VasEBoot_uint64_t newpos; newpos = pos + (((VasEBoot_uint64_t) VasEBoot_bfs_to_cpu16 (ino->direct[i].len)) << VasEBoot_bfs_to_cpu32 (sb->log2_bsize)); if (newpos > off) { VasEBoot_size_t read_size; VasEBoot_err_t err; read_size = newpos - off; if (read_size > len) read_size = len; disk->read_hook = read_hook; disk->read_hook_data = read_hook_data; err = read_extent (disk, sb, &ino->direct[i], 0, off - pos, buf, read_size); disk->read_hook = 0; if (err) return err; off += read_size; len -= read_size; buf = (char *) buf + read_size; if (len == 0) return VAS_EBOOT_ERR_NONE; } pos = newpos; } } if (off < (VasEBoot_bfs_to_cpu64 (ino->max_direct_range) << RANGE_SHIFT)) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "incorrect direct blocks"); if (off < (VasEBoot_bfs_to_cpu64 (ino->max_indirect_range) << RANGE_SHIFT)) { unsigned i; struct VasEBoot_bfs_extent *entries; VasEBoot_size_t nentries; VasEBoot_err_t err; VasEBoot_uint64_t pos = (VasEBoot_bfs_to_cpu64 (ino->max_direct_range) << RANGE_SHIFT); nentries = (((VasEBoot_size_t) VasEBoot_bfs_to_cpu16 (ino->indirect.len)) << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE)); entries = VasEBoot_malloc (nentries << LOG_EXTENT_SIZE); if (!entries) return VasEBoot_errno; err = read_extent (disk, sb, &ino->indirect, 0, 0, entries, nentries << LOG_EXTENT_SIZE); for (i = 0; i < nentries; i++) { VasEBoot_uint64_t newpos; newpos = pos + (((VasEBoot_uint64_t) VasEBoot_bfs_to_cpu16 (entries[i].len)) << VasEBoot_bfs_to_cpu32 (sb->log2_bsize)); if (newpos > off) { VasEBoot_size_t read_size; read_size = newpos - off; if (read_size > len) read_size = len; disk->read_hook = read_hook; disk->read_hook_data = read_hook_data; err = read_extent (disk, sb, &entries[i], 0, off - pos, buf, read_size); disk->read_hook = 0; if (err) { VasEBoot_free (entries); return err; } off += read_size; len -= read_size; buf = (char *) buf + read_size; if (len == 0) { VasEBoot_free (entries); return VAS_EBOOT_ERR_NONE; } } pos = newpos; } VasEBoot_free (entries); } if (off < (VasEBoot_bfs_to_cpu64 (ino->max_indirect_range) << RANGE_SHIFT)) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "incorrect indirect blocks"); { struct VasEBoot_bfs_extent *l1_entries, *l2_entries; VasEBoot_size_t nl1_entries, nl2_entries; VasEBoot_off_t last_l1n = ~0ULL; VasEBoot_err_t err; nl1_entries = (((VasEBoot_uint64_t) VasEBoot_bfs_to_cpu16 (ino->double_indirect.len)) << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE)); l1_entries = VasEBoot_malloc (nl1_entries << LOG_EXTENT_SIZE); if (!l1_entries) return VasEBoot_errno; nl2_entries = 0; l2_entries = VasEBoot_malloc (1 << (DOUBLE_INDIRECT_SHIFT + VasEBoot_bfs_to_cpu32 (sb->log2_bsize))); if (!l2_entries) { VasEBoot_free (l1_entries); return VasEBoot_errno; } err = read_extent (disk, sb, &ino->double_indirect, 0, 0, l1_entries, nl1_entries << LOG_EXTENT_SIZE); if (err) { VasEBoot_free (l1_entries); VasEBoot_free (l2_entries); return err; } while (len > 0) { VasEBoot_off_t boff, l2n, l1n; VasEBoot_size_t read_size; VasEBoot_off_t double_indirect_offset; double_indirect_offset = off - VasEBoot_bfs_to_cpu64 (ino->max_indirect_range); boff = (double_indirect_offset & ((1 << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) + DOUBLE_INDIRECT_SHIFT)) - 1)); l2n = ((double_indirect_offset >> (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) + DOUBLE_INDIRECT_SHIFT)) & ((1 << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE + DOUBLE_INDIRECT_SHIFT)) - 1)); l1n = (double_indirect_offset >> (2 * VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE + 2 * DOUBLE_INDIRECT_SHIFT)); if (l1n > nl1_entries) { VasEBoot_free (l1_entries); VasEBoot_free (l2_entries); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "incorrect double-indirect block"); } if (l1n != last_l1n) { nl2_entries = (((VasEBoot_uint64_t) VasEBoot_bfs_to_cpu16 (l1_entries[l1n].len)) << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE)); if (nl2_entries > (1U << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE + DOUBLE_INDIRECT_SHIFT))) nl2_entries = (1 << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE + DOUBLE_INDIRECT_SHIFT)); err = read_extent (disk, sb, &l1_entries[l1n], 0, 0, l2_entries, nl2_entries << LOG_EXTENT_SIZE); if (err) { VasEBoot_free (l1_entries); VasEBoot_free (l2_entries); return err; } last_l1n = l1n; } if (l2n > nl2_entries) { VasEBoot_free (l1_entries); VasEBoot_free (l2_entries); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "incorrect double-indirect block"); } read_size = (1 << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) + DOUBLE_INDIRECT_SHIFT)) - boff; if (read_size > len) read_size = len; disk->read_hook = read_hook; disk->read_hook_data = read_hook_data; err = read_extent (disk, sb, &l2_entries[l2n], 0, boff, buf, read_size); disk->read_hook = 0; if (err) { VasEBoot_free (l1_entries); VasEBoot_free (l2_entries); return err; } off += read_size; len -= read_size; buf = (char *) buf + read_size; } VasEBoot_free (l1_entries); VasEBoot_free (l2_entries); return VAS_EBOOT_ERR_NONE; } } static VasEBoot_err_t read_b_node (VasEBoot_disk_t disk, const struct VasEBoot_bfs_superblock *sb, const struct VasEBoot_bfs_inode *ino, VasEBoot_uint64_t node_off, struct VasEBoot_bfs_btree_node **node, char **key_data, VasEBoot_uint16_t **keylen_idx, VasEBoot_unaligned_uint64_t **key_values) { void *ret; struct VasEBoot_bfs_btree_node node_head; VasEBoot_size_t total_size; VasEBoot_err_t err; *node = NULL; *key_data = NULL; *keylen_idx = NULL; *key_values = NULL; err = read_bfs_file (disk, sb, ino, node_off, &node_head, sizeof (node_head), 0, 0); if (err) return err; total_size = ALIGN_UP (sizeof (node_head) + VasEBoot_bfs_to_cpu_treehead (node_head.total_key_len), BTREE_ALIGN) + VasEBoot_bfs_to_cpu_treehead (node_head.count_keys) * sizeof (VasEBoot_uint16_t) + VasEBoot_bfs_to_cpu_treehead (node_head.count_keys) * sizeof (VasEBoot_uint64_t); ret = VasEBoot_malloc (total_size); if (!ret) return VasEBoot_errno; err = read_bfs_file (disk, sb, ino, node_off, ret, total_size, 0, 0); if (err) { VasEBoot_free (ret); return err; } *node = ret; *key_data = (char *) ret + sizeof (node_head); *keylen_idx = (VasEBoot_uint16_t *) ret + ALIGN_UP (sizeof (node_head) + VasEBoot_bfs_to_cpu_treehead (node_head.total_key_len), BTREE_ALIGN) / 2; *key_values = (VasEBoot_unaligned_uint64_t *) (*keylen_idx + VasEBoot_bfs_to_cpu_treehead (node_head.count_keys)); return VAS_EBOOT_ERR_NONE; } static int iterate_in_b_tree (VasEBoot_disk_t disk, const struct VasEBoot_bfs_superblock *sb, const struct VasEBoot_bfs_inode *ino, int (*hook) (const char *name, VasEBoot_uint64_t value, struct VasEBoot_bfs_dir_ctx *ctx), struct VasEBoot_bfs_dir_ctx *ctx) { struct VasEBoot_bfs_btree_header head; VasEBoot_err_t err; int level; VasEBoot_uint64_t node_off; err = read_bfs_file (disk, sb, ino, 0, &head, sizeof (head), 0, 0); if (err) return 0; node_off = VasEBoot_bfs_to_cpu64 (head.root); level = VasEBoot_bfs_to_cpu32 (head.level) - 1; while (level--) { struct VasEBoot_bfs_btree_node node; VasEBoot_uint64_t key_value; err = read_bfs_file (disk, sb, ino, node_off, &node, sizeof (node), 0, 0); if (err) return 0; err = read_bfs_file (disk, sb, ino, node_off + ALIGN_UP (sizeof (node) + VasEBoot_bfs_to_cpu_treehead (node. total_key_len), BTREE_ALIGN) + VasEBoot_bfs_to_cpu_treehead (node.count_keys) * sizeof (VasEBoot_uint16_t), &key_value, sizeof (VasEBoot_uint64_t), 0, 0); if (err) return 0; node_off = VasEBoot_bfs_to_cpu64 (key_value); } while (1) { struct VasEBoot_bfs_btree_node *node; char *key_data; VasEBoot_uint16_t *keylen_idx; VasEBoot_unaligned_uint64_t *key_values; unsigned i; VasEBoot_uint16_t start = 0, end = 0; err = read_b_node (disk, sb, ino, node_off, &node, &key_data, &keylen_idx, &key_values); if (err) return 0; for (i = 0; i < VasEBoot_bfs_to_cpu_treehead (node->count_keys); i++) { char c; start = end; end = VasEBoot_bfs_to_cpu16 (keylen_idx[i]); if (VasEBoot_bfs_to_cpu_treehead (node->total_key_len) <= end) end = VasEBoot_bfs_to_cpu_treehead (node->total_key_len); c = key_data[end]; key_data[end] = 0; if (hook (key_data + start, VasEBoot_bfs_to_cpu64 (key_values[i].val), ctx)) { VasEBoot_free (node); return 1; } key_data[end] = c; } node_off = VasEBoot_bfs_to_cpu64 (node->right); VasEBoot_free (node); if (node_off == POINTER_INVALID) return 0; } } static int bfs_strcmp (const char *a, const char *b, VasEBoot_size_t alen) { char ac, bc; while (*b && alen) { if (*a != *b) break; a++; b++; alen--; } ac = alen ? *a : 0; bc = *b; #ifdef MODE_AFS return (int) (VasEBoot_int8_t) ac - (int) (VasEBoot_int8_t) bc; #else return (int) (VasEBoot_uint8_t) ac - (int) (VasEBoot_uint8_t) bc; #endif } static VasEBoot_err_t find_in_b_tree (VasEBoot_disk_t disk, const struct VasEBoot_bfs_superblock *sb, const struct VasEBoot_bfs_inode *ino, const char *name, VasEBoot_uint64_t * res) { struct VasEBoot_bfs_btree_header head; VasEBoot_err_t err; int level; VasEBoot_uint64_t node_off; err = read_bfs_file (disk, sb, ino, 0, &head, sizeof (head), 0, 0); if (err) return err; node_off = VasEBoot_bfs_to_cpu64 (head.root); level = VasEBoot_bfs_to_cpu32 (head.level) - 1; while (1) { struct VasEBoot_bfs_btree_node *node; char *key_data; VasEBoot_uint16_t *keylen_idx; VasEBoot_unaligned_uint64_t *key_values; int lg, j; unsigned i; err = read_b_node (disk, sb, ino, node_off, &node, &key_data, &keylen_idx, &key_values); if (err) return err; if (node->count_keys == 0) { VasEBoot_free (node); return VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); } for (lg = 0; VasEBoot_bfs_to_cpu_treehead (node->count_keys) >> lg; lg++); i = 0; for (j = lg - 1; j >= 0; j--) { int cmp; VasEBoot_uint16_t start = 0, end = 0; if ((i | (1 << j)) >= VasEBoot_bfs_to_cpu_treehead (node->count_keys)) continue; start = VasEBoot_bfs_to_cpu16 (keylen_idx[(i | (1 << j)) - 1]); end = VasEBoot_bfs_to_cpu16 (keylen_idx[(i | (1 << j))]); if (VasEBoot_bfs_to_cpu_treehead (node->total_key_len) <= end) end = VasEBoot_bfs_to_cpu_treehead (node->total_key_len); cmp = bfs_strcmp (key_data + start, name, end - start); if (cmp == 0 && level == 0) { *res = VasEBoot_bfs_to_cpu64 (key_values[i | (1 << j)].val); VasEBoot_free (node); return VAS_EBOOT_ERR_NONE; } #ifdef MODE_AFS if (cmp <= 0) #else if (cmp < 0) #endif i |= (1 << j); } if (i == 0) { VasEBoot_uint16_t end = 0; int cmp; end = VasEBoot_bfs_to_cpu16 (keylen_idx[0]); if (VasEBoot_bfs_to_cpu_treehead (node->total_key_len) <= end) end = VasEBoot_bfs_to_cpu_treehead (node->total_key_len); cmp = bfs_strcmp (key_data, name, end); if (cmp == 0 && level == 0) { *res = VasEBoot_bfs_to_cpu64 (key_values[0].val); VasEBoot_free (node); return VAS_EBOOT_ERR_NONE; } #ifdef MODE_AFS if (cmp > 0 && level != 0) #else if (cmp >= 0 && level != 0) #endif { node_off = VasEBoot_bfs_to_cpu64 (key_values[0].val); level--; VasEBoot_free (node); continue; } else if (level != 0 && VasEBoot_bfs_to_cpu_treehead (node->count_keys) >= 2) { node_off = VasEBoot_bfs_to_cpu64 (key_values[1].val); level--; VasEBoot_free (node); continue; } } else if (level != 0 && i + 1 < VasEBoot_bfs_to_cpu_treehead (node->count_keys)) { node_off = VasEBoot_bfs_to_cpu64 (key_values[i + 1].val); level--; VasEBoot_free (node); continue; } if (node->overflow != POINTER_INVALID) { node_off = VasEBoot_bfs_to_cpu64 (node->overflow); /* This level-- isn't specified but is needed. */ level--; VasEBoot_free (node); continue; } VasEBoot_free (node); return VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); } } struct VasEBoot_fshelp_node { VasEBoot_disk_t disk; const struct VasEBoot_bfs_superblock *sb; struct VasEBoot_bfs_inode ino; }; static VasEBoot_err_t lookup_file (VasEBoot_fshelp_node_t dir, const char *name, VasEBoot_fshelp_node_t *foundnode, enum VasEBoot_fshelp_filetype *foundtype) { VasEBoot_err_t err; struct VasEBoot_bfs_inode *new_ino; VasEBoot_uint64_t res = 0; err = find_in_b_tree (dir->disk, dir->sb, &dir->ino, name, &res); if (err) return err; *foundnode = VasEBoot_malloc (sizeof (struct VasEBoot_fshelp_node)); if (!*foundnode) return VasEBoot_errno; (*foundnode)->disk = dir->disk; (*foundnode)->sb = dir->sb; new_ino = &(*foundnode)->ino; if (VasEBoot_disk_read (dir->disk, res << (VasEBoot_bfs_to_cpu32 (dir->sb->log2_bsize) - VAS_EBOOT_DISK_SECTOR_BITS), 0, sizeof (*new_ino), (char *) new_ino)) { VasEBoot_free (*foundnode); return VasEBoot_errno; } switch (VasEBoot_bfs_to_cpu32 (new_ino->mode) & ATTR_TYPE) { default: case ATTR_REG: *foundtype = VAS_EBOOT_FSHELP_REG; break; case ATTR_DIR: *foundtype = VAS_EBOOT_FSHELP_DIR; break; case ATTR_LNK: *foundtype = VAS_EBOOT_FSHELP_SYMLINK; break; } return VAS_EBOOT_ERR_NONE; } static char * read_symlink (VasEBoot_fshelp_node_t node) { char *alloc = NULL; VasEBoot_err_t err; #ifndef MODE_AFS if (!(VasEBoot_bfs_to_cpu32 (node->ino.flags) & LONG_SYMLINK)) { alloc = VasEBoot_malloc (sizeof (node->ino.inplace_link) + 1); if (!alloc) { return NULL; } VasEBoot_memcpy (alloc, node->ino.inplace_link, sizeof (node->ino.inplace_link)); alloc[sizeof (node->ino.inplace_link)] = 0; } else #endif { VasEBoot_size_t symsize = VasEBoot_bfs_to_cpu64 (node->ino.size); alloc = VasEBoot_malloc (symsize + 1); if (!alloc) return NULL; err = read_bfs_file (node->disk, node->sb, &node->ino, 0, alloc, symsize, 0, 0); if (err) { VasEBoot_free (alloc); return NULL; } alloc[symsize] = 0; } return alloc; } static VasEBoot_err_t find_file (const char *path, VasEBoot_disk_t disk, const struct VasEBoot_bfs_superblock *sb, struct VasEBoot_bfs_inode *ino, enum VasEBoot_fshelp_filetype exptype) { VasEBoot_err_t err; struct VasEBoot_fshelp_node root = { .disk = disk, .sb = sb, }; struct VasEBoot_fshelp_node *found = NULL; err = read_extent (disk, sb, &sb->root_dir, 0, 0, &root.ino, sizeof (root.ino)); if (err) return err; err = VasEBoot_fshelp_find_file_lookup (path, &root, &found, lookup_file, read_symlink, exptype); if (!err) VasEBoot_memcpy (ino, &found->ino, sizeof (*ino)); if (&root != found) VasEBoot_free (found); return err; } static VasEBoot_err_t mount (VasEBoot_disk_t disk, struct VasEBoot_bfs_superblock *sb) { VasEBoot_err_t err; err = VasEBoot_disk_read (disk, SUPERBLOCK, 0, sizeof (*sb), sb); if (err == VAS_EBOOT_ERR_OUT_OF_RANGE) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, #ifdef MODE_AFS "not an AFS filesystem" #else "not a BFS filesystem" #endif ); if (err) return err; if (sb->magic1 != VasEBoot_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC1) || sb->magic2 != VasEBoot_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC2) || sb->magic3 != VasEBoot_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC3) || sb->bsize == 0 || (VasEBoot_bfs_to_cpu32 (sb->bsize) != (1U << VasEBoot_bfs_to_cpu32 (sb->log2_bsize))) || VasEBoot_bfs_to_cpu32 (sb->log2_bsize) < VAS_EBOOT_DISK_SECTOR_BITS) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, #ifdef MODE_AFS "not an AFS filesystem" #else "not a BFS filesystem" #endif ); return VAS_EBOOT_ERR_NONE; } /* Helper for VasEBoot_bfs_dir. */ static int VasEBoot_bfs_dir_iter (const char *name, VasEBoot_uint64_t value, struct VasEBoot_bfs_dir_ctx *ctx) { VasEBoot_err_t err2; struct VasEBoot_bfs_inode ino; struct VasEBoot_dirhook_info info; err2 = VasEBoot_disk_read (ctx->device->disk, value << (VasEBoot_bfs_to_cpu32 (ctx->sb.log2_bsize) - VAS_EBOOT_DISK_SECTOR_BITS), 0, sizeof (ino), (char *) &ino); if (err2) { VasEBoot_print_error (); return 0; } info.mtimeset = 1; #ifdef MODE_AFS info.mtime = VasEBoot_divmod64 (VasEBoot_bfs_to_cpu64 (ino.mtime), 1000000, 0); #else info.mtime = VasEBoot_bfs_to_cpu64 (ino.mtime) >> 16; #endif info.dir = ((VasEBoot_bfs_to_cpu32 (ino.mode) & ATTR_TYPE) == ATTR_DIR); return ctx->hook (name, &info, ctx->hook_data); } static VasEBoot_err_t VasEBoot_bfs_dir (VasEBoot_device_t device, const char *path, VasEBoot_fs_dir_hook_t hook, void *hook_data) { struct VasEBoot_bfs_dir_ctx ctx = { .device = device, .hook = hook, .hook_data = hook_data }; VasEBoot_err_t err; err = mount (device->disk, &ctx.sb); if (err) return err; { struct VasEBoot_bfs_inode ino; err = find_file (path, device->disk, &ctx.sb, &ino, VAS_EBOOT_FSHELP_DIR); if (err) return err; iterate_in_b_tree (device->disk, &ctx.sb, &ino, VasEBoot_bfs_dir_iter, &ctx); } return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_bfs_open (struct VasEBoot_file *file, const char *name) { struct VasEBoot_bfs_superblock sb; VasEBoot_err_t err; err = mount (file->device->disk, &sb); if (err) return err; { struct VasEBoot_bfs_inode ino; struct VasEBoot_bfs_data *data; err = find_file (name, file->device->disk, &sb, &ino, VAS_EBOOT_FSHELP_REG); if (err) return err; data = VasEBoot_zalloc (sizeof (struct VasEBoot_bfs_data)); if (!data) return VasEBoot_errno; data->sb = sb; VasEBoot_memcpy (&data->ino, &ino, sizeof (data->ino)); file->data = data; file->size = VasEBoot_bfs_to_cpu64 (ino.size); } return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_bfs_close (VasEBoot_file_t file) { VasEBoot_free (file->data); return VAS_EBOOT_ERR_NONE; } static VasEBoot_ssize_t VasEBoot_bfs_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len) { VasEBoot_err_t err; struct VasEBoot_bfs_data *data = file->data; err = read_bfs_file (file->device->disk, &data->sb, &data->ino, file->offset, buf, len, file->read_hook, file->read_hook_data); if (err) return -1; return len; } static VasEBoot_err_t VasEBoot_bfs_label (VasEBoot_device_t device, char **label) { struct VasEBoot_bfs_superblock sb; VasEBoot_err_t err; *label = 0; err = mount (device->disk, &sb); if (err) return err; *label = VasEBoot_strndup (sb.label, sizeof (sb.label)); return VAS_EBOOT_ERR_NONE; } #ifndef MODE_AFS static VasEBoot_ssize_t read_bfs_attr (VasEBoot_disk_t disk, const struct VasEBoot_bfs_superblock *sb, struct VasEBoot_bfs_inode *ino, const char *name, void *buf, VasEBoot_size_t len) { VasEBoot_uint8_t *ptr = (VasEBoot_uint8_t *) ino->small_data; VasEBoot_uint8_t *end = ((VasEBoot_uint8_t *) ino + VasEBoot_bfs_to_cpu32 (sb->bsize)); while (ptr + sizeof (struct VasEBoot_bfs_small_data_element_header) < end) { struct VasEBoot_bfs_small_data_element_header *el; char *el_name; VasEBoot_uint8_t *data; el = (struct VasEBoot_bfs_small_data_element_header *) ptr; if (el->name_len == 0) break; el_name = (char *) (el + 1); data = (VasEBoot_uint8_t *) el_name + VasEBoot_bfs_to_cpu16 (el->name_len) + 3; ptr = data + VasEBoot_bfs_to_cpu16 (el->value_len) + 1; if (VasEBoot_memcmp (name, el_name, VasEBoot_bfs_to_cpu16 (el->name_len)) == 0 && name[el->name_len] == 0) { VasEBoot_size_t copy; copy = len; if (VasEBoot_bfs_to_cpu16 (el->value_len) > copy) copy = VasEBoot_bfs_to_cpu16 (el->value_len); VasEBoot_memcpy (buf, data, copy); return copy; } } if (ino->attr.len != 0) { VasEBoot_size_t read; VasEBoot_err_t err; VasEBoot_uint64_t res; err = read_extent (disk, sb, &ino->attr, 0, 0, ino, VasEBoot_bfs_to_cpu32 (sb->bsize)); if (err) return -1; err = find_in_b_tree (disk, sb, ino, name, &res); if (err) return -1; VasEBoot_disk_read (disk, res << (VasEBoot_bfs_to_cpu32 (sb->log2_bsize) - VAS_EBOOT_DISK_SECTOR_BITS), 0, VasEBoot_bfs_to_cpu32 (sb->bsize), (char *) ino); read = VasEBoot_bfs_to_cpu64 (ino->size); if (read > len) read = len; err = read_bfs_file (disk, sb, ino, 0, buf, read, 0, 0); if (err) return -1; return read; } return -1; } static VasEBoot_err_t VasEBoot_bfs_uuid (VasEBoot_device_t device, char **uuid) { struct VasEBoot_bfs_superblock sb; VasEBoot_err_t err; struct VasEBoot_bfs_inode *ino; VasEBoot_uint64_t vid; *uuid = 0; err = mount (device->disk, &sb); if (err) return err; ino = VasEBoot_malloc (VasEBoot_bfs_to_cpu32 (sb.bsize)); if (!ino) return VasEBoot_errno; err = read_extent (device->disk, &sb, &sb.root_dir, 0, 0, ino, VasEBoot_bfs_to_cpu32 (sb.bsize)); if (err) { VasEBoot_free (ino); return err; } if (read_bfs_attr (device->disk, &sb, ino, "be:volume_id", &vid, sizeof (vid)) == sizeof (vid)) *uuid = VasEBoot_xasprintf ("%016" PRIxVAS_EBOOT_UINT64_T, VasEBoot_bfs_to_cpu64 (vid)); VasEBoot_free (ino); return VAS_EBOOT_ERR_NONE; } #endif static struct VasEBoot_fs VasEBoot_bfs_fs = { #ifdef MODE_AFS .name = "afs", #else .name = "bfs", #endif .fs_dir = VasEBoot_bfs_dir, .fs_open = VasEBoot_bfs_open, .fs_read = VasEBoot_bfs_read, .fs_close = VasEBoot_bfs_close, .fs_label = VasEBoot_bfs_label, #ifndef MODE_AFS .fs_uuid = VasEBoot_bfs_uuid, #endif #ifdef VAS_EBOOT_UTIL .reserved_first_sector = 1, .blocklist_install = 1, #endif }; #ifdef MODE_AFS VAS_EBOOT_MOD_INIT (afs) #else VAS_EBOOT_MOD_INIT (bfs) #endif { COMPILE_TIME_ASSERT (1 << LOG_EXTENT_SIZE == sizeof (struct VasEBoot_bfs_extent)); if (!VasEBoot_is_lockdown ()) { VasEBoot_bfs_fs.mod = mod; VasEBoot_fs_register (&VasEBoot_bfs_fs); } } #ifdef MODE_AFS VAS_EBOOT_MOD_FINI (afs) #else VAS_EBOOT_MOD_FINI (bfs) #endif { if (!VasEBoot_is_lockdown ()) VasEBoot_fs_unregister (&VasEBoot_bfs_fs); }