/* affs.c - Amiga Fast 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 VAS_EBOOT_MOD_LICENSE ("GPLv3+"); /* The affs bootblock. */ struct VasEBoot_affs_bblock { VasEBoot_uint8_t type[3]; VasEBoot_uint8_t flags; VasEBoot_uint32_t checksum; VasEBoot_uint32_t rootblock; } VAS_EBOOT_PACKED; /* Set if the filesystem is a AFFS filesystem. Otherwise this is an OFS filesystem. */ #define VAS_EBOOT_AFFS_FLAG_FFS 1 /* The affs rootblock. */ struct VasEBoot_affs_rblock { VasEBoot_uint32_t type; VasEBoot_uint8_t unused1[8]; VasEBoot_uint32_t htsize; VasEBoot_uint32_t unused2; VasEBoot_uint32_t checksum; VasEBoot_uint32_t hashtable[1]; } VAS_EBOOT_PACKED; struct VasEBoot_affs_time { VasEBoot_int32_t day; VasEBoot_uint32_t min; VasEBoot_uint32_t hz; } VAS_EBOOT_PACKED; /* The second part of a file header block. */ struct VasEBoot_affs_file { VasEBoot_uint8_t unused1[12]; VasEBoot_uint32_t size; VasEBoot_uint8_t unused2[92]; struct VasEBoot_affs_time mtime; VasEBoot_uint8_t namelen; VasEBoot_uint8_t name[30]; VasEBoot_uint8_t unused3[5]; VasEBoot_uint32_t hardlink; VasEBoot_uint32_t unused4[6]; VasEBoot_uint32_t next; VasEBoot_uint32_t parent; VasEBoot_uint32_t extension; VasEBoot_uint32_t type; } VAS_EBOOT_PACKED; /* The location of `struct VasEBoot_affs_file' relative to the end of a file header block. */ #define VAS_EBOOT_AFFS_FILE_LOCATION 200 /* The offset in both the rootblock and the file header block for the hashtable, symlink and block pointers (all synonyms). */ #define VAS_EBOOT_AFFS_HASHTABLE_OFFSET 24 #define VAS_EBOOT_AFFS_BLOCKPTR_OFFSET 24 #define VAS_EBOOT_AFFS_SYMLINK_OFFSET 24 enum { VAS_EBOOT_AFFS_FILETYPE_DIR = 2, VAS_EBOOT_AFFS_FILETYPE_SYMLINK = 3, VAS_EBOOT_AFFS_FILETYPE_HARDLINK = 0xfffffffc, VAS_EBOOT_AFFS_FILETYPE_REG = 0xfffffffd }; #define AFFS_MAX_LOG_BLOCK_SIZE 4 #define AFFS_MAX_SUPERBLOCK 1 struct VasEBoot_fshelp_node { struct VasEBoot_affs_data *data; VasEBoot_uint32_t block; struct VasEBoot_fshelp_node *parent; struct VasEBoot_affs_file di; VasEBoot_uint32_t *block_cache; VasEBoot_uint32_t last_block_cache; }; /* Information about a "mounted" affs filesystem. */ struct VasEBoot_affs_data { struct VasEBoot_affs_bblock bblock; struct VasEBoot_fshelp_node diropen; VasEBoot_disk_t disk; /* Log blocksize in sectors. */ int log_blocksize; /* The number of entries in the hashtable. */ unsigned int htsize; }; static VasEBoot_dl_t my_mod; static VasEBoot_disk_addr_t VasEBoot_affs_read_block (VasEBoot_fshelp_node_t node, VasEBoot_disk_addr_t fileblock) { VasEBoot_uint32_t target, curblock; VasEBoot_uint32_t pos; struct VasEBoot_affs_file file; struct VasEBoot_affs_data *data = node->data; VasEBoot_uint64_t mod; if (!node->block_cache) { node->block_cache = VasEBoot_malloc (((VasEBoot_be_to_cpu32 (node->di.size) >> (9 + node->data->log_blocksize)) / data->htsize + 2) * sizeof (node->block_cache[0])); if (!node->block_cache) return -1; node->last_block_cache = 0; node->block_cache[0] = node->block; } /* Files are at most 2G on AFFS, so no need for 64-bit division. */ target = (VasEBoot_uint32_t) fileblock / data->htsize; mod = (VasEBoot_uint32_t) fileblock % data->htsize; /* Find the block that points to the fileblock we are looking up by following the chain until the right table is reached. */ for (curblock = node->last_block_cache + 1; curblock < target + 1; curblock++) { VasEBoot_disk_read (data->disk, (((VasEBoot_uint64_t) node->block_cache[curblock - 1] + 1) << data->log_blocksize) - 1, VAS_EBOOT_DISK_SECTOR_SIZE - VAS_EBOOT_AFFS_FILE_LOCATION, sizeof (file), &file); if (VasEBoot_errno) return 0; node->block_cache[curblock] = VasEBoot_be_to_cpu32 (file.extension); node->last_block_cache = curblock; } /* Translate the fileblock to the block within the right table. */ VasEBoot_disk_read (data->disk, (VasEBoot_uint64_t) node->block_cache[target] << data->log_blocksize, VAS_EBOOT_AFFS_BLOCKPTR_OFFSET + (data->htsize - mod - 1) * sizeof (pos), sizeof (pos), &pos); if (VasEBoot_errno) return 0; return VasEBoot_be_to_cpu32 (pos); } static struct VasEBoot_affs_data * VasEBoot_affs_mount (VasEBoot_disk_t disk) { struct VasEBoot_affs_data *data; VasEBoot_uint32_t *rootblock = 0; struct VasEBoot_affs_rblock *rblock = 0; int log_blocksize = 0; int bsnum = 0; data = VasEBoot_zalloc (sizeof (struct VasEBoot_affs_data)); if (!data) return 0; for (bsnum = 0; bsnum < AFFS_MAX_SUPERBLOCK + 1; bsnum++) { /* Read the bootblock. */ VasEBoot_disk_read (disk, bsnum, 0, sizeof (struct VasEBoot_affs_bblock), &data->bblock); if (VasEBoot_errno) goto fail; /* Make sure this is an affs filesystem. */ if (VasEBoot_strncmp ((char *) (data->bblock.type), "DOS", 3) != 0 /* Test if the filesystem is a OFS filesystem. */ || !(data->bblock.flags & VAS_EBOOT_AFFS_FLAG_FFS)) continue; /* No sane person uses more than 8KB for a block. At least I hope for that person because in that case this won't work. */ if (!rootblock) rootblock = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE << AFFS_MAX_LOG_BLOCK_SIZE); if (!rootblock) goto fail; rblock = (struct VasEBoot_affs_rblock *) rootblock; /* The filesystem blocksize is not stored anywhere in the filesystem itself. One way to determine it is try reading blocks for the rootblock until the checksum is correct. */ for (log_blocksize = 0; log_blocksize <= AFFS_MAX_LOG_BLOCK_SIZE; log_blocksize++) { VasEBoot_uint32_t *currblock = rootblock; unsigned int i; VasEBoot_uint32_t checksum = 0; /* Read the rootblock. */ VasEBoot_disk_read (disk, (VasEBoot_uint64_t) VasEBoot_be_to_cpu32 (data->bblock.rootblock) << log_blocksize, 0, VAS_EBOOT_DISK_SECTOR_SIZE << log_blocksize, rootblock); if (VasEBoot_errno == VAS_EBOOT_ERR_OUT_OF_RANGE) { VasEBoot_errno = 0; break; } if (VasEBoot_errno) goto fail; if (rblock->type != VasEBoot_cpu_to_be32_compile_time (2) || rblock->htsize == 0 || currblock[(VAS_EBOOT_DISK_SECTOR_SIZE << log_blocksize) / sizeof (*currblock) - 1] != VasEBoot_cpu_to_be32_compile_time (1)) continue; for (i = 0; i < (VAS_EBOOT_DISK_SECTOR_SIZE << log_blocksize) / sizeof (*currblock); i++) checksum += VasEBoot_be_to_cpu32 (currblock[i]); if (checksum == 0) { data->log_blocksize = log_blocksize; data->disk = disk; data->htsize = VasEBoot_be_to_cpu32 (rblock->htsize); data->diropen.data = data; data->diropen.block = VasEBoot_be_to_cpu32 (data->bblock.rootblock); data->diropen.parent = NULL; VasEBoot_memcpy (&data->diropen.di, rootblock, sizeof (data->diropen.di)); VasEBoot_free (rootblock); return data; } } } fail: if (VasEBoot_errno == VAS_EBOOT_ERR_NONE || VasEBoot_errno == VAS_EBOOT_ERR_OUT_OF_RANGE) VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not an AFFS filesystem"); VasEBoot_free (data); VasEBoot_free (rootblock); return 0; } static char * VasEBoot_affs_read_symlink (VasEBoot_fshelp_node_t node) { struct VasEBoot_affs_data *data = node->data; VasEBoot_uint8_t *latin1, *utf8; const VasEBoot_size_t symlink_size = ((VAS_EBOOT_DISK_SECTOR_SIZE << data->log_blocksize) - VAS_EBOOT_AFFS_SYMLINK_OFFSET); latin1 = VasEBoot_malloc (symlink_size + 1); if (!latin1) return 0; VasEBoot_disk_read (data->disk, (VasEBoot_uint64_t) node->block << data->log_blocksize, VAS_EBOOT_AFFS_SYMLINK_OFFSET, symlink_size, latin1); if (VasEBoot_errno) { VasEBoot_free (latin1); return 0; } latin1[symlink_size] = 0; utf8 = VasEBoot_calloc (VAS_EBOOT_MAX_UTF8_PER_LATIN1 + 1, symlink_size); if (!utf8) { VasEBoot_free (latin1); return 0; } *VasEBoot_latin1_to_utf8 (utf8, latin1, symlink_size) = '\0'; VasEBoot_dprintf ("affs", "Symlink: `%s'\n", utf8); VasEBoot_free (latin1); if (utf8[0] == ':') utf8[0] = '/'; return (char *) utf8; } /* Helper for VasEBoot_affs_iterate_dir. */ static int VasEBoot_affs_create_node (VasEBoot_fshelp_node_t dir, VasEBoot_fshelp_iterate_dir_hook_t hook, void *hook_data, struct VasEBoot_fshelp_node **node, VasEBoot_uint32_t block, const struct VasEBoot_affs_file *fil) { struct VasEBoot_affs_data *data = dir->data; int type = VAS_EBOOT_FSHELP_REG; VasEBoot_uint8_t name_u8[sizeof (fil->name) * VAS_EBOOT_MAX_UTF8_PER_LATIN1 + 1]; VasEBoot_size_t len; unsigned int nest; *node = VasEBoot_zalloc (sizeof (**node)); if (!*node) return 1; (*node)->data = data; (*node)->block = block; (*node)->parent = dir; len = fil->namelen; if (len > sizeof (fil->name)) len = sizeof (fil->name); *VasEBoot_latin1_to_utf8 (name_u8, fil->name, len) = '\0'; (*node)->di = *fil; for (nest = 0; nest < 8; nest++) { switch ((*node)->di.type) { case VasEBoot_cpu_to_be32_compile_time (VAS_EBOOT_AFFS_FILETYPE_REG): type = VAS_EBOOT_FSHELP_REG; break; case VasEBoot_cpu_to_be32_compile_time (VAS_EBOOT_AFFS_FILETYPE_DIR): type = VAS_EBOOT_FSHELP_DIR; break; case VasEBoot_cpu_to_be32_compile_time (VAS_EBOOT_AFFS_FILETYPE_SYMLINK): type = VAS_EBOOT_FSHELP_SYMLINK; break; case VasEBoot_cpu_to_be32_compile_time (VAS_EBOOT_AFFS_FILETYPE_HARDLINK): { VasEBoot_err_t err; (*node)->block = VasEBoot_be_to_cpu32 ((*node)->di.hardlink); err = VasEBoot_disk_read (data->disk, (((VasEBoot_uint64_t) (*node)->block + 1) << data->log_blocksize) - 1, VAS_EBOOT_DISK_SECTOR_SIZE - VAS_EBOOT_AFFS_FILE_LOCATION, sizeof ((*node)->di), (char *) &(*node)->di); if (err) { VasEBoot_free (*node); return 1; } continue; } default: { VasEBoot_free (*node); return 0; } } break; } if (nest == 8) { VasEBoot_free (*node); return 0; } type |= VAS_EBOOT_FSHELP_CASE_INSENSITIVE; if (hook ((char *) name_u8, type, *node, hook_data)) { *node = 0; return 1; } *node = 0; return 0; } static int VasEBoot_affs_iterate_dir (VasEBoot_fshelp_node_t dir, VasEBoot_fshelp_iterate_dir_hook_t hook, void *hook_data) { unsigned int i; struct VasEBoot_affs_file file; struct VasEBoot_fshelp_node *node, *orig_node; struct VasEBoot_affs_data *data = dir->data; VasEBoot_uint32_t *hashtable; /* Create the directory entries for `.' and `..'. */ node = orig_node = VasEBoot_zalloc (sizeof (*node)); if (!node) return 1; *node = *dir; if (hook (".", VAS_EBOOT_FSHELP_DIR, node, hook_data)) return 1; if (dir->parent) { *node = *dir->parent; if (hook ("..", VAS_EBOOT_FSHELP_DIR, node, hook_data)) return 1; } hashtable = VasEBoot_calloc (data->htsize, sizeof (*hashtable)); if (!hashtable) return 1; VasEBoot_disk_read (data->disk, (VasEBoot_uint64_t) dir->block << data->log_blocksize, VAS_EBOOT_AFFS_HASHTABLE_OFFSET, data->htsize * sizeof (*hashtable), (char *) hashtable); if (VasEBoot_errno) goto fail; for (i = 0; i < data->htsize; i++) { VasEBoot_uint32_t next; if (!hashtable[i]) continue; /* Every entry in the hashtable can be chained. Read the entire chain. */ next = VasEBoot_be_to_cpu32 (hashtable[i]); while (next) { VasEBoot_disk_read (data->disk, (((VasEBoot_uint64_t) next + 1) << data->log_blocksize) - 1, VAS_EBOOT_DISK_SECTOR_SIZE - VAS_EBOOT_AFFS_FILE_LOCATION, sizeof (file), (char *) &file); if (VasEBoot_errno) goto fail; if (VasEBoot_affs_create_node (dir, hook, hook_data, &node, next, &file)) { /* Node has been replaced in function. */ VasEBoot_free (orig_node); VasEBoot_free (hashtable); return 1; } next = VasEBoot_be_to_cpu32 (file.next); } } fail: VasEBoot_free (orig_node); VasEBoot_free (hashtable); return 0; } /* Open a file named NAME and initialize FILE. */ static VasEBoot_err_t VasEBoot_affs_open (struct VasEBoot_file *file, const char *name) { struct VasEBoot_affs_data *data; struct VasEBoot_fshelp_node *fdiro = 0; VasEBoot_dl_ref (my_mod); data = VasEBoot_affs_mount (file->device->disk); if (!data) goto fail; VasEBoot_fshelp_find_file (name, &data->diropen, &fdiro, VasEBoot_affs_iterate_dir, VasEBoot_affs_read_symlink, VAS_EBOOT_FSHELP_REG); if (VasEBoot_errno) goto fail; file->size = VasEBoot_be_to_cpu32 (fdiro->di.size); data->diropen = *fdiro; VasEBoot_free (fdiro); file->data = data; file->offset = 0; return 0; fail: if (data && fdiro != &data->diropen) VasEBoot_free (fdiro); VasEBoot_free (data); VasEBoot_dl_unref (my_mod); return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_affs_close (VasEBoot_file_t file) { struct VasEBoot_affs_data *data = (struct VasEBoot_affs_data *) file->data; VasEBoot_free (data->diropen.block_cache); VasEBoot_free (file->data); VasEBoot_dl_unref (my_mod); return VAS_EBOOT_ERR_NONE; } /* Read LEN bytes data from FILE into BUF. */ static VasEBoot_ssize_t VasEBoot_affs_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len) { struct VasEBoot_affs_data *data = (struct VasEBoot_affs_data *) file->data; return VasEBoot_fshelp_read_file (data->diropen.data->disk, &data->diropen, file->read_hook, file->read_hook_data, file->offset, len, buf, VasEBoot_affs_read_block, VasEBoot_be_to_cpu32 (data->diropen.di.size), data->log_blocksize, 0); } static VasEBoot_int32_t aftime2ctime (const struct VasEBoot_affs_time *t) { return VasEBoot_be_to_cpu32 (t->day) * 86400 + VasEBoot_be_to_cpu32 (t->min) * 60 + VasEBoot_be_to_cpu32 (t->hz) / 50 + 8 * 365 * 86400 + 86400 * 2; } /* Context for VasEBoot_affs_dir. */ struct VasEBoot_affs_dir_ctx { VasEBoot_fs_dir_hook_t hook; void *hook_data; }; /* Helper for VasEBoot_affs_dir. */ static int VasEBoot_affs_dir_iter (const char *filename, enum VasEBoot_fshelp_filetype filetype, VasEBoot_fshelp_node_t node, void *data) { struct VasEBoot_affs_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.mtimeset = 1; info.mtime = aftime2ctime (&node->di.mtime); VasEBoot_free (node); return ctx->hook (filename, &info, ctx->hook_data); } static VasEBoot_err_t VasEBoot_affs_dir (VasEBoot_device_t device, const char *path, VasEBoot_fs_dir_hook_t hook, void *hook_data) { struct VasEBoot_affs_dir_ctx ctx = { hook, hook_data }; struct VasEBoot_affs_data *data = 0; struct VasEBoot_fshelp_node *fdiro = 0; VasEBoot_dl_ref (my_mod); data = VasEBoot_affs_mount (device->disk); if (!data) goto fail; VasEBoot_fshelp_find_file (path, &data->diropen, &fdiro, VasEBoot_affs_iterate_dir, VasEBoot_affs_read_symlink, VAS_EBOOT_FSHELP_DIR); if (VasEBoot_errno) goto fail; VasEBoot_affs_iterate_dir (fdiro, VasEBoot_affs_dir_iter, &ctx); fail: if (data && fdiro != &data->diropen) VasEBoot_free (fdiro); VasEBoot_free (data); VasEBoot_dl_unref (my_mod); return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_affs_label (VasEBoot_device_t device, char **label) { struct VasEBoot_affs_data *data; struct VasEBoot_affs_file file; VasEBoot_disk_t disk = device->disk; VasEBoot_dl_ref (my_mod); data = VasEBoot_affs_mount (disk); if (data) { VasEBoot_size_t len; /* The rootblock maps quite well on a file header block, it's something we can use here. */ VasEBoot_disk_read (data->disk, (((VasEBoot_uint64_t) VasEBoot_be_to_cpu32 (data->bblock.rootblock) + 1) << data->log_blocksize) - 1, VAS_EBOOT_DISK_SECTOR_SIZE - VAS_EBOOT_AFFS_FILE_LOCATION, sizeof (file), &file); if (VasEBoot_errno) return VasEBoot_errno; len = file.namelen; if (len > sizeof (file.name)) len = sizeof (file.name); *label = VasEBoot_calloc (VAS_EBOOT_MAX_UTF8_PER_LATIN1 + 1, len); if (*label) *VasEBoot_latin1_to_utf8 ((VasEBoot_uint8_t *) *label, file.name, len) = '\0'; } else *label = 0; VasEBoot_dl_unref (my_mod); VasEBoot_free (data); return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_affs_mtime (VasEBoot_device_t device, VasEBoot_int64_t *t) { struct VasEBoot_affs_data *data; VasEBoot_disk_t disk = device->disk; struct VasEBoot_affs_time af_time; *t = 0; VasEBoot_dl_ref (my_mod); data = VasEBoot_affs_mount (disk); if (!data) { VasEBoot_dl_unref (my_mod); return VasEBoot_errno; } VasEBoot_disk_read (data->disk, (((VasEBoot_uint64_t) VasEBoot_be_to_cpu32 (data->bblock.rootblock) + 1) << data->log_blocksize) - 1, VAS_EBOOT_DISK_SECTOR_SIZE - 40, sizeof (af_time), &af_time); if (VasEBoot_errno) { VasEBoot_dl_unref (my_mod); VasEBoot_free (data); return VasEBoot_errno; } *t = aftime2ctime (&af_time); VasEBoot_dl_unref (my_mod); VasEBoot_free (data); return VAS_EBOOT_ERR_NONE; } static struct VasEBoot_fs VasEBoot_affs_fs = { .name = "affs", .fs_dir = VasEBoot_affs_dir, .fs_open = VasEBoot_affs_open, .fs_read = VasEBoot_affs_read, .fs_close = VasEBoot_affs_close, .fs_label = VasEBoot_affs_label, .fs_mtime = VasEBoot_affs_mtime, #ifdef VAS_EBOOT_UTIL .reserved_first_sector = 0, .blocklist_install = 1, #endif .next = 0 }; VAS_EBOOT_MOD_INIT(affs) { if (!VasEBoot_is_lockdown ()) { VasEBoot_affs_fs.mod = mod; VasEBoot_fs_register (&VasEBoot_affs_fs); } my_mod = mod; } VAS_EBOOT_MOD_FINI(affs) { if (!VasEBoot_is_lockdown ()) VasEBoot_fs_unregister (&VasEBoot_affs_fs); }