123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720 |
- /* affs.c - Amiga Fast FileSystem. */
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2005,2006,2007,2008,2009 Free Software Foundation, Inc.
- *
- * GRUB 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.
- *
- * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <grub/err.h>
- #include <grub/file.h>
- #include <grub/mm.h>
- #include <grub/misc.h>
- #include <grub/disk.h>
- #include <grub/dl.h>
- #include <grub/types.h>
- #include <grub/fshelp.h>
- #include <grub/charset.h>
- #include <grub/lockdown.h>
- GRUB_MOD_LICENSE ("GPLv3+");
- /* The affs bootblock. */
- struct grub_affs_bblock
- {
- grub_uint8_t type[3];
- grub_uint8_t flags;
- grub_uint32_t checksum;
- grub_uint32_t rootblock;
- } GRUB_PACKED;
- /* Set if the filesystem is a AFFS filesystem. Otherwise this is an
- OFS filesystem. */
- #define GRUB_AFFS_FLAG_FFS 1
- /* The affs rootblock. */
- struct grub_affs_rblock
- {
- grub_uint32_t type;
- grub_uint8_t unused1[8];
- grub_uint32_t htsize;
- grub_uint32_t unused2;
- grub_uint32_t checksum;
- grub_uint32_t hashtable[1];
- } GRUB_PACKED;
- struct grub_affs_time
- {
- grub_int32_t day;
- grub_uint32_t min;
- grub_uint32_t hz;
- } GRUB_PACKED;
- /* The second part of a file header block. */
- struct grub_affs_file
- {
- grub_uint8_t unused1[12];
- grub_uint32_t size;
- grub_uint8_t unused2[92];
- struct grub_affs_time mtime;
- grub_uint8_t namelen;
- grub_uint8_t name[30];
- grub_uint8_t unused3[5];
- grub_uint32_t hardlink;
- grub_uint32_t unused4[6];
- grub_uint32_t next;
- grub_uint32_t parent;
- grub_uint32_t extension;
- grub_uint32_t type;
- } GRUB_PACKED;
- /* The location of `struct grub_affs_file' relative to the end of a
- file header block. */
- #define GRUB_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 GRUB_AFFS_HASHTABLE_OFFSET 24
- #define GRUB_AFFS_BLOCKPTR_OFFSET 24
- #define GRUB_AFFS_SYMLINK_OFFSET 24
- enum
- {
- GRUB_AFFS_FILETYPE_DIR = 2,
- GRUB_AFFS_FILETYPE_SYMLINK = 3,
- GRUB_AFFS_FILETYPE_HARDLINK = 0xfffffffc,
- GRUB_AFFS_FILETYPE_REG = 0xfffffffd
- };
- #define AFFS_MAX_LOG_BLOCK_SIZE 4
- #define AFFS_MAX_SUPERBLOCK 1
- struct grub_fshelp_node
- {
- struct grub_affs_data *data;
- grub_uint32_t block;
- struct grub_fshelp_node *parent;
- struct grub_affs_file di;
- grub_uint32_t *block_cache;
- grub_uint32_t last_block_cache;
- };
- /* Information about a "mounted" affs filesystem. */
- struct grub_affs_data
- {
- struct grub_affs_bblock bblock;
- struct grub_fshelp_node diropen;
- grub_disk_t disk;
- /* Log blocksize in sectors. */
- int log_blocksize;
- /* The number of entries in the hashtable. */
- unsigned int htsize;
- };
- static grub_dl_t my_mod;
- static grub_disk_addr_t
- grub_affs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
- {
- grub_uint32_t target, curblock;
- grub_uint32_t pos;
- struct grub_affs_file file;
- struct grub_affs_data *data = node->data;
- grub_uint64_t mod;
- if (!node->block_cache)
- {
- node->block_cache = grub_malloc (((grub_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 = (grub_uint32_t) fileblock / data->htsize;
- mod = (grub_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++)
- {
- grub_disk_read (data->disk,
- (((grub_uint64_t) node->block_cache[curblock - 1] + 1)
- << data->log_blocksize) - 1,
- GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
- sizeof (file), &file);
- if (grub_errno)
- return 0;
- node->block_cache[curblock] = grub_be_to_cpu32 (file.extension);
- node->last_block_cache = curblock;
- }
- /* Translate the fileblock to the block within the right table. */
- grub_disk_read (data->disk, (grub_uint64_t) node->block_cache[target]
- << data->log_blocksize,
- GRUB_AFFS_BLOCKPTR_OFFSET
- + (data->htsize - mod - 1) * sizeof (pos),
- sizeof (pos), &pos);
- if (grub_errno)
- return 0;
- return grub_be_to_cpu32 (pos);
- }
- static struct grub_affs_data *
- grub_affs_mount (grub_disk_t disk)
- {
- struct grub_affs_data *data;
- grub_uint32_t *rootblock = 0;
- struct grub_affs_rblock *rblock = 0;
- int log_blocksize = 0;
- int bsnum = 0;
- data = grub_zalloc (sizeof (struct grub_affs_data));
- if (!data)
- return 0;
- for (bsnum = 0; bsnum < AFFS_MAX_SUPERBLOCK + 1; bsnum++)
- {
- /* Read the bootblock. */
- grub_disk_read (disk, bsnum, 0, sizeof (struct grub_affs_bblock),
- &data->bblock);
- if (grub_errno)
- goto fail;
- /* Make sure this is an affs filesystem. */
- if (grub_strncmp ((char *) (data->bblock.type), "DOS", 3) != 0
- /* Test if the filesystem is a OFS filesystem. */
- || !(data->bblock.flags & GRUB_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 = grub_malloc (GRUB_DISK_SECTOR_SIZE
- << AFFS_MAX_LOG_BLOCK_SIZE);
- if (!rootblock)
- goto fail;
- rblock = (struct grub_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++)
- {
- grub_uint32_t *currblock = rootblock;
- unsigned int i;
- grub_uint32_t checksum = 0;
- /* Read the rootblock. */
- grub_disk_read (disk,
- (grub_uint64_t) grub_be_to_cpu32 (data->bblock.rootblock)
- << log_blocksize, 0,
- GRUB_DISK_SECTOR_SIZE << log_blocksize, rootblock);
- if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
- {
- grub_errno = 0;
- break;
- }
- if (grub_errno)
- goto fail;
- if (rblock->type != grub_cpu_to_be32_compile_time (2)
- || rblock->htsize == 0
- || currblock[(GRUB_DISK_SECTOR_SIZE << log_blocksize)
- / sizeof (*currblock) - 1]
- != grub_cpu_to_be32_compile_time (1))
- continue;
- for (i = 0; i < (GRUB_DISK_SECTOR_SIZE << log_blocksize)
- / sizeof (*currblock);
- i++)
- checksum += grub_be_to_cpu32 (currblock[i]);
- if (checksum == 0)
- {
- data->log_blocksize = log_blocksize;
- data->disk = disk;
- data->htsize = grub_be_to_cpu32 (rblock->htsize);
- data->diropen.data = data;
- data->diropen.block = grub_be_to_cpu32 (data->bblock.rootblock);
- data->diropen.parent = NULL;
- grub_memcpy (&data->diropen.di, rootblock,
- sizeof (data->diropen.di));
- grub_free (rootblock);
- return data;
- }
- }
- }
- fail:
- if (grub_errno == GRUB_ERR_NONE || grub_errno == GRUB_ERR_OUT_OF_RANGE)
- grub_error (GRUB_ERR_BAD_FS, "not an AFFS filesystem");
- grub_free (data);
- grub_free (rootblock);
- return 0;
- }
- static char *
- grub_affs_read_symlink (grub_fshelp_node_t node)
- {
- struct grub_affs_data *data = node->data;
- grub_uint8_t *latin1, *utf8;
- const grub_size_t symlink_size = ((GRUB_DISK_SECTOR_SIZE
- << data->log_blocksize) - GRUB_AFFS_SYMLINK_OFFSET);
- latin1 = grub_malloc (symlink_size + 1);
- if (!latin1)
- return 0;
- grub_disk_read (data->disk,
- (grub_uint64_t) node->block << data->log_blocksize,
- GRUB_AFFS_SYMLINK_OFFSET,
- symlink_size, latin1);
- if (grub_errno)
- {
- grub_free (latin1);
- return 0;
- }
- latin1[symlink_size] = 0;
- utf8 = grub_calloc (GRUB_MAX_UTF8_PER_LATIN1 + 1, symlink_size);
- if (!utf8)
- {
- grub_free (latin1);
- return 0;
- }
- *grub_latin1_to_utf8 (utf8, latin1, symlink_size) = '\0';
- grub_dprintf ("affs", "Symlink: `%s'\n", utf8);
- grub_free (latin1);
- if (utf8[0] == ':')
- utf8[0] = '/';
- return (char *) utf8;
- }
- /* Helper for grub_affs_iterate_dir. */
- static int
- grub_affs_create_node (grub_fshelp_node_t dir,
- grub_fshelp_iterate_dir_hook_t hook, void *hook_data,
- struct grub_fshelp_node **node,
- grub_uint32_t block, const struct grub_affs_file *fil)
- {
- struct grub_affs_data *data = dir->data;
- int type = GRUB_FSHELP_REG;
- grub_uint8_t name_u8[sizeof (fil->name) * GRUB_MAX_UTF8_PER_LATIN1 + 1];
- grub_size_t len;
- unsigned int nest;
- *node = grub_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);
- *grub_latin1_to_utf8 (name_u8, fil->name, len) = '\0';
- (*node)->di = *fil;
- for (nest = 0; nest < 8; nest++)
- {
- switch ((*node)->di.type)
- {
- case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_REG):
- type = GRUB_FSHELP_REG;
- break;
- case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_DIR):
- type = GRUB_FSHELP_DIR;
- break;
- case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_SYMLINK):
- type = GRUB_FSHELP_SYMLINK;
- break;
- case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_HARDLINK):
- {
- grub_err_t err;
- (*node)->block = grub_be_to_cpu32 ((*node)->di.hardlink);
- err = grub_disk_read (data->disk,
- (((grub_uint64_t) (*node)->block + 1) << data->log_blocksize)
- - 1,
- GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
- sizeof ((*node)->di), (char *) &(*node)->di);
- if (err)
- {
- grub_free (*node);
- return 1;
- }
- continue;
- }
- default:
- {
- grub_free (*node);
- return 0;
- }
- }
- break;
- }
- if (nest == 8)
- {
- grub_free (*node);
- return 0;
- }
- type |= GRUB_FSHELP_CASE_INSENSITIVE;
- if (hook ((char *) name_u8, type, *node, hook_data))
- {
- *node = 0;
- return 1;
- }
- *node = 0;
- return 0;
- }
- static int
- grub_affs_iterate_dir (grub_fshelp_node_t dir,
- grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
- {
- unsigned int i;
- struct grub_affs_file file;
- struct grub_fshelp_node *node, *orig_node;
- struct grub_affs_data *data = dir->data;
- grub_uint32_t *hashtable;
- /* Create the directory entries for `.' and `..'. */
- node = orig_node = grub_zalloc (sizeof (*node));
- if (!node)
- return 1;
- *node = *dir;
- if (hook (".", GRUB_FSHELP_DIR, node, hook_data))
- return 1;
- if (dir->parent)
- {
- *node = *dir->parent;
- if (hook ("..", GRUB_FSHELP_DIR, node, hook_data))
- return 1;
- }
- hashtable = grub_calloc (data->htsize, sizeof (*hashtable));
- if (!hashtable)
- return 1;
- grub_disk_read (data->disk,
- (grub_uint64_t) dir->block << data->log_blocksize,
- GRUB_AFFS_HASHTABLE_OFFSET,
- data->htsize * sizeof (*hashtable), (char *) hashtable);
- if (grub_errno)
- goto fail;
- for (i = 0; i < data->htsize; i++)
- {
- grub_uint32_t next;
- if (!hashtable[i])
- continue;
- /* Every entry in the hashtable can be chained. Read the entire
- chain. */
- next = grub_be_to_cpu32 (hashtable[i]);
- while (next)
- {
- grub_disk_read (data->disk,
- (((grub_uint64_t) next + 1) << data->log_blocksize)
- - 1,
- GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
- sizeof (file), (char *) &file);
- if (grub_errno)
- goto fail;
- if (grub_affs_create_node (dir, hook, hook_data, &node, next, &file))
- {
- /* Node has been replaced in function. */
- grub_free (orig_node);
- grub_free (hashtable);
- return 1;
- }
- next = grub_be_to_cpu32 (file.next);
- }
- }
- fail:
- grub_free (orig_node);
- grub_free (hashtable);
- return 0;
- }
- /* Open a file named NAME and initialize FILE. */
- static grub_err_t
- grub_affs_open (struct grub_file *file, const char *name)
- {
- struct grub_affs_data *data;
- struct grub_fshelp_node *fdiro = 0;
- grub_dl_ref (my_mod);
- data = grub_affs_mount (file->device->disk);
- if (!data)
- goto fail;
- grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_affs_iterate_dir,
- grub_affs_read_symlink, GRUB_FSHELP_REG);
- if (grub_errno)
- goto fail;
- file->size = grub_be_to_cpu32 (fdiro->di.size);
- data->diropen = *fdiro;
- grub_free (fdiro);
- file->data = data;
- file->offset = 0;
- return 0;
- fail:
- if (data && fdiro != &data->diropen)
- grub_free (fdiro);
- grub_free (data);
- grub_dl_unref (my_mod);
- return grub_errno;
- }
- static grub_err_t
- grub_affs_close (grub_file_t file)
- {
- struct grub_affs_data *data =
- (struct grub_affs_data *) file->data;
- grub_free (data->diropen.block_cache);
- grub_free (file->data);
- grub_dl_unref (my_mod);
- return GRUB_ERR_NONE;
- }
- /* Read LEN bytes data from FILE into BUF. */
- static grub_ssize_t
- grub_affs_read (grub_file_t file, char *buf, grub_size_t len)
- {
- struct grub_affs_data *data =
- (struct grub_affs_data *) file->data;
- return grub_fshelp_read_file (data->diropen.data->disk, &data->diropen,
- file->read_hook, file->read_hook_data,
- file->offset, len, buf, grub_affs_read_block,
- grub_be_to_cpu32 (data->diropen.di.size),
- data->log_blocksize, 0);
- }
- static grub_int32_t
- aftime2ctime (const struct grub_affs_time *t)
- {
- return grub_be_to_cpu32 (t->day) * 86400
- + grub_be_to_cpu32 (t->min) * 60
- + grub_be_to_cpu32 (t->hz) / 50
- + 8 * 365 * 86400 + 86400 * 2;
- }
- /* Context for grub_affs_dir. */
- struct grub_affs_dir_ctx
- {
- grub_fs_dir_hook_t hook;
- void *hook_data;
- };
- /* Helper for grub_affs_dir. */
- static int
- grub_affs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype,
- grub_fshelp_node_t node, void *data)
- {
- struct grub_affs_dir_ctx *ctx = data;
- struct grub_dirhook_info info;
- grub_memset (&info, 0, sizeof (info));
- info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
- info.mtimeset = 1;
- info.mtime = aftime2ctime (&node->di.mtime);
- grub_free (node);
- return ctx->hook (filename, &info, ctx->hook_data);
- }
- static grub_err_t
- grub_affs_dir (grub_device_t device, const char *path,
- grub_fs_dir_hook_t hook, void *hook_data)
- {
- struct grub_affs_dir_ctx ctx = { hook, hook_data };
- struct grub_affs_data *data = 0;
- struct grub_fshelp_node *fdiro = 0;
- grub_dl_ref (my_mod);
- data = grub_affs_mount (device->disk);
- if (!data)
- goto fail;
- grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_affs_iterate_dir,
- grub_affs_read_symlink, GRUB_FSHELP_DIR);
- if (grub_errno)
- goto fail;
- grub_affs_iterate_dir (fdiro, grub_affs_dir_iter, &ctx);
- fail:
- if (data && fdiro != &data->diropen)
- grub_free (fdiro);
- grub_free (data);
- grub_dl_unref (my_mod);
- return grub_errno;
- }
- static grub_err_t
- grub_affs_label (grub_device_t device, char **label)
- {
- struct grub_affs_data *data;
- struct grub_affs_file file;
- grub_disk_t disk = device->disk;
- grub_dl_ref (my_mod);
- data = grub_affs_mount (disk);
- if (data)
- {
- grub_size_t len;
- /* The rootblock maps quite well on a file header block, it's
- something we can use here. */
- grub_disk_read (data->disk,
- (((grub_uint64_t)
- grub_be_to_cpu32 (data->bblock.rootblock) + 1)
- << data->log_blocksize) - 1,
- GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
- sizeof (file), &file);
- if (grub_errno)
- return grub_errno;
- len = file.namelen;
- if (len > sizeof (file.name))
- len = sizeof (file.name);
- *label = grub_calloc (GRUB_MAX_UTF8_PER_LATIN1 + 1, len);
- if (*label)
- *grub_latin1_to_utf8 ((grub_uint8_t *) *label, file.name, len) = '\0';
- }
- else
- *label = 0;
- grub_dl_unref (my_mod);
- grub_free (data);
- return grub_errno;
- }
- static grub_err_t
- grub_affs_mtime (grub_device_t device, grub_int64_t *t)
- {
- struct grub_affs_data *data;
- grub_disk_t disk = device->disk;
- struct grub_affs_time af_time;
- *t = 0;
- grub_dl_ref (my_mod);
- data = grub_affs_mount (disk);
- if (!data)
- {
- grub_dl_unref (my_mod);
- return grub_errno;
- }
- grub_disk_read (data->disk,
- (((grub_uint64_t)
- grub_be_to_cpu32 (data->bblock.rootblock) + 1)
- << data->log_blocksize) - 1,
- GRUB_DISK_SECTOR_SIZE - 40,
- sizeof (af_time), &af_time);
- if (grub_errno)
- {
- grub_dl_unref (my_mod);
- grub_free (data);
- return grub_errno;
- }
- *t = aftime2ctime (&af_time);
- grub_dl_unref (my_mod);
- grub_free (data);
- return GRUB_ERR_NONE;
- }
- static struct grub_fs grub_affs_fs =
- {
- .name = "affs",
- .fs_dir = grub_affs_dir,
- .fs_open = grub_affs_open,
- .fs_read = grub_affs_read,
- .fs_close = grub_affs_close,
- .fs_label = grub_affs_label,
- .fs_mtime = grub_affs_mtime,
- #ifdef GRUB_UTIL
- .reserved_first_sector = 0,
- .blocklist_install = 1,
- #endif
- .next = 0
- };
- GRUB_MOD_INIT(affs)
- {
- if (!grub_is_lockdown ())
- {
- grub_affs_fs.mod = mod;
- grub_fs_register (&grub_affs_fs);
- }
- my_mod = mod;
- }
- GRUB_MOD_FINI(affs)
- {
- if (!grub_is_lockdown ())
- grub_fs_unregister (&grub_affs_fs);
- }
|