/* sfs.c - Amiga Smart FileSystem. */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2005,2006,2007,2008,2009 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 . */ #include #include #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); /* The common header for a block. */ struct VasEBoot_sfs_bheader { VasEBoot_uint8_t magic[4]; VasEBoot_uint32_t chksum; VasEBoot_uint32_t ipointtomyself; } VAS_EBOOT_PACKED; /* The sfs rootblock. */ struct VasEBoot_sfs_rblock { struct VasEBoot_sfs_bheader header; VasEBoot_uint32_t version; VasEBoot_uint32_t createtime; VasEBoot_uint8_t flags; VasEBoot_uint8_t unused1[31]; VasEBoot_uint32_t blocksize; VasEBoot_uint8_t unused2[40]; VasEBoot_uint8_t unused3[8]; VasEBoot_uint32_t rootobject; VasEBoot_uint32_t btree; } VAS_EBOOT_PACKED; enum { FLAGS_CASE_SENSITIVE = 0x80 }; /* A SFS object container. */ struct VasEBoot_sfs_obj { VasEBoot_uint8_t unused1[4]; VasEBoot_uint32_t nodeid; VasEBoot_uint8_t unused2[4]; union { struct { VasEBoot_uint32_t first_block; VasEBoot_uint32_t size; } VAS_EBOOT_PACKED file; struct { VasEBoot_uint32_t hashtable; VasEBoot_uint32_t dir_objc; } VAS_EBOOT_PACKED dir; } file_dir; VasEBoot_uint32_t mtime; VasEBoot_uint8_t type; VasEBoot_uint8_t filename[1]; VasEBoot_uint8_t comment[1]; } VAS_EBOOT_PACKED; #define VAS_EBOOT_SFS_TYPE_DELETED 32 #define VAS_EBOOT_SFS_TYPE_SYMLINK 64 #define VAS_EBOOT_SFS_TYPE_DIR 128 /* A SFS object container. */ struct VasEBoot_sfs_objc { struct VasEBoot_sfs_bheader header; VasEBoot_uint32_t parent; VasEBoot_uint32_t next; VasEBoot_uint32_t prev; /* The amount of objects depends on the blocksize. */ struct VasEBoot_sfs_obj objects[1]; } VAS_EBOOT_PACKED; struct VasEBoot_sfs_btree_node { VasEBoot_uint32_t key; VasEBoot_uint32_t data; } VAS_EBOOT_PACKED; struct VasEBoot_sfs_btree_extent { VasEBoot_uint32_t key; VasEBoot_uint32_t next; VasEBoot_uint32_t prev; VasEBoot_uint16_t size; } VAS_EBOOT_PACKED; struct VasEBoot_sfs_btree { struct VasEBoot_sfs_bheader header; VasEBoot_uint16_t nodes; VasEBoot_uint8_t leaf; VasEBoot_uint8_t nodesize; /* Normally this can be kind of node, but just extents are supported. */ struct VasEBoot_sfs_btree_node node[1]; } VAS_EBOOT_PACKED; struct cache_entry { VasEBoot_uint32_t off; VasEBoot_uint32_t block; }; struct VasEBoot_fshelp_node { struct VasEBoot_sfs_data *data; VasEBoot_uint32_t block; VasEBoot_uint32_t size; VasEBoot_uint32_t mtime; VasEBoot_uint32_t cache_off; VasEBoot_uint32_t next_extent; VasEBoot_size_t cache_allocated; VasEBoot_size_t cache_size; struct cache_entry *cache; }; /* Information about a "mounted" sfs filesystem. */ struct VasEBoot_sfs_data { struct VasEBoot_sfs_rblock rblock; struct VasEBoot_fshelp_node diropen; VasEBoot_disk_t disk; /* Log of blocksize in sectors. */ int log_blocksize; int fshelp_flags; /* Label of the filesystem. */ char *label; }; static VasEBoot_dl_t my_mod; /* Lookup the extent starting with BLOCK in the filesystem described by DATA. Return the extent size in SIZE and the following extent in NEXTEXT. */ static VasEBoot_err_t VasEBoot_sfs_read_extent (struct VasEBoot_sfs_data *data, unsigned int block, VasEBoot_uint32_t *size, VasEBoot_uint32_t *nextext) { char *treeblock; struct VasEBoot_sfs_btree *tree; int i; VasEBoot_uint32_t next; VasEBoot_size_t blocksize = VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize; treeblock = VasEBoot_malloc (blocksize); if (!treeblock) return VasEBoot_errno; next = VasEBoot_be_to_cpu32 (data->rblock.btree); tree = (struct VasEBoot_sfs_btree *) treeblock; /* Handle this level in the btree. */ do { VasEBoot_uint16_t nnodes; VasEBoot_disk_read (data->disk, ((VasEBoot_disk_addr_t) next) << data->log_blocksize, 0, blocksize, treeblock); if (VasEBoot_errno) { VasEBoot_free (treeblock); return VasEBoot_errno; } nnodes = VasEBoot_be_to_cpu16 (tree->nodes); if (nnodes * (VasEBoot_uint32_t) (tree)->nodesize > blocksize) break; for (i = (int) nnodes - 1; i >= 0; i--) { #define EXTNODE(tree, index) \ ((struct VasEBoot_sfs_btree_node *) (((char *) &(tree)->node[0]) \ + (index) * (tree)->nodesize)) /* Follow the tree down to the leaf level. */ if ((VasEBoot_be_to_cpu32 (EXTNODE(tree, i)->key) <= block) && !tree->leaf) { next = VasEBoot_be_to_cpu32 (EXTNODE (tree, i)->data); break; } /* If the leaf level is reached, just find the correct extent. */ if (VasEBoot_be_to_cpu32 (EXTNODE (tree, i)->key) == block && tree->leaf) { struct VasEBoot_sfs_btree_extent *extent; extent = (struct VasEBoot_sfs_btree_extent *) EXTNODE (tree, i); /* We found a correct leaf. */ *size = VasEBoot_be_to_cpu16 (extent->size); *nextext = VasEBoot_be_to_cpu32 (extent->next); VasEBoot_free (treeblock); return 0; } #undef EXTNODE } } while (!tree->leaf); VasEBoot_free (treeblock); return VasEBoot_error (VAS_EBOOT_ERR_FILE_READ_ERROR, "SFS extent not found"); } static VasEBoot_disk_addr_t VasEBoot_sfs_read_block (VasEBoot_fshelp_node_t node, VasEBoot_disk_addr_t fileblock) { VasEBoot_uint32_t blk; VasEBoot_uint32_t size = 0; VasEBoot_uint32_t next = 0; VasEBoot_disk_addr_t off; struct VasEBoot_sfs_data *data = node->data; /* In case of the first block we don't have to lookup the extent, the minimum size is always 1. */ if (fileblock == 0) return node->block; if (!node->cache) { VasEBoot_size_t cache_size; /* Assume half-max extents (32768 sectors). */ cache_size = ((node->size >> (data->log_blocksize + VAS_EBOOT_DISK_SECTOR_BITS + 15)) + 3); if (cache_size < 8) cache_size = 8; node->cache_off = 0; node->next_extent = node->block; node->cache_size = 0; node->cache = VasEBoot_calloc (cache_size, sizeof (node->cache[0])); if (!node->cache) { VasEBoot_errno = 0; node->cache_allocated = 0; } else { node->cache_allocated = cache_size; node->cache[0].off = 0; node->cache[0].block = node->block; } } if (fileblock < node->cache_off) { unsigned int i = 0; int j, lg; for (lg = 0; node->cache_size >> lg; lg++); for (j = lg - 1; j >= 0; j--) if ((i | (1 << j)) < node->cache_size && node->cache[(i | (1 << j))].off <= fileblock) i |= (1 << j); return node->cache[i].block + fileblock - node->cache[i].off; } off = node->cache_off; blk = node->next_extent; while (blk) { VasEBoot_err_t err; err = VasEBoot_sfs_read_extent (node->data, blk, &size, &next); if (err) return 0; if (node->cache && node->cache_size >= node->cache_allocated) { struct cache_entry *e = node->cache; VasEBoot_size_t sz; if (VasEBoot_mul (node->cache_allocated, 2 * sizeof (e[0]), &sz)) goto fail; e = VasEBoot_realloc (node->cache, sz); if (!e) { fail: VasEBoot_errno = 0; VasEBoot_free (node->cache); node->cache = 0; } else { node->cache_allocated *= 2; node->cache = e; } } if (node->cache) { node->cache_off = off + size; node->next_extent = next; node->cache[node->cache_size].off = off; node->cache[node->cache_size].block = blk; node->cache_size++; } if (fileblock - off < size) return fileblock - off + blk; off += size; blk = next; } VasEBoot_error (VAS_EBOOT_ERR_FILE_READ_ERROR, "reading a SFS block outside the extent"); return 0; } /* Read LEN bytes from the file described by DATA starting with byte POS. Return the amount of read bytes in READ. */ static VasEBoot_ssize_t VasEBoot_sfs_read_file (VasEBoot_fshelp_node_t node, VasEBoot_disk_read_hook_t read_hook, void *read_hook_data, VasEBoot_off_t pos, VasEBoot_size_t len, char *buf) { return VasEBoot_fshelp_read_file (node->data->disk, node, read_hook, read_hook_data, pos, len, buf, VasEBoot_sfs_read_block, node->size, node->data->log_blocksize, 0); } static struct VasEBoot_sfs_data * VasEBoot_sfs_mount (VasEBoot_disk_t disk) { struct VasEBoot_sfs_data *data; struct VasEBoot_sfs_objc *rootobjc; char *rootobjc_data = 0; VasEBoot_uint32_t blk; unsigned int max_len; data = VasEBoot_malloc (sizeof (*data)); if (!data) return 0; /* Read the rootblock. */ VasEBoot_disk_read (disk, 0, 0, sizeof (struct VasEBoot_sfs_rblock), &data->rblock); if (VasEBoot_errno) goto fail; /* Make sure this is a sfs filesystem. */ if (VasEBoot_strncmp ((char *) (data->rblock.header.magic), "SFS", 4) || data->rblock.blocksize == 0 || (data->rblock.blocksize & (data->rblock.blocksize - 1)) != 0 || (data->rblock.blocksize & VasEBoot_cpu_to_be32_compile_time (0xf00001ff))) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not a SFS filesystem"); goto fail; } for (data->log_blocksize = 9; (1U << data->log_blocksize) < VasEBoot_be_to_cpu32 (data->rblock.blocksize); data->log_blocksize++); data->log_blocksize -= VAS_EBOOT_DISK_SECTOR_BITS; if (data->rblock.flags & FLAGS_CASE_SENSITIVE) data->fshelp_flags = 0; else data->fshelp_flags = VAS_EBOOT_FSHELP_CASE_INSENSITIVE; rootobjc_data = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize); if (! rootobjc_data) goto fail; /* Read the root object container. */ VasEBoot_disk_read (disk, ((VasEBoot_disk_addr_t) VasEBoot_be_to_cpu32 (data->rblock.rootobject)) << data->log_blocksize, 0, VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize, rootobjc_data); if (VasEBoot_errno) goto fail; rootobjc = (struct VasEBoot_sfs_objc *) rootobjc_data; blk = VasEBoot_be_to_cpu32 (rootobjc->objects[0].file_dir.dir.dir_objc); data->diropen.size = 0; data->diropen.block = blk; data->diropen.data = data; data->diropen.cache = 0; data->disk = disk; /* We only read 1 block of data, so truncate the name if needed. */ max_len = ((VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize) - 24 /* offsetof (struct VasEBoot_sfs_objc, objects) */ - 25); /* offsetof (struct VasEBoot_sfs_obj, filename) */ data->label = VasEBoot_zalloc (max_len + 1); if (data->label == NULL) goto fail; VasEBoot_strncpy (data->label, (char *) rootobjc->objects[0].filename, max_len); VasEBoot_free (rootobjc_data); return data; fail: if (VasEBoot_errno == VAS_EBOOT_ERR_OUT_OF_RANGE) VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not an SFS filesystem"); VasEBoot_free (data); VasEBoot_free (rootobjc_data); return 0; } static char * VasEBoot_sfs_read_symlink (VasEBoot_fshelp_node_t node) { struct VasEBoot_sfs_data *data = node->data; char *symlink; char *block; block = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize); if (!block) return 0; VasEBoot_disk_read (data->disk, ((VasEBoot_disk_addr_t) node->block) << data->log_blocksize, 0, VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize, block); if (VasEBoot_errno) { VasEBoot_free (block); return 0; } /* This is just a wild guess, but it always worked for me. How the SLNK block looks like is not documented in the SFS docs. */ symlink = VasEBoot_malloc (((VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize) - 24) * VAS_EBOOT_MAX_UTF8_PER_LATIN1 + 1); if (!symlink) { VasEBoot_free (block); return 0; } *VasEBoot_latin1_to_utf8 ((VasEBoot_uint8_t *) symlink, (VasEBoot_uint8_t *) &block[24], (VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize) - 24) = '\0'; VasEBoot_free (block); return symlink; } /* Helper for VasEBoot_sfs_iterate_dir. */ static int VasEBoot_sfs_create_node (struct VasEBoot_fshelp_node **node, struct VasEBoot_sfs_data *data, const char *name, VasEBoot_uint32_t block, VasEBoot_uint32_t size, int type, VasEBoot_uint32_t mtime, VasEBoot_fshelp_iterate_dir_hook_t hook, void *hook_data) { VasEBoot_size_t len = VasEBoot_strlen (name); VasEBoot_uint8_t *name_u8; int ret; VasEBoot_size_t sz; if (VasEBoot_mul (len, VAS_EBOOT_MAX_UTF8_PER_LATIN1, &sz) || VasEBoot_add (sz, 1, &sz)) return 1; *node = VasEBoot_malloc (sizeof (**node)); if (!*node) return 1; name_u8 = VasEBoot_malloc (sz); if (!name_u8) { VasEBoot_free (*node); return 1; } (*node)->data = data; (*node)->size = size; (*node)->block = block; (*node)->mtime = mtime; (*node)->cache = 0; (*node)->cache_off = 0; (*node)->next_extent = block; (*node)->cache_size = 0; (*node)->cache_allocated = 0; *VasEBoot_latin1_to_utf8 (name_u8, (const VasEBoot_uint8_t *) name, len) = '\0'; ret = hook ((char *) name_u8, type | data->fshelp_flags, *node, hook_data); VasEBoot_free (name_u8); return ret; } static int VasEBoot_sfs_iterate_dir (VasEBoot_fshelp_node_t dir, VasEBoot_fshelp_iterate_dir_hook_t hook, void *hook_data) { struct VasEBoot_fshelp_node *node = 0; struct VasEBoot_sfs_data *data = dir->data; char *objc_data; struct VasEBoot_sfs_objc *objc; unsigned int next = dir->block; VasEBoot_uint32_t pos; objc_data = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize); if (!objc_data) goto fail; /* The Object container can consist of multiple blocks, iterate over every block. */ while (next) { VasEBoot_disk_read (data->disk, ((VasEBoot_disk_addr_t) next) << data->log_blocksize, 0, VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize, objc_data); if (VasEBoot_errno) goto fail; objc = (struct VasEBoot_sfs_objc *) objc_data; pos = (char *) &objc->objects[0] - (char *) objc; /* Iterate over all entries in this block. */ while (pos + sizeof (struct VasEBoot_sfs_obj) < (1U << (VAS_EBOOT_DISK_SECTOR_BITS + data->log_blocksize))) { struct VasEBoot_sfs_obj *obj; obj = (struct VasEBoot_sfs_obj *) ((char *) objc + pos); const char *filename = (const char *) obj->filename; VasEBoot_size_t len; enum VasEBoot_fshelp_filetype type; VasEBoot_uint32_t block; /* The filename and comment dynamically increase the size of the object. */ len = VasEBoot_strlen (filename); len += VasEBoot_strlen (filename + len + 1); pos += sizeof (*obj) + len; /* Round up to a multiple of two bytes. */ pos = ((pos + 1) >> 1) << 1; if (filename[0] == 0) continue; /* First check if the file was not deleted. */ if (obj->type & VAS_EBOOT_SFS_TYPE_DELETED) continue; else if (obj->type & VAS_EBOOT_SFS_TYPE_SYMLINK) type = VAS_EBOOT_FSHELP_SYMLINK; else if (obj->type & VAS_EBOOT_SFS_TYPE_DIR) type = VAS_EBOOT_FSHELP_DIR; else type = VAS_EBOOT_FSHELP_REG; if (type == VAS_EBOOT_FSHELP_DIR) block = VasEBoot_be_to_cpu32 (obj->file_dir.dir.dir_objc); else block = VasEBoot_be_to_cpu32 (obj->file_dir.file.first_block); if (VasEBoot_sfs_create_node (&node, data, filename, block, VasEBoot_be_to_cpu32 (obj->file_dir.file.size), type, VasEBoot_be_to_cpu32 (obj->mtime), hook, hook_data)) { VasEBoot_free (objc_data); return 1; } } next = VasEBoot_be_to_cpu32 (objc->next); } fail: VasEBoot_free (objc_data); return 0; } /* Open a file named NAME and initialize FILE. */ static VasEBoot_err_t VasEBoot_sfs_open (struct VasEBoot_file *file, const char *name) { struct VasEBoot_sfs_data *data; struct VasEBoot_fshelp_node *fdiro = 0; VasEBoot_dl_ref (my_mod); data = VasEBoot_sfs_mount (file->device->disk); if (!data) goto fail; VasEBoot_fshelp_find_file (name, &data->diropen, &fdiro, VasEBoot_sfs_iterate_dir, VasEBoot_sfs_read_symlink, VAS_EBOOT_FSHELP_REG); if (VasEBoot_errno) goto fail; file->size = fdiro->size; data->diropen = *fdiro; VasEBoot_free (fdiro); file->data = data; file->offset = 0; return 0; fail: if (data && fdiro != &data->diropen) VasEBoot_free (fdiro); if (data) VasEBoot_free (data->label); VasEBoot_free (data); VasEBoot_dl_unref (my_mod); return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_sfs_close (VasEBoot_file_t file) { struct VasEBoot_sfs_data *data = (struct VasEBoot_sfs_data *) file->data; VasEBoot_free (data->diropen.cache); VasEBoot_free (data->label); VasEBoot_free (data); VasEBoot_dl_unref (my_mod); return VAS_EBOOT_ERR_NONE; } /* Read LEN bytes data from FILE into BUF. */ static VasEBoot_ssize_t VasEBoot_sfs_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len) { struct VasEBoot_sfs_data *data = (struct VasEBoot_sfs_data *) file->data; return VasEBoot_sfs_read_file (&data->diropen, file->read_hook, file->read_hook_data, file->offset, len, buf); } /* Context for VasEBoot_sfs_dir. */ struct VasEBoot_sfs_dir_ctx { VasEBoot_fs_dir_hook_t hook; void *hook_data; }; /* Helper for VasEBoot_sfs_dir. */ static int VasEBoot_sfs_dir_iter (const char *filename, enum VasEBoot_fshelp_filetype filetype, VasEBoot_fshelp_node_t node, void *data) { struct VasEBoot_sfs_dir_ctx *ctx = data; struct VasEBoot_dirhook_info info; VasEBoot_memset (&info, 0, sizeof (info)); info.dir = ((filetype & VAS_EBOOT_FSHELP_TYPE_MASK) == VAS_EBOOT_FSHELP_DIR); info.mtime = node->mtime + 8 * 365 * 86400 + 86400 * 2; info.mtimeset = 1; VasEBoot_free (node->cache); VasEBoot_free (node); return ctx->hook (filename, &info, ctx->hook_data); } static VasEBoot_err_t VasEBoot_sfs_dir (VasEBoot_device_t device, const char *path, VasEBoot_fs_dir_hook_t hook, void *hook_data) { struct VasEBoot_sfs_dir_ctx ctx = { hook, hook_data }; struct VasEBoot_sfs_data *data = 0; struct VasEBoot_fshelp_node *fdiro = 0; VasEBoot_dl_ref (my_mod); data = VasEBoot_sfs_mount (device->disk); if (!data) goto fail; VasEBoot_fshelp_find_file (path, &data->diropen, &fdiro, VasEBoot_sfs_iterate_dir, VasEBoot_sfs_read_symlink, VAS_EBOOT_FSHELP_DIR); if (VasEBoot_errno) goto fail; VasEBoot_sfs_iterate_dir (fdiro, VasEBoot_sfs_dir_iter, &ctx); fail: if (data && fdiro != &data->diropen) VasEBoot_free (fdiro); if (data) VasEBoot_free (data->label); VasEBoot_free (data); VasEBoot_dl_unref (my_mod); return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_sfs_label (VasEBoot_device_t device, char **label) { struct VasEBoot_sfs_data *data; VasEBoot_disk_t disk = device->disk; data = VasEBoot_sfs_mount (disk); if (data) { VasEBoot_size_t sz, len = VasEBoot_strlen (data->label); if (VasEBoot_mul (len, VAS_EBOOT_MAX_UTF8_PER_LATIN1, &sz) || VasEBoot_add (sz, 1, &sz)) return VAS_EBOOT_ERR_OUT_OF_RANGE; *label = VasEBoot_malloc (sz); if (*label) *VasEBoot_latin1_to_utf8 ((VasEBoot_uint8_t *) *label, (const VasEBoot_uint8_t *) data->label, len) = '\0'; VasEBoot_free (data->label); } VasEBoot_free (data); return VasEBoot_errno; } static struct VasEBoot_fs VasEBoot_sfs_fs = { .name = "sfs", .fs_dir = VasEBoot_sfs_dir, .fs_open = VasEBoot_sfs_open, .fs_read = VasEBoot_sfs_read, .fs_close = VasEBoot_sfs_close, .fs_label = VasEBoot_sfs_label, #ifdef VAS_EBOOT_UTIL .reserved_first_sector = 0, .blocklist_install = 1, #endif .next = 0 }; VAS_EBOOT_MOD_INIT(sfs) { if (!VasEBoot_is_lockdown ()) { VasEBoot_sfs_fs.mod = mod; VasEBoot_fs_register (&VasEBoot_sfs_fs); } my_mod = mod; } VAS_EBOOT_MOD_FINI(sfs) { if (!VasEBoot_is_lockdown ()) VasEBoot_fs_unregister (&VasEBoot_sfs_fs); }