diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
commit | 485f6ecd453d8a2fd8b9b9fadea03159d8b50797 (patch) | |
tree | 32451fa3cdd9321fb2591fada9891b2cb70a9cd1 /grub-core/fs | |
parent | Initial commit. (diff) | |
download | grub2-485f6ecd453d8a2fd8b9b9fadea03159d8b50797.tar.xz grub2-485f6ecd453d8a2fd8b9b9fadea03159d8b50797.zip |
Adding upstream version 2.06.upstream/2.06upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'grub-core/fs')
48 files changed, 31972 insertions, 0 deletions
diff --git a/grub-core/fs/affs.c b/grub-core/fs/affs.c new file mode 100644 index 0000000..cafcd0f --- /dev/null +++ b/grub-core/fs/affs.c @@ -0,0 +1,709 @@ +/* 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> + +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 **hashtable, + 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) + { + grub_free (*hashtable); + 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) + return 1; + continue; + } + default: + return 0; + } + break; + } + + if (nest == 8) + return 0; + + type |= GRUB_FSHELP_CASE_INSENSITIVE; + + if (hook ((char *) name_u8, type, *node, hook_data)) + { + grub_free (*hashtable); + *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, &hashtable, + next, &file)) + { + /* Node has been replaced in function. */ + grub_free (orig_node); + 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) +{ + grub_fs_register (&grub_affs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(affs) +{ + grub_fs_unregister (&grub_affs_fs); +} diff --git a/grub-core/fs/afs.c b/grub-core/fs/afs.c new file mode 100644 index 0000000..00a5e31 --- /dev/null +++ b/grub-core/fs/afs.c @@ -0,0 +1,3 @@ +#define MODE_AFS 1 +#include "bfs.c" + diff --git a/grub-core/fs/archelp.c b/grub-core/fs/archelp.c new file mode 100644 index 0000000..0cf544f --- /dev/null +++ b/grub-core/fs/archelp.c @@ -0,0 +1,301 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/archelp.h> +#include <grub/err.h> +#include <grub/fs.h> +#include <grub/disk.h> +#include <grub/dl.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static inline void +canonicalize (char *name) +{ + char *iptr, *optr; + for (iptr = name, optr = name; *iptr; ) + { + while (*iptr == '/') + iptr++; + if (iptr[0] == '.' && (iptr[1] == '/' || iptr[1] == 0)) + { + iptr++; + continue; + } + if (iptr[0] == '.' && iptr[1] == '.' && (iptr[2] == '/' || iptr[2] == 0)) + { + iptr += 2; + if (optr == name) + continue; + for (optr -= 2; optr >= name && *optr != '/'; optr--); + optr++; + continue; + } + while (*iptr && *iptr != '/') + *optr++ = *iptr++; + if (*iptr) + *optr++ = *iptr++; + } + *optr = 0; +} + +static grub_err_t +handle_symlink (struct grub_archelp_data *data, + struct grub_archelp_ops *arcops, + const char *fn, char **name, + grub_uint32_t mode, int *restart) +{ + grub_size_t flen; + char *target; + char *ptr; + char *lastslash; + grub_size_t prefixlen; + char *rest; + char *linktarget; + grub_size_t linktarget_len; + + *restart = 0; + + if ((mode & GRUB_ARCHELP_ATTR_TYPE) != GRUB_ARCHELP_ATTR_LNK + || !arcops->get_link_target) + return GRUB_ERR_NONE; + flen = grub_strlen (fn); + if (grub_memcmp (*name, fn, flen) != 0 + || ((*name)[flen] != 0 && (*name)[flen] != '/')) + return GRUB_ERR_NONE; + rest = *name + flen; + lastslash = rest; + if (*rest) + rest++; + while (lastslash >= *name && *lastslash != '/') + lastslash--; + if (lastslash >= *name) + prefixlen = lastslash - *name; + else + prefixlen = 0; + + if (prefixlen) + prefixlen++; + + linktarget = arcops->get_link_target (data); + if (!linktarget) + return grub_errno; + if (linktarget[0] == '\0') + return GRUB_ERR_NONE; + linktarget_len = grub_strlen (linktarget); + target = grub_malloc (linktarget_len + grub_strlen (*name) + 2); + if (!target) + return grub_errno; + + grub_strcpy (target + prefixlen, linktarget); + grub_free (linktarget); + if (target[prefixlen] == '/') + { + ptr = grub_stpcpy (target, target + prefixlen); + ptr = grub_stpcpy (ptr, rest); + *ptr = 0; + grub_dprintf ("archelp", "symlink redirected %s to %s\n", + *name, target); + grub_free (*name); + + canonicalize (target); + *name = target; + *restart = 1; + return GRUB_ERR_NONE; + } + if (prefixlen) + { + grub_memcpy (target, *name, prefixlen); + target[prefixlen-1] = '/'; + } + grub_strcpy (target + prefixlen + linktarget_len, rest); + grub_dprintf ("archelp", "symlink redirected %s to %s\n", + *name, target); + grub_free (*name); + canonicalize (target); + *name = target; + *restart = 1; + return GRUB_ERR_NONE; +} + +grub_err_t +grub_archelp_dir (struct grub_archelp_data *data, + struct grub_archelp_ops *arcops, + const char *path_in, + grub_fs_dir_hook_t hook, void *hook_data) +{ + char *prev, *name, *path, *ptr; + grub_size_t len; + int symlinknest = 0; + + path = grub_strdup (path_in + 1); + if (!path) + return grub_errno; + canonicalize (path); + for (ptr = path + grub_strlen (path) - 1; ptr >= path && *ptr == '/'; ptr--) + *ptr = 0; + + prev = 0; + + len = grub_strlen (path); + while (1) + { + grub_int32_t mtime; + grub_uint32_t mode; + grub_err_t err; + + if (arcops->find_file (data, &name, &mtime, &mode)) + goto fail; + + if (mode == GRUB_ARCHELP_ATTR_END) + break; + + canonicalize (name); + + if (grub_memcmp (path, name, len) == 0 + && (name[len] == 0 || name[len] == '/' || len == 0)) + { + char *p, *n; + + n = name + len; + while (*n == '/') + n++; + + p = grub_strchr (n, '/'); + if (p) + *p = 0; + + if (((!prev) || (grub_strcmp (prev, name) != 0)) && *n != 0) + { + struct grub_dirhook_info info; + grub_memset (&info, 0, sizeof (info)); + info.dir = (p != NULL) || ((mode & GRUB_ARCHELP_ATTR_TYPE) + == GRUB_ARCHELP_ATTR_DIR); + if (!(mode & GRUB_ARCHELP_ATTR_NOTIME)) + { + info.mtime = mtime; + info.mtimeset = 1; + } + if (hook (n, &info, hook_data)) + { + grub_free (name); + goto fail; + } + grub_free (prev); + prev = name; + } + else + { + int restart = 0; + err = handle_symlink (data, arcops, name, + &path, mode, &restart); + grub_free (name); + if (err) + goto fail; + if (restart) + { + len = grub_strlen (path); + if (++symlinknest == 8) + { + grub_error (GRUB_ERR_SYMLINK_LOOP, + N_("too deep nesting of symlinks")); + goto fail; + } + arcops->rewind (data); + } + } + } + else + grub_free (name); + } + +fail: + + grub_free (path); + grub_free (prev); + + return grub_errno; +} + +grub_err_t +grub_archelp_open (struct grub_archelp_data *data, + struct grub_archelp_ops *arcops, + const char *name_in) +{ + char *fn; + char *name = grub_strdup (name_in + 1); + int symlinknest = 0; + + if (!name) + return grub_errno; + + canonicalize (name); + + while (1) + { + grub_uint32_t mode; + grub_int32_t mtime; + int restart; + + if (arcops->find_file (data, &fn, &mtime, &mode)) + goto fail; + + if (mode == GRUB_ARCHELP_ATTR_END) + { + grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name_in); + break; + } + + canonicalize (fn); + + if (handle_symlink (data, arcops, fn, &name, mode, &restart)) + { + grub_free (fn); + goto fail; + } + + if (restart) + { + arcops->rewind (data); + if (++symlinknest == 8) + { + grub_error (GRUB_ERR_SYMLINK_LOOP, + N_("too deep nesting of symlinks")); + goto fail; + } + goto no_match; + } + + if (grub_strcmp (name, fn) != 0) + goto no_match; + + grub_free (fn); + grub_free (name); + + return GRUB_ERR_NONE; + + no_match: + + grub_free (fn); + } + +fail: + grub_free (name); + + return grub_errno; +} diff --git a/grub-core/fs/bfs.c b/grub-core/fs/bfs.c new file mode 100644 index 0000000..47dbe20 --- /dev/null +++ b/grub-core/fs/bfs.c @@ -0,0 +1,1117 @@ +/* bfs.c - The Bee File System. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 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/>. + */ +/* + Based on the book "Practical File System Design by Dominic Giampaolo + with corrections and completitions based on Haiku code. +*/ + +#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/i18n.h> +#include <grub/fshelp.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#ifdef MODE_AFS +#define BTREE_ALIGN 4 +#define SUPERBLOCK 2 +#else +#define BTREE_ALIGN 8 +#define SUPERBLOCK 1 +#endif + +#define grub_bfs_to_cpu16 grub_le_to_cpu16 +#define grub_bfs_to_cpu32 grub_le_to_cpu32 +#define grub_bfs_to_cpu64 grub_le_to_cpu64 +#define grub_cpu_to_bfs32_compile_time grub_cpu_to_le32_compile_time + +#ifdef MODE_AFS +#define grub_bfs_to_cpu_treehead grub_bfs_to_cpu32 +#else +#define grub_bfs_to_cpu_treehead grub_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 grub_bfs_extent +{ + grub_uint32_t ag; + grub_uint16_t start; + grub_uint16_t len; +} GRUB_PACKED; + +struct grub_bfs_superblock +{ + char label[32]; + grub_uint32_t magic1; + grub_uint32_t unused1; + grub_uint32_t bsize; + grub_uint32_t log2_bsize; + grub_uint8_t unused[20]; + grub_uint32_t magic2; + grub_uint32_t unused2; + grub_uint32_t log2_ag_size; + grub_uint8_t unused3[32]; + grub_uint32_t magic3; + struct grub_bfs_extent root_dir; +} GRUB_PACKED; + +struct grub_bfs_inode +{ + grub_uint8_t unused[20]; + grub_uint32_t mode; + grub_uint32_t flags; +#ifdef MODE_AFS + grub_uint8_t unused2[12]; +#else + grub_uint8_t unused2[8]; +#endif + grub_uint64_t mtime; + grub_uint8_t unused3[8]; + struct grub_bfs_extent attr; + grub_uint8_t unused4[12]; + + union + { + struct + { + struct grub_bfs_extent direct[12]; + grub_uint64_t max_direct_range; + struct grub_bfs_extent indirect; + grub_uint64_t max_indirect_range; + struct grub_bfs_extent double_indirect; + grub_uint64_t max_double_indirect_range; + grub_uint64_t size; + grub_uint32_t pad[4]; + } GRUB_PACKED; + char inplace_link[144]; + } GRUB_PACKED; + grub_uint8_t small_data[0]; +} GRUB_PACKED; + +enum +{ + LONG_SYMLINK = 0x40 +}; + +struct grub_bfs_small_data_element_header +{ + grub_uint32_t type; + grub_uint16_t name_len; + grub_uint16_t value_len; +} GRUB_PACKED; + +struct grub_bfs_btree_header +{ + grub_uint32_t magic; +#ifdef MODE_AFS + grub_uint64_t root; + grub_uint32_t level; + grub_uint32_t node_size; + grub_uint32_t unused; +#else + grub_uint32_t node_size; + grub_uint32_t level; + grub_uint32_t unused; + grub_uint64_t root; +#endif + grub_uint32_t unused2[2]; +} GRUB_PACKED; + +struct grub_bfs_btree_node +{ + grub_uint64_t unused; + grub_uint64_t right; + grub_uint64_t overflow; +#ifdef MODE_AFS + grub_uint32_t count_keys; + grub_uint32_t total_key_len; +#else + grub_uint16_t count_keys; + grub_uint16_t total_key_len; +#endif +} GRUB_PACKED; + +struct grub_bfs_data +{ + struct grub_bfs_superblock sb; + struct grub_bfs_inode ino; +}; + +/* Context for grub_bfs_dir. */ +struct grub_bfs_dir_ctx +{ + grub_device_t device; + grub_fs_dir_hook_t hook; + void *hook_data; + struct grub_bfs_superblock sb; +}; + +static grub_err_t +read_extent (grub_disk_t disk, + const struct grub_bfs_superblock *sb, + const struct grub_bfs_extent *in, + grub_off_t off, grub_off_t byteoff, void *buf, grub_size_t len) +{ +#ifdef MODE_AFS + return grub_disk_read (disk, ((grub_bfs_to_cpu32 (in->ag) + << (grub_bfs_to_cpu32 (sb->log2_ag_size) + - GRUB_DISK_SECTOR_BITS)) + + ((grub_bfs_to_cpu16 (in->start) + off) + << (grub_bfs_to_cpu32 (sb->log2_bsize) + - GRUB_DISK_SECTOR_BITS))), + byteoff, len, buf); +#else + return grub_disk_read (disk, (((grub_bfs_to_cpu32 (in->ag) + << grub_bfs_to_cpu32 (sb->log2_ag_size)) + + grub_bfs_to_cpu16 (in->start) + off) + << (grub_bfs_to_cpu32 (sb->log2_bsize) + - GRUB_DISK_SECTOR_BITS)), + byteoff, len, buf); +#endif +} + +#ifdef MODE_AFS +#define RANGE_SHIFT grub_bfs_to_cpu32 (sb->log2_bsize) +#else +#define RANGE_SHIFT 0 +#endif + +static grub_err_t +read_bfs_file (grub_disk_t disk, + const struct grub_bfs_superblock *sb, + const struct grub_bfs_inode *ino, + grub_off_t off, void *buf, grub_size_t len, + grub_disk_read_hook_t read_hook, void *read_hook_data) +{ + if (len == 0) + return GRUB_ERR_NONE; + + if (off + len > grub_bfs_to_cpu64 (ino->size)) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("attempt to read past the end of file")); + + if (off < (grub_bfs_to_cpu64 (ino->max_direct_range) << RANGE_SHIFT)) + { + unsigned i; + grub_uint64_t pos = 0; + for (i = 0; i < ARRAY_SIZE (ino->direct); i++) + { + grub_uint64_t newpos; + newpos = pos + (((grub_uint64_t) grub_bfs_to_cpu16 (ino->direct[i].len)) + << grub_bfs_to_cpu32 (sb->log2_bsize)); + if (newpos > off) + { + grub_size_t read_size; + grub_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 GRUB_ERR_NONE; + } + pos = newpos; + } + } + + if (off < (grub_bfs_to_cpu64 (ino->max_direct_range) << RANGE_SHIFT)) + return grub_error (GRUB_ERR_BAD_FS, "incorrect direct blocks"); + + if (off < (grub_bfs_to_cpu64 (ino->max_indirect_range) << RANGE_SHIFT)) + { + unsigned i; + struct grub_bfs_extent *entries; + grub_size_t nentries; + grub_err_t err; + grub_uint64_t pos = (grub_bfs_to_cpu64 (ino->max_direct_range) + << RANGE_SHIFT); + nentries = (((grub_size_t) grub_bfs_to_cpu16 (ino->indirect.len)) + << (grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE)); + entries = grub_malloc (nentries << LOG_EXTENT_SIZE); + if (!entries) + return grub_errno; + err = read_extent (disk, sb, &ino->indirect, 0, 0, + entries, nentries << LOG_EXTENT_SIZE); + for (i = 0; i < nentries; i++) + { + grub_uint64_t newpos; + newpos = pos + (((grub_uint64_t) grub_bfs_to_cpu16 (entries[i].len)) + << grub_bfs_to_cpu32 (sb->log2_bsize)); + if (newpos > off) + { + grub_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) + { + grub_free (entries); + return err; + } + off += read_size; + len -= read_size; + buf = (char *) buf + read_size; + if (len == 0) + { + grub_free (entries); + return GRUB_ERR_NONE; + } + } + pos = newpos; + } + grub_free (entries); + } + + if (off < (grub_bfs_to_cpu64 (ino->max_indirect_range) << RANGE_SHIFT)) + return grub_error (GRUB_ERR_BAD_FS, "incorrect indirect blocks"); + + { + struct grub_bfs_extent *l1_entries, *l2_entries; + grub_size_t nl1_entries, nl2_entries; + grub_off_t last_l1n = ~0ULL; + grub_err_t err; + nl1_entries = (((grub_uint64_t) grub_bfs_to_cpu16 (ino->double_indirect.len)) + << (grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE)); + l1_entries = grub_malloc (nl1_entries << LOG_EXTENT_SIZE); + if (!l1_entries) + return grub_errno; + nl2_entries = 0; + l2_entries = grub_malloc (1 << (DOUBLE_INDIRECT_SHIFT + + grub_bfs_to_cpu32 (sb->log2_bsize))); + if (!l2_entries) + { + grub_free (l1_entries); + return grub_errno; + } + err = read_extent (disk, sb, &ino->double_indirect, 0, 0, + l1_entries, nl1_entries << LOG_EXTENT_SIZE); + if (err) + { + grub_free (l1_entries); + grub_free (l2_entries); + return err; + } + + while (len > 0) + { + grub_off_t boff, l2n, l1n; + grub_size_t read_size; + grub_off_t double_indirect_offset; + double_indirect_offset = off + - grub_bfs_to_cpu64 (ino->max_indirect_range); + boff = (double_indirect_offset + & ((1 << (grub_bfs_to_cpu32 (sb->log2_bsize) + + DOUBLE_INDIRECT_SHIFT)) - 1)); + l2n = ((double_indirect_offset >> (grub_bfs_to_cpu32 (sb->log2_bsize) + + DOUBLE_INDIRECT_SHIFT)) + & ((1 << (grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE + + DOUBLE_INDIRECT_SHIFT)) - 1)); + l1n = + (double_indirect_offset >> + (2 * grub_bfs_to_cpu32 (sb->log2_bsize) - LOG_EXTENT_SIZE + + 2 * DOUBLE_INDIRECT_SHIFT)); + if (l1n > nl1_entries) + { + grub_free (l1_entries); + grub_free (l2_entries); + return grub_error (GRUB_ERR_BAD_FS, + "incorrect double-indirect block"); + } + if (l1n != last_l1n) + { + nl2_entries = (((grub_uint64_t) grub_bfs_to_cpu16 (l1_entries[l1n].len)) + << (grub_bfs_to_cpu32 (sb->log2_bsize) + - LOG_EXTENT_SIZE)); + if (nl2_entries > (1U << (grub_bfs_to_cpu32 (sb->log2_bsize) + - LOG_EXTENT_SIZE + + DOUBLE_INDIRECT_SHIFT))) + nl2_entries = (1 << (grub_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) + { + grub_free (l1_entries); + grub_free (l2_entries); + return err; + } + last_l1n = l1n; + } + if (l2n > nl2_entries) + { + grub_free (l1_entries); + grub_free (l2_entries); + return grub_error (GRUB_ERR_BAD_FS, + "incorrect double-indirect block"); + } + + read_size = (1 << (grub_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) + { + grub_free (l1_entries); + grub_free (l2_entries); + return err; + } + off += read_size; + len -= read_size; + buf = (char *) buf + read_size; + } + return GRUB_ERR_NONE; + } +} + +static grub_err_t +read_b_node (grub_disk_t disk, + const struct grub_bfs_superblock *sb, + const struct grub_bfs_inode *ino, + grub_uint64_t node_off, + struct grub_bfs_btree_node **node, + char **key_data, grub_uint16_t **keylen_idx, + grub_unaligned_uint64_t **key_values) +{ + void *ret; + struct grub_bfs_btree_node node_head; + grub_size_t total_size; + grub_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) + + grub_bfs_to_cpu_treehead + (node_head.total_key_len), + BTREE_ALIGN) + + grub_bfs_to_cpu_treehead (node_head.count_keys) * + sizeof (grub_uint16_t) + + grub_bfs_to_cpu_treehead (node_head.count_keys) * + sizeof (grub_uint64_t); + + ret = grub_malloc (total_size); + if (!ret) + return grub_errno; + + err = read_bfs_file (disk, sb, ino, node_off, ret, total_size, 0, 0); + if (err) + { + grub_free (ret); + return err; + } + + *node = ret; + *key_data = (char *) ret + sizeof (node_head); + *keylen_idx = (grub_uint16_t *) ret + + ALIGN_UP (sizeof (node_head) + + grub_bfs_to_cpu_treehead (node_head.total_key_len), + BTREE_ALIGN) / 2; + *key_values = (grub_unaligned_uint64_t *) + (*keylen_idx + + grub_bfs_to_cpu_treehead (node_head.count_keys)); + + return GRUB_ERR_NONE; +} + +static int +iterate_in_b_tree (grub_disk_t disk, + const struct grub_bfs_superblock *sb, + const struct grub_bfs_inode *ino, + int (*hook) (const char *name, grub_uint64_t value, + struct grub_bfs_dir_ctx *ctx), + struct grub_bfs_dir_ctx *ctx) +{ + struct grub_bfs_btree_header head; + grub_err_t err; + int level; + grub_uint64_t node_off; + + err = read_bfs_file (disk, sb, ino, 0, &head, sizeof (head), 0, 0); + if (err) + return 0; + node_off = grub_bfs_to_cpu64 (head.root); + + level = grub_bfs_to_cpu32 (head.level) - 1; + while (level--) + { + struct grub_bfs_btree_node node; + grub_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) + + grub_bfs_to_cpu_treehead (node. + total_key_len), + BTREE_ALIGN) + + grub_bfs_to_cpu_treehead (node.count_keys) * + sizeof (grub_uint16_t), &key_value, + sizeof (grub_uint64_t), 0, 0); + if (err) + return 0; + + node_off = grub_bfs_to_cpu64 (key_value); + } + + while (1) + { + struct grub_bfs_btree_node *node; + char *key_data; + grub_uint16_t *keylen_idx; + grub_unaligned_uint64_t *key_values; + unsigned i; + grub_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 < grub_bfs_to_cpu_treehead (node->count_keys); i++) + { + char c; + start = end; + end = grub_bfs_to_cpu16 (keylen_idx[i]); + if (grub_bfs_to_cpu_treehead (node->total_key_len) <= end) + end = grub_bfs_to_cpu_treehead (node->total_key_len); + c = key_data[end]; + key_data[end] = 0; + if (hook (key_data + start, grub_bfs_to_cpu64 (key_values[i].val), + ctx)) + { + grub_free (node); + return 1; + } + key_data[end] = c; + } + node_off = grub_bfs_to_cpu64 (node->right); + grub_free (node); + if (node_off == POINTER_INVALID) + return 0; + } +} + +static int +bfs_strcmp (const char *a, const char *b, grub_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) (grub_int8_t) ac - (int) (grub_int8_t) bc; +#else + return (int) (grub_uint8_t) ac - (int) (grub_uint8_t) bc; +#endif +} + +static grub_err_t +find_in_b_tree (grub_disk_t disk, + const struct grub_bfs_superblock *sb, + const struct grub_bfs_inode *ino, const char *name, + grub_uint64_t * res) +{ + struct grub_bfs_btree_header head; + grub_err_t err; + int level; + grub_uint64_t node_off; + + err = read_bfs_file (disk, sb, ino, 0, &head, sizeof (head), 0, 0); + if (err) + return err; + node_off = grub_bfs_to_cpu64 (head.root); + + level = grub_bfs_to_cpu32 (head.level) - 1; + while (1) + { + struct grub_bfs_btree_node *node; + char *key_data; + grub_uint16_t *keylen_idx; + grub_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) + { + grub_free (node); + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), + name); + } + + for (lg = 0; grub_bfs_to_cpu_treehead (node->count_keys) >> lg; lg++); + + i = 0; + + for (j = lg - 1; j >= 0; j--) + { + int cmp; + grub_uint16_t start = 0, end = 0; + if ((i | (1 << j)) >= grub_bfs_to_cpu_treehead (node->count_keys)) + continue; + start = grub_bfs_to_cpu16 (keylen_idx[(i | (1 << j)) - 1]); + end = grub_bfs_to_cpu16 (keylen_idx[(i | (1 << j))]); + if (grub_bfs_to_cpu_treehead (node->total_key_len) <= end) + end = grub_bfs_to_cpu_treehead (node->total_key_len); + cmp = bfs_strcmp (key_data + start, name, end - start); + if (cmp == 0 && level == 0) + { + *res = grub_bfs_to_cpu64 (key_values[i | (1 << j)].val); + grub_free (node); + return GRUB_ERR_NONE; + } +#ifdef MODE_AFS + if (cmp <= 0) +#else + if (cmp < 0) +#endif + i |= (1 << j); + } + if (i == 0) + { + grub_uint16_t end = 0; + int cmp; + end = grub_bfs_to_cpu16 (keylen_idx[0]); + if (grub_bfs_to_cpu_treehead (node->total_key_len) <= end) + end = grub_bfs_to_cpu_treehead (node->total_key_len); + cmp = bfs_strcmp (key_data, name, end); + if (cmp == 0 && level == 0) + { + *res = grub_bfs_to_cpu64 (key_values[0].val); + grub_free (node); + return GRUB_ERR_NONE; + } +#ifdef MODE_AFS + if (cmp > 0 && level != 0) +#else + if (cmp >= 0 && level != 0) +#endif + { + node_off = grub_bfs_to_cpu64 (key_values[0].val); + level--; + grub_free (node); + continue; + } + else if (level != 0 + && grub_bfs_to_cpu_treehead (node->count_keys) >= 2) + { + node_off = grub_bfs_to_cpu64 (key_values[1].val); + level--; + grub_free (node); + continue; + } + } + else if (level != 0 + && i + 1 < grub_bfs_to_cpu_treehead (node->count_keys)) + { + node_off = grub_bfs_to_cpu64 (key_values[i + 1].val); + level--; + grub_free (node); + continue; + } + if (node->overflow != POINTER_INVALID) + { + node_off = grub_bfs_to_cpu64 (node->overflow); + /* This level-- isn't specified but is needed. */ + level--; + grub_free (node); + continue; + } + grub_free (node); + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), + name); + } +} + +struct grub_fshelp_node +{ + grub_disk_t disk; + const struct grub_bfs_superblock *sb; + struct grub_bfs_inode ino; +}; + +static grub_err_t +lookup_file (grub_fshelp_node_t dir, + const char *name, + grub_fshelp_node_t *foundnode, + enum grub_fshelp_filetype *foundtype) +{ + grub_err_t err; + struct grub_bfs_inode *new_ino; + grub_uint64_t res = 0; + + err = find_in_b_tree (dir->disk, dir->sb, &dir->ino, name, &res); + if (err) + return err; + + *foundnode = grub_malloc (sizeof (struct grub_fshelp_node)); + if (!*foundnode) + return grub_errno; + + (*foundnode)->disk = dir->disk; + (*foundnode)->sb = dir->sb; + new_ino = &(*foundnode)->ino; + + if (grub_disk_read (dir->disk, res + << (grub_bfs_to_cpu32 (dir->sb->log2_bsize) + - GRUB_DISK_SECTOR_BITS), 0, + sizeof (*new_ino), (char *) new_ino)) + { + grub_free (*foundnode); + return grub_errno; + } + switch (grub_bfs_to_cpu32 (new_ino->mode) & ATTR_TYPE) + { + default: + case ATTR_REG: + *foundtype = GRUB_FSHELP_REG; + break; + case ATTR_DIR: + *foundtype = GRUB_FSHELP_DIR; + break; + case ATTR_LNK: + *foundtype = GRUB_FSHELP_SYMLINK; + break; + } + return GRUB_ERR_NONE; +} + +static char * +read_symlink (grub_fshelp_node_t node) +{ + char *alloc = NULL; + grub_err_t err; + +#ifndef MODE_AFS + if (!(grub_bfs_to_cpu32 (node->ino.flags) & LONG_SYMLINK)) + { + alloc = grub_malloc (sizeof (node->ino.inplace_link) + 1); + if (!alloc) + { + return NULL; + } + grub_memcpy (alloc, node->ino.inplace_link, + sizeof (node->ino.inplace_link)); + alloc[sizeof (node->ino.inplace_link)] = 0; + } + else +#endif + { + grub_size_t symsize = grub_bfs_to_cpu64 (node->ino.size); + alloc = grub_malloc (symsize + 1); + if (!alloc) + return NULL; + err = read_bfs_file (node->disk, node->sb, &node->ino, 0, alloc, symsize, 0, 0); + if (err) + { + grub_free (alloc); + return NULL; + } + alloc[symsize] = 0; + } + + return alloc; +} + +static grub_err_t +find_file (const char *path, grub_disk_t disk, + const struct grub_bfs_superblock *sb, struct grub_bfs_inode *ino, + enum grub_fshelp_filetype exptype) +{ + grub_err_t err; + struct grub_fshelp_node root = { + .disk = disk, + .sb = sb, + }; + struct grub_fshelp_node *found; + + err = read_extent (disk, sb, &sb->root_dir, 0, 0, &root.ino, + sizeof (root.ino)); + if (err) + return err; + err = grub_fshelp_find_file_lookup (path, &root, &found, lookup_file, read_symlink, exptype); + if (!err) + grub_memcpy (ino, &found->ino, sizeof (*ino)); + + if (&root != found) + grub_free (found); + return err; +} + +static grub_err_t +mount (grub_disk_t disk, struct grub_bfs_superblock *sb) +{ + grub_err_t err; + err = grub_disk_read (disk, SUPERBLOCK, 0, sizeof (*sb), sb); + if (err == GRUB_ERR_OUT_OF_RANGE) + return grub_error (GRUB_ERR_BAD_FS, +#ifdef MODE_AFS + "not an AFS filesystem" +#else + "not a BFS filesystem" +#endif + ); + if (err) + return err; + if (sb->magic1 != grub_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC1) + || sb->magic2 != grub_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC2) + || sb->magic3 != grub_cpu_to_bfs32_compile_time (SUPER_BLOCK_MAGIC3) + || sb->bsize == 0 + || (grub_bfs_to_cpu32 (sb->bsize) + != (1U << grub_bfs_to_cpu32 (sb->log2_bsize))) + || grub_bfs_to_cpu32 (sb->log2_bsize) < GRUB_DISK_SECTOR_BITS) + return grub_error (GRUB_ERR_BAD_FS, +#ifdef MODE_AFS + "not an AFS filesystem" +#else + "not a BFS filesystem" +#endif + ); + return GRUB_ERR_NONE; +} + +/* Helper for grub_bfs_dir. */ +static int +grub_bfs_dir_iter (const char *name, grub_uint64_t value, + struct grub_bfs_dir_ctx *ctx) +{ + grub_err_t err2; + struct grub_bfs_inode ino; + struct grub_dirhook_info info; + + err2 = grub_disk_read (ctx->device->disk, value + << (grub_bfs_to_cpu32 (ctx->sb.log2_bsize) + - GRUB_DISK_SECTOR_BITS), 0, + sizeof (ino), (char *) &ino); + if (err2) + { + grub_print_error (); + return 0; + } + + info.mtimeset = 1; +#ifdef MODE_AFS + info.mtime = + grub_divmod64 (grub_bfs_to_cpu64 (ino.mtime), 1000000, 0); +#else + info.mtime = grub_bfs_to_cpu64 (ino.mtime) >> 16; +#endif + info.dir = ((grub_bfs_to_cpu32 (ino.mode) & ATTR_TYPE) == ATTR_DIR); + return ctx->hook (name, &info, ctx->hook_data); +} + +static grub_err_t +grub_bfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_bfs_dir_ctx ctx = { + .device = device, + .hook = hook, + .hook_data = hook_data + }; + grub_err_t err; + + err = mount (device->disk, &ctx.sb); + if (err) + return err; + + { + struct grub_bfs_inode ino; + err = find_file (path, device->disk, &ctx.sb, &ino, GRUB_FSHELP_DIR); + if (err) + return err; + iterate_in_b_tree (device->disk, &ctx.sb, &ino, grub_bfs_dir_iter, + &ctx); + } + + return grub_errno; +} + +static grub_err_t +grub_bfs_open (struct grub_file *file, const char *name) +{ + struct grub_bfs_superblock sb; + grub_err_t err; + + err = mount (file->device->disk, &sb); + if (err) + return err; + + { + struct grub_bfs_inode ino; + struct grub_bfs_data *data; + err = find_file (name, file->device->disk, &sb, &ino, GRUB_FSHELP_REG); + if (err) + return err; + + data = grub_zalloc (sizeof (struct grub_bfs_data)); + if (!data) + return grub_errno; + data->sb = sb; + grub_memcpy (&data->ino, &ino, sizeof (data->ino)); + file->data = data; + file->size = grub_bfs_to_cpu64 (ino.size); + } + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_bfs_close (grub_file_t file) +{ + grub_free (file->data); + + return GRUB_ERR_NONE; +} + +static grub_ssize_t +grub_bfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + grub_err_t err; + struct grub_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 grub_err_t +grub_bfs_label (grub_device_t device, char **label) +{ + struct grub_bfs_superblock sb; + grub_err_t err; + + *label = 0; + + err = mount (device->disk, &sb); + if (err) + return err; + + *label = grub_strndup (sb.label, sizeof (sb.label)); + return GRUB_ERR_NONE; +} + +#ifndef MODE_AFS +static grub_ssize_t +read_bfs_attr (grub_disk_t disk, + const struct grub_bfs_superblock *sb, + struct grub_bfs_inode *ino, + const char *name, void *buf, grub_size_t len) +{ + grub_uint8_t *ptr = (grub_uint8_t *) ino->small_data; + grub_uint8_t *end = ((grub_uint8_t *) ino + grub_bfs_to_cpu32 (sb->bsize)); + + while (ptr + sizeof (struct grub_bfs_small_data_element_header) < end) + { + struct grub_bfs_small_data_element_header *el; + char *el_name; + grub_uint8_t *data; + el = (struct grub_bfs_small_data_element_header *) ptr; + if (el->name_len == 0) + break; + el_name = (char *) (el + 1); + data = (grub_uint8_t *) el_name + grub_bfs_to_cpu16 (el->name_len) + 3; + ptr = data + grub_bfs_to_cpu16 (el->value_len) + 1; + if (grub_memcmp (name, el_name, grub_bfs_to_cpu16 (el->name_len)) == 0 + && name[el->name_len] == 0) + { + grub_size_t copy; + copy = len; + if (grub_bfs_to_cpu16 (el->value_len) > copy) + copy = grub_bfs_to_cpu16 (el->value_len); + grub_memcpy (buf, data, copy); + return copy; + } + } + + if (ino->attr.len != 0) + { + grub_size_t read; + grub_err_t err; + grub_uint64_t res; + + err = read_extent (disk, sb, &ino->attr, 0, 0, ino, + grub_bfs_to_cpu32 (sb->bsize)); + if (err) + return -1; + + err = find_in_b_tree (disk, sb, ino, name, &res); + if (err) + return -1; + grub_disk_read (disk, res + << (grub_bfs_to_cpu32 (sb->log2_bsize) + - GRUB_DISK_SECTOR_BITS), 0, + grub_bfs_to_cpu32 (sb->bsize), (char *) ino); + read = grub_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 grub_err_t +grub_bfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_bfs_superblock sb; + grub_err_t err; + struct grub_bfs_inode *ino; + grub_uint64_t vid; + + *uuid = 0; + + err = mount (device->disk, &sb); + if (err) + return err; + + ino = grub_malloc (grub_bfs_to_cpu32 (sb.bsize)); + if (!ino) + return grub_errno; + + err = read_extent (device->disk, &sb, &sb.root_dir, 0, 0, + ino, grub_bfs_to_cpu32 (sb.bsize)); + if (err) + { + grub_free (ino); + return err; + } + if (read_bfs_attr (device->disk, &sb, ino, "be:volume_id", + &vid, sizeof (vid)) == sizeof (vid)) + *uuid = + grub_xasprintf ("%016" PRIxGRUB_UINT64_T, grub_bfs_to_cpu64 (vid)); + + grub_free (ino); + + return GRUB_ERR_NONE; +} +#endif + +static struct grub_fs grub_bfs_fs = { +#ifdef MODE_AFS + .name = "afs", +#else + .name = "bfs", +#endif + .fs_dir = grub_bfs_dir, + .fs_open = grub_bfs_open, + .fs_read = grub_bfs_read, + .fs_close = grub_bfs_close, + .fs_label = grub_bfs_label, +#ifndef MODE_AFS + .fs_uuid = grub_bfs_uuid, +#endif +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif +}; + +#ifdef MODE_AFS +GRUB_MOD_INIT (afs) +#else +GRUB_MOD_INIT (bfs) +#endif +{ + COMPILE_TIME_ASSERT (1 << LOG_EXTENT_SIZE == + sizeof (struct grub_bfs_extent)); + grub_fs_register (&grub_bfs_fs); +} + +#ifdef MODE_AFS +GRUB_MOD_FINI (afs) +#else +GRUB_MOD_FINI (bfs) +#endif +{ + grub_fs_unregister (&grub_bfs_fs); +} diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c new file mode 100644 index 0000000..6320303 --- /dev/null +++ b/grub-core/fs/btrfs.c @@ -0,0 +1,2216 @@ +/* btrfs.c - B-tree file system. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011,2012,2013 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/>. + */ + +/* + * Tell zstd to expose functions that aren't part of the stable API, which + * aren't safe to use when linking against a dynamic library. We vendor in a + * specific zstd version, so we know what we're getting. We need these unstable + * functions to provide our own allocator, which uses grub_malloc(), to zstd. + */ +#define ZSTD_STATIC_LINKING_ONLY + +#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/lib/crc.h> +#include <grub/deflate.h> +#include <minilzo.h> +#include <zstd.h> +#include <grub/i18n.h> +#include <grub/btrfs.h> +#include <grub/crypto.h> +#include <grub/diskfilter.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_BTRFS_SIGNATURE "_BHRfS_M" + +/* From http://www.oberhumer.com/opensource/lzo/lzofaq.php + * LZO will expand incompressible data by a little amount. I still haven't + * computed the exact values, but I suggest using these formulas for + * a worst-case expansion calculation: + * + * output_block_size = input_block_size + (input_block_size / 16) + 64 + 3 + * */ +#define GRUB_BTRFS_LZO_BLOCK_SIZE 4096 +#define GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE (GRUB_BTRFS_LZO_BLOCK_SIZE + \ + (GRUB_BTRFS_LZO_BLOCK_SIZE / 16) + 64 + 3) + +#define ZSTD_BTRFS_MAX_WINDOWLOG 17 +#define ZSTD_BTRFS_MAX_INPUT (1 << ZSTD_BTRFS_MAX_WINDOWLOG) + +typedef grub_uint8_t grub_btrfs_checksum_t[0x20]; +typedef grub_uint16_t grub_btrfs_uuid_t[8]; + +struct grub_btrfs_device +{ + grub_uint64_t device_id; + grub_uint64_t size; + grub_uint8_t dummy[0x62 - 0x10]; +} GRUB_PACKED; + +struct grub_btrfs_superblock +{ + grub_btrfs_checksum_t checksum; + grub_btrfs_uuid_t uuid; + grub_uint8_t dummy[0x10]; + grub_uint8_t signature[sizeof (GRUB_BTRFS_SIGNATURE) - 1]; + grub_uint64_t generation; + grub_uint64_t root_tree; + grub_uint64_t chunk_tree; + grub_uint8_t dummy2[0x20]; + grub_uint64_t root_dir_objectid; + grub_uint8_t dummy3[0x41]; + struct grub_btrfs_device this_device; + char label[0x100]; + grub_uint8_t dummy4[0x100]; + grub_uint8_t bootstrap_mapping[0x800]; +} GRUB_PACKED; + +struct btrfs_header +{ + grub_btrfs_checksum_t checksum; + grub_btrfs_uuid_t uuid; + grub_uint64_t bytenr; + grub_uint8_t dummy[0x28]; + grub_uint32_t nitems; + grub_uint8_t level; +} GRUB_PACKED; + +struct grub_btrfs_device_desc +{ + grub_device_t dev; + grub_uint64_t id; +}; + +struct grub_btrfs_data +{ + struct grub_btrfs_superblock sblock; + grub_uint64_t tree; + grub_uint64_t inode; + + struct grub_btrfs_device_desc *devices_attached; + unsigned n_devices_attached; + unsigned n_devices_allocated; + + /* Cached extent data. */ + grub_uint64_t extstart; + grub_uint64_t extend; + grub_uint64_t extino; + grub_uint64_t exttree; + grub_size_t extsize; + struct grub_btrfs_extent_data *extent; +}; + +struct grub_btrfs_chunk_item +{ + grub_uint64_t size; + grub_uint64_t dummy; + grub_uint64_t stripe_length; + grub_uint64_t type; +#define GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE 0x07 +#define GRUB_BTRFS_CHUNK_TYPE_SINGLE 0x00 +#define GRUB_BTRFS_CHUNK_TYPE_RAID0 0x08 +#define GRUB_BTRFS_CHUNK_TYPE_RAID1 0x10 +#define GRUB_BTRFS_CHUNK_TYPE_DUPLICATED 0x20 +#define GRUB_BTRFS_CHUNK_TYPE_RAID10 0x40 +#define GRUB_BTRFS_CHUNK_TYPE_RAID5 0x80 +#define GRUB_BTRFS_CHUNK_TYPE_RAID6 0x100 +#define GRUB_BTRFS_CHUNK_TYPE_RAID1C3 0x200 +#define GRUB_BTRFS_CHUNK_TYPE_RAID1C4 0x400 + grub_uint8_t dummy2[0xc]; + grub_uint16_t nstripes; + grub_uint16_t nsubstripes; +} GRUB_PACKED; + +struct grub_btrfs_chunk_stripe +{ + grub_uint64_t device_id; + grub_uint64_t offset; + grub_btrfs_uuid_t device_uuid; +} GRUB_PACKED; + +struct grub_btrfs_leaf_node +{ + struct grub_btrfs_key key; + grub_uint32_t offset; + grub_uint32_t size; +} GRUB_PACKED; + +struct grub_btrfs_internal_node +{ + struct grub_btrfs_key key; + grub_uint64_t addr; + grub_uint64_t dummy; +} GRUB_PACKED; + +struct grub_btrfs_dir_item +{ + struct grub_btrfs_key key; + grub_uint8_t dummy[8]; + grub_uint16_t m; + grub_uint16_t n; +#define GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR 1 +#define GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY 2 +#define GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK 7 + grub_uint8_t type; + char name[0]; +} GRUB_PACKED; + +struct grub_btrfs_leaf_descriptor +{ + unsigned depth; + unsigned allocated; + struct + { + grub_disk_addr_t addr; + unsigned iter; + unsigned maxiter; + int leaf; + } *data; +}; + +struct grub_btrfs_time +{ + grub_int64_t sec; + grub_uint32_t nanosec; +} GRUB_PACKED; + +struct grub_btrfs_inode +{ + grub_uint8_t dummy1[0x10]; + grub_uint64_t size; + grub_uint8_t dummy2[0x70]; + struct grub_btrfs_time mtime; +} GRUB_PACKED; + +struct grub_btrfs_extent_data +{ + grub_uint64_t dummy; + grub_uint64_t size; + grub_uint8_t compression; + grub_uint8_t encryption; + grub_uint16_t encoding; + grub_uint8_t type; + union + { + char inl[0]; + struct + { + grub_uint64_t laddr; + grub_uint64_t compressed_size; + grub_uint64_t offset; + grub_uint64_t filled; + }; + }; +} GRUB_PACKED; + +#define GRUB_BTRFS_EXTENT_INLINE 0 +#define GRUB_BTRFS_EXTENT_REGULAR 1 + +#define GRUB_BTRFS_COMPRESSION_NONE 0 +#define GRUB_BTRFS_COMPRESSION_ZLIB 1 +#define GRUB_BTRFS_COMPRESSION_LZO 2 +#define GRUB_BTRFS_COMPRESSION_ZSTD 3 + +#define GRUB_BTRFS_OBJECT_ID_CHUNK 0x100 + +static grub_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2, + 256 * 1048576 * 2, 1048576ULL * 1048576ULL * 2 +}; + +static grub_err_t +grub_btrfs_read_logical (struct grub_btrfs_data *data, + grub_disk_addr_t addr, void *buf, grub_size_t size, + int recursion_depth); + +static grub_err_t +read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb) +{ + struct grub_btrfs_superblock sblock; + unsigned i; + grub_err_t err = GRUB_ERR_NONE; + for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++) + { + /* Don't try additional superblocks beyond device size. */ + if (i && (grub_le_to_cpu64 (sblock.this_device.size) + >> GRUB_DISK_SECTOR_BITS) <= superblock_sectors[i]) + break; + err = grub_disk_read (disk, superblock_sectors[i], 0, + sizeof (sblock), &sblock); + if (err == GRUB_ERR_OUT_OF_RANGE) + break; + + if (grub_memcmp ((char *) sblock.signature, GRUB_BTRFS_SIGNATURE, + sizeof (GRUB_BTRFS_SIGNATURE) - 1) != 0) + break; + if (i == 0 || grub_le_to_cpu64 (sblock.generation) + > grub_le_to_cpu64 (sb->generation)) + grub_memcpy (sb, &sblock, sizeof (sblock)); + } + + if ((err == GRUB_ERR_OUT_OF_RANGE || !err) && i == 0) + return grub_error (GRUB_ERR_BAD_FS, "not a Btrfs filesystem"); + + if (err == GRUB_ERR_OUT_OF_RANGE) + grub_errno = err = GRUB_ERR_NONE; + + return err; +} + +static int +key_cmp (const struct grub_btrfs_key *a, const struct grub_btrfs_key *b) +{ + if (grub_le_to_cpu64 (a->object_id) < grub_le_to_cpu64 (b->object_id)) + return -1; + if (grub_le_to_cpu64 (a->object_id) > grub_le_to_cpu64 (b->object_id)) + return +1; + + if (a->type < b->type) + return -1; + if (a->type > b->type) + return +1; + + if (grub_le_to_cpu64 (a->offset) < grub_le_to_cpu64 (b->offset)) + return -1; + if (grub_le_to_cpu64 (a->offset) > grub_le_to_cpu64 (b->offset)) + return +1; + return 0; +} + +static void +free_iterator (struct grub_btrfs_leaf_descriptor *desc) +{ + grub_free (desc->data); +} + +static grub_err_t +check_btrfs_header (struct grub_btrfs_data *data, struct btrfs_header *header, + grub_disk_addr_t addr) +{ + if (grub_le_to_cpu64 (header->bytenr) != addr) + { + grub_dprintf ("btrfs", "btrfs_header.bytenr is not equal node addr\n"); + return grub_error (GRUB_ERR_BAD_FS, + "header bytenr is not equal node addr"); + } + if (grub_memcmp (data->sblock.uuid, header->uuid, sizeof(grub_btrfs_uuid_t))) + { + grub_dprintf ("btrfs", "btrfs_header.uuid doesn't match sblock uuid\n"); + return grub_error (GRUB_ERR_BAD_FS, + "header uuid doesn't match sblock uuid"); + } + return GRUB_ERR_NONE; +} + +static grub_err_t +save_ref (struct grub_btrfs_leaf_descriptor *desc, + grub_disk_addr_t addr, unsigned i, unsigned m, int l) +{ + desc->depth++; + if (desc->allocated < desc->depth) + { + void *newdata; + grub_size_t sz; + + if (grub_mul (desc->allocated, 2, &desc->allocated) || + grub_mul (desc->allocated, sizeof (desc->data[0]), &sz)) + return GRUB_ERR_OUT_OF_RANGE; + + newdata = grub_realloc (desc->data, sz); + if (!newdata) + return grub_errno; + desc->data = newdata; + } + desc->data[desc->depth - 1].addr = addr; + desc->data[desc->depth - 1].iter = i; + desc->data[desc->depth - 1].maxiter = m; + desc->data[desc->depth - 1].leaf = l; + return GRUB_ERR_NONE; +} + +static int +next (struct grub_btrfs_data *data, + struct grub_btrfs_leaf_descriptor *desc, + grub_disk_addr_t * outaddr, grub_size_t * outsize, + struct grub_btrfs_key *key_out) +{ + grub_err_t err; + struct grub_btrfs_leaf_node leaf; + + for (; desc->depth > 0; desc->depth--) + { + desc->data[desc->depth - 1].iter++; + if (desc->data[desc->depth - 1].iter + < desc->data[desc->depth - 1].maxiter) + break; + } + if (desc->depth == 0) + return 0; + while (!desc->data[desc->depth - 1].leaf) + { + struct grub_btrfs_internal_node node; + struct btrfs_header head; + + err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter + * sizeof (node) + + sizeof (struct btrfs_header) + + desc->data[desc->depth - 1].addr, + &node, sizeof (node), 0); + if (err) + return -err; + + err = grub_btrfs_read_logical (data, grub_le_to_cpu64 (node.addr), + &head, sizeof (head), 0); + if (err) + return -err; + check_btrfs_header (data, &head, grub_le_to_cpu64 (node.addr)); + + save_ref (desc, grub_le_to_cpu64 (node.addr), 0, + grub_le_to_cpu32 (head.nitems), !head.level); + } + err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter + * sizeof (leaf) + + sizeof (struct btrfs_header) + + desc->data[desc->depth - 1].addr, &leaf, + sizeof (leaf), 0); + if (err) + return -err; + *outsize = grub_le_to_cpu32 (leaf.size); + *outaddr = desc->data[desc->depth - 1].addr + sizeof (struct btrfs_header) + + grub_le_to_cpu32 (leaf.offset); + *key_out = leaf.key; + return 1; +} + +static grub_err_t +lower_bound (struct grub_btrfs_data *data, + const struct grub_btrfs_key *key_in, + struct grub_btrfs_key *key_out, + grub_uint64_t root, + grub_disk_addr_t *outaddr, grub_size_t *outsize, + struct grub_btrfs_leaf_descriptor *desc, + int recursion_depth) +{ + grub_disk_addr_t addr = grub_le_to_cpu64 (root); + int depth = -1; + + if (desc) + { + desc->allocated = 16; + desc->depth = 0; + desc->data = grub_calloc (desc->allocated, sizeof (desc->data[0])); + if (!desc->data) + return grub_errno; + } + + /* > 2 would work as well but be robust and allow a bit more just in case. + */ + if (recursion_depth > 10) + return grub_error (GRUB_ERR_BAD_FS, "too deep btrfs virtual nesting"); + + grub_dprintf ("btrfs", + "retrieving %" PRIxGRUB_UINT64_T + " %x %" PRIxGRUB_UINT64_T "\n", + key_in->object_id, key_in->type, key_in->offset); + + while (1) + { + grub_err_t err; + struct btrfs_header head; + + reiter: + depth++; + /* FIXME: preread few nodes into buffer. */ + err = grub_btrfs_read_logical (data, addr, &head, sizeof (head), + recursion_depth + 1); + if (err) + return err; + check_btrfs_header (data, &head, addr); + addr += sizeof (head); + if (head.level) + { + unsigned i; + struct grub_btrfs_internal_node node, node_last; + int have_last = 0; + grub_memset (&node_last, 0, sizeof (node_last)); + for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++) + { + err = grub_btrfs_read_logical (data, addr + i * sizeof (node), + &node, sizeof (node), + recursion_depth + 1); + if (err) + return err; + + grub_dprintf ("btrfs", + "internal node (depth %d) %" PRIxGRUB_UINT64_T + " %x %" PRIxGRUB_UINT64_T "\n", depth, + node.key.object_id, node.key.type, + node.key.offset); + + if (key_cmp (&node.key, key_in) == 0) + { + err = GRUB_ERR_NONE; + if (desc) + err = save_ref (desc, addr - sizeof (head), i, + grub_le_to_cpu32 (head.nitems), 0); + if (err) + return err; + addr = grub_le_to_cpu64 (node.addr); + goto reiter; + } + if (key_cmp (&node.key, key_in) > 0) + break; + node_last = node; + have_last = 1; + } + if (have_last) + { + err = GRUB_ERR_NONE; + if (desc) + err = save_ref (desc, addr - sizeof (head), i - 1, + grub_le_to_cpu32 (head.nitems), 0); + if (err) + return err; + addr = grub_le_to_cpu64 (node_last.addr); + goto reiter; + } + *outsize = 0; + *outaddr = 0; + grub_memset (key_out, 0, sizeof (*key_out)); + if (desc) + return save_ref (desc, addr - sizeof (head), -1, + grub_le_to_cpu32 (head.nitems), 0); + return GRUB_ERR_NONE; + } + { + unsigned i; + struct grub_btrfs_leaf_node leaf, leaf_last; + int have_last = 0; + for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++) + { + err = grub_btrfs_read_logical (data, addr + i * sizeof (leaf), + &leaf, sizeof (leaf), + recursion_depth + 1); + if (err) + return err; + + grub_dprintf ("btrfs", + "leaf (depth %d) %" PRIxGRUB_UINT64_T + " %x %" PRIxGRUB_UINT64_T "\n", depth, + leaf.key.object_id, leaf.key.type, leaf.key.offset); + + if (key_cmp (&leaf.key, key_in) == 0) + { + grub_memcpy (key_out, &leaf.key, sizeof (*key_out)); + *outsize = grub_le_to_cpu32 (leaf.size); + *outaddr = addr + grub_le_to_cpu32 (leaf.offset); + if (desc) + return save_ref (desc, addr - sizeof (head), i, + grub_le_to_cpu32 (head.nitems), 1); + return GRUB_ERR_NONE; + } + + if (key_cmp (&leaf.key, key_in) > 0) + break; + + have_last = 1; + leaf_last = leaf; + } + + if (have_last) + { + grub_memcpy (key_out, &leaf_last.key, sizeof (*key_out)); + *outsize = grub_le_to_cpu32 (leaf_last.size); + *outaddr = addr + grub_le_to_cpu32 (leaf_last.offset); + if (desc) + return save_ref (desc, addr - sizeof (head), i - 1, + grub_le_to_cpu32 (head.nitems), 1); + return GRUB_ERR_NONE; + } + *outsize = 0; + *outaddr = 0; + grub_memset (key_out, 0, sizeof (*key_out)); + if (desc) + return save_ref (desc, addr - sizeof (head), -1, + grub_le_to_cpu32 (head.nitems), 1); + return GRUB_ERR_NONE; + } + } +} + +/* Context for find_device. */ +struct find_device_ctx +{ + struct grub_btrfs_data *data; + grub_uint64_t id; + grub_device_t dev_found; +}; + +/* Helper for find_device. */ +static int +find_device_iter (const char *name, void *data) +{ + struct find_device_ctx *ctx = data; + grub_device_t dev; + grub_err_t err; + struct grub_btrfs_superblock sb; + + dev = grub_device_open (name); + if (!dev) + return 0; + if (!dev->disk) + { + grub_device_close (dev); + return 0; + } + err = read_sblock (dev->disk, &sb); + if (err == GRUB_ERR_BAD_FS) + { + grub_device_close (dev); + grub_errno = GRUB_ERR_NONE; + return 0; + } + if (err) + { + grub_device_close (dev); + grub_print_error (); + return 0; + } + if (grub_memcmp (ctx->data->sblock.uuid, sb.uuid, sizeof (sb.uuid)) != 0 + || sb.this_device.device_id != ctx->id) + { + grub_device_close (dev); + return 0; + } + + ctx->dev_found = dev; + return 1; +} + +static grub_device_t +find_device (struct grub_btrfs_data *data, grub_uint64_t id) +{ + struct find_device_ctx ctx = { + .data = data, + .id = id, + .dev_found = NULL + }; + unsigned i; + + for (i = 0; i < data->n_devices_attached; i++) + if (id == data->devices_attached[i].id) + return data->devices_attached[i].dev; + + grub_device_iterate (find_device_iter, &ctx); + + data->n_devices_attached++; + if (data->n_devices_attached > data->n_devices_allocated) + { + void *tmp; + grub_size_t sz; + + if (grub_mul (data->n_devices_attached, 2, &data->n_devices_allocated) || + grub_add (data->n_devices_allocated, 1, &data->n_devices_allocated) || + grub_mul (data->n_devices_allocated, sizeof (data->devices_attached[0]), &sz)) + goto fail; + + data->devices_attached = grub_realloc (tmp = data->devices_attached, sz); + if (!data->devices_attached) + { + data->devices_attached = tmp; + + fail: + if (ctx.dev_found) + grub_device_close (ctx.dev_found); + return NULL; + } + } + data->devices_attached[data->n_devices_attached - 1].id = id; + data->devices_attached[data->n_devices_attached - 1].dev = ctx.dev_found; + return ctx.dev_found; +} + +static grub_err_t +btrfs_read_from_chunk (struct grub_btrfs_data *data, + struct grub_btrfs_chunk_item *chunk, + grub_uint64_t stripen, grub_uint64_t stripe_offset, + int redundancy, grub_uint64_t csize, + void *buf) +{ + struct grub_btrfs_chunk_stripe *stripe; + grub_disk_addr_t paddr; + grub_device_t dev; + grub_err_t err; + + stripe = (struct grub_btrfs_chunk_stripe *) (chunk + 1); + /* Right now the redundancy handling is easy. + With RAID5-like it will be more difficult. */ + stripe += stripen + redundancy; + + paddr = grub_le_to_cpu64 (stripe->offset) + stripe_offset; + + grub_dprintf ("btrfs", "stripe %" PRIxGRUB_UINT64_T + " maps to 0x%" PRIxGRUB_UINT64_T "\n" + "reading paddr 0x%" PRIxGRUB_UINT64_T "\n", + stripen, stripe->offset, paddr); + + dev = find_device (data, stripe->device_id); + if (!dev) + { + grub_dprintf ("btrfs", + "couldn't find a necessary member device " + "of multi-device filesystem\n"); + grub_errno = GRUB_ERR_NONE; + return GRUB_ERR_READ_ERROR; + } + + err = grub_disk_read (dev->disk, paddr >> GRUB_DISK_SECTOR_BITS, + paddr & (GRUB_DISK_SECTOR_SIZE - 1), + csize, buf); + return err; +} + +struct raid56_buffer { + void *buf; + int data_is_valid; +}; + +static void +rebuild_raid5 (char *dest, struct raid56_buffer *buffers, + grub_uint64_t nstripes, grub_uint64_t csize) +{ + grub_uint64_t i; + int first; + + for(i = 0; buffers[i].data_is_valid && i < nstripes; i++); + + if (i == nstripes) + { + grub_dprintf ("btrfs", "called rebuild_raid5(), but all disks are OK\n"); + return; + } + + grub_dprintf ("btrfs", "rebuilding RAID 5 stripe #%" PRIuGRUB_UINT64_T "\n", i); + + for (i = 0, first = 1; i < nstripes; i++) + { + if (!buffers[i].data_is_valid) + continue; + + if (first) { + grub_memcpy(dest, buffers[i].buf, csize); + first = 0; + } else + grub_crypto_xor (dest, dest, buffers[i].buf, csize); + } +} + +static grub_err_t +raid6_recover_read_buffer (void *data, int disk_nr, + grub_uint64_t addr __attribute__ ((unused)), + void *dest, grub_size_t size) +{ + struct raid56_buffer *buffers = data; + + if (!buffers[disk_nr].data_is_valid) + return grub_errno = GRUB_ERR_READ_ERROR; + + grub_memcpy(dest, buffers[disk_nr].buf, size); + + return grub_errno = GRUB_ERR_NONE; +} + +static void +rebuild_raid6 (struct raid56_buffer *buffers, grub_uint64_t nstripes, + grub_uint64_t csize, grub_uint64_t parities_pos, void *dest, + grub_uint64_t stripen) + +{ + grub_raid6_recover_gen (buffers, nstripes, stripen, parities_pos, + dest, 0, csize, 0, raid6_recover_read_buffer); +} + +static grub_err_t +raid56_read_retry (struct grub_btrfs_data *data, + struct grub_btrfs_chunk_item *chunk, + grub_uint64_t stripe_offset, grub_uint64_t stripen, + grub_uint64_t csize, void *buf, grub_uint64_t parities_pos) +{ + struct raid56_buffer *buffers; + grub_uint64_t nstripes = grub_le_to_cpu16 (chunk->nstripes); + grub_uint64_t chunk_type = grub_le_to_cpu64 (chunk->type); + grub_err_t ret = GRUB_ERR_OUT_OF_MEMORY; + grub_uint64_t i, failed_devices; + + buffers = grub_calloc (nstripes, sizeof (*buffers)); + if (!buffers) + goto cleanup; + + for (i = 0; i < nstripes; i++) + { + buffers[i].buf = grub_zalloc (csize); + if (!buffers[i].buf) + goto cleanup; + } + + for (failed_devices = 0, i = 0; i < nstripes; i++) + { + struct grub_btrfs_chunk_stripe *stripe; + grub_disk_addr_t paddr; + grub_device_t dev; + grub_err_t err; + + /* + * The struct grub_btrfs_chunk_stripe array lives + * behind struct grub_btrfs_chunk_item. + */ + stripe = (struct grub_btrfs_chunk_stripe *) (chunk + 1) + i; + + paddr = grub_le_to_cpu64 (stripe->offset) + stripe_offset; + grub_dprintf ("btrfs", "reading paddr %" PRIxGRUB_UINT64_T + " from stripe ID %" PRIxGRUB_UINT64_T "\n", + paddr, stripe->device_id); + + dev = find_device (data, stripe->device_id); + if (!dev) + { + grub_dprintf ("btrfs", "stripe %" PRIuGRUB_UINT64_T " FAILED (dev ID %" + PRIxGRUB_UINT64_T ")\n", i, stripe->device_id); + failed_devices++; + continue; + } + + err = grub_disk_read (dev->disk, paddr >> GRUB_DISK_SECTOR_BITS, + paddr & (GRUB_DISK_SECTOR_SIZE - 1), + csize, buffers[i].buf); + if (err == GRUB_ERR_NONE) + { + buffers[i].data_is_valid = 1; + grub_dprintf ("btrfs", "stripe %" PRIuGRUB_UINT64_T " OK (dev ID %" + PRIxGRUB_UINT64_T ")\n", i, stripe->device_id); + } + else + { + grub_dprintf ("btrfs", "stripe %" PRIuGRUB_UINT64_T + " READ FAILED (dev ID %" PRIxGRUB_UINT64_T ")\n", + i, stripe->device_id); + failed_devices++; + } + } + + if (failed_devices > 1 && (chunk_type & GRUB_BTRFS_CHUNK_TYPE_RAID5)) + { + grub_dprintf ("btrfs", "not enough disks for RAID 5: total %" PRIuGRUB_UINT64_T + ", missing %" PRIuGRUB_UINT64_T "\n", + nstripes, failed_devices); + ret = GRUB_ERR_READ_ERROR; + goto cleanup; + } + else if (failed_devices > 2 && (chunk_type & GRUB_BTRFS_CHUNK_TYPE_RAID6)) + { + grub_dprintf ("btrfs", "not enough disks for RAID 6: total %" PRIuGRUB_UINT64_T + ", missing %" PRIuGRUB_UINT64_T "\n", + nstripes, failed_devices); + ret = GRUB_ERR_READ_ERROR; + goto cleanup; + } + else + grub_dprintf ("btrfs", "enough disks for RAID 5: total %" + PRIuGRUB_UINT64_T ", missing %" PRIuGRUB_UINT64_T "\n", + nstripes, failed_devices); + + /* We have enough disks. So, rebuild the data. */ + if (chunk_type & GRUB_BTRFS_CHUNK_TYPE_RAID5) + rebuild_raid5 (buf, buffers, nstripes, csize); + else + rebuild_raid6 (buffers, nstripes, csize, parities_pos, buf, stripen); + + ret = GRUB_ERR_NONE; + cleanup: + if (buffers) + for (i = 0; i < nstripes; i++) + grub_free (buffers[i].buf); + grub_free (buffers); + + return ret; +} + +static grub_err_t +grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, + void *buf, grub_size_t size, int recursion_depth) +{ + while (size > 0) + { + grub_uint8_t *ptr; + struct grub_btrfs_key *key; + struct grub_btrfs_chunk_item *chunk; + grub_uint64_t csize; + grub_err_t err = 0; + struct grub_btrfs_key key_out; + int challoc = 0; + struct grub_btrfs_key key_in; + grub_size_t chsize; + grub_disk_addr_t chaddr; + + grub_dprintf ("btrfs", "searching for laddr %" PRIxGRUB_UINT64_T "\n", + addr); + for (ptr = data->sblock.bootstrap_mapping; + ptr < data->sblock.bootstrap_mapping + + sizeof (data->sblock.bootstrap_mapping) + - sizeof (struct grub_btrfs_key);) + { + key = (struct grub_btrfs_key *) ptr; + if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK) + break; + chunk = (struct grub_btrfs_chunk_item *) (key + 1); + grub_dprintf ("btrfs", + "%" PRIxGRUB_UINT64_T " %" PRIxGRUB_UINT64_T " \n", + grub_le_to_cpu64 (key->offset), + grub_le_to_cpu64 (chunk->size)); + if (grub_le_to_cpu64 (key->offset) <= addr + && addr < grub_le_to_cpu64 (key->offset) + + grub_le_to_cpu64 (chunk->size)) + goto chunk_found; + ptr += sizeof (*key) + sizeof (*chunk) + + sizeof (struct grub_btrfs_chunk_stripe) + * grub_le_to_cpu16 (chunk->nstripes); + } + + key_in.object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK); + key_in.type = GRUB_BTRFS_ITEM_TYPE_CHUNK; + key_in.offset = grub_cpu_to_le64 (addr); + err = lower_bound (data, &key_in, &key_out, + data->sblock.chunk_tree, + &chaddr, &chsize, NULL, recursion_depth); + if (err) + return err; + key = &key_out; + if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK + || !(grub_le_to_cpu64 (key->offset) <= addr)) + return grub_error (GRUB_ERR_BAD_FS, + "couldn't find the chunk descriptor"); + + chunk = grub_malloc (chsize); + if (!chunk) + return grub_errno; + + challoc = 1; + err = grub_btrfs_read_logical (data, chaddr, chunk, chsize, + recursion_depth); + if (err) + { + grub_free (chunk); + return err; + } + + chunk_found: + { + grub_uint64_t stripen; + grub_uint64_t stripe_offset; + grub_uint64_t off = addr - grub_le_to_cpu64 (key->offset); + grub_uint64_t chunk_stripe_length; + grub_uint16_t nstripes; + unsigned redundancy = 1; + unsigned i, j; + int is_raid56; + grub_uint64_t parities_pos = 0; + + is_raid56 = !!(grub_le_to_cpu64 (chunk->type) & + (GRUB_BTRFS_CHUNK_TYPE_RAID5 | + GRUB_BTRFS_CHUNK_TYPE_RAID6)); + + if (grub_le_to_cpu64 (chunk->size) <= off) + { + grub_dprintf ("btrfs", "no chunk\n"); + return grub_error (GRUB_ERR_BAD_FS, + "couldn't find the chunk descriptor"); + } + + nstripes = grub_le_to_cpu16 (chunk->nstripes) ? : 1; + chunk_stripe_length = grub_le_to_cpu64 (chunk->stripe_length) ? : 512; + grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T + "+0x%" PRIxGRUB_UINT64_T + " (%d stripes (%d substripes) of %" + PRIxGRUB_UINT64_T ")\n", + grub_le_to_cpu64 (key->offset), + grub_le_to_cpu64 (chunk->size), + nstripes, + grub_le_to_cpu16 (chunk->nsubstripes), + chunk_stripe_length); + + switch (grub_le_to_cpu64 (chunk->type) + & ~GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE) + { + case GRUB_BTRFS_CHUNK_TYPE_SINGLE: + { + grub_uint64_t stripe_length; + grub_dprintf ("btrfs", "single\n"); + stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size), + nstripes, + NULL); + if (stripe_length == 0) + stripe_length = 512; + stripen = grub_divmod64 (off, stripe_length, &stripe_offset); + csize = (stripen + 1) * stripe_length - off; + break; + } + case GRUB_BTRFS_CHUNK_TYPE_RAID1C4: + redundancy++; + /* fall through */ + case GRUB_BTRFS_CHUNK_TYPE_RAID1C3: + redundancy++; + /* fall through */ + case GRUB_BTRFS_CHUNK_TYPE_DUPLICATED: + case GRUB_BTRFS_CHUNK_TYPE_RAID1: + { + grub_dprintf ("btrfs", "RAID1 (copies: %d)\n", ++redundancy); + stripen = 0; + stripe_offset = off; + csize = grub_le_to_cpu64 (chunk->size) - off; + break; + } + case GRUB_BTRFS_CHUNK_TYPE_RAID0: + { + grub_uint64_t middle, high; + grub_uint64_t low; + grub_dprintf ("btrfs", "RAID0\n"); + middle = grub_divmod64 (off, + chunk_stripe_length, + &low); + + high = grub_divmod64 (middle, nstripes, + &stripen); + stripe_offset = + low + chunk_stripe_length * high; + csize = chunk_stripe_length - low; + break; + } + case GRUB_BTRFS_CHUNK_TYPE_RAID10: + { + grub_uint64_t middle, high; + grub_uint64_t low; + grub_uint16_t nsubstripes; + nsubstripes = grub_le_to_cpu16 (chunk->nsubstripes) ? : 1; + middle = grub_divmod64 (off, + chunk_stripe_length, + &low); + + high = grub_divmod64 (middle, + nstripes / nsubstripes ? : 1, + &stripen); + stripen *= nsubstripes; + redundancy = nsubstripes; + stripe_offset = low + chunk_stripe_length + * high; + csize = chunk_stripe_length - low; + break; + } + case GRUB_BTRFS_CHUNK_TYPE_RAID5: + case GRUB_BTRFS_CHUNK_TYPE_RAID6: + { + grub_uint64_t nparities, stripe_nr, high, low; + + redundancy = 1; /* no redundancy for now */ + + if (grub_le_to_cpu64 (chunk->type) & GRUB_BTRFS_CHUNK_TYPE_RAID5) + { + grub_dprintf ("btrfs", "RAID5\n"); + nparities = 1; + } + else + { + grub_dprintf ("btrfs", "RAID6\n"); + nparities = 2; + } + + /* + * RAID 6 layout consists of several stripes spread over + * the disks, e.g.: + * + * Disk_0 Disk_1 Disk_2 Disk_3 + * A0 B0 P0 Q0 + * Q1 A1 B1 P1 + * P2 Q2 A2 B2 + * + * Note: placement of the parities depend on row number. + * + * Pay attention that the btrfs terminology may differ from + * terminology used in other RAID implementations, e.g. LVM, + * dm or md. The main difference is that btrfs calls contiguous + * block of data on a given disk, e.g. A0, stripe instead of chunk. + * + * The variables listed below have following meaning: + * - stripe_nr is the stripe number excluding the parities + * (A0 = 0, B0 = 1, A1 = 2, B1 = 3, etc.), + * - high is the row number (0 for A0...Q0, 1 for Q1...P1, etc.), + * - stripen is the disk number in a row (0 for A0, Q1, P2, + * 1 for B0, A1, Q2, etc.), + * - off is the logical address to read, + * - chunk_stripe_length is the size of a stripe (typically 64 KiB), + * - nstripes is the number of disks in a row, + * - low is the offset of the data inside a stripe, + * - stripe_offset is the data offset in an array, + * - csize is the "potential" data to read; it will be reduced + * to size if the latter is smaller, + * - nparities is the number of parities (1 for RAID 5, 2 for + * RAID 6); used only in RAID 5/6 code. + */ + stripe_nr = grub_divmod64 (off, chunk_stripe_length, &low); + + /* + * stripen is computed without the parities + * (0 for A0, A1, A2, 1 for B0, B1, B2, etc.). + */ + if (nparities >= nstripes) + return grub_error (GRUB_ERR_BAD_FS, + "invalid RAID5/6: nparities >= nstripes"); + high = grub_divmod64 (stripe_nr, nstripes - nparities, &stripen); + + /* + * The stripes are spread over the disks. Every each row their + * positions are shifted by 1 place. So, the real disks number + * change. Hence, we have to take into account current row number + * modulo nstripes (0 for A0, 1 for A1, 2 for A2, etc.). + */ + grub_divmod64 (high + stripen, nstripes, &stripen); + + /* + * parities_pos is equal to ((high - nparities) % nstripes) + * (see the diagram above). However, (high - nparities) can + * be negative, e.g. when high == 0, leading to an incorrect + * results. (high + nstripes - nparities) is always positive and + * modulo nstripes is equal to ((high - nparities) % nstripes). + */ + grub_divmod64 (high + nstripes - nparities, nstripes, &parities_pos); + + stripe_offset = chunk_stripe_length * high + low; + csize = chunk_stripe_length - low; + + break; + } + default: + grub_dprintf ("btrfs", "unsupported RAID\n"); + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "unsupported RAID flags %" PRIxGRUB_UINT64_T, + grub_le_to_cpu64 (chunk->type)); + } + if (csize == 0) + return grub_error (GRUB_ERR_BUG, + "couldn't find the chunk descriptor"); + if (csize > (grub_uint64_t) size) + csize = size; + + for (j = 0; j < 2; j++) + { + grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T + "+0x%" PRIxGRUB_UINT64_T + " (%d stripes (%d substripes) of %" + PRIxGRUB_UINT64_T ")\n", + grub_le_to_cpu64 (key->offset), + grub_le_to_cpu64 (chunk->size), + grub_le_to_cpu16 (chunk->nstripes), + grub_le_to_cpu16 (chunk->nsubstripes), + grub_le_to_cpu64 (chunk->stripe_length)); + grub_dprintf ("btrfs", "reading laddr 0x%" PRIxGRUB_UINT64_T "\n", + addr); + + if (is_raid56) + { + err = btrfs_read_from_chunk (data, chunk, stripen, + stripe_offset, + 0, /* no mirror */ + csize, buf); + grub_errno = GRUB_ERR_NONE; + if (err) + err = raid56_read_retry (data, chunk, stripe_offset, + stripen, csize, buf, parities_pos); + } + else + for (i = 0; i < redundancy; i++) + { + err = btrfs_read_from_chunk (data, chunk, stripen, + stripe_offset, + i, /* redundancy */ + csize, buf); + if (!err) + break; + grub_errno = GRUB_ERR_NONE; + } + if (!err) + break; + } + if (err) + return grub_errno = err; + } + size -= csize; + buf = (grub_uint8_t *) buf + csize; + addr += csize; + if (challoc) + grub_free (chunk); + } + return GRUB_ERR_NONE; +} + +static struct grub_btrfs_data * +grub_btrfs_mount (grub_device_t dev) +{ + struct grub_btrfs_data *data; + grub_err_t err; + + if (!dev->disk) + { + grub_error (GRUB_ERR_BAD_FS, "not BtrFS"); + return NULL; + } + + data = grub_zalloc (sizeof (*data)); + if (!data) + return NULL; + + err = read_sblock (dev->disk, &data->sblock); + if (err) + { + grub_free (data); + return NULL; + } + + data->n_devices_allocated = 16; + data->devices_attached = grub_malloc (sizeof (data->devices_attached[0]) + * data->n_devices_allocated); + if (!data->devices_attached) + { + grub_free (data); + return NULL; + } + data->n_devices_attached = 1; + data->devices_attached[0].dev = dev; + data->devices_attached[0].id = data->sblock.this_device.device_id; + + return data; +} + +static void +grub_btrfs_unmount (struct grub_btrfs_data *data) +{ + unsigned i; + /* The device 0 is closed one layer upper. */ + for (i = 1; i < data->n_devices_attached; i++) + if (data->devices_attached[i].dev) + grub_device_close (data->devices_attached[i].dev); + grub_free (data->devices_attached); + grub_free (data->extent); + grub_free (data); +} + +static grub_err_t +grub_btrfs_read_inode (struct grub_btrfs_data *data, + struct grub_btrfs_inode *inode, grub_uint64_t num, + grub_uint64_t tree) +{ + struct grub_btrfs_key key_in, key_out; + grub_disk_addr_t elemaddr; + grub_size_t elemsize; + grub_err_t err; + + key_in.object_id = num; + key_in.type = GRUB_BTRFS_ITEM_TYPE_INODE_ITEM; + key_in.offset = 0; + + err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, NULL, + 0); + if (err) + return err; + if (num != key_out.object_id + || key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_ITEM) + return grub_error (GRUB_ERR_BAD_FS, "inode not found"); + + return grub_btrfs_read_logical (data, elemaddr, inode, sizeof (*inode), 0); +} + +static void *grub_zstd_malloc (void *state __attribute__((unused)), size_t size) +{ + return grub_malloc (size); +} + +static void grub_zstd_free (void *state __attribute__((unused)), void *address) +{ + return grub_free (address); +} + +static ZSTD_customMem grub_zstd_allocator (void) +{ + ZSTD_customMem allocator; + + allocator.customAlloc = &grub_zstd_malloc; + allocator.customFree = &grub_zstd_free; + allocator.opaque = NULL; + + return allocator; +} + +static grub_ssize_t +grub_btrfs_zstd_decompress (char *ibuf, grub_size_t isize, grub_off_t off, + char *obuf, grub_size_t osize) +{ + void *allocated = NULL; + char *otmpbuf = obuf; + grub_size_t otmpsize = osize; + ZSTD_DCtx *dctx = NULL; + grub_size_t zstd_ret; + grub_ssize_t ret = -1; + + /* + * Zstd will fail if it can't fit the entire output in the destination + * buffer, so if osize isn't large enough, allocate a temporary buffer. + */ + if (otmpsize < ZSTD_BTRFS_MAX_INPUT) + { + allocated = grub_malloc (ZSTD_BTRFS_MAX_INPUT); + if (!allocated) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "failed allocate a zstd buffer"); + goto err; + } + otmpbuf = (char *) allocated; + otmpsize = ZSTD_BTRFS_MAX_INPUT; + } + + /* Create the ZSTD_DCtx. */ + dctx = ZSTD_createDCtx_advanced (grub_zstd_allocator ()); + if (!dctx) + { + /* ZSTD_createDCtx_advanced() only fails if it is out of memory. */ + grub_error (GRUB_ERR_OUT_OF_MEMORY, "failed to create a zstd context"); + goto err; + } + + /* + * Get the real input size, there may be junk at the + * end of the frame. + */ + isize = ZSTD_findFrameCompressedSize (ibuf, isize); + if (ZSTD_isError (isize)) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "zstd data corrupted"); + goto err; + } + + /* Decompress and check for errors. */ + zstd_ret = ZSTD_decompressDCtx (dctx, otmpbuf, otmpsize, ibuf, isize); + if (ZSTD_isError (zstd_ret)) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "zstd data corrupted"); + goto err; + } + + /* + * Move the requested data into the obuf. obuf may be equal + * to otmpbuf, which is why grub_memmove() is required. + */ + grub_memmove (obuf, otmpbuf + off, osize); + ret = osize; + +err: + grub_free (allocated); + ZSTD_freeDCtx (dctx); + + return ret; +} + +static grub_ssize_t +grub_btrfs_lzo_decompress(char *ibuf, grub_size_t isize, grub_off_t off, + char *obuf, grub_size_t osize) +{ + grub_uint32_t total_size, cblock_size; + grub_size_t ret = 0; + char *ibuf0 = ibuf; + + total_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf)); + ibuf += sizeof (total_size); + + if (isize < total_size) + return -1; + + /* Jump forward to first block with requested data. */ + while (off >= GRUB_BTRFS_LZO_BLOCK_SIZE) + { + /* Don't let following uint32_t cross the page boundary. */ + if (((ibuf - ibuf0) & 0xffc) == 0xffc) + ibuf = ((ibuf - ibuf0 + 3) & ~3) + ibuf0; + + cblock_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf)); + ibuf += sizeof (cblock_size); + + if (cblock_size > GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE) + return -1; + + off -= GRUB_BTRFS_LZO_BLOCK_SIZE; + ibuf += cblock_size; + } + + while (osize > 0) + { + lzo_uint usize = GRUB_BTRFS_LZO_BLOCK_SIZE; + + /* Don't let following uint32_t cross the page boundary. */ + if (((ibuf - ibuf0) & 0xffc) == 0xffc) + ibuf = ((ibuf - ibuf0 + 3) & ~3) + ibuf0; + + cblock_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf)); + ibuf += sizeof (cblock_size); + + if (cblock_size > GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE) + return -1; + + /* Block partially filled with requested data. */ + if (off > 0 || osize < GRUB_BTRFS_LZO_BLOCK_SIZE) + { + grub_size_t to_copy = GRUB_BTRFS_LZO_BLOCK_SIZE - off; + grub_uint8_t *buf; + + if (to_copy > osize) + to_copy = osize; + + buf = grub_malloc (GRUB_BTRFS_LZO_BLOCK_SIZE); + if (!buf) + return -1; + + if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, buf, &usize, + NULL) != LZO_E_OK) + { + grub_free (buf); + return -1; + } + + if (to_copy > usize) + to_copy = usize; + grub_memcpy(obuf, buf + off, to_copy); + + osize -= to_copy; + ret += to_copy; + obuf += to_copy; + ibuf += cblock_size; + off = 0; + + grub_free (buf); + continue; + } + + /* Decompress whole block directly to output buffer. */ + if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, (lzo_bytep)obuf, + &usize, NULL) != LZO_E_OK) + return -1; + + osize -= usize; + ret += usize; + obuf += usize; + ibuf += cblock_size; + } + + return ret; +} + +static grub_ssize_t +grub_btrfs_extent_read (struct grub_btrfs_data *data, + grub_uint64_t ino, grub_uint64_t tree, + grub_off_t pos0, char *buf, grub_size_t len) +{ + grub_off_t pos = pos0; + while (len) + { + grub_size_t csize; + grub_err_t err; + grub_off_t extoff; + if (!data->extent || data->extstart > pos || data->extino != ino + || data->exttree != tree || data->extend <= pos) + { + struct grub_btrfs_key key_in, key_out; + grub_disk_addr_t elemaddr; + grub_size_t elemsize; + + grub_free (data->extent); + key_in.object_id = ino; + key_in.type = GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM; + key_in.offset = grub_cpu_to_le64 (pos); + err = lower_bound (data, &key_in, &key_out, tree, + &elemaddr, &elemsize, NULL, 0); + if (err) + return -1; + if (key_out.object_id != ino + || key_out.type != GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM) + { + grub_error (GRUB_ERR_BAD_FS, "extent not found"); + return -1; + } + if ((grub_ssize_t) elemsize < ((char *) &data->extent->inl + - (char *) data->extent)) + { + grub_error (GRUB_ERR_BAD_FS, "extent descriptor is too short"); + return -1; + } + data->extstart = grub_le_to_cpu64 (key_out.offset); + data->extsize = elemsize; + data->extent = grub_malloc (elemsize); + data->extino = ino; + data->exttree = tree; + if (!data->extent) + return grub_errno; + + err = grub_btrfs_read_logical (data, elemaddr, data->extent, + elemsize, 0); + if (err) + return err; + + data->extend = data->extstart + grub_le_to_cpu64 (data->extent->size); + if (data->extent->type == GRUB_BTRFS_EXTENT_REGULAR + && (char *) data->extent + elemsize + >= (char *) &data->extent->filled + sizeof (data->extent->filled)) + data->extend = + data->extstart + grub_le_to_cpu64 (data->extent->filled); + + grub_dprintf ("btrfs", "regular extent 0x%" PRIxGRUB_UINT64_T "+0x%" + PRIxGRUB_UINT64_T "\n", + grub_le_to_cpu64 (key_out.offset), + grub_le_to_cpu64 (data->extent->size)); + if (data->extend <= pos) + { + grub_error (GRUB_ERR_BAD_FS, "extent not found"); + return -1; + } + } + csize = data->extend - pos; + extoff = pos - data->extstart; + if (csize > len) + csize = len; + + if (data->extent->encryption) + { + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "encryption not supported"); + return -1; + } + + if (data->extent->compression != GRUB_BTRFS_COMPRESSION_NONE + && data->extent->compression != GRUB_BTRFS_COMPRESSION_ZLIB + && data->extent->compression != GRUB_BTRFS_COMPRESSION_LZO + && data->extent->compression != GRUB_BTRFS_COMPRESSION_ZSTD) + { + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "compression type 0x%x not supported", + data->extent->compression); + return -1; + } + + if (data->extent->encoding) + { + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "encoding not supported"); + return -1; + } + + switch (data->extent->type) + { + case GRUB_BTRFS_EXTENT_INLINE: + if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB) + { + if (grub_zlib_decompress (data->extent->inl, data->extsize - + ((grub_uint8_t *) data->extent->inl + - (grub_uint8_t *) data->extent), + extoff, buf, csize) + != (grub_ssize_t) csize) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "premature end of compressed"); + return -1; + } + } + else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_LZO) + { + if (grub_btrfs_lzo_decompress(data->extent->inl, data->extsize - + ((grub_uint8_t *) data->extent->inl + - (grub_uint8_t *) data->extent), + extoff, buf, csize) + != (grub_ssize_t) csize) + return -1; + } + else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZSTD) + { + if (grub_btrfs_zstd_decompress (data->extent->inl, data->extsize - + ((grub_uint8_t *) data->extent->inl + - (grub_uint8_t *) data->extent), + extoff, buf, csize) + != (grub_ssize_t) csize) + return -1; + } + else + grub_memcpy (buf, data->extent->inl + extoff, csize); + break; + case GRUB_BTRFS_EXTENT_REGULAR: + if (!data->extent->laddr) + { + grub_memset (buf, 0, csize); + break; + } + + if (data->extent->compression != GRUB_BTRFS_COMPRESSION_NONE) + { + char *tmp; + grub_uint64_t zsize; + grub_ssize_t ret; + + zsize = grub_le_to_cpu64 (data->extent->compressed_size); + tmp = grub_malloc (zsize); + if (!tmp) + return -1; + err = grub_btrfs_read_logical (data, + grub_le_to_cpu64 (data->extent->laddr), + tmp, zsize, 0); + if (err) + { + grub_free (tmp); + return -1; + } + + if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB) + ret = grub_zlib_decompress (tmp, zsize, extoff + + grub_le_to_cpu64 (data->extent->offset), + buf, csize); + else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_LZO) + ret = grub_btrfs_lzo_decompress (tmp, zsize, extoff + + grub_le_to_cpu64 (data->extent->offset), + buf, csize); + else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZSTD) + ret = grub_btrfs_zstd_decompress (tmp, zsize, extoff + + grub_le_to_cpu64 (data->extent->offset), + buf, csize); + else + ret = -1; + + grub_free (tmp); + + if (ret != (grub_ssize_t) csize) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "premature end of compressed"); + return -1; + } + + break; + } + err = grub_btrfs_read_logical (data, + grub_le_to_cpu64 (data->extent->laddr) + + grub_le_to_cpu64 (data->extent->offset) + + extoff, buf, csize, 0); + if (err) + return -1; + break; + default: + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "unsupported extent type 0x%x", data->extent->type); + return -1; + } + buf += csize; + pos += csize; + len -= csize; + } + return pos - pos0; +} + +static grub_err_t +get_root (struct grub_btrfs_data *data, struct grub_btrfs_key *key, + grub_uint64_t *tree, grub_uint8_t *type) +{ + grub_err_t err; + grub_disk_addr_t elemaddr; + grub_size_t elemsize; + struct grub_btrfs_key key_out, key_in; + struct grub_btrfs_root_item ri; + + key_in.object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_ROOT_VOL_OBJECTID); + key_in.offset = 0; + key_in.type = GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM; + err = lower_bound (data, &key_in, &key_out, + data->sblock.root_tree, + &elemaddr, &elemsize, NULL, 0); + if (err) + return err; + if (key_in.object_id != key_out.object_id + || key_in.type != key_out.type + || key_in.offset != key_out.offset) + return grub_error (GRUB_ERR_BAD_FS, "no root"); + err = grub_btrfs_read_logical (data, elemaddr, &ri, + sizeof (ri), 0); + if (err) + return err; + key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; + key->offset = 0; + key->object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK); + *tree = ri.tree; + *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; + return GRUB_ERR_NONE; +} + +static grub_err_t +find_path (struct grub_btrfs_data *data, + const char *path, struct grub_btrfs_key *key, + grub_uint64_t *tree, grub_uint8_t *type) +{ + const char *slash = path; + grub_err_t err; + grub_disk_addr_t elemaddr; + grub_size_t elemsize; + grub_size_t allocated = 0; + struct grub_btrfs_dir_item *direl = NULL; + struct grub_btrfs_key key_out; + const char *ctoken; + grub_size_t ctokenlen; + char *path_alloc = NULL; + char *origpath = NULL; + unsigned symlinks_max = 32; + + err = get_root (data, key, tree, type); + if (err) + return err; + + origpath = grub_strdup (path); + if (!origpath) + return grub_errno; + + while (1) + { + while (path[0] == '/') + path++; + if (!path[0]) + break; + slash = grub_strchr (path, '/'); + if (!slash) + slash = path + grub_strlen (path); + ctoken = path; + ctokenlen = slash - path; + + if (*type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) + { + grub_free (path_alloc); + grub_free (origpath); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + } + + if (ctokenlen == 1 && ctoken[0] == '.') + { + path = slash; + continue; + } + if (ctokenlen == 2 && ctoken[0] == '.' && ctoken[1] == '.') + { + key->type = GRUB_BTRFS_ITEM_TYPE_INODE_REF; + key->offset = -1; + + err = lower_bound (data, key, &key_out, *tree, &elemaddr, &elemsize, + NULL, 0); + if (err) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return err; + } + + if (key_out.type != key->type + || key->object_id != key_out.object_id) + { + grub_free (direl); + grub_free (path_alloc); + err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); + grub_free (origpath); + return err; + } + + *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; + key->object_id = key_out.offset; + + path = slash; + + continue; + } + + key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; + key->offset = grub_cpu_to_le64 (~grub_getcrc32c (1, ctoken, ctokenlen)); + + err = lower_bound (data, key, &key_out, *tree, &elemaddr, &elemsize, + NULL, 0); + if (err) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return err; + } + if (key_cmp (key, &key_out) != 0) + { + grub_free (direl); + grub_free (path_alloc); + err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); + grub_free (origpath); + return err; + } + + struct grub_btrfs_dir_item *cdirel; + if (elemsize > allocated) + { + allocated = 2 * elemsize; + grub_free (direl); + direl = grub_malloc (allocated + 1); + if (!direl) + { + grub_free (path_alloc); + grub_free (origpath); + return grub_errno; + } + } + + err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize, 0); + if (err) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return err; + } + + for (cdirel = direl; + (grub_uint8_t *) cdirel - (grub_uint8_t *) direl + < (grub_ssize_t) elemsize; + cdirel = (void *) ((grub_uint8_t *) (direl + 1) + + grub_le_to_cpu16 (cdirel->n) + + grub_le_to_cpu16 (cdirel->m))) + { + if (ctokenlen == grub_le_to_cpu16 (cdirel->n) + && grub_memcmp (cdirel->name, ctoken, ctokenlen) == 0) + break; + } + if ((grub_uint8_t *) cdirel - (grub_uint8_t *) direl + >= (grub_ssize_t) elemsize) + { + grub_free (direl); + grub_free (path_alloc); + err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); + grub_free (origpath); + return err; + } + + path = slash; + if (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK) + { + struct grub_btrfs_inode inode; + char *tmp; + if (--symlinks_max == 0) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return grub_error (GRUB_ERR_SYMLINK_LOOP, + N_("too deep nesting of symlinks")); + } + + err = grub_btrfs_read_inode (data, &inode, + cdirel->key.object_id, *tree); + if (err) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return err; + } + tmp = grub_malloc (grub_le_to_cpu64 (inode.size) + + grub_strlen (path) + 1); + if (!tmp) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return grub_errno; + } + + if (grub_btrfs_extent_read (data, cdirel->key.object_id, + *tree, 0, tmp, + grub_le_to_cpu64 (inode.size)) + != (grub_ssize_t) grub_le_to_cpu64 (inode.size)) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + grub_free (tmp); + return grub_errno; + } + grub_memcpy (tmp + grub_le_to_cpu64 (inode.size), path, + grub_strlen (path) + 1); + grub_free (path_alloc); + path = path_alloc = tmp; + if (path[0] == '/') + { + err = get_root (data, key, tree, type); + if (err) + return err; + } + continue; + } + *type = cdirel->type; + + switch (cdirel->key.type) + { + case GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM: + { + struct grub_btrfs_root_item ri; + err = lower_bound (data, &cdirel->key, &key_out, + data->sblock.root_tree, + &elemaddr, &elemsize, NULL, 0); + if (err) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return err; + } + if (cdirel->key.object_id != key_out.object_id + || cdirel->key.type != key_out.type) + { + grub_free (direl); + grub_free (path_alloc); + err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); + grub_free (origpath); + return err; + } + err = grub_btrfs_read_logical (data, elemaddr, &ri, + sizeof (ri), 0); + if (err) + { + grub_free (direl); + grub_free (path_alloc); + grub_free (origpath); + return err; + } + key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; + key->offset = 0; + key->object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK); + *tree = ri.tree; + break; + } + case GRUB_BTRFS_ITEM_TYPE_INODE_ITEM: + if (*slash && *type == GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR) + { + grub_free (direl); + grub_free (path_alloc); + err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); + grub_free (origpath); + return err; + } + *key = cdirel->key; + if (*type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) + key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; + break; + default: + grub_free (path_alloc); + grub_free (origpath); + grub_free (direl); + return grub_error (GRUB_ERR_BAD_FS, "unrecognised object type 0x%x", + cdirel->key.type); + } + } + + grub_free (direl); + grub_free (origpath); + grub_free (path_alloc); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_btrfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_btrfs_data *data = grub_btrfs_mount (device); + struct grub_btrfs_key key_in, key_out; + grub_err_t err; + grub_disk_addr_t elemaddr; + grub_size_t elemsize; + grub_size_t allocated = 0; + struct grub_btrfs_dir_item *direl = NULL; + struct grub_btrfs_leaf_descriptor desc; + int r = 0; + grub_uint64_t tree; + grub_uint8_t type; + + if (!data) + return grub_errno; + + err = find_path (data, path, &key_in, &tree, &type); + if (err) + { + grub_btrfs_unmount (data); + return err; + } + if (type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) + { + grub_btrfs_unmount (data); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + } + + err = lower_bound (data, &key_in, &key_out, tree, + &elemaddr, &elemsize, &desc, 0); + if (err) + { + grub_btrfs_unmount (data); + return err; + } + if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM + || key_out.object_id != key_in.object_id) + { + r = next (data, &desc, &elemaddr, &elemsize, &key_out); + if (r <= 0) + goto out; + } + do + { + struct grub_btrfs_dir_item *cdirel; + if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM + || key_out.object_id != key_in.object_id) + { + r = 0; + break; + } + if (elemsize > allocated) + { + allocated = 2 * elemsize; + grub_free (direl); + direl = grub_malloc (allocated + 1); + if (!direl) + { + r = -grub_errno; + break; + } + } + + err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize, 0); + if (err) + { + r = -err; + break; + } + + for (cdirel = direl; + (grub_uint8_t *) cdirel - (grub_uint8_t *) direl + < (grub_ssize_t) elemsize; + cdirel = (void *) ((grub_uint8_t *) (direl + 1) + + grub_le_to_cpu16 (cdirel->n) + + grub_le_to_cpu16 (cdirel->m))) + { + char c; + struct grub_btrfs_inode inode; + struct grub_dirhook_info info; + err = grub_btrfs_read_inode (data, &inode, cdirel->key.object_id, + tree); + grub_memset (&info, 0, sizeof (info)); + if (err) + grub_errno = GRUB_ERR_NONE; + else + { + info.mtime = grub_le_to_cpu64 (inode.mtime.sec); + info.mtimeset = 1; + } + c = cdirel->name[grub_le_to_cpu16 (cdirel->n)]; + cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0; + info.dir = (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY); + if (hook (cdirel->name, &info, hook_data)) + goto out; + cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c; + } + r = next (data, &desc, &elemaddr, &elemsize, &key_out); + } + while (r > 0); + +out: + grub_free (direl); + + free_iterator (&desc); + grub_btrfs_unmount (data); + + return -r; +} + +static grub_err_t +grub_btrfs_open (struct grub_file *file, const char *name) +{ + struct grub_btrfs_data *data = grub_btrfs_mount (file->device); + grub_err_t err; + struct grub_btrfs_inode inode; + grub_uint8_t type; + struct grub_btrfs_key key_in; + + if (!data) + return grub_errno; + + err = find_path (data, name, &key_in, &data->tree, &type); + if (err) + { + grub_btrfs_unmount (data); + return err; + } + if (type != GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR) + { + grub_btrfs_unmount (data); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file")); + } + + data->inode = key_in.object_id; + err = grub_btrfs_read_inode (data, &inode, data->inode, data->tree); + if (err) + { + grub_btrfs_unmount (data); + return err; + } + + file->data = data; + file->size = grub_le_to_cpu64 (inode.size); + + return err; +} + +static grub_err_t +grub_btrfs_close (grub_file_t file) +{ + grub_btrfs_unmount (file->data); + + return GRUB_ERR_NONE; +} + +static grub_ssize_t +grub_btrfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_btrfs_data *data = file->data; + + return grub_btrfs_extent_read (data, data->inode, + data->tree, file->offset, buf, len); +} + +static grub_err_t +grub_btrfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_btrfs_data *data; + + *uuid = NULL; + + data = grub_btrfs_mount (device); + if (!data) + return grub_errno; + + *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + grub_be_to_cpu16 (data->sblock.uuid[0]), + grub_be_to_cpu16 (data->sblock.uuid[1]), + grub_be_to_cpu16 (data->sblock.uuid[2]), + grub_be_to_cpu16 (data->sblock.uuid[3]), + grub_be_to_cpu16 (data->sblock.uuid[4]), + grub_be_to_cpu16 (data->sblock.uuid[5]), + grub_be_to_cpu16 (data->sblock.uuid[6]), + grub_be_to_cpu16 (data->sblock.uuid[7])); + + grub_btrfs_unmount (data); + + return grub_errno; +} + +static grub_err_t +grub_btrfs_label (grub_device_t device, char **label) +{ + struct grub_btrfs_data *data; + + *label = NULL; + + data = grub_btrfs_mount (device); + if (!data) + return grub_errno; + + *label = grub_strndup (data->sblock.label, sizeof (data->sblock.label)); + + grub_btrfs_unmount (data); + + return grub_errno; +} + +#ifdef GRUB_UTIL +static grub_err_t +grub_btrfs_embed (grub_device_t device __attribute__ ((unused)), + unsigned int *nsectors, + unsigned int max_nsectors, + grub_embed_type_t embed_type, + grub_disk_addr_t **sectors) +{ + unsigned i; + + if (embed_type != GRUB_EMBED_PCBIOS) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "BtrFS currently supports only PC-BIOS embedding"); + + if (64 * 2 - 1 < *nsectors) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("your core.img is unusually large. " + "It won't fit in the embedding area")); + + *nsectors = 64 * 2 - 1; + if (*nsectors > max_nsectors) + *nsectors = max_nsectors; + *sectors = grub_calloc (*nsectors, sizeof (**sectors)); + if (!*sectors) + return grub_errno; + for (i = 0; i < *nsectors; i++) + (*sectors)[i] = i + 1; + + return GRUB_ERR_NONE; +} +#endif + +static struct grub_fs grub_btrfs_fs = { + .name = "btrfs", + .fs_dir = grub_btrfs_dir, + .fs_open = grub_btrfs_open, + .fs_read = grub_btrfs_read, + .fs_close = grub_btrfs_close, + .fs_uuid = grub_btrfs_uuid, + .fs_label = grub_btrfs_label, +#ifdef GRUB_UTIL + .fs_embed = grub_btrfs_embed, + .reserved_first_sector = 1, + .blocklist_install = 0, +#endif +}; + +GRUB_MOD_INIT (btrfs) +{ + grub_fs_register (&grub_btrfs_fs); +} + +GRUB_MOD_FINI (btrfs) +{ + grub_fs_unregister (&grub_btrfs_fs); +} diff --git a/grub-core/fs/cbfs.c b/grub-core/fs/cbfs.c new file mode 100644 index 0000000..581215e --- /dev/null +++ b/grub-core/fs/cbfs.c @@ -0,0 +1,402 @@ +/* cbfs.c - cbfs and tar filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> +#include <grub/disk.h> +#include <grub/archelp.h> + +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/dl.h> +#include <grub/i18n.h> +#include <grub/cbfs_core.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + + +struct grub_archelp_data +{ + grub_disk_t disk; + grub_off_t hofs, next_hofs; + grub_off_t dofs; + grub_off_t size; + grub_off_t cbfs_start; + grub_off_t cbfs_end; + grub_off_t cbfs_align; +}; + +static grub_err_t +grub_cbfs_find_file (struct grub_archelp_data *data, char **name, + grub_int32_t *mtime, + grub_uint32_t *mode) +{ + grub_size_t offset; + for (;; + data->dofs = data->hofs + offset, + data->next_hofs = ALIGN_UP (data->dofs + data->size, data->cbfs_align)) + { + struct cbfs_file hd; + grub_size_t namesize; + + data->hofs = data->next_hofs; + + if (data->hofs >= data->cbfs_end) + { + *mode = GRUB_ARCHELP_ATTR_END; + return GRUB_ERR_NONE; + } + + if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd)) + return grub_errno; + + if (grub_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (hd.magic)) != 0) + { + *mode = GRUB_ARCHELP_ATTR_END; + return GRUB_ERR_NONE; + } + data->size = grub_be_to_cpu32 (hd.len); + (void) mtime; + offset = grub_be_to_cpu32 (hd.offset); + + *mode = GRUB_ARCHELP_ATTR_FILE | GRUB_ARCHELP_ATTR_NOTIME; + + namesize = offset; + if (namesize >= sizeof (hd)) + namesize -= sizeof (hd); + if (namesize == 0) + continue; + *name = grub_malloc (namesize + 1); + if (*name == NULL) + return grub_errno; + + if (grub_disk_read (data->disk, 0, data->hofs + sizeof (hd), + namesize, *name)) + { + grub_free (*name); + return grub_errno; + } + + if ((*name)[0] == '\0') + { + grub_free (*name); + *name = NULL; + continue; + } + + (*name)[namesize] = 0; + + data->dofs = data->hofs + offset; + data->next_hofs = ALIGN_UP (data->dofs + data->size, data->cbfs_align); + return GRUB_ERR_NONE; + } +} + +static void +grub_cbfs_rewind (struct grub_archelp_data *data) +{ + data->next_hofs = data->cbfs_start; +} + +static struct grub_archelp_ops arcops = + { + .find_file = grub_cbfs_find_file, + .rewind = grub_cbfs_rewind + }; + +static int +validate_head (struct cbfs_header *head) +{ + return (head->magic == grub_cpu_to_be32_compile_time (CBFS_HEADER_MAGIC) + && (head->version + == grub_cpu_to_be32_compile_time (CBFS_HEADER_VERSION1) + || head->version + == grub_cpu_to_be32_compile_time (CBFS_HEADER_VERSION2)) + && (grub_be_to_cpu32 (head->bootblocksize) + < grub_be_to_cpu32 (head->romsize)) + && (grub_be_to_cpu32 (head->offset) + < grub_be_to_cpu32 (head->romsize)) + && (grub_be_to_cpu32 (head->offset) + + grub_be_to_cpu32 (head->bootblocksize) + < grub_be_to_cpu32 (head->romsize)) + && head->align != 0 + && (head->align & (head->align - 1)) == 0 + && head->romsize != 0); +} + +static struct grub_archelp_data * +grub_cbfs_mount (grub_disk_t disk) +{ + struct cbfs_file hd; + struct grub_archelp_data *data = NULL; + grub_uint32_t ptr; + grub_off_t header_off; + struct cbfs_header head; + + if (grub_disk_native_sectors (disk) == GRUB_DISK_SIZE_UNKNOWN) + goto fail; + + if (grub_disk_read (disk, grub_disk_native_sectors (disk) - 1, + GRUB_DISK_SECTOR_SIZE - sizeof (ptr), + sizeof (ptr), &ptr)) + goto fail; + + ptr = grub_cpu_to_le32 (ptr); + header_off = (grub_disk_native_sectors (disk) << GRUB_DISK_SECTOR_BITS) + + (grub_int32_t) ptr; + + if (grub_disk_read (disk, 0, header_off, + sizeof (head), &head)) + goto fail; + + if (!validate_head (&head)) + goto fail; + + data = (struct grub_archelp_data *) grub_zalloc (sizeof (*data)); + if (!data) + goto fail; + + data->cbfs_start = (grub_disk_native_sectors (disk) << GRUB_DISK_SECTOR_BITS) + - (grub_be_to_cpu32 (head.romsize) - grub_be_to_cpu32 (head.offset)); + data->cbfs_end = (grub_disk_native_sectors (disk) << GRUB_DISK_SECTOR_BITS) + - grub_be_to_cpu32 (head.bootblocksize); + data->cbfs_align = grub_be_to_cpu32 (head.align); + + if (data->cbfs_start >= (grub_disk_native_sectors (disk) << GRUB_DISK_SECTOR_BITS)) + goto fail; + if (data->cbfs_end > (grub_disk_native_sectors (disk) << GRUB_DISK_SECTOR_BITS)) + data->cbfs_end = (grub_disk_native_sectors (disk) << GRUB_DISK_SECTOR_BITS); + + data->next_hofs = data->cbfs_start; + + if (grub_disk_read (disk, 0, data->cbfs_start, sizeof (hd), &hd)) + goto fail; + + if (grub_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (CBFS_FILE_MAGIC) - 1)) + goto fail; + + data->disk = disk; + + return data; + +fail: + grub_free (data); + grub_error (GRUB_ERR_BAD_FS, "not a cbfs filesystem"); + return 0; +} + +static grub_err_t +grub_cbfs_dir (grub_device_t device, const char *path_in, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_archelp_data *data; + grub_err_t err; + + data = grub_cbfs_mount (device->disk); + if (!data) + return grub_errno; + + err = grub_archelp_dir (data, &arcops, + path_in, hook, hook_data); + + grub_free (data); + + return err; +} + +static grub_err_t +grub_cbfs_open (grub_file_t file, const char *name_in) +{ + struct grub_archelp_data *data; + grub_err_t err; + + data = grub_cbfs_mount (file->device->disk); + if (!data) + return grub_errno; + + err = grub_archelp_open (data, &arcops, name_in); + if (err) + { + grub_free (data); + } + else + { + file->data = data; + file->size = data->size; + } + return err; +} + +static grub_ssize_t +grub_cbfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_archelp_data *data; + grub_ssize_t ret; + + data = file->data; + data->disk->read_hook = file->read_hook; + data->disk->read_hook_data = file->read_hook_data; + + ret = (grub_disk_read (data->disk, 0, data->dofs + file->offset, + len, buf)) ? -1 : (grub_ssize_t) len; + data->disk->read_hook = 0; + + return ret; +} + +static grub_err_t +grub_cbfs_close (grub_file_t file) +{ + struct grub_archelp_data *data; + + data = file->data; + grub_free (data); + + return grub_errno; +} + +#if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL) \ + && !defined (GRUB_MACHINE_EMU) && !defined (GRUB_MACHINE_XEN) + +static char *cbfsdisk_addr; +static grub_off_t cbfsdisk_size = 0; + +static int +grub_cbfsdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, + grub_disk_pull_t pull) +{ + if (pull != GRUB_DISK_PULL_NONE) + return 0; + + return hook ("cbfsdisk", hook_data); +} + +static grub_err_t +grub_cbfsdisk_open (const char *name, grub_disk_t disk) +{ + if (grub_strcmp (name, "cbfsdisk")) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a cbfsdisk"); + + disk->total_sectors = cbfsdisk_size / GRUB_DISK_SECTOR_SIZE; + disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE; + disk->id = 0; + + return GRUB_ERR_NONE; +} + +static void +grub_cbfsdisk_close (grub_disk_t disk __attribute((unused))) +{ +} + +static grub_err_t +grub_cbfsdisk_read (grub_disk_t disk __attribute((unused)), + grub_disk_addr_t sector, + grub_size_t size, char *buf) +{ + grub_memcpy (buf, cbfsdisk_addr + (sector << GRUB_DISK_SECTOR_BITS), + size << GRUB_DISK_SECTOR_BITS); + return 0; +} + +static grub_err_t +grub_cbfsdisk_write (grub_disk_t disk __attribute__ ((unused)), + grub_disk_addr_t sector __attribute__ ((unused)), + grub_size_t size __attribute__ ((unused)), + const char *buf __attribute__ ((unused))) +{ + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "rom flashing isn't implemented yet"); +} + +static struct grub_disk_dev grub_cbfsdisk_dev = + { + .name = "cbfsdisk", + .id = GRUB_DISK_DEVICE_CBFSDISK_ID, + .disk_iterate = grub_cbfsdisk_iterate, + .disk_open = grub_cbfsdisk_open, + .disk_close = grub_cbfsdisk_close, + .disk_read = grub_cbfsdisk_read, + .disk_write = grub_cbfsdisk_write, + .next = 0 + }; + +static void +init_cbfsdisk (void) +{ + grub_uint32_t ptr; + struct cbfs_header *head; + + ptr = *(grub_uint32_t *) 0xfffffffc; + head = (struct cbfs_header *) (grub_addr_t) ptr; + grub_dprintf ("cbfs", "head=%p\n", head); + + /* coreboot current supports only ROMs <= 16 MiB. Bigger ROMs will + have problems as RCBA is 18 MiB below end of 32-bit typically, + so either memory map would have to be rearranged or we'd need to support + reading ROMs through controller directly. + */ + if (ptr < 0xff000000 + || 0xffffffff - ptr < (grub_uint32_t) sizeof (*head) + 0xf + || !validate_head (head)) + return; + + cbfsdisk_size = ALIGN_UP (grub_be_to_cpu32 (head->romsize), + GRUB_DISK_SECTOR_SIZE); + cbfsdisk_addr = (void *) (grub_addr_t) (0x100000000ULL - cbfsdisk_size); + + grub_disk_dev_register (&grub_cbfsdisk_dev); +} + +static void +fini_cbfsdisk (void) +{ + if (! cbfsdisk_size) + return; + grub_disk_dev_unregister (&grub_cbfsdisk_dev); +} + +#endif + +static struct grub_fs grub_cbfs_fs = { + .name = "cbfs", + .fs_dir = grub_cbfs_dir, + .fs_open = grub_cbfs_open, + .fs_read = grub_cbfs_read, + .fs_close = grub_cbfs_close, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, + .blocklist_install = 0, +#endif +}; + +GRUB_MOD_INIT (cbfs) +{ +#if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL) && !defined (GRUB_MACHINE_EMU) && !defined (GRUB_MACHINE_XEN) + init_cbfsdisk (); +#endif + grub_fs_register (&grub_cbfs_fs); +} + +GRUB_MOD_FINI (cbfs) +{ + grub_fs_unregister (&grub_cbfs_fs); +#if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL) && !defined (GRUB_MACHINE_EMU) && !defined (GRUB_MACHINE_XEN) + fini_cbfsdisk (); +#endif +} diff --git a/grub-core/fs/cpio.c b/grub-core/fs/cpio.c new file mode 100644 index 0000000..dab5f98 --- /dev/null +++ b/grub-core/fs/cpio.c @@ -0,0 +1,61 @@ +/* cpio.c - cpio and tar filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> + +/* cpio support */ +#define ALIGN_CPIO(x) (ALIGN_UP ((x), 2)) +#define MAGIC "\xc7\x71" +struct head +{ + grub_uint16_t magic[1]; + grub_uint16_t dev; + grub_uint16_t ino; + grub_uint16_t mode[1]; + grub_uint16_t uid; + grub_uint16_t gid; + grub_uint16_t nlink; + grub_uint16_t rdev; + grub_uint16_t mtime[2]; + grub_uint16_t namesize[1]; + grub_uint16_t filesize[2]; +} GRUB_PACKED; + +static inline unsigned long long +read_number (const grub_uint16_t *arr, grub_size_t size) +{ + long long ret = 0; + while (size--) + ret = (ret << 16) | grub_le_to_cpu16 (*arr++); + return ret; +} + +#define FSNAME "cpiofs" + +#include "cpio_common.c" + +GRUB_MOD_INIT (cpio) +{ + grub_fs_register (&grub_cpio_fs); +} + +GRUB_MOD_FINI (cpio) +{ + grub_fs_unregister (&grub_cpio_fs); +} diff --git a/grub-core/fs/cpio_be.c b/grub-core/fs/cpio_be.c new file mode 100644 index 0000000..8465488 --- /dev/null +++ b/grub-core/fs/cpio_be.c @@ -0,0 +1,61 @@ +/* cpio.c - cpio and tar filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> + +#define ALIGN_CPIO(x) (ALIGN_UP ((x), 2)) +#define MAGIC "\x71\xc7" + +struct head +{ + grub_uint16_t magic[1]; + grub_uint16_t dev; + grub_uint16_t ino; + grub_uint16_t mode[1]; + grub_uint16_t uid; + grub_uint16_t gid; + grub_uint16_t nlink; + grub_uint16_t rdev; + grub_uint16_t mtime[2]; + grub_uint16_t namesize[1]; + grub_uint16_t filesize[2]; +} GRUB_PACKED; + +static inline unsigned long long +read_number (const grub_uint16_t *arr, grub_size_t size) +{ + long long ret = 0; + while (size--) + ret = (ret << 16) | grub_be_to_cpu16 (*arr++); + return ret; +} + +#define FSNAME "cpiofs_be" + +#include "cpio_common.c" + +GRUB_MOD_INIT (cpio_be) +{ + grub_fs_register (&grub_cpio_fs); +} + +GRUB_MOD_FINI (cpio_be) +{ + grub_fs_unregister (&grub_cpio_fs); +} diff --git a/grub-core/fs/cpio_common.c b/grub-core/fs/cpio_common.c new file mode 100644 index 0000000..4e885d6 --- /dev/null +++ b/grub-core/fs/cpio_common.c @@ -0,0 +1,253 @@ +/* cpio.c - cpio and tar filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/disk.h> +#include <grub/dl.h> +#include <grub/i18n.h> +#include <grub/archelp.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +struct grub_archelp_data +{ + grub_disk_t disk; + grub_off_t hofs; + grub_off_t next_hofs; + grub_off_t dofs; + grub_off_t size; +}; + +#if __GNUC__ >= 9 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" +#endif + +static grub_err_t +grub_cpio_find_file (struct grub_archelp_data *data, char **name, + grub_int32_t *mtime, grub_uint32_t *mode) +{ + struct head hd; + grub_size_t namesize; + grub_uint32_t modeval; + + data->hofs = data->next_hofs; + + if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd)) + return grub_errno; + + if (grub_memcmp (hd.magic, MAGIC, sizeof (hd.magic)) != 0 +#ifdef MAGIC2 + && grub_memcmp (hd.magic, MAGIC2, sizeof (hd.magic)) != 0 +#endif + ) + return grub_error (GRUB_ERR_BAD_FS, "invalid cpio archive"); + data->size = read_number (hd.filesize, ARRAY_SIZE (hd.filesize)); + if (mtime) + *mtime = read_number (hd.mtime, ARRAY_SIZE (hd.mtime)); + modeval = read_number (hd.mode, ARRAY_SIZE (hd.mode)); + namesize = read_number (hd.namesize, ARRAY_SIZE (hd.namesize)); + + /* Don't allow negative numbers. */ + if (namesize >= 0x80000000) + { + /* Probably a corruption, don't attempt to recover. */ + *mode = GRUB_ARCHELP_ATTR_END; + return GRUB_ERR_NONE; + } + + *mode = modeval; + + *name = grub_malloc (namesize + 1); + if (*name == NULL) + return grub_errno; + + if (grub_disk_read (data->disk, 0, data->hofs + sizeof (hd), + namesize, *name)) + { + grub_free (*name); + return grub_errno; + } + (*name)[namesize] = 0; + + if (data->size == 0 && modeval == 0 && namesize == 11 + && grub_memcmp(*name, "TRAILER!!!", 11) == 0) + { + *mode = GRUB_ARCHELP_ATTR_END; + grub_free (*name); + return GRUB_ERR_NONE; + } + + data->dofs = data->hofs + ALIGN_CPIO (sizeof (hd) + namesize); + data->next_hofs = data->dofs + ALIGN_CPIO (data->size); + return GRUB_ERR_NONE; +} + +#if __GNUC__ >= 9 +#pragma GCC diagnostic pop +#endif + +static char * +grub_cpio_get_link_target (struct grub_archelp_data *data) +{ + char *ret; + grub_err_t err; + + if (data->size == 0) + return grub_strdup (""); + ret = grub_malloc (data->size + 1); + if (!ret) + return NULL; + + err = grub_disk_read (data->disk, 0, data->dofs, data->size, + ret); + if (err) + { + grub_free (ret); + return NULL; + } + ret[data->size] = '\0'; + return ret; +} + +static void +grub_cpio_rewind (struct grub_archelp_data *data) +{ + data->next_hofs = 0; +} + +static struct grub_archelp_ops arcops = + { + .find_file = grub_cpio_find_file, + .get_link_target = grub_cpio_get_link_target, + .rewind = grub_cpio_rewind + }; + +static struct grub_archelp_data * +grub_cpio_mount (grub_disk_t disk) +{ + struct head hd; + struct grub_archelp_data *data; + + if (grub_disk_read (disk, 0, 0, sizeof (hd), &hd)) + goto fail; + + if (grub_memcmp (hd.magic, MAGIC, sizeof (MAGIC) - 1) +#ifdef MAGIC2 + && grub_memcmp (hd.magic, MAGIC2, sizeof (MAGIC2) - 1) +#endif + ) + goto fail; + + data = (struct grub_archelp_data *) grub_zalloc (sizeof (*data)); + if (!data) + goto fail; + + data->disk = disk; + + return data; + +fail: + grub_error (GRUB_ERR_BAD_FS, "not a " FSNAME " filesystem"); + return 0; +} + +static grub_err_t +grub_cpio_dir (grub_device_t device, const char *path_in, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_archelp_data *data; + grub_err_t err; + + data = grub_cpio_mount (device->disk); + if (!data) + return grub_errno; + + err = grub_archelp_dir (data, &arcops, + path_in, hook, hook_data); + + grub_free (data); + + return err; +} + +static grub_err_t +grub_cpio_open (grub_file_t file, const char *name_in) +{ + struct grub_archelp_data *data; + grub_err_t err; + + data = grub_cpio_mount (file->device->disk); + if (!data) + return grub_errno; + + err = grub_archelp_open (data, &arcops, name_in); + if (err) + { + grub_free (data); + } + else + { + file->data = data; + file->size = data->size; + } + return err; +} + +static grub_ssize_t +grub_cpio_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_archelp_data *data; + grub_ssize_t ret; + + data = file->data; + data->disk->read_hook = file->read_hook; + data->disk->read_hook_data = file->read_hook_data; + + ret = (grub_disk_read (data->disk, 0, data->dofs + file->offset, + len, buf)) ? -1 : (grub_ssize_t) len; + data->disk->read_hook = 0; + + return ret; +} + +static grub_err_t +grub_cpio_close (grub_file_t file) +{ + struct grub_archelp_data *data; + + data = file->data; + grub_free (data); + + return grub_errno; +} + +static struct grub_fs grub_cpio_fs = { + .name = FSNAME, + .fs_dir = grub_cpio_dir, + .fs_open = grub_cpio_open, + .fs_read = grub_cpio_read, + .fs_close = grub_cpio_close, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, + .blocklist_install = 0, +#endif +}; diff --git a/grub-core/fs/exfat.c b/grub-core/fs/exfat.c new file mode 100644 index 0000000..fe149dd --- /dev/null +++ b/grub-core/fs/exfat.c @@ -0,0 +1,2 @@ +#define MODE_EXFAT 1 +#include "fat.c" diff --git a/grub-core/fs/ext2.c b/grub-core/fs/ext2.c new file mode 100644 index 0000000..e7dd78e --- /dev/null +++ b/grub-core/fs/ext2.c @@ -0,0 +1,1107 @@ +/* ext2.c - Second Extended filesystem */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,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/>. + */ + +/* Magic value used to identify an ext2 filesystem. */ +#define EXT2_MAGIC 0xEF53 +/* Amount of indirect blocks in an inode. */ +#define INDIRECT_BLOCKS 12 + +/* The good old revision and the default inode size. */ +#define EXT2_GOOD_OLD_REVISION 0 +#define EXT2_GOOD_OLD_INODE_SIZE 128 + +/* Filetype used in directory entry. */ +#define FILETYPE_UNKNOWN 0 +#define FILETYPE_REG 1 +#define FILETYPE_DIRECTORY 2 +#define FILETYPE_SYMLINK 7 + +/* Filetype information as used in inodes. */ +#define FILETYPE_INO_MASK 0170000 +#define FILETYPE_INO_REG 0100000 +#define FILETYPE_INO_DIRECTORY 0040000 +#define FILETYPE_INO_SYMLINK 0120000 + +#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/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* Log2 size of ext2 block in 512 blocks. */ +#define LOG2_EXT2_BLOCK_SIZE(data) \ + (grub_le_to_cpu32 (data->sblock.log2_block_size) + 1) + +/* Log2 size of ext2 block in bytes. */ +#define LOG2_BLOCK_SIZE(data) \ + (grub_le_to_cpu32 (data->sblock.log2_block_size) + 10) + +/* The size of an ext2 block in bytes. */ +#define EXT2_BLOCK_SIZE(data) (1U << LOG2_BLOCK_SIZE (data)) + +/* The revision level. */ +#define EXT2_REVISION(data) grub_le_to_cpu32 (data->sblock.revision_level) + +/* The inode size. */ +#define EXT2_INODE_SIZE(data) \ + (data->sblock.revision_level \ + == grub_cpu_to_le32_compile_time (EXT2_GOOD_OLD_REVISION) \ + ? EXT2_GOOD_OLD_INODE_SIZE \ + : grub_le_to_cpu16 (data->sblock.inode_size)) + +/* Superblock filesystem feature flags (RW compatible) + * A filesystem with any of these enabled can be read and written by a driver + * that does not understand them without causing metadata/data corruption. */ +#define EXT2_FEATURE_COMPAT_DIR_PREALLOC 0x0001 +#define EXT2_FEATURE_COMPAT_IMAGIC_INODES 0x0002 +#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x0004 +#define EXT2_FEATURE_COMPAT_EXT_ATTR 0x0008 +#define EXT2_FEATURE_COMPAT_RESIZE_INODE 0x0010 +#define EXT2_FEATURE_COMPAT_DIR_INDEX 0x0020 +/* Superblock filesystem feature flags (RO compatible) + * A filesystem with any of these enabled can be safely read by a driver that + * does not understand them, but should not be written to, usually because + * additional metadata is required. */ +#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 +#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 +#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 +#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010 +#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020 +#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040 +/* Superblock filesystem feature flags (back-incompatible) + * A filesystem with any of these enabled should not be attempted to be read + * by a driver that does not understand them, since they usually indicate + * metadata format changes that might confuse the reader. */ +#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001 +#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002 +#define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 /* Needs recovery */ +#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 /* Volume is journal device */ +#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 +#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x0040 /* Extents used */ +#define EXT4_FEATURE_INCOMPAT_64BIT 0x0080 +#define EXT4_FEATURE_INCOMPAT_MMP 0x0100 +#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x0200 +#define EXT4_FEATURE_INCOMPAT_ENCRYPT 0x10000 + +/* The set of back-incompatible features this driver DOES support. Add (OR) + * flags here as the related features are implemented into the driver. */ +#define EXT2_DRIVER_SUPPORTED_INCOMPAT ( EXT2_FEATURE_INCOMPAT_FILETYPE \ + | EXT4_FEATURE_INCOMPAT_EXTENTS \ + | EXT4_FEATURE_INCOMPAT_FLEX_BG \ + | EXT2_FEATURE_INCOMPAT_META_BG \ + | EXT4_FEATURE_INCOMPAT_64BIT \ + | EXT4_FEATURE_INCOMPAT_ENCRYPT) +/* List of rationales for the ignored "incompatible" features: + * needs_recovery: Not really back-incompatible - was added as such to forbid + * ext2 drivers from mounting an ext3 volume with a dirty + * journal because they will ignore the journal, but the next + * ext3 driver to mount the volume will find the journal and + * replay it, potentially corrupting the metadata written by + * the ext2 drivers. Safe to ignore for this RO driver. + * mmp: Not really back-incompatible - was added as such to + * avoid multiple read-write mounts. Safe to ignore for this + * RO driver. + */ +#define EXT2_DRIVER_IGNORED_INCOMPAT ( EXT3_FEATURE_INCOMPAT_RECOVER \ + | EXT4_FEATURE_INCOMPAT_MMP) + + +#define EXT3_JOURNAL_MAGIC_NUMBER 0xc03b3998U + +#define EXT3_JOURNAL_DESCRIPTOR_BLOCK 1 +#define EXT3_JOURNAL_COMMIT_BLOCK 2 +#define EXT3_JOURNAL_SUPERBLOCK_V1 3 +#define EXT3_JOURNAL_SUPERBLOCK_V2 4 +#define EXT3_JOURNAL_REVOKE_BLOCK 5 + +#define EXT3_JOURNAL_FLAG_ESCAPE 1 +#define EXT3_JOURNAL_FLAG_SAME_UUID 2 +#define EXT3_JOURNAL_FLAG_DELETED 4 +#define EXT3_JOURNAL_FLAG_LAST_TAG 8 + +#define EXT4_ENCRYPT_FLAG 0x800 +#define EXT4_EXTENTS_FLAG 0x80000 + +/* The ext2 superblock. */ +struct grub_ext2_sblock +{ + grub_uint32_t total_inodes; + grub_uint32_t total_blocks; + grub_uint32_t reserved_blocks; + grub_uint32_t free_blocks; + grub_uint32_t free_inodes; + grub_uint32_t first_data_block; + grub_uint32_t log2_block_size; + grub_uint32_t log2_fragment_size; + grub_uint32_t blocks_per_group; + grub_uint32_t fragments_per_group; + grub_uint32_t inodes_per_group; + grub_uint32_t mtime; + grub_uint32_t utime; + grub_uint16_t mnt_count; + grub_uint16_t max_mnt_count; + grub_uint16_t magic; + grub_uint16_t fs_state; + grub_uint16_t error_handling; + grub_uint16_t minor_revision_level; + grub_uint32_t lastcheck; + grub_uint32_t checkinterval; + grub_uint32_t creator_os; + grub_uint32_t revision_level; + grub_uint16_t uid_reserved; + grub_uint16_t gid_reserved; + grub_uint32_t first_inode; + grub_uint16_t inode_size; + grub_uint16_t block_group_number; + grub_uint32_t feature_compatibility; + grub_uint32_t feature_incompat; + grub_uint32_t feature_ro_compat; + grub_uint16_t uuid[8]; + char volume_name[16]; + char last_mounted_on[64]; + grub_uint32_t compression_info; + grub_uint8_t prealloc_blocks; + grub_uint8_t prealloc_dir_blocks; + grub_uint16_t reserved_gdt_blocks; + grub_uint8_t journal_uuid[16]; + grub_uint32_t journal_inum; + grub_uint32_t journal_dev; + grub_uint32_t last_orphan; + grub_uint32_t hash_seed[4]; + grub_uint8_t def_hash_version; + grub_uint8_t jnl_backup_type; + grub_uint16_t group_desc_size; + grub_uint32_t default_mount_opts; + grub_uint32_t first_meta_bg; + grub_uint32_t mkfs_time; + grub_uint32_t jnl_blocks[17]; +}; + +/* The ext2 blockgroup. */ +struct grub_ext2_block_group +{ + grub_uint32_t block_id; + grub_uint32_t inode_id; + grub_uint32_t inode_table_id; + grub_uint16_t free_blocks; + grub_uint16_t free_inodes; + grub_uint16_t used_dirs; + grub_uint16_t pad; + grub_uint32_t reserved[3]; + grub_uint32_t block_id_hi; + grub_uint32_t inode_id_hi; + grub_uint32_t inode_table_id_hi; + grub_uint16_t free_blocks_hi; + grub_uint16_t free_inodes_hi; + grub_uint16_t used_dirs_hi; + grub_uint16_t pad2; + grub_uint32_t reserved2[3]; +}; + +/* The ext2 inode. */ +struct grub_ext2_inode +{ + grub_uint16_t mode; + grub_uint16_t uid; + grub_uint32_t size; + grub_uint32_t atime; + grub_uint32_t ctime; + grub_uint32_t mtime; + grub_uint32_t dtime; + grub_uint16_t gid; + grub_uint16_t nlinks; + grub_uint32_t blockcnt; /* Blocks of 512 bytes!! */ + grub_uint32_t flags; + grub_uint32_t osd1; + union + { + struct datablocks + { + grub_uint32_t dir_blocks[INDIRECT_BLOCKS]; + grub_uint32_t indir_block; + grub_uint32_t double_indir_block; + grub_uint32_t triple_indir_block; + } blocks; + char symlink[60]; + }; + grub_uint32_t version; + grub_uint32_t acl; + grub_uint32_t size_high; + grub_uint32_t fragment_addr; + grub_uint32_t osd2[3]; +}; + +/* The header of an ext2 directory entry. */ +struct ext2_dirent +{ + grub_uint32_t inode; + grub_uint16_t direntlen; +#define MAX_NAMELEN 255 + grub_uint8_t namelen; + grub_uint8_t filetype; +}; + +struct grub_ext3_journal_header +{ + grub_uint32_t magic; + grub_uint32_t block_type; + grub_uint32_t sequence; +}; + +struct grub_ext3_journal_revoke_header +{ + struct grub_ext3_journal_header header; + grub_uint32_t count; + grub_uint32_t data[0]; +}; + +struct grub_ext3_journal_block_tag +{ + grub_uint32_t block; + grub_uint32_t flags; +}; + +struct grub_ext3_journal_sblock +{ + struct grub_ext3_journal_header header; + grub_uint32_t block_size; + grub_uint32_t maxlen; + grub_uint32_t first; + grub_uint32_t sequence; + grub_uint32_t start; +}; + +#define EXT4_EXT_MAGIC 0xf30a + +struct grub_ext4_extent_header +{ + grub_uint16_t magic; + grub_uint16_t entries; + grub_uint16_t max; + grub_uint16_t depth; + grub_uint32_t generation; +}; + +struct grub_ext4_extent +{ + grub_uint32_t block; + grub_uint16_t len; + grub_uint16_t start_hi; + grub_uint32_t start; +}; + +struct grub_ext4_extent_idx +{ + grub_uint32_t block; + grub_uint32_t leaf; + grub_uint16_t leaf_hi; + grub_uint16_t unused; +}; + +struct grub_fshelp_node +{ + struct grub_ext2_data *data; + struct grub_ext2_inode inode; + int ino; + int inode_read; +}; + +/* Information about a "mounted" ext2 filesystem. */ +struct grub_ext2_data +{ + struct grub_ext2_sblock sblock; + int log_group_desc_size; + grub_disk_t disk; + struct grub_ext2_inode *inode; + struct grub_fshelp_node diropen; +}; + +static grub_dl_t my_mod; + + + +/* Check is a = b^x for some x. */ +static inline int +is_power_of (grub_uint64_t a, grub_uint32_t b) +{ + grub_uint64_t c; + /* Prevent overflow assuming b < 8. */ + if (a >= (1LL << 60)) + return 0; + for (c = 1; c <= a; c *= b); + return (c == a); +} + + +static inline int +group_has_super_block (struct grub_ext2_data *data, grub_uint64_t group) +{ + if (!(data->sblock.feature_ro_compat + & grub_cpu_to_le32_compile_time(EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER))) + return 1; + /* Algorithm looked up in Linux source. */ + if (group <= 1) + return 1; + /* Even number is never a power of odd number. */ + if (!(group & 1)) + return 0; + return (is_power_of(group, 7) || is_power_of(group, 5) || + is_power_of(group, 3)); +} + +/* Read into BLKGRP the blockgroup descriptor of blockgroup GROUP of + the mounted filesystem DATA. */ +inline static grub_err_t +grub_ext2_blockgroup (struct grub_ext2_data *data, grub_uint64_t group, + struct grub_ext2_block_group *blkgrp) +{ + grub_uint64_t full_offset = (group << data->log_group_desc_size); + grub_uint64_t block, offset; + block = (full_offset >> LOG2_BLOCK_SIZE (data)); + offset = (full_offset & ((1 << LOG2_BLOCK_SIZE (data)) - 1)); + if ((data->sblock.feature_incompat + & grub_cpu_to_le32_compile_time (EXT2_FEATURE_INCOMPAT_META_BG)) + && block >= grub_le_to_cpu32(data->sblock.first_meta_bg)) + { + grub_uint64_t first_block_group; + /* Find the first block group for which a descriptor + is stored in given block. */ + first_block_group = (block << (LOG2_BLOCK_SIZE (data) + - data->log_group_desc_size)); + + block = (first_block_group + * grub_le_to_cpu32(data->sblock.blocks_per_group)); + + if (group_has_super_block (data, first_block_group)) + block++; + } + else + /* Superblock. */ + block++; + return grub_disk_read (data->disk, + ((grub_le_to_cpu32 (data->sblock.first_data_block) + + block) + << LOG2_EXT2_BLOCK_SIZE (data)), offset, + sizeof (struct grub_ext2_block_group), blkgrp); +} + +static struct grub_ext4_extent_header * +grub_ext4_find_leaf (struct grub_ext2_data *data, + struct grub_ext4_extent_header *ext_block, + grub_uint32_t fileblock) +{ + struct grub_ext4_extent_idx *index; + void *buf = NULL; + + while (1) + { + int i; + grub_disk_addr_t block; + + index = (struct grub_ext4_extent_idx *) (ext_block + 1); + + if (ext_block->magic != grub_cpu_to_le16_compile_time (EXT4_EXT_MAGIC)) + goto fail; + + if (ext_block->depth == 0) + return ext_block; + + for (i = 0; i < grub_le_to_cpu16 (ext_block->entries); i++) + { + if (fileblock < grub_le_to_cpu32(index[i].block)) + break; + } + + if (--i < 0) + goto fail; + + block = grub_le_to_cpu16 (index[i].leaf_hi); + block = (block << 32) | grub_le_to_cpu32 (index[i].leaf); + if (!buf) + buf = grub_malloc (EXT2_BLOCK_SIZE(data)); + if (!buf) + goto fail; + if (grub_disk_read (data->disk, + block << LOG2_EXT2_BLOCK_SIZE (data), + 0, EXT2_BLOCK_SIZE(data), buf)) + goto fail; + + ext_block = buf; + } + fail: + grub_free (buf); + return 0; +} + +static grub_disk_addr_t +grub_ext2_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +{ + struct grub_ext2_data *data = node->data; + struct grub_ext2_inode *inode = &node->inode; + unsigned int blksz = EXT2_BLOCK_SIZE (data); + grub_disk_addr_t blksz_quarter = blksz / 4; + int log2_blksz = LOG2_EXT2_BLOCK_SIZE (data); + int log_perblock = log2_blksz + 9 - 2; + grub_uint32_t indir; + int shift; + + if (inode->flags & grub_cpu_to_le32_compile_time (EXT4_EXTENTS_FLAG)) + { + struct grub_ext4_extent_header *leaf; + struct grub_ext4_extent *ext; + int i; + grub_disk_addr_t ret; + + leaf = grub_ext4_find_leaf (data, (struct grub_ext4_extent_header *) inode->blocks.dir_blocks, fileblock); + if (! leaf) + { + grub_error (GRUB_ERR_BAD_FS, "invalid extent"); + return -1; + } + + ext = (struct grub_ext4_extent *) (leaf + 1); + for (i = 0; i < grub_le_to_cpu16 (leaf->entries); i++) + { + if (fileblock < grub_le_to_cpu32 (ext[i].block)) + break; + } + + if (--i >= 0) + { + fileblock -= grub_le_to_cpu32 (ext[i].block); + if (fileblock >= grub_le_to_cpu16 (ext[i].len)) + ret = 0; + else + { + grub_disk_addr_t start; + + start = grub_le_to_cpu16 (ext[i].start_hi); + start = (start << 32) + grub_le_to_cpu32 (ext[i].start); + + ret = fileblock + start; + } + } + else + { + grub_error (GRUB_ERR_BAD_FS, "something wrong with extent"); + ret = -1; + } + + if (leaf != (struct grub_ext4_extent_header *) inode->blocks.dir_blocks) + grub_free (leaf); + + return ret; + } + + /* Direct blocks. */ + if (fileblock < INDIRECT_BLOCKS) + return grub_le_to_cpu32 (inode->blocks.dir_blocks[fileblock]); + fileblock -= INDIRECT_BLOCKS; + /* Indirect. */ + if (fileblock < blksz_quarter) + { + indir = inode->blocks.indir_block; + shift = 0; + goto indirect; + } + fileblock -= blksz_quarter; + /* Double indirect. */ + if (fileblock < blksz_quarter * blksz_quarter) + { + indir = inode->blocks.double_indir_block; + shift = 1; + goto indirect; + } + fileblock -= blksz_quarter * blksz_quarter; + /* Triple indirect. */ + if (fileblock < blksz_quarter * blksz_quarter * (blksz_quarter + 1)) + { + indir = inode->blocks.triple_indir_block; + shift = 2; + goto indirect; + } + grub_error (GRUB_ERR_BAD_FS, + "ext2fs doesn't support quadruple indirect blocks"); + return -1; + +indirect: + do { + /* If the indirect block is zero, all child blocks are absent + (i.e. filled with zeros.) */ + if (indir == 0) + return 0; + if (grub_disk_read (data->disk, + ((grub_disk_addr_t) grub_le_to_cpu32 (indir)) + << log2_blksz, + ((fileblock >> (log_perblock * shift)) + & ((1 << log_perblock) - 1)) + * sizeof (indir), + sizeof (indir), &indir)) + return -1; + } while (shift--); + + return grub_le_to_cpu32 (indir); +} + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +static grub_ssize_t +grub_ext2_read_file (grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + return grub_fshelp_read_file (node->data->disk, node, + read_hook, read_hook_data, + pos, len, buf, grub_ext2_read_block, + grub_cpu_to_le32 (node->inode.size) + | (((grub_off_t) grub_cpu_to_le32 (node->inode.size_high)) << 32), + LOG2_EXT2_BLOCK_SIZE (node->data), 0); + +} + + +/* Read the inode INO for the file described by DATA into INODE. */ +static grub_err_t +grub_ext2_read_inode (struct grub_ext2_data *data, + int ino, struct grub_ext2_inode *inode) +{ + struct grub_ext2_block_group blkgrp; + struct grub_ext2_sblock *sblock = &data->sblock; + int inodes_per_block; + unsigned int blkno; + unsigned int blkoff; + grub_disk_addr_t base; + + /* It is easier to calculate if the first inode is 0. */ + ino--; + + grub_ext2_blockgroup (data, + ino / grub_le_to_cpu32 (sblock->inodes_per_group), + &blkgrp); + if (grub_errno) + return grub_errno; + + inodes_per_block = EXT2_BLOCK_SIZE (data) / EXT2_INODE_SIZE (data); + blkno = (ino % grub_le_to_cpu32 (sblock->inodes_per_group)) + / inodes_per_block; + blkoff = (ino % grub_le_to_cpu32 (sblock->inodes_per_group)) + % inodes_per_block; + + base = grub_le_to_cpu32 (blkgrp.inode_table_id); + if (data->log_group_desc_size >= 6) + base |= (((grub_disk_addr_t) grub_le_to_cpu32 (blkgrp.inode_table_id_hi)) + << 32); + + /* Read the inode. */ + if (grub_disk_read (data->disk, + ((base + blkno) << LOG2_EXT2_BLOCK_SIZE (data)), + EXT2_INODE_SIZE (data) * blkoff, + sizeof (struct grub_ext2_inode), inode)) + return grub_errno; + + return 0; +} + +static struct grub_ext2_data * +grub_ext2_mount (grub_disk_t disk) +{ + struct grub_ext2_data *data; + + data = grub_malloc (sizeof (struct grub_ext2_data)); + if (!data) + return 0; + + /* Read the superblock. */ + grub_disk_read (disk, 1 * 2, 0, sizeof (struct grub_ext2_sblock), + &data->sblock); + if (grub_errno) + goto fail; + + /* Make sure this is an ext2 filesystem. */ + if (data->sblock.magic != grub_cpu_to_le16_compile_time (EXT2_MAGIC) + || grub_le_to_cpu32 (data->sblock.log2_block_size) >= 16 + || data->sblock.inodes_per_group == 0 + /* 20 already means 1GiB blocks. We don't want to deal with blocks overflowing int32. */ + || grub_le_to_cpu32 (data->sblock.log2_block_size) > 20 + || EXT2_INODE_SIZE (data) == 0 + || EXT2_BLOCK_SIZE (data) / EXT2_INODE_SIZE (data) == 0) + { + grub_error (GRUB_ERR_BAD_FS, "not an ext2 filesystem"); + goto fail; + } + + /* Check the FS doesn't have feature bits enabled that we don't support. */ + if (data->sblock.revision_level != grub_cpu_to_le32_compile_time (EXT2_GOOD_OLD_REVISION) + && (data->sblock.feature_incompat + & grub_cpu_to_le32_compile_time (~(EXT2_DRIVER_SUPPORTED_INCOMPAT + | EXT2_DRIVER_IGNORED_INCOMPAT)))) + { + grub_error (GRUB_ERR_BAD_FS, "filesystem has unsupported incompatible features"); + goto fail; + } + + if (data->sblock.revision_level != grub_cpu_to_le32_compile_time (EXT2_GOOD_OLD_REVISION) + && (data->sblock.feature_incompat + & grub_cpu_to_le32_compile_time (EXT4_FEATURE_INCOMPAT_64BIT)) + && data->sblock.group_desc_size != 0 + && ((data->sblock.group_desc_size & (data->sblock.group_desc_size - 1)) + == 0) + && (data->sblock.group_desc_size & grub_cpu_to_le16_compile_time (0x1fe0))) + { + grub_uint16_t b = grub_le_to_cpu16 (data->sblock.group_desc_size); + for (data->log_group_desc_size = 0; b != (1 << data->log_group_desc_size); + data->log_group_desc_size++); + } + else + data->log_group_desc_size = 5; + + data->disk = disk; + + data->diropen.data = data; + data->diropen.ino = 2; + data->diropen.inode_read = 1; + + data->inode = &data->diropen.inode; + + grub_ext2_read_inode (data, 2, data->inode); + if (grub_errno) + goto fail; + + return data; + + fail: + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not an ext2 filesystem"); + + grub_free (data); + return 0; +} + +static char * +grub_ext2_read_symlink (grub_fshelp_node_t node) +{ + char *symlink; + struct grub_fshelp_node *diro = node; + grub_size_t sz; + + if (! diro->inode_read) + { + grub_ext2_read_inode (diro->data, diro->ino, &diro->inode); + if (grub_errno) + return 0; + + if (diro->inode.flags & grub_cpu_to_le32_compile_time (EXT4_ENCRYPT_FLAG)) + { + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "symlink is encrypted"); + return 0; + } + } + + if (grub_add (grub_le_to_cpu32 (diro->inode.size), 1, &sz)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected")); + return NULL; + } + + symlink = grub_malloc (sz); + if (! symlink) + return 0; + + /* + * If the filesize of the symlink is equal to or bigger than 60 the symlink + * is stored in a separate block, otherwise it is stored in the inode. + */ + if (grub_le_to_cpu32 (diro->inode.size) < sizeof (diro->inode.symlink)) + grub_memcpy (symlink, + diro->inode.symlink, + grub_le_to_cpu32 (diro->inode.size)); + else + { + grub_ext2_read_file (diro, 0, 0, 0, + grub_le_to_cpu32 (diro->inode.size), + symlink); + if (grub_errno) + { + grub_free (symlink); + return 0; + } + } + + symlink[grub_le_to_cpu32 (diro->inode.size)] = '\0'; + return symlink; +} + +static int +grub_ext2_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + unsigned int fpos = 0; + struct grub_fshelp_node *diro = (struct grub_fshelp_node *) dir; + + if (! diro->inode_read) + { + grub_ext2_read_inode (diro->data, diro->ino, &diro->inode); + if (grub_errno) + return 0; + } + + if (diro->inode.flags & grub_cpu_to_le32_compile_time (EXT4_ENCRYPT_FLAG)) + { + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "directory is encrypted"); + return 0; + } + + /* Search the file. */ + while (fpos < grub_le_to_cpu32 (diro->inode.size)) + { + struct ext2_dirent dirent; + + grub_ext2_read_file (diro, 0, 0, fpos, sizeof (struct ext2_dirent), + (char *) &dirent); + if (grub_errno) + return 0; + + if (dirent.direntlen == 0) + return 0; + + if (dirent.inode != 0 && dirent.namelen != 0) + { + char filename[MAX_NAMELEN + 1]; + struct grub_fshelp_node *fdiro; + enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN; + + grub_ext2_read_file (diro, 0, 0, fpos + sizeof (struct ext2_dirent), + dirent.namelen, filename); + if (grub_errno) + return 0; + + fdiro = grub_malloc (sizeof (struct grub_fshelp_node)); + if (! fdiro) + return 0; + + fdiro->data = diro->data; + fdiro->ino = grub_le_to_cpu32 (dirent.inode); + + filename[dirent.namelen] = '\0'; + + if (dirent.filetype != FILETYPE_UNKNOWN) + { + fdiro->inode_read = 0; + + if (dirent.filetype == FILETYPE_DIRECTORY) + type = GRUB_FSHELP_DIR; + else if (dirent.filetype == FILETYPE_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + else if (dirent.filetype == FILETYPE_REG) + type = GRUB_FSHELP_REG; + } + else + { + /* The filetype can not be read from the dirent, read + the inode to get more information. */ + grub_ext2_read_inode (diro->data, + grub_le_to_cpu32 (dirent.inode), + &fdiro->inode); + if (grub_errno) + { + grub_free (fdiro); + return 0; + } + + fdiro->inode_read = 1; + + if ((grub_le_to_cpu16 (fdiro->inode.mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_DIRECTORY) + type = GRUB_FSHELP_DIR; + else if ((grub_le_to_cpu16 (fdiro->inode.mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + else if ((grub_le_to_cpu16 (fdiro->inode.mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_REG) + type = GRUB_FSHELP_REG; + } + + if (hook (filename, type, fdiro, hook_data)) + return 1; + } + + fpos += grub_le_to_cpu16 (dirent.direntlen); + } + + return 0; +} + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_ext2_open (struct grub_file *file, const char *name) +{ + struct grub_ext2_data *data; + struct grub_fshelp_node *fdiro = 0; + grub_err_t err; + + grub_dl_ref (my_mod); + + data = grub_ext2_mount (file->device->disk); + if (! data) + { + err = grub_errno; + goto fail; + } + + err = grub_fshelp_find_file (name, &data->diropen, &fdiro, + grub_ext2_iterate_dir, + grub_ext2_read_symlink, GRUB_FSHELP_REG); + if (err) + goto fail; + + if (! fdiro->inode_read) + { + err = grub_ext2_read_inode (data, fdiro->ino, &fdiro->inode); + if (err) + goto fail; + } + + if (fdiro->inode.flags & grub_cpu_to_le32_compile_time (EXT4_ENCRYPT_FLAG)) + { + err = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "file is encrypted"); + goto fail; + } + + grub_memcpy (data->inode, &fdiro->inode, sizeof (struct grub_ext2_inode)); + grub_free (fdiro); + + file->size = grub_le_to_cpu32 (data->inode->size); + file->size |= ((grub_off_t) grub_le_to_cpu32 (data->inode->size_high)) << 32; + file->data = data; + file->offset = 0; + + return 0; + + fail: + if (fdiro != &data->diropen) + grub_free (fdiro); + grub_free (data); + + grub_dl_unref (my_mod); + + return err; +} + +static grub_err_t +grub_ext2_close (grub_file_t file) +{ + 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_ext2_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_ext2_data *data = (struct grub_ext2_data *) file->data; + + return grub_ext2_read_file (&data->diropen, + file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + + +/* Context for grub_ext2_dir. */ +struct grub_ext2_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; + struct grub_ext2_data *data; +}; + +/* Helper for grub_ext2_dir. */ +static int +grub_ext2_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_ext2_dir_ctx *ctx = data; + struct grub_dirhook_info info; + + grub_memset (&info, 0, sizeof (info)); + if (! node->inode_read) + { + grub_ext2_read_inode (ctx->data, node->ino, &node->inode); + if (!grub_errno) + node->inode_read = 1; + grub_errno = GRUB_ERR_NONE; + } + if (node->inode_read) + { + info.mtimeset = 1; + info.mtime = grub_le_to_cpu32 (node->inode.mtime); + } + + info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_ext2_dir (grub_device_t device, const char *path, grub_fs_dir_hook_t hook, + void *hook_data) +{ + struct grub_ext2_dir_ctx ctx = { + .hook = hook, + .hook_data = hook_data + }; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + ctx.data = grub_ext2_mount (device->disk); + if (! ctx.data) + goto fail; + + grub_fshelp_find_file (path, &ctx.data->diropen, &fdiro, + grub_ext2_iterate_dir, grub_ext2_read_symlink, + GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + + grub_ext2_iterate_dir (fdiro, grub_ext2_dir_iter, &ctx); + + fail: + if (fdiro != &ctx.data->diropen) + grub_free (fdiro); + grub_free (ctx.data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_ext2_label (grub_device_t device, char **label) +{ + struct grub_ext2_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_ext2_mount (disk); + if (data) + *label = grub_strndup (data->sblock.volume_name, + sizeof (data->sblock.volume_name)); + else + *label = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_ext2_uuid (grub_device_t device, char **uuid) +{ + struct grub_ext2_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_ext2_mount (disk); + if (data) + { + *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + grub_be_to_cpu16 (data->sblock.uuid[0]), + grub_be_to_cpu16 (data->sblock.uuid[1]), + grub_be_to_cpu16 (data->sblock.uuid[2]), + grub_be_to_cpu16 (data->sblock.uuid[3]), + grub_be_to_cpu16 (data->sblock.uuid[4]), + grub_be_to_cpu16 (data->sblock.uuid[5]), + grub_be_to_cpu16 (data->sblock.uuid[6]), + grub_be_to_cpu16 (data->sblock.uuid[7])); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +/* Get mtime. */ +static grub_err_t +grub_ext2_mtime (grub_device_t device, grub_int64_t *tm) +{ + struct grub_ext2_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_ext2_mount (disk); + if (!data) + *tm = 0; + else + *tm = grub_le_to_cpu32 (data->sblock.utime); + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; + +} + + + +static struct grub_fs grub_ext2_fs = + { + .name = "ext2", + .fs_dir = grub_ext2_dir, + .fs_open = grub_ext2_open, + .fs_read = grub_ext2_read, + .fs_close = grub_ext2_close, + .fs_label = grub_ext2_label, + .fs_uuid = grub_ext2_uuid, + .fs_mtime = grub_ext2_mtime, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(ext2) +{ + grub_fs_register (&grub_ext2_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(ext2) +{ + grub_fs_unregister (&grub_ext2_fs); +} diff --git a/grub-core/fs/f2fs.c b/grub-core/fs/f2fs.c new file mode 100644 index 0000000..8a9992c --- /dev/null +++ b/grub-core/fs/f2fs.c @@ -0,0 +1,1328 @@ +/* + * f2fs.c - Flash-Friendly File System + * + * Written by Jaegeuk Kim <jaegeuk@kernel.org> + * + * Copyright (C) 2015 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/charset.h> +#include <grub/fshelp.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* F2FS Magic Number. */ +#define F2FS_SUPER_MAGIC 0xf2f52010 + +#define CHECKSUM_OFFSET 4092 /* Must be aligned 4 bytes. */ +#define U32_CHECKSUM_OFFSET (CHECKSUM_OFFSET >> 2) +#define CRCPOLY_LE 0xedb88320 + +/* Byte-size offset. */ +#define F2FS_SUPER_OFFSET ((grub_disk_addr_t)1024) +#define F2FS_SUPER_OFFSET0 (F2FS_SUPER_OFFSET >> GRUB_DISK_SECTOR_BITS) +#define F2FS_SUPER_OFFSET1 ((F2FS_SUPER_OFFSET + F2FS_BLKSIZE) >> \ + GRUB_DISK_SECTOR_BITS) + +/* 9 bits for 512 bytes. */ +#define F2FS_MIN_LOG_SECTOR_SIZE 9 + +/* Support only 4KB block. */ +#define F2FS_BLK_BITS 12 +#define F2FS_BLKSIZE (1 << F2FS_BLK_BITS) +#define F2FS_BLK_SEC_BITS (F2FS_BLK_BITS - GRUB_DISK_SECTOR_BITS) + +#define VERSION_LEN 256 +#define F2FS_MAX_EXTENSION 64 + +#define CP_COMPACT_SUM_FLAG 0x00000004 +#define CP_UMOUNT_FLAG 0x00000001 + +#define MAX_ACTIVE_LOGS 16 +#define MAX_ACTIVE_NODE_LOGS 8 +#define MAX_ACTIVE_DATA_LOGS 8 +#define NR_CURSEG_DATA_TYPE 3 +#define NR_CURSEG_NODE_TYPE 3 +#define NR_CURSEG_TYPE (NR_CURSEG_DATA_TYPE + NR_CURSEG_NODE_TYPE) + +#define ENTRIES_IN_SUM 512 +#define SUMMARY_SIZE 7 +#define SUM_FOOTER_SIZE 5 +#define JENTRY_SIZE (sizeof(struct grub_f2fs_nat_jent)) +#define SUM_ENTRIES_SIZE (SUMMARY_SIZE * ENTRIES_IN_SUM) +#define SUM_JOURNAL_SIZE (F2FS_BLKSIZE - SUM_FOOTER_SIZE - SUM_ENTRIES_SIZE) +#define NAT_JOURNAL_ENTRIES ((SUM_JOURNAL_SIZE - 2) / JENTRY_SIZE) +#define NAT_JOURNAL_RESERVED ((SUM_JOURNAL_SIZE - 2) % JENTRY_SIZE) + +#define NAT_ENTRY_SIZE (sizeof(struct grub_f2fs_nat_entry)) +#define NAT_ENTRY_PER_BLOCK (F2FS_BLKSIZE / NAT_ENTRY_SIZE) + +#define F2FS_NAME_LEN 255 +#define F2FS_SLOT_LEN 8 +#define NR_DENTRY_IN_BLOCK 214 +#define SIZE_OF_DIR_ENTRY 11 /* By byte. */ +#define BITS_PER_BYTE 8 +#define SIZE_OF_DENTRY_BITMAP ((NR_DENTRY_IN_BLOCK + BITS_PER_BYTE - 1) / \ + BITS_PER_BYTE) +#define SIZE_OF_RESERVED (F2FS_BLKSIZE - \ + ((SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN) * \ + NR_DENTRY_IN_BLOCK + SIZE_OF_DENTRY_BITMAP)) + +#define F2FS_INLINE_XATTR_ADDRS 50 /* 200 bytes for inline xattrs. */ +#define DEF_ADDRS_PER_INODE 923 /* Address Pointers in an Inode. */ + +#define ADDRS_PER_BLOCK 1018 /* Address Pointers in a Direct Block. */ +#define NIDS_PER_BLOCK 1018 /* Node IDs in an Indirect Block. */ +#define NODE_DIR1_BLOCK (DEF_ADDRS_PER_INODE + 1) +#define NODE_DIR2_BLOCK (DEF_ADDRS_PER_INODE + 2) +#define NODE_IND1_BLOCK (DEF_ADDRS_PER_INODE + 3) +#define NODE_IND2_BLOCK (DEF_ADDRS_PER_INODE + 4) +#define NODE_DIND_BLOCK (DEF_ADDRS_PER_INODE + 5) + +#define MAX_INLINE_DATA (4 * (DEF_ADDRS_PER_INODE - \ + F2FS_INLINE_XATTR_ADDRS - 1)) +#define NR_INLINE_DENTRY (MAX_INLINE_DATA * BITS_PER_BYTE / \ + ((SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN) * \ + BITS_PER_BYTE + 1)) +#define INLINE_DENTRY_BITMAP_SIZE ((NR_INLINE_DENTRY + BITS_PER_BYTE - 1) / \ + BITS_PER_BYTE) +#define INLINE_RESERVED_SIZE (MAX_INLINE_DATA - \ + ((SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN) * \ + NR_INLINE_DENTRY + \ + INLINE_DENTRY_BITMAP_SIZE)) +#define CURSEG_HOT_DATA 0 + +#define CKPT_FLAG_SET(ckpt, f) (ckpt)->ckpt_flags & \ + grub_cpu_to_le32_compile_time (f) + +#define F2FS_INLINE_XATTR 0x01 /* File inline xattr flag. */ +#define F2FS_INLINE_DATA 0x02 /* File inline data flag. */ +#define F2FS_INLINE_DENTRY 0x04 /* File inline dentry flag. */ +#define F2FS_DATA_EXIST 0x08 /* File inline data exist flag. */ +#define F2FS_INLINE_DOTS 0x10 /* File having implicit dot dentries. */ + +#define MAX_VOLUME_NAME 512 + +enum FILE_TYPE +{ + F2FS_FT_UNKNOWN, + F2FS_FT_REG_FILE = 1, + F2FS_FT_DIR = 2, + F2FS_FT_SYMLINK = 7 +}; + +struct grub_f2fs_superblock +{ + grub_uint32_t magic; + grub_uint16_t dummy1[2]; + grub_uint32_t log_sectorsize; + grub_uint32_t log_sectors_per_block; + grub_uint32_t log_blocksize; + grub_uint32_t log_blocks_per_seg; + grub_uint32_t segs_per_sec; + grub_uint32_t secs_per_zone; + grub_uint32_t checksum_offset; + grub_uint8_t dummy2[40]; + grub_uint32_t cp_blkaddr; + grub_uint32_t sit_blkaddr; + grub_uint32_t nat_blkaddr; + grub_uint32_t ssa_blkaddr; + grub_uint32_t main_blkaddr; + grub_uint32_t root_ino; + grub_uint32_t node_ino; + grub_uint32_t meta_ino; + grub_uint8_t uuid[16]; + grub_uint16_t volume_name[MAX_VOLUME_NAME]; + grub_uint32_t extension_count; + grub_uint8_t extension_list[F2FS_MAX_EXTENSION][8]; + grub_uint32_t cp_payload; + grub_uint8_t version[VERSION_LEN]; + grub_uint8_t init_version[VERSION_LEN]; +} GRUB_PACKED; + +struct grub_f2fs_checkpoint +{ + grub_uint64_t checkpoint_ver; + grub_uint64_t user_block_count; + grub_uint64_t valid_block_count; + grub_uint32_t rsvd_segment_count; + grub_uint32_t overprov_segment_count; + grub_uint32_t free_segment_count; + grub_uint32_t cur_node_segno[MAX_ACTIVE_NODE_LOGS]; + grub_uint16_t cur_node_blkoff[MAX_ACTIVE_NODE_LOGS]; + grub_uint32_t cur_data_segno[MAX_ACTIVE_DATA_LOGS]; + grub_uint16_t cur_data_blkoff[MAX_ACTIVE_DATA_LOGS]; + grub_uint32_t ckpt_flags; + grub_uint32_t cp_pack_total_block_count; + grub_uint32_t cp_pack_start_sum; + grub_uint32_t valid_node_count; + grub_uint32_t valid_inode_count; + grub_uint32_t next_free_nid; + grub_uint32_t sit_ver_bitmap_bytesize; + grub_uint32_t nat_ver_bitmap_bytesize; + grub_uint32_t checksum_offset; + grub_uint64_t elapsed_time; + grub_uint8_t alloc_type[MAX_ACTIVE_LOGS]; + grub_uint8_t sit_nat_version_bitmap[3900]; + grub_uint32_t checksum; +} GRUB_PACKED; + +struct grub_f2fs_nat_entry { + grub_uint8_t version; + grub_uint32_t ino; + grub_uint32_t block_addr; +} GRUB_PACKED; + +struct grub_f2fs_nat_jent +{ + grub_uint32_t nid; + struct grub_f2fs_nat_entry ne; +} GRUB_PACKED; + +struct grub_f2fs_nat_journal { + grub_uint16_t n_nats; + struct grub_f2fs_nat_jent entries[NAT_JOURNAL_ENTRIES]; + grub_uint8_t reserved[NAT_JOURNAL_RESERVED]; +} GRUB_PACKED; + +struct grub_f2fs_nat_block { + struct grub_f2fs_nat_entry ne[NAT_ENTRY_PER_BLOCK]; +} GRUB_PACKED; + +struct grub_f2fs_dir_entry +{ + grub_uint32_t hash_code; + grub_uint32_t ino; + grub_uint16_t name_len; + grub_uint8_t file_type; +} GRUB_PACKED; + +struct grub_f2fs_inline_dentry +{ + grub_uint8_t dentry_bitmap[INLINE_DENTRY_BITMAP_SIZE]; + grub_uint8_t reserved[INLINE_RESERVED_SIZE]; + struct grub_f2fs_dir_entry dentry[NR_INLINE_DENTRY]; + grub_uint8_t filename[NR_INLINE_DENTRY][F2FS_SLOT_LEN]; +} GRUB_PACKED; + +struct grub_f2fs_dentry_block { + grub_uint8_t dentry_bitmap[SIZE_OF_DENTRY_BITMAP]; + grub_uint8_t reserved[SIZE_OF_RESERVED]; + struct grub_f2fs_dir_entry dentry[NR_DENTRY_IN_BLOCK]; + grub_uint8_t filename[NR_DENTRY_IN_BLOCK][F2FS_SLOT_LEN]; +} GRUB_PACKED; + +struct grub_f2fs_inode +{ + grub_uint16_t i_mode; + grub_uint8_t i_advise; + grub_uint8_t i_inline; + grub_uint32_t i_uid; + grub_uint32_t i_gid; + grub_uint32_t i_links; + grub_uint64_t i_size; + grub_uint64_t i_blocks; + grub_uint64_t i_atime; + grub_uint64_t i_ctime; + grub_uint64_t i_mtime; + grub_uint32_t i_atime_nsec; + grub_uint32_t i_ctime_nsec; + grub_uint32_t i_mtime_nsec; + grub_uint32_t i_generation; + grub_uint32_t i_current_depth; + grub_uint32_t i_xattr_nid; + grub_uint32_t i_flags; + grub_uint32_t i_pino; + grub_uint32_t i_namelen; + grub_uint8_t i_name[F2FS_NAME_LEN]; + grub_uint8_t i_dir_level; + grub_uint8_t i_ext[12]; + grub_uint32_t i_addr[DEF_ADDRS_PER_INODE]; + grub_uint32_t i_nid[5]; +} GRUB_PACKED; + +struct grub_direct_node { + grub_uint32_t addr[ADDRS_PER_BLOCK]; +} GRUB_PACKED; + +struct grub_indirect_node { + grub_uint32_t nid[NIDS_PER_BLOCK]; +} GRUB_PACKED; + +struct grub_f2fs_node +{ + union + { + struct grub_f2fs_inode i; + struct grub_direct_node dn; + struct grub_indirect_node in; + /* Should occupy F2FS_BLKSIZE totally. */ + char buf[F2FS_BLKSIZE - 40]; + }; + grub_uint8_t dummy[40]; +} GRUB_PACKED; + +struct grub_fshelp_node +{ + struct grub_f2fs_data *data; + struct grub_f2fs_node inode; + grub_uint32_t ino; + int inode_read; +}; + +struct grub_f2fs_data +{ + struct grub_f2fs_superblock sblock; + struct grub_f2fs_checkpoint ckpt; + + grub_uint32_t root_ino; + grub_uint32_t blocks_per_seg; + grub_uint32_t cp_blkaddr; + grub_uint32_t nat_blkaddr; + + struct grub_f2fs_nat_journal nat_j; + char *nat_bitmap; + + grub_disk_t disk; + struct grub_f2fs_node *inode; + struct grub_fshelp_node diropen; +}; + +struct grub_f2fs_dir_iter_ctx +{ + struct grub_f2fs_data *data; + grub_fshelp_iterate_dir_hook_t hook; + void *hook_data; + grub_uint8_t *bitmap; + grub_uint8_t (*filename)[F2FS_SLOT_LEN]; + struct grub_f2fs_dir_entry *dentry; + int max; +}; + +struct grub_f2fs_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; + struct grub_f2fs_data *data; +}; + +static grub_dl_t my_mod; + +static int +grub_f2fs_test_bit_le (int nr, const grub_uint8_t *addr) +{ + return addr[nr >> 3] & (1 << (nr & 7)); +} + +static char * +get_inline_addr (struct grub_f2fs_inode *inode) +{ + return (char *) &inode->i_addr[1]; +} + +static grub_uint64_t +grub_f2fs_file_size (struct grub_f2fs_inode *inode) +{ + return grub_le_to_cpu64 (inode->i_size); +} + +static grub_uint32_t +start_cp_addr (struct grub_f2fs_data *data) +{ + struct grub_f2fs_checkpoint *ckpt = &data->ckpt; + grub_uint32_t start_addr = data->cp_blkaddr; + + if (!(ckpt->checkpoint_ver & grub_cpu_to_le64_compile_time(1))) + return start_addr + data->blocks_per_seg; + + return start_addr; +} + +static grub_uint32_t +start_sum_block (struct grub_f2fs_data *data) +{ + struct grub_f2fs_checkpoint *ckpt = &data->ckpt; + + return start_cp_addr (data) + grub_le_to_cpu32 (ckpt->cp_pack_start_sum); +} + +static grub_uint32_t +sum_blk_addr (struct grub_f2fs_data *data, int base, int type) +{ + struct grub_f2fs_checkpoint *ckpt = &data->ckpt; + + return start_cp_addr (data) + + grub_le_to_cpu32 (ckpt->cp_pack_total_block_count) - + (base + 1) + type; +} + +static void * +nat_bitmap_ptr (struct grub_f2fs_data *data) +{ + struct grub_f2fs_checkpoint *ckpt = &data->ckpt; + grub_uint32_t offset; + + if (grub_le_to_cpu32 (data->sblock.cp_payload) > 0) + return ckpt->sit_nat_version_bitmap; + + offset = grub_le_to_cpu32 (ckpt->sit_ver_bitmap_bytesize); + + return ckpt->sit_nat_version_bitmap + offset; +} + +static grub_uint32_t +get_node_id (struct grub_f2fs_node *rn, int off, int inode_block) +{ + if (inode_block) + return grub_le_to_cpu32 (rn->i.i_nid[off - NODE_DIR1_BLOCK]); + + return grub_le_to_cpu32 (rn->in.nid[off]); +} + +static grub_err_t +grub_f2fs_block_read (struct grub_f2fs_data *data, grub_uint32_t blkaddr, + void *buf) +{ + return grub_disk_read (data->disk, + ((grub_disk_addr_t)blkaddr) << F2FS_BLK_SEC_BITS, + 0, F2FS_BLKSIZE, buf); +} + +/* CRC32 */ +static grub_uint32_t +grub_f2fs_cal_crc32 (const void *buf, const grub_uint32_t len) +{ + grub_uint32_t crc = F2FS_SUPER_MAGIC; + unsigned char *p = (unsigned char *)buf; + grub_uint32_t tmp = len; + int i; + + while (tmp--) + { + crc ^= *p++; + for (i = 0; i < 8; i++) + crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0); + } + + return crc; +} + +static int +grub_f2fs_crc_valid (grub_uint32_t blk_crc, void *buf, const grub_uint32_t len) +{ + grub_uint32_t cal_crc = 0; + + cal_crc = grub_f2fs_cal_crc32 (buf, len); + + return (cal_crc == blk_crc) ? 1 : 0; +} + +static int +grub_f2fs_test_bit (grub_uint32_t nr, const char *p) +{ + int mask; + + p += (nr >> 3); + mask = 1 << (7 - (nr & 0x07)); + + return mask & *p; +} + +static int +grub_f2fs_sanity_check_sb (struct grub_f2fs_superblock *sb) +{ + grub_uint32_t log_sectorsize, log_sectors_per_block; + + if (sb->magic != grub_cpu_to_le32_compile_time (F2FS_SUPER_MAGIC)) + return -1; + + if (sb->log_blocksize != grub_cpu_to_le32_compile_time (F2FS_BLK_BITS)) + return -1; + + log_sectorsize = grub_le_to_cpu32 (sb->log_sectorsize); + log_sectors_per_block = grub_le_to_cpu32 (sb->log_sectors_per_block); + + if (log_sectorsize > F2FS_BLK_BITS) + return -1; + + if (log_sectorsize < F2FS_MIN_LOG_SECTOR_SIZE) + return -1; + + if (log_sectors_per_block + log_sectorsize != F2FS_BLK_BITS) + return -1; + + return 0; +} + +static int +grub_f2fs_read_sb (struct grub_f2fs_data *data, grub_disk_addr_t offset) +{ + grub_disk_t disk = data->disk; + grub_err_t err; + + /* Read first super block. */ + err = grub_disk_read (disk, offset, 0, sizeof (data->sblock), &data->sblock); + if (err) + return -1; + + return grub_f2fs_sanity_check_sb (&data->sblock); +} + +static void * +validate_checkpoint (struct grub_f2fs_data *data, grub_uint32_t cp_addr, + grub_uint64_t *version) +{ + grub_uint32_t *cp_page_1, *cp_page_2; + struct grub_f2fs_checkpoint *cp_block; + grub_uint64_t cur_version = 0, pre_version = 0; + grub_uint32_t crc = 0; + grub_uint32_t crc_offset; + grub_err_t err; + + /* Read the 1st cp block in this CP pack. */ + cp_page_1 = grub_malloc (F2FS_BLKSIZE); + if (!cp_page_1) + return NULL; + + err = grub_f2fs_block_read (data, cp_addr, cp_page_1); + if (err) + goto invalid_cp1; + + cp_block = (struct grub_f2fs_checkpoint *)cp_page_1; + crc_offset = grub_le_to_cpu32 (cp_block->checksum_offset); + if (crc_offset != CHECKSUM_OFFSET) + goto invalid_cp1; + + crc = grub_le_to_cpu32 (*(cp_page_1 + U32_CHECKSUM_OFFSET)); + if (!grub_f2fs_crc_valid (crc, cp_block, crc_offset)) + goto invalid_cp1; + + pre_version = grub_le_to_cpu64 (cp_block->checkpoint_ver); + + /* Read the 2nd cp block in this CP pack. */ + cp_page_2 = grub_malloc (F2FS_BLKSIZE); + if (!cp_page_2) + goto invalid_cp1; + + cp_addr += grub_le_to_cpu32 (cp_block->cp_pack_total_block_count) - 1; + + err = grub_f2fs_block_read (data, cp_addr, cp_page_2); + if (err) + goto invalid_cp2; + + cp_block = (struct grub_f2fs_checkpoint *)cp_page_2; + crc_offset = grub_le_to_cpu32 (cp_block->checksum_offset); + if (crc_offset != CHECKSUM_OFFSET) + goto invalid_cp2; + + crc = grub_le_to_cpu32 (*(cp_page_2 + U32_CHECKSUM_OFFSET)); + if (!grub_f2fs_crc_valid (crc, cp_block, crc_offset)) + goto invalid_cp2; + + cur_version = grub_le_to_cpu64 (cp_block->checkpoint_ver); + if (cur_version == pre_version) + { + *version = cur_version; + grub_free (cp_page_2); + + return cp_page_1; + } + + invalid_cp2: + grub_free (cp_page_2); + + invalid_cp1: + grub_free (cp_page_1); + + return NULL; +} + +static grub_err_t +grub_f2fs_read_cp (struct grub_f2fs_data *data) +{ + void *cp1, *cp2, *cur_page; + grub_uint64_t cp1_version = 0, cp2_version = 0; + grub_uint64_t cp_start_blk_no; + + /* + * Finding out valid cp block involves read both + * sets (cp pack1 and cp pack 2). + */ + cp_start_blk_no = data->cp_blkaddr; + cp1 = validate_checkpoint (data, cp_start_blk_no, &cp1_version); + if (!cp1 && grub_errno) + return grub_errno; + + /* The second checkpoint pack should start at the next segment. */ + cp_start_blk_no += data->blocks_per_seg; + cp2 = validate_checkpoint (data, cp_start_blk_no, &cp2_version); + if (!cp2 && grub_errno) + { + grub_free (cp1); + return grub_errno; + } + + if (cp1 && cp2) + cur_page = (cp2_version > cp1_version) ? cp2 : cp1; + else if (cp1) + cur_page = cp1; + else if (cp2) + cur_page = cp2; + else + return grub_error (GRUB_ERR_BAD_FS, "no checkpoints"); + + grub_memcpy (&data->ckpt, cur_page, F2FS_BLKSIZE); + + grub_free (cp1); + grub_free (cp2); + + return 0; +} + +static grub_err_t +get_nat_journal (struct grub_f2fs_data *data) +{ + grub_uint32_t block; + char *buf; + grub_err_t err; + + buf = grub_malloc (F2FS_BLKSIZE); + if (!buf) + return grub_errno; + + if (CKPT_FLAG_SET(&data->ckpt, CP_COMPACT_SUM_FLAG)) + block = start_sum_block (data); + else if (CKPT_FLAG_SET (&data->ckpt, CP_UMOUNT_FLAG)) + block = sum_blk_addr (data, NR_CURSEG_TYPE, CURSEG_HOT_DATA); + else + block = sum_blk_addr (data, NR_CURSEG_DATA_TYPE, CURSEG_HOT_DATA); + + err = grub_f2fs_block_read (data, block, buf); + if (err) + goto fail; + + if (CKPT_FLAG_SET (&data->ckpt, CP_COMPACT_SUM_FLAG)) + grub_memcpy (&data->nat_j, buf, SUM_JOURNAL_SIZE); + else + grub_memcpy (&data->nat_j, buf + SUM_ENTRIES_SIZE, SUM_JOURNAL_SIZE); + + fail: + grub_free (buf); + + return err; +} + +static grub_uint32_t +get_blkaddr_from_nat_journal (struct grub_f2fs_data *data, grub_uint32_t nid) +{ + grub_uint16_t n = grub_le_to_cpu16 (data->nat_j.n_nats); + grub_uint32_t blkaddr = 0; + grub_uint16_t i; + + for (i = 0; i < n; i++) + { + if (grub_le_to_cpu32 (data->nat_j.entries[i].nid) == nid) + { + blkaddr = grub_le_to_cpu32 (data->nat_j.entries[i].ne.block_addr); + break; + } + } + + return blkaddr; +} + +static grub_uint32_t +get_node_blkaddr (struct grub_f2fs_data *data, grub_uint32_t nid) +{ + struct grub_f2fs_nat_block *nat_block; + grub_uint32_t seg_off, block_off, entry_off, block_addr; + grub_uint32_t blkaddr; + grub_err_t err; + + blkaddr = get_blkaddr_from_nat_journal (data, nid); + if (blkaddr) + return blkaddr; + + nat_block = grub_malloc (F2FS_BLKSIZE); + if (!nat_block) + return 0; + + block_off = nid / NAT_ENTRY_PER_BLOCK; + entry_off = nid % NAT_ENTRY_PER_BLOCK; + + seg_off = block_off / data->blocks_per_seg; + block_addr = data->nat_blkaddr + + ((seg_off * data->blocks_per_seg) << 1) + + (block_off & (data->blocks_per_seg - 1)); + + if (grub_f2fs_test_bit (block_off, data->nat_bitmap)) + block_addr += data->blocks_per_seg; + + err = grub_f2fs_block_read (data, block_addr, nat_block); + if (err) + { + grub_free (nat_block); + return 0; + } + + blkaddr = grub_le_to_cpu32 (nat_block->ne[entry_off].block_addr); + + grub_free (nat_block); + + return blkaddr; +} + +static int +grub_get_node_path (struct grub_f2fs_inode *inode, grub_uint32_t block, + grub_uint32_t offset[4], grub_uint32_t noffset[4]) +{ + grub_uint32_t direct_blks = ADDRS_PER_BLOCK; + grub_uint32_t dptrs_per_blk = NIDS_PER_BLOCK; + grub_uint32_t indirect_blks = ADDRS_PER_BLOCK * NIDS_PER_BLOCK; + grub_uint32_t dindirect_blks = indirect_blks * NIDS_PER_BLOCK; + grub_uint32_t direct_index = DEF_ADDRS_PER_INODE; + int n = 0; + int level = -1; + + if (inode->i_inline & F2FS_INLINE_XATTR) + direct_index -= F2FS_INLINE_XATTR_ADDRS; + + noffset[0] = 0; + + if (block < direct_index) + { + offset[n] = block; + level = 0; + goto got; + } + + block -= direct_index; + if (block < direct_blks) + { + offset[n++] = NODE_DIR1_BLOCK; + noffset[n] = 1; + offset[n] = block; + level = 1; + goto got; + } + + block -= direct_blks; + if (block < direct_blks) + { + offset[n++] = NODE_DIR2_BLOCK; + noffset[n] = 2; + offset[n] = block; + level = 1; + goto got; + } + + block -= direct_blks; + if (block < indirect_blks) + { + offset[n++] = NODE_IND1_BLOCK; + noffset[n] = 3; + offset[n++] = block / direct_blks; + noffset[n] = 4 + offset[n - 1]; + offset[n] = block % direct_blks; + level = 2; + goto got; + } + + block -= indirect_blks; + if (block < indirect_blks) + { + offset[n++] = NODE_IND2_BLOCK; + noffset[n] = 4 + dptrs_per_blk; + offset[n++] = block / direct_blks; + noffset[n] = 5 + dptrs_per_blk + offset[n - 1]; + offset[n] = block % direct_blks; + level = 2; + goto got; + } + + block -= indirect_blks; + if (block < dindirect_blks) + { + offset[n++] = NODE_DIND_BLOCK; + noffset[n] = 5 + (dptrs_per_blk * 2); + offset[n++] = block / indirect_blks; + noffset[n] = 6 + (dptrs_per_blk * 2) + + offset[n - 1] * (dptrs_per_blk + 1); + offset[n++] = (block / direct_blks) % dptrs_per_blk; + noffset[n] = 7 + (dptrs_per_blk * 2) + + offset[n - 2] * (dptrs_per_blk + 1) + offset[n - 1]; + offset[n] = block % direct_blks; + level = 3; + goto got; + } + + got: + return level; +} + +static grub_err_t +grub_f2fs_read_node (struct grub_f2fs_data *data, + grub_uint32_t nid, struct grub_f2fs_node *np) +{ + grub_uint32_t blkaddr; + + blkaddr = get_node_blkaddr (data, nid); + if (!blkaddr) + return grub_errno; + + return grub_f2fs_block_read (data, blkaddr, np); +} + +static struct grub_f2fs_data * +grub_f2fs_mount (grub_disk_t disk) +{ + struct grub_f2fs_data *data; + grub_err_t err; + + data = grub_malloc (sizeof (*data)); + if (!data) + return NULL; + + data->disk = disk; + + if (grub_f2fs_read_sb (data, F2FS_SUPER_OFFSET0)) + { + if (grub_f2fs_read_sb (data, F2FS_SUPER_OFFSET1)) + { + if (grub_errno == GRUB_ERR_NONE) + grub_error (GRUB_ERR_BAD_FS, + "not a F2FS filesystem (no superblock)"); + goto fail; + } + } + + data->root_ino = grub_le_to_cpu32 (data->sblock.root_ino); + data->cp_blkaddr = grub_le_to_cpu32 (data->sblock.cp_blkaddr); + data->nat_blkaddr = grub_le_to_cpu32 (data->sblock.nat_blkaddr); + data->blocks_per_seg = 1 << + grub_le_to_cpu32 (data->sblock.log_blocks_per_seg); + + err = grub_f2fs_read_cp (data); + if (err) + goto fail; + + data->nat_bitmap = nat_bitmap_ptr (data); + + err = get_nat_journal (data); + if (err) + goto fail; + + data->diropen.data = data; + data->diropen.ino = data->root_ino; + data->diropen.inode_read = 1; + data->inode = &data->diropen.inode; + + err = grub_f2fs_read_node (data, data->root_ino, data->inode); + if (err) + goto fail; + + return data; + + fail: + grub_free (data); + + return NULL; +} + +/* Guarantee inline_data was handled by caller. */ +static grub_disk_addr_t +grub_f2fs_get_block (grub_fshelp_node_t node, grub_disk_addr_t block_ofs) +{ + struct grub_f2fs_data *data = node->data; + struct grub_f2fs_inode *inode = &node->inode.i; + grub_uint32_t offset[4], noffset[4], nids[4]; + struct grub_f2fs_node *node_block; + grub_uint32_t block_addr = -1; + int level, i; + + level = grub_get_node_path (inode, block_ofs, offset, noffset); + + if (level < 0) + return -1; + + if (level == 0) + return grub_le_to_cpu32 (inode->i_addr[offset[0]]); + + node_block = grub_malloc (F2FS_BLKSIZE); + if (!node_block) + return -1; + + nids[1] = get_node_id (&node->inode, offset[0], 1); + + /* Get indirect or direct nodes. */ + for (i = 1; i <= level; i++) + { + grub_f2fs_read_node (data, nids[i], node_block); + if (grub_errno) + goto fail; + + if (i < level) + nids[i + 1] = get_node_id (node_block, offset[i], 0); + } + + block_addr = grub_le_to_cpu32 (node_block->dn.addr[offset[level]]); + + fail: + grub_free (node_block); + + return block_addr; +} + +static grub_ssize_t +grub_f2fs_read_file (grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + struct grub_f2fs_inode *inode = &node->inode.i; + grub_off_t filesize = grub_f2fs_file_size (inode); + char *inline_addr = get_inline_addr (inode); + + if (inode->i_inline & F2FS_INLINE_DATA) + { + if (filesize > MAX_INLINE_DATA) + return -1; + + if (len > filesize - pos) + len = filesize - pos; + + grub_memcpy (buf, inline_addr + pos, len); + return len; + } + + return grub_fshelp_read_file (node->data->disk, node, + read_hook, read_hook_data, + pos, len, buf, grub_f2fs_get_block, + filesize, + F2FS_BLK_SEC_BITS, 0); +} + +static char * +grub_f2fs_read_symlink (grub_fshelp_node_t node) +{ + char *symlink; + struct grub_fshelp_node *diro = node; + grub_uint64_t filesize; + + if (!diro->inode_read) + { + grub_f2fs_read_node (diro->data, diro->ino, &diro->inode); + if (grub_errno) + return 0; + } + + filesize = grub_f2fs_file_size(&diro->inode.i); + + symlink = grub_malloc (filesize + 1); + if (!symlink) + return 0; + + grub_f2fs_read_file (diro, 0, 0, 0, filesize, symlink); + if (grub_errno) + { + grub_free (symlink); + return 0; + } + + symlink[filesize] = '\0'; + + return symlink; +} + +static int +grub_f2fs_check_dentries (struct grub_f2fs_dir_iter_ctx *ctx) +{ + struct grub_fshelp_node *fdiro; + int i; + + for (i = 0; i < ctx->max;) + { + char *filename; + enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN; + enum FILE_TYPE ftype; + int name_len; + int ret; + + if (grub_f2fs_test_bit_le (i, ctx->bitmap) == 0) + { + i++; + continue; + } + + ftype = ctx->dentry[i].file_type; + name_len = grub_le_to_cpu16 (ctx->dentry[i].name_len); + filename = grub_malloc (name_len + 1); + if (!filename) + return 0; + + grub_memcpy (filename, ctx->filename[i], name_len); + filename[name_len] = 0; + + fdiro = grub_malloc (sizeof (struct grub_fshelp_node)); + if (!fdiro) + { + grub_free(filename); + return 0; + } + + if (ftype == F2FS_FT_DIR) + type = GRUB_FSHELP_DIR; + else if (ftype == F2FS_FT_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + else if (ftype == F2FS_FT_REG_FILE) + type = GRUB_FSHELP_REG; + + fdiro->data = ctx->data; + fdiro->ino = grub_le_to_cpu32 (ctx->dentry[i].ino); + fdiro->inode_read = 0; + + ret = ctx->hook (filename, type, fdiro, ctx->hook_data); + grub_free(filename); + if (ret) + return 1; + + i += (name_len + F2FS_SLOT_LEN - 1) / F2FS_SLOT_LEN; + } + + return 0; +} + +static int +grub_f2fs_iterate_inline_dir (struct grub_f2fs_inode *dir, + struct grub_f2fs_dir_iter_ctx *ctx) +{ + struct grub_f2fs_inline_dentry *de_blk; + + de_blk = (struct grub_f2fs_inline_dentry *) get_inline_addr (dir); + + ctx->bitmap = de_blk->dentry_bitmap; + ctx->dentry = de_blk->dentry; + ctx->filename = de_blk->filename; + ctx->max = NR_INLINE_DENTRY; + + return grub_f2fs_check_dentries (ctx); +} + +static int +grub_f2fs_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + struct grub_fshelp_node *diro = (struct grub_fshelp_node *) dir; + struct grub_f2fs_inode *inode; + struct grub_f2fs_dir_iter_ctx ctx = { + .data = diro->data, + .hook = hook, + .hook_data = hook_data + }; + grub_off_t fpos = 0; + + if (!diro->inode_read) + { + grub_f2fs_read_node (diro->data, diro->ino, &diro->inode); + if (grub_errno) + return 0; + } + + inode = &diro->inode.i; + + if (inode->i_inline & F2FS_INLINE_DENTRY) + return grub_f2fs_iterate_inline_dir (inode, &ctx); + + while (fpos < grub_f2fs_file_size (inode)) + { + struct grub_f2fs_dentry_block *de_blk; + char *buf; + int ret; + + buf = grub_zalloc (F2FS_BLKSIZE); + if (!buf) + return 0; + + grub_f2fs_read_file (diro, 0, 0, fpos, F2FS_BLKSIZE, buf); + if (grub_errno) + { + grub_free (buf); + return 0; + } + + de_blk = (struct grub_f2fs_dentry_block *) buf; + + ctx.bitmap = de_blk->dentry_bitmap; + ctx.dentry = de_blk->dentry; + ctx.filename = de_blk->filename; + ctx.max = NR_DENTRY_IN_BLOCK; + + ret = grub_f2fs_check_dentries (&ctx); + grub_free (buf); + if (ret) + return 1; + + fpos += F2FS_BLKSIZE; + } + + return 0; +} + +static int +grub_f2fs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_f2fs_dir_ctx *ctx = data; + struct grub_dirhook_info info; + + grub_memset (&info, 0, sizeof (info)); + if (!node->inode_read) + { + grub_f2fs_read_node (ctx->data, node->ino, &node->inode); + if (!grub_errno) + node->inode_read = 1; + grub_errno = GRUB_ERR_NONE; + } + if (node->inode_read) + { + info.mtimeset = 1; + info.mtime = grub_le_to_cpu64 (node->inode.i.i_mtime); + } + + info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); + grub_free (node); + + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_f2fs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_f2fs_dir_ctx ctx = { + .hook = hook, + .hook_data = hook_data + }; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + ctx.data = grub_f2fs_mount (device->disk); + if (!ctx.data) + goto fail; + + grub_fshelp_find_file (path, &ctx.data->diropen, &fdiro, + grub_f2fs_iterate_dir, grub_f2fs_read_symlink, + GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + + grub_f2fs_iterate_dir (fdiro, grub_f2fs_dir_iter, &ctx); + + fail: + if (fdiro != &ctx.data->diropen) + grub_free (fdiro); + grub_free (ctx.data); + grub_dl_unref (my_mod); + + return grub_errno; +} + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_f2fs_open (struct grub_file *file, const char *name) +{ + struct grub_f2fs_data *data = NULL; + struct grub_fshelp_node *fdiro = 0; + struct grub_f2fs_inode *inode; + + grub_dl_ref (my_mod); + + data = grub_f2fs_mount (file->device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file (name, &data->diropen, &fdiro, + grub_f2fs_iterate_dir, grub_f2fs_read_symlink, + GRUB_FSHELP_REG); + if (grub_errno) + goto fail; + + if (!fdiro->inode_read) + { + grub_f2fs_read_node (data, fdiro->ino, &fdiro->inode); + if (grub_errno) + goto fail; + } + + grub_memcpy (data->inode, &fdiro->inode, sizeof (*data->inode)); + grub_free (fdiro); + + inode = &(data->inode->i); + file->size = grub_f2fs_file_size (inode); + file->data = data; + file->offset = 0; + + if (inode->i_inline & F2FS_INLINE_DATA && file->size > MAX_INLINE_DATA) + grub_error (GRUB_ERR_BAD_FS, "corrupted inline_data: need fsck"); + + return 0; + + fail: + if (fdiro != &data->diropen) + grub_free (fdiro); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_ssize_t +grub_f2fs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_f2fs_data *data = (struct grub_f2fs_data *) file->data; + + return grub_f2fs_read_file (&data->diropen, + file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + +static grub_err_t +grub_f2fs_close (grub_file_t file) +{ + struct grub_f2fs_data *data = (struct grub_f2fs_data *) file->data; + + grub_free (data); + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + +static grub_uint8_t * +grub_f2fs_utf16_to_utf8 (grub_uint16_t *in_buf_le) +{ + grub_uint16_t in_buf[MAX_VOLUME_NAME]; + grub_uint8_t *out_buf; + int len = 0; + + out_buf = grub_malloc (MAX_VOLUME_NAME * GRUB_MAX_UTF8_PER_UTF16 + 1); + if (!out_buf) + return NULL; + + while (*in_buf_le != 0 && len < MAX_VOLUME_NAME) { + in_buf[len] = grub_le_to_cpu16 (in_buf_le[len]); + len++; + } + + *grub_utf16_to_utf8 (out_buf, in_buf, len) = '\0'; + + return out_buf; +} + +#if __GNUC__ >= 9 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" +#endif + +static grub_err_t +grub_f2fs_label (grub_device_t device, char **label) +{ + struct grub_f2fs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_f2fs_mount (disk); + if (data) + *label = (char *) grub_f2fs_utf16_to_utf8 (data->sblock.volume_name); + else + *label = NULL; + + grub_free (data); + grub_dl_unref (my_mod); + + return grub_errno; +} + +#if __GNUC__ >= 9 +#pragma GCC diagnostic pop +#endif + +static grub_err_t +grub_f2fs_uuid (grub_device_t device, char **uuid) +{ + struct grub_f2fs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_f2fs_mount (disk); + if (data) + { + *uuid = + grub_xasprintf + ("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + data->sblock.uuid[0], data->sblock.uuid[1], + data->sblock.uuid[2], data->sblock.uuid[3], + data->sblock.uuid[4], data->sblock.uuid[5], + data->sblock.uuid[6], data->sblock.uuid[7], + data->sblock.uuid[8], data->sblock.uuid[9], + data->sblock.uuid[10], data->sblock.uuid[11], + data->sblock.uuid[12], data->sblock.uuid[13], + data->sblock.uuid[14], data->sblock.uuid[15]); + } + else + *uuid = NULL; + + grub_free (data); + grub_dl_unref (my_mod); + + return grub_errno; +} + +static struct grub_fs grub_f2fs_fs = { + .name = "f2fs", + .fs_dir = grub_f2fs_dir, + .fs_open = grub_f2fs_open, + .fs_read = grub_f2fs_read, + .fs_close = grub_f2fs_close, + .fs_label = grub_f2fs_label, + .fs_uuid = grub_f2fs_uuid, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 0, +#endif + .next = 0 +}; + +GRUB_MOD_INIT (f2fs) +{ + grub_fs_register (&grub_f2fs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI (f2fs) +{ + grub_fs_unregister (&grub_f2fs_fs); +} diff --git a/grub-core/fs/fat.c b/grub-core/fs/fat.c new file mode 100644 index 0000000..dd82e4e --- /dev/null +++ b/grub-core/fs/fat.c @@ -0,0 +1,1329 @@ +/* fat.c - FAT filesystem */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,2004,2005,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/fs.h> +#include <grub/disk.h> +#include <grub/file.h> +#include <grub/types.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/err.h> +#include <grub/dl.h> +#include <grub/charset.h> +#include <grub/datetime.h> +#ifndef MODE_EXFAT +#include <grub/fat.h> +#else +#include <grub/exfat.h> +#endif +#include <grub/fshelp.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +enum + { + GRUB_FAT_ATTR_READ_ONLY = 0x01, + GRUB_FAT_ATTR_HIDDEN = 0x02, + GRUB_FAT_ATTR_SYSTEM = 0x04, +#ifndef MODE_EXFAT + GRUB_FAT_ATTR_VOLUME_ID = 0x08, +#endif + GRUB_FAT_ATTR_DIRECTORY = 0x10, + GRUB_FAT_ATTR_ARCHIVE = 0x20, + +#ifndef MODE_EXFAT + GRUB_FAT_ATTR_LONG_NAME = (GRUB_FAT_ATTR_READ_ONLY + | GRUB_FAT_ATTR_HIDDEN + | GRUB_FAT_ATTR_SYSTEM + | GRUB_FAT_ATTR_VOLUME_ID), +#endif + GRUB_FAT_ATTR_VALID = (GRUB_FAT_ATTR_READ_ONLY + | GRUB_FAT_ATTR_HIDDEN + | GRUB_FAT_ATTR_SYSTEM + | GRUB_FAT_ATTR_DIRECTORY + | GRUB_FAT_ATTR_ARCHIVE +#ifndef MODE_EXFAT + | GRUB_FAT_ATTR_VOLUME_ID +#endif + ) + }; + +#ifdef MODE_EXFAT +typedef struct grub_exfat_bpb grub_current_fat_bpb_t; +#else +typedef struct grub_fat_bpb grub_current_fat_bpb_t; +#endif + +#ifdef MODE_EXFAT +enum + { + FLAG_CONTIGUOUS = 2 + }; +struct grub_fat_dir_entry +{ + grub_uint8_t entry_type; + union + { + grub_uint8_t placeholder[31]; + struct { + grub_uint8_t secondary_count; + grub_uint16_t checksum; + grub_uint16_t attr; + grub_uint16_t reserved1; + grub_uint32_t c_time; + grub_uint32_t m_time; + grub_uint32_t a_time; + grub_uint8_t c_time_tenth; + grub_uint8_t m_time_tenth; + grub_uint8_t a_time_tenth; + grub_uint8_t reserved2[9]; + } GRUB_PACKED file; + struct { + grub_uint8_t flags; + grub_uint8_t reserved1; + grub_uint8_t name_length; + grub_uint16_t name_hash; + grub_uint16_t reserved2; + grub_uint64_t valid_size; + grub_uint32_t reserved3; + grub_uint32_t first_cluster; + grub_uint64_t file_size; + } GRUB_PACKED stream_extension; + struct { + grub_uint8_t flags; + grub_uint16_t str[15]; + } GRUB_PACKED file_name; + struct { + grub_uint8_t character_count; + grub_uint16_t str[15]; + } GRUB_PACKED volume_label; + } GRUB_PACKED type_specific; +} GRUB_PACKED; + +struct grub_fat_dir_node +{ + grub_uint32_t attr; + grub_uint32_t first_cluster; + grub_uint64_t file_size; + grub_uint64_t valid_size; + int have_stream; + int is_contiguous; +}; + +typedef struct grub_fat_dir_node grub_fat_dir_node_t; + +#else +struct grub_fat_dir_entry +{ + grub_uint8_t name[11]; + grub_uint8_t attr; + grub_uint8_t nt_reserved; + grub_uint8_t c_time_tenth; + grub_uint16_t c_time; + grub_uint16_t c_date; + grub_uint16_t a_date; + grub_uint16_t first_cluster_high; + grub_uint16_t w_time; + grub_uint16_t w_date; + grub_uint16_t first_cluster_low; + grub_uint32_t file_size; +} GRUB_PACKED; + +struct grub_fat_long_name_entry +{ + grub_uint8_t id; + grub_uint16_t name1[5]; + grub_uint8_t attr; + grub_uint8_t reserved; + grub_uint8_t checksum; + grub_uint16_t name2[6]; + grub_uint16_t first_cluster; + grub_uint16_t name3[2]; +} GRUB_PACKED; + +typedef struct grub_fat_dir_entry grub_fat_dir_node_t; + +#endif + +struct grub_fat_data +{ + int logical_sector_bits; + grub_uint32_t num_sectors; + + grub_uint32_t fat_sector; + grub_uint32_t sectors_per_fat; + int fat_size; + + grub_uint32_t root_cluster; +#ifndef MODE_EXFAT + grub_uint32_t root_sector; + grub_uint32_t num_root_sectors; +#endif + + int cluster_bits; + grub_uint32_t cluster_eof_mark; + grub_uint32_t cluster_sector; + grub_uint32_t num_clusters; + + grub_uint32_t uuid; +}; + +struct grub_fshelp_node { + grub_disk_t disk; + struct grub_fat_data *data; + + grub_uint8_t attr; +#ifndef MODE_EXFAT + grub_uint32_t file_size; +#else + grub_uint64_t file_size; +#endif + grub_uint32_t file_cluster; + grub_uint32_t cur_cluster_num; + grub_uint32_t cur_cluster; + +#ifdef MODE_EXFAT + int is_contiguous; +#endif +}; + +static grub_dl_t my_mod; + +#ifndef MODE_EXFAT +static int +fat_log2 (unsigned x) +{ + int i; + + if (x == 0) + return -1; + + for (i = 0; (x & 1) == 0; i++) + x >>= 1; + + if (x != 1) + return -1; + + return i; +} +#endif + +static struct grub_fat_data * +grub_fat_mount (grub_disk_t disk) +{ + grub_current_fat_bpb_t bpb; + struct grub_fat_data *data = 0; + grub_uint32_t first_fat, magic; + + if (! disk) + goto fail; + + data = (struct grub_fat_data *) grub_malloc (sizeof (*data)); + if (! data) + goto fail; + + /* Read the BPB. */ + if (grub_disk_read (disk, 0, 0, sizeof (bpb), &bpb)) + goto fail; + +#ifdef MODE_EXFAT + if (grub_memcmp ((const char *) bpb.oem_name, "EXFAT ", + sizeof (bpb.oem_name)) != 0) + goto fail; +#endif + + /* Get the sizes of logical sectors and clusters. */ +#ifdef MODE_EXFAT + data->logical_sector_bits = bpb.bytes_per_sector_shift; +#else + data->logical_sector_bits = + fat_log2 (grub_le_to_cpu16 (bpb.bytes_per_sector)); +#endif + if (data->logical_sector_bits < GRUB_DISK_SECTOR_BITS + || data->logical_sector_bits >= 16) + goto fail; + data->logical_sector_bits -= GRUB_DISK_SECTOR_BITS; + +#ifdef MODE_EXFAT + data->cluster_bits = bpb.sectors_per_cluster_shift; +#else + data->cluster_bits = fat_log2 (bpb.sectors_per_cluster); +#endif + if (data->cluster_bits < 0 || data->cluster_bits > 25) + goto fail; + data->cluster_bits += data->logical_sector_bits; + + /* Get information about FATs. */ +#ifdef MODE_EXFAT + data->fat_sector = (grub_le_to_cpu32 (bpb.num_reserved_sectors) + << data->logical_sector_bits); +#else + data->fat_sector = (grub_le_to_cpu16 (bpb.num_reserved_sectors) + << data->logical_sector_bits); +#endif + if (data->fat_sector == 0) + goto fail; + +#ifdef MODE_EXFAT + data->sectors_per_fat = (grub_le_to_cpu32 (bpb.sectors_per_fat) + << data->logical_sector_bits); +#else + data->sectors_per_fat = ((bpb.sectors_per_fat_16 + ? grub_le_to_cpu16 (bpb.sectors_per_fat_16) + : grub_le_to_cpu32 (bpb.version_specific.fat32.sectors_per_fat_32)) + << data->logical_sector_bits); +#endif + if (data->sectors_per_fat == 0) + goto fail; + + /* Get the number of sectors in this volume. */ +#ifdef MODE_EXFAT + data->num_sectors = ((grub_le_to_cpu64 (bpb.num_total_sectors)) + << data->logical_sector_bits); +#else + data->num_sectors = ((bpb.num_total_sectors_16 + ? grub_le_to_cpu16 (bpb.num_total_sectors_16) + : grub_le_to_cpu32 (bpb.num_total_sectors_32)) + << data->logical_sector_bits); +#endif + if (data->num_sectors == 0) + goto fail; + + /* Get information about the root directory. */ + if (bpb.num_fats == 0) + goto fail; + +#ifndef MODE_EXFAT + data->root_sector = data->fat_sector + bpb.num_fats * data->sectors_per_fat; + data->num_root_sectors + = ((((grub_uint32_t) grub_le_to_cpu16 (bpb.num_root_entries) + * sizeof (struct grub_fat_dir_entry) + + grub_le_to_cpu16 (bpb.bytes_per_sector) - 1) + >> (data->logical_sector_bits + GRUB_DISK_SECTOR_BITS)) + << (data->logical_sector_bits)); +#endif + +#ifdef MODE_EXFAT + data->cluster_sector = (grub_le_to_cpu32 (bpb.cluster_offset) + << data->logical_sector_bits); + data->num_clusters = (grub_le_to_cpu32 (bpb.cluster_count) + << data->logical_sector_bits); +#else + data->cluster_sector = data->root_sector + data->num_root_sectors; + data->num_clusters = (((data->num_sectors - data->cluster_sector) + >> data->cluster_bits) + + 2); +#endif + + if (data->num_clusters <= 2) + goto fail; + +#ifdef MODE_EXFAT + { + /* exFAT. */ + data->root_cluster = grub_le_to_cpu32 (bpb.root_cluster); + data->fat_size = 32; + data->cluster_eof_mark = 0xffffffff; + + if ((bpb.volume_flags & grub_cpu_to_le16_compile_time (0x1)) + && bpb.num_fats > 1) + data->fat_sector += data->sectors_per_fat; + } +#else + if (! bpb.sectors_per_fat_16) + { + /* FAT32. */ + grub_uint16_t flags = grub_le_to_cpu16 (bpb.version_specific.fat32.extended_flags); + + data->root_cluster = grub_le_to_cpu32 (bpb.version_specific.fat32.root_cluster); + data->fat_size = 32; + data->cluster_eof_mark = 0x0ffffff8; + + if (flags & 0x80) + { + /* Get an active FAT. */ + unsigned active_fat = flags & 0xf; + + if (active_fat > bpb.num_fats) + goto fail; + + data->fat_sector += active_fat * data->sectors_per_fat; + } + + if (bpb.num_root_entries != 0 || bpb.version_specific.fat32.fs_version != 0) + goto fail; + } + else + { + /* FAT12 or FAT16. */ + data->root_cluster = ~0U; + + if (data->num_clusters <= 4085 + 2) + { + /* FAT12. */ + data->fat_size = 12; + data->cluster_eof_mark = 0x0ff8; + } + else + { + /* FAT16. */ + data->fat_size = 16; + data->cluster_eof_mark = 0xfff8; + } + } +#endif + + /* More sanity checks. */ + if (data->num_sectors <= data->fat_sector) + goto fail; + + if (grub_disk_read (disk, + data->fat_sector, + 0, + sizeof (first_fat), + &first_fat)) + goto fail; + + first_fat = grub_le_to_cpu32 (first_fat); + + if (data->fat_size == 32) + { + first_fat &= 0x0fffffff; + magic = 0x0fffff00; + } + else if (data->fat_size == 16) + { + first_fat &= 0x0000ffff; + magic = 0xff00; + } + else + { + first_fat &= 0x00000fff; + magic = 0x0f00; + } + + /* Serial number. */ +#ifdef MODE_EXFAT + data->uuid = grub_le_to_cpu32 (bpb.num_serial); +#else + if (bpb.sectors_per_fat_16) + data->uuid = grub_le_to_cpu32 (bpb.version_specific.fat12_or_fat16.num_serial); + else + data->uuid = grub_le_to_cpu32 (bpb.version_specific.fat32.num_serial); +#endif + +#ifndef MODE_EXFAT + /* Ignore the 3rd bit, because some BIOSes assigns 0xF0 to the media + descriptor, even if it is a so-called superfloppy (e.g. an USB key). + The check may be too strict for this kind of stupid BIOSes, as + they overwrite the media descriptor. */ + if ((first_fat | 0x8) != (magic | bpb.media | 0x8)) + goto fail; +#else + (void) magic; +#endif + + return data; + + fail: + + grub_free (data); + grub_error (GRUB_ERR_BAD_FS, "not a FAT filesystem"); + return 0; +} + +static grub_ssize_t +grub_fat_read_data (grub_disk_t disk, grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t offset, grub_size_t len, char *buf) +{ + grub_size_t size; + grub_uint32_t logical_cluster; + unsigned logical_cluster_bits; + grub_ssize_t ret = 0; + unsigned long sector; + +#ifndef MODE_EXFAT + /* This is a special case. FAT12 and FAT16 doesn't have the root directory + in clusters. */ + if (node->file_cluster == ~0U) + { + size = (node->data->num_root_sectors << GRUB_DISK_SECTOR_BITS) - offset; + if (size > len) + size = len; + + if (grub_disk_read (disk, node->data->root_sector, offset, size, buf)) + return -1; + + return size; + } +#endif + +#ifdef MODE_EXFAT + if (node->is_contiguous) + { + /* Read the data here. */ + sector = (node->data->cluster_sector + + ((node->file_cluster - 2) + << node->data->cluster_bits)); + + disk->read_hook = read_hook; + disk->read_hook_data = read_hook_data; + grub_disk_read (disk, sector + (offset >> GRUB_DISK_SECTOR_BITS), + offset & (GRUB_DISK_SECTOR_SIZE - 1), len, buf); + disk->read_hook = 0; + if (grub_errno) + return -1; + + return len; + } +#endif + + /* Calculate the logical cluster number and offset. */ + logical_cluster_bits = (node->data->cluster_bits + + GRUB_DISK_SECTOR_BITS); + logical_cluster = offset >> logical_cluster_bits; + offset &= (1ULL << logical_cluster_bits) - 1; + + if (logical_cluster < node->cur_cluster_num) + { + node->cur_cluster_num = 0; + node->cur_cluster = node->file_cluster; + } + + while (len) + { + while (logical_cluster > node->cur_cluster_num) + { + /* Find next cluster. */ + grub_uint32_t next_cluster; + grub_uint32_t fat_offset; + + switch (node->data->fat_size) + { + case 32: + fat_offset = node->cur_cluster << 2; + break; + case 16: + fat_offset = node->cur_cluster << 1; + break; + default: + /* case 12: */ + fat_offset = node->cur_cluster + (node->cur_cluster >> 1); + break; + } + + /* Read the FAT. */ + if (grub_disk_read (disk, node->data->fat_sector, fat_offset, + (node->data->fat_size + 7) >> 3, + (char *) &next_cluster)) + return -1; + + next_cluster = grub_le_to_cpu32 (next_cluster); + switch (node->data->fat_size) + { + case 16: + next_cluster &= 0xFFFF; + break; + case 12: + if (node->cur_cluster & 1) + next_cluster >>= 4; + + next_cluster &= 0x0FFF; + break; + } + + grub_dprintf ("fat", "fat_size=%d, next_cluster=%u\n", + node->data->fat_size, next_cluster); + + /* Check the end. */ + if (next_cluster >= node->data->cluster_eof_mark) + return ret; + + if (next_cluster < 2 || next_cluster >= node->data->num_clusters) + { + grub_error (GRUB_ERR_BAD_FS, "invalid cluster %u", + next_cluster); + return -1; + } + + node->cur_cluster = next_cluster; + node->cur_cluster_num++; + } + + /* Read the data here. */ + sector = (node->data->cluster_sector + + ((node->cur_cluster - 2) + << node->data->cluster_bits)); + size = (1 << logical_cluster_bits) - offset; + if (size > len) + size = len; + + disk->read_hook = read_hook; + disk->read_hook_data = read_hook_data; + grub_disk_read (disk, sector, offset, size, buf); + disk->read_hook = 0; + if (grub_errno) + return -1; + + len -= size; + buf += size; + ret += size; + logical_cluster++; + offset = 0; + } + + return ret; +} + +struct grub_fat_iterate_context +{ +#ifdef MODE_EXFAT + struct grub_fat_dir_node dir; + struct grub_fat_dir_entry entry; +#else + struct grub_fat_dir_entry dir; +#endif + char *filename; + grub_uint16_t *unibuf; + grub_ssize_t offset; +}; + +static grub_err_t +grub_fat_iterate_init (struct grub_fat_iterate_context *ctxt) +{ + ctxt->offset = -sizeof (struct grub_fat_dir_entry); + +#ifndef MODE_EXFAT + /* Allocate space enough to hold a long name. */ + ctxt->filename = grub_malloc (0x40 * 13 * GRUB_MAX_UTF8_PER_UTF16 + 1); + ctxt->unibuf = (grub_uint16_t *) grub_malloc (0x40 * 13 * 2); +#else + ctxt->unibuf = grub_malloc (15 * 256 * 2); + ctxt->filename = grub_malloc (15 * 256 * GRUB_MAX_UTF8_PER_UTF16 + 1); +#endif + + if (! ctxt->filename || ! ctxt->unibuf) + { + grub_free (ctxt->filename); + grub_free (ctxt->unibuf); + return grub_errno; + } + return GRUB_ERR_NONE; +} + +static void +grub_fat_iterate_fini (struct grub_fat_iterate_context *ctxt) +{ + grub_free (ctxt->filename); + grub_free (ctxt->unibuf); +} + +#ifdef MODE_EXFAT +static grub_err_t +grub_fat_iterate_dir_next (grub_fshelp_node_t node, + struct grub_fat_iterate_context *ctxt) +{ + grub_memset (&ctxt->dir, 0, sizeof (ctxt->dir)); + while (1) + { + struct grub_fat_dir_entry *dir = &ctxt->entry; + + ctxt->offset += sizeof (*dir); + + if (grub_fat_read_data (node->disk, node, 0, 0, ctxt->offset, sizeof (*dir), + (char *) dir) + != sizeof (*dir)) + break; + + if (dir->entry_type == 0) + break; + if (!(dir->entry_type & 0x80)) + continue; + + if (dir->entry_type == 0x85) + { + unsigned i, nsec, slots = 0; + + nsec = dir->type_specific.file.secondary_count; + + ctxt->dir.attr = grub_cpu_to_le16 (dir->type_specific.file.attr); + ctxt->dir.have_stream = 0; + for (i = 0; i < nsec; i++) + { + struct grub_fat_dir_entry sec; + ctxt->offset += sizeof (sec); + if (grub_fat_read_data (node->disk, node, 0, 0, + ctxt->offset, sizeof (sec), (char *) &sec) + != sizeof (sec)) + break; + if (!(sec.entry_type & 0x80)) + continue; + if (!(sec.entry_type & 0x40)) + break; + switch (sec.entry_type) + { + case 0xc0: + ctxt->dir.first_cluster = grub_cpu_to_le32 (sec.type_specific.stream_extension.first_cluster); + ctxt->dir.valid_size + = grub_cpu_to_le64 (sec.type_specific.stream_extension.valid_size); + ctxt->dir.file_size + = grub_cpu_to_le64 (sec.type_specific.stream_extension.file_size); + ctxt->dir.have_stream = 1; + ctxt->dir.is_contiguous = !!(sec.type_specific.stream_extension.flags + & grub_cpu_to_le16_compile_time (FLAG_CONTIGUOUS)); + break; + case 0xc1: + { + int j; + for (j = 0; j < 15; j++) + ctxt->unibuf[slots * 15 + j] + = grub_le_to_cpu16 (sec.type_specific.file_name.str[j]); + slots++; + } + break; + default: + grub_dprintf ("exfat", "unknown secondary type 0x%02x\n", + sec.entry_type); + } + } + + if (i != nsec) + { + ctxt->offset -= sizeof (*dir); + continue; + } + + *grub_utf16_to_utf8 ((grub_uint8_t *) ctxt->filename, ctxt->unibuf, + slots * 15) = '\0'; + + return 0; + } + /* Allocation bitmap. */ + if (dir->entry_type == 0x81) + continue; + /* Upcase table. */ + if (dir->entry_type == 0x82) + continue; + /* Volume label. */ + if (dir->entry_type == 0x83) + continue; + grub_dprintf ("exfat", "unknown primary type 0x%02x\n", + dir->entry_type); + } + return grub_errno ? : GRUB_ERR_EOF; +} + +/* + * Convert a timestamp in exFAT format to seconds since the UNIX epoch + * according to sections 7.4.8 and 7.4.9 in the exFAT specification. + * https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification + */ +static int +grub_exfat_timestamp (grub_uint32_t field, grub_uint8_t msec, grub_int64_t *nix) { + struct grub_datetime datetime = { + .year = (field >> 25) + 1980, + .month = (field & 0x01E00000) >> 21, + .day = (field & 0x001F0000) >> 16, + .hour = (field & 0x0000F800) >> 11, + .minute = (field & 0x000007E0) >> 5, + .second = (field & 0x0000001F) * 2 + (msec >= 100 ? 1 : 0), + }; + + /* The conversion below allows seconds=60, so don't trust its validation. */ + if ((field & 0x1F) > 29) + return 0; + + /* Validate the 10-msec field even though it is rounded down to seconds. */ + if (msec > 199) + return 0; + + return grub_datetime2unixtime (&datetime, nix); +} + +#else + +static grub_err_t +grub_fat_iterate_dir_next (grub_fshelp_node_t node, + struct grub_fat_iterate_context *ctxt) +{ + char *filep = 0; + int checksum = -1; + int slot = -1, slots = -1; + + while (1) + { + unsigned i; + + /* Adjust the offset. */ + ctxt->offset += sizeof (ctxt->dir); + + /* Read a directory entry. */ + if (grub_fat_read_data (node->disk, node, 0, 0, + ctxt->offset, sizeof (ctxt->dir), + (char *) &ctxt->dir) + != sizeof (ctxt->dir) || ctxt->dir.name[0] == 0) + break; + + /* Handle long name entries. */ + if (ctxt->dir.attr == GRUB_FAT_ATTR_LONG_NAME) + { + struct grub_fat_long_name_entry *long_name + = (struct grub_fat_long_name_entry *) &ctxt->dir; + grub_uint8_t id = long_name->id; + + if (id & 0x40) + { + id &= 0x3f; + slots = slot = id; + checksum = long_name->checksum; + } + + if (id != slot || slot == 0 || checksum != long_name->checksum) + { + checksum = -1; + continue; + } + + slot--; + grub_memcpy (ctxt->unibuf + slot * 13, long_name->name1, 5 * 2); + grub_memcpy (ctxt->unibuf + slot * 13 + 5, long_name->name2, 6 * 2); + grub_memcpy (ctxt->unibuf + slot * 13 + 11, long_name->name3, 2 * 2); + continue; + } + + /* Check if this entry is valid. */ + if (ctxt->dir.name[0] == 0xe5 || (ctxt->dir.attr & ~GRUB_FAT_ATTR_VALID)) + continue; + + /* This is a workaround for Japanese. */ + if (ctxt->dir.name[0] == 0x05) + ctxt->dir.name[0] = 0xe5; + + if (checksum != -1 && slot == 0) + { + grub_uint8_t sum; + + for (sum = 0, i = 0; i < sizeof (ctxt->dir.name); i++) + sum = ((sum >> 1) | (sum << 7)) + ctxt->dir.name[i]; + + if (sum == checksum) + { + int u; + + for (u = 0; u < slots * 13; u++) + ctxt->unibuf[u] = grub_le_to_cpu16 (ctxt->unibuf[u]); + + *grub_utf16_to_utf8 ((grub_uint8_t *) ctxt->filename, + ctxt->unibuf, + slots * 13) = '\0'; + + return GRUB_ERR_NONE; + } + + checksum = -1; + } + + /* Convert the 8.3 file name. */ + filep = ctxt->filename; + if (ctxt->dir.attr & GRUB_FAT_ATTR_VOLUME_ID) + { + for (i = 0; i < sizeof (ctxt->dir.name) && ctxt->dir.name[i]; i++) + *filep++ = ctxt->dir.name[i]; + while (i > 0 && ctxt->dir.name[i - 1] == ' ') + { + filep--; + i--; + } + } + else + { + for (i = 0; i < 8 && ctxt->dir.name[i]; i++) + *filep++ = grub_tolower (ctxt->dir.name[i]); + while (i > 0 && ctxt->dir.name[i - 1] == ' ') + { + filep--; + i--; + } + + /* XXX should we check that dir position is 0 or 1? */ + if (i > 2 || filep[0] != '.' || (i == 2 && filep[1] != '.')) + *filep++ = '.'; + + for (i = 8; i < 11 && ctxt->dir.name[i]; i++) + *filep++ = grub_tolower (ctxt->dir.name[i]); + while (i > 8 && ctxt->dir.name[i - 1] == ' ') + { + filep--; + i--; + } + + if (i == 8) + filep--; + } + *filep = '\0'; + return GRUB_ERR_NONE; + } + + return grub_errno ? : GRUB_ERR_EOF; +} + +/* + * Convert a date and time in FAT format to seconds since the UNIX epoch + * according to sections 11.3.5 and 11.3.6 in ECMA-107. + * https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-107.pdf + */ +static int +grub_fat_timestamp (grub_uint16_t time, grub_uint16_t date, grub_int64_t *nix) { + struct grub_datetime datetime = { + .year = (date >> 9) + 1980, + .month = (date & 0x01E0) >> 5, + .day = (date & 0x001F), + .hour = (time >> 11), + .minute = (time & 0x07E0) >> 5, + .second = (time & 0x001F) * 2, + }; + + /* The conversion below allows seconds=60, so don't trust its validation. */ + if ((time & 0x1F) > 29) + return 0; + + return grub_datetime2unixtime (&datetime, nix); +} + +#endif + +static grub_err_t lookup_file (grub_fshelp_node_t node, + const char *name, + grub_fshelp_node_t *foundnode, + enum grub_fshelp_filetype *foundtype) +{ + grub_err_t err; + struct grub_fat_iterate_context ctxt; + + err = grub_fat_iterate_init (&ctxt); + if (err) + return err; + + while (!(err = grub_fat_iterate_dir_next (node, &ctxt))) + { + +#ifdef MODE_EXFAT + if (!ctxt.dir.have_stream) + continue; +#else + if (ctxt.dir.attr & GRUB_FAT_ATTR_VOLUME_ID) + continue; +#endif + + if (grub_strcasecmp (name, ctxt.filename) == 0) + { + *foundnode = grub_malloc (sizeof (struct grub_fshelp_node)); + if (!*foundnode) + return grub_errno; + (*foundnode)->attr = ctxt.dir.attr; +#ifdef MODE_EXFAT + (*foundnode)->file_size = ctxt.dir.file_size; + (*foundnode)->file_cluster = ctxt.dir.first_cluster; + (*foundnode)->is_contiguous = ctxt.dir.is_contiguous; +#else + (*foundnode)->file_size = grub_le_to_cpu32 (ctxt.dir.file_size); + (*foundnode)->file_cluster = ((grub_le_to_cpu16 (ctxt.dir.first_cluster_high) << 16) + | grub_le_to_cpu16 (ctxt.dir.first_cluster_low)); + /* If directory points to root, starting cluster is 0 */ + if (!(*foundnode)->file_cluster) + (*foundnode)->file_cluster = node->data->root_cluster; +#endif + (*foundnode)->cur_cluster_num = ~0U; + (*foundnode)->data = node->data; + (*foundnode)->disk = node->disk; + + *foundtype = ((*foundnode)->attr & GRUB_FAT_ATTR_DIRECTORY) ? GRUB_FSHELP_DIR : GRUB_FSHELP_REG; + + grub_fat_iterate_fini (&ctxt); + return GRUB_ERR_NONE; + } + } + + grub_fat_iterate_fini (&ctxt); + if (err == GRUB_ERR_EOF) + err = 0; + + return err; + +} + +static grub_err_t +grub_fat_dir (grub_device_t device, const char *path, grub_fs_dir_hook_t hook, + void *hook_data) +{ + struct grub_fat_data *data = 0; + grub_disk_t disk = device->disk; + grub_fshelp_node_t found = NULL; + grub_err_t err; + struct grub_fat_iterate_context ctxt; + + grub_dl_ref (my_mod); + + data = grub_fat_mount (disk); + if (! data) + goto fail; + + struct grub_fshelp_node root = { + .data = data, + .disk = disk, + .attr = GRUB_FAT_ATTR_DIRECTORY, + .file_size = 0, + .file_cluster = data->root_cluster, + .cur_cluster_num = ~0U, + .cur_cluster = 0, +#ifdef MODE_EXFAT + .is_contiguous = 0, +#endif + }; + + err = grub_fshelp_find_file_lookup (path, &root, &found, lookup_file, NULL, GRUB_FSHELP_DIR); + if (err) + goto fail; + + err = grub_fat_iterate_init (&ctxt); + if (err) + goto fail; + + while (!(err = grub_fat_iterate_dir_next (found, &ctxt))) + { + struct grub_dirhook_info info; + grub_memset (&info, 0, sizeof (info)); + + info.dir = !! (ctxt.dir.attr & GRUB_FAT_ATTR_DIRECTORY); + info.case_insensitive = 1; +#ifdef MODE_EXFAT + if (!ctxt.dir.have_stream) + continue; + info.mtimeset = grub_exfat_timestamp (grub_le_to_cpu32 (ctxt.entry.type_specific.file.m_time), + ctxt.entry.type_specific.file.m_time_tenth, + &info.mtime); +#else + if (ctxt.dir.attr & GRUB_FAT_ATTR_VOLUME_ID) + continue; + info.mtimeset = grub_fat_timestamp (grub_le_to_cpu16 (ctxt.dir.w_time), + grub_le_to_cpu16 (ctxt.dir.w_date), + &info.mtime); +#endif + if (info.mtimeset == 0) + grub_error (GRUB_ERR_OUT_OF_RANGE, + "invalid modification timestamp for %s", path); + + if (hook (ctxt.filename, &info, hook_data)) + break; + } + grub_fat_iterate_fini (&ctxt); + if (err == GRUB_ERR_EOF) + err = 0; + + fail: + if (found != &root) + grub_free (found); + + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_fat_open (grub_file_t file, const char *name) +{ + struct grub_fat_data *data = 0; + grub_fshelp_node_t found = NULL; + grub_err_t err; + grub_disk_t disk = file->device->disk; + + grub_dl_ref (my_mod); + + data = grub_fat_mount (disk); + if (! data) + goto fail; + + struct grub_fshelp_node root = { + .data = data, + .disk = disk, + .attr = GRUB_FAT_ATTR_DIRECTORY, + .file_size = 0, + .file_cluster = data->root_cluster, + .cur_cluster_num = ~0U, + .cur_cluster = 0, +#ifdef MODE_EXFAT + .is_contiguous = 0, +#endif + }; + + err = grub_fshelp_find_file_lookup (name, &root, &found, lookup_file, NULL, GRUB_FSHELP_REG); + if (err) + goto fail; + + file->data = found; + file->size = found->file_size; + + return GRUB_ERR_NONE; + + fail: + + if (found != &root) + grub_free (found); + + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_ssize_t +grub_fat_read (grub_file_t file, char *buf, grub_size_t len) +{ + return grub_fat_read_data (file->device->disk, file->data, + file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + +static grub_err_t +grub_fat_close (grub_file_t file) +{ + grub_fshelp_node_t node = file->data; + + grub_free (node->data); + grub_free (node); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +#ifdef MODE_EXFAT +static grub_err_t +grub_fat_label (grub_device_t device, char **label) +{ + struct grub_fat_dir_entry dir; + grub_ssize_t offset = -sizeof(dir); + grub_disk_t disk = device->disk; + struct grub_fshelp_node root = { + .disk = disk, + .attr = GRUB_FAT_ATTR_DIRECTORY, + .file_size = 0, + .cur_cluster_num = ~0U, + .cur_cluster = 0, + .is_contiguous = 0, + }; + + root.data = grub_fat_mount (disk); + if (! root.data) + return grub_errno; + + root.file_cluster = root.data->root_cluster; + + *label = NULL; + + while (1) + { + offset += sizeof (dir); + + if (grub_fat_read_data (disk, &root, 0, 0, + offset, sizeof (dir), (char *) &dir) + != sizeof (dir)) + break; + + if (dir.entry_type == 0) + break; + if (!(dir.entry_type & 0x80)) + continue; + + /* Volume label. */ + if (dir.entry_type == 0x83) + { + grub_size_t chc; + grub_uint16_t t[ARRAY_SIZE (dir.type_specific.volume_label.str)]; + grub_size_t i; + *label = grub_malloc (ARRAY_SIZE (dir.type_specific.volume_label.str) + * GRUB_MAX_UTF8_PER_UTF16 + 1); + if (!*label) + { + grub_free (root.data); + return grub_errno; + } + chc = dir.type_specific.volume_label.character_count; + if (chc > ARRAY_SIZE (dir.type_specific.volume_label.str)) + chc = ARRAY_SIZE (dir.type_specific.volume_label.str); + for (i = 0; i < chc; i++) + t[i] = grub_le_to_cpu16 (dir.type_specific.volume_label.str[i]); + *grub_utf16_to_utf8 ((grub_uint8_t *) *label, t, chc) = '\0'; + } + } + + grub_free (root.data); + return grub_errno; +} + +#else + +static grub_err_t +grub_fat_label (grub_device_t device, char **label) +{ + grub_disk_t disk = device->disk; + grub_err_t err; + struct grub_fat_iterate_context ctxt; + struct grub_fshelp_node root = { + .disk = disk, + .attr = GRUB_FAT_ATTR_DIRECTORY, + .file_size = 0, + .cur_cluster_num = ~0U, + .cur_cluster = 0, + }; + + *label = 0; + + grub_dl_ref (my_mod); + + root.data = grub_fat_mount (disk); + if (! root.data) + goto fail; + + root.file_cluster = root.data->root_cluster; + + err = grub_fat_iterate_init (&ctxt); + if (err) + goto fail; + + while (!(err = grub_fat_iterate_dir_next (&root, &ctxt))) + if ((ctxt.dir.attr & ~GRUB_FAT_ATTR_ARCHIVE) == GRUB_FAT_ATTR_VOLUME_ID) + { + *label = grub_strdup (ctxt.filename); + break; + } + + grub_fat_iterate_fini (&ctxt); + + fail: + + grub_dl_unref (my_mod); + + grub_free (root.data); + + return grub_errno; +} + +#endif + +static grub_err_t +grub_fat_uuid (grub_device_t device, char **uuid) +{ + struct grub_fat_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_fat_mount (disk); + if (data) + { + char *ptr; + *uuid = grub_xasprintf ("%04x-%04x", + (grub_uint16_t) (data->uuid >> 16), + (grub_uint16_t) data->uuid); + for (ptr = *uuid; ptr && *ptr; ptr++) + *ptr = grub_toupper (*ptr); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +#ifdef GRUB_UTIL +#ifndef MODE_EXFAT +grub_disk_addr_t +grub_fat_get_cluster_sector (grub_disk_t disk, grub_uint64_t *sec_per_lcn) +#else +grub_disk_addr_t + grub_exfat_get_cluster_sector (grub_disk_t disk, grub_uint64_t *sec_per_lcn) +#endif +{ + grub_disk_addr_t ret; + struct grub_fat_data *data; + data = grub_fat_mount (disk); + if (!data) + return 0; + ret = data->cluster_sector; + + *sec_per_lcn = 1ULL << data->cluster_bits; + + grub_free (data); + return ret; +} +#endif + +static struct grub_fs grub_fat_fs = + { +#ifdef MODE_EXFAT + .name = "exfat", +#else + .name = "fat", +#endif + .fs_dir = grub_fat_dir, + .fs_open = grub_fat_open, + .fs_read = grub_fat_read, + .fs_close = grub_fat_close, + .fs_label = grub_fat_label, + .fs_uuid = grub_fat_uuid, +#ifdef GRUB_UTIL +#ifdef MODE_EXFAT + /* ExFAT BPB is 30 larger than FAT32 one. */ + .reserved_first_sector = 0, +#else + .reserved_first_sector = 1, +#endif + .blocklist_install = 1, +#endif + .next = 0 + }; + +#ifdef MODE_EXFAT +GRUB_MOD_INIT(exfat) +#else +GRUB_MOD_INIT(fat) +#endif +{ + COMPILE_TIME_ASSERT (sizeof (struct grub_fat_dir_entry) == 32); + grub_fs_register (&grub_fat_fs); + my_mod = mod; +} +#ifdef MODE_EXFAT +GRUB_MOD_FINI(exfat) +#else +GRUB_MOD_FINI(fat) +#endif +{ + grub_fs_unregister (&grub_fat_fs); +} + diff --git a/grub-core/fs/fshelp.c b/grub-core/fs/fshelp.c new file mode 100644 index 0000000..a2d0d29 --- /dev/null +++ b/grub-core/fs/fshelp.c @@ -0,0 +1,441 @@ +/* fshelp.c -- Filesystem helper functions */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,2005,2006,2007,2008 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/mm.h> +#include <grub/misc.h> +#include <grub/disk.h> +#include <grub/fshelp.h> +#include <grub/dl.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +typedef int (*iterate_dir_func) (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, + void *data); +typedef grub_err_t (*lookup_file_func) (grub_fshelp_node_t dir, + const char *name, + grub_fshelp_node_t *foundnode, + enum grub_fshelp_filetype *foundtype); +typedef char *(*read_symlink_func) (grub_fshelp_node_t node); + +struct stack_element { + struct stack_element *parent; + grub_fshelp_node_t node; + enum grub_fshelp_filetype type; +}; + +/* Context for grub_fshelp_find_file. */ +struct grub_fshelp_find_file_ctx +{ + /* Inputs. */ + const char *path; + grub_fshelp_node_t rootnode; + + /* Global options. */ + int symlinknest; + + /* Current file being traversed and its parents. */ + struct stack_element *currnode; +}; + +/* Helper for find_file_iter. */ +static void +free_node (grub_fshelp_node_t node, struct grub_fshelp_find_file_ctx *ctx) +{ + if (node != ctx->rootnode) + grub_free (node); +} + +static void +pop_element (struct grub_fshelp_find_file_ctx *ctx) +{ + struct stack_element *el; + el = ctx->currnode; + ctx->currnode = el->parent; + free_node (el->node, ctx); + grub_free (el); +} + +static void +free_stack (struct grub_fshelp_find_file_ctx *ctx) +{ + while (ctx->currnode) + pop_element (ctx); +} + +static void +go_up_a_level (struct grub_fshelp_find_file_ctx *ctx) +{ + if (!ctx->currnode->parent) + return; + pop_element (ctx); +} + +static grub_err_t +push_node (struct grub_fshelp_find_file_ctx *ctx, grub_fshelp_node_t node, enum grub_fshelp_filetype filetype) +{ + struct stack_element *nst; + nst = grub_malloc (sizeof (*nst)); + if (!nst) + return grub_errno; + nst->node = node; + nst->type = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE; + nst->parent = ctx->currnode; + ctx->currnode = nst; + return GRUB_ERR_NONE; +} + +static grub_err_t +go_to_root (struct grub_fshelp_find_file_ctx *ctx) +{ + free_stack (ctx); + return push_node (ctx, ctx->rootnode, GRUB_FSHELP_DIR); +} + +struct grub_fshelp_find_file_iter_ctx +{ + const char *name; + grub_fshelp_node_t *foundnode; + enum grub_fshelp_filetype *foundtype; +}; + +/* Helper for grub_fshelp_find_file. */ +static int +find_file_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_fshelp_find_file_iter_ctx *ctx = data; + + if (filetype == GRUB_FSHELP_UNKNOWN || + ((filetype & GRUB_FSHELP_CASE_INSENSITIVE) + ? grub_strcasecmp (ctx->name, filename) + : grub_strcmp (ctx->name, filename))) + { + grub_free (node); + return 0; + } + + /* The node is found, stop iterating over the nodes. */ + *ctx->foundnode = node; + *ctx->foundtype = filetype; + return 1; +} + +static grub_err_t +directory_find_file (grub_fshelp_node_t node, const char *name, grub_fshelp_node_t *foundnode, + enum grub_fshelp_filetype *foundtype, iterate_dir_func iterate_dir) +{ + int found; + struct grub_fshelp_find_file_iter_ctx ctx = { + .foundnode = foundnode, + .foundtype = foundtype, + .name = name + }; + found = iterate_dir (node, find_file_iter, &ctx); + if (! found) + { + if (grub_errno) + return grub_errno; + } + return GRUB_ERR_NONE; +} + +static grub_err_t +find_file (char *currpath, + iterate_dir_func iterate_dir, lookup_file_func lookup_file, + read_symlink_func read_symlink, + struct grub_fshelp_find_file_ctx *ctx) +{ + char *name, *next; + grub_err_t err; + for (name = currpath; ; name = next) + { + char c; + grub_fshelp_node_t foundnode = NULL; + enum grub_fshelp_filetype foundtype = 0; + + /* Remove all leading slashes. */ + while (*name == '/') + name++; + + /* Found the node! */ + if (! *name) + return 0; + + /* Extract the actual part from the pathname. */ + for (next = name; *next && *next != '/'; next++); + + /* At this point it is expected that the current node is a + directory, check if this is true. */ + if (ctx->currnode->type != GRUB_FSHELP_DIR) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + + /* Don't rely on fs providing actual . in the listing. */ + if (next - name == 1 && name[0] == '.') + continue; + + /* Don't rely on fs providing actual .. in the listing. */ + if (next - name == 2 && name[0] == '.' && name[1] == '.') + { + go_up_a_level (ctx); + continue; + } + + /* Iterate over the directory. */ + c = *next; + *next = '\0'; + if (lookup_file) + err = lookup_file (ctx->currnode->node, name, &foundnode, &foundtype); + else + err = directory_find_file (ctx->currnode->node, name, &foundnode, &foundtype, iterate_dir); + *next = c; + + if (err) + return err; + + if (!foundnode) + break; + + push_node (ctx, foundnode, foundtype); + + /* Read in the symlink and follow it. */ + if (ctx->currnode->type == GRUB_FSHELP_SYMLINK) + { + char *symlink; + + /* Test if the symlink does not loop. */ + if (++ctx->symlinknest == 8) + return grub_error (GRUB_ERR_SYMLINK_LOOP, + N_("too deep nesting of symlinks")); + + symlink = read_symlink (ctx->currnode->node); + + if (!symlink) + return grub_errno; + + /* The symlink is an absolute path, go back to the root inode. */ + if (symlink[0] == '/') + { + err = go_to_root (ctx); + if (err) + return err; + } + else + { + /* Get from symlink to containing directory. */ + go_up_a_level (ctx); + } + + + /* Lookup the node the symlink points to. */ + find_file (symlink, iterate_dir, lookup_file, read_symlink, ctx); + grub_free (symlink); + + if (grub_errno) + return grub_errno; + } + } + + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), + ctx->path); +} + +static grub_err_t +grub_fshelp_find_file_real (const char *path, grub_fshelp_node_t rootnode, + grub_fshelp_node_t *foundnode, + iterate_dir_func iterate_dir, + lookup_file_func lookup_file, + read_symlink_func read_symlink, + enum grub_fshelp_filetype expecttype) +{ + struct grub_fshelp_find_file_ctx ctx = { + .path = path, + .rootnode = rootnode, + .symlinknest = 0, + .currnode = 0 + }; + grub_err_t err; + enum grub_fshelp_filetype foundtype; + char *duppath; + + if (!path || path[0] != '/') + { + return grub_error (GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), path); + } + + err = go_to_root (&ctx); + if (err) + return err; + + duppath = grub_strdup (path); + if (!duppath) + return grub_errno; + err = find_file (duppath, iterate_dir, lookup_file, read_symlink, &ctx); + grub_free (duppath); + if (err) + { + free_stack (&ctx); + return err; + } + + *foundnode = ctx.currnode->node; + foundtype = ctx.currnode->type; + /* Avoid the node being freed. */ + ctx.currnode->node = 0; + free_stack (&ctx); + + /* Check if the node that was found was of the expected type. */ + if (expecttype == GRUB_FSHELP_REG && foundtype != expecttype) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file")); + else if (expecttype == GRUB_FSHELP_DIR && foundtype != expecttype) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + + return 0; +} + +/* Lookup the node PATH. The node ROOTNODE describes the root of the + directory tree. The node found is returned in FOUNDNODE, which is + either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to + iterate over all directory entries in the current node. + READ_SYMLINK is used to read the symlink if a node is a symlink. + EXPECTTYPE is the type node that is expected by the called, an + error is generated if the node is not of the expected type. */ +grub_err_t +grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode, + grub_fshelp_node_t *foundnode, + iterate_dir_func iterate_dir, + read_symlink_func read_symlink, + enum grub_fshelp_filetype expecttype) +{ + return grub_fshelp_find_file_real (path, rootnode, foundnode, + iterate_dir, NULL, + read_symlink, expecttype); + +} + +grub_err_t +grub_fshelp_find_file_lookup (const char *path, grub_fshelp_node_t rootnode, + grub_fshelp_node_t *foundnode, + lookup_file_func lookup_file, + read_symlink_func read_symlink, + enum grub_fshelp_filetype expecttype) +{ + return grub_fshelp_find_file_real (path, rootnode, foundnode, + NULL, lookup_file, + read_symlink, expecttype); + +} + +/* Read LEN bytes from the file NODE on disk DISK into the buffer BUF, + beginning with the block POS. READ_HOOK should be set before + reading a block from the file. READ_HOOK_DATA is passed through as + the DATA argument to READ_HOOK. GET_BLOCK is used to translate + file blocks to disk blocks. The file is FILESIZE bytes big and the + blocks have a size of LOG2BLOCKSIZE (in log2). */ +grub_ssize_t +grub_fshelp_read_file (grub_disk_t disk, grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf, + grub_disk_addr_t (*get_block) (grub_fshelp_node_t node, + grub_disk_addr_t block), + grub_off_t filesize, int log2blocksize, + grub_disk_addr_t blocks_start) +{ + grub_disk_addr_t i, blockcnt; + int blocksize = 1 << (log2blocksize + GRUB_DISK_SECTOR_BITS); + + /* + * Catch blatantly invalid log2blocksize. We could be a lot stricter, but + * this is the most permissive we can be before we start to see integer + * overflow/underflow issues. + */ + if (log2blocksize + GRUB_DISK_SECTOR_BITS >= 31) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("blocksize too large")); + return -1; + } + + if (pos > filesize) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("attempt to read past the end of file")); + return -1; + } + + /* Adjust LEN so it we can't read past the end of the file. */ + if (pos + len > filesize) + len = filesize - pos; + + blockcnt = ((len + pos) + blocksize - 1) >> (log2blocksize + GRUB_DISK_SECTOR_BITS); + + for (i = pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS); i < blockcnt; i++) + { + grub_disk_addr_t blknr; + int blockoff = pos & (blocksize - 1); + int blockend = blocksize; + + int skipfirst = 0; + + blknr = get_block (node, i); + if (grub_errno) + return -1; + + blknr = blknr << log2blocksize; + + /* Last block. */ + if (i == blockcnt - 1) + { + blockend = (len + pos) & (blocksize - 1); + + /* The last portion is exactly blocksize. */ + if (! blockend) + blockend = blocksize; + } + + /* First block. */ + if (i == (pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS))) + { + skipfirst = blockoff; + blockend -= skipfirst; + } + + /* If the block number is 0 this block is not stored on disk but + is zero filled instead. */ + if (blknr) + { + disk->read_hook = read_hook; + disk->read_hook_data = read_hook_data; + + grub_disk_read (disk, blknr + blocks_start, skipfirst, + blockend, buf); + disk->read_hook = 0; + if (grub_errno) + return -1; + } + else + grub_memset (buf, 0, blockend); + + buf += blocksize - skipfirst; + } + + return len; +} diff --git a/grub-core/fs/hfs.c b/grub-core/fs/hfs.c new file mode 100644 index 0000000..f419965 --- /dev/null +++ b/grub-core/fs/hfs.c @@ -0,0 +1,1446 @@ +/* hfs.c - HFS. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,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/>. + */ + +/* HFS is documented at + http://developer.apple.com/documentation/mac/Files/Files-2.html */ + +#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/hfs.h> +#include <grub/i18n.h> +#include <grub/fshelp.h> +#include <grub/lockdown.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_HFS_SBLOCK 2 +#define GRUB_HFS_EMBED_HFSPLUS_SIG 0x482B + +#define GRUB_HFS_BLKS (data->blksz >> 9) + +#define GRUB_HFS_NODE_LEAF 0xFF + +/* The two supported filesystems a record can have. */ +enum + { + GRUB_HFS_FILETYPE_DIR = 1, + GRUB_HFS_FILETYPE_FILE = 2 + }; + +/* Catalog node ID (CNID). */ +enum grub_hfs_cnid_type + { + GRUB_HFS_CNID_ROOT_PARENT = 1, + GRUB_HFS_CNID_ROOT = 2, + GRUB_HFS_CNID_EXT = 3, + GRUB_HFS_CNID_CAT = 4, + GRUB_HFS_CNID_BAD = 5 + }; + +/* A node descriptor. This is the header of every node. */ +struct grub_hfs_node +{ + grub_uint32_t next; + grub_uint32_t prev; + grub_uint8_t type; + grub_uint8_t level; + grub_uint16_t reccnt; + grub_uint16_t unused; +} GRUB_PACKED; + +/* The head of the B*-Tree. */ +struct grub_hfs_treeheader +{ + grub_uint16_t tree_depth; + /* The number of the first node. */ + grub_uint32_t root_node; + grub_uint32_t leaves; + grub_uint32_t first_leaf; + grub_uint32_t last_leaf; + grub_uint16_t node_size; + grub_uint16_t key_size; + grub_uint32_t nodes; + grub_uint32_t free_nodes; + grub_uint8_t unused[76]; +} GRUB_PACKED; + +/* The state of a mounted HFS filesystem. */ +struct grub_hfs_data +{ + struct grub_hfs_sblock sblock; + grub_disk_t disk; + grub_hfs_datarecord_t extents; + int fileid; + int size; + int ext_root; + int ext_size; + int cat_root; + int cat_size; + int blksz; + int log2_blksz; + int rootdir; +}; + +/* The key as used on disk in a catalog tree. This is used to lookup + file/directory nodes by parent directory ID and filename. */ +struct grub_hfs_catalog_key +{ + grub_uint8_t unused; + grub_uint32_t parent_dir; + + /* Filename length. */ + grub_uint8_t strlen; + + /* Filename. */ + grub_uint8_t str[31]; +} GRUB_PACKED; + +/* The key as used on disk in a extent overflow tree. Using this key + the extents can be looked up using a fileid and logical start block + as index. */ +struct grub_hfs_extent_key +{ + /* The kind of fork. This is used to store meta information like + icons, attributes, etc. We will only use the datafork, which is + 0. */ + grub_uint8_t forktype; + grub_uint32_t fileid; + grub_uint16_t first_block; +} GRUB_PACKED; + +/* A directory record. This is used to find out the directory ID. */ +struct grub_hfs_dirrec +{ + /* For a directory, type == 1. */ + grub_uint8_t type; + grub_uint8_t unused[5]; + grub_uint32_t dirid; + grub_uint32_t ctime; + grub_uint32_t mtime; +} GRUB_PACKED; + +/* Information about a file. */ +struct grub_hfs_filerec +{ + /* For a file, type == 2. */ + grub_uint8_t type; + grub_uint8_t unused[19]; + grub_uint32_t fileid; + grub_uint8_t unused2[2]; + grub_uint32_t size; + grub_uint8_t unused3[18]; + grub_uint32_t mtime; + grub_uint8_t unused4[22]; + + /* The first 3 extents of the file. The other extents can be found + in the extent overflow file. */ + grub_hfs_datarecord_t extents; +} GRUB_PACKED; + +/* A record descriptor, both key and data, used to pass to call back + functions. */ +struct grub_hfs_record +{ + void *key; + grub_size_t keylen; + void *data; + grub_size_t datalen; +}; + +static grub_dl_t my_mod; + +static int grub_hfs_find_node (struct grub_hfs_data *, char *, + grub_uint32_t, int, char *, grub_size_t); + +/* Find block BLOCK of the file FILE in the mounted UFS filesystem + DATA. The first 3 extents are described by DAT. If cache is set, + using caching to improve non-random reads. */ +static unsigned int +grub_hfs_block (struct grub_hfs_data *data, grub_hfs_datarecord_t dat, + int file, int block, int cache) +{ + grub_hfs_datarecord_t dr; + int pos = 0; + struct grub_hfs_extent_key key; + + int tree = 0; + static int cache_file = 0; + static int cache_pos = 0; + static grub_hfs_datarecord_t cache_dr; + + grub_memcpy (dr, dat, sizeof (dr)); + + key.forktype = 0; + key.fileid = grub_cpu_to_be32 (file); + + if (cache && cache_file == file && block > cache_pos) + { + pos = cache_pos; + key.first_block = grub_cpu_to_be16 (pos); + grub_memcpy (dr, cache_dr, sizeof (cache_dr)); + } + + for (;;) + { + int i; + + /* Try all 3 extents. */ + for (i = 0; i < 3; i++) + { + /* Check if the block is stored in this extent. */ + if (grub_be_to_cpu16 (dr[i].count) + pos > block) + { + int first = grub_be_to_cpu16 (dr[i].first_block); + + /* If the cache is enabled, store the current position + in the tree. */ + if (tree && cache) + { + cache_file = file; + cache_pos = pos; + grub_memcpy (cache_dr, dr, sizeof (cache_dr)); + } + + return (grub_be_to_cpu16 (data->sblock.first_block) + + (first + block - pos) * GRUB_HFS_BLKS); + } + + /* Try the next extent. */ + pos += grub_be_to_cpu16 (dr[i].count); + } + + /* Lookup the block in the extent overflow file. */ + key.first_block = grub_cpu_to_be16 (pos); + tree = 1; + grub_hfs_find_node (data, (char *) &key, data->ext_root, + 1, (char *) &dr, sizeof (dr)); + if (grub_errno) + return 0; + } +} + + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +static grub_ssize_t +grub_hfs_read_file (struct grub_hfs_data *data, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_uint32_t pos, grub_size_t len, char *buf) +{ + grub_off_t i; + grub_off_t blockcnt; + + /* Files are at most 2G/4G - 1 bytes on hfs. Avoid 64-bit division. + Moreover len > 0 as checked in upper layer. */ + blockcnt = (len + pos - 1) / data->blksz + 1; + + for (i = pos / data->blksz; i < blockcnt; i++) + { + grub_disk_addr_t blknr; + grub_off_t blockoff; + grub_off_t blockend = data->blksz; + + int skipfirst = 0; + + blockoff = pos % data->blksz; + + blknr = grub_hfs_block (data, data->extents, data->fileid, i, 1); + if (grub_errno) + return -1; + + /* Last block. */ + if (i == blockcnt - 1) + { + blockend = (len + pos) % data->blksz; + + /* The last portion is exactly EXT2_BLOCK_SIZE (data). */ + if (! blockend) + blockend = data->blksz; + } + + /* First block. */ + if (i == pos / data->blksz) + { + skipfirst = blockoff; + blockend -= skipfirst; + } + + /* If the block number is 0 this block is not stored on disk but + is zero filled instead. */ + if (blknr) + { + data->disk->read_hook = read_hook; + data->disk->read_hook_data = read_hook_data; + grub_disk_read (data->disk, blknr, skipfirst, + blockend, buf); + data->disk->read_hook = 0; + if (grub_errno) + return -1; + } + + buf += data->blksz - skipfirst; + } + + return len; +} + + +/* Mount the filesystem on the disk DISK. */ +static struct grub_hfs_data * +grub_hfs_mount (grub_disk_t disk) +{ + struct grub_hfs_data *data; + struct grub_hfs_catalog_key key; + struct grub_hfs_dirrec dir; + int first_block; + + struct + { + struct grub_hfs_node node; + struct grub_hfs_treeheader head; + } treehead; + + data = grub_malloc (sizeof (struct grub_hfs_data)); + if (!data) + return 0; + + /* Read the superblock. */ + if (grub_disk_read (disk, GRUB_HFS_SBLOCK, 0, + sizeof (struct grub_hfs_sblock), &data->sblock)) + goto fail; + + /* Check if this is a HFS filesystem. */ + if (grub_be_to_cpu16 (data->sblock.magic) != GRUB_HFS_MAGIC + || data->sblock.blksz == 0 + || (data->sblock.blksz & grub_cpu_to_be32_compile_time (0xc00001ff))) + { + grub_error (GRUB_ERR_BAD_FS, "not an HFS filesystem"); + goto fail; + } + + /* Check if this is an embedded HFS+ filesystem. */ + if (grub_be_to_cpu16 (data->sblock.embed_sig) == GRUB_HFS_EMBED_HFSPLUS_SIG) + { + grub_error (GRUB_ERR_BAD_FS, "embedded HFS+ filesystem"); + goto fail; + } + + data->blksz = grub_be_to_cpu32 (data->sblock.blksz); + data->disk = disk; + + /* Lookup the root node of the extent overflow tree. */ + first_block = ((grub_be_to_cpu16 (data->sblock.extent_recs[0].first_block) + * GRUB_HFS_BLKS) + + grub_be_to_cpu16 (data->sblock.first_block)); + + if (grub_disk_read (data->disk, first_block, 0, + sizeof (treehead), &treehead)) + goto fail; + data->ext_root = grub_be_to_cpu32 (treehead.head.root_node); + data->ext_size = grub_be_to_cpu16 (treehead.head.node_size); + + /* Lookup the root node of the catalog tree. */ + first_block = ((grub_be_to_cpu16 (data->sblock.catalog_recs[0].first_block) + * GRUB_HFS_BLKS) + + grub_be_to_cpu16 (data->sblock.first_block)); + if (grub_disk_read (data->disk, first_block, 0, + sizeof (treehead), &treehead)) + goto fail; + data->cat_root = grub_be_to_cpu32 (treehead.head.root_node); + data->cat_size = grub_be_to_cpu16 (treehead.head.node_size); + + if (data->cat_size == 0 + || data->blksz < data->cat_size + || data->blksz < data->ext_size) + goto fail; + + /* Lookup the root directory node in the catalog tree using the + volume name. */ + key.parent_dir = grub_cpu_to_be32_compile_time (1); + key.strlen = data->sblock.volname[0]; + grub_strcpy ((char *) key.str, (char *) (data->sblock.volname + 1)); + + if (grub_hfs_find_node (data, (char *) &key, data->cat_root, + 0, (char *) &dir, sizeof (dir)) == 0) + { + grub_error (GRUB_ERR_BAD_FS, "cannot find the HFS root directory"); + goto fail; + } + + if (grub_errno) + goto fail; + + data->rootdir = grub_be_to_cpu32 (dir.dirid); + + return data; + fail: + grub_free (data); + + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a HFS filesystem"); + + return 0; +} + +/* Compare the K1 and K2 catalog file keys using HFS character ordering. */ +static int +grub_hfs_cmp_catkeys (const struct grub_hfs_catalog_key *k1, + const struct grub_hfs_catalog_key *k2) +{ + /* Taken from hfsutils 3.2.6 and converted to a readable form */ + static const unsigned char hfs_charorder[256] = { + [0x00] = 0, + [0x01] = 1, + [0x02] = 2, + [0x03] = 3, + [0x04] = 4, + [0x05] = 5, + [0x06] = 6, + [0x07] = 7, + [0x08] = 8, + [0x09] = 9, + [0x0A] = 10, + [0x0B] = 11, + [0x0C] = 12, + [0x0D] = 13, + [0x0E] = 14, + [0x0F] = 15, + [0x10] = 16, + [0x11] = 17, + [0x12] = 18, + [0x13] = 19, + [0x14] = 20, + [0x15] = 21, + [0x16] = 22, + [0x17] = 23, + [0x18] = 24, + [0x19] = 25, + [0x1A] = 26, + [0x1B] = 27, + [0x1C] = 28, + [0x1D] = 29, + [0x1E] = 30, + [0x1F] = 31, + [' '] = 32, [0xCA] = 32, + ['!'] = 33, + ['"'] = 34, + [0xD2] = 35, + [0xD3] = 36, + [0xC7] = 37, + [0xC8] = 38, + ['#'] = 39, + ['$'] = 40, + ['%'] = 41, + ['&'] = 42, + ['\''] = 43, + [0xD4] = 44, + [0xD5] = 45, + ['('] = 46, + [')'] = 47, + ['*'] = 48, + ['+'] = 49, + [','] = 50, + ['-'] = 51, + ['.'] = 52, + ['/'] = 53, + ['0'] = 54, + ['1'] = 55, + ['2'] = 56, + ['3'] = 57, + ['4'] = 58, + ['5'] = 59, + ['6'] = 60, + ['7'] = 61, + ['8'] = 62, + ['9'] = 63, + [':'] = 64, + [';'] = 65, + ['<'] = 66, + ['='] = 67, + ['>'] = 68, + ['?'] = 69, + ['@'] = 70, + ['A'] = 71, ['a'] = 71, + [0x88] = 72, [0xCB] = 72, + [0x80] = 73, [0x8A] = 73, + [0x8B] = 74, [0xCC] = 74, + [0x81] = 75, [0x8C] = 75, + [0xAE] = 76, [0xBE] = 76, + ['`'] = 77, + [0x87] = 78, + [0x89] = 79, + [0xBB] = 80, + ['B'] = 81, ['b'] = 81, + ['C'] = 82, ['c'] = 82, + [0x82] = 83, [0x8D] = 83, + ['D'] = 84, ['d'] = 84, + ['E'] = 85, ['e'] = 85, + [0x83] = 86, [0x8E] = 86, + [0x8F] = 87, + [0x90] = 88, + [0x91] = 89, + ['F'] = 90, ['f'] = 90, + ['G'] = 91, ['g'] = 91, + ['H'] = 92, ['h'] = 92, + ['I'] = 93, ['i'] = 93, + [0x92] = 94, + [0x93] = 95, + [0x94] = 96, + [0x95] = 97, + ['J'] = 98, ['j'] = 98, + ['K'] = 99, ['k'] = 99, + ['L'] = 100, ['l'] = 100, + ['M'] = 101, ['m'] = 101, + ['N'] = 102, ['n'] = 102, + [0x84] = 103, [0x96] = 103, + ['O'] = 104, ['o'] = 104, + [0x85] = 105, [0x9A] = 105, + [0x9B] = 106, [0xCD] = 106, + [0xAF] = 107, [0xBF] = 107, + [0xCE] = 108, [0xCF] = 108, + [0x97] = 109, + [0x98] = 110, + [0x99] = 111, + [0xBC] = 112, + ['P'] = 113, ['p'] = 113, + ['Q'] = 114, ['q'] = 114, + ['R'] = 115, ['r'] = 115, + ['S'] = 116, ['s'] = 116, + [0xA7] = 117, + ['T'] = 118, ['t'] = 118, + ['U'] = 119, ['u'] = 119, + [0x86] = 120, [0x9F] = 120, + [0x9C] = 121, + [0x9D] = 122, + [0x9E] = 123, + ['V'] = 124, ['v'] = 124, + ['W'] = 125, ['w'] = 125, + ['X'] = 126, ['x'] = 126, + ['Y'] = 127, ['y'] = 127, + [0xD8] = 128, + ['Z'] = 129, ['z'] = 129, + ['['] = 130, + ['\\'] = 131, + [']'] = 132, + ['^'] = 133, + ['_'] = 134, + ['{'] = 135, + ['|'] = 136, + ['}'] = 137, + ['~'] = 138, + [0x7F] = 139, + [0xA0] = 140, + [0xA1] = 141, + [0xA2] = 142, + [0xA3] = 143, + [0xA4] = 144, + [0xA5] = 145, + [0xA6] = 146, + [0xA8] = 147, + [0xA9] = 148, + [0xAA] = 149, + [0xAB] = 150, + [0xAC] = 151, + [0xAD] = 152, + [0xB0] = 153, + [0xB1] = 154, + [0xB2] = 155, + [0xB3] = 156, + [0xB4] = 157, + [0xB5] = 158, + [0xB6] = 159, + [0xB7] = 160, + [0xB8] = 161, + [0xB9] = 162, + [0xBA] = 163, + [0xBD] = 164, + [0xC0] = 165, + [0xC1] = 166, + [0xC2] = 167, + [0xC3] = 168, + [0xC4] = 169, + [0xC5] = 170, + [0xC6] = 171, + [0xC9] = 172, + [0xD0] = 173, + [0xD1] = 174, + [0xD6] = 175, + [0xD7] = 176, + [0xD9] = 177, + [0xDA] = 178, + [0xDB] = 179, + [0xDC] = 180, + [0xDD] = 181, + [0xDE] = 182, + [0xDF] = 183, + [0xE0] = 184, + [0xE1] = 185, + [0xE2] = 186, + [0xE3] = 187, + [0xE4] = 188, + [0xE5] = 189, + [0xE6] = 190, + [0xE7] = 191, + [0xE8] = 192, + [0xE9] = 193, + [0xEA] = 194, + [0xEB] = 195, + [0xEC] = 196, + [0xED] = 197, + [0xEE] = 198, + [0xEF] = 199, + [0xF0] = 200, + [0xF1] = 201, + [0xF2] = 202, + [0xF3] = 203, + [0xF4] = 204, + [0xF5] = 205, + [0xF6] = 206, + [0xF7] = 207, + [0xF8] = 208, + [0xF9] = 209, + [0xFA] = 210, + [0xFB] = 211, + [0xFC] = 212, + [0xFD] = 213, + [0xFE] = 214, + [0xFF] = 215, + }; + int i; + int cmp; + int minlen = (k1->strlen < k2->strlen) ? k1->strlen : k2->strlen; + + cmp = (grub_be_to_cpu32 (k1->parent_dir) - grub_be_to_cpu32 (k2->parent_dir)); + if (cmp != 0) + return cmp; + + for (i = 0; i < minlen; i++) + { + cmp = (hfs_charorder[k1->str[i]] - hfs_charorder[k2->str[i]]); + if (cmp != 0) + return cmp; + } + + /* Shorter strings precede long ones. */ + return (k1->strlen - k2->strlen); +} + + +/* Compare the K1 and K2 extent overflow file keys. */ +static int +grub_hfs_cmp_extkeys (const struct grub_hfs_extent_key *k1, + const struct grub_hfs_extent_key *k2) +{ + int cmp = k1->forktype - k2->forktype; + if (cmp == 0) + cmp = grub_be_to_cpu32 (k1->fileid) - grub_be_to_cpu32 (k2->fileid); + if (cmp == 0) + cmp = (grub_be_to_cpu16 (k1->first_block) + - grub_be_to_cpu16 (k2->first_block)); + return cmp; +} + + +/* Iterate the records in the node with index IDX in the mounted HFS + filesystem DATA. This node holds data of the type TYPE (0 = + catalog node, 1 = extent overflow node). If this is set, continue + iterating to the next node. For every records, call NODE_HOOK. */ +static grub_err_t +grub_hfs_iterate_records (struct grub_hfs_data *data, int type, int idx, + int this, int (*node_hook) (struct grub_hfs_node *hnd, + struct grub_hfs_record *, + void *hook_arg), + void *hook_arg) +{ + grub_size_t nodesize = type == 0 ? data->cat_size : data->ext_size; + + union node_union + { + struct grub_hfs_node node; + char rawnode[0]; + grub_uint16_t offsets[0]; + } *node; + + if (nodesize < sizeof (struct grub_hfs_node)) + nodesize = sizeof (struct grub_hfs_node); + + node = grub_malloc (nodesize); + if (!node) + return grub_errno; + + do + { + int i; + struct grub_hfs_extent *dat; + int blk; + grub_uint16_t reccnt; + + dat = (struct grub_hfs_extent *) (type == 0 + ? (&data->sblock.catalog_recs) + : (&data->sblock.extent_recs)); + + /* Read the node into memory. */ + blk = grub_hfs_block (data, dat, + (type == 0) ? GRUB_HFS_CNID_CAT : GRUB_HFS_CNID_EXT, + idx / (data->blksz / nodesize), 0); + blk += (idx % (data->blksz / nodesize)); + + if (grub_errno || grub_disk_read (data->disk, blk, 0, + nodesize, node)) + { + grub_free (node); + return grub_errno; + } + + reccnt = grub_be_to_cpu16 (node->node.reccnt); + if (reccnt > (nodesize >> 1)) + reccnt = (nodesize >> 1); + + /* Iterate over all records in this node. */ + for (i = 0; i < reccnt; i++) + { + int pos = (nodesize >> 1) - 1 - i; + struct pointer + { + grub_uint8_t keylen; + grub_uint8_t key; + } GRUB_PACKED *pnt; + grub_uint16_t off = grub_be_to_cpu16 (node->offsets[pos]); + if (off > nodesize - sizeof(*pnt)) + continue; + pnt = (struct pointer *) (off + node->rawnode); + if (nodesize < (grub_size_t) off + pnt->keylen + 1) + continue; + + struct grub_hfs_record rec = + { + &pnt->key, + pnt->keylen, + &pnt->key + pnt->keylen +(pnt->keylen + 1) % 2, + nodesize - off - pnt->keylen - 1 + }; + + if (node_hook (&node->node, &rec, hook_arg)) + { + grub_free (node); + return 0; + } + } + + idx = grub_be_to_cpu32 (node->node.next); + } while (idx && this); + grub_free (node); + return 0; +} + +struct grub_hfs_find_node_node_found_ctx +{ + int found; + int isleaf; + int done; + int type; + const char *key; + char *datar; + grub_size_t datalen; +}; + +static int +grub_hfs_find_node_node_found (struct grub_hfs_node *hnd, struct grub_hfs_record *rec, + void *hook_arg) +{ + struct grub_hfs_find_node_node_found_ctx *ctx = hook_arg; + int cmp = 1; + + if (ctx->type == 0) + cmp = grub_hfs_cmp_catkeys (rec->key, (const void *) ctx->key); + else + cmp = grub_hfs_cmp_extkeys (rec->key, (const void *) ctx->key); + + /* If the key is smaller or equal to the current node, mark the + entry. In case of a non-leaf mode it will be used to lookup + the rest of the tree. */ + if (cmp <= 0) + ctx->found = grub_be_to_cpu32 (grub_get_unaligned32 (rec->data)); + else /* The key can not be found in the tree. */ + return 1; + + /* Check if this node is a leaf node. */ + if (hnd->type == GRUB_HFS_NODE_LEAF) + { + ctx->isleaf = 1; + + /* Found it!!!! */ + if (cmp == 0) + { + ctx->done = 1; + + grub_memcpy (ctx->datar, rec->data, + rec->datalen < ctx->datalen ? rec->datalen : ctx->datalen); + return 1; + } + } + + return 0; +} + + +/* Lookup a record in the mounted filesystem DATA using the key KEY. + The index of the node on top of the tree is IDX. The tree is of + the type TYPE (0 = catalog node, 1 = extent overflow node). Return + the data in DATAR with a maximum length of DATALEN. */ +static int +grub_hfs_find_node (struct grub_hfs_data *data, char *key, + grub_uint32_t idx, int type, char *datar, grub_size_t datalen) +{ + struct grub_hfs_find_node_node_found_ctx ctx = + { + .found = -1, + .isleaf = 0, + .done = 0, + .type = type, + .key = key, + .datar = datar, + .datalen = datalen + }; + + do + { + ctx.found = -1; + + if (grub_hfs_iterate_records (data, type, idx, 0, grub_hfs_find_node_node_found, &ctx)) + return 0; + + if (ctx.found == -1) + return 0; + + idx = ctx.found; + } while (! ctx.isleaf); + + return ctx.done; +} + +struct grub_hfs_iterate_dir_node_found_ctx +{ + grub_uint32_t dir_be; + int found; + int isleaf; + grub_uint32_t next; + int (*hook) (struct grub_hfs_record *, void *hook_arg); + void *hook_arg; +}; + +static int +grub_hfs_iterate_dir_node_found (struct grub_hfs_node *hnd, struct grub_hfs_record *rec, + void *hook_arg) +{ + struct grub_hfs_iterate_dir_node_found_ctx *ctx = hook_arg; + struct grub_hfs_catalog_key *ckey = rec->key; + + /* The lowest key possible with DIR as root directory. */ + const struct grub_hfs_catalog_key key = {0, ctx->dir_be, 0, ""}; + + if (grub_hfs_cmp_catkeys (rec->key, &key) <= 0) + ctx->found = grub_be_to_cpu32 (grub_get_unaligned32 (rec->data)); + + if (hnd->type == 0xFF && ckey->strlen > 0) + { + ctx->isleaf = 1; + ctx->next = grub_be_to_cpu32 (hnd->next); + + /* An entry was found. */ + if (ckey->parent_dir == ctx->dir_be) + return ctx->hook (rec, ctx->hook_arg); + } + + return 0; +} + +static int +grub_hfs_iterate_dir_it_dir (struct grub_hfs_node *hnd __attribute ((unused)), + struct grub_hfs_record *rec, + void *hook_arg) +{ + struct grub_hfs_catalog_key *ckey = rec->key; + struct grub_hfs_iterate_dir_node_found_ctx *ctx = hook_arg; + + /* Stop when the entries do not match anymore. */ + if (ckey->parent_dir != ctx->dir_be) + return 1; + + return ctx->hook (rec, ctx->hook_arg); +} + + +/* Iterate over the directory with the id DIR. The tree is searched + starting with the node ROOT_IDX. For every entry in this directory + call HOOK. */ +static grub_err_t +grub_hfs_iterate_dir (struct grub_hfs_data *data, grub_uint32_t root_idx, + grub_uint32_t dir, int (*hook) (struct grub_hfs_record *, void *hook_arg), + void *hook_arg) +{ + struct grub_hfs_iterate_dir_node_found_ctx ctx = + { + .dir_be = grub_cpu_to_be32 (dir), + .found = -1, + .isleaf = 0, + .next = 0, + .hook = hook, + .hook_arg = hook_arg + }; + + do + { + ctx.found = -1; + + if (grub_hfs_iterate_records (data, 0, root_idx, 0, grub_hfs_iterate_dir_node_found, &ctx)) + return grub_errno; + + if (ctx.found == -1) + return 0; + + root_idx = ctx.found; + } while (! ctx.isleaf); + + /* If there was a matching record in this leaf node, continue the + iteration until the last record was found. */ + grub_hfs_iterate_records (data, 0, ctx.next, 1, grub_hfs_iterate_dir_it_dir, &ctx); + return grub_errno; +} + +#define MAX_UTF8_PER_MAC_ROMAN 3 + +static const char macroman[0x80][MAX_UTF8_PER_MAC_ROMAN + 1] = + { + /* 80 */ "\xc3\x84", + /* 81 */ "\xc3\x85", + /* 82 */ "\xc3\x87", + /* 83 */ "\xc3\x89", + /* 84 */ "\xc3\x91", + /* 85 */ "\xc3\x96", + /* 86 */ "\xc3\x9c", + /* 87 */ "\xc3\xa1", + /* 88 */ "\xc3\xa0", + /* 89 */ "\xc3\xa2", + /* 8A */ "\xc3\xa4", + /* 8B */ "\xc3\xa3", + /* 8C */ "\xc3\xa5", + /* 8D */ "\xc3\xa7", + /* 8E */ "\xc3\xa9", + /* 8F */ "\xc3\xa8", + /* 90 */ "\xc3\xaa", + /* 91 */ "\xc3\xab", + /* 92 */ "\xc3\xad", + /* 93 */ "\xc3\xac", + /* 94 */ "\xc3\xae", + /* 95 */ "\xc3\xaf", + /* 96 */ "\xc3\xb1", + /* 97 */ "\xc3\xb3", + /* 98 */ "\xc3\xb2", + /* 99 */ "\xc3\xb4", + /* 9A */ "\xc3\xb6", + /* 9B */ "\xc3\xb5", + /* 9C */ "\xc3\xba", + /* 9D */ "\xc3\xb9", + /* 9E */ "\xc3\xbb", + /* 9F */ "\xc3\xbc", + /* A0 */ "\xe2\x80\xa0", + /* A1 */ "\xc2\xb0", + /* A2 */ "\xc2\xa2", + /* A3 */ "\xc2\xa3", + /* A4 */ "\xc2\xa7", + /* A5 */ "\xe2\x80\xa2", + /* A6 */ "\xc2\xb6", + /* A7 */ "\xc3\x9f", + /* A8 */ "\xc2\xae", + /* A9 */ "\xc2\xa9", + /* AA */ "\xe2\x84\xa2", + /* AB */ "\xc2\xb4", + /* AC */ "\xc2\xa8", + /* AD */ "\xe2\x89\xa0", + /* AE */ "\xc3\x86", + /* AF */ "\xc3\x98", + /* B0 */ "\xe2\x88\x9e", + /* B1 */ "\xc2\xb1", + /* B2 */ "\xe2\x89\xa4", + /* B3 */ "\xe2\x89\xa5", + /* B4 */ "\xc2\xa5", + /* B5 */ "\xc2\xb5", + /* B6 */ "\xe2\x88\x82", + /* B7 */ "\xe2\x88\x91", + /* B8 */ "\xe2\x88\x8f", + /* B9 */ "\xcf\x80", + /* BA */ "\xe2\x88\xab", + /* BB */ "\xc2\xaa", + /* BC */ "\xc2\xba", + /* BD */ "\xce\xa9", + /* BE */ "\xc3\xa6", + /* BF */ "\xc3\xb8", + /* C0 */ "\xc2\xbf", + /* C1 */ "\xc2\xa1", + /* C2 */ "\xc2\xac", + /* C3 */ "\xe2\x88\x9a", + /* C4 */ "\xc6\x92", + /* C5 */ "\xe2\x89\x88", + /* C6 */ "\xe2\x88\x86", + /* C7 */ "\xc2\xab", + /* C8 */ "\xc2\xbb", + /* C9 */ "\xe2\x80\xa6", + /* CA */ "\xc2\xa0", + /* CB */ "\xc3\x80", + /* CC */ "\xc3\x83", + /* CD */ "\xc3\x95", + /* CE */ "\xc5\x92", + /* CF */ "\xc5\x93", + /* D0 */ "\xe2\x80\x93", + /* D1 */ "\xe2\x80\x94", + /* D2 */ "\xe2\x80\x9c", + /* D3 */ "\xe2\x80\x9d", + /* D4 */ "\xe2\x80\x98", + /* D5 */ "\xe2\x80\x99", + /* D6 */ "\xc3\xb7", + /* D7 */ "\xe2\x97\x8a", + /* D8 */ "\xc3\xbf", + /* D9 */ "\xc5\xb8", + /* DA */ "\xe2\x81\x84", + /* DB */ "\xe2\x82\xac", + /* DC */ "\xe2\x80\xb9", + /* DD */ "\xe2\x80\xba", + /* DE */ "\xef\xac\x81", + /* DF */ "\xef\xac\x82", + /* E0 */ "\xe2\x80\xa1", + /* E1 */ "\xc2\xb7", + /* E2 */ "\xe2\x80\x9a", + /* E3 */ "\xe2\x80\x9e", + /* E4 */ "\xe2\x80\xb0", + /* E5 */ "\xc3\x82", + /* E6 */ "\xc3\x8a", + /* E7 */ "\xc3\x81", + /* E8 */ "\xc3\x8b", + /* E9 */ "\xc3\x88", + /* EA */ "\xc3\x8d", + /* EB */ "\xc3\x8e", + /* EC */ "\xc3\x8f", + /* ED */ "\xc3\x8c", + /* EE */ "\xc3\x93", + /* EF */ "\xc3\x94", + /* F0 */ "\xef\xa3\xbf", + /* F1 */ "\xc3\x92", + /* F2 */ "\xc3\x9a", + /* F3 */ "\xc3\x9b", + /* F4 */ "\xc3\x99", + /* F5 */ "\xc4\xb1", + /* F6 */ "\xcb\x86", + /* F7 */ "\xcb\x9c", + /* F8 */ "\xc2\xaf", + /* F9 */ "\xcb\x98", + /* FA */ "\xcb\x99", + /* FB */ "\xcb\x9a", + /* FC */ "\xc2\xb8", + /* FD */ "\xcb\x9d", + /* FE */ "\xcb\x9b", + /* FF */ "\xcb\x87", + }; + +static void +macroman_to_utf8 (char *to, const grub_uint8_t *from, grub_size_t len, + int translate_slash) +{ + char *optr = to; + const grub_uint8_t *iptr; + + for (iptr = from; iptr < from + len && *iptr; iptr++) + { + /* Translate '/' to ':' as per HFS spec. */ + if (*iptr == '/' && translate_slash) + { + *optr++ = ':'; + continue; + } + if (!(*iptr & 0x80)) + { + *optr++ = *iptr; + continue; + } + optr = grub_stpcpy (optr, macroman[*iptr & 0x7f]); + } + *optr = 0; +} + +static grub_ssize_t +utf8_to_macroman (grub_uint8_t *to, const char *from) +{ + grub_uint8_t *end = to + 31; + grub_uint8_t *optr = to; + const char *iptr = from; + + while (*iptr && optr < end) + { + int i, clen; + /* Translate ':' to '/' as per HFS spec. */ + if (*iptr == ':') + { + *optr++ = '/'; + iptr++; + continue; + } + if (!(*iptr & 0x80)) + { + *optr++ = *iptr++; + continue; + } + clen = 2; + if ((*iptr & 0xf0) == 0xe0) + clen++; + for (i = 0; i < 0x80; i++) + if (grub_memcmp (macroman[i], iptr, clen) == 0) + break; + if (i == 0x80) + break; + *optr++ = i | 0x80; + iptr += clen; + } + /* Too long or not encodable. */ + if (*iptr) + return -1; + return optr - to; +} + +union grub_hfs_anyrec { + struct grub_hfs_filerec frec; + struct grub_hfs_dirrec dir; +}; + +struct grub_fshelp_node +{ + struct grub_hfs_data *data; + union grub_hfs_anyrec fdrec; + grub_uint32_t inode; +}; + +static grub_err_t +lookup_file (grub_fshelp_node_t dir, + const char *name, + grub_fshelp_node_t *foundnode, + enum grub_fshelp_filetype *foundtype) +{ + struct grub_hfs_catalog_key key; + grub_ssize_t slen; + union grub_hfs_anyrec fdrec; + + key.parent_dir = grub_cpu_to_be32 (dir->inode); + slen = utf8_to_macroman (key.str, name); + if (slen < 0) + /* Not found */ + return GRUB_ERR_NONE; + key.strlen = slen; + + /* Lookup this node. */ + if (! grub_hfs_find_node (dir->data, (char *) &key, dir->data->cat_root, + 0, (char *) &fdrec.frec, sizeof (fdrec.frec))) + /* Not found */ + return GRUB_ERR_NONE; + + *foundnode = grub_malloc (sizeof (struct grub_fshelp_node)); + if (!*foundnode) + return grub_errno; + + (*foundnode)->inode = grub_be_to_cpu32 (fdrec.dir.dirid); + (*foundnode)->fdrec = fdrec; + (*foundnode)->data = dir->data; + *foundtype = (fdrec.frec.type == GRUB_HFS_FILETYPE_DIR) ? GRUB_FSHELP_DIR : GRUB_FSHELP_REG; + return GRUB_ERR_NONE; +} + +/* Find a file or directory with the pathname PATH in the filesystem + DATA. Return the file record in RETDATA when it is non-zero. + Return the directory number in RETINODE when it is non-zero. */ +static grub_err_t +grub_hfs_find_dir (struct grub_hfs_data *data, const char *path, + grub_fshelp_node_t *found, + enum grub_fshelp_filetype exptype) +{ + struct grub_fshelp_node root = { + .data = data, + .inode = data->rootdir, + .fdrec = { + .frec = { + .type = GRUB_HFS_FILETYPE_DIR + } + } + }; + grub_err_t err; + + err = grub_fshelp_find_file_lookup (path, &root, found, lookup_file, NULL, exptype); + + if (&root == *found) + { + *found = grub_malloc (sizeof (root)); + if (!*found) + return grub_errno; + grub_memcpy (*found, &root, sizeof (root)); + } + return err; +} + +struct grub_hfs_dir_hook_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +static int +grub_hfs_dir_hook (struct grub_hfs_record *rec, void *hook_arg) +{ + struct grub_hfs_dir_hook_ctx *ctx = hook_arg; + struct grub_hfs_dirrec *drec = rec->data; + struct grub_hfs_filerec *frec = rec->data; + struct grub_hfs_catalog_key *ckey = rec->key; + char fname[sizeof (ckey->str) * MAX_UTF8_PER_MAC_ROMAN + 1]; + struct grub_dirhook_info info; + grub_size_t len; + + grub_memset (fname, 0, sizeof (fname)); + + grub_memset (&info, 0, sizeof (info)); + + len = ckey->strlen; + if (len > sizeof (ckey->str)) + len = sizeof (ckey->str); + macroman_to_utf8 (fname, ckey->str, len, 1); + + info.case_insensitive = 1; + + if (drec->type == GRUB_HFS_FILETYPE_DIR) + { + info.dir = 1; + info.mtimeset = 1; + info.inodeset = 1; + info.mtime = grub_be_to_cpu32 (drec->mtime) - 2082844800; + info.inode = grub_be_to_cpu32 (drec->dirid); + return ctx->hook (fname, &info, ctx->hook_data); + } + if (frec->type == GRUB_HFS_FILETYPE_FILE) + { + info.dir = 0; + info.mtimeset = 1; + info.inodeset = 1; + info.mtime = grub_be_to_cpu32 (frec->mtime) - 2082844800; + info.inode = grub_be_to_cpu32 (frec->fileid); + return ctx->hook (fname, &info, ctx->hook_data); + } + + return 0; +} + + +static grub_err_t +grub_hfs_dir (grub_device_t device, const char *path, grub_fs_dir_hook_t hook, + void *hook_data) +{ + struct grub_hfs_data *data; + struct grub_hfs_dir_hook_ctx ctx = + { + .hook = hook, + .hook_data = hook_data + }; + grub_fshelp_node_t found = NULL; + + grub_dl_ref (my_mod); + + data = grub_hfs_mount (device->disk); + if (!data) + goto fail; + + /* First the directory ID for the directory. */ + if (grub_hfs_find_dir (data, path, &found, GRUB_FSHELP_DIR)) + goto fail; + + grub_hfs_iterate_dir (data, data->cat_root, found->inode, grub_hfs_dir_hook, &ctx); + + fail: + grub_free (found); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_hfs_open (struct grub_file *file, const char *name) +{ + struct grub_hfs_data *data; + grub_fshelp_node_t found = NULL; + + grub_dl_ref (my_mod); + + data = grub_hfs_mount (file->device->disk); + + if (!data) + { + grub_dl_unref (my_mod); + return grub_errno; + } + + if (grub_hfs_find_dir (data, name, &found, GRUB_FSHELP_REG)) + { + grub_free (data); + grub_free (found); + grub_dl_unref (my_mod); + return grub_errno; + } + + grub_memcpy (data->extents, found->fdrec.frec.extents, sizeof (grub_hfs_datarecord_t)); + file->size = grub_be_to_cpu32 (found->fdrec.frec.size); + data->size = grub_be_to_cpu32 (found->fdrec.frec.size); + data->fileid = grub_be_to_cpu32 (found->fdrec.frec.fileid); + file->offset = 0; + + file->data = data; + + grub_free (found); + + return 0; +} + +static grub_ssize_t +grub_hfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_hfs_data *data = + (struct grub_hfs_data *) file->data; + + return grub_hfs_read_file (data, file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + + +static grub_err_t +grub_hfs_close (grub_file_t file) +{ + grub_free (file->data); + + grub_dl_unref (my_mod); + + return 0; +} + + +static grub_err_t +grub_hfs_label (grub_device_t device, char **label) +{ + struct grub_hfs_data *data; + + data = grub_hfs_mount (device->disk); + + if (data) + { + grub_size_t len = data->sblock.volname[0]; + if (len > sizeof (data->sblock.volname) - 1) + len = sizeof (data->sblock.volname) - 1; + *label = grub_calloc (MAX_UTF8_PER_MAC_ROMAN + 1, len); + if (*label) + macroman_to_utf8 (*label, data->sblock.volname + 1, + len + 1, 0); + } + else + *label = 0; + + grub_free (data); + return grub_errno; +} + +static grub_err_t +grub_hfs_mtime (grub_device_t device, grub_int64_t *tm) +{ + struct grub_hfs_data *data; + + data = grub_hfs_mount (device->disk); + + if (data) + *tm = grub_be_to_cpu32 (data->sblock.mtime) - 2082844800; + else + *tm = 0; + + grub_free (data); + return grub_errno; +} + +static grub_err_t +grub_hfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_hfs_data *data; + + grub_dl_ref (my_mod); + + data = grub_hfs_mount (device->disk); + if (data && data->sblock.num_serial != 0) + { + *uuid = grub_xasprintf ("%016llx", + (unsigned long long) + grub_be_to_cpu64 (data->sblock.num_serial)); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + + +static struct grub_fs grub_hfs_fs = + { + .name = "hfs", + .fs_dir = grub_hfs_dir, + .fs_open = grub_hfs_open, + .fs_read = grub_hfs_read, + .fs_close = grub_hfs_close, + .fs_label = grub_hfs_label, + .fs_uuid = grub_hfs_uuid, + .fs_mtime = grub_hfs_mtime, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(hfs) +{ + if (!grub_is_lockdown ()) + grub_fs_register (&grub_hfs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(hfs) +{ + if (!grub_is_lockdown()) + grub_fs_unregister (&grub_hfs_fs); +} diff --git a/grub-core/fs/hfsplus.c b/grub-core/fs/hfsplus.c new file mode 100644 index 0000000..19c7b33 --- /dev/null +++ b/grub-core/fs/hfsplus.c @@ -0,0 +1,1160 @@ +/* hfsplus.c - HFS+ 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/>. + */ + +/* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */ + +#define grub_fshelp_node grub_hfsplus_file +#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/hfs.h> +#include <grub/charset.h> +#include <grub/hfsplus.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* The type of node. */ +enum grub_hfsplus_btnode_type + { + GRUB_HFSPLUS_BTNODE_TYPE_LEAF = -1, + GRUB_HFSPLUS_BTNODE_TYPE_INDEX = 0, + GRUB_HFSPLUS_BTNODE_TYPE_HEADER = 1, + GRUB_HFSPLUS_BTNODE_TYPE_MAP = 2, + }; + +/* The header of a HFS+ B+ Tree. */ +struct grub_hfsplus_btheader +{ + grub_uint16_t depth; + grub_uint32_t root; + grub_uint32_t leaf_records; + grub_uint32_t first_leaf_node; + grub_uint32_t last_leaf_node; + grub_uint16_t nodesize; + grub_uint16_t keysize; + grub_uint32_t total_nodes; + grub_uint32_t free_nodes; + grub_uint16_t reserved1; + grub_uint32_t clump_size; /* ignored */ + grub_uint8_t btree_type; + grub_uint8_t key_compare; + grub_uint32_t attributes; +} GRUB_PACKED; + +struct grub_hfsplus_catfile +{ + grub_uint16_t type; + grub_uint16_t flags; + grub_uint32_t parentid; /* Thread only. */ + grub_uint32_t fileid; + grub_uint8_t unused1[4]; + grub_uint32_t mtime; + grub_uint8_t unused2[22]; + grub_uint16_t mode; + grub_uint8_t unused3[44]; + struct grub_hfsplus_forkdata data; + struct grub_hfsplus_forkdata resource; +} GRUB_PACKED; + +/* Filetype information as used in inodes. */ +#define GRUB_HFSPLUS_FILEMODE_MASK 0170000 +#define GRUB_HFSPLUS_FILEMODE_REG 0100000 +#define GRUB_HFSPLUS_FILEMODE_DIRECTORY 0040000 +#define GRUB_HFSPLUS_FILEMODE_SYMLINK 0120000 + +/* Some pre-defined file IDs. */ +enum + { + GRUB_HFSPLUS_FILEID_ROOTDIR = 2, + GRUB_HFSPLUS_FILEID_OVERFLOW = 3, + GRUB_HFSPLUS_FILEID_CATALOG = 4, + GRUB_HFSPLUS_FILEID_ATTR = 8 + }; + +enum grub_hfsplus_filetype + { + GRUB_HFSPLUS_FILETYPE_DIR = 1, + GRUB_HFSPLUS_FILETYPE_REG = 2, + GRUB_HFSPLUS_FILETYPE_DIR_THREAD = 3, + GRUB_HFSPLUS_FILETYPE_REG_THREAD = 4 + }; + +#define GRUB_HFSPLUSX_BINARYCOMPARE 0xBC +#define GRUB_HFSPLUSX_CASEFOLDING 0xCF + +static grub_dl_t my_mod; + + + +grub_err_t (*grub_hfsplus_open_compressed) (struct grub_fshelp_node *node); +grub_ssize_t (*grub_hfsplus_read_compressed) (struct grub_hfsplus_file *node, + grub_off_t pos, + grub_size_t len, + char *buf); + +/* Find the extent that points to FILEBLOCK. If it is not in one of + the 8 extents described by EXTENT, return -1. In that case set + FILEBLOCK to the next block. */ +static grub_disk_addr_t +grub_hfsplus_find_block (struct grub_hfsplus_extent *extent, + grub_disk_addr_t *fileblock) +{ + int i; + grub_disk_addr_t blksleft = *fileblock; + + /* First lookup the file in the given extents. */ + for (i = 0; i < 8; i++) + { + if (blksleft < grub_be_to_cpu32 (extent[i].count)) + return grub_be_to_cpu32 (extent[i].start) + blksleft; + blksleft -= grub_be_to_cpu32 (extent[i].count); + } + + *fileblock = blksleft; + return 0xffffffffffffffffULL; +} + +static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya, + struct grub_hfsplus_key_internal *keyb); + +/* Search for the block FILEBLOCK inside the file NODE. Return the + blocknumber of this block on disk. */ +static grub_disk_addr_t +grub_hfsplus_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +{ + struct grub_hfsplus_btnode *nnode = 0; + grub_disk_addr_t blksleft = fileblock; + struct grub_hfsplus_extent *extents = node->compressed + ? &node->resource_extents[0] : &node->extents[0]; + + while (1) + { + struct grub_hfsplus_extkey *key; + struct grub_hfsplus_key_internal extoverflow; + grub_disk_addr_t blk; + grub_off_t ptr; + + /* Try to find this block in the current set of extents. */ + blk = grub_hfsplus_find_block (extents, &blksleft); + + /* The previous iteration of this loop allocated memory. The + code above used this memory, it can be freed now. */ + grub_free (nnode); + nnode = 0; + + if (blk != 0xffffffffffffffffULL) + return blk; + + /* For the extent overflow file, extra extents can't be found in + the extent overflow file. If this happens, you found a + bug... */ + if (node->fileid == GRUB_HFSPLUS_FILEID_OVERFLOW) + { + grub_error (GRUB_ERR_READ_ERROR, + "extra extents found in an extend overflow file"); + break; + } + + /* + * If the extent overflow tree isn't ready yet, we can't look + * in it. This can happen where the catalog file is corrupted. + */ + if (!node->data->extoverflow_tree_ready) + { + grub_error (GRUB_ERR_BAD_FS, + "attempted to read extent overflow tree before loading"); + break; + } + + /* Set up the key to look for in the extent overflow file. */ + extoverflow.extkey.fileid = node->fileid; + extoverflow.extkey.type = 0; + extoverflow.extkey.start = fileblock - blksleft; + extoverflow.extkey.type = node->compressed ? 0xff : 0; + if (grub_hfsplus_btree_search (&node->data->extoverflow_tree, + &extoverflow, + grub_hfsplus_cmp_extkey, &nnode, &ptr) + || !nnode) + { + grub_error (GRUB_ERR_READ_ERROR, + "no block found for the file id 0x%x and the block" + " offset 0x%" PRIuGRUB_UINT64_T, + node->fileid, fileblock); + break; + } + + /* The extent overflow file has 8 extents right after the key. */ + key = (struct grub_hfsplus_extkey *) + grub_hfsplus_btree_recptr (&node->data->extoverflow_tree, nnode, ptr); + extents = (struct grub_hfsplus_extent *) (key + 1); + + /* The block wasn't found. Perhaps the next iteration will find + it. The last block we found is stored in BLKSLEFT now. */ + } + + grub_free (nnode); + + /* Too bad, you lose. */ + return -1; +} + + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +grub_ssize_t +grub_hfsplus_read_file (grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + return grub_fshelp_read_file (node->data->disk, node, + read_hook, read_hook_data, + pos, len, buf, grub_hfsplus_read_block, + node->size, + node->data->log2blksize - GRUB_DISK_SECTOR_BITS, + node->data->embedded_offset); +} + +static struct grub_hfsplus_data * +grub_hfsplus_mount (grub_disk_t disk) +{ + struct grub_hfsplus_data *data; + struct grub_hfsplus_btheader header; + struct grub_hfsplus_btnode node; + grub_uint16_t magic; + union { + struct grub_hfs_sblock hfs; + struct grub_hfsplus_volheader hfsplus; + } volheader; + + data = grub_malloc (sizeof (*data)); + if (!data) + return 0; + + data->disk = disk; + data->extoverflow_tree_ready = 0; + + /* Read the bootblock. */ + grub_disk_read (disk, GRUB_HFSPLUS_SBLOCK, 0, sizeof (volheader), + &volheader); + if (grub_errno) + goto fail; + + data->embedded_offset = 0; + if (grub_be_to_cpu16 (volheader.hfs.magic) == GRUB_HFS_MAGIC) + { + grub_disk_addr_t extent_start; + grub_disk_addr_t ablk_size; + grub_disk_addr_t ablk_start; + + /* See if there's an embedded HFS+ filesystem. */ + if (grub_be_to_cpu16 (volheader.hfs.embed_sig) != GRUB_HFSPLUS_MAGIC) + { + grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem"); + goto fail; + } + + /* Calculate the offset needed to translate HFS+ sector numbers. */ + extent_start = grub_be_to_cpu16 (volheader.hfs.embed_extent.first_block); + ablk_size = grub_be_to_cpu32 (volheader.hfs.blksz); + ablk_start = grub_be_to_cpu16 (volheader.hfs.first_block); + data->embedded_offset = (ablk_start + + extent_start + * (ablk_size >> GRUB_DISK_SECTOR_BITS)); + + grub_disk_read (disk, data->embedded_offset + GRUB_HFSPLUS_SBLOCK, 0, + sizeof (volheader), &volheader); + if (grub_errno) + goto fail; + } + + /* Make sure this is an HFS+ filesystem. XXX: Do we really support + HFX? */ + magic = grub_be_to_cpu16 (volheader.hfsplus.magic); + if (((magic != GRUB_HFSPLUS_MAGIC) && (magic != GRUB_HFSPLUSX_MAGIC)) + || volheader.hfsplus.blksize == 0 + || ((volheader.hfsplus.blksize & (volheader.hfsplus.blksize - 1)) != 0) + || grub_be_to_cpu32 (volheader.hfsplus.blksize) < GRUB_DISK_SECTOR_SIZE) + { + grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem"); + goto fail; + } + + grub_memcpy (&data->volheader, &volheader.hfsplus, + sizeof (volheader.hfsplus)); + + for (data->log2blksize = 0; + (1U << data->log2blksize) < grub_be_to_cpu32 (data->volheader.blksize); + data->log2blksize++); + + /* Make a new node for the catalog tree. */ + data->catalog_tree.file.data = data; + data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG; + data->catalog_tree.file.compressed = 0; + grub_memcpy (&data->catalog_tree.file.extents, + data->volheader.catalog_file.extents, + sizeof data->volheader.catalog_file.extents); + data->catalog_tree.file.size = + grub_be_to_cpu64 (data->volheader.catalog_file.size); + + data->attr_tree.file.data = data; + data->attr_tree.file.fileid = GRUB_HFSPLUS_FILEID_ATTR; + grub_memcpy (&data->attr_tree.file.extents, + data->volheader.attr_file.extents, + sizeof data->volheader.attr_file.extents); + + data->attr_tree.file.size = + grub_be_to_cpu64 (data->volheader.attr_file.size); + data->attr_tree.file.compressed = 0; + + /* Make a new node for the extent overflow file. */ + data->extoverflow_tree.file.data = data; + data->extoverflow_tree.file.fileid = GRUB_HFSPLUS_FILEID_OVERFLOW; + data->extoverflow_tree.file.compressed = 0; + grub_memcpy (&data->extoverflow_tree.file.extents, + data->volheader.extents_file.extents, + sizeof data->volheader.catalog_file.extents); + + data->extoverflow_tree.file.size = + grub_be_to_cpu64 (data->volheader.extents_file.size); + + /* Read the essential information about the trees. */ + if (grub_hfsplus_read_file (&data->catalog_tree.file, 0, 0, + sizeof (struct grub_hfsplus_btnode), + sizeof (header), (char *) &header) <= 0) + goto fail; + + data->catalog_tree.root = grub_be_to_cpu32 (header.root); + data->catalog_tree.nodesize = grub_be_to_cpu16 (header.nodesize); + data->case_sensitive = ((magic == GRUB_HFSPLUSX_MAGIC) && + (header.key_compare == GRUB_HFSPLUSX_BINARYCOMPARE)); + + if (data->catalog_tree.nodesize < 2) + goto fail; + + if (grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0, + sizeof (struct grub_hfsplus_btnode), + sizeof (header), (char *) &header) <= 0) + goto fail; + + data->extoverflow_tree.root = grub_be_to_cpu32 (header.root); + + if (grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0, 0, + sizeof (node), (char *) &node) <= 0) + goto fail; + + data->extoverflow_tree.root = grub_be_to_cpu32 (header.root); + data->extoverflow_tree.nodesize = grub_be_to_cpu16 (header.nodesize); + + if (data->extoverflow_tree.nodesize < 2) + goto fail; + + data->extoverflow_tree_ready = 1; + + if (grub_hfsplus_read_file (&data->attr_tree.file, 0, 0, + sizeof (struct grub_hfsplus_btnode), + sizeof (header), (char *) &header) <= 0) + { + grub_errno = 0; + data->attr_tree.root = 0; + data->attr_tree.nodesize = 0; + } + else + { + data->attr_tree.root = grub_be_to_cpu32 (header.root); + data->attr_tree.nodesize = grub_be_to_cpu16 (header.nodesize); + } + + data->dirroot.data = data; + data->dirroot.fileid = GRUB_HFSPLUS_FILEID_ROOTDIR; + + return data; + + fail: + + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem"); + + grub_free (data); + return 0; +} + +/* Compare the on disk catalog key KEYA with the catalog key we are + looking for (KEYB). */ +static int +grub_hfsplus_cmp_catkey (struct grub_hfsplus_key *keya, + struct grub_hfsplus_key_internal *keyb) +{ + struct grub_hfsplus_catkey *catkey_a = &keya->catkey; + struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey; + int diff; + grub_size_t len; + + /* Safe unsigned comparison */ + grub_uint32_t aparent = grub_be_to_cpu32 (catkey_a->parent); + if (aparent > catkey_b->parent) + return 1; + if (aparent < catkey_b->parent) + return -1; + + len = grub_be_to_cpu16 (catkey_a->namelen); + if (len > catkey_b->namelen) + len = catkey_b->namelen; + /* Since it's big-endian memcmp gives the same result as manually comparing + uint16_t but may be faster. */ + diff = grub_memcmp (catkey_a->name, catkey_b->name, + len * sizeof (catkey_a->name[0])); + if (diff == 0) + diff = grub_be_to_cpu16 (catkey_a->namelen) - catkey_b->namelen; + + return diff; +} + +/* Compare the on disk catalog key KEYA with the catalog key we are + looking for (KEYB). */ +static int +grub_hfsplus_cmp_catkey_id (struct grub_hfsplus_key *keya, + struct grub_hfsplus_key_internal *keyb) +{ + struct grub_hfsplus_catkey *catkey_a = &keya->catkey; + struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey; + + /* Safe unsigned comparison */ + grub_uint32_t aparent = grub_be_to_cpu32 (catkey_a->parent); + if (aparent > catkey_b->parent) + return 1; + if (aparent < catkey_b->parent) + return -1; + + return 0; +} + +/* Compare the on disk extent overflow key KEYA with the extent + overflow key we are looking for (KEYB). */ +static int +grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya, + struct grub_hfsplus_key_internal *keyb) +{ + struct grub_hfsplus_extkey *extkey_a = &keya->extkey; + struct grub_hfsplus_extkey_internal *extkey_b = &keyb->extkey; + grub_uint32_t akey; + + /* Safe unsigned comparison */ + akey = grub_be_to_cpu32 (extkey_a->fileid); + if (akey > extkey_b->fileid) + return 1; + if (akey < extkey_b->fileid) + return -1; + + if (extkey_a->type > extkey_b->type) + return 1; + if (extkey_a->type < extkey_b->type) + return -1; + + if (extkey_a->type > extkey_b->type) + return +1; + + if (extkey_a->type < extkey_b->type) + return -1; + + akey = grub_be_to_cpu32 (extkey_a->start); + if (akey > extkey_b->start) + return 1; + if (akey < extkey_b->start) + return -1; + return 0; +} + +static char * +grub_hfsplus_read_symlink (grub_fshelp_node_t node) +{ + char *symlink; + grub_ssize_t numread; + grub_size_t sz = node->size; + + if (grub_add (sz, 1, &sz)) + return NULL; + + symlink = grub_malloc (sz); + if (!symlink) + return 0; + + numread = grub_hfsplus_read_file (node, 0, 0, 0, node->size, symlink); + if (numread != (grub_ssize_t) node->size) + { + grub_free (symlink); + return 0; + } + symlink[node->size] = '\0'; + + return symlink; +} + +static int +grub_hfsplus_btree_iterate_node (struct grub_hfsplus_btree *btree, + struct grub_hfsplus_btnode *first_node, + grub_disk_addr_t first_rec, + int (*hook) (void *record, void *hook_arg), + void *hook_arg) +{ + grub_disk_addr_t rec; + grub_uint64_t saved_node = -1; + grub_uint64_t node_count = 0; + + for (;;) + { + char *cnode = (char *) first_node; + + /* Iterate over all records in this node. */ + for (rec = first_rec; rec < grub_be_to_cpu16 (first_node->count); rec++) + { + if (hook (grub_hfsplus_btree_recptr (btree, first_node, rec), hook_arg)) + return 1; + } + + if (! first_node->next) + break; + + if (node_count && first_node->next == saved_node) + { + grub_error (GRUB_ERR_BAD_FS, "HFS+ btree loop"); + return 0; + } + if (!(node_count & (node_count - 1))) + saved_node = first_node->next; + node_count++; + + if (grub_hfsplus_read_file (&btree->file, 0, 0, + (((grub_disk_addr_t) + grub_be_to_cpu32 (first_node->next)) + * btree->nodesize), + btree->nodesize, cnode) <= 0) + return 1; + + /* Don't skip any record in the next iteration. */ + first_rec = 0; + } + + return 0; +} + +/* Lookup the node described by KEY in the B+ Tree BTREE. Compare + keys using the function COMPARE_KEYS. When a match is found, + return the node in MATCHNODE and a pointer to the data in this node + in KEYOFFSET. MATCHNODE should be freed by the caller. */ +grub_err_t +grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree, + struct grub_hfsplus_key_internal *key, + int (*compare_keys) (struct grub_hfsplus_key *keya, + struct grub_hfsplus_key_internal *keyb), + struct grub_hfsplus_btnode **matchnode, + grub_off_t *keyoffset) +{ + grub_uint64_t currnode; + char *node; + struct grub_hfsplus_btnode *nodedesc; + grub_disk_addr_t rec; + grub_uint64_t save_node; + grub_uint64_t node_count = 0; + + if (!btree->nodesize) + { + *matchnode = 0; + return 0; + } + + node = grub_malloc (btree->nodesize); + if (! node) + return grub_errno; + + currnode = btree->root; + save_node = currnode - 1; + while (1) + { + int match = 0; + + if (save_node == currnode) + { + grub_free (node); + return grub_error (GRUB_ERR_BAD_FS, "HFS+ btree loop"); + } + if (!(node_count & (node_count - 1))) + save_node = currnode; + node_count++; + + /* Read a node. */ + if (grub_hfsplus_read_file (&btree->file, 0, 0, + (grub_disk_addr_t) currnode + * (grub_disk_addr_t) btree->nodesize, + btree->nodesize, (char *) node) <= 0) + { + grub_free (node); + return grub_error (GRUB_ERR_BAD_FS, "couldn't read i-node"); + } + + nodedesc = (struct grub_hfsplus_btnode *) node; + + /* Find the record in this tree. */ + for (rec = 0; rec < grub_be_to_cpu16 (nodedesc->count); rec++) + { + struct grub_hfsplus_key *currkey; + currkey = grub_hfsplus_btree_recptr (btree, nodedesc, rec); + + /* The action that has to be taken depend on the type of + record. */ + if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_LEAF + && compare_keys (currkey, key) == 0) + { + /* An exact match was found! */ + + *matchnode = nodedesc; + *keyoffset = rec; + + return 0; + } + else if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_INDEX) + { + void *pointer; + + /* The place where the key could have been found didn't + contain the key. This means that the previous match + is the one that should be followed. */ + if (compare_keys (currkey, key) > 0) + break; + + /* Mark the last key which is lower or equal to the key + that we are looking for. The last match that is + found will be used to locate the child which can + contain the record. */ + pointer = ((char *) currkey + + grub_be_to_cpu16 (currkey->keylen) + + 2); + + if ((char *) pointer > node + btree->nodesize - 2) + return grub_error (GRUB_ERR_BAD_FS, "HFS+ key beyond end of node"); + + currnode = grub_be_to_cpu32 (grub_get_unaligned32 (pointer)); + match = 1; + } + } + + /* No match is found, no record with this key exists in the + tree. */ + if (! match) + { + *matchnode = 0; + grub_free (node); + return 0; + } + } +} + +struct list_nodes_ctx +{ + int ret; + grub_fshelp_node_t dir; + grub_fshelp_iterate_dir_hook_t hook; + void *hook_data; +}; + +static int +list_nodes (void *record, void *hook_arg) +{ + struct grub_hfsplus_catkey *catkey; + char *filename; + int i; + struct grub_fshelp_node *node; + grub_uint16_t *keyname; + struct grub_hfsplus_catfile *fileinfo; + enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN; + struct list_nodes_ctx *ctx = hook_arg; + + catkey = (struct grub_hfsplus_catkey *) record; + + fileinfo = + (struct grub_hfsplus_catfile *) ((char *) record + + grub_be_to_cpu16 (catkey->keylen) + + 2 + (grub_be_to_cpu16(catkey->keylen) + % 2)); + + /* Stop iterating when the last directory entry is found. */ + if (grub_be_to_cpu32 (catkey->parent) != ctx->dir->fileid) + return 1; + + /* Determine the type of the node that is found. */ + switch (fileinfo->type) + { + case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_REG): + { + int mode = (grub_be_to_cpu16 (fileinfo->mode) + & GRUB_HFSPLUS_FILEMODE_MASK); + + if (mode == GRUB_HFSPLUS_FILEMODE_REG) + type = GRUB_FSHELP_REG; + else if (mode == GRUB_HFSPLUS_FILEMODE_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + else + type = GRUB_FSHELP_UNKNOWN; + break; + } + case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR): + type = GRUB_FSHELP_DIR; + break; + case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR_THREAD): + if (ctx->dir->fileid == 2) + return 0; + node = grub_malloc (sizeof (*node)); + if (!node) + return 1; + node->data = ctx->dir->data; + node->mtime = 0; + node->size = 0; + node->fileid = grub_be_to_cpu32 (fileinfo->parentid); + + ctx->ret = ctx->hook ("..", GRUB_FSHELP_DIR, node, ctx->hook_data); + return ctx->ret; + } + + if (type == GRUB_FSHELP_UNKNOWN) + return 0; + + filename = grub_calloc (grub_be_to_cpu16 (catkey->namelen), + GRUB_MAX_UTF8_PER_UTF16 + 1); + if (! filename) + return 0; + + keyname = grub_calloc (grub_be_to_cpu16 (catkey->namelen), sizeof (*keyname)); + if (!keyname) + { + grub_free (filename); + return 0; + } + + /* Make sure the byte order of the UTF16 string is correct. */ + for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++) + { + keyname[i] = grub_be_to_cpu16 (catkey->name[i]); + + if (keyname[i] == '/') + keyname[i] = ':'; + + /* If the name is obviously invalid, skip this node. */ + if (keyname[i] == 0) + { + grub_free (keyname); + grub_free (filename); + return 0; + } + } + + *grub_utf16_to_utf8 ((grub_uint8_t *) filename, keyname, + grub_be_to_cpu16 (catkey->namelen)) = '\0'; + + grub_free (keyname); + + /* hfs+ is case insensitive. */ + if (! ctx->dir->data->case_sensitive) + type |= GRUB_FSHELP_CASE_INSENSITIVE; + + /* A valid node is found; setup the node and call the + callback function. */ + node = grub_malloc (sizeof (*node)); + if (!node) + { + grub_free (filename); + return 1; + } + node->data = ctx->dir->data; + node->compressed = 0; + node->cbuf = 0; + node->compress_index = 0; + + grub_memcpy (node->extents, fileinfo->data.extents, + sizeof (node->extents)); + grub_memcpy (node->resource_extents, fileinfo->resource.extents, + sizeof (node->resource_extents)); + node->mtime = grub_be_to_cpu32 (fileinfo->mtime) - 2082844800; + node->size = grub_be_to_cpu64 (fileinfo->data.size); + node->resource_size = grub_be_to_cpu64 (fileinfo->resource.size); + node->fileid = grub_be_to_cpu32 (fileinfo->fileid); + + ctx->ret = ctx->hook (filename, type, node, ctx->hook_data); + + grub_free (filename); + + return ctx->ret; +} + +static int +grub_hfsplus_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + struct list_nodes_ctx ctx = + { + .ret = 0, + .dir = dir, + .hook = hook, + .hook_data = hook_data + }; + + struct grub_hfsplus_key_internal intern; + struct grub_hfsplus_btnode *node = NULL; + grub_disk_addr_t ptr = 0; + + { + struct grub_fshelp_node *fsnode; + fsnode = grub_malloc (sizeof (*fsnode)); + if (!fsnode) + return 1; + *fsnode = *dir; + if (hook (".", GRUB_FSHELP_DIR, fsnode, hook_data)) + return 1; + } + + /* Create a key that points to the first entry in the directory. */ + intern.catkey.parent = dir->fileid; + intern.catkey.name = 0; + intern.catkey.namelen = 0; + + /* First lookup the first entry. */ + if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern, + grub_hfsplus_cmp_catkey, &node, &ptr) + || !node) + return 0; + + /* Iterate over all entries in this directory. */ + grub_hfsplus_btree_iterate_node (&dir->data->catalog_tree, node, ptr, + list_nodes, &ctx); + + grub_free (node); + + return ctx.ret; +} + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_hfsplus_open (struct grub_file *file, const char *name) +{ + struct grub_hfsplus_data *data; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_hfsplus_mount (file->device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file (name, &data->dirroot, &fdiro, + grub_hfsplus_iterate_dir, + grub_hfsplus_read_symlink, GRUB_FSHELP_REG); + if (grub_errno) + goto fail; + + if (grub_hfsplus_open_compressed) + { + grub_err_t err; + err = grub_hfsplus_open_compressed (fdiro); + if (err) + goto fail; + } + + file->size = fdiro->size; + data->opened_file = *fdiro; + grub_free (fdiro); + + file->data = data; + file->offset = 0; + + return 0; + + fail: + if (data && fdiro != &data->dirroot) + grub_free (fdiro); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +static grub_err_t +grub_hfsplus_close (grub_file_t file) +{ + struct grub_hfsplus_data *data = + (struct grub_hfsplus_data *) file->data; + + grub_free (data->opened_file.cbuf); + grub_free (data->opened_file.compress_index); + + grub_free (data); + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + +/* Read LEN bytes data from FILE into BUF. */ +static grub_ssize_t +grub_hfsplus_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_hfsplus_data *data = + (struct grub_hfsplus_data *) file->data; + + data->opened_file.file = file; + + if (grub_hfsplus_read_compressed && data->opened_file.compressed) + return grub_hfsplus_read_compressed (&data->opened_file, + file->offset, len, buf); + + return grub_hfsplus_read_file (&data->opened_file, + file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + +/* Context for grub_hfsplus_dir. */ +struct grub_hfsplus_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_hfsplus_dir. */ +static int +grub_hfsplus_dir_iter (const char *filename, + enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_hfsplus_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 = node->mtime; + info.inodeset = 1; + info.inode = node->fileid; + info.case_insensitive = !! (filetype & GRUB_FSHELP_CASE_INSENSITIVE); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_hfsplus_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_hfsplus_dir_ctx ctx = { hook, hook_data }; + struct grub_hfsplus_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_hfsplus_mount (device->disk); + if (!data) + goto fail; + + /* Find the directory that should be opened. */ + grub_fshelp_find_file (path, &data->dirroot, &fdiro, + grub_hfsplus_iterate_dir, + grub_hfsplus_read_symlink, GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + + /* Iterate over all entries in this directory. */ + grub_hfsplus_iterate_dir (fdiro, grub_hfsplus_dir_iter, &ctx); + + fail: + if (data && fdiro != &data->dirroot) + grub_free (fdiro); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +static grub_err_t +grub_hfsplus_label (grub_device_t device, char **label) +{ + struct grub_hfsplus_data *data; + grub_disk_t disk = device->disk; + struct grub_hfsplus_catkey *catkey; + int i, label_len; + grub_uint16_t *label_name; + struct grub_hfsplus_key_internal intern; + struct grub_hfsplus_btnode *node = NULL; + grub_disk_addr_t ptr = 0; + + *label = 0; + + data = grub_hfsplus_mount (disk); + if (!data) + return grub_errno; + + /* Create a key that points to the label. */ + intern.catkey.parent = 1; + intern.catkey.name = 0; + intern.catkey.namelen = 0; + + /* First lookup the first entry. */ + if (grub_hfsplus_btree_search (&data->catalog_tree, &intern, + grub_hfsplus_cmp_catkey_id, &node, &ptr) + || !node) + { + grub_free (data); + return 0; + } + + catkey = (struct grub_hfsplus_catkey *) + grub_hfsplus_btree_recptr (&data->catalog_tree, node, ptr); + + label_len = grub_be_to_cpu16 (catkey->namelen); + + /* Ensure that the length is >= 0. */ + if (label_len < 0) + label_len = 0; + + /* Ensure label length is at most 255 Unicode characters. */ + if (label_len > 255) + label_len = 255; + + label_name = grub_calloc (label_len, sizeof (*label_name)); + if (!label_name) + { + grub_free (node); + grub_free (data); + return grub_errno; + } + + for (i = 0; i < label_len; i++) + { + label_name[i] = grub_be_to_cpu16 (catkey->name[i]); + + /* If the name is obviously invalid, skip this node. */ + if (label_name[i] == 0) + { + grub_free (label_name); + grub_free (node); + grub_free (data); + return 0; + } + } + + *label = grub_calloc (label_len, GRUB_MAX_UTF8_PER_UTF16 + 1); + if (! *label) + { + grub_free (label_name); + grub_free (node); + grub_free (data); + return grub_errno; + } + + *grub_utf16_to_utf8 ((grub_uint8_t *) (*label), label_name, + label_len) = '\0'; + + grub_free (label_name); + grub_free (node); + grub_free (data); + + return GRUB_ERR_NONE; +} + +/* Get mtime. */ +static grub_err_t +grub_hfsplus_mtime (grub_device_t device, grub_int64_t *tm) +{ + struct grub_hfsplus_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_hfsplus_mount (disk); + if (!data) + *tm = 0; + else + *tm = grub_be_to_cpu32 (data->volheader.utime) - 2082844800; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; + +} + +static grub_err_t +grub_hfsplus_uuid (grub_device_t device, char **uuid) +{ + struct grub_hfsplus_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_hfsplus_mount (disk); + if (data) + { + *uuid = grub_xasprintf ("%016llx", + (unsigned long long) + grub_be_to_cpu64 (data->volheader.num_serial)); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + + +static struct grub_fs grub_hfsplus_fs = + { + .name = "hfsplus", + .fs_dir = grub_hfsplus_dir, + .fs_open = grub_hfsplus_open, + .fs_read = grub_hfsplus_read, + .fs_close = grub_hfsplus_close, + .fs_label = grub_hfsplus_label, + .fs_mtime = grub_hfsplus_mtime, + .fs_uuid = grub_hfsplus_uuid, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(hfsplus) +{ + grub_fs_register (&grub_hfsplus_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(hfsplus) +{ + grub_fs_unregister (&grub_hfsplus_fs); +} diff --git a/grub-core/fs/hfspluscomp.c b/grub-core/fs/hfspluscomp.c new file mode 100644 index 0000000..d76f3f1 --- /dev/null +++ b/grub-core/fs/hfspluscomp.c @@ -0,0 +1,317 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2012 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/>. + */ + +/* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */ + +#include <grub/hfsplus.h> +#include <grub/dl.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/deflate.h> +#include <grub/file.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* big-endian. */ +struct grub_hfsplus_compress_header1 +{ + grub_uint32_t header_size; + grub_uint32_t end_descriptor_offset; + grub_uint32_t total_compressed_size_including_seek_blocks_and_header2; + grub_uint32_t value_0x32; + grub_uint8_t unused[0xf0]; +} GRUB_PACKED; + +/* big-endian. */ +struct grub_hfsplus_compress_header2 +{ + grub_uint32_t total_compressed_size_including_seek_blocks; +} GRUB_PACKED; + +/* little-endian. */ +struct grub_hfsplus_compress_header3 +{ + grub_uint32_t num_chunks; +} GRUB_PACKED; + +/* little-endian. */ +struct grub_hfsplus_compress_block_descriptor +{ + grub_uint32_t offset; + grub_uint32_t size; +}; + +struct grub_hfsplus_compress_end_descriptor +{ + grub_uint8_t always_the_same[50]; +} GRUB_PACKED; + +struct grub_hfsplus_attr_header +{ + grub_uint8_t unused[3]; + grub_uint8_t type; + grub_uint32_t unknown[1]; + grub_uint64_t size; +} GRUB_PACKED; + +struct grub_hfsplus_compress_attr +{ + grub_uint32_t magic; + grub_uint32_t type; + grub_uint32_t uncompressed_inline_size; + grub_uint32_t always_0; +} GRUB_PACKED; + +enum + { + HFSPLUS_COMPRESSION_INLINE = 3, + HFSPLUS_COMPRESSION_RESOURCE = 4 + }; + +static int +grub_hfsplus_cmp_attrkey (struct grub_hfsplus_key *keya, + struct grub_hfsplus_key_internal *keyb) +{ + struct grub_hfsplus_attrkey *attrkey_a = &keya->attrkey; + struct grub_hfsplus_attrkey_internal *attrkey_b = &keyb->attrkey; + grub_uint32_t aparent = grub_be_to_cpu32 (attrkey_a->cnid); + grub_size_t len; + int diff; + + if (aparent > attrkey_b->cnid) + return 1; + if (aparent < attrkey_b->cnid) + return -1; + + len = grub_be_to_cpu16 (attrkey_a->namelen); + if (len > attrkey_b->namelen) + len = attrkey_b->namelen; + /* Since it's big-endian memcmp gives the same result as manually comparing + uint16_t but may be faster. */ + diff = grub_memcmp (attrkey_a->name, attrkey_b->name, + len * sizeof (attrkey_a->name[0])); + if (diff == 0) + diff = grub_be_to_cpu16 (attrkey_a->namelen) - attrkey_b->namelen; + return diff; +} + +#define HFSPLUS_COMPRESS_BLOCK_SIZE 65536 + +static grub_ssize_t +hfsplus_read_compressed_real (struct grub_hfsplus_file *node, + grub_off_t pos, grub_size_t len, char *buf) +{ + char *tmp_buf = 0; + grub_size_t len0 = len; + + if (node->compressed == 1) + { + grub_memcpy (buf, node->cbuf + pos, len); + if (grub_file_progress_hook && node->file) + grub_file_progress_hook (0, 0, len, node->file); + return len; + } + + while (len) + { + grub_uint32_t block = pos / HFSPLUS_COMPRESS_BLOCK_SIZE; + grub_size_t curlen = HFSPLUS_COMPRESS_BLOCK_SIZE + - (pos % HFSPLUS_COMPRESS_BLOCK_SIZE); + + if (curlen > len) + curlen = len; + + if (node->cbuf_block != block) + { + grub_uint32_t sz = grub_le_to_cpu32 (node->compress_index[block].size); + grub_size_t ts; + if (!tmp_buf) + tmp_buf = grub_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE); + if (!tmp_buf) + return -1; + if (grub_hfsplus_read_file (node, 0, 0, + grub_le_to_cpu32 (node->compress_index[block].start) + 0x104, + sz, tmp_buf) + != (grub_ssize_t) sz) + { + grub_free (tmp_buf); + return -1; + } + ts = HFSPLUS_COMPRESS_BLOCK_SIZE; + if (ts > node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE))) + ts = node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE)); + if (grub_zlib_decompress (tmp_buf, sz, 0, + node->cbuf, ts) != (grub_ssize_t) ts) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "premature end of compressed"); + + grub_free (tmp_buf); + return -1; + } + node->cbuf_block = block; + } + grub_memcpy (buf, node->cbuf + (pos % HFSPLUS_COMPRESS_BLOCK_SIZE), + curlen); + if (grub_file_progress_hook && node->file) + grub_file_progress_hook (0, 0, curlen, node->file); + buf += curlen; + pos += curlen; + len -= curlen; + } + grub_free (tmp_buf); + return len0; +} + +static grub_err_t +hfsplus_open_compressed_real (struct grub_hfsplus_file *node) +{ + grub_err_t err; + struct grub_hfsplus_btnode *attr_node; + grub_off_t attr_off; + struct grub_hfsplus_key_internal key; + struct grub_hfsplus_attr_header *attr_head; + struct grub_hfsplus_compress_attr *cmp_head; +#define c grub_cpu_to_be16_compile_time + const grub_uint16_t compress_attr_name[] = + { + c('c'), c('o'), c('m'), c('.'), c('a'), c('p'), c('p'), c('l'), c('e'), + c('.'), c('d'), c('e'), c('c'), c('m'), c('p'), c('f'), c('s') }; +#undef c + if (node->size) + return 0; + + key.attrkey.cnid = node->fileid; + key.attrkey.namelen = sizeof (compress_attr_name) / sizeof (compress_attr_name[0]); + key.attrkey.name = compress_attr_name; + + err = grub_hfsplus_btree_search (&node->data->attr_tree, &key, + grub_hfsplus_cmp_attrkey, + &attr_node, &attr_off); + if (err || !attr_node) + { + grub_errno = 0; + return 0; + } + + attr_head = (struct grub_hfsplus_attr_header *) + ((char *) grub_hfsplus_btree_recptr (&node->data->attr_tree, + attr_node, attr_off) + + sizeof (struct grub_hfsplus_attrkey) + sizeof (compress_attr_name)); + if (attr_head->type != 0x10 + || !(attr_head->size & grub_cpu_to_be64_compile_time(~0xfULL))) + { + grub_free (attr_node); + return 0; + } + cmp_head = (struct grub_hfsplus_compress_attr *) (attr_head + 1); + if (cmp_head->magic != grub_cpu_to_be32_compile_time (0x66706d63)) + { + grub_free (attr_node); + return 0; + } + node->size = grub_le_to_cpu32 (cmp_head->uncompressed_inline_size); + + if (cmp_head->type == grub_cpu_to_le32_compile_time (HFSPLUS_COMPRESSION_RESOURCE)) + { + grub_uint32_t index_size; + node->compressed = 2; + + if (grub_hfsplus_read_file (node, 0, 0, + 0x104, sizeof (index_size), + (char *) &index_size) + != 4) + { + node->compressed = 0; + grub_free (attr_node); + grub_errno = 0; + return 0; + } + node->compress_index_size = grub_le_to_cpu32 (index_size); + node->compress_index = grub_malloc (node->compress_index_size + * sizeof (node->compress_index[0])); + if (!node->compress_index) + { + node->compressed = 0; + grub_free (attr_node); + return grub_errno; + } + if (grub_hfsplus_read_file (node, 0, 0, + 0x104 + sizeof (index_size), + node->compress_index_size + * sizeof (node->compress_index[0]), + (char *) node->compress_index) + != (grub_ssize_t) (node->compress_index_size + * sizeof (node->compress_index[0]))) + { + node->compressed = 0; + grub_free (attr_node); + grub_free (node->compress_index); + grub_errno = 0; + return 0; + } + + node->cbuf_block = -1; + + node->cbuf = grub_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE); + grub_free (attr_node); + if (!node->cbuf) + { + node->compressed = 0; + grub_free (node->compress_index); + return grub_errno; + } + return 0; + } + if (cmp_head->type != HFSPLUS_COMPRESSION_INLINE) + { + grub_free (attr_node); + return 0; + } + + node->cbuf = grub_malloc (node->size); + if (!node->cbuf) + return grub_errno; + + if (grub_zlib_decompress ((char *) (cmp_head + 1), + grub_cpu_to_be64 (attr_head->size) + - sizeof (*cmp_head), 0, + node->cbuf, node->size) + != (grub_ssize_t) node->size) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "premature end of compressed"); + return grub_errno; + } + node->compressed = 1; + return 0; +} + +GRUB_MOD_INIT(hfspluscomp) +{ + grub_hfsplus_open_compressed = hfsplus_open_compressed_real; + grub_hfsplus_read_compressed = hfsplus_read_compressed_real; +} + +GRUB_MOD_FINI(hfspluscomp) +{ + grub_hfsplus_open_compressed = 0; + grub_hfsplus_read_compressed = 0; +} diff --git a/grub-core/fs/iso9660.c b/grub-core/fs/iso9660.c new file mode 100644 index 0000000..ac01195 --- /dev/null +++ b/grub-core/fs/iso9660.c @@ -0,0 +1,1162 @@ +/* iso9660.c - iso9660 implementation with extensions: + SUSP, Rock Ridge. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,2005,2006,2007,2008,2009,2010 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/datetime.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_ISO9660_FSTYPE_DIR 0040000 +#define GRUB_ISO9660_FSTYPE_REG 0100000 +#define GRUB_ISO9660_FSTYPE_SYMLINK 0120000 +#define GRUB_ISO9660_FSTYPE_MASK 0170000 + +#define GRUB_ISO9660_LOG2_BLKSZ 2 +#define GRUB_ISO9660_BLKSZ 2048 + +#define GRUB_ISO9660_RR_DOT 2 +#define GRUB_ISO9660_RR_DOTDOT 4 + +#define GRUB_ISO9660_VOLDESC_BOOT 0 +#define GRUB_ISO9660_VOLDESC_PRIMARY 1 +#define GRUB_ISO9660_VOLDESC_SUPP 2 +#define GRUB_ISO9660_VOLDESC_PART 3 +#define GRUB_ISO9660_VOLDESC_END 255 + +/* The head of a volume descriptor. */ +struct grub_iso9660_voldesc +{ + grub_uint8_t type; + grub_uint8_t magic[5]; + grub_uint8_t version; +} GRUB_PACKED; + +struct grub_iso9660_date2 +{ + grub_uint8_t year; + grub_uint8_t month; + grub_uint8_t day; + grub_uint8_t hour; + grub_uint8_t minute; + grub_uint8_t second; + grub_uint8_t offset; +} GRUB_PACKED; + +/* A directory entry. */ +struct grub_iso9660_dir +{ + grub_uint8_t len; + grub_uint8_t ext_sectors; + grub_uint32_t first_sector; + grub_uint32_t first_sector_be; + grub_uint32_t size; + grub_uint32_t size_be; + struct grub_iso9660_date2 mtime; + grub_uint8_t flags; + grub_uint8_t unused2[6]; +#define MAX_NAMELEN 255 + grub_uint8_t namelen; +} GRUB_PACKED; + +struct grub_iso9660_date +{ + grub_uint8_t year[4]; + grub_uint8_t month[2]; + grub_uint8_t day[2]; + grub_uint8_t hour[2]; + grub_uint8_t minute[2]; + grub_uint8_t second[2]; + grub_uint8_t hundredth[2]; + grub_uint8_t offset; +} GRUB_PACKED; + +/* The primary volume descriptor. Only little endian is used. */ +struct grub_iso9660_primary_voldesc +{ + struct grub_iso9660_voldesc voldesc; + grub_uint8_t unused1[33]; + grub_uint8_t volname[32]; + grub_uint8_t unused2[16]; + grub_uint8_t escape[32]; + grub_uint8_t unused3[12]; + grub_uint32_t path_table_size; + grub_uint8_t unused4[4]; + grub_uint32_t path_table; + grub_uint8_t unused5[12]; + struct grub_iso9660_dir rootdir; + grub_uint8_t unused6[624]; + struct grub_iso9660_date created; + struct grub_iso9660_date modified; +} GRUB_PACKED; + +/* A single entry in the path table. */ +struct grub_iso9660_path +{ + grub_uint8_t len; + grub_uint8_t sectors; + grub_uint32_t first_sector; + grub_uint16_t parentdir; + grub_uint8_t name[0]; +} GRUB_PACKED; + +/* An entry in the System Usage area of the directory entry. */ +struct grub_iso9660_susp_entry +{ + grub_uint8_t sig[2]; + grub_uint8_t len; + grub_uint8_t version; + grub_uint8_t data[0]; +} GRUB_PACKED; + +/* The CE entry. This is used to describe the next block where data + can be found. */ +struct grub_iso9660_susp_ce +{ + struct grub_iso9660_susp_entry entry; + grub_uint32_t blk; + grub_uint32_t blk_be; + grub_uint32_t off; + grub_uint32_t off_be; + grub_uint32_t len; + grub_uint32_t len_be; +} GRUB_PACKED; + +struct grub_iso9660_data +{ + struct grub_iso9660_primary_voldesc voldesc; + grub_disk_t disk; + int rockridge; + int susp_skip; + int joliet; + struct grub_fshelp_node *node; +}; + +struct grub_fshelp_node +{ + struct grub_iso9660_data *data; + grub_size_t have_dirents, alloc_dirents; + int have_symlink; + struct grub_iso9660_dir dirents[8]; + char symlink[0]; +}; + +enum + { + FLAG_TYPE_PLAIN = 0, + FLAG_TYPE_DIR = 2, + FLAG_TYPE = 3, + FLAG_MORE_EXTENTS = 0x80 + }; + +static grub_dl_t my_mod; + + +static grub_err_t +iso9660_to_unixtime (const struct grub_iso9660_date *i, grub_int64_t *nix) +{ + struct grub_datetime datetime; + + if (! i->year[0] && ! i->year[1] + && ! i->year[2] && ! i->year[3] + && ! i->month[0] && ! i->month[1] + && ! i->day[0] && ! i->day[1] + && ! i->hour[0] && ! i->hour[1] + && ! i->minute[0] && ! i->minute[1] + && ! i->second[0] && ! i->second[1] + && ! i->hundredth[0] && ! i->hundredth[1]) + return grub_error (GRUB_ERR_BAD_NUMBER, "empty date"); + datetime.year = (i->year[0] - '0') * 1000 + (i->year[1] - '0') * 100 + + (i->year[2] - '0') * 10 + (i->year[3] - '0'); + datetime.month = (i->month[0] - '0') * 10 + (i->month[1] - '0'); + datetime.day = (i->day[0] - '0') * 10 + (i->day[1] - '0'); + datetime.hour = (i->hour[0] - '0') * 10 + (i->hour[1] - '0'); + datetime.minute = (i->minute[0] - '0') * 10 + (i->minute[1] - '0'); + datetime.second = (i->second[0] - '0') * 10 + (i->second[1] - '0'); + + if (!grub_datetime2unixtime (&datetime, nix)) + return grub_error (GRUB_ERR_BAD_NUMBER, "incorrect date"); + *nix -= i->offset * 60 * 15; + return GRUB_ERR_NONE; +} + +static int +iso9660_to_unixtime2 (const struct grub_iso9660_date2 *i, grub_int64_t *nix) +{ + struct grub_datetime datetime; + + datetime.year = i->year + 1900; + datetime.month = i->month; + datetime.day = i->day; + datetime.hour = i->hour; + datetime.minute = i->minute; + datetime.second = i->second; + + if (!grub_datetime2unixtime (&datetime, nix)) + return 0; + *nix -= i->offset * 60 * 15; + return 1; +} + +static grub_err_t +read_node (grub_fshelp_node_t node, grub_off_t off, grub_size_t len, char *buf) +{ + grub_size_t i = 0; + + while (len > 0) + { + grub_size_t toread; + grub_err_t err; + while (i < node->have_dirents + && off >= grub_le_to_cpu32 (node->dirents[i].size)) + { + off -= grub_le_to_cpu32 (node->dirents[i].size); + i++; + } + if (i == node->have_dirents) + return grub_error (GRUB_ERR_OUT_OF_RANGE, "read out of range"); + toread = grub_le_to_cpu32 (node->dirents[i].size); + if (toread > len) + toread = len; + err = grub_disk_read (node->data->disk, + ((grub_disk_addr_t) grub_le_to_cpu32 (node->dirents[i].first_sector)) << GRUB_ISO9660_LOG2_BLKSZ, + off, toread, buf); + if (err) + return err; + len -= toread; + off += toread; + buf += toread; + } + return GRUB_ERR_NONE; +} + +/* Iterate over the susp entries, starting with block SUA_BLOCK on the + offset SUA_POS with a size of SUA_SIZE bytes. Hook is called for + every entry. */ +static grub_err_t +grub_iso9660_susp_iterate (grub_fshelp_node_t node, grub_off_t off, + grub_ssize_t sua_size, + grub_err_t (*hook) + (struct grub_iso9660_susp_entry *entry, void *hook_arg), + void *hook_arg) +{ + char *sua; + struct grub_iso9660_susp_entry *entry; + grub_err_t err; + + if (sua_size <= 0) + return GRUB_ERR_NONE; + + sua = grub_malloc (sua_size); + if (!sua) + return grub_errno; + + /* Load a part of the System Usage Area. */ + err = read_node (node, off, sua_size, sua); + if (err) + return err; + + for (entry = (struct grub_iso9660_susp_entry *) sua; (char *) entry < (char *) sua + sua_size - 1 && entry->len > 0; + entry = (struct grub_iso9660_susp_entry *) + ((char *) entry + entry->len)) + { + /* The last entry. */ + if (grub_strncmp ((char *) entry->sig, "ST", 2) == 0) + break; + + /* Additional entries are stored elsewhere. */ + if (grub_strncmp ((char *) entry->sig, "CE", 2) == 0) + { + struct grub_iso9660_susp_ce *ce; + grub_disk_addr_t ce_block; + + ce = (struct grub_iso9660_susp_ce *) entry; + sua_size = grub_le_to_cpu32 (ce->len); + off = grub_le_to_cpu32 (ce->off); + ce_block = grub_le_to_cpu32 (ce->blk) << GRUB_ISO9660_LOG2_BLKSZ; + + grub_free (sua); + sua = grub_malloc (sua_size); + if (!sua) + return grub_errno; + + /* Load a part of the System Usage Area. */ + err = grub_disk_read (node->data->disk, ce_block, off, + sua_size, sua); + if (err) + return err; + + entry = (struct grub_iso9660_susp_entry *) sua; + } + + if (hook (entry, hook_arg)) + { + grub_free (sua); + return 0; + } + } + + grub_free (sua); + return 0; +} + +static char * +grub_iso9660_convert_string (grub_uint8_t *us, int len) +{ + char *p; + int i; + grub_uint16_t t[MAX_NAMELEN / 2 + 1]; + + p = grub_calloc (len, GRUB_MAX_UTF8_PER_UTF16 + 1); + if (! p) + return NULL; + + for (i=0; i<len; i++) + t[i] = grub_be_to_cpu16 (grub_get_unaligned16 (us + 2 * i)); + + *grub_utf16_to_utf8 ((grub_uint8_t *) p, t, len) = '\0'; + + return p; +} + +static grub_err_t +susp_iterate_set_rockridge (struct grub_iso9660_susp_entry *susp_entry, + void *_data) +{ + struct grub_iso9660_data *data = _data; + /* The "ER" entry is used to detect extensions. The + `IEEE_P1285' extension means Rock ridge. */ + if (grub_strncmp ((char *) susp_entry->sig, "ER", 2) == 0) + { + data->rockridge = 1; + return 1; + } + return 0; +} + +static grub_err_t +set_rockridge (struct grub_iso9660_data *data) +{ + int sua_pos; + int sua_size; + char *sua; + struct grub_iso9660_dir rootdir; + struct grub_iso9660_susp_entry *entry; + + data->rockridge = 0; + + /* Read the system use area and test it to see if SUSP is + supported. */ + if (grub_disk_read (data->disk, + (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector) + << GRUB_ISO9660_LOG2_BLKSZ), 0, + sizeof (rootdir), (char *) &rootdir)) + return grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); + + sua_pos = (sizeof (rootdir) + rootdir.namelen + + (rootdir.namelen % 2) - 1); + sua_size = rootdir.len - sua_pos; + + if (!sua_size) + return GRUB_ERR_NONE; + + sua = grub_malloc (sua_size); + if (! sua) + return grub_errno; + + if (grub_disk_read (data->disk, + (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector) + << GRUB_ISO9660_LOG2_BLKSZ), sua_pos, + sua_size, sua)) + { + grub_free (sua); + return grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); + } + + entry = (struct grub_iso9660_susp_entry *) sua; + + /* Test if the SUSP protocol is used on this filesystem. */ + if (grub_strncmp ((char *) entry->sig, "SP", 2) == 0) + { + struct grub_fshelp_node rootnode; + + rootnode.data = data; + rootnode.alloc_dirents = ARRAY_SIZE (rootnode.dirents); + rootnode.have_dirents = 1; + rootnode.have_symlink = 0; + rootnode.dirents[0] = data->voldesc.rootdir; + + /* The 2nd data byte stored how many bytes are skipped every time + to get to the SUA (System Usage Area). */ + data->susp_skip = entry->data[2]; + entry = (struct grub_iso9660_susp_entry *) ((char *) entry + entry->len); + + /* Iterate over the entries in the SUA area to detect + extensions. */ + if (grub_iso9660_susp_iterate (&rootnode, + sua_pos, sua_size, susp_iterate_set_rockridge, + data)) + { + grub_free (sua); + return grub_errno; + } + } + grub_free (sua); + return GRUB_ERR_NONE; +} + +static struct grub_iso9660_data * +grub_iso9660_mount (grub_disk_t disk) +{ + struct grub_iso9660_data *data = 0; + struct grub_iso9660_primary_voldesc voldesc; + int block; + + data = grub_zalloc (sizeof (struct grub_iso9660_data)); + if (! data) + return 0; + + data->disk = disk; + + block = 16; + do + { + int copy_voldesc = 0; + + /* Read the superblock. */ + if (grub_disk_read (disk, block << GRUB_ISO9660_LOG2_BLKSZ, 0, + sizeof (struct grub_iso9660_primary_voldesc), + (char *) &voldesc)) + { + grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); + goto fail; + } + + if (grub_strncmp ((char *) voldesc.voldesc.magic, "CD001", 5) != 0) + { + grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); + goto fail; + } + + if (voldesc.voldesc.type == GRUB_ISO9660_VOLDESC_PRIMARY) + copy_voldesc = 1; + else if (!data->rockridge + && (voldesc.voldesc.type == GRUB_ISO9660_VOLDESC_SUPP) + && (voldesc.escape[0] == 0x25) && (voldesc.escape[1] == 0x2f) + && + ((voldesc.escape[2] == 0x40) || /* UCS-2 Level 1. */ + (voldesc.escape[2] == 0x43) || /* UCS-2 Level 2. */ + (voldesc.escape[2] == 0x45))) /* UCS-2 Level 3. */ + { + copy_voldesc = 1; + data->joliet = 1; + } + + if (copy_voldesc) + { + grub_memcpy((char *) &data->voldesc, (char *) &voldesc, + sizeof (struct grub_iso9660_primary_voldesc)); + if (set_rockridge (data)) + goto fail; + } + + block++; + } while (voldesc.voldesc.type != GRUB_ISO9660_VOLDESC_END); + + return data; + + fail: + grub_free (data); + return 0; +} + + +static char * +grub_iso9660_read_symlink (grub_fshelp_node_t node) +{ + return node->have_symlink + ? grub_strdup (node->symlink + + (node->have_dirents) * sizeof (node->dirents[0]) + - sizeof (node->dirents)) : grub_strdup (""); +} + +static grub_off_t +get_node_size (grub_fshelp_node_t node) +{ + grub_off_t ret = 0; + grub_size_t i; + + for (i = 0; i < node->have_dirents; i++) + ret += grub_le_to_cpu32 (node->dirents[i].size); + return ret; +} + +struct iterate_dir_ctx +{ + char *filename; + int filename_alloc; + enum grub_fshelp_filetype type; + char *symlink; + int was_continue; +}; + + /* Extend the symlink. */ +static void +add_part (struct iterate_dir_ctx *ctx, + const char *part, + int len2) +{ + int size = ctx->symlink ? grub_strlen (ctx->symlink) : 0; + grub_size_t sz; + char *new; + + if (grub_add (size, len2, &sz) || + grub_add (sz, 1, &sz)) + return; + + new = grub_realloc (ctx->symlink, sz); + if (!new) + { + grub_free (ctx->symlink); + ctx->symlink = NULL; + return; + } + ctx->symlink = new; + + grub_memcpy (ctx->symlink + size, part, len2); + ctx->symlink[size + len2] = 0; +} + +static grub_err_t +susp_iterate_dir (struct grub_iso9660_susp_entry *entry, + void *_ctx) +{ + struct iterate_dir_ctx *ctx = _ctx; + + /* The filename in the rock ridge entry. */ + if (grub_strncmp ("NM", (char *) entry->sig, 2) == 0) + { + /* The flags are stored at the data position 0, here the + filename type is stored. */ + /* FIXME: Fix this slightly improper cast. */ + if (entry->data[0] & GRUB_ISO9660_RR_DOT) + ctx->filename = (char *) "."; + else if (entry->data[0] & GRUB_ISO9660_RR_DOTDOT) + ctx->filename = (char *) ".."; + else if (entry->len >= 5) + { + grub_size_t off = 0, csize = 1; + char *old; + grub_size_t sz; + + csize = entry->len - 5; + old = ctx->filename; + if (ctx->filename_alloc) + { + off = grub_strlen (ctx->filename); + if (grub_add (csize, off, &sz) || + grub_add (sz, 1, &sz)) + return GRUB_ERR_OUT_OF_RANGE; + ctx->filename = grub_realloc (ctx->filename, sz); + } + else + { + off = 0; + if (grub_add (csize, 1, &sz)) + return GRUB_ERR_OUT_OF_RANGE; + ctx->filename = grub_zalloc (sz); + } + if (!ctx->filename) + { + ctx->filename = old; + return grub_errno; + } + ctx->filename_alloc = 1; + grub_memcpy (ctx->filename + off, (char *) &entry->data[1], csize); + ctx->filename[off + csize] = '\0'; + } + } + /* The mode information (st_mode). */ + else if (grub_strncmp ((char *) entry->sig, "PX", 2) == 0) + { + /* At position 0 of the PX record the st_mode information is + stored (little-endian). */ + grub_uint32_t mode = ((entry->data[0] + (entry->data[1] << 8)) + & GRUB_ISO9660_FSTYPE_MASK); + + switch (mode) + { + case GRUB_ISO9660_FSTYPE_DIR: + ctx->type = GRUB_FSHELP_DIR; + break; + case GRUB_ISO9660_FSTYPE_REG: + ctx->type = GRUB_FSHELP_REG; + break; + case GRUB_ISO9660_FSTYPE_SYMLINK: + ctx->type = GRUB_FSHELP_SYMLINK; + break; + default: + ctx->type = GRUB_FSHELP_UNKNOWN; + } + } + else if (grub_strncmp ("SL", (char *) entry->sig, 2) == 0) + { + unsigned int pos = 1; + + /* The symlink is not stored as a POSIX symlink, translate it. */ + while (pos + sizeof (*entry) < entry->len) + { + /* The current position is the `Component Flag'. */ + switch (entry->data[pos] & 30) + { + case 0: + { + /* The data on pos + 2 is the actual data, pos + 1 + is the length. Both are part of the `Component + Record'. */ + if (ctx->symlink && !ctx->was_continue) + { + add_part (ctx, "/", 1); + if (grub_errno) + return grub_errno; + } + + add_part (ctx, (char *) &entry->data[pos + 2], + entry->data[pos + 1]); + ctx->was_continue = (entry->data[pos] & 1); + break; + } + + case 2: + add_part (ctx, "./", 2); + break; + + case 4: + add_part (ctx, "../", 3); + break; + + case 8: + add_part (ctx, "/", 1); + break; + } + + /* Check if grub_realloc() failed in add_part(). */ + if (grub_errno) + return grub_errno; + + /* In pos + 1 the length of the `Component Record' is + stored. */ + pos += entry->data[pos + 1] + 2; + } + + /* Check if `grub_realloc' failed. */ + if (grub_errno) + return grub_errno; + } + + return 0; +} + +static int +grub_iso9660_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + struct grub_iso9660_dir dirent; + grub_off_t offset = 0; + grub_off_t len; + struct iterate_dir_ctx ctx; + + len = get_node_size (dir); + + for (; offset < len; offset += dirent.len) + { + ctx.symlink = 0; + ctx.was_continue = 0; + + if (read_node (dir, offset, sizeof (dirent), (char *) &dirent)) + return 0; + + /* The end of the block, skip to the next one. */ + if (!dirent.len) + { + offset = (offset / GRUB_ISO9660_BLKSZ + 1) * GRUB_ISO9660_BLKSZ; + continue; + } + + { + char name[MAX_NAMELEN + 1]; + int nameoffset = offset + sizeof (dirent); + struct grub_fshelp_node *node; + int sua_off = (sizeof (dirent) + dirent.namelen + 1 + - (dirent.namelen % 2)); + int sua_size = dirent.len - sua_off; + + sua_off += offset + dir->data->susp_skip; + + ctx.filename = 0; + ctx.filename_alloc = 0; + ctx.type = GRUB_FSHELP_UNKNOWN; + + if (dir->data->rockridge + && grub_iso9660_susp_iterate (dir, sua_off, sua_size, + susp_iterate_dir, &ctx)) + return 0; + + /* Read the name. */ + if (read_node (dir, nameoffset, dirent.namelen, (char *) name)) + return 0; + + node = grub_malloc (sizeof (struct grub_fshelp_node)); + if (!node) + return 0; + + node->alloc_dirents = ARRAY_SIZE (node->dirents); + node->have_dirents = 1; + + /* Setup a new node. */ + node->data = dir->data; + node->have_symlink = 0; + + /* If the filetype was not stored using rockridge, use + whatever is stored in the iso9660 filesystem. */ + if (ctx.type == GRUB_FSHELP_UNKNOWN) + { + if ((dirent.flags & FLAG_TYPE) == FLAG_TYPE_DIR) + ctx.type = GRUB_FSHELP_DIR; + else + ctx.type = GRUB_FSHELP_REG; + } + + /* . and .. */ + if (!ctx.filename && dirent.namelen == 1 && name[0] == 0) + ctx.filename = (char *) "."; + + if (!ctx.filename && dirent.namelen == 1 && name[0] == 1) + ctx.filename = (char *) ".."; + + /* The filename was not stored in a rock ridge entry. Read it + from the iso9660 filesystem. */ + if (!dir->data->joliet && !ctx.filename) + { + char *ptr; + name[dirent.namelen] = '\0'; + ctx.filename = grub_strrchr (name, ';'); + if (ctx.filename) + *ctx.filename = '\0'; + /* ISO9660 names are not case-preserving. */ + ctx.type |= GRUB_FSHELP_CASE_INSENSITIVE; + for (ptr = name; *ptr; ptr++) + *ptr = grub_tolower (*ptr); + if (ptr != name && *(ptr - 1) == '.') + *(ptr - 1) = 0; + ctx.filename = name; + } + + if (dir->data->joliet && !ctx.filename) + { + char *semicolon; + + ctx.filename = grub_iso9660_convert_string + ((grub_uint8_t *) name, dirent.namelen >> 1); + + semicolon = grub_strrchr (ctx.filename, ';'); + if (semicolon) + *semicolon = '\0'; + + ctx.filename_alloc = 1; + } + + node->dirents[0] = dirent; + while (dirent.flags & FLAG_MORE_EXTENTS) + { + offset += dirent.len; + if (read_node (dir, offset, sizeof (dirent), (char *) &dirent)) + { + if (ctx.filename_alloc) + grub_free (ctx.filename); + grub_free (node); + return 0; + } + if (node->have_dirents >= node->alloc_dirents) + { + struct grub_fshelp_node *new_node; + grub_size_t sz; + + if (grub_mul (node->alloc_dirents, 2, &node->alloc_dirents) || + grub_sub (node->alloc_dirents, ARRAY_SIZE (node->dirents), &sz) || + grub_mul (sz, sizeof (node->dirents[0]), &sz) || + grub_add (sz, sizeof (struct grub_fshelp_node), &sz)) + goto fail_0; + + new_node = grub_realloc (node, sz); + if (!new_node) + { + fail_0: + if (ctx.filename_alloc) + grub_free (ctx.filename); + grub_free (node); + return 0; + } + node = new_node; + } + node->dirents[node->have_dirents++] = dirent; + } + if (ctx.symlink) + { + if ((node->alloc_dirents - node->have_dirents) + * sizeof (node->dirents[0]) < grub_strlen (ctx.symlink) + 1) + { + struct grub_fshelp_node *new_node; + grub_size_t sz; + + if (grub_sub (node->alloc_dirents, ARRAY_SIZE (node->dirents), &sz) || + grub_mul (sz, sizeof (node->dirents[0]), &sz) || + grub_add (sz, sizeof (struct grub_fshelp_node) + 1, &sz) || + grub_add (sz, grub_strlen (ctx.symlink), &sz)) + goto fail_1; + + new_node = grub_realloc (node, sz); + if (!new_node) + { + fail_1: + if (ctx.filename_alloc) + grub_free (ctx.filename); + grub_free (node); + return 0; + } + node = new_node; + } + node->have_symlink = 1; + grub_strcpy (node->symlink + + node->have_dirents * sizeof (node->dirents[0]) + - sizeof (node->dirents), ctx.symlink); + grub_free (ctx.symlink); + ctx.symlink = 0; + ctx.was_continue = 0; + } + if (hook (ctx.filename, ctx.type, node, hook_data)) + { + if (ctx.filename_alloc) + grub_free (ctx.filename); + return 1; + } + if (ctx.filename_alloc) + grub_free (ctx.filename); + } + } + + return 0; +} + + + +/* Context for grub_iso9660_dir. */ +struct grub_iso9660_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_iso9660_dir. */ +static int +grub_iso9660_dir_iter (const char *filename, + enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_iso9660_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 = !!iso9660_to_unixtime2 (&node->dirents[0].mtime, &info.mtime); + + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_iso9660_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_iso9660_dir_ctx ctx = { hook, hook_data }; + struct grub_iso9660_data *data = 0; + struct grub_fshelp_node rootnode; + struct grub_fshelp_node *foundnode; + + grub_dl_ref (my_mod); + + data = grub_iso9660_mount (device->disk); + if (! data) + goto fail; + + rootnode.data = data; + rootnode.alloc_dirents = 0; + rootnode.have_dirents = 1; + rootnode.have_symlink = 0; + rootnode.dirents[0] = data->voldesc.rootdir; + + /* Use the fshelp function to traverse the path. */ + if (grub_fshelp_find_file (path, &rootnode, + &foundnode, + grub_iso9660_iterate_dir, + grub_iso9660_read_symlink, + GRUB_FSHELP_DIR)) + goto fail; + + /* List the files in the directory. */ + grub_iso9660_iterate_dir (foundnode, grub_iso9660_dir_iter, &ctx); + + if (foundnode != &rootnode) + grub_free (foundnode); + + fail: + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_iso9660_open (struct grub_file *file, const char *name) +{ + struct grub_iso9660_data *data; + struct grub_fshelp_node rootnode; + struct grub_fshelp_node *foundnode; + + grub_dl_ref (my_mod); + + data = grub_iso9660_mount (file->device->disk); + if (!data) + goto fail; + + rootnode.data = data; + rootnode.alloc_dirents = 0; + rootnode.have_dirents = 1; + rootnode.have_symlink = 0; + rootnode.dirents[0] = data->voldesc.rootdir; + + /* Use the fshelp function to traverse the path. */ + if (grub_fshelp_find_file (name, &rootnode, + &foundnode, + grub_iso9660_iterate_dir, + grub_iso9660_read_symlink, + GRUB_FSHELP_REG)) + goto fail; + + data->node = foundnode; + file->data = data; + file->size = get_node_size (foundnode); + file->offset = 0; + + return 0; + + fail: + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + +static grub_ssize_t +grub_iso9660_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_iso9660_data *data = + (struct grub_iso9660_data *) file->data; + grub_err_t err; + + /* XXX: The file is stored in as a single extent. */ + data->disk->read_hook = file->read_hook; + data->disk->read_hook_data = file->read_hook_data; + err = read_node (data->node, file->offset, len, buf); + data->disk->read_hook = NULL; + + if (err || grub_errno) + return -1; + + return len; +} + + +static grub_err_t +grub_iso9660_close (grub_file_t file) +{ + struct grub_iso9660_data *data = + (struct grub_iso9660_data *) file->data; + grub_free (data->node); + grub_free (data); + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + + +static grub_err_t +grub_iso9660_label (grub_device_t device, char **label) +{ + struct grub_iso9660_data *data; + data = grub_iso9660_mount (device->disk); + + if (data) + { + if (data->joliet) + *label = grub_iso9660_convert_string (data->voldesc.volname, 16); + else + *label = grub_strndup ((char *) data->voldesc.volname, 32); + if (*label) + { + char *ptr; + for (ptr = *label; *ptr;ptr++); + ptr--; + while (ptr >= *label && *ptr == ' ') + *ptr-- = 0; + } + + grub_free (data); + } + else + *label = 0; + + return grub_errno; +} + + +static grub_err_t +grub_iso9660_uuid (grub_device_t device, char **uuid) +{ + struct grub_iso9660_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_iso9660_mount (disk); + if (data) + { + if (! data->voldesc.modified.year[0] && ! data->voldesc.modified.year[1] + && ! data->voldesc.modified.year[2] && ! data->voldesc.modified.year[3] + && ! data->voldesc.modified.month[0] && ! data->voldesc.modified.month[1] + && ! data->voldesc.modified.day[0] && ! data->voldesc.modified.day[1] + && ! data->voldesc.modified.hour[0] && ! data->voldesc.modified.hour[1] + && ! data->voldesc.modified.minute[0] && ! data->voldesc.modified.minute[1] + && ! data->voldesc.modified.second[0] && ! data->voldesc.modified.second[1] + && ! data->voldesc.modified.hundredth[0] && ! data->voldesc.modified.hundredth[1]) + { + grub_error (GRUB_ERR_BAD_NUMBER, "no creation date in filesystem to generate UUID"); + *uuid = NULL; + } + else + { + *uuid = grub_xasprintf ("%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c", + data->voldesc.modified.year[0], + data->voldesc.modified.year[1], + data->voldesc.modified.year[2], + data->voldesc.modified.year[3], + data->voldesc.modified.month[0], + data->voldesc.modified.month[1], + data->voldesc.modified.day[0], + data->voldesc.modified.day[1], + data->voldesc.modified.hour[0], + data->voldesc.modified.hour[1], + data->voldesc.modified.minute[0], + data->voldesc.modified.minute[1], + data->voldesc.modified.second[0], + data->voldesc.modified.second[1], + data->voldesc.modified.hundredth[0], + data->voldesc.modified.hundredth[1]); + } + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +/* Get writing time of filesystem. */ +static grub_err_t +grub_iso9660_mtime (grub_device_t device, grub_int64_t *timebuf) +{ + struct grub_iso9660_data *data; + grub_disk_t disk = device->disk; + grub_err_t err; + + grub_dl_ref (my_mod); + + data = grub_iso9660_mount (disk); + if (!data) + { + grub_dl_unref (my_mod); + return grub_errno; + } + err = iso9660_to_unixtime (&data->voldesc.modified, timebuf); + + grub_dl_unref (my_mod); + + grub_free (data); + + return err; +} + + + + +static struct grub_fs grub_iso9660_fs = + { + .name = "iso9660", + .fs_dir = grub_iso9660_dir, + .fs_open = grub_iso9660_open, + .fs_read = grub_iso9660_read, + .fs_close = grub_iso9660_close, + .fs_label = grub_iso9660_label, + .fs_uuid = grub_iso9660_uuid, + .fs_mtime = grub_iso9660_mtime, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(iso9660) +{ + grub_fs_register (&grub_iso9660_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(iso9660) +{ + grub_fs_unregister (&grub_iso9660_fs); +} diff --git a/grub-core/fs/jfs.c b/grub-core/fs/jfs.c new file mode 100644 index 0000000..6f7c439 --- /dev/null +++ b/grub-core/fs/jfs.c @@ -0,0 +1,973 @@ +/* jfs.c - JFS. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,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/charset.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_JFS_MAX_SYMLNK_CNT 8 +#define GRUB_JFS_FILETYPE_MASK 0170000 +#define GRUB_JFS_FILETYPE_REG 0100000 +#define GRUB_JFS_FILETYPE_LNK 0120000 +#define GRUB_JFS_FILETYPE_DIR 0040000 + +#define GRUB_JFS_SBLOCK 64 +#define GRUB_JFS_AGGR_INODE 2 +#define GRUB_JFS_FS1_INODE_BLK 104 + +#define GRUB_JFS_TREE_LEAF 2 + +struct grub_jfs_sblock +{ + /* The magic for JFS. It should contain the string "JFS1". */ + grub_uint8_t magic[4]; + grub_uint32_t version; + grub_uint64_t ag_size; + + /* The size of a filesystem block in bytes. XXX: currently only + 4096 was tested. */ + grub_uint32_t blksz; + grub_uint16_t log2_blksz; + grub_uint8_t unused[14]; + grub_uint32_t flags; + grub_uint8_t unused3[61]; + char volname[11]; + grub_uint8_t unused2[24]; + grub_uint8_t uuid[16]; + char volname2[16]; +}; + +struct grub_jfs_extent +{ + /* The length of the extent in filesystem blocks. */ + grub_uint16_t length; + grub_uint8_t length2; + + /* The physical offset of the first block on the disk. */ + grub_uint8_t blk1; + grub_uint32_t blk2; +} GRUB_PACKED; + +#define GRUB_JFS_IAG_INODES_OFFSET 3072 +#define GRUB_JFS_IAG_INODES_COUNT 128 + +struct grub_jfs_iag +{ + grub_uint8_t unused[GRUB_JFS_IAG_INODES_OFFSET]; + struct grub_jfs_extent inodes[GRUB_JFS_IAG_INODES_COUNT]; +} GRUB_PACKED; + + +/* The head of the tree used to find extents. */ +struct grub_jfs_treehead +{ + grub_uint64_t next; + grub_uint64_t prev; + + grub_uint8_t flags; + grub_uint8_t unused; + + grub_uint16_t count; + grub_uint16_t max; + grub_uint8_t unused2[10]; +} GRUB_PACKED; + +/* A node in the extent tree. */ +struct grub_jfs_tree_extent +{ + grub_uint8_t flags; + grub_uint16_t unused; + + /* The offset is the key used to lookup an extent. */ + grub_uint8_t offset1; + grub_uint32_t offset2; + + struct grub_jfs_extent extent; +} GRUB_PACKED; + +/* The tree of directory entries. */ +struct grub_jfs_tree_dir +{ + /* Pointers to the previous and next tree headers of other nodes on + this level. */ + grub_uint64_t nextb; + grub_uint64_t prevb; + + grub_uint8_t flags; + + /* The amount of dirents in this node. */ + grub_uint8_t count; + grub_uint8_t freecnt; + grub_uint8_t freelist; + grub_uint8_t maxslot; + + /* The location of the sorted array of pointers to dirents. */ + grub_uint8_t sindex; + grub_uint8_t unused[10]; +} GRUB_PACKED; + +/* An internal node in the dirents tree. */ +struct grub_jfs_internal_dirent +{ + struct grub_jfs_extent ex; + grub_uint8_t next; + grub_uint8_t len; + grub_uint16_t namepart[11]; +} GRUB_PACKED; + +/* A leaf node in the dirents tree. */ +struct grub_jfs_leaf_dirent +{ + /* The inode for this dirent. */ + grub_uint32_t inode; + grub_uint8_t next; + + /* The size of the name. */ + grub_uint8_t len; + grub_uint16_t namepart[11]; + grub_uint32_t index; +} GRUB_PACKED; + +/* A leaf in the dirents tree. This one is used if the previously + dirent was not big enough to store the name. */ +struct grub_jfs_leaf_next_dirent +{ + grub_uint8_t next; + grub_uint8_t len; + grub_uint16_t namepart[15]; +} GRUB_PACKED; + +struct grub_jfs_time +{ + grub_int32_t sec; + grub_int32_t nanosec; +} GRUB_PACKED; + +struct grub_jfs_inode +{ + grub_uint32_t stamp; + grub_uint32_t fileset; + grub_uint32_t inode; + grub_uint8_t unused[12]; + grub_uint64_t size; + grub_uint8_t unused2[20]; + grub_uint32_t mode; + struct grub_jfs_time atime; + struct grub_jfs_time ctime; + struct grub_jfs_time mtime; + grub_uint8_t unused3[48]; + grub_uint8_t unused4[96]; + + union + { + /* The tree describing the extents of the file. */ + struct GRUB_PACKED + { + struct grub_jfs_treehead tree; + struct grub_jfs_tree_extent extents[16]; + } file; + union + { + /* The tree describing the dirents. */ + struct + { + grub_uint8_t unused[16]; + grub_uint8_t flags; + + /* Amount of dirents in this node. */ + grub_uint8_t count; + grub_uint8_t freecnt; + grub_uint8_t freelist; + grub_uint32_t idotdot; + grub_uint8_t sorted[8]; + } header; + struct grub_jfs_leaf_dirent dirents[8]; + } GRUB_PACKED dir; + /* Fast symlink. */ + struct + { + grub_uint8_t unused[32]; + grub_uint8_t path[256]; + } symlink; + } GRUB_PACKED; +} GRUB_PACKED; + +struct grub_jfs_data +{ + struct grub_jfs_sblock sblock; + grub_disk_t disk; + struct grub_jfs_inode fileset; + struct grub_jfs_inode currinode; + int caseins; + int pos; + int linknest; + int namecomponentlen; +} GRUB_PACKED; + +struct grub_jfs_diropen +{ + int index; + union + { + struct grub_jfs_tree_dir header; + struct grub_jfs_leaf_dirent dirent[0]; + struct grub_jfs_leaf_next_dirent next_dirent[0]; + grub_uint8_t sorted[0]; + } GRUB_PACKED *dirpage; + struct grub_jfs_data *data; + struct grub_jfs_inode *inode; + int count; + grub_uint8_t *sorted; + struct grub_jfs_leaf_dirent *leaf; + struct grub_jfs_leaf_next_dirent *next_leaf; + + /* The filename and inode of the last read dirent. */ + /* On-disk name is at most 255 UTF-16 codepoints. + Every UTF-16 codepoint is at most 4 UTF-8 bytes. + */ + char name[256 * GRUB_MAX_UTF8_PER_UTF16 + 1]; + grub_uint32_t ino; +} GRUB_PACKED; + + +static grub_dl_t my_mod; + +static grub_err_t grub_jfs_lookup_symlink (struct grub_jfs_data *data, grub_uint32_t ino); + +static grub_int64_t +getblk (struct grub_jfs_treehead *treehead, + struct grub_jfs_tree_extent *extents, + int max_extents, + struct grub_jfs_data *data, + grub_uint64_t blk) +{ + int found = -1; + int i; + + for (i = 0; i < grub_le_to_cpu16 (treehead->count) - 2 && + i < max_extents; i++) + { + if (treehead->flags & GRUB_JFS_TREE_LEAF) + { + /* Read the leafnode. */ + if (grub_le_to_cpu32 (extents[i].offset2) <= blk + && ((grub_le_to_cpu16 (extents[i].extent.length)) + + (extents[i].extent.length2 << 16) + + grub_le_to_cpu32 (extents[i].offset2)) > blk) + return (blk - grub_le_to_cpu32 (extents[i].offset2) + + grub_le_to_cpu32 (extents[i].extent.blk2)); + } + else + if (blk >= grub_le_to_cpu32 (extents[i].offset2)) + found = i; + } + + if (found != -1) + { + grub_int64_t ret = -1; + struct + { + struct grub_jfs_treehead treehead; + struct grub_jfs_tree_extent extents[254]; + } *tree; + + tree = grub_zalloc (sizeof (*tree)); + if (!tree) + return -1; + + if (!grub_disk_read (data->disk, + ((grub_disk_addr_t) grub_le_to_cpu32 (extents[found].extent.blk2)) + << (grub_le_to_cpu16 (data->sblock.log2_blksz) + - GRUB_DISK_SECTOR_BITS), 0, + sizeof (*tree), (char *) tree)) + { + if (grub_memcmp (&tree->treehead, treehead, sizeof (struct grub_jfs_treehead)) || + grub_memcmp (&tree->extents, extents, 254 * sizeof (struct grub_jfs_tree_extent))) + ret = getblk (&tree->treehead, &tree->extents[0], 254, data, blk); + else + { + grub_error (GRUB_ERR_BAD_FS, "jfs: infinite recursion detected"); + ret = -1; + } + } + grub_free (tree); + return ret; + } + + return -1; +} + +/* Get the block number for the block BLK in the node INODE in the + mounted filesystem DATA. */ +static grub_int64_t +grub_jfs_blkno (struct grub_jfs_data *data, struct grub_jfs_inode *inode, + grub_uint64_t blk) +{ + return getblk (&inode->file.tree, &inode->file.extents[0], 16, data, blk); +} + + +static grub_err_t +grub_jfs_read_inode (struct grub_jfs_data *data, grub_uint32_t ino, + struct grub_jfs_inode *inode) +{ + struct grub_jfs_extent iag_inodes[GRUB_JFS_IAG_INODES_COUNT]; + grub_uint32_t iagnum = ino / 4096; + unsigned inoext = (ino % 4096) / 32; + unsigned inonum = (ino % 4096) % 32; + grub_uint64_t iagblk; + grub_uint64_t inoblk; + + iagblk = grub_jfs_blkno (data, &data->fileset, iagnum + 1); + if (grub_errno) + return grub_errno; + + /* Read in the IAG. */ + if (grub_disk_read (data->disk, + iagblk << (grub_le_to_cpu16 (data->sblock.log2_blksz) + - GRUB_DISK_SECTOR_BITS), + GRUB_JFS_IAG_INODES_OFFSET, + sizeof (iag_inodes), &iag_inodes)) + return grub_errno; + + inoblk = grub_le_to_cpu32 (iag_inodes[inoext].blk2); + inoblk <<= (grub_le_to_cpu16 (data->sblock.log2_blksz) + - GRUB_DISK_SECTOR_BITS); + inoblk += inonum; + + if (grub_disk_read (data->disk, inoblk, 0, + sizeof (struct grub_jfs_inode), inode)) + return grub_errno; + + return 0; +} + + +static struct grub_jfs_data * +grub_jfs_mount (grub_disk_t disk) +{ + struct grub_jfs_data *data = 0; + + data = grub_malloc (sizeof (struct grub_jfs_data)); + if (!data) + return 0; + + /* Read the superblock. */ + if (grub_disk_read (disk, GRUB_JFS_SBLOCK, 0, + sizeof (struct grub_jfs_sblock), &data->sblock)) + goto fail; + + if (grub_strncmp ((char *) (data->sblock.magic), "JFS1", 4)) + { + grub_error (GRUB_ERR_BAD_FS, "not a JFS filesystem"); + goto fail; + } + + if (data->sblock.blksz == 0 + || grub_le_to_cpu32 (data->sblock.blksz) + != (1U << grub_le_to_cpu16 (data->sblock.log2_blksz)) + || grub_le_to_cpu16 (data->sblock.log2_blksz) < GRUB_DISK_SECTOR_BITS) + { + grub_error (GRUB_ERR_BAD_FS, "not a JFS filesystem"); + goto fail; + } + + data->disk = disk; + data->pos = 0; + data->linknest = 0; + + /* Read the inode of the first fileset. */ + if (grub_disk_read (data->disk, GRUB_JFS_FS1_INODE_BLK, 0, + sizeof (struct grub_jfs_inode), &data->fileset)) + goto fail; + + if (data->sblock.flags & grub_cpu_to_le32_compile_time (0x00200000)) + data->namecomponentlen = 11; + else + data->namecomponentlen = 13; + + if (data->sblock.flags & grub_cpu_to_le32_compile_time (0x40000000)) + data->caseins = 1; + else + data->caseins = 0; + + return data; + + fail: + grub_free (data); + + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a JFS filesystem"); + + return 0; +} + + +static struct grub_jfs_diropen * +grub_jfs_opendir (struct grub_jfs_data *data, struct grub_jfs_inode *inode) +{ + struct grub_jfs_internal_dirent *de; + struct grub_jfs_diropen *diro; + grub_disk_addr_t blk; + + de = (struct grub_jfs_internal_dirent *) inode->dir.dirents; + + if (!((grub_le_to_cpu32 (inode->mode) + & GRUB_JFS_FILETYPE_MASK) == GRUB_JFS_FILETYPE_DIR)) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + return 0; + } + + diro = grub_zalloc (sizeof (struct grub_jfs_diropen)); + if (!diro) + return 0; + + diro->data = data; + diro->inode = inode; + + /* Check if the entire tree is contained within the inode. */ + if (inode->file.tree.flags & GRUB_JFS_TREE_LEAF) + { + diro->leaf = inode->dir.dirents; + diro->next_leaf = (struct grub_jfs_leaf_next_dirent *) de; + diro->sorted = inode->dir.header.sorted; + diro->count = inode->dir.header.count; + + return diro; + } + + diro->dirpage = grub_malloc (grub_le_to_cpu32 (data->sblock.blksz)); + if (!diro->dirpage) + { + grub_free (diro); + return 0; + } + + blk = grub_le_to_cpu32 (de[inode->dir.header.sorted[0]].ex.blk2); + blk <<= (grub_le_to_cpu16 (data->sblock.log2_blksz) - GRUB_DISK_SECTOR_BITS); + + /* Read in the nodes until we are on the leaf node level. */ + do + { + int index; + if (grub_disk_read (data->disk, blk, 0, + grub_le_to_cpu32 (data->sblock.blksz), + diro->dirpage->sorted)) + { + grub_free (diro->dirpage); + grub_free (diro); + return 0; + } + + de = (struct grub_jfs_internal_dirent *) diro->dirpage->dirent; + index = diro->dirpage->sorted[diro->dirpage->header.sindex * 32]; + blk = (grub_le_to_cpu32 (de[index].ex.blk2) + << (grub_le_to_cpu16 (data->sblock.log2_blksz) + - GRUB_DISK_SECTOR_BITS)); + } while (!(diro->dirpage->header.flags & GRUB_JFS_TREE_LEAF)); + + diro->leaf = diro->dirpage->dirent; + diro->next_leaf = diro->dirpage->next_dirent; + diro->sorted = &diro->dirpage->sorted[diro->dirpage->header.sindex * 32]; + diro->count = diro->dirpage->header.count; + + return diro; +} + + +static void +grub_jfs_closedir (struct grub_jfs_diropen *diro) +{ + if (!diro) + return; + grub_free (diro->dirpage); + grub_free (diro); +} + +static void +le_to_cpu16_copy (grub_uint16_t *out, grub_uint16_t *in, grub_size_t len) +{ + while (len--) + *out++ = grub_le_to_cpu16 (*in++); +} + +#if __GNUC__ >= 9 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" +#endif + +/* Read in the next dirent from the directory described by DIRO. */ +static grub_err_t +grub_jfs_getent (struct grub_jfs_diropen *diro) +{ + int strpos = 0; + struct grub_jfs_leaf_dirent *leaf; + struct grub_jfs_leaf_next_dirent *next_leaf; + int len; + int nextent; + grub_uint16_t filename[256]; + + /* The last node, read in more. */ + if (diro->index == diro->count) + { + grub_disk_addr_t next; + + /* If the inode contains the entry tree or if this was the last + node, there is nothing to read. */ + if ((diro->inode->file.tree.flags & GRUB_JFS_TREE_LEAF) + || !grub_le_to_cpu64 (diro->dirpage->header.nextb)) + return GRUB_ERR_OUT_OF_RANGE; + + next = grub_le_to_cpu64 (diro->dirpage->header.nextb); + next <<= (grub_le_to_cpu16 (diro->data->sblock.log2_blksz) + - GRUB_DISK_SECTOR_BITS); + + if (grub_disk_read (diro->data->disk, next, 0, + grub_le_to_cpu32 (diro->data->sblock.blksz), + diro->dirpage->sorted)) + return grub_errno; + + diro->leaf = diro->dirpage->dirent; + diro->next_leaf = diro->dirpage->next_dirent; + diro->sorted = &diro->dirpage->sorted[diro->dirpage->header.sindex * 32]; + diro->count = diro->dirpage->header.count; + diro->index = 0; + } + + leaf = &diro->leaf[diro->sorted[diro->index]]; + next_leaf = &diro->next_leaf[diro->index]; + + len = leaf->len; + if (!len) + { + diro->index++; + return grub_jfs_getent (diro); + } + + le_to_cpu16_copy (filename + strpos, leaf->namepart, len < diro->data->namecomponentlen ? len + : diro->data->namecomponentlen); + strpos += len < diro->data->namecomponentlen ? len + : diro->data->namecomponentlen; + diro->ino = grub_le_to_cpu32 (leaf->inode); + len -= diro->data->namecomponentlen; + + /* Move down to the leaf level. */ + nextent = leaf->next; + if (leaf->next != 255 && len > 0) + do + { + next_leaf = &diro->next_leaf[nextent]; + le_to_cpu16_copy (filename + strpos, next_leaf->namepart, len < 15 ? len : 15); + strpos += len < 15 ? len : 15; + + len -= 15; + nextent = next_leaf->next; + } while (next_leaf->next != 255 && len > 0); + + diro->index++; + + /* Convert the temporary UTF16 filename to UTF8. */ + *grub_utf16_to_utf8 ((grub_uint8_t *) (diro->name), filename, strpos) = '\0'; + + return 0; +} + +#if __GNUC__ >= 9 +#pragma GCC diagnostic pop +#endif + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +static grub_ssize_t +grub_jfs_read_file (struct grub_jfs_data *data, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + grub_off_t i; + grub_off_t blockcnt; + + blockcnt = (len + pos + grub_le_to_cpu32 (data->sblock.blksz) - 1) + >> grub_le_to_cpu16 (data->sblock.log2_blksz); + + for (i = pos >> grub_le_to_cpu16 (data->sblock.log2_blksz); i < blockcnt; i++) + { + grub_disk_addr_t blknr; + grub_uint32_t blockoff = pos & (grub_le_to_cpu32 (data->sblock.blksz) - 1); + grub_uint32_t blockend = grub_le_to_cpu32 (data->sblock.blksz); + + grub_uint64_t skipfirst = 0; + + blknr = grub_jfs_blkno (data, &data->currinode, i); + if (grub_errno) + return -1; + + /* Last block. */ + if (i == blockcnt - 1) + { + blockend = (len + pos) & (grub_le_to_cpu32 (data->sblock.blksz) - 1); + + if (!blockend) + blockend = grub_le_to_cpu32 (data->sblock.blksz); + } + + /* First block. */ + if (i == (pos >> grub_le_to_cpu16 (data->sblock.log2_blksz))) + { + skipfirst = blockoff; + blockend -= skipfirst; + } + + data->disk->read_hook = read_hook; + data->disk->read_hook_data = read_hook_data; + grub_disk_read (data->disk, + blknr << (grub_le_to_cpu16 (data->sblock.log2_blksz) + - GRUB_DISK_SECTOR_BITS), + skipfirst, blockend, buf); + + data->disk->read_hook = 0; + if (grub_errno) + return -1; + + buf += grub_le_to_cpu32 (data->sblock.blksz) - skipfirst; + } + + return len; +} + + +/* Find the file with the pathname PATH on the filesystem described by + DATA. */ +static grub_err_t +grub_jfs_find_file (struct grub_jfs_data *data, const char *path, + grub_uint32_t start_ino) +{ + const char *name; + const char *next = path; + struct grub_jfs_diropen *diro = NULL; + + if (grub_jfs_read_inode (data, start_ino, &data->currinode)) + return grub_errno; + + while (1) + { + name = next; + while (*name == '/') + name++; + if (name[0] == 0) + return GRUB_ERR_NONE; + for (next = name; *next && *next != '/'; next++); + + if (name[0] == '.' && name + 1 == next) + continue; + + if (name[0] == '.' && name[1] == '.' && name + 2 == next) + { + grub_uint32_t ino = grub_le_to_cpu32 (data->currinode.dir.header.idotdot); + + if (grub_jfs_read_inode (data, ino, &data->currinode)) + return grub_errno; + + continue; + } + + diro = grub_jfs_opendir (data, &data->currinode); + if (!diro) + return grub_errno; + + for (;;) + { + if (grub_jfs_getent (diro) == GRUB_ERR_OUT_OF_RANGE) + { + grub_jfs_closedir (diro); + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), path); + } + + /* Check if the current direntry matches the current part of the + pathname. */ + if ((data->caseins ? grub_strncasecmp (name, diro->name, next - name) == 0 + : grub_strncmp (name, diro->name, next - name) == 0) && !diro->name[next - name]) + { + grub_uint32_t ino = diro->ino; + grub_uint32_t dirino = grub_le_to_cpu32 (data->currinode.inode); + + grub_jfs_closedir (diro); + diro = 0; + + if (grub_jfs_read_inode (data, ino, &data->currinode)) + break; + + /* Check if this is a symlink. */ + if ((grub_le_to_cpu32 (data->currinode.mode) + & GRUB_JFS_FILETYPE_MASK) == GRUB_JFS_FILETYPE_LNK) + { + grub_jfs_lookup_symlink (data, dirino); + if (grub_errno) + return grub_errno; + } + + break; + } + } + } +} + + +static grub_err_t +grub_jfs_lookup_symlink (struct grub_jfs_data *data, grub_uint32_t ino) +{ + grub_size_t size = grub_le_to_cpu64 (data->currinode.size); + char *symlink; + + if (++data->linknest > GRUB_JFS_MAX_SYMLNK_CNT) + return grub_error (GRUB_ERR_SYMLINK_LOOP, N_("too deep nesting of symlinks")); + + symlink = grub_malloc (size + 1); + if (!symlink) + return grub_errno; + if (size <= sizeof (data->currinode.symlink.path)) + grub_memcpy (symlink, (char *) (data->currinode.symlink.path), size); + else if (grub_jfs_read_file (data, 0, 0, 0, size, symlink) < 0) + { + grub_free (symlink); + return grub_errno; + } + + symlink[size] = '\0'; + + /* The symlink is an absolute path, go back to the root inode. */ + if (symlink[0] == '/') + ino = 2; + + grub_jfs_find_file (data, symlink, ino); + + grub_free (symlink); + + return grub_errno; +} + + +static grub_err_t +grub_jfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_jfs_data *data = 0; + struct grub_jfs_diropen *diro = 0; + + grub_dl_ref (my_mod); + + data = grub_jfs_mount (device->disk); + if (!data) + goto fail; + + if (grub_jfs_find_file (data, path, GRUB_JFS_AGGR_INODE)) + goto fail; + + diro = grub_jfs_opendir (data, &data->currinode); + if (!diro) + goto fail; + + /* Iterate over the dirents in the directory that was found. */ + while (grub_jfs_getent (diro) != GRUB_ERR_OUT_OF_RANGE) + { + struct grub_jfs_inode inode; + struct grub_dirhook_info info; + grub_memset (&info, 0, sizeof (info)); + + if (grub_jfs_read_inode (data, diro->ino, &inode)) + goto fail; + + info.dir = (grub_le_to_cpu32 (inode.mode) + & GRUB_JFS_FILETYPE_MASK) == GRUB_JFS_FILETYPE_DIR; + info.mtimeset = 1; + info.mtime = grub_le_to_cpu32 (inode.mtime.sec); + if (hook (diro->name, &info, hook_data)) + goto fail; + } + + /* XXX: GRUB_ERR_OUT_OF_RANGE is used for the last dirent. */ + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_errno = 0; + + fail: + grub_jfs_closedir (diro); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_jfs_open (struct grub_file *file, const char *name) +{ + struct grub_jfs_data *data; + + grub_dl_ref (my_mod); + + data = grub_jfs_mount (file->device->disk); + if (!data) + goto fail; + + grub_jfs_find_file (data, name, GRUB_JFS_AGGR_INODE); + if (grub_errno) + goto fail; + + /* It is only possible for open regular files. */ + if (! ((grub_le_to_cpu32 (data->currinode.mode) + & GRUB_JFS_FILETYPE_MASK) == GRUB_JFS_FILETYPE_REG)) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file")); + goto fail; + } + + file->data = data; + file->size = grub_le_to_cpu64 (data->currinode.size); + + return 0; + + fail: + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + +static grub_ssize_t +grub_jfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_jfs_data *data = + (struct grub_jfs_data *) file->data; + + return grub_jfs_read_file (data, file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + + +static grub_err_t +grub_jfs_close (grub_file_t file) +{ + grub_free (file->data); + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_jfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_jfs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_jfs_mount (disk); + if (data) + { + *uuid = grub_xasprintf ("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x", + data->sblock.uuid[0], data->sblock.uuid[1], + data->sblock.uuid[2], data->sblock.uuid[3], + data->sblock.uuid[4], data->sblock.uuid[5], + data->sblock.uuid[6], data->sblock.uuid[7], + data->sblock.uuid[8], data->sblock.uuid[9], + data->sblock.uuid[10], data->sblock.uuid[11], + data->sblock.uuid[12], data->sblock.uuid[13], + data->sblock.uuid[14], data->sblock.uuid[15]); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_jfs_label (grub_device_t device, char **label) +{ + struct grub_jfs_data *data; + data = grub_jfs_mount (device->disk); + + if (data) + { + if (data->sblock.volname2[0] < ' ') + { + char *ptr; + ptr = data->sblock.volname + sizeof (data->sblock.volname) - 1; + while (ptr >= data->sblock.volname && *ptr == ' ') + ptr--; + *label = grub_strndup (data->sblock.volname, + ptr - data->sblock.volname + 1); + } + else + *label = grub_strndup (data->sblock.volname2, + sizeof (data->sblock.volname2)); + } + else + *label = 0; + + grub_free (data); + + return grub_errno; +} + + +static struct grub_fs grub_jfs_fs = + { + .name = "jfs", + .fs_dir = grub_jfs_dir, + .fs_open = grub_jfs_open, + .fs_read = grub_jfs_read, + .fs_close = grub_jfs_close, + .fs_label = grub_jfs_label, + .fs_uuid = grub_jfs_uuid, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(jfs) +{ + grub_fs_register (&grub_jfs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(jfs) +{ + grub_fs_unregister (&grub_jfs_fs); +} diff --git a/grub-core/fs/minix.c b/grub-core/fs/minix.c new file mode 100644 index 0000000..3cd18c8 --- /dev/null +++ b/grub-core/fs/minix.c @@ -0,0 +1,758 @@ +/* minix.c - The minix filesystem, version 1 and 2. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,2005,2006,2007,2008 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/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#ifdef MODE_MINIX3 +#define GRUB_MINIX_MAGIC 0x4D5A +#elif defined(MODE_MINIX2) +#define GRUB_MINIX_MAGIC 0x2468 +#define GRUB_MINIX_MAGIC_30 0x2478 +#else +#define GRUB_MINIX_MAGIC 0x137F +#define GRUB_MINIX_MAGIC_30 0x138F +#endif + +#define EXT2_MAGIC 0xEF53 + +#define GRUB_MINIX_INODE_DIR_BLOCKS 7 +#define GRUB_MINIX_LOG2_BSIZE 1 +#define GRUB_MINIX_ROOT_INODE 1 +#define GRUB_MINIX_MAX_SYMLNK_CNT 8 +#define GRUB_MINIX_SBLOCK 2 + +#define GRUB_MINIX_IFDIR 0040000U +#define GRUB_MINIX_IFLNK 0120000U + +#ifdef MODE_BIGENDIAN +#define grub_cpu_to_minix16_compile_time grub_cpu_to_be16_compile_time +#define grub_minix_to_cpu16 grub_be_to_cpu16 +#define grub_minix_to_cpu32 grub_be_to_cpu32 +#else +#define grub_cpu_to_minix16_compile_time grub_cpu_to_le16_compile_time +#define grub_minix_to_cpu16 grub_le_to_cpu16 +#define grub_minix_to_cpu32 grub_le_to_cpu32 +#endif + +#if defined(MODE_MINIX2) || defined(MODE_MINIX3) +typedef grub_uint32_t grub_minix_uintn_t; +#define grub_minix_to_cpu_n grub_minix_to_cpu32 +#else +typedef grub_uint16_t grub_minix_uintn_t; +#define grub_minix_to_cpu_n grub_minix_to_cpu16 +#endif + +#ifdef MODE_MINIX3 +typedef grub_uint32_t grub_minix_ino_t; +#define grub_minix_to_cpu_ino grub_minix_to_cpu32 +#else +typedef grub_uint16_t grub_minix_ino_t; +#define grub_minix_to_cpu_ino grub_minix_to_cpu16 +#endif + +#define GRUB_MINIX_INODE_SIZE(data) (grub_minix_to_cpu32 (data->inode.size)) +#define GRUB_MINIX_INODE_MODE(data) (grub_minix_to_cpu16 (data->inode.mode)) +#define GRUB_MINIX_INODE_DIR_ZONES(data,blk) (grub_minix_to_cpu_n \ + (data->inode.dir_zones[blk])) +#define GRUB_MINIX_INODE_INDIR_ZONE(data) (grub_minix_to_cpu_n \ + (data->inode.indir_zone)) +#define GRUB_MINIX_INODE_DINDIR_ZONE(data) (grub_minix_to_cpu_n \ + (data->inode.double_indir_zone)) + + +#ifdef MODE_MINIX3 +struct grub_minix_sblock +{ + grub_uint32_t inode_cnt; + grub_uint16_t zone_cnt; + grub_uint16_t inode_bmap_size; + grub_uint16_t zone_bmap_size; + grub_uint16_t first_data_zone; + grub_uint16_t log2_zone_size; + grub_uint16_t pad; + grub_uint32_t max_file_size; + grub_uint32_t zones; + grub_uint16_t magic; + + grub_uint16_t pad2; + grub_uint16_t block_size; + grub_uint8_t disk_version; +}; +#else +struct grub_minix_sblock +{ + grub_uint16_t inode_cnt; + grub_uint16_t zone_cnt; + grub_uint16_t inode_bmap_size; + grub_uint16_t zone_bmap_size; + grub_uint16_t first_data_zone; + grub_uint16_t log2_zone_size; + grub_uint32_t max_file_size; + grub_uint16_t magic; +}; +#endif + +#if defined(MODE_MINIX3) || defined(MODE_MINIX2) +struct grub_minix_inode +{ + grub_uint16_t mode; + grub_uint16_t nlinks; + grub_uint16_t uid; + grub_uint16_t gid; + grub_uint32_t size; + grub_uint32_t atime; + grub_uint32_t mtime; + grub_uint32_t ctime; + grub_uint32_t dir_zones[7]; + grub_uint32_t indir_zone; + grub_uint32_t double_indir_zone; + grub_uint32_t triple_indir_zone; +}; +#else +struct grub_minix_inode +{ + grub_uint16_t mode; + grub_uint16_t uid; + grub_uint32_t size; + grub_uint32_t mtime; + grub_uint8_t gid; + grub_uint8_t nlinks; + grub_uint16_t dir_zones[7]; + grub_uint16_t indir_zone; + grub_uint16_t double_indir_zone; +}; + +#endif + +#if defined(MODE_MINIX3) +#define MAX_MINIX_FILENAME_SIZE 60 +#else +#define MAX_MINIX_FILENAME_SIZE 30 +#endif + +/* Information about a "mounted" minix filesystem. */ +struct grub_minix_data +{ + struct grub_minix_sblock sblock; + struct grub_minix_inode inode; + grub_uint32_t block_per_zone; + grub_minix_ino_t ino; + int linknest; + grub_disk_t disk; + int filename_size; + grub_size_t block_size; +}; + +static grub_dl_t my_mod; + +static grub_err_t grub_minix_find_file (struct grub_minix_data *data, + const char *path); + +#ifdef MODE_MINIX3 +static inline grub_disk_addr_t +grub_minix_zone2sect (struct grub_minix_data *data, grub_minix_uintn_t zone) +{ + return ((grub_disk_addr_t) zone) * data->block_size; +} +#else +static inline grub_disk_addr_t +grub_minix_zone2sect (struct grub_minix_data *data, grub_minix_uintn_t zone) +{ + int log2_zonesz = (GRUB_MINIX_LOG2_BSIZE + + grub_minix_to_cpu16 (data->sblock.log2_zone_size)); + return (((grub_disk_addr_t) zone) << log2_zonesz); +} +#endif + + + /* Read the block pointer in ZONE, on the offset NUM. */ +static grub_minix_uintn_t +grub_get_indir (struct grub_minix_data *data, + grub_minix_uintn_t zone, + grub_minix_uintn_t num) +{ + grub_minix_uintn_t indirn; + grub_disk_read (data->disk, + grub_minix_zone2sect(data, zone), + sizeof (grub_minix_uintn_t) * num, + sizeof (grub_minix_uintn_t), (char *) &indirn); + return grub_minix_to_cpu_n (indirn); +} + +static grub_minix_uintn_t +grub_minix_get_file_block (struct grub_minix_data *data, unsigned int blk) +{ + grub_minix_uintn_t indir; + + /* Direct block. */ + if (blk < GRUB_MINIX_INODE_DIR_BLOCKS) + return GRUB_MINIX_INODE_DIR_ZONES (data, blk); + + /* Indirect block. */ + blk -= GRUB_MINIX_INODE_DIR_BLOCKS; + if (blk < data->block_per_zone) + { + indir = grub_get_indir (data, GRUB_MINIX_INODE_INDIR_ZONE (data), blk); + return indir; + } + + /* Double indirect block. */ + blk -= data->block_per_zone; + if (blk < (grub_uint64_t) data->block_per_zone * (grub_uint64_t) data->block_per_zone) + { + indir = grub_get_indir (data, GRUB_MINIX_INODE_DINDIR_ZONE (data), + blk / data->block_per_zone); + + indir = grub_get_indir (data, indir, blk % data->block_per_zone); + + return indir; + } + +#if defined (MODE_MINIX3) || defined (MODE_MINIX2) + blk -= data->block_per_zone * data->block_per_zone; + if (blk < ((grub_uint64_t) data->block_per_zone * (grub_uint64_t) data->block_per_zone + * (grub_uint64_t) data->block_per_zone)) + { + indir = grub_get_indir (data, grub_minix_to_cpu_n (data->inode.triple_indir_zone), + (blk / data->block_per_zone) / data->block_per_zone); + indir = grub_get_indir (data, indir, (blk / data->block_per_zone) % data->block_per_zone); + indir = grub_get_indir (data, indir, blk % data->block_per_zone); + + return indir; + } +#endif + + /* This should never happen. */ + grub_error (GRUB_ERR_OUT_OF_RANGE, "file bigger than maximum size"); + + return 0; +} + + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +static grub_ssize_t +grub_minix_read_file (struct grub_minix_data *data, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + grub_uint32_t i; + grub_uint32_t blockcnt; + grub_uint32_t posblock; + grub_uint32_t blockoff; + + if (pos > GRUB_MINIX_INODE_SIZE (data)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("attempt to read past the end of file")); + return -1; + } + + /* Adjust len so it we can't read past the end of the file. */ + if (len + pos > GRUB_MINIX_INODE_SIZE (data)) + len = GRUB_MINIX_INODE_SIZE (data) - pos; + if (len == 0) + return 0; + + /* Files are at most 2G/4G - 1 bytes on minixfs. Avoid 64-bit division. */ + blockcnt = ((grub_uint32_t) ((len + pos - 1) + >> GRUB_DISK_SECTOR_BITS)) / data->block_size + 1; + posblock = (((grub_uint32_t) pos) + / (data->block_size << GRUB_DISK_SECTOR_BITS)); + blockoff = (((grub_uint32_t) pos) + % (data->block_size << GRUB_DISK_SECTOR_BITS)); + + for (i = posblock; i < blockcnt; i++) + { + grub_minix_uintn_t blknr; + grub_uint64_t blockend = data->block_size << GRUB_DISK_SECTOR_BITS; + grub_off_t skipfirst = 0; + + blknr = grub_minix_get_file_block (data, i); + if (grub_errno) + return -1; + + /* Last block. */ + if (i == blockcnt - 1) + { + /* len + pos < 4G (checked above), so it doesn't overflow. */ + blockend = (((grub_uint32_t) (len + pos)) + % (data->block_size << GRUB_DISK_SECTOR_BITS)); + + if (!blockend) + blockend = data->block_size << GRUB_DISK_SECTOR_BITS; + } + + /* First block. */ + if (i == posblock) + { + skipfirst = blockoff; + blockend -= skipfirst; + } + + data->disk->read_hook = read_hook; + data->disk->read_hook_data = read_hook_data; + grub_disk_read (data->disk, + grub_minix_zone2sect(data, blknr), + skipfirst, blockend, buf); + data->disk->read_hook = 0; + if (grub_errno) + return -1; + + buf += (data->block_size << GRUB_DISK_SECTOR_BITS) - skipfirst; + } + + return len; +} + + +/* Read inode INO from the mounted filesystem described by DATA. This + inode is used by default now. */ +static grub_err_t +grub_minix_read_inode (struct grub_minix_data *data, grub_minix_ino_t ino) +{ + struct grub_minix_sblock *sblock = &data->sblock; + + /* Block in which the inode is stored. */ + grub_disk_addr_t block; + data->ino = ino; + + /* The first inode in minix is inode 1. */ + ino--; + block = grub_minix_zone2sect (data, + 2 + grub_minix_to_cpu16 (sblock->inode_bmap_size) + + grub_minix_to_cpu16 (sblock->zone_bmap_size)); + block += ino / (GRUB_DISK_SECTOR_SIZE / sizeof (struct grub_minix_inode)); + int offs = (ino % (GRUB_DISK_SECTOR_SIZE + / sizeof (struct grub_minix_inode)) + * sizeof (struct grub_minix_inode)); + + grub_disk_read (data->disk, block, offs, + sizeof (struct grub_minix_inode), &data->inode); + + return GRUB_ERR_NONE; +} + + +/* Lookup the symlink the current inode points to. INO is the inode + number of the directory the symlink is relative to. */ +static grub_err_t +grub_minix_lookup_symlink (struct grub_minix_data *data, grub_minix_ino_t ino) +{ + char *symlink; + grub_size_t sz = GRUB_MINIX_INODE_SIZE (data); + + if (++data->linknest > GRUB_MINIX_MAX_SYMLNK_CNT) + return grub_error (GRUB_ERR_SYMLINK_LOOP, N_("too deep nesting of symlinks")); + + symlink = grub_malloc (sz + 1); + if (!symlink) + return grub_errno; + if (grub_minix_read_file (data, 0, 0, 0, sz, symlink) < 0) + return grub_errno; + + symlink[sz] = '\0'; + + /* The symlink is an absolute path, go back to the root inode. */ + if (symlink[0] == '/') + ino = GRUB_MINIX_ROOT_INODE; + + /* Now load in the old inode. */ + if (grub_minix_read_inode (data, ino)) + return grub_errno; + + grub_minix_find_file (data, symlink); + + return grub_errno; +} + + +/* Find the file with the pathname PATH on the filesystem described by + DATA. */ +static grub_err_t +grub_minix_find_file (struct grub_minix_data *data, const char *path) +{ + const char *name; + const char *next = path; + unsigned int pos = 0; + grub_minix_ino_t dirino; + + while (1) + { + name = next; + /* Skip the first slash. */ + while (*name == '/') + name++; + if (!*name) + return GRUB_ERR_NONE; + + if ((GRUB_MINIX_INODE_MODE (data) + & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + + /* Extract the actual part from the pathname. */ + for (next = name; *next && *next != '/'; next++); + + for (pos = 0; ; ) + { + grub_minix_ino_t ino; + char filename[MAX_MINIX_FILENAME_SIZE + 1]; + + if (pos >= GRUB_MINIX_INODE_SIZE (data)) + { + grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), path); + return grub_errno; + } + + if (grub_minix_read_file (data, 0, 0, pos, sizeof (ino), + (char *) &ino) < 0) + return grub_errno; + if (grub_minix_read_file (data, 0, 0, pos + sizeof (ino), + data->filename_size, (char *) filename)< 0) + return grub_errno; + + pos += sizeof (ino) + data->filename_size; + + filename[data->filename_size] = '\0'; + + /* Check if the current direntry matches the current part of the + pathname. */ + if (grub_strncmp (name, filename, next - name) == 0 + && filename[next - name] == '\0') + { + dirino = data->ino; + grub_minix_read_inode (data, grub_minix_to_cpu_ino (ino)); + + /* Follow the symlink. */ + if ((GRUB_MINIX_INODE_MODE (data) + & GRUB_MINIX_IFLNK) == GRUB_MINIX_IFLNK) + { + grub_minix_lookup_symlink (data, dirino); + if (grub_errno) + return grub_errno; + } + + break; + } + } + } +} + + +/* Mount the filesystem on the disk DISK. */ +static struct grub_minix_data * +grub_minix_mount (grub_disk_t disk) +{ + struct grub_minix_data *data = NULL; + grub_uint16_t ext2_marker; + + grub_disk_read (disk, 2, 56, sizeof (ext2_marker), &ext2_marker); + if (grub_errno != GRUB_ERR_NONE) + goto fail; + + /* + * The ext2 filesystems can sometimes be mistakenly identified as MINIX, e.g. + * due to the number of free ext2 inodes being written to the same location + * where the MINIX superblock magic is found. Avoid such situations by + * skipping any filesystems that have the ext2 superblock magic. + */ + if (ext2_marker == grub_cpu_to_le16_compile_time (EXT2_MAGIC)) + goto fail; + + data = grub_malloc (sizeof (struct grub_minix_data)); + if (!data) + return 0; + + /* Read the superblock. */ + grub_disk_read (disk, GRUB_MINIX_SBLOCK, 0, + sizeof (struct grub_minix_sblock),&data->sblock); + if (grub_errno) + goto fail; + + if (data->sblock.magic == grub_cpu_to_minix16_compile_time (GRUB_MINIX_MAGIC)) + { +#if !defined(MODE_MINIX3) + data->filename_size = 14; +#else + data->filename_size = 60; +#endif + } +#if !defined(MODE_MINIX3) + else if (data->sblock.magic + == grub_cpu_to_minix16_compile_time (GRUB_MINIX_MAGIC_30)) + data->filename_size = 30; +#endif + else + goto fail; + + /* 20 means 1G zones. We could support up to 31 but already 1G isn't + supported by anything else. */ + if (grub_minix_to_cpu16 (data->sblock.log2_zone_size) >= 20) + goto fail; + + data->disk = disk; + data->linknest = 0; +#ifdef MODE_MINIX3 + /* These tests are endian-independent. No need to byteswap. */ + if (data->sblock.block_size == 0xffff) + data->block_size = 2; + else + { + if ((data->sblock.block_size == grub_cpu_to_minix16_compile_time (0x200)) + || (data->sblock.block_size == 0) + || (data->sblock.block_size & grub_cpu_to_minix16_compile_time (0x1ff))) + goto fail; + data->block_size = grub_minix_to_cpu16 (data->sblock.block_size) + >> GRUB_DISK_SECTOR_BITS; + } +#else + data->block_size = 2; +#endif + + data->block_per_zone = (((grub_uint64_t) data->block_size << \ + (GRUB_DISK_SECTOR_BITS + grub_minix_to_cpu16 (data->sblock.log2_zone_size))) + / sizeof (grub_minix_uintn_t)); + if (!data->block_per_zone) + goto fail; + + return data; + + fail: + grub_free (data); +#if defined(MODE_MINIX3) + grub_error (GRUB_ERR_BAD_FS, "not a minix3 filesystem"); +#elif defined(MODE_MINIX2) + grub_error (GRUB_ERR_BAD_FS, "not a minix2 filesystem"); +#else + grub_error (GRUB_ERR_BAD_FS, "not a minix filesystem"); +#endif + return 0; +} + +static grub_err_t +grub_minix_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_minix_data *data = 0; + unsigned int pos = 0; + + data = grub_minix_mount (device->disk); + if (!data) + return grub_errno; + + grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE); + if (grub_errno) + goto fail; + + grub_minix_find_file (data, path); + if (grub_errno) + goto fail; + + if ((GRUB_MINIX_INODE_MODE (data) & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + goto fail; + } + + while (pos < GRUB_MINIX_INODE_SIZE (data)) + { + grub_minix_ino_t ino; + char filename[MAX_MINIX_FILENAME_SIZE + 1]; + grub_minix_ino_t dirino = data->ino; + struct grub_dirhook_info info; + grub_memset (&info, 0, sizeof (info)); + + + if (grub_minix_read_file (data, 0, 0, pos, sizeof (ino), + (char *) &ino) < 0) + return grub_errno; + + if (grub_minix_read_file (data, 0, 0, pos + sizeof (ino), + data->filename_size, + (char *) filename) < 0) + return grub_errno; + filename[data->filename_size] = '\0'; + if (!ino) + { + pos += sizeof (ino) + data->filename_size; + continue; + } + + grub_minix_read_inode (data, grub_minix_to_cpu_ino (ino)); + info.dir = ((GRUB_MINIX_INODE_MODE (data) + & GRUB_MINIX_IFDIR) == GRUB_MINIX_IFDIR); + info.mtimeset = 1; + info.mtime = grub_minix_to_cpu32 (data->inode.mtime); + + if (hook (filename, &info, hook_data) ? 1 : 0) + break; + + /* Load the old inode back in. */ + grub_minix_read_inode (data, dirino); + + pos += sizeof (ino) + data->filename_size; + } + + fail: + grub_free (data); + return grub_errno; +} + + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_minix_open (struct grub_file *file, const char *name) +{ + struct grub_minix_data *data; + data = grub_minix_mount (file->device->disk); + if (!data) + return grub_errno; + + /* Open the inode op the root directory. */ + grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE); + if (grub_errno) + { + grub_free (data); + return grub_errno; + } + + if (!name || name[0] != '/') + { + grub_error (GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), name); + return grub_errno; + } + + /* Traverse the directory tree to the node that should be + opened. */ + grub_minix_find_file (data, name); + if (grub_errno) + { + grub_free (data); + return grub_errno; + } + + file->data = data; + file->size = GRUB_MINIX_INODE_SIZE (data); + + return GRUB_ERR_NONE; +} + + +static grub_ssize_t +grub_minix_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_minix_data *data = + (struct grub_minix_data *) file->data; + + return grub_minix_read_file (data, file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + + +static grub_err_t +grub_minix_close (grub_file_t file) +{ + grub_free (file->data); + + return GRUB_ERR_NONE; +} + + + +static struct grub_fs grub_minix_fs = + { +#ifdef MODE_BIGENDIAN +#if defined(MODE_MINIX3) + .name = "minix3_be", +#elif defined(MODE_MINIX2) + .name = "minix2_be", +#else + .name = "minix_be", +#endif +#else +#if defined(MODE_MINIX3) + .name = "minix3", +#elif defined(MODE_MINIX2) + .name = "minix2", +#else + .name = "minix", +#endif +#endif + .fs_dir = grub_minix_dir, + .fs_open = grub_minix_open, + .fs_read = grub_minix_read, + .fs_close = grub_minix_close, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 + }; + +#ifdef MODE_BIGENDIAN +#if defined(MODE_MINIX3) +GRUB_MOD_INIT(minix3_be) +#elif defined(MODE_MINIX2) +GRUB_MOD_INIT(minix2_be) +#else +GRUB_MOD_INIT(minix_be) +#endif +#else +#if defined(MODE_MINIX3) +GRUB_MOD_INIT(minix3) +#elif defined(MODE_MINIX2) +GRUB_MOD_INIT(minix2) +#else +GRUB_MOD_INIT(minix) +#endif +#endif +{ + grub_fs_register (&grub_minix_fs); + my_mod = mod; +} + +#ifdef MODE_BIGENDIAN +#if defined(MODE_MINIX3) +GRUB_MOD_FINI(minix3_be) +#elif defined(MODE_MINIX2) +GRUB_MOD_FINI(minix2_be) +#else +GRUB_MOD_FINI(minix_be) +#endif +#else +#if defined(MODE_MINIX3) +GRUB_MOD_FINI(minix3) +#elif defined(MODE_MINIX2) +GRUB_MOD_FINI(minix2) +#else +GRUB_MOD_FINI(minix) +#endif +#endif +{ + grub_fs_unregister (&grub_minix_fs); +} diff --git a/grub-core/fs/minix2.c b/grub-core/fs/minix2.c new file mode 100644 index 0000000..0fcd4b1 --- /dev/null +++ b/grub-core/fs/minix2.c @@ -0,0 +1,2 @@ +#define MODE_MINIX2 1 +#include "minix.c" diff --git a/grub-core/fs/minix2_be.c b/grub-core/fs/minix2_be.c new file mode 100644 index 0000000..cb786df --- /dev/null +++ b/grub-core/fs/minix2_be.c @@ -0,0 +1,3 @@ +#define MODE_MINIX2 1 +#define MODE_BIGENDIAN 1 +#include "minix.c" diff --git a/grub-core/fs/minix3.c b/grub-core/fs/minix3.c new file mode 100644 index 0000000..58a21d2 --- /dev/null +++ b/grub-core/fs/minix3.c @@ -0,0 +1,2 @@ +#define MODE_MINIX3 1 +#include "minix.c" diff --git a/grub-core/fs/minix3_be.c b/grub-core/fs/minix3_be.c new file mode 100644 index 0000000..d0305e4 --- /dev/null +++ b/grub-core/fs/minix3_be.c @@ -0,0 +1,3 @@ +#define MODE_MINIX3 1 +#define MODE_BIGENDIAN 1 +#include "minix.c" diff --git a/grub-core/fs/minix_be.c b/grub-core/fs/minix_be.c new file mode 100644 index 0000000..fade347 --- /dev/null +++ b/grub-core/fs/minix_be.c @@ -0,0 +1,2 @@ +#define MODE_BIGENDIAN 1 +#include "minix.c" diff --git a/grub-core/fs/newc.c b/grub-core/fs/newc.c new file mode 100644 index 0000000..4fb8b2e --- /dev/null +++ b/grub-core/fs/newc.c @@ -0,0 +1,73 @@ +/* cpio.c - cpio and tar filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> + +#define ALIGN_CPIO(x) (ALIGN_UP ((x), 4)) +#define MAGIC "070701" +#define MAGIC2 "070702" +struct head +{ + char magic[6]; + char ino[8]; + char mode[8]; + char uid[8]; + char gid[8]; + char nlink[8]; + char mtime[8]; + char filesize[8]; + char devmajor[8]; + char devminor[8]; + char rdevmajor[8]; + char rdevminor[8]; + char namesize[8]; + char check[8]; +} GRUB_PACKED; + +static inline unsigned long long +read_number (const char *str, grub_size_t size) +{ + unsigned long long ret = 0; + while (size-- && grub_isxdigit (*str)) + { + char dig = *str++; + if (dig >= '0' && dig <= '9') + dig &= 0xf; + else if (dig >= 'a' && dig <= 'f') + dig -= 'a' - 10; + else + dig -= 'A' - 10; + ret = (ret << 4) | (dig); + } + return ret; +} + +#define FSNAME "newc" + +#include "cpio_common.c" + +GRUB_MOD_INIT (newc) +{ + grub_fs_register (&grub_cpio_fs); +} + +GRUB_MOD_FINI (newc) +{ + grub_fs_unregister (&grub_cpio_fs); +} diff --git a/grub-core/fs/nilfs2.c b/grub-core/fs/nilfs2.c new file mode 100644 index 0000000..3c248a9 --- /dev/null +++ b/grub-core/fs/nilfs2.c @@ -0,0 +1,1241 @@ +/* + * nilfs2.c - New Implementation of Log filesystem + * + * Written by Jiro SEKIBA <jir@unicus.jp> + * + * Copyright (C) 2003,2004,2005,2007,2008,2010 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/>. + */ + + +/* Filetype information as used in inodes. */ +#define FILETYPE_INO_MASK 0170000 +#define FILETYPE_INO_REG 0100000 +#define FILETYPE_INO_DIRECTORY 0040000 +#define FILETYPE_INO_SYMLINK 0120000 + +#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> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define NILFS_INODE_BMAP_SIZE 7 + +#define NILFS_SUPORT_REV 2 + +/* Magic value used to identify an nilfs2 filesystem. */ +#define NILFS2_SUPER_MAGIC 0x3434 +/* nilfs btree node flag. */ +#define NILFS_BTREE_NODE_ROOT 0x01 + +/* nilfs btree node level. */ +#define NILFS_BTREE_LEVEL_DATA 0 +#define NILFS_BTREE_LEVEL_NODE_MIN (NILFS_BTREE_LEVEL_DATA + 1) + +/* nilfs 1st super block posission from beginning of the partition + in 512 block size */ +#define NILFS_1ST_SUPER_BLOCK 2 +/* nilfs 2nd super block posission from beginning of the partition + in 512 block size */ +#define NILFS_2ND_SUPER_BLOCK(devsize) (((devsize >> 3) - 1) << 3) + +#define LOG_INODE_SIZE 7 +struct grub_nilfs2_inode +{ + grub_uint64_t i_blocks; + grub_uint64_t i_size; + grub_uint64_t i_ctime; + grub_uint64_t i_mtime; + grub_uint32_t i_ctime_nsec; + grub_uint32_t i_mtime_nsec; + grub_uint32_t i_uid; + grub_uint32_t i_gid; + grub_uint16_t i_mode; + grub_uint16_t i_links_count; + grub_uint32_t i_flags; + grub_uint64_t i_bmap[NILFS_INODE_BMAP_SIZE]; +#define i_device_code i_bmap[0] + grub_uint64_t i_xattr; + grub_uint32_t i_generation; + grub_uint32_t i_pad; +}; + +struct grub_nilfs2_super_root +{ + grub_uint32_t sr_sum; + grub_uint16_t sr_bytes; + grub_uint16_t sr_flags; + grub_uint64_t sr_nongc_ctime; + struct grub_nilfs2_inode sr_dat; + struct grub_nilfs2_inode sr_cpfile; + struct grub_nilfs2_inode sr_sufile; +}; + +struct grub_nilfs2_super_block +{ + grub_uint32_t s_rev_level; + grub_uint16_t s_minor_rev_level; + grub_uint16_t s_magic; + grub_uint16_t s_bytes; + grub_uint16_t s_flags; + grub_uint32_t s_crc_seed; + grub_uint32_t s_sum; + grub_uint32_t s_log_block_size; + grub_uint64_t s_nsegments; + grub_uint64_t s_dev_size; + grub_uint64_t s_first_data_block; + grub_uint32_t s_blocks_per_segment; + grub_uint32_t s_r_segments_percentage; + grub_uint64_t s_last_cno; + grub_uint64_t s_last_pseg; + grub_uint64_t s_last_seq; + grub_uint64_t s_free_blocks_count; + grub_uint64_t s_ctime; + grub_uint64_t s_mtime; + grub_uint64_t s_wtime; + grub_uint16_t s_mnt_count; + grub_uint16_t s_max_mnt_count; + grub_uint16_t s_state; + grub_uint16_t s_errors; + grub_uint64_t s_lastcheck; + grub_uint32_t s_checkinterval; + grub_uint32_t s_creator_os; + grub_uint16_t s_def_resuid; + grub_uint16_t s_def_resgid; + grub_uint32_t s_first_ino; + grub_uint16_t s_inode_size; + grub_uint16_t s_dat_entry_size; + grub_uint16_t s_checkpoint_size; + grub_uint16_t s_segment_usage_size; + grub_uint8_t s_uuid[16]; + char s_volume_name[80]; + grub_uint32_t s_c_interval; + grub_uint32_t s_c_block_max; + grub_uint32_t s_reserved[192]; +}; + +struct grub_nilfs2_dir_entry +{ + grub_uint64_t inode; + grub_uint16_t rec_len; +#define MAX_NAMELEN 255 + grub_uint8_t name_len; + grub_uint8_t file_type; +#if 0 /* followed by file name. */ + char name[NILFS_NAME_LEN]; + char pad; +#endif +} GRUB_PACKED; + +enum +{ + NILFS_FT_UNKNOWN, + NILFS_FT_REG_FILE, + NILFS_FT_DIR, + NILFS_FT_CHRDEV, + NILFS_FT_BLKDEV, + NILFS_FT_FIFO, + NILFS_FT_SOCK, + NILFS_FT_SYMLINK, + NILFS_FT_MAX +}; + +struct grub_nilfs2_finfo +{ + grub_uint64_t fi_ino; + grub_uint64_t fi_cno; + grub_uint32_t fi_nblocks; + grub_uint32_t fi_ndatablk; +}; + +struct grub_nilfs2_binfo_v +{ + grub_uint64_t bi_vblocknr; + grub_uint64_t bi_blkoff; +}; + +struct grub_nilfs2_binfo_dat +{ + grub_uint64_t bi_blkoff; + grub_uint8_t bi_level; + grub_uint8_t bi_pad[7]; +}; + +union grub_nilfs2_binfo +{ + struct grub_nilfs2_binfo_v bi_v; + struct grub_nilfs2_binfo_dat bi_dat; +}; + +struct grub_nilfs2_segment_summary +{ + grub_uint32_t ss_datasum; + grub_uint32_t ss_sumsum; + grub_uint32_t ss_magic; + grub_uint16_t ss_bytes; + grub_uint16_t ss_flags; + grub_uint64_t ss_seq; + grub_uint64_t ss_create; + grub_uint64_t ss_next; + grub_uint32_t ss_nblocks; + grub_uint32_t ss_nfinfo; + grub_uint32_t ss_sumbytes; + grub_uint32_t ss_pad; +}; + +struct grub_nilfs2_btree_node +{ + grub_uint8_t bn_flags; + grub_uint8_t bn_level; + grub_uint16_t bn_nchildren; + grub_uint32_t bn_pad; + grub_uint64_t keys[0]; +}; + +struct grub_nilfs2_palloc_group_desc +{ + grub_uint32_t pg_nfrees; +}; + +#define LOG_SIZE_GROUP_DESC 2 + +#define LOG_NILFS_DAT_ENTRY_SIZE 5 +struct grub_nilfs2_dat_entry +{ + grub_uint64_t de_blocknr; + grub_uint64_t de_start; + grub_uint64_t de_end; + grub_uint64_t de_rsv; +}; + +struct grub_nilfs2_snapshot_list +{ + grub_uint64_t ssl_next; + grub_uint64_t ssl_prev; +}; + +struct grub_nilfs2_cpfile_header +{ + grub_uint64_t ch_ncheckpoints; + grub_uint64_t ch_nsnapshots; + struct grub_nilfs2_snapshot_list ch_snapshot_list; +}; + +struct grub_nilfs2_checkpoint +{ + grub_uint32_t cp_flags; + grub_uint32_t cp_checkpoints_count; + struct grub_nilfs2_snapshot_list cp_snapshot_list; + grub_uint64_t cp_cno; + grub_uint64_t cp_create; + grub_uint64_t cp_nblk_inc; + grub_uint64_t cp_inodes_count; + grub_uint64_t cp_blocks_count; + struct grub_nilfs2_inode cp_ifile_inode; +}; + + +#define NILFS_BMAP_LARGE 0x1 +#define NILFS_BMAP_SIZE (NILFS_INODE_BMAP_SIZE * sizeof(grub_uint64_t)) + +/* nilfs extra padding for nonroot btree node. */ +#define NILFS_BTREE_NODE_EXTRA_PAD_SIZE (sizeof(grub_uint64_t)) +#define NILFS_BTREE_ROOT_SIZE NILFS_BMAP_SIZE +#define NILFS_BTREE_ROOT_NCHILDREN_MAX \ + ((NILFS_BTREE_ROOT_SIZE - sizeof(struct nilfs_btree_node)) / \ + (sizeof(grub_uint64_t) + sizeof(grub_uint64_t)) ) + + +struct grub_fshelp_node +{ + struct grub_nilfs2_data *data; + struct grub_nilfs2_inode inode; + grub_uint64_t ino; + int inode_read; +}; + +struct grub_nilfs2_data +{ + struct grub_nilfs2_super_block sblock; + struct grub_nilfs2_super_root sroot; + struct grub_nilfs2_inode ifile; + grub_disk_t disk; + struct grub_nilfs2_inode *inode; + struct grub_fshelp_node diropen; +}; + +/* Log2 size of nilfs2 block in 512 blocks. */ +#define LOG2_NILFS2_BLOCK_SIZE(data) \ + (grub_le_to_cpu32 (data->sblock.s_log_block_size) + 1) + +/* Log2 size of nilfs2 block in bytes. */ +#define LOG2_BLOCK_SIZE(data) \ + (grub_le_to_cpu32 (data->sblock.s_log_block_size) + 10) + +/* The size of an nilfs2 block in bytes. */ +#define NILFS2_BLOCK_SIZE(data) (1 << LOG2_BLOCK_SIZE (data)) + +static grub_uint64_t +grub_nilfs2_dat_translate (struct grub_nilfs2_data *data, grub_uint64_t key); +static grub_dl_t my_mod; + + + +static inline unsigned long +grub_nilfs2_log_palloc_entries_per_group (struct grub_nilfs2_data *data) +{ + return LOG2_BLOCK_SIZE (data) + 3; +} + +static inline grub_uint64_t +grub_nilfs2_palloc_group (struct grub_nilfs2_data *data, + grub_uint64_t nr, grub_uint64_t * offset) +{ + *offset = nr & ((1 << grub_nilfs2_log_palloc_entries_per_group (data)) - 1); + return nr >> grub_nilfs2_log_palloc_entries_per_group (data); +} + +static inline grub_uint32_t +grub_nilfs2_palloc_log_groups_per_desc_block (struct grub_nilfs2_data *data) +{ + return LOG2_BLOCK_SIZE (data) - LOG_SIZE_GROUP_DESC; + + COMPILE_TIME_ASSERT (sizeof (struct grub_nilfs2_palloc_group_desc) + == (1 << LOG_SIZE_GROUP_DESC)); +} + +static inline grub_uint32_t +grub_nilfs2_log_entries_per_block_log (struct grub_nilfs2_data *data, + unsigned long log_entry_size) +{ + return LOG2_BLOCK_SIZE (data) - log_entry_size; +} + + +static inline grub_uint32_t +grub_nilfs2_blocks_per_group_log (struct grub_nilfs2_data *data, + unsigned long log_entry_size) +{ + return (1 << (grub_nilfs2_log_palloc_entries_per_group (data) + - grub_nilfs2_log_entries_per_block_log (data, + log_entry_size))) + 1; +} + +static inline grub_uint32_t +grub_nilfs2_blocks_per_desc_block_log (struct grub_nilfs2_data *data, + unsigned long log_entry_size) +{ + return(grub_nilfs2_blocks_per_group_log (data, log_entry_size) + << grub_nilfs2_palloc_log_groups_per_desc_block (data)) + 1; +} + +static inline grub_uint32_t +grub_nilfs2_palloc_desc_block_offset_log (struct grub_nilfs2_data *data, + unsigned long group, + unsigned long log_entry_size) +{ + grub_uint32_t desc_block = + group >> grub_nilfs2_palloc_log_groups_per_desc_block (data); + return desc_block * grub_nilfs2_blocks_per_desc_block_log (data, + log_entry_size); +} + +static inline grub_uint32_t +grub_nilfs2_palloc_bitmap_block_offset (struct grub_nilfs2_data *data, + unsigned long group, + unsigned long log_entry_size) +{ + unsigned long desc_offset = group + & ((1 << grub_nilfs2_palloc_log_groups_per_desc_block (data)) - 1); + + return grub_nilfs2_palloc_desc_block_offset_log (data, group, log_entry_size) + + 1 + + desc_offset * grub_nilfs2_blocks_per_group_log (data, log_entry_size); +} + +static inline grub_uint32_t +grub_nilfs2_palloc_entry_offset_log (struct grub_nilfs2_data *data, + grub_uint64_t nr, + unsigned long log_entry_size) +{ + unsigned long group; + grub_uint64_t group_offset; + + group = grub_nilfs2_palloc_group (data, nr, &group_offset); + + return grub_nilfs2_palloc_bitmap_block_offset (data, group, + log_entry_size) + 1 + + (group_offset >> grub_nilfs2_log_entries_per_block_log (data, + log_entry_size)); + +} + +static inline struct grub_nilfs2_btree_node * +grub_nilfs2_btree_get_root (struct grub_nilfs2_inode *inode) +{ + return (struct grub_nilfs2_btree_node *) &inode->i_bmap[0]; +} + +static inline int +grub_nilfs2_btree_get_level (struct grub_nilfs2_btree_node *node) +{ + return node->bn_level; +} + +static inline grub_uint64_t * +grub_nilfs2_btree_node_dkeys (struct grub_nilfs2_btree_node *node) +{ + return (node->keys + + ((node->bn_flags & NILFS_BTREE_NODE_ROOT) ? + 0 : (NILFS_BTREE_NODE_EXTRA_PAD_SIZE / sizeof (grub_uint64_t)))); +} + +static inline grub_uint64_t +grub_nilfs2_btree_node_get_key (struct grub_nilfs2_btree_node *node, + int index) +{ + return grub_le_to_cpu64 (*(grub_nilfs2_btree_node_dkeys (node) + index)); +} + +static inline int +grub_nilfs2_btree_node_nchildren_max (struct grub_nilfs2_data *data, + struct grub_nilfs2_btree_node *node) +{ + int node_children_max = ((NILFS2_BLOCK_SIZE (data) - + sizeof (struct grub_nilfs2_btree_node) - + NILFS_BTREE_NODE_EXTRA_PAD_SIZE) / + (sizeof (grub_uint64_t) + sizeof (grub_uint64_t))); + + return (node->bn_flags & NILFS_BTREE_NODE_ROOT) ? 3 : node_children_max; +} + +static inline int +grub_nilfs2_btree_node_lookup (struct grub_nilfs2_data *data, + struct grub_nilfs2_btree_node *node, + grub_uint64_t key, int *indexp) +{ + grub_uint64_t nkey; + int index = 0, low, high, s; + + low = 0; + + high = grub_le_to_cpu16 (node->bn_nchildren) - 1; + if (high >= grub_nilfs2_btree_node_nchildren_max (data, node)) + { + grub_error (GRUB_ERR_BAD_FS, "too many children"); + *indexp = index; + return 0; + } + + s = 0; + while (low <= high) + { + index = (low + high) / 2; + nkey = grub_nilfs2_btree_node_get_key (node, index); + if (nkey == key) + { + *indexp = index; + return 1; + } + else if (nkey < key) + { + low = index + 1; + s = -1; + } + else + { + high = index - 1; + s = 1; + } + } + + if (node->bn_level > NILFS_BTREE_LEVEL_NODE_MIN) + { + if (s > 0 && index > 0) + index--; + } + else if (s < 0) + index++; + + *indexp = index; + return s == 0; +} + +static inline grub_uint64_t * +grub_nilfs2_btree_node_dptrs (struct grub_nilfs2_data *data, + struct grub_nilfs2_btree_node *node) +{ + return (grub_uint64_t *) (grub_nilfs2_btree_node_dkeys (node) + + grub_nilfs2_btree_node_nchildren_max (data, + node)); +} + +static inline grub_uint64_t +grub_nilfs2_btree_node_get_ptr (struct grub_nilfs2_data *data, + struct grub_nilfs2_btree_node *node, + int index) +{ + return + grub_le_to_cpu64 (*(grub_nilfs2_btree_node_dptrs (data, node) + index)); +} + +static inline int +grub_nilfs2_btree_get_nonroot_node (struct grub_nilfs2_data *data, + grub_uint64_t ptr, void *block) +{ + grub_disk_t disk = data->disk; + unsigned int nilfs2_block_count = (1 << LOG2_NILFS2_BLOCK_SIZE (data)); + + return grub_disk_read (disk, ptr * nilfs2_block_count, 0, + NILFS2_BLOCK_SIZE (data), block); +} + +static grub_uint64_t +grub_nilfs2_btree_lookup (struct grub_nilfs2_data *data, + struct grub_nilfs2_inode *inode, + grub_uint64_t key, int need_translate) +{ + struct grub_nilfs2_btree_node *node; + void *block; + grub_uint64_t ptr; + int level, found = 0, index; + + block = grub_malloc (NILFS2_BLOCK_SIZE (data)); + if (!block) + return -1; + + node = grub_nilfs2_btree_get_root (inode); + level = grub_nilfs2_btree_get_level (node); + + found = grub_nilfs2_btree_node_lookup (data, node, key, &index); + + if (grub_errno != GRUB_ERR_NONE) + goto fail; + + ptr = grub_nilfs2_btree_node_get_ptr (data, node, index); + if (need_translate) + ptr = grub_nilfs2_dat_translate (data, ptr); + + for (level--; level >= NILFS_BTREE_LEVEL_NODE_MIN; level--) + { + grub_nilfs2_btree_get_nonroot_node (data, ptr, block); + if (grub_errno) + { + goto fail; + } + node = (struct grub_nilfs2_btree_node *) block; + + if (node->bn_level != level) + { + grub_error (GRUB_ERR_BAD_FS, "btree level mismatch\n"); + goto fail; + } + + if (!found) + found = grub_nilfs2_btree_node_lookup (data, node, key, &index); + else + index = 0; + + if (index < grub_nilfs2_btree_node_nchildren_max (data, node) && + grub_errno == GRUB_ERR_NONE) + { + ptr = grub_nilfs2_btree_node_get_ptr (data, node, index); + if (need_translate) + ptr = grub_nilfs2_dat_translate (data, ptr); + } + else + { + grub_error (GRUB_ERR_BAD_FS, "btree corruption\n"); + goto fail; + } + } + + grub_free (block); + + if (!found) + return -1; + + return ptr; + fail: + grub_free (block); + return -1; +} + +static inline grub_uint64_t +grub_nilfs2_direct_lookup (struct grub_nilfs2_inode *inode, grub_uint64_t key) +{ + if (1 + key > 6) + { + grub_error (GRUB_ERR_BAD_FS, "key is too large"); + return 0xffffffffffffffff; + } + return grub_le_to_cpu64 (inode->i_bmap[1 + key]); +} + +static inline grub_uint64_t +grub_nilfs2_bmap_lookup (struct grub_nilfs2_data *data, + struct grub_nilfs2_inode *inode, + grub_uint64_t key, int need_translate) +{ + struct grub_nilfs2_btree_node *root = grub_nilfs2_btree_get_root (inode); + if (root->bn_flags & NILFS_BMAP_LARGE) + return grub_nilfs2_btree_lookup (data, inode, key, need_translate); + else + { + grub_uint64_t ptr; + ptr = grub_nilfs2_direct_lookup (inode, key); + if (ptr != ((grub_uint64_t) 0xffffffffffffffff) && need_translate) + ptr = grub_nilfs2_dat_translate (data, ptr); + return ptr; + } +} + +static grub_uint64_t +grub_nilfs2_dat_translate (struct grub_nilfs2_data *data, grub_uint64_t key) +{ + struct grub_nilfs2_dat_entry entry; + grub_disk_t disk = data->disk; + grub_uint64_t pptr; + grub_uint64_t blockno, offset; + unsigned int nilfs2_block_count = (1 << LOG2_NILFS2_BLOCK_SIZE (data)); + + blockno = grub_nilfs2_palloc_entry_offset_log (data, key, + LOG_NILFS_DAT_ENTRY_SIZE); + + offset = ((key * sizeof (struct grub_nilfs2_dat_entry)) + & ((1 << LOG2_BLOCK_SIZE (data)) - 1)); + + pptr = grub_nilfs2_bmap_lookup (data, &data->sroot.sr_dat, blockno, 0); + if (pptr == (grub_uint64_t) - 1) + { + grub_error (GRUB_ERR_BAD_FS, "btree lookup failure"); + return -1; + } + + grub_disk_read (disk, pptr * nilfs2_block_count, offset, + sizeof (struct grub_nilfs2_dat_entry), &entry); + + return grub_le_to_cpu64 (entry.de_blocknr); +} + + +static grub_disk_addr_t +grub_nilfs2_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +{ + struct grub_nilfs2_data *data = node->data; + struct grub_nilfs2_inode *inode = &node->inode; + grub_uint64_t pptr = -1; + + pptr = grub_nilfs2_bmap_lookup (data, inode, fileblock, 1); + if (pptr == (grub_uint64_t) - 1) + { + grub_error (GRUB_ERR_BAD_FS, "btree lookup failure"); + return -1; + } + + return pptr; +} + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +static grub_ssize_t +grub_nilfs2_read_file (grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + return grub_fshelp_read_file (node->data->disk, node, + read_hook, read_hook_data, + pos, len, buf, grub_nilfs2_read_block, + grub_le_to_cpu64 (node->inode.i_size), + LOG2_NILFS2_BLOCK_SIZE (node->data), 0); + +} + +static grub_err_t +grub_nilfs2_read_checkpoint (struct grub_nilfs2_data *data, + grub_uint64_t cpno, + struct grub_nilfs2_checkpoint *cpp) +{ + grub_uint64_t blockno; + grub_uint64_t offset; + grub_uint64_t pptr; + grub_disk_t disk = data->disk; + unsigned int nilfs2_block_count = (1 << LOG2_NILFS2_BLOCK_SIZE (data)); + + /* Assume sizeof(struct grub_nilfs2_cpfile_header) < + sizeof(struct grub_nilfs2_checkpoint). + */ + blockno = grub_divmod64 (cpno, NILFS2_BLOCK_SIZE (data) / + sizeof (struct grub_nilfs2_checkpoint), &offset); + + pptr = grub_nilfs2_bmap_lookup (data, &data->sroot.sr_cpfile, blockno, 1); + if (pptr == (grub_uint64_t) - 1) + { + return grub_error (GRUB_ERR_BAD_FS, "btree lookup failure"); + } + + return grub_disk_read (disk, pptr * nilfs2_block_count, + offset * sizeof (struct grub_nilfs2_checkpoint), + sizeof (struct grub_nilfs2_checkpoint), cpp); +} + +static inline grub_err_t +grub_nilfs2_read_last_checkpoint (struct grub_nilfs2_data *data, + struct grub_nilfs2_checkpoint *cpp) +{ + return grub_nilfs2_read_checkpoint (data, + grub_le_to_cpu64 (data-> + sblock.s_last_cno), + cpp); +} + +/* Read the inode INO for the file described by DATA into INODE. */ +static grub_err_t +grub_nilfs2_read_inode (struct grub_nilfs2_data *data, + grub_uint64_t ino, struct grub_nilfs2_inode *inodep) +{ + grub_uint64_t blockno; + grub_uint64_t offset; + grub_uint64_t pptr; + grub_disk_t disk = data->disk; + unsigned int nilfs2_block_count = (1 << LOG2_NILFS2_BLOCK_SIZE (data)); + + blockno = grub_nilfs2_palloc_entry_offset_log (data, ino, + LOG_INODE_SIZE); + + offset = ((sizeof (struct grub_nilfs2_inode) * ino) + & ((1 << LOG2_BLOCK_SIZE (data)) - 1)); + pptr = grub_nilfs2_bmap_lookup (data, &data->ifile, blockno, 1); + if (pptr == (grub_uint64_t) - 1) + { + return grub_error (GRUB_ERR_BAD_FS, "btree lookup failure"); + } + + return grub_disk_read (disk, pptr * nilfs2_block_count, offset, + sizeof (struct grub_nilfs2_inode), inodep); +} + +static int +grub_nilfs2_valid_sb (struct grub_nilfs2_super_block *sbp) +{ + if (grub_le_to_cpu16 (sbp->s_magic) != NILFS2_SUPER_MAGIC) + return 0; + + if (grub_le_to_cpu32 (sbp->s_rev_level) != NILFS_SUPORT_REV) + return 0; + + /* 20 already means 1GiB blocks. We don't want to deal with blocks overflowing int32. */ + if (grub_le_to_cpu32 (sbp->s_log_block_size) > 20) + return 0; + + return 1; +} + +static grub_err_t +grub_nilfs2_load_sb (struct grub_nilfs2_data *data) +{ + grub_disk_t disk = data->disk; + struct grub_nilfs2_super_block sb2; + grub_uint64_t partition_size; + int valid[2]; + int swp = 0; + grub_err_t err; + + /* Read first super block. */ + err = grub_disk_read (disk, NILFS_1ST_SUPER_BLOCK, 0, + sizeof (struct grub_nilfs2_super_block), &data->sblock); + if (err) + return err; + /* Make sure if 1st super block is valid. */ + valid[0] = grub_nilfs2_valid_sb (&data->sblock); + + if (valid[0]) + partition_size = (grub_le_to_cpu64 (data->sblock.s_dev_size) + >> GRUB_DISK_SECTOR_BITS); + else + partition_size = grub_disk_native_sectors (disk); + if (partition_size != GRUB_DISK_SIZE_UNKNOWN) + { + /* Read second super block. */ + err = grub_disk_read (disk, NILFS_2ND_SUPER_BLOCK (partition_size), 0, + sizeof (struct grub_nilfs2_super_block), &sb2); + if (err) + { + valid[1] = 0; + grub_errno = GRUB_ERR_NONE; + } + else + /* Make sure if 2nd super block is valid. */ + valid[1] = grub_nilfs2_valid_sb (&sb2); + } + else + /* 2nd super block may not exist, so it's invalid. */ + valid[1] = 0; + + if (!valid[0] && !valid[1]) + return grub_error (GRUB_ERR_BAD_FS, "not a nilfs2 filesystem"); + + swp = valid[1] && (!valid[0] || + grub_le_to_cpu64 (data->sblock.s_last_cno) < + grub_le_to_cpu64 (sb2.s_last_cno)); + + /* swap if first super block is invalid or older than second one. */ + if (swp) + grub_memcpy (&data->sblock, &sb2, + sizeof (struct grub_nilfs2_super_block)); + + return GRUB_ERR_NONE; +} + +static struct grub_nilfs2_data * +grub_nilfs2_mount (grub_disk_t disk) +{ + struct grub_nilfs2_data *data; + struct grub_nilfs2_segment_summary ss; + struct grub_nilfs2_checkpoint last_checkpoint; + grub_uint64_t last_pseg; + grub_uint32_t nblocks; + unsigned int nilfs2_block_count; + + data = grub_malloc (sizeof (struct grub_nilfs2_data)); + if (!data) + return 0; + + data->disk = disk; + + /* Read the superblock. */ + grub_nilfs2_load_sb (data); + if (grub_errno) + goto fail; + + nilfs2_block_count = (1 << LOG2_NILFS2_BLOCK_SIZE (data)); + + /* Read the last segment summary. */ + last_pseg = grub_le_to_cpu64 (data->sblock.s_last_pseg); + grub_disk_read (disk, last_pseg * nilfs2_block_count, 0, + sizeof (struct grub_nilfs2_segment_summary), &ss); + + if (grub_errno) + goto fail; + + /* Read the super root block. */ + nblocks = grub_le_to_cpu32 (ss.ss_nblocks); + grub_disk_read (disk, (last_pseg + (nblocks - 1)) * nilfs2_block_count, 0, + sizeof (struct grub_nilfs2_super_root), &data->sroot); + + if (grub_errno) + goto fail; + + grub_nilfs2_read_last_checkpoint (data, &last_checkpoint); + + if (grub_errno) + goto fail; + + grub_memcpy (&data->ifile, &last_checkpoint.cp_ifile_inode, + sizeof (struct grub_nilfs2_inode)); + + data->diropen.data = data; + data->diropen.ino = 2; + data->diropen.inode_read = 1; + data->inode = &data->diropen.inode; + + grub_nilfs2_read_inode (data, 2, data->inode); + + return data; + +fail: + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a nilfs2 filesystem"); + + grub_free (data); + return 0; +} + +static char * +grub_nilfs2_read_symlink (grub_fshelp_node_t node) +{ + char *symlink; + struct grub_fshelp_node *diro = node; + + if (!diro->inode_read) + { + grub_nilfs2_read_inode (diro->data, diro->ino, &diro->inode); + if (grub_errno) + return 0; + } + + symlink = grub_malloc (grub_le_to_cpu64 (diro->inode.i_size) + 1); + if (!symlink) + return 0; + + grub_nilfs2_read_file (diro, 0, 0, 0, + grub_le_to_cpu64 (diro->inode.i_size), symlink); + if (grub_errno) + { + grub_free (symlink); + return 0; + } + + symlink[grub_le_to_cpu64 (diro->inode.i_size)] = '\0'; + return symlink; +} + +static int +grub_nilfs2_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + grub_off_t fpos = 0; + struct grub_fshelp_node *diro = (struct grub_fshelp_node *) dir; + + if (!diro->inode_read) + { + grub_nilfs2_read_inode (diro->data, diro->ino, &diro->inode); + if (grub_errno) + return 0; + } + + /* Iterate files. */ + while (fpos < grub_le_to_cpu64 (diro->inode.i_size)) + { + struct grub_nilfs2_dir_entry dirent; + + grub_nilfs2_read_file (diro, 0, 0, fpos, + sizeof (struct grub_nilfs2_dir_entry), + (char *) &dirent); + if (grub_errno) + return 0; + + if (dirent.rec_len == 0) + return 0; + + if (dirent.name_len != 0) + { + char filename[MAX_NAMELEN + 1]; + struct grub_fshelp_node *fdiro; + enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN; + + grub_nilfs2_read_file (diro, 0, 0, + fpos + sizeof (struct grub_nilfs2_dir_entry), + dirent.name_len, filename); + if (grub_errno) + return 0; + + fdiro = grub_malloc (sizeof (struct grub_fshelp_node)); + if (!fdiro) + return 0; + + fdiro->data = diro->data; + fdiro->ino = grub_le_to_cpu64 (dirent.inode); + + filename[dirent.name_len] = '\0'; + + if (dirent.file_type != NILFS_FT_UNKNOWN) + { + fdiro->inode_read = 0; + + if (dirent.file_type == NILFS_FT_DIR) + type = GRUB_FSHELP_DIR; + else if (dirent.file_type == NILFS_FT_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + else if (dirent.file_type == NILFS_FT_REG_FILE) + type = GRUB_FSHELP_REG; + } + else + { + /* The filetype can not be read from the dirent, read + the inode to get more information. */ + grub_nilfs2_read_inode (diro->data, + grub_le_to_cpu64 (dirent.inode), + &fdiro->inode); + if (grub_errno) + { + grub_free (fdiro); + return 0; + } + + fdiro->inode_read = 1; + + if ((grub_le_to_cpu16 (fdiro->inode.i_mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_DIRECTORY) + type = GRUB_FSHELP_DIR; + else if ((grub_le_to_cpu16 (fdiro->inode.i_mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + else if ((grub_le_to_cpu16 (fdiro->inode.i_mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_REG) + type = GRUB_FSHELP_REG; + } + + if (hook (filename, type, fdiro, hook_data)) + return 1; + } + + fpos += grub_le_to_cpu16 (dirent.rec_len); + } + + return 0; +} + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_nilfs2_open (struct grub_file *file, const char *name) +{ + struct grub_nilfs2_data *data = NULL; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_nilfs2_mount (file->device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file (name, &data->diropen, &fdiro, + grub_nilfs2_iterate_dir, grub_nilfs2_read_symlink, + GRUB_FSHELP_REG); + if (grub_errno) + goto fail; + + if (!fdiro->inode_read) + { + grub_nilfs2_read_inode (data, fdiro->ino, &fdiro->inode); + if (grub_errno) + goto fail; + } + + grub_memcpy (data->inode, &fdiro->inode, sizeof (struct grub_nilfs2_inode)); + grub_free (fdiro); + + file->size = grub_le_to_cpu64 (data->inode->i_size); + file->data = data; + file->offset = 0; + + return 0; + +fail: + if (fdiro != &data->diropen) + grub_free (fdiro); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_nilfs2_close (grub_file_t file) +{ + 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_nilfs2_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_nilfs2_data *data = (struct grub_nilfs2_data *) file->data; + + return grub_nilfs2_read_file (&data->diropen, + file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + +/* Context for grub_nilfs2_dir. */ +struct grub_nilfs2_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; + struct grub_nilfs2_data *data; +}; + +/* Helper for grub_nilfs2_dir. */ +static int +grub_nilfs2_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_nilfs2_dir_ctx *ctx = data; + struct grub_dirhook_info info; + + grub_memset (&info, 0, sizeof (info)); + if (!node->inode_read) + { + grub_nilfs2_read_inode (ctx->data, node->ino, &node->inode); + if (!grub_errno) + node->inode_read = 1; + grub_errno = GRUB_ERR_NONE; + } + if (node->inode_read) + { + info.mtimeset = 1; + info.mtime = grub_le_to_cpu64 (node->inode.i_mtime); + } + + info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_nilfs2_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_nilfs2_dir_ctx ctx = { + .hook = hook, + .hook_data = hook_data + }; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + ctx.data = grub_nilfs2_mount (device->disk); + if (!ctx.data) + goto fail; + + grub_fshelp_find_file (path, &ctx.data->diropen, &fdiro, + grub_nilfs2_iterate_dir, grub_nilfs2_read_symlink, + GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + + grub_nilfs2_iterate_dir (fdiro, grub_nilfs2_dir_iter, &ctx); + +fail: + if (fdiro != &ctx.data->diropen) + grub_free (fdiro); + grub_free (ctx.data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_nilfs2_label (grub_device_t device, char **label) +{ + struct grub_nilfs2_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_nilfs2_mount (disk); + if (data) + *label = grub_strndup (data->sblock.s_volume_name, + sizeof (data->sblock.s_volume_name)); + else + *label = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_nilfs2_uuid (grub_device_t device, char **uuid) +{ + struct grub_nilfs2_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_nilfs2_mount (disk); + if (data) + { + *uuid = + grub_xasprintf + ("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + data->sblock.s_uuid[0], data->sblock.s_uuid[1], + data->sblock.s_uuid[2], data->sblock.s_uuid[3], + data->sblock.s_uuid[4], data->sblock.s_uuid[5], + data->sblock.s_uuid[6], data->sblock.s_uuid[7], + data->sblock.s_uuid[8], data->sblock.s_uuid[9], + data->sblock.s_uuid[10], data->sblock.s_uuid[11], + data->sblock.s_uuid[12], data->sblock.s_uuid[13], + data->sblock.s_uuid[14], data->sblock.s_uuid[15]); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +/* Get mtime. */ +static grub_err_t +grub_nilfs2_mtime (grub_device_t device, grub_int64_t * tm) +{ + struct grub_nilfs2_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_nilfs2_mount (disk); + if (!data) + *tm = 0; + else + *tm = (grub_int32_t) grub_le_to_cpu64 (data->sblock.s_wtime); + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + + +static struct grub_fs grub_nilfs2_fs = { + .name = "nilfs2", + .fs_dir = grub_nilfs2_dir, + .fs_open = grub_nilfs2_open, + .fs_read = grub_nilfs2_read, + .fs_close = grub_nilfs2_close, + .fs_label = grub_nilfs2_label, + .fs_uuid = grub_nilfs2_uuid, + .fs_mtime = grub_nilfs2_mtime, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 0, +#endif + .next = 0 +}; + +GRUB_MOD_INIT (nilfs2) +{ + COMPILE_TIME_ASSERT ((1 << LOG_NILFS_DAT_ENTRY_SIZE) + == sizeof (struct + grub_nilfs2_dat_entry)); + COMPILE_TIME_ASSERT (1 << LOG_INODE_SIZE + == sizeof (struct grub_nilfs2_inode)); + grub_fs_register (&grub_nilfs2_fs); + my_mod = mod; +} + +GRUB_MOD_FINI (nilfs2) +{ + grub_fs_unregister (&grub_nilfs2_fs); +} diff --git a/grub-core/fs/ntfs.c b/grub-core/fs/ntfs.c new file mode 100644 index 0000000..2f34f76 --- /dev/null +++ b/grub-core/fs/ntfs.c @@ -0,0 +1,1237 @@ +/* ntfs.c - NTFS filesystem */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define grub_fshelp_node grub_ntfs_file + +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/disk.h> +#include <grub/dl.h> +#include <grub/fshelp.h> +#include <grub/ntfs.h> +#include <grub/charset.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; + +#define grub_fshelp_node grub_ntfs_file + +static inline grub_uint16_t +u16at (void *ptr, grub_size_t ofs) +{ + return grub_le_to_cpu16 (grub_get_unaligned16 ((char *) ptr + ofs)); +} + +static inline grub_uint32_t +u32at (void *ptr, grub_size_t ofs) +{ + return grub_le_to_cpu32 (grub_get_unaligned32 ((char *) ptr + ofs)); +} + +static inline grub_uint64_t +u64at (void *ptr, grub_size_t ofs) +{ + return grub_le_to_cpu64 (grub_get_unaligned64 ((char *) ptr + ofs)); +} + +grub_ntfscomp_func_t grub_ntfscomp_func; + +static grub_err_t +fixup (grub_uint8_t *buf, grub_size_t len, const grub_uint8_t *magic) +{ + grub_uint16_t ss; + grub_uint8_t *pu; + grub_uint16_t us; + + COMPILE_TIME_ASSERT ((1 << GRUB_NTFS_BLK_SHR) == GRUB_DISK_SECTOR_SIZE); + + if (grub_memcmp (buf, magic, 4)) + return grub_error (GRUB_ERR_BAD_FS, "%s label not found", magic); + + ss = u16at (buf, 6) - 1; + if (ss != len) + return grub_error (GRUB_ERR_BAD_FS, "size not match"); + pu = buf + u16at (buf, 4); + us = u16at (pu, 0); + buf -= 2; + while (ss > 0) + { + buf += GRUB_DISK_SECTOR_SIZE; + pu += 2; + if (u16at (buf, 0) != us) + return grub_error (GRUB_ERR_BAD_FS, "fixup signature not match"); + buf[0] = pu[0]; + buf[1] = pu[1]; + ss--; + } + + return 0; +} + +static grub_err_t read_mft (struct grub_ntfs_data *data, grub_uint8_t *buf, + grub_uint64_t mftno); +static grub_err_t read_attr (struct grub_ntfs_attr *at, grub_uint8_t *dest, + grub_disk_addr_t ofs, grub_size_t len, + int cached, + grub_disk_read_hook_t read_hook, + void *read_hook_data); + +static grub_err_t read_data (struct grub_ntfs_attr *at, grub_uint8_t *pa, + grub_uint8_t *dest, + grub_disk_addr_t ofs, grub_size_t len, + int cached, + grub_disk_read_hook_t read_hook, + void *read_hook_data); + +static void +init_attr (struct grub_ntfs_attr *at, struct grub_ntfs_file *mft) +{ + at->mft = mft; + at->flags = (mft == &mft->data->mmft) ? GRUB_NTFS_AF_MMFT : 0; + at->attr_nxt = mft->buf + u16at (mft->buf, 0x14); + at->attr_end = at->emft_buf = at->edat_buf = at->sbuf = NULL; +} + +static void +free_attr (struct grub_ntfs_attr *at) +{ + grub_free (at->emft_buf); + grub_free (at->edat_buf); + grub_free (at->sbuf); +} + +static grub_uint8_t * +find_attr (struct grub_ntfs_attr *at, grub_uint8_t attr) +{ + if (at->flags & GRUB_NTFS_AF_ALST) + { + retry: + while (at->attr_nxt < at->attr_end) + { + at->attr_cur = at->attr_nxt; + at->attr_nxt += u16at (at->attr_cur, 4); + if ((*at->attr_cur == attr) || (attr == 0)) + { + grub_uint8_t *new_pos; + + if (at->flags & GRUB_NTFS_AF_MMFT) + { + if ((grub_disk_read + (at->mft->data->disk, u32at (at->attr_cur, 0x10), 0, + 512, at->emft_buf)) + || + (grub_disk_read + (at->mft->data->disk, u32at (at->attr_cur, 0x14), 0, + 512, at->emft_buf + 512))) + return NULL; + + if (fixup (at->emft_buf, at->mft->data->mft_size, + (const grub_uint8_t *) "FILE")) + return NULL; + } + else + { + if (read_mft (at->mft->data, at->emft_buf, + u32at (at->attr_cur, 0x10))) + return NULL; + } + + new_pos = &at->emft_buf[u16at (at->emft_buf, 0x14)]; + while (*new_pos != 0xFF) + { + if ((*new_pos == *at->attr_cur) + && (u16at (new_pos, 0xE) == u16at (at->attr_cur, 0x18))) + { + return new_pos; + } + new_pos += u16at (new_pos, 4); + } + grub_error (GRUB_ERR_BAD_FS, + "can\'t find 0x%X in attribute list", + (unsigned char) *at->attr_cur); + return NULL; + } + } + return NULL; + } + at->attr_cur = at->attr_nxt; + while (*at->attr_cur != 0xFF) + { + at->attr_nxt += u16at (at->attr_cur, 4); + if (*at->attr_cur == GRUB_NTFS_AT_ATTRIBUTE_LIST) + at->attr_end = at->attr_cur; + if ((*at->attr_cur == attr) || (attr == 0)) + return at->attr_cur; + at->attr_cur = at->attr_nxt; + } + if (at->attr_end) + { + grub_uint8_t *pa; + + at->emft_buf = grub_malloc (at->mft->data->mft_size << GRUB_NTFS_BLK_SHR); + if (at->emft_buf == NULL) + return NULL; + + pa = at->attr_end; + if (pa[8]) + { + grub_uint32_t n; + + n = ((u32at (pa, 0x30) + GRUB_DISK_SECTOR_SIZE - 1) + & (~(GRUB_DISK_SECTOR_SIZE - 1))); + at->attr_cur = at->attr_end; + at->edat_buf = grub_malloc (n); + if (!at->edat_buf) + return NULL; + if (read_data (at, pa, at->edat_buf, 0, n, 0, 0, 0)) + { + grub_error (GRUB_ERR_BAD_FS, + "fail to read non-resident attribute list"); + return NULL; + } + at->attr_nxt = at->edat_buf; + at->attr_end = at->edat_buf + u32at (pa, 0x30); + } + else + { + at->attr_nxt = at->attr_end + u16at (pa, 0x14); + at->attr_end = at->attr_end + u32at (pa, 4); + } + at->flags |= GRUB_NTFS_AF_ALST; + while (at->attr_nxt < at->attr_end) + { + if ((*at->attr_nxt == attr) || (attr == 0)) + break; + at->attr_nxt += u16at (at->attr_nxt, 4); + } + if (at->attr_nxt >= at->attr_end) + return NULL; + + if ((at->flags & GRUB_NTFS_AF_MMFT) && (attr == GRUB_NTFS_AT_DATA)) + { + at->flags |= GRUB_NTFS_AF_GPOS; + at->attr_cur = at->attr_nxt; + pa = at->attr_cur; + grub_set_unaligned32 ((char *) pa + 0x10, + grub_cpu_to_le32 (at->mft->data->mft_start)); + grub_set_unaligned32 ((char *) pa + 0x14, + grub_cpu_to_le32 (at->mft->data->mft_start + + 1)); + pa = at->attr_nxt + u16at (pa, 4); + while (pa < at->attr_end) + { + if (*pa != attr) + break; + if (read_attr + (at, pa + 0x10, + u32at (pa, 0x10) * (at->mft->data->mft_size << GRUB_NTFS_BLK_SHR), + at->mft->data->mft_size << GRUB_NTFS_BLK_SHR, 0, 0, 0)) + return NULL; + pa += u16at (pa, 4); + } + at->attr_nxt = at->attr_cur; + at->flags &= ~GRUB_NTFS_AF_GPOS; + } + goto retry; + } + return NULL; +} + +static grub_uint8_t * +locate_attr (struct grub_ntfs_attr *at, struct grub_ntfs_file *mft, + grub_uint8_t attr) +{ + grub_uint8_t *pa; + + init_attr (at, mft); + pa = find_attr (at, attr); + if (pa == NULL) + return NULL; + if ((at->flags & GRUB_NTFS_AF_ALST) == 0) + { + while (1) + { + pa = find_attr (at, attr); + if (pa == NULL) + break; + if (at->flags & GRUB_NTFS_AF_ALST) + return pa; + } + grub_errno = GRUB_ERR_NONE; + free_attr (at); + init_attr (at, mft); + pa = find_attr (at, attr); + } + return pa; +} + +static grub_disk_addr_t +read_run_data (const grub_uint8_t *run, int nn, int sig) +{ + grub_uint64_t r = 0; + + if (sig && nn && (run[nn - 1] & 0x80)) + r = -1; + + grub_memcpy (&r, run, nn); + + return grub_le_to_cpu64 (r); +} + +grub_err_t +grub_ntfs_read_run_list (struct grub_ntfs_rlst * ctx) +{ + grub_uint8_t c1, c2; + grub_disk_addr_t val; + grub_uint8_t *run; + + run = ctx->cur_run; +retry: + c1 = ((*run) & 0x7); + c2 = ((*run) >> 4) & 0x7; + run++; + if (!c1) + { + if ((ctx->attr) && (ctx->attr->flags & GRUB_NTFS_AF_ALST)) + { + grub_disk_read_hook_t save_hook; + + save_hook = ctx->comp.disk->read_hook; + ctx->comp.disk->read_hook = 0; + run = find_attr (ctx->attr, *ctx->attr->attr_cur); + ctx->comp.disk->read_hook = save_hook; + if (run) + { + if (run[8] == 0) + return grub_error (GRUB_ERR_BAD_FS, + "$DATA should be non-resident"); + + run += u16at (run, 0x20); + ctx->curr_lcn = 0; + goto retry; + } + } + return grub_error (GRUB_ERR_BAD_FS, "run list overflown"); + } + ctx->curr_vcn = ctx->next_vcn; + ctx->next_vcn += read_run_data (run, c1, 0); /* length of current VCN */ + run += c1; + val = read_run_data (run, c2, 1); /* offset to previous LCN */ + run += c2; + ctx->curr_lcn += val; + if (val == 0) + ctx->flags |= GRUB_NTFS_RF_BLNK; + else + ctx->flags &= ~GRUB_NTFS_RF_BLNK; + ctx->cur_run = run; + return 0; +} + +static grub_disk_addr_t +grub_ntfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t block) +{ + struct grub_ntfs_rlst *ctx; + + ctx = (struct grub_ntfs_rlst *) node; + if (block >= ctx->next_vcn) + { + if (grub_ntfs_read_run_list (ctx)) + return -1; + return ctx->curr_lcn; + } + else + return (ctx->flags & GRUB_NTFS_RF_BLNK) ? 0 : (block - + ctx->curr_vcn + ctx->curr_lcn); +} + +static grub_err_t +read_data (struct grub_ntfs_attr *at, grub_uint8_t *pa, grub_uint8_t *dest, + grub_disk_addr_t ofs, grub_size_t len, int cached, + grub_disk_read_hook_t read_hook, void *read_hook_data) +{ + struct grub_ntfs_rlst cc, *ctx; + + if (len == 0) + return 0; + + grub_memset (&cc, 0, sizeof (cc)); + ctx = &cc; + ctx->attr = at; + ctx->comp.log_spc = at->mft->data->log_spc; + ctx->comp.disk = at->mft->data->disk; + + if (read_hook == grub_file_progress_hook) + ctx->file = read_hook_data; + + if (pa[8] == 0) + { + if (ofs + len > u32at (pa, 0x10)) + return grub_error (GRUB_ERR_BAD_FS, "read out of range"); + grub_memcpy (dest, pa + u32at (pa, 0x14) + ofs, len); + return 0; + } + + ctx->cur_run = pa + u16at (pa, 0x20); + + ctx->next_vcn = u32at (pa, 0x10); + ctx->curr_lcn = 0; + + if ((pa[0xC] & GRUB_NTFS_FLAG_COMPRESSED) + && !(at->flags & GRUB_NTFS_AF_GPOS)) + { + if (!cached) + return grub_error (GRUB_ERR_BAD_FS, "attribute can\'t be compressed"); + + return (grub_ntfscomp_func) ? grub_ntfscomp_func (dest, ofs, len, ctx) + : grub_error (GRUB_ERR_BAD_FS, N_("module `%s' isn't loaded"), + "ntfscomp"); + } + + ctx->target_vcn = ofs >> (GRUB_NTFS_BLK_SHR + ctx->comp.log_spc); + while (ctx->next_vcn <= ctx->target_vcn) + { + if (grub_ntfs_read_run_list (ctx)) + return grub_errno; + } + + if (at->flags & GRUB_NTFS_AF_GPOS) + { + grub_disk_addr_t st0, st1; + grub_uint64_t m; + + m = (ofs >> GRUB_NTFS_BLK_SHR) & ((1 << ctx->comp.log_spc) - 1); + + st0 = + ((ctx->target_vcn - ctx->curr_vcn + ctx->curr_lcn) << ctx->comp.log_spc) + m; + st1 = st0 + 1; + if (st1 == + (ctx->next_vcn - ctx->curr_vcn + ctx->curr_lcn) << ctx->comp.log_spc) + { + if (grub_ntfs_read_run_list (ctx)) + return grub_errno; + st1 = ctx->curr_lcn << ctx->comp.log_spc; + } + grub_set_unaligned32 (dest, grub_cpu_to_le32 (st0)); + grub_set_unaligned32 (dest + 4, grub_cpu_to_le32 (st1)); + return 0; + } + + grub_fshelp_read_file (ctx->comp.disk, (grub_fshelp_node_t) ctx, + read_hook, read_hook_data, ofs, len, + (char *) dest, + grub_ntfs_read_block, ofs + len, + ctx->comp.log_spc, 0); + return grub_errno; +} + +static grub_err_t +read_attr (struct grub_ntfs_attr *at, grub_uint8_t *dest, grub_disk_addr_t ofs, + grub_size_t len, int cached, + grub_disk_read_hook_t read_hook, void *read_hook_data) +{ + grub_uint8_t *save_cur; + grub_uint8_t attr; + grub_uint8_t *pp; + grub_err_t ret; + + save_cur = at->attr_cur; + at->attr_nxt = at->attr_cur; + attr = *at->attr_nxt; + if (at->flags & GRUB_NTFS_AF_ALST) + { + grub_uint8_t *pa; + grub_disk_addr_t vcn; + + /* If compression is possible make sure that we include possible + compressed block size. */ + if (GRUB_NTFS_LOG_COM_SEC >= at->mft->data->log_spc) + vcn = ((ofs >> GRUB_NTFS_COM_LOG_LEN) + << (GRUB_NTFS_LOG_COM_SEC - at->mft->data->log_spc)) & ~0xFULL; + else + vcn = ofs >> (at->mft->data->log_spc + GRUB_NTFS_BLK_SHR); + pa = at->attr_nxt + u16at (at->attr_nxt, 4); + while (pa < at->attr_end) + { + if (*pa != attr) + break; + if (u32at (pa, 8) > vcn) + break; + at->attr_nxt = pa; + pa += u16at (pa, 4); + } + } + pp = find_attr (at, attr); + if (pp) + ret = read_data (at, pp, dest, ofs, len, cached, + read_hook, read_hook_data); + else + ret = + (grub_errno) ? grub_errno : grub_error (GRUB_ERR_BAD_FS, + "attribute not found"); + at->attr_cur = save_cur; + return ret; +} + +static grub_err_t +read_mft (struct grub_ntfs_data *data, grub_uint8_t *buf, grub_uint64_t mftno) +{ + if (read_attr + (&data->mmft.attr, buf, mftno * ((grub_disk_addr_t) data->mft_size << GRUB_NTFS_BLK_SHR), + data->mft_size << GRUB_NTFS_BLK_SHR, 0, 0, 0)) + return grub_error (GRUB_ERR_BAD_FS, "read MFT 0x%llx fails", (unsigned long long) mftno); + return fixup (buf, data->mft_size, (const grub_uint8_t *) "FILE"); +} + +static grub_err_t +init_file (struct grub_ntfs_file *mft, grub_uint64_t mftno) +{ + unsigned short flag; + + mft->inode_read = 1; + + mft->buf = grub_malloc (mft->data->mft_size << GRUB_NTFS_BLK_SHR); + if (mft->buf == NULL) + return grub_errno; + + if (read_mft (mft->data, mft->buf, mftno)) + return grub_errno; + + flag = u16at (mft->buf, 0x16); + if ((flag & 1) == 0) + return grub_error (GRUB_ERR_BAD_FS, "MFT 0x%llx is not in use", + (unsigned long long) mftno); + + if ((flag & 2) == 0) + { + grub_uint8_t *pa; + + pa = locate_attr (&mft->attr, mft, GRUB_NTFS_AT_DATA); + if (pa == NULL) + return grub_error (GRUB_ERR_BAD_FS, "no $DATA in MFT 0x%llx", + (unsigned long long) mftno); + + if (!pa[8]) + mft->size = u32at (pa, 0x10); + else + mft->size = u64at (pa, 0x30); + + if ((mft->attr.flags & GRUB_NTFS_AF_ALST) == 0) + mft->attr.attr_end = 0; /* Don't jump to attribute list */ + } + else + init_attr (&mft->attr, mft); + + return 0; +} + +static void +free_file (struct grub_ntfs_file *mft) +{ + free_attr (&mft->attr); + grub_free (mft->buf); +} + +static char * +get_utf8 (grub_uint8_t *in, grub_size_t len) +{ + grub_uint8_t *buf; + grub_uint16_t *tmp; + grub_size_t i; + + buf = grub_calloc (len, GRUB_MAX_UTF8_PER_UTF16 + 1); + tmp = grub_calloc (len, sizeof (tmp[0])); + if (!buf || !tmp) + { + grub_free (buf); + grub_free (tmp); + return NULL; + } + for (i = 0; i < len; i++) + tmp[i] = grub_le_to_cpu16 (grub_get_unaligned16 (in + 2 * i)); + *grub_utf16_to_utf8 (buf, tmp, len) = '\0'; + grub_free (tmp); + return (char *) buf; +} + +static int +list_file (struct grub_ntfs_file *diro, grub_uint8_t *pos, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + grub_uint8_t *np; + int ns; + + while (1) + { + grub_uint8_t namespace; + char *ustr; + + if (pos[0xC] & 2) /* end signature */ + break; + + np = pos + 0x50; + ns = *(np++); + namespace = *(np++); + + /* + * Ignore files in DOS namespace, as they will reappear as Win32 + * names. + */ + if ((ns) && (namespace != 2)) + { + enum grub_fshelp_filetype type; + struct grub_ntfs_file *fdiro; + grub_uint32_t attr; + + attr = u32at (pos, 0x48); + if (attr & GRUB_NTFS_ATTR_REPARSE) + type = GRUB_FSHELP_SYMLINK; + else if (attr & GRUB_NTFS_ATTR_DIRECTORY) + type = GRUB_FSHELP_DIR; + else + type = GRUB_FSHELP_REG; + + fdiro = grub_zalloc (sizeof (struct grub_ntfs_file)); + if (!fdiro) + return 0; + + fdiro->data = diro->data; + fdiro->ino = u64at (pos, 0) & 0xffffffffffffULL; + fdiro->mtime = u64at (pos, 0x20); + + ustr = get_utf8 (np, ns); + if (ustr == NULL) + { + grub_free (fdiro); + return 0; + } + if (namespace) + type |= GRUB_FSHELP_CASE_INSENSITIVE; + + if (hook (ustr, type, fdiro, hook_data)) + { + grub_free (ustr); + return 1; + } + + grub_free (ustr); + } + pos += u16at (pos, 8); + } + return 0; +} + +struct symlink_descriptor +{ + grub_uint32_t type; + grub_uint32_t total_len; + grub_uint16_t off1; + grub_uint16_t len1; + grub_uint16_t off2; + grub_uint16_t len2; +} GRUB_PACKED; + +static char * +grub_ntfs_read_symlink (grub_fshelp_node_t node) +{ + struct grub_ntfs_file *mft; + struct symlink_descriptor symdesc; + grub_err_t err; + grub_uint8_t *buf16; + char *buf, *end; + grub_size_t len; + grub_uint8_t *pa; + grub_size_t off; + + mft = (struct grub_ntfs_file *) node; + + mft->buf = grub_malloc (mft->data->mft_size << GRUB_NTFS_BLK_SHR); + if (mft->buf == NULL) + return NULL; + + if (read_mft (mft->data, mft->buf, mft->ino)) + return NULL; + + pa = locate_attr (&mft->attr, mft, GRUB_NTFS_AT_SYMLINK); + if (pa == NULL) + { + grub_error (GRUB_ERR_BAD_FS, "no $SYMLINK in MFT 0x%llx", + (unsigned long long) mft->ino); + return NULL; + } + + err = read_attr (&mft->attr, (grub_uint8_t *) &symdesc, 0, + sizeof (struct symlink_descriptor), 1, 0, 0); + if (err) + return NULL; + + switch (grub_cpu_to_le32 (symdesc.type)) + { + case 0xa000000c: + off = (sizeof (struct symlink_descriptor) + 4 + + grub_cpu_to_le32 (symdesc.off1)); + len = grub_cpu_to_le32 (symdesc.len1); + break; + case 0xa0000003: + off = (sizeof (struct symlink_descriptor) + + grub_cpu_to_le32 (symdesc.off1)); + len = grub_cpu_to_le32 (symdesc.len1); + break; + default: + grub_error (GRUB_ERR_BAD_FS, "symlink type invalid (%x)", + grub_cpu_to_le32 (symdesc.type)); + return NULL; + } + + buf16 = grub_malloc (len); + if (!buf16) + return NULL; + + err = read_attr (&mft->attr, buf16, off, len, 1, 0, 0); + if (err) + return NULL; + + buf = get_utf8 (buf16, len / 2); + if (!buf) + { + grub_free (buf16); + return NULL; + } + grub_free (buf16); + + for (end = buf; *end; end++) + if (*end == '\\') + *end = '/'; + + /* Split the sequence to avoid GCC thinking that this is a trigraph. */ + if (grub_memcmp (buf, "/?" "?/", 4) == 0 && buf[5] == ':' && buf[6] == '/' + && grub_isalpha (buf[4])) + { + grub_memmove (buf, buf + 6, end - buf + 1 - 6); + end -= 6; + } + return buf; +} + +static int +grub_ntfs_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + grub_uint8_t *bitmap; + struct grub_ntfs_attr attr, *at; + grub_uint8_t *cur_pos, *indx, *bmp; + int ret = 0; + grub_size_t bitmap_len; + struct grub_ntfs_file *mft; + + mft = (struct grub_ntfs_file *) dir; + + if (!mft->inode_read) + { + if (init_file (mft, mft->ino)) + return 0; + } + + indx = NULL; + bmp = NULL; + + at = &attr; + init_attr (at, mft); + while (1) + { + cur_pos = find_attr (at, GRUB_NTFS_AT_INDEX_ROOT); + if (cur_pos == NULL) + { + grub_error (GRUB_ERR_BAD_FS, "no $INDEX_ROOT"); + goto done; + } + + /* Resident, Namelen=4, Offset=0x18, Flags=0x00, Name="$I30" */ + if ((u32at (cur_pos, 8) != 0x180400) || + (u32at (cur_pos, 0x18) != 0x490024) || + (u32at (cur_pos, 0x1C) != 0x300033)) + continue; + cur_pos += u16at (cur_pos, 0x14); + if (*cur_pos != 0x30) /* Not filename index */ + continue; + break; + } + + cur_pos += 0x10; /* Skip index root */ + ret = list_file (mft, cur_pos + u16at (cur_pos, 0), hook, hook_data); + if (ret) + goto done; + + bitmap = NULL; + bitmap_len = 0; + free_attr (at); + init_attr (at, mft); + while ((cur_pos = find_attr (at, GRUB_NTFS_AT_BITMAP)) != NULL) + { + int ofs; + + ofs = cur_pos[0xA]; + /* Namelen=4, Name="$I30" */ + if ((cur_pos[9] == 4) && + (u32at (cur_pos, ofs) == 0x490024) && + (u32at (cur_pos, ofs + 4) == 0x300033)) + { + int is_resident = (cur_pos[8] == 0); + + bitmap_len = ((is_resident) ? u32at (cur_pos, 0x10) : + u32at (cur_pos, 0x28)); + + bmp = grub_malloc (bitmap_len); + if (bmp == NULL) + goto done; + + if (is_resident) + { + grub_memcpy (bmp, cur_pos + u16at (cur_pos, 0x14), + bitmap_len); + } + else + { + if (read_data (at, cur_pos, bmp, 0, bitmap_len, 0, 0, 0)) + { + grub_error (GRUB_ERR_BAD_FS, + "fails to read non-resident $BITMAP"); + goto done; + } + bitmap_len = u32at (cur_pos, 0x30); + } + + bitmap = bmp; + break; + } + } + + free_attr (at); + cur_pos = locate_attr (at, mft, GRUB_NTFS_AT_INDEX_ALLOCATION); + while (cur_pos != NULL) + { + /* Non-resident, Namelen=4, Offset=0x40, Flags=0, Name="$I30" */ + if ((u32at (cur_pos, 8) == 0x400401) && + (u32at (cur_pos, 0x40) == 0x490024) && + (u32at (cur_pos, 0x44) == 0x300033)) + break; + cur_pos = find_attr (at, GRUB_NTFS_AT_INDEX_ALLOCATION); + } + + if ((!cur_pos) && (bitmap)) + { + grub_error (GRUB_ERR_BAD_FS, "$BITMAP without $INDEX_ALLOCATION"); + goto done; + } + + if (bitmap) + { + grub_disk_addr_t i; + grub_uint8_t v; + + indx = grub_malloc (mft->data->idx_size << GRUB_NTFS_BLK_SHR); + if (indx == NULL) + goto done; + + v = 1; + for (i = 0; i < (grub_disk_addr_t)bitmap_len * 8; i++) + { + if (*bitmap & v) + { + if ((read_attr + (at, indx, i * (mft->data->idx_size << GRUB_NTFS_BLK_SHR), + (mft->data->idx_size << GRUB_NTFS_BLK_SHR), 0, 0, 0)) + || (fixup (indx, mft->data->idx_size, + (const grub_uint8_t *) "INDX"))) + goto done; + ret = list_file (mft, &indx[0x18 + u16at (indx, 0x18)], + hook, hook_data); + if (ret) + goto done; + } + v <<= 1; + if (!v) + { + v = 1; + bitmap++; + } + } + } + +done: + free_attr (at); + grub_free (indx); + grub_free (bmp); + + return ret; +} + +static struct grub_ntfs_data * +grub_ntfs_mount (grub_disk_t disk) +{ + struct grub_ntfs_bpb bpb; + struct grub_ntfs_data *data = 0; + grub_uint32_t spc; + + if (!disk) + goto fail; + + data = (struct grub_ntfs_data *) grub_zalloc (sizeof (*data)); + if (!data) + goto fail; + + data->disk = disk; + + /* Read the BPB. */ + if (grub_disk_read (disk, 0, 0, sizeof (bpb), &bpb)) + goto fail; + + if (grub_memcmp ((char *) &bpb.oem_name, "NTFS", 4) != 0 + || bpb.sectors_per_cluster == 0 + || (bpb.sectors_per_cluster & (bpb.sectors_per_cluster - 1)) != 0 + || bpb.bytes_per_sector == 0 + || (bpb.bytes_per_sector & (bpb.bytes_per_sector - 1)) != 0) + goto fail; + + spc = (((grub_uint32_t) bpb.sectors_per_cluster + * (grub_uint32_t) grub_le_to_cpu16 (bpb.bytes_per_sector)) + >> GRUB_NTFS_BLK_SHR); + if (spc == 0) + goto fail; + + for (data->log_spc = 0; (1U << data->log_spc) < spc; data->log_spc++); + + if (bpb.clusters_per_mft > 0) + data->mft_size = ((grub_disk_addr_t) bpb.clusters_per_mft) << data->log_spc; + else if (-bpb.clusters_per_mft < GRUB_NTFS_BLK_SHR || -bpb.clusters_per_mft >= 31) + goto fail; + else + data->mft_size = 1ULL << (-bpb.clusters_per_mft - GRUB_NTFS_BLK_SHR); + + if (bpb.clusters_per_index > 0) + data->idx_size = (((grub_disk_addr_t) bpb.clusters_per_index) + << data->log_spc); + else if (-bpb.clusters_per_index < GRUB_NTFS_BLK_SHR || -bpb.clusters_per_index >= 31) + goto fail; + else + data->idx_size = 1ULL << (-bpb.clusters_per_index - GRUB_NTFS_BLK_SHR); + + data->mft_start = grub_le_to_cpu64 (bpb.mft_lcn) << data->log_spc; + + if ((data->mft_size > GRUB_NTFS_MAX_MFT) || (data->idx_size > GRUB_NTFS_MAX_IDX)) + goto fail; + + data->mmft.data = data; + data->cmft.data = data; + + data->mmft.buf = grub_malloc (data->mft_size << GRUB_NTFS_BLK_SHR); + if (!data->mmft.buf) + goto fail; + + if (grub_disk_read + (disk, data->mft_start, 0, data->mft_size << GRUB_NTFS_BLK_SHR, data->mmft.buf)) + goto fail; + + data->uuid = grub_le_to_cpu64 (bpb.num_serial); + + if (fixup (data->mmft.buf, data->mft_size, (const grub_uint8_t *) "FILE")) + goto fail; + + if (!locate_attr (&data->mmft.attr, &data->mmft, GRUB_NTFS_AT_DATA)) + goto fail; + + if (init_file (&data->cmft, GRUB_NTFS_FILE_ROOT)) + goto fail; + + return data; + +fail: + grub_error (GRUB_ERR_BAD_FS, "not an ntfs filesystem"); + + if (data) + { + free_file (&data->mmft); + free_file (&data->cmft); + grub_free (data); + } + return 0; +} + +/* Context for grub_ntfs_dir. */ +struct grub_ntfs_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_ntfs_dir. */ +static int +grub_ntfs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_ntfs_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 = grub_divmod64 (node->mtime, 10000000, 0) + - 86400ULL * 365 * (1970 - 1601) + - 86400ULL * ((1970 - 1601) / 4) + 86400ULL * ((1970 - 1601) / 100); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_ntfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_ntfs_dir_ctx ctx = { hook, hook_data }; + struct grub_ntfs_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_ntfs_mount (device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file (path, &data->cmft, &fdiro, grub_ntfs_iterate_dir, + grub_ntfs_read_symlink, GRUB_FSHELP_DIR); + + if (grub_errno) + goto fail; + + grub_ntfs_iterate_dir (fdiro, grub_ntfs_dir_iter, &ctx); + +fail: + if ((fdiro) && (fdiro != &data->cmft)) + { + free_file (fdiro); + grub_free (fdiro); + } + if (data) + { + free_file (&data->mmft); + free_file (&data->cmft); + grub_free (data); + } + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_ntfs_open (grub_file_t file, const char *name) +{ + struct grub_ntfs_data *data = 0; + struct grub_fshelp_node *mft = 0; + + grub_dl_ref (my_mod); + + data = grub_ntfs_mount (file->device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file (name, &data->cmft, &mft, grub_ntfs_iterate_dir, + grub_ntfs_read_symlink, GRUB_FSHELP_REG); + + if (grub_errno) + goto fail; + + if (mft != &data->cmft) + { + free_file (&data->cmft); + grub_memcpy (&data->cmft, mft, sizeof (*mft)); + grub_free (mft); + if (!data->cmft.inode_read) + { + if (init_file (&data->cmft, data->cmft.ino)) + goto fail; + } + } + + file->size = data->cmft.size; + file->data = data; + file->offset = 0; + + return 0; + +fail: + if (data) + { + free_file (&data->mmft); + free_file (&data->cmft); + grub_free (data); + } + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_ssize_t +grub_ntfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_ntfs_file *mft; + + mft = &((struct grub_ntfs_data *) file->data)->cmft; + if (file->read_hook) + mft->attr.save_pos = 1; + + read_attr (&mft->attr, (grub_uint8_t *) buf, file->offset, len, 1, + file->read_hook, file->read_hook_data); + return (grub_errno) ? -1 : (grub_ssize_t) len; +} + +static grub_err_t +grub_ntfs_close (grub_file_t file) +{ + struct grub_ntfs_data *data; + + data = file->data; + + if (data) + { + free_file (&data->mmft); + free_file (&data->cmft); + grub_free (data); + } + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_ntfs_label (grub_device_t device, char **label) +{ + struct grub_ntfs_data *data = 0; + struct grub_fshelp_node *mft = 0; + grub_uint8_t *pa; + + grub_dl_ref (my_mod); + + *label = 0; + + data = grub_ntfs_mount (device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file ("/$Volume", &data->cmft, &mft, grub_ntfs_iterate_dir, + 0, GRUB_FSHELP_REG); + + if (grub_errno) + goto fail; + + if (!mft->inode_read) + { + mft->buf = grub_malloc (mft->data->mft_size << GRUB_NTFS_BLK_SHR); + if (mft->buf == NULL) + goto fail; + + if (read_mft (mft->data, mft->buf, mft->ino)) + goto fail; + } + + init_attr (&mft->attr, mft); + pa = find_attr (&mft->attr, GRUB_NTFS_AT_VOLUME_NAME); + if ((pa) && (pa[8] == 0) && (u32at (pa, 0x10))) + { + int len; + + len = u32at (pa, 0x10) / 2; + pa += u16at (pa, 0x14); + *label = get_utf8 (pa, len); + } + +fail: + if ((mft) && (mft != &data->cmft)) + { + free_file (mft); + grub_free (mft); + } + if (data) + { + free_file (&data->mmft); + free_file (&data->cmft); + grub_free (data); + } + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_ntfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_ntfs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_ntfs_mount (disk); + if (data) + { + char *ptr; + *uuid = grub_xasprintf ("%016llx", (unsigned long long) data->uuid); + if (*uuid) + for (ptr = *uuid; *ptr; ptr++) + *ptr = grub_toupper (*ptr); + free_file (&data->mmft); + free_file (&data->cmft); + grub_free (data); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static struct grub_fs grub_ntfs_fs = + { + .name = "ntfs", + .fs_dir = grub_ntfs_dir, + .fs_open = grub_ntfs_open, + .fs_read = grub_ntfs_read, + .fs_close = grub_ntfs_close, + .fs_label = grub_ntfs_label, + .fs_uuid = grub_ntfs_uuid, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 +}; + +GRUB_MOD_INIT (ntfs) +{ + grub_fs_register (&grub_ntfs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI (ntfs) +{ + grub_fs_unregister (&grub_ntfs_fs); +} diff --git a/grub-core/fs/ntfscomp.c b/grub-core/fs/ntfscomp.c new file mode 100644 index 0000000..3cd97d3 --- /dev/null +++ b/grub-core/fs/ntfscomp.c @@ -0,0 +1,443 @@ +/* ntfscomp.c - compression support for the NTFS filesystem */ +/* + * Copyright (C) 2007 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/disk.h> +#include <grub/dl.h> +#include <grub/ntfs.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_err_t +decomp_nextvcn (struct grub_ntfs_comp *cc) +{ + if (cc->comp_head >= cc->comp_tail) + return grub_error (GRUB_ERR_BAD_FS, "compression block overflown"); + if (grub_disk_read + (cc->disk, + (cc->comp_table[cc->comp_head].next_lcn - + (cc->comp_table[cc->comp_head].next_vcn - cc->cbuf_vcn)) << cc->log_spc, + 0, + 1 << (cc->log_spc + GRUB_NTFS_BLK_SHR), cc->cbuf)) + return grub_errno; + cc->cbuf_vcn++; + if ((cc->cbuf_vcn >= cc->comp_table[cc->comp_head].next_vcn)) + cc->comp_head++; + cc->cbuf_ofs = 0; + return 0; +} + +static grub_err_t +decomp_getch (struct grub_ntfs_comp *cc, grub_uint8_t *res) +{ + if (cc->cbuf_ofs >= (1U << (cc->log_spc + GRUB_NTFS_BLK_SHR))) + { + if (decomp_nextvcn (cc)) + return grub_errno; + } + *res = cc->cbuf[cc->cbuf_ofs++]; + return 0; +} + +static grub_err_t +decomp_get16 (struct grub_ntfs_comp *cc, grub_uint16_t * res) +{ + grub_uint8_t c1 = 0, c2 = 0; + + if ((decomp_getch (cc, &c1)) || (decomp_getch (cc, &c2))) + return grub_errno; + *res = ((grub_uint16_t) c2) * 256 + ((grub_uint16_t) c1); + return 0; +} + +/* Decompress a block (4096 bytes) */ +static grub_err_t +decomp_block (struct grub_ntfs_comp *cc, grub_uint8_t *dest) +{ + grub_uint16_t flg, cnt; + + if (decomp_get16 (cc, &flg)) + return grub_errno; + cnt = (flg & 0xFFF) + 1; + + if (dest) + { + if (flg & 0x8000) + { + grub_uint8_t tag; + grub_uint32_t bits, copied; + + bits = copied = tag = 0; + while (cnt > 0) + { + if (copied > GRUB_NTFS_COM_LEN) + return grub_error (GRUB_ERR_BAD_FS, + "compression block too large"); + + if (!bits) + { + if (decomp_getch (cc, &tag)) + return grub_errno; + + bits = 8; + cnt--; + if (cnt <= 0) + break; + } + if (tag & 1) + { + grub_uint32_t i, len, delta, code, lmask, dshift; + grub_uint16_t word = 0; + + if (decomp_get16 (cc, &word)) + return grub_errno; + + code = word; + cnt -= 2; + + if (!copied) + { + grub_error (GRUB_ERR_BAD_FS, "nontext window empty"); + return 0; + } + + for (i = copied - 1, lmask = 0xFFF, dshift = 12; i >= 0x10; + i >>= 1) + { + lmask >>= 1; + dshift--; + } + + delta = code >> dshift; + len = (code & lmask) + 3; + + for (i = 0; i < len; i++) + { + dest[copied] = dest[copied - delta - 1]; + copied++; + } + } + else + { + grub_uint8_t ch = 0; + + if (decomp_getch (cc, &ch)) + return grub_errno; + dest[copied++] = ch; + cnt--; + } + tag >>= 1; + bits--; + } + return 0; + } + else + { + if (cnt != GRUB_NTFS_COM_LEN) + return grub_error (GRUB_ERR_BAD_FS, + "invalid compression block size"); + } + } + + while (cnt > 0) + { + int n; + + n = (1 << (cc->log_spc + GRUB_NTFS_BLK_SHR)) - cc->cbuf_ofs; + if (n > cnt) + n = cnt; + if ((dest) && (n)) + { + grub_memcpy (dest, &cc->cbuf[cc->cbuf_ofs], n); + dest += n; + } + cnt -= n; + cc->cbuf_ofs += n; + if ((cnt) && (decomp_nextvcn (cc))) + return grub_errno; + } + return 0; +} + +static grub_err_t +read_block (struct grub_ntfs_rlst *ctx, grub_uint8_t *buf, grub_size_t num) +{ + int log_cpb = GRUB_NTFS_LOG_COM_SEC - ctx->comp.log_spc; + + while (num) + { + grub_size_t nn; + + if ((ctx->target_vcn & 0xF) == 0) + { + + if (ctx->comp.comp_head != ctx->comp.comp_tail + && !(ctx->flags & GRUB_NTFS_RF_BLNK)) + return grub_error (GRUB_ERR_BAD_FS, "invalid compression block"); + ctx->comp.comp_head = ctx->comp.comp_tail = 0; + ctx->comp.cbuf_vcn = ctx->target_vcn; + ctx->comp.cbuf_ofs = (1 << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR)); + if (ctx->target_vcn >= ctx->next_vcn) + { + if (grub_ntfs_read_run_list (ctx)) + return grub_errno; + } + while (ctx->target_vcn + 16 > ctx->next_vcn) + { + if (ctx->flags & GRUB_NTFS_RF_BLNK) + break; + ctx->comp.comp_table[ctx->comp.comp_tail].next_vcn = ctx->next_vcn; + ctx->comp.comp_table[ctx->comp.comp_tail].next_lcn = + ctx->curr_lcn + ctx->next_vcn - ctx->curr_vcn; + ctx->comp.comp_tail++; + if (grub_ntfs_read_run_list (ctx)) + return grub_errno; + } + } + + nn = (16 - (unsigned) (ctx->target_vcn & 0xF)) >> log_cpb; + if (nn > num) + nn = num; + num -= nn; + + if (ctx->flags & GRUB_NTFS_RF_BLNK) + { + ctx->target_vcn += nn << log_cpb; + if (ctx->comp.comp_tail == 0) + { + if (buf) + { + grub_memset (buf, 0, nn * GRUB_NTFS_COM_LEN); + buf += nn * GRUB_NTFS_COM_LEN; + if (grub_file_progress_hook && ctx->file) + grub_file_progress_hook (0, 0, nn * GRUB_NTFS_COM_LEN, + ctx->file); + } + } + else + { + while (nn) + { + if (decomp_block (&ctx->comp, buf)) + return grub_errno; + if (buf) + buf += GRUB_NTFS_COM_LEN; + if (grub_file_progress_hook && ctx->file) + grub_file_progress_hook (0, 0, GRUB_NTFS_COM_LEN, + ctx->file); + nn--; + } + } + } + else + { + nn <<= log_cpb; + while ((ctx->comp.comp_head < ctx->comp.comp_tail) && (nn)) + { + grub_disk_addr_t tt; + + tt = + ctx->comp.comp_table[ctx->comp.comp_head].next_vcn - + ctx->target_vcn; + if (tt > nn) + tt = nn; + ctx->target_vcn += tt; + if (buf) + { + if (grub_disk_read + (ctx->comp.disk, + (ctx->comp.comp_table[ctx->comp.comp_head].next_lcn - + (ctx->comp.comp_table[ctx->comp.comp_head].next_vcn - + ctx->target_vcn)) << ctx->comp.log_spc, 0, + tt << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR), buf)) + return grub_errno; + if (grub_file_progress_hook && ctx->file) + grub_file_progress_hook (0, 0, + tt << (ctx->comp.log_spc + + GRUB_NTFS_BLK_SHR), + ctx->file); + buf += tt << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR); + } + nn -= tt; + if (ctx->target_vcn >= + ctx->comp.comp_table[ctx->comp.comp_head].next_vcn) + ctx->comp.comp_head++; + } + if (nn) + { + if (buf) + { + if (grub_disk_read + (ctx->comp.disk, + (ctx->target_vcn - ctx->curr_vcn + + ctx->curr_lcn) << ctx->comp.log_spc, 0, + nn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR), buf)) + return grub_errno; + buf += nn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR); + if (grub_file_progress_hook && ctx->file) + grub_file_progress_hook (0, 0, + nn << (ctx->comp.log_spc + + GRUB_NTFS_BLK_SHR), + ctx->file); + } + ctx->target_vcn += nn; + } + } + } + return 0; +} + +static grub_err_t +ntfscomp (grub_uint8_t *dest, grub_disk_addr_t ofs, + grub_size_t len, struct grub_ntfs_rlst *ctx) +{ + grub_err_t ret; + grub_disk_addr_t vcn; + + if (ctx->attr->sbuf) + { + if ((ofs & (~(GRUB_NTFS_COM_LEN - 1))) == ctx->attr->save_pos) + { + grub_disk_addr_t n; + + n = GRUB_NTFS_COM_LEN - (ofs - ctx->attr->save_pos); + if (n > len) + n = len; + + grub_memcpy (dest, ctx->attr->sbuf + ofs - ctx->attr->save_pos, n); + if (grub_file_progress_hook && ctx->file) + grub_file_progress_hook (0, 0, n, ctx->file); + if (n == len) + return 0; + + dest += n; + len -= n; + ofs += n; + } + } + else + { + ctx->attr->sbuf = grub_malloc (GRUB_NTFS_COM_LEN); + if (ctx->attr->sbuf == NULL) + return grub_errno; + ctx->attr->save_pos = 1; + } + + vcn = ctx->target_vcn = (ofs >> GRUB_NTFS_COM_LOG_LEN) * (GRUB_NTFS_COM_SEC >> ctx->comp.log_spc); + ctx->target_vcn &= ~0xFULL; + while (ctx->next_vcn <= ctx->target_vcn) + { + if (grub_ntfs_read_run_list (ctx)) + return grub_errno; + } + + ctx->comp.comp_head = ctx->comp.comp_tail = 0; + ctx->comp.cbuf = grub_malloc (1 << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR)); + if (!ctx->comp.cbuf) + return 0; + + ret = 0; + + //ctx->comp.disk->read_hook = read_hook; + //ctx->comp.disk->read_hook_data = read_hook_data; + + if ((vcn > ctx->target_vcn) && + (read_block + (ctx, NULL, (vcn - ctx->target_vcn) >> (GRUB_NTFS_LOG_COM_SEC - ctx->comp.log_spc)))) + { + ret = grub_errno; + goto quit; + } + + if (ofs % GRUB_NTFS_COM_LEN) + { + grub_uint32_t t, n, o; + void *file = ctx->file; + + ctx->file = 0; + + t = ctx->target_vcn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR); + if (read_block (ctx, ctx->attr->sbuf, 1)) + { + ret = grub_errno; + goto quit; + } + + ctx->file = file; + + ctx->attr->save_pos = t; + + o = ofs % GRUB_NTFS_COM_LEN; + n = GRUB_NTFS_COM_LEN - o; + if (n > len) + n = len; + grub_memcpy (dest, &ctx->attr->sbuf[o], n); + if (grub_file_progress_hook && ctx->file) + grub_file_progress_hook (0, 0, n, ctx->file); + if (n == len) + goto quit; + dest += n; + len -= n; + } + + if (read_block (ctx, dest, len / GRUB_NTFS_COM_LEN)) + { + ret = grub_errno; + goto quit; + } + + dest += (len / GRUB_NTFS_COM_LEN) * GRUB_NTFS_COM_LEN; + len = len % GRUB_NTFS_COM_LEN; + if (len) + { + grub_uint32_t t; + void *file = ctx->file; + + ctx->file = 0; + t = ctx->target_vcn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR); + if (read_block (ctx, ctx->attr->sbuf, 1)) + { + ret = grub_errno; + goto quit; + } + + ctx->attr->save_pos = t; + + grub_memcpy (dest, ctx->attr->sbuf, len); + if (grub_file_progress_hook && file) + grub_file_progress_hook (0, 0, len, file); + } + +quit: + //ctx->comp.disk->read_hook = 0; + if (ctx->comp.cbuf) + grub_free (ctx->comp.cbuf); + return ret; +} + +GRUB_MOD_INIT (ntfscomp) +{ + grub_ntfscomp_func = ntfscomp; +} + +GRUB_MOD_FINI (ntfscomp) +{ + grub_ntfscomp_func = NULL; +} diff --git a/grub-core/fs/odc.c b/grub-core/fs/odc.c new file mode 100644 index 0000000..7900006 --- /dev/null +++ b/grub-core/fs/odc.c @@ -0,0 +1,61 @@ +/* cpio.c - cpio and tar filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> + +#define ALIGN_CPIO(x) x + +#define MAGIC "070707" +struct head +{ + char magic[6]; + char dev[6]; + char ino[6]; + char mode[6]; + char uid[6]; + char gid[6]; + char nlink[6]; + char rdev[6]; + char mtime[11]; + char namesize[6]; + char filesize[11]; +} GRUB_PACKED; + +static inline unsigned long long +read_number (const char *str, grub_size_t size) +{ + unsigned long long ret = 0; + while (size-- && *str >= '0' && *str <= '7') + ret = (ret << 3) | (*str++ & 0xf); + return ret; +} + +#define FSNAME "odc" + +#include "cpio_common.c" + +GRUB_MOD_INIT (odc) +{ + grub_fs_register (&grub_cpio_fs); +} + +GRUB_MOD_FINI (odc) +{ + grub_fs_unregister (&grub_cpio_fs); +} diff --git a/grub-core/fs/proc.c b/grub-core/fs/proc.c new file mode 100644 index 0000000..5f51650 --- /dev/null +++ b/grub-core/fs/proc.c @@ -0,0 +1,203 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2013 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/procfs.h> +#include <grub/disk.h> +#include <grub/fs.h> +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/dl.h> +#include <grub/archelp.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +struct grub_procfs_entry *grub_procfs_entries; + +static int +grub_procdev_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, + grub_disk_pull_t pull) +{ + if (pull != GRUB_DISK_PULL_NONE) + return 0; + + return hook ("proc", hook_data); +} + +static grub_err_t +grub_procdev_open (const char *name, grub_disk_t disk) +{ + if (grub_strcmp (name, "proc")) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a procfs disk"); + + disk->total_sectors = 0; + disk->id = 0; + + disk->data = 0; + + return GRUB_ERR_NONE; +} + +static void +grub_procdev_close (grub_disk_t disk __attribute((unused))) +{ +} + +static grub_err_t +grub_procdev_read (grub_disk_t disk __attribute((unused)), + grub_disk_addr_t sector __attribute((unused)), + grub_size_t size __attribute((unused)), + char *buf __attribute((unused))) +{ + return GRUB_ERR_OUT_OF_RANGE; +} + +static grub_err_t +grub_procdev_write (grub_disk_t disk __attribute ((unused)), + grub_disk_addr_t sector __attribute ((unused)), + grub_size_t size __attribute ((unused)), + const char *buf __attribute ((unused))) +{ + return GRUB_ERR_OUT_OF_RANGE; +} + +struct grub_archelp_data +{ + struct grub_procfs_entry *entry, *next_entry; +}; + +static void +grub_procfs_rewind (struct grub_archelp_data *data) +{ + data->entry = NULL; + data->next_entry = grub_procfs_entries; +} + +static grub_err_t +grub_procfs_find_file (struct grub_archelp_data *data, char **name, + grub_int32_t *mtime, + grub_uint32_t *mode) +{ + data->entry = data->next_entry; + if (!data->entry) + { + *mode = GRUB_ARCHELP_ATTR_END; + return GRUB_ERR_NONE; + } + data->next_entry = data->entry->next; + *mode = GRUB_ARCHELP_ATTR_FILE | GRUB_ARCHELP_ATTR_NOTIME; + *name = grub_strdup (data->entry->name); + *mtime = 0; + if (!*name) + return grub_errno; + return GRUB_ERR_NONE; +} + +static struct grub_archelp_ops arcops = + { + .find_file = grub_procfs_find_file, + .rewind = grub_procfs_rewind + }; + +static grub_ssize_t +grub_procfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + char *data = file->data; + + grub_memcpy (buf, data + file->offset, len); + + return len; +} + +static grub_err_t +grub_procfs_close (grub_file_t file) +{ + char *data; + + data = file->data; + grub_free (data); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_procfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_archelp_data data; + + /* Check if the disk is our dummy disk. */ + if (grub_strcmp (device->disk->name, "proc")) + return grub_error (GRUB_ERR_BAD_FS, "not a procfs"); + + grub_procfs_rewind (&data); + + return grub_archelp_dir (&data, &arcops, + path, hook, hook_data); +} + +static grub_err_t +grub_procfs_open (struct grub_file *file, const char *path) +{ + grub_err_t err; + struct grub_archelp_data data; + grub_size_t sz; + + grub_procfs_rewind (&data); + + err = grub_archelp_open (&data, &arcops, path); + if (err) + return err; + file->data = data.entry->get_contents (&sz); + if (!file->data) + return grub_errno; + file->size = sz; + return GRUB_ERR_NONE; +} + +static struct grub_disk_dev grub_procfs_dev = { + .name = "proc", + .id = GRUB_DISK_DEVICE_PROCFS_ID, + .disk_iterate = grub_procdev_iterate, + .disk_open = grub_procdev_open, + .disk_close = grub_procdev_close, + .disk_read = grub_procdev_read, + .disk_write = grub_procdev_write, + .next = 0 +}; + +static struct grub_fs grub_procfs_fs = + { + .name = "procfs", + .fs_dir = grub_procfs_dir, + .fs_open = grub_procfs_open, + .fs_read = grub_procfs_read, + .fs_close = grub_procfs_close, + .next = 0 + }; + +GRUB_MOD_INIT (procfs) +{ + grub_disk_dev_register (&grub_procfs_dev); + grub_fs_register (&grub_procfs_fs); +} + +GRUB_MOD_FINI (procfs) +{ + grub_disk_dev_unregister (&grub_procfs_dev); + grub_fs_unregister (&grub_procfs_fs); +} diff --git a/grub-core/fs/reiserfs.c b/grub-core/fs/reiserfs.c new file mode 100644 index 0000000..af6a226 --- /dev/null +++ b/grub-core/fs/reiserfs.c @@ -0,0 +1,1427 @@ +/* reiserfs.c - ReiserFS versions up to 3.6 */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2008 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/>. + */ + +/* + TODO: + implement journal handling (ram replay) + test tail packing & direct files + validate partition label position +*/ + +#if 0 +# define GRUB_REISERFS_DEBUG +# define GRUB_REISERFS_JOURNALING +# define GRUB_HEXDUMP +#endif + +#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/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define MIN(a, b) \ + ({ typeof (a) _a = (a); \ + typeof (b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define MAX(a, b) \ + ({ typeof (a) _a = (a); \ + typeof (b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define REISERFS_SUPER_BLOCK_OFFSET 0x10000 +#define REISERFS_MAGIC_LEN 12 +#define REISERFS_MAGIC_STRING "ReIsEr" +#define REISERFS_MAGIC_DESC_BLOCK "ReIsErLB" +/* If the 3rd bit of an item state is set, then it's visible. */ +#define GRUB_REISERFS_VISIBLE_MASK ((grub_uint16_t) 0x04) + +#define S_IFLNK 0xA000 + +static grub_dl_t my_mod; + +#define assert(boolean) real_assert (boolean, GRUB_FILE, __LINE__) +static inline void +real_assert (int boolean, const char *file, const int line) +{ + if (! boolean) + grub_printf ("Assertion failed at %s:%d\n", file, line); +} + +enum grub_reiserfs_item_type + { + GRUB_REISERFS_STAT, + GRUB_REISERFS_DIRECTORY, + GRUB_REISERFS_DIRECT, + GRUB_REISERFS_INDIRECT, + /* Matches both _DIRECT and _INDIRECT when searching. */ + GRUB_REISERFS_ANY, + GRUB_REISERFS_UNKNOWN + }; + +struct grub_reiserfs_superblock +{ + grub_uint32_t block_count; + grub_uint32_t block_free_count; + grub_uint32_t root_block; + grub_uint32_t journal_block; + grub_uint32_t journal_device; + grub_uint32_t journal_original_size; + grub_uint32_t journal_max_transaction_size; + grub_uint32_t journal_block_count; + grub_uint32_t journal_max_batch; + grub_uint32_t journal_max_commit_age; + grub_uint32_t journal_max_transaction_age; + grub_uint16_t block_size; + grub_uint16_t oid_max_size; + grub_uint16_t oid_current_size; + grub_uint16_t state; + grub_uint8_t magic_string[REISERFS_MAGIC_LEN]; + grub_uint32_t function_hash_code; + grub_uint16_t tree_height; + grub_uint16_t bitmap_number; + grub_uint16_t version; + grub_uint16_t reserved; + grub_uint32_t inode_generation; + grub_uint8_t unused[4]; + grub_uint16_t uuid[8]; + char label[16]; +} GRUB_PACKED; + +struct grub_reiserfs_journal_header +{ + grub_uint32_t last_flush_uid; + grub_uint32_t unflushed_offset; + grub_uint32_t mount_id; +} GRUB_PACKED; + +struct grub_reiserfs_description_block +{ + grub_uint32_t id; + grub_uint32_t len; + grub_uint32_t mount_id; + grub_uint32_t real_blocks[0]; +} GRUB_PACKED; + +struct grub_reiserfs_commit_block +{ + grub_uint32_t id; + grub_uint32_t len; + grub_uint32_t real_blocks[0]; +} GRUB_PACKED; + +struct grub_reiserfs_stat_item_v1 +{ + grub_uint16_t mode; + grub_uint16_t hardlink_count; + grub_uint16_t uid; + grub_uint16_t gid; + grub_uint32_t size; + grub_uint32_t atime; + grub_uint32_t mtime; + grub_uint32_t ctime; + grub_uint32_t rdev; + grub_uint32_t first_direct_byte; +} GRUB_PACKED; + +struct grub_reiserfs_stat_item_v2 +{ + grub_uint16_t mode; + grub_uint16_t reserved; + grub_uint32_t hardlink_count; + grub_uint64_t size; + grub_uint32_t uid; + grub_uint32_t gid; + grub_uint32_t atime; + grub_uint32_t mtime; + grub_uint32_t ctime; + grub_uint32_t blocks; + grub_uint32_t first_direct_byte; +} GRUB_PACKED; + +struct grub_reiserfs_key +{ + grub_uint32_t directory_id; + grub_uint32_t object_id; + union + { + struct + { + grub_uint32_t offset; + grub_uint32_t type; + } GRUB_PACKED v1; + struct + { + grub_uint64_t offset_type; + } GRUB_PACKED v2; + } u; +} GRUB_PACKED; + +struct grub_reiserfs_item_header +{ + struct grub_reiserfs_key key; + union + { + grub_uint16_t free_space; + grub_uint16_t entry_count; + } GRUB_PACKED u; + grub_uint16_t item_size; + grub_uint16_t item_location; + grub_uint16_t version; +} GRUB_PACKED; + +struct grub_reiserfs_block_header +{ + grub_uint16_t level; + grub_uint16_t item_count; + grub_uint16_t free_space; + grub_uint16_t reserved; + struct grub_reiserfs_key block_right_delimiting_key; +} GRUB_PACKED; + +struct grub_reiserfs_disk_child +{ + grub_uint32_t block_number; + grub_uint16_t size; + grub_uint16_t reserved; +} GRUB_PACKED; + +struct grub_reiserfs_directory_header +{ + grub_uint32_t offset; + grub_uint32_t directory_id; + grub_uint32_t object_id; + grub_uint16_t location; + grub_uint16_t state; +} GRUB_PACKED; + +struct grub_fshelp_node +{ + struct grub_reiserfs_data *data; + grub_uint32_t block_number; /* 0 if node is not found. */ + grub_uint16_t block_position; + grub_uint64_t next_offset; + grub_int32_t mtime; + grub_off_t size; + enum grub_reiserfs_item_type type; /* To know how to read the header. */ + struct grub_reiserfs_item_header header; +}; + +/* Returned when opening a file. */ +struct grub_reiserfs_data +{ + struct grub_reiserfs_superblock superblock; + grub_disk_t disk; +}; + +static grub_ssize_t +grub_reiserfs_read_real (struct grub_fshelp_node *node, + grub_off_t off, char *buf, grub_size_t len, + grub_disk_read_hook_t read_hook, + void *read_hook_data); + +/* Internal-only functions. Not to be used outside of this file. */ + +/* Return the type of given v2 key. */ +static enum grub_reiserfs_item_type +grub_reiserfs_get_key_v2_type (const struct grub_reiserfs_key *key) +{ + switch (grub_le_to_cpu64 (key->u.v2.offset_type) >> 60) + { + case 0: + return GRUB_REISERFS_STAT; + case 15: + return GRUB_REISERFS_ANY; + case 3: + return GRUB_REISERFS_DIRECTORY; + case 2: + return GRUB_REISERFS_DIRECT; + case 1: + return GRUB_REISERFS_INDIRECT; + } + return GRUB_REISERFS_UNKNOWN; +} + +/* Return the type of given v1 key. */ +static enum grub_reiserfs_item_type +grub_reiserfs_get_key_v1_type (const struct grub_reiserfs_key *key) +{ + switch (grub_le_to_cpu32 (key->u.v1.type)) + { + case 0: + return GRUB_REISERFS_STAT; + case 555: + return GRUB_REISERFS_ANY; + case 500: + return GRUB_REISERFS_DIRECTORY; + case 0x20000000: + case 0xFFFFFFFF: + return GRUB_REISERFS_DIRECT; + case 0x10000000: + case 0xFFFFFFFE: + return GRUB_REISERFS_INDIRECT; + } + return GRUB_REISERFS_UNKNOWN; +} + +/* Return 1 if the given key is version 1 key, 2 otherwise. */ +static int +grub_reiserfs_get_key_version (const struct grub_reiserfs_key *key) +{ + return grub_reiserfs_get_key_v1_type (key) == GRUB_REISERFS_UNKNOWN ? 2 : 1; +} + +#ifdef GRUB_HEXDUMP +static void +grub_hexdump (char *buffer, grub_size_t len) +{ + grub_size_t a; + for (a = 0; a < len; a++) + { + if (! (a & 0x0F)) + grub_printf ("\n%08x ", a); + grub_printf ("%02x ", + ((unsigned int) ((unsigned char *) buffer)[a]) & 0xFF); + } + grub_printf ("\n"); +} +#endif + +#ifdef GRUB_REISERFS_DEBUG +static grub_uint64_t +grub_reiserfs_get_key_offset (const struct grub_reiserfs_key *key); + +static enum grub_reiserfs_item_type +grub_reiserfs_get_key_type (const struct grub_reiserfs_key *key); + +static void +grub_reiserfs_print_key (const struct grub_reiserfs_key *key) +{ + unsigned int a; + char *reiserfs_type_strings[] = { + "stat ", + "directory", + "direct ", + "indirect ", + "any ", + "unknown " + }; + + for (a = 0; a < sizeof (struct grub_reiserfs_key); a++) + grub_printf ("%02x ", ((unsigned int) ((unsigned char *) key)[a]) & 0xFF); + grub_printf ("parent id = 0x%08x, self id = 0x%08x, type = %s, offset = ", + grub_le_to_cpu32 (key->directory_id), + grub_le_to_cpu32 (key->object_id), + reiserfs_type_strings [grub_reiserfs_get_key_type (key)]); + if (grub_reiserfs_get_key_version (key) == 1) + grub_printf("%08x", (unsigned int) grub_reiserfs_get_key_offset (key)); + else + grub_printf("0x%07x%08x", + (unsigned) (grub_reiserfs_get_key_offset (key) >> 32), + (unsigned) (grub_reiserfs_get_key_offset (key) & 0xFFFFFFFF)); + grub_printf ("\n"); +} +#endif + +/* Return the offset of given key. */ +static grub_uint64_t +grub_reiserfs_get_key_offset (const struct grub_reiserfs_key *key) +{ + if (grub_reiserfs_get_key_version (key) == 1) + return grub_le_to_cpu32 (key->u.v1.offset); + else + return grub_le_to_cpu64 (key->u.v2.offset_type) & (~0ULL >> 4); +} + +/* Set the offset of given key. */ +static void +grub_reiserfs_set_key_offset (struct grub_reiserfs_key *key, + grub_uint64_t value) +{ + if (grub_reiserfs_get_key_version (key) == 1) + key->u.v1.offset = grub_cpu_to_le32 (value); + else + key->u.v2.offset_type \ + = ((key->u.v2.offset_type & grub_cpu_to_le64_compile_time (15ULL << 60)) + | grub_cpu_to_le64 (value & (~0ULL >> 4))); +} + +/* Return the type of given key. */ +static enum grub_reiserfs_item_type +grub_reiserfs_get_key_type (const struct grub_reiserfs_key *key) +{ + if (grub_reiserfs_get_key_version (key) == 1) + return grub_reiserfs_get_key_v1_type (key); + else + return grub_reiserfs_get_key_v2_type (key); +} + +/* Set the type of given key, with given version number. */ +static void +grub_reiserfs_set_key_type (struct grub_reiserfs_key *key, + enum grub_reiserfs_item_type grub_type, + int version) +{ + grub_uint32_t type; + + switch (grub_type) + { + case GRUB_REISERFS_STAT: + type = 0; + break; + case GRUB_REISERFS_ANY: + type = (version == 1) ? 555 : 15; + break; + case GRUB_REISERFS_DIRECTORY: + type = (version == 1) ? 500 : 3; + break; + case GRUB_REISERFS_DIRECT: + type = (version == 1) ? 0xFFFFFFFF : 2; + break; + case GRUB_REISERFS_INDIRECT: + type = (version == 1) ? 0xFFFFFFFE : 1; + break; + default: + return; + } + + if (version == 1) + key->u.v1.type = grub_cpu_to_le32 (type); + else + key->u.v2.offset_type + = ((key->u.v2.offset_type & grub_cpu_to_le64_compile_time (~0ULL >> 4)) + | grub_cpu_to_le64 ((grub_uint64_t) type << 60)); + + assert (grub_reiserfs_get_key_type (key) == grub_type); +} + +/* -1 if key 1 if lower than key 2. + 0 if key 1 is equal to key 2. + 1 if key 1 is higher than key 2. */ +static int +grub_reiserfs_compare_keys (const struct grub_reiserfs_key *key1, + const struct grub_reiserfs_key *key2) +{ + grub_uint64_t offset1, offset2; + enum grub_reiserfs_item_type type1, type2; + grub_uint32_t id1, id2; + + if (! key1 || ! key2) + return -2; + + id1 = grub_le_to_cpu32 (key1->directory_id); + id2 = grub_le_to_cpu32 (key2->directory_id); + if (id1 < id2) + return -1; + if (id1 > id2) + return 1; + + id1 = grub_le_to_cpu32 (key1->object_id); + id2 = grub_le_to_cpu32 (key2->object_id); + if (id1 < id2) + return -1; + if (id1 > id2) + return 1; + + offset1 = grub_reiserfs_get_key_offset (key1); + offset2 = grub_reiserfs_get_key_offset (key2); + if (offset1 < offset2) + return -1; + if (offset1 > offset2) + return 1; + + type1 = grub_reiserfs_get_key_type (key1); + type2 = grub_reiserfs_get_key_type (key2); + if ((type1 == GRUB_REISERFS_ANY + && (type2 == GRUB_REISERFS_DIRECT + || type2 == GRUB_REISERFS_INDIRECT)) + || (type2 == GRUB_REISERFS_ANY + && (type1 == GRUB_REISERFS_DIRECT + || type1 == GRUB_REISERFS_INDIRECT))) + return 0; + if (type1 < type2) + return -1; + if (type1 > type2) + return 1; + + return 0; +} + +/* Find the item identified by KEY in mounted filesystem DATA, and fill ITEM + accordingly to what was found. */ +static grub_err_t +grub_reiserfs_get_item (struct grub_reiserfs_data *data, + const struct grub_reiserfs_key *key, + struct grub_fshelp_node *item, int exact) +{ + grub_uint32_t block_number; + struct grub_reiserfs_block_header *block_header = 0; + struct grub_reiserfs_key *block_key = 0; + grub_uint16_t block_size, item_count, current_level; + grub_uint16_t i; + grub_uint16_t previous_level = ~0; + struct grub_reiserfs_item_header *item_headers = 0; + +#if 0 + if (! data) + { + grub_error (GRUB_ERR_BAD_FS, "data is NULL"); + goto fail; + } + + if (! key) + { + grub_error (GRUB_ERR_BAD_FS, "key is NULL"); + goto fail; + } + + if (! item) + { + grub_error (GRUB_ERR_BAD_FS, "item is NULL"); + goto fail; + } +#endif + + block_size = grub_le_to_cpu16 (data->superblock.block_size); + block_number = grub_le_to_cpu32 (data->superblock.root_block); +#ifdef GRUB_REISERFS_DEBUG + grub_printf("Searching for "); + grub_reiserfs_print_key (key); +#endif + block_header = grub_malloc (block_size); + if (! block_header) + goto fail; + + item->next_offset = 0; + do + { + grub_disk_read (data->disk, + block_number * (block_size >> GRUB_DISK_SECTOR_BITS), + (((grub_off_t) block_number * block_size) + & (GRUB_DISK_SECTOR_SIZE - 1)), + block_size, block_header); + if (grub_errno) + goto fail; + current_level = grub_le_to_cpu16 (block_header->level); + grub_dprintf ("reiserfs_tree", " at level %d\n", current_level); + if (current_level >= previous_level) + { + grub_dprintf ("reiserfs_tree", "level loop detected, aborting\n"); + grub_error (GRUB_ERR_BAD_FS, "level loop"); + goto fail; + } + previous_level = current_level; + item_count = grub_le_to_cpu16 (block_header->item_count); + grub_dprintf ("reiserfs_tree", " number of contained items : %d\n", + item_count); + if (current_level > 1) + { + /* Internal node. Navigate to the child that should contain + the searched key. */ + struct grub_reiserfs_key *keys + = (struct grub_reiserfs_key *) (block_header + 1); + struct grub_reiserfs_disk_child *children + = ((struct grub_reiserfs_disk_child *) + (keys + item_count)); + + for (i = 0; + i < item_count + && grub_reiserfs_compare_keys (key, &(keys[i])) >= 0; + i++) + { +#ifdef GRUB_REISERFS_DEBUG + grub_printf("i %03d/%03d ", i + 1, item_count + 1); + grub_reiserfs_print_key (&(keys[i])); +#endif + } + block_number = grub_le_to_cpu32 (children[i].block_number); + if ((i < item_count) && (key->directory_id == keys[i].directory_id) + && (key->object_id == keys[i].object_id)) + item->next_offset = grub_reiserfs_get_key_offset(&(keys[i])); +#ifdef GRUB_REISERFS_DEBUG + if (i == item_count + || grub_reiserfs_compare_keys (key, &(keys[i])) == 0) + grub_printf(">"); + else + grub_printf("<"); + if (i < item_count) + { + grub_printf (" %03d/%03d ", i + 1, item_count + 1); + grub_reiserfs_print_key (&(keys[i])); + if (i + 1 < item_count) + { + grub_printf ("+ %03d/%03d ", i + 2, item_count); + grub_reiserfs_print_key (&(keys[i + 1])); + } + } + else + grub_printf ("Accessing rightmost child at block %d.\n", + block_number); +#endif + } + else + { + /* Leaf node. Check that the key is actually present. */ + item_headers + = (struct grub_reiserfs_item_header *) (block_header + 1); + for (i = 0; + i < item_count; + i++) + { + int val; + val = grub_reiserfs_compare_keys (key, &(item_headers[i].key)); + if (val == 0) + { + block_key = &(item_headers[i].key); + break; + } + if (val < 0 && exact) + break; + if (val < 0) + { + if (i == 0) + { + grub_error (GRUB_ERR_READ_ERROR, "unexpected btree node"); + goto fail; + } + i--; + block_key = &(item_headers[i].key); + break; + } + } + if (!exact && i == item_count) + { + if (i == 0) + { + grub_error (GRUB_ERR_READ_ERROR, "unexpected btree node"); + goto fail; + } + i--; + block_key = &(item_headers[i].key); + } + } + } + while (current_level > 1); + + item->data = data; + + if (!block_key) + { + item->block_number = 0; + item->block_position = 0; + item->type = GRUB_REISERFS_UNKNOWN; +#ifdef GRUB_REISERFS_DEBUG + grub_printf("Not found.\n"); +#endif + } + else + { + item->block_number = block_number; + item->block_position = i; + item->type = grub_reiserfs_get_key_type (block_key); + grub_memcpy (&(item->header), &(item_headers[i]), + sizeof (struct grub_reiserfs_item_header)); +#ifdef GRUB_REISERFS_DEBUG + grub_printf ("F %03d/%03d ", i + 1, item_count); + grub_reiserfs_print_key (block_key); +#endif + } + + assert (grub_errno == GRUB_ERR_NONE); + grub_free (block_header); + return GRUB_ERR_NONE; + + fail: + assert (grub_errno != GRUB_ERR_NONE); + grub_free (block_header); + assert (grub_errno != GRUB_ERR_NONE); + return grub_errno; +} + +/* Return the path of the file which is pointed at by symlink NODE. */ +static char * +grub_reiserfs_read_symlink (grub_fshelp_node_t node) +{ + char *symlink_buffer = 0; + grub_size_t len = node->size; + grub_ssize_t ret; + + symlink_buffer = grub_malloc (len + 1); + if (! symlink_buffer) + return 0; + + ret = grub_reiserfs_read_real (node, 0, symlink_buffer, len, 0, 0); + if (ret < 0) + { + grub_free (symlink_buffer); + return 0; + } + + symlink_buffer[ret] = 0; + return symlink_buffer; +} + +/* Fill the mounted filesystem structure and return it. */ +static struct grub_reiserfs_data * +grub_reiserfs_mount (grub_disk_t disk) +{ + struct grub_reiserfs_data *data = 0; + data = grub_malloc (sizeof (*data)); + if (! data) + goto fail; + grub_disk_read (disk, REISERFS_SUPER_BLOCK_OFFSET / GRUB_DISK_SECTOR_SIZE, + 0, sizeof (data->superblock), &(data->superblock)); + if (grub_errno) + goto fail; + if (grub_memcmp (data->superblock.magic_string, + REISERFS_MAGIC_STRING, sizeof (REISERFS_MAGIC_STRING) - 1)) + { + grub_error (GRUB_ERR_BAD_FS, "not a ReiserFS filesystem"); + goto fail; + } + data->disk = disk; + return data; + + fail: + /* Disk is too small to contain a ReiserFS. */ + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a ReiserFS filesystem"); + + grub_free (data); + return 0; +} + +/* Call HOOK for each file in directory ITEM. */ +static int +grub_reiserfs_iterate_dir (grub_fshelp_node_t item, + grub_fshelp_iterate_dir_hook_t hook, + void *hook_data) +{ + struct grub_reiserfs_data *data = item->data; + struct grub_reiserfs_block_header *block_header = 0; + grub_uint16_t block_size, block_position; + grub_uint32_t block_number; + grub_uint64_t next_offset = item->next_offset; + int ret = 0; + + if (item->type != GRUB_REISERFS_DIRECTORY) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + goto fail; + } + block_size = grub_le_to_cpu16 (data->superblock.block_size); + block_header = grub_malloc (block_size + 1); + if (! block_header) + goto fail; + block_number = item->block_number; + block_position = item->block_position; + grub_dprintf ("reiserfs", "Iterating directory...\n"); + do + { + struct grub_reiserfs_directory_header *directory_headers; + struct grub_fshelp_node directory_item; + grub_uint16_t entry_count, entry_number; + struct grub_reiserfs_item_header *item_headers; + + grub_disk_read (data->disk, + block_number * (block_size >> GRUB_DISK_SECTOR_BITS), + (((grub_off_t) block_number * block_size) + & (GRUB_DISK_SECTOR_SIZE - 1)), + block_size, (char *) block_header); + if (grub_errno) + goto fail; + + ((char *) block_header)[block_size] = 0; + +#if 0 + if (grub_le_to_cpu16 (block_header->level) != 1) + { + grub_error (GRUB_ERR_BAD_FS, + "reiserfs: block %d is not a leaf block", + block_number); + goto fail; + } +#endif + + item_headers = (struct grub_reiserfs_item_header *) (block_header + 1); + directory_headers + = ((struct grub_reiserfs_directory_header *) + ((char *) block_header + + grub_le_to_cpu16 (item_headers[block_position].item_location))); + entry_count + = grub_le_to_cpu16 (item_headers[block_position].u.entry_count); + for (entry_number = 0; entry_number < entry_count; entry_number++) + { + struct grub_reiserfs_directory_header *directory_header + = &directory_headers[entry_number]; + grub_uint16_t entry_state + = grub_le_to_cpu16 (directory_header->state); + grub_fshelp_node_t entry_item; + struct grub_reiserfs_key entry_key; + enum grub_fshelp_filetype entry_type; + char *entry_name; + char *entry_name_end = 0; + char c; + + if (!(entry_state & GRUB_REISERFS_VISIBLE_MASK)) + continue; + + entry_name = (((char *) directory_headers) + + grub_le_to_cpu16 (directory_header->location)); + if (entry_number == 0) + { + entry_name_end = (char *) block_header + + grub_le_to_cpu16 (item_headers[block_position].item_location) + + grub_le_to_cpu16 (item_headers[block_position].item_size); + } + else + { + entry_name_end = (((char *) directory_headers) + + grub_le_to_cpu16 (directory_headers[entry_number - 1].location)); + } + if (entry_name_end < entry_name || entry_name_end > (char *) block_header + block_size) + { + entry_name_end = (char *) block_header + block_size; + } + + entry_key.directory_id = directory_header->directory_id; + entry_key.object_id = directory_header->object_id; + entry_key.u.v2.offset_type = 0; + grub_reiserfs_set_key_type (&entry_key, GRUB_REISERFS_DIRECTORY, + 2); + grub_reiserfs_set_key_offset (&entry_key, 1); + + entry_item = grub_malloc (sizeof (*entry_item)); + if (! entry_item) + goto fail; + + if (grub_reiserfs_get_item (data, &entry_key, entry_item, 1) + != GRUB_ERR_NONE) + { + grub_free (entry_item); + goto fail; + } + + if (entry_item->type == GRUB_REISERFS_DIRECTORY) + entry_type = GRUB_FSHELP_DIR; + else + { + grub_uint32_t entry_block_number; + /* Order is very important here. + First set the offset to 0 using current key version. + Then change the key type, which affects key version + detection. */ + grub_reiserfs_set_key_offset (&entry_key, 0); + grub_reiserfs_set_key_type (&entry_key, GRUB_REISERFS_STAT, + 2); + if (grub_reiserfs_get_item (data, &entry_key, entry_item, 1) + != GRUB_ERR_NONE) + { + grub_free (entry_item); + goto fail; + } + + if (entry_item->block_number != 0) + { + grub_uint16_t entry_version; + entry_version + = grub_le_to_cpu16 (entry_item->header.version); + entry_block_number = entry_item->block_number; +#if 0 + grub_dprintf ("reiserfs", + "version %04x block %08x (%08x) position %08x\n", + entry_version, entry_block_number, + ((grub_disk_addr_t) entry_block_number * block_size) / GRUB_DISK_SECTOR_SIZE, + grub_le_to_cpu16 (entry_item->header.item_location)); +#endif + if (entry_version == 0) /* Version 1 stat item. */ + { + struct grub_reiserfs_stat_item_v1 entry_v1_stat; + grub_disk_read (data->disk, + entry_block_number * (block_size >> GRUB_DISK_SECTOR_BITS), + grub_le_to_cpu16 (entry_item->header.item_location), + sizeof (entry_v1_stat), + (char *) &entry_v1_stat); + if (grub_errno) + goto fail; +#if 0 + grub_dprintf ("reiserfs", + "%04x %04x %04x %04x %08x %08x | %08x %08x %08x %08x\n", + grub_le_to_cpu16 (entry_v1_stat.mode), + grub_le_to_cpu16 (entry_v1_stat.hardlink_count), + grub_le_to_cpu16 (entry_v1_stat.uid), + grub_le_to_cpu16 (entry_v1_stat.gid), + grub_le_to_cpu32 (entry_v1_stat.size), + grub_le_to_cpu32 (entry_v1_stat.atime), + grub_le_to_cpu32 (entry_v1_stat.mtime), + grub_le_to_cpu32 (entry_v1_stat.ctime), + grub_le_to_cpu32 (entry_v1_stat.rdev), + grub_le_to_cpu32 (entry_v1_stat.first_direct_byte)); + grub_dprintf ("reiserfs", + "%04x %04x %04x %04x %08x %08x | %08x %08x %08x %08x\n", + entry_v1_stat.mode, + entry_v1_stat.hardlink_count, + entry_v1_stat.uid, + entry_v1_stat.gid, + entry_v1_stat.size, + entry_v1_stat.atime, + entry_v1_stat.mtime, + entry_v1_stat.ctime, + entry_v1_stat.rdev, + entry_v1_stat.first_direct_byte); +#endif + entry_item->mtime = grub_le_to_cpu32 (entry_v1_stat.mtime); + if ((grub_le_to_cpu16 (entry_v1_stat.mode) & S_IFLNK) + == S_IFLNK) + entry_type = GRUB_FSHELP_SYMLINK; + else + entry_type = GRUB_FSHELP_REG; + entry_item->size = (grub_off_t) grub_le_to_cpu32 (entry_v1_stat.size); + } + else + { + struct grub_reiserfs_stat_item_v2 entry_v2_stat; + grub_disk_read (data->disk, + entry_block_number * (block_size >> GRUB_DISK_SECTOR_BITS), + grub_le_to_cpu16 (entry_item->header.item_location), + sizeof (entry_v2_stat), + (char *) &entry_v2_stat); + if (grub_errno) + goto fail; +#if 0 + grub_dprintf ("reiserfs", + "%04x %04x %08x %08x%08x | %08x %08x %08x %08x | %08x %08x %08x\n", + grub_le_to_cpu16 (entry_v2_stat.mode), + grub_le_to_cpu16 (entry_v2_stat.reserved), + grub_le_to_cpu32 (entry_v2_stat.hardlink_count), + (unsigned int) (grub_le_to_cpu64 (entry_v2_stat.size) >> 32), + (unsigned int) (grub_le_to_cpu64 (entry_v2_stat.size) && 0xFFFFFFFF), + grub_le_to_cpu32 (entry_v2_stat.uid), + grub_le_to_cpu32 (entry_v2_stat.gid), + grub_le_to_cpu32 (entry_v2_stat.atime), + grub_le_to_cpu32 (entry_v2_stat.mtime), + grub_le_to_cpu32 (entry_v2_stat.ctime), + grub_le_to_cpu32 (entry_v2_stat.blocks), + grub_le_to_cpu32 (entry_v2_stat.first_direct_byte)); + grub_dprintf ("reiserfs", + "%04x %04x %08x %08x%08x | %08x %08x %08x %08x | %08x %08x %08x\n", + entry_v2_stat.mode, + entry_v2_stat.reserved, + entry_v2_stat.hardlink_count, + (unsigned int) (entry_v2_stat.size >> 32), + (unsigned int) (entry_v2_stat.size && 0xFFFFFFFF), + entry_v2_stat.uid, + entry_v2_stat.gid, + entry_v2_stat.atime, + entry_v2_stat.mtime, + entry_v2_stat.ctime, + entry_v2_stat.blocks, + entry_v2_stat.first_direct_byte); +#endif + entry_item->mtime = grub_le_to_cpu32 (entry_v2_stat.mtime); + entry_item->size = (grub_off_t) grub_le_to_cpu64 (entry_v2_stat.size); + if ((grub_le_to_cpu16 (entry_v2_stat.mode) & S_IFLNK) + == S_IFLNK) + entry_type = GRUB_FSHELP_SYMLINK; + else + entry_type = GRUB_FSHELP_REG; + } + } + else + { + /* Pseudo file ".." never has stat block. */ + if (entry_name_end == entry_name + 2 && grub_memcmp (entry_name, "..", 2) != 0) + grub_dprintf ("reiserfs", + "Warning : %s has no stat block !\n", + entry_name); + grub_free (entry_item); + goto next; + } + } + + c = *entry_name_end; + *entry_name_end = 0; + if (hook (entry_name, entry_type, entry_item, hook_data)) + { + *entry_name_end = c; + grub_dprintf ("reiserfs", "Found : %s, type=%d\n", + entry_name, entry_type); + ret = 1; + goto found; + } + *entry_name_end = c; + + next: + ; + } + + if (next_offset == 0) + break; + + grub_reiserfs_set_key_offset (&(item_headers[block_position].key), + next_offset); + if (grub_reiserfs_get_item (data, &(item_headers[block_position].key), + &directory_item, 1) != GRUB_ERR_NONE) + goto fail; + block_number = directory_item.block_number; + block_position = directory_item.block_position; + next_offset = directory_item.next_offset; + } + while (block_number); + + found: + assert (grub_errno == GRUB_ERR_NONE); + grub_free (block_header); + return ret; + fail: + assert (grub_errno != GRUB_ERR_NONE); + grub_free (block_header); + return 0; +} + +/****************************************************************************/ +/* grub api functions */ +/****************************************************************************/ + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_reiserfs_open (struct grub_file *file, const char *name) +{ + struct grub_reiserfs_data *data = 0; + struct grub_fshelp_node root, *found = 0; + struct grub_reiserfs_key key; + + grub_dl_ref (my_mod); + data = grub_reiserfs_mount (file->device->disk); + if (! data) + goto fail; + key.directory_id = grub_cpu_to_le32_compile_time (1); + key.object_id = grub_cpu_to_le32_compile_time (2); + key.u.v2.offset_type = 0; + grub_reiserfs_set_key_type (&key, GRUB_REISERFS_DIRECTORY, 2); + grub_reiserfs_set_key_offset (&key, 1); + if (grub_reiserfs_get_item (data, &key, &root, 1) != GRUB_ERR_NONE) + goto fail; + if (root.block_number == 0) + { + grub_error (GRUB_ERR_BAD_FS, "unable to find root item"); + goto fail; /* Should never happen since checked at mount. */ + } + grub_fshelp_find_file (name, &root, &found, + grub_reiserfs_iterate_dir, + grub_reiserfs_read_symlink, GRUB_FSHELP_REG); + if (grub_errno) + goto fail; + file->size = found->size; + + grub_dprintf ("reiserfs", "file size : %d (%08x%08x)\n", + (unsigned int) file->size, + (unsigned int) (file->size >> 32), (unsigned int) file->size); + file->offset = 0; + file->data = found; + return GRUB_ERR_NONE; + + fail: + assert (grub_errno != GRUB_ERR_NONE); + if (found != &root) + grub_free (found); + grub_free (data); + grub_dl_unref (my_mod); + return grub_errno; +} + +static grub_ssize_t +grub_reiserfs_read_real (struct grub_fshelp_node *node, + grub_off_t off, char *buf, grub_size_t len, + grub_disk_read_hook_t read_hook, void *read_hook_data) +{ + unsigned int indirect_block, indirect_block_count; + struct grub_reiserfs_key key; + struct grub_reiserfs_data *data = node->data; + struct grub_fshelp_node found; + grub_uint16_t block_size = grub_le_to_cpu16 (data->superblock.block_size); + grub_uint16_t item_size; + grub_uint32_t *indirect_block_ptr = 0; + grub_uint64_t current_key_offset = 1; + grub_off_t initial_position, current_position, final_position, length; + grub_disk_addr_t block; + grub_off_t offset; + + key.directory_id = node->header.key.directory_id; + key.object_id = node->header.key.object_id; + key.u.v2.offset_type = 0; + grub_reiserfs_set_key_type (&key, GRUB_REISERFS_ANY, 2); + initial_position = off; + current_position = 0; + final_position = MIN (len + initial_position, node->size); + grub_dprintf ("reiserfs", + "Reading from %lld to %lld (%lld instead of requested %ld)\n", + (unsigned long long) initial_position, + (unsigned long long) final_position, + (unsigned long long) (final_position - initial_position), + (unsigned long) len); + + grub_reiserfs_set_key_offset (&key, initial_position + 1); + + if (grub_reiserfs_get_item (data, &key, &found, 0) != GRUB_ERR_NONE) + goto fail; + + if (found.block_number == 0) + { + grub_error (GRUB_ERR_READ_ERROR, "offset %lld not found", + (unsigned long long) initial_position); + goto fail; + } + + current_key_offset = grub_reiserfs_get_key_offset (&found.header.key); + current_position = current_key_offset - 1; + + while (current_position < final_position) + { + grub_reiserfs_set_key_offset (&key, current_key_offset); + + if (grub_reiserfs_get_item (data, &key, &found, 1) != GRUB_ERR_NONE) + goto fail; + if (found.block_number == 0) + goto fail; + item_size = grub_le_to_cpu16 (found.header.item_size); + switch (found.type) + { + case GRUB_REISERFS_DIRECT: + block = ((grub_disk_addr_t) found.block_number) * (block_size >> GRUB_DISK_SECTOR_BITS); + grub_dprintf ("reiserfs_blocktype", "D: %u\n", (unsigned) block); + if (initial_position < current_position + item_size) + { + offset = MAX ((signed) (initial_position - current_position), 0); + length = (MIN (item_size, final_position - current_position) + - offset); + grub_dprintf ("reiserfs", + "Reading direct block %u from %u to %u...\n", + (unsigned) block, (unsigned) offset, + (unsigned) (offset + length)); + found.data->disk->read_hook = read_hook; + found.data->disk->read_hook_data = read_hook_data; + grub_disk_read (found.data->disk, + block, + offset + + grub_le_to_cpu16 (found.header.item_location), + length, buf); + found.data->disk->read_hook = 0; + if (grub_errno) + goto fail; + buf += length; + current_position += offset + length; + } + else + current_position += item_size; + break; + case GRUB_REISERFS_INDIRECT: + indirect_block_count = item_size / sizeof (*indirect_block_ptr); + indirect_block_ptr = grub_malloc (item_size); + if (! indirect_block_ptr) + goto fail; + grub_disk_read (found.data->disk, + found.block_number * (block_size >> GRUB_DISK_SECTOR_BITS), + grub_le_to_cpu16 (found.header.item_location), + item_size, indirect_block_ptr); + if (grub_errno) + goto fail; + found.data->disk->read_hook = read_hook; + found.data->disk->read_hook_data = read_hook_data; + for (indirect_block = 0; + indirect_block < indirect_block_count + && current_position < final_position; + indirect_block++) + { + block = grub_le_to_cpu32 (indirect_block_ptr[indirect_block]) * + (block_size >> GRUB_DISK_SECTOR_BITS); + grub_dprintf ("reiserfs_blocktype", "I: %u\n", (unsigned) block); + if (current_position + block_size >= initial_position) + { + offset = MAX ((signed) (initial_position - current_position), + 0); + length = (MIN (block_size, final_position - current_position) + - offset); + grub_dprintf ("reiserfs", + "Reading indirect block %u from %u to %u...\n", + (unsigned) block, (unsigned) offset, + (unsigned) (offset + length)); +#if 0 + grub_dprintf ("reiserfs", + "\nib=%04d/%04d, ip=%d, cp=%d, fp=%d, off=%d, l=%d, tl=%d\n", + indirect_block + 1, indirect_block_count, + initial_position, current_position, + final_position, offset, length, len); +#endif + grub_disk_read (found.data->disk, block, offset, length, buf); + if (grub_errno) + goto fail; + buf += length; + current_position += offset + length; + } + else + current_position += block_size; + } + found.data->disk->read_hook = 0; + grub_free (indirect_block_ptr); + indirect_block_ptr = 0; + break; + default: + goto fail; + } + current_key_offset = current_position + 1; + } + + grub_dprintf ("reiserfs", + "Have successfully read %lld bytes (%ld requested)\n", + (unsigned long long) (current_position - initial_position), + (unsigned long) len); + return current_position - initial_position; + +#if 0 + switch (found.type) + { + case GRUB_REISERFS_DIRECT: + read_length = MIN (len, item_size - file->offset); + grub_disk_read (found.data->disk, + (found.block_number * block_size) / GRUB_DISK_SECTOR_SIZE, + grub_le_to_cpu16 (found.header.item_location) + file->offset, + read_length, buf); + if (grub_errno) + goto fail; + break; + case GRUB_REISERFS_INDIRECT: + indirect_block_count = item_size / sizeof (*indirect_block_ptr); + indirect_block_ptr = grub_malloc (item_size); + if (!indirect_block_ptr) + goto fail; + grub_disk_read (found.data->disk, + (found.block_number * block_size) / GRUB_DISK_SECTOR_SIZE, + grub_le_to_cpu16 (found.header.item_location), + item_size, (char *) indirect_block_ptr); + if (grub_errno) + goto fail; + len = MIN (len, file->size - file->offset); + for (indirect_block = file->offset / block_size; + indirect_block < indirect_block_count && read_length < len; + indirect_block++) + { + read = MIN (block_size, len - read_length); + grub_disk_read (found.data->disk, + (grub_le_to_cpu32 (indirect_block_ptr[indirect_block]) * block_size) / GRUB_DISK_SECTOR_SIZE, + file->offset % block_size, read, + ((void *) buf) + read_length); + if (grub_errno) + goto fail; + read_length += read; + } + grub_free (indirect_block_ptr); + break; + default: + goto fail; + } + + return read_length; +#endif + + fail: + grub_free (indirect_block_ptr); + return -1; +} + +static grub_ssize_t +grub_reiserfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + return grub_reiserfs_read_real (file->data, file->offset, buf, len, + file->read_hook, file->read_hook_data); +} + +/* Close the file FILE. */ +static grub_err_t +grub_reiserfs_close (grub_file_t file) +{ + struct grub_fshelp_node *node = file->data; + struct grub_reiserfs_data *data = node->data; + + grub_free (data); + grub_free (node); + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +/* Context for grub_reiserfs_dir. */ +struct grub_reiserfs_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_reiserfs_dir. */ +static int +grub_reiserfs_dir_iter (const char *filename, + enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_reiserfs_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 = node->mtime; + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +/* Call HOOK with each file under DIR. */ +static grub_err_t +grub_reiserfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_reiserfs_dir_ctx ctx = { hook, hook_data }; + struct grub_reiserfs_data *data = 0; + struct grub_fshelp_node root, *found; + struct grub_reiserfs_key root_key; + + grub_dl_ref (my_mod); + data = grub_reiserfs_mount (device->disk); + if (! data) + goto fail; + root_key.directory_id = grub_cpu_to_le32_compile_time (1); + root_key.object_id = grub_cpu_to_le32_compile_time (2); + root_key.u.v2.offset_type = 0; + grub_reiserfs_set_key_type (&root_key, GRUB_REISERFS_DIRECTORY, 2); + grub_reiserfs_set_key_offset (&root_key, 1); + if (grub_reiserfs_get_item (data, &root_key, &root, 1) != GRUB_ERR_NONE) + goto fail; + if (root.block_number == 0) + { + grub_error(GRUB_ERR_BAD_FS, "root not found"); + goto fail; + } + grub_fshelp_find_file (path, &root, &found, grub_reiserfs_iterate_dir, + grub_reiserfs_read_symlink, GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + grub_reiserfs_iterate_dir (found, grub_reiserfs_dir_iter, &ctx); + grub_free (data); + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; + + fail: + grub_free (data); + grub_dl_unref (my_mod); + return grub_errno; +} + +/* Return the label of the device DEVICE in LABEL. The label is + returned in a grub_malloc'ed buffer and should be freed by the + caller. */ +static grub_err_t +grub_reiserfs_label (grub_device_t device, char **label) +{ + struct grub_reiserfs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_reiserfs_mount (disk); + if (data) + { + *label = grub_strndup (data->superblock.label, + sizeof (data->superblock.label)); + } + else + *label = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_reiserfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_reiserfs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + *uuid = NULL; + data = grub_reiserfs_mount (disk); + if (data) + { + unsigned i; + for (i = 0; i < ARRAY_SIZE (data->superblock.uuid); i++) + if (data->superblock.uuid[i]) + break; + if (i < ARRAY_SIZE (data->superblock.uuid)) + *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + grub_be_to_cpu16 (data->superblock.uuid[0]), + grub_be_to_cpu16 (data->superblock.uuid[1]), + grub_be_to_cpu16 (data->superblock.uuid[2]), + grub_be_to_cpu16 (data->superblock.uuid[3]), + grub_be_to_cpu16 (data->superblock.uuid[4]), + grub_be_to_cpu16 (data->superblock.uuid[5]), + grub_be_to_cpu16 (data->superblock.uuid[6]), + grub_be_to_cpu16 (data->superblock.uuid[7])); + } + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +static struct grub_fs grub_reiserfs_fs = + { + .name = "reiserfs", + .fs_dir = grub_reiserfs_dir, + .fs_open = grub_reiserfs_open, + .fs_read = grub_reiserfs_read, + .fs_close = grub_reiserfs_close, + .fs_label = grub_reiserfs_label, + .fs_uuid = grub_reiserfs_uuid, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(reiserfs) +{ + grub_fs_register (&grub_reiserfs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(reiserfs) +{ + grub_fs_unregister (&grub_reiserfs_fs); +} diff --git a/grub-core/fs/romfs.c b/grub-core/fs/romfs.c new file mode 100644 index 0000000..d97b8fb --- /dev/null +++ b/grub-core/fs/romfs.c @@ -0,0 +1,484 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010 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/file.h> +#include <grub/types.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/disk.h> +#include <grub/fs.h> +#include <grub/fshelp.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +struct grub_romfs_superblock +{ + char magic[8]; +#define GRUB_ROMFS_MAGIC "-rom1fs-" + grub_uint32_t total_size; + grub_uint32_t chksum; + char label[0]; +}; + +struct grub_romfs_file_header +{ + grub_uint32_t next_file; + grub_uint32_t spec; + grub_uint32_t size; + grub_uint32_t chksum; + char name[0]; +}; + +struct grub_romfs_data +{ + grub_disk_addr_t first_file; + grub_disk_t disk; +}; + +struct grub_fshelp_node +{ + grub_disk_addr_t addr; + struct grub_romfs_data *data; + grub_disk_addr_t data_addr; + /* Not filled for root. */ + struct grub_romfs_file_header file; +}; + +#define GRUB_ROMFS_ALIGN 16 +#define GRUB_ROMFS_TYPE_MASK 7 +#define GRUB_ROMFS_TYPE_HARDLINK 0 +#define GRUB_ROMFS_TYPE_DIRECTORY 1 +#define GRUB_ROMFS_TYPE_REGULAR 2 +#define GRUB_ROMFS_TYPE_SYMLINK 3 + +static grub_err_t +do_checksum (void *in, grub_size_t insize) +{ + grub_uint32_t *a = in; + grub_size_t sz = insize / 4; + grub_uint32_t *b = a + sz; + grub_uint32_t csum = 0; + + while (a < b) + csum += grub_be_to_cpu32 (*a++); + if (csum) + return grub_error (GRUB_ERR_BAD_FS, "invalid checksum"); + return GRUB_ERR_NONE; +} + +static struct grub_romfs_data * +grub_romfs_mount (grub_device_t dev) +{ + union { + struct grub_romfs_superblock sb; + char d[512]; + } sb; + grub_err_t err; + char *ptr; + grub_disk_addr_t sec = 0; + struct grub_romfs_data *data; + if (!dev->disk) + { + grub_error (GRUB_ERR_BAD_FS, "not a disk"); + return NULL; + } + err = grub_disk_read (dev->disk, 0, 0, sizeof (sb), &sb); + if (err == GRUB_ERR_OUT_OF_RANGE) + err = grub_errno = GRUB_ERR_BAD_FS; + if (err) + return NULL; + if (grub_be_to_cpu32 (sb.sb.total_size) < sizeof (sb)) + { + grub_error (GRUB_ERR_BAD_FS, "too short filesystem"); + return NULL; + } + if (grub_memcmp (sb.sb.magic, GRUB_ROMFS_MAGIC, + sizeof (sb.sb.magic)) != 0) + { + grub_error (GRUB_ERR_BAD_FS, "not romfs"); + return NULL; + } + err = do_checksum (&sb, sizeof (sb) < grub_be_to_cpu32 (sb.sb.total_size) ? + sizeof (sb) : grub_be_to_cpu32 (sb.sb.total_size)); + if (err) + { + grub_error (GRUB_ERR_BAD_FS, "checksum incorrect"); + return NULL; + } + for (ptr = sb.sb.label; (void *) ptr < (void *) (&sb + 1) + && ptr - sb.d < (grub_ssize_t) grub_be_to_cpu32 (sb.sb.total_size); ptr++) + if (!*ptr) + break; + while ((void *) ptr == &sb + 1) + { + sec++; + err = grub_disk_read (dev->disk, sec, 0, sizeof (sb), &sb); + if (err == GRUB_ERR_OUT_OF_RANGE) + err = grub_errno = GRUB_ERR_BAD_FS; + if (err) + return NULL; + for (ptr = sb.d; (void *) ptr < (void *) (&sb + 1) + && (ptr - sb.d + (sec << GRUB_DISK_SECTOR_BITS) + < grub_be_to_cpu32 (sb.sb.total_size)); + ptr++) + if (!*ptr) + break; + } + data = grub_malloc (sizeof (*data)); + if (!data) + return NULL; + data->first_file = ALIGN_UP (ptr + 1 - sb.d, GRUB_ROMFS_ALIGN) + + (sec << GRUB_DISK_SECTOR_BITS); + data->disk = dev->disk; + return data; +} + +static char * +grub_romfs_read_symlink (grub_fshelp_node_t node) +{ + char *ret; + grub_err_t err; + ret = grub_malloc (grub_be_to_cpu32 (node->file.size) + 1); + if (!ret) + return NULL; + err = grub_disk_read (node->data->disk, + (node->data_addr) >> GRUB_DISK_SECTOR_BITS, + (node->data_addr) & (GRUB_DISK_SECTOR_SIZE - 1), + grub_be_to_cpu32 (node->file.size), ret); + if (err) + { + grub_free (ret); + return NULL; + } + ret[grub_be_to_cpu32 (node->file.size)] = 0; + return ret; +} + +static int +grub_romfs_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + grub_disk_addr_t caddr; + struct grub_romfs_file_header hdr; + unsigned nptr; + unsigned i, j; + grub_size_t a = 0; + grub_properly_aligned_t *name = NULL; + + for (caddr = dir->data_addr; caddr; + caddr = grub_be_to_cpu32 (hdr.next_file) & ~(GRUB_ROMFS_ALIGN - 1)) + { + grub_disk_addr_t naddr = caddr + sizeof (hdr); + grub_uint32_t csum = 0; + enum grub_fshelp_filetype filetype = GRUB_FSHELP_UNKNOWN; + struct grub_fshelp_node *node = NULL; + grub_err_t err; + + err = grub_disk_read (dir->data->disk, caddr >> GRUB_DISK_SECTOR_BITS, + caddr & (GRUB_DISK_SECTOR_SIZE - 1), + sizeof (hdr), &hdr); + if (err) + { + grub_free (name); + return 1; + } + for (nptr = 0; ; nptr++, naddr += 16) + { + if (a <= nptr) + { + grub_properly_aligned_t *on; + a = 2 * (nptr + 1); + on = name; + name = grub_realloc (name, a * 16); + if (!name) + { + grub_free (on); + return 1; + } + } + COMPILE_TIME_ASSERT (16 % sizeof (name[0]) == 0); + err = grub_disk_read (dir->data->disk, naddr >> GRUB_DISK_SECTOR_BITS, + naddr & (GRUB_DISK_SECTOR_SIZE - 1), + 16, name + (16 / sizeof (name[0])) * nptr); + if (err) + return 1; + for (j = 0; j < 16; j++) + if (!((char *) name)[16 * nptr + j]) + break; + if (j != 16) + break; + } + for (i = 0; i < sizeof (hdr) / sizeof (grub_uint32_t); i++) + csum += grub_be_to_cpu32 (((grub_uint32_t *) &hdr)[i]); + for (i = 0; i < (nptr + 1) * 4; i++) + csum += grub_be_to_cpu32 (((grub_uint32_t *) name)[i]); + if (csum != 0) + { + grub_error (GRUB_ERR_BAD_FS, "invalid checksum"); + grub_free (name); + return 1; + } + node = grub_malloc (sizeof (*node)); + if (!node) + return 1; + node->addr = caddr; + node->data_addr = caddr + (nptr + 1) * 16 + sizeof (hdr); + node->data = dir->data; + node->file = hdr; + switch (grub_be_to_cpu32 (hdr.next_file) & GRUB_ROMFS_TYPE_MASK) + { + case GRUB_ROMFS_TYPE_REGULAR: + filetype = GRUB_FSHELP_REG; + break; + case GRUB_ROMFS_TYPE_SYMLINK: + filetype = GRUB_FSHELP_SYMLINK; + break; + case GRUB_ROMFS_TYPE_DIRECTORY: + node->data_addr = grub_be_to_cpu32 (hdr.spec); + filetype = GRUB_FSHELP_DIR; + break; + case GRUB_ROMFS_TYPE_HARDLINK: + { + grub_disk_addr_t laddr; + node->addr = laddr = grub_be_to_cpu32 (hdr.spec); + err = grub_disk_read (dir->data->disk, + laddr >> GRUB_DISK_SECTOR_BITS, + laddr & (GRUB_DISK_SECTOR_SIZE - 1), + sizeof (node->file), &node->file); + if (err) + return 1; + if ((grub_be_to_cpu32 (node->file.next_file) & GRUB_ROMFS_TYPE_MASK) + == GRUB_ROMFS_TYPE_REGULAR + || (grub_be_to_cpu32 (node->file.next_file) + & GRUB_ROMFS_TYPE_MASK) == GRUB_ROMFS_TYPE_SYMLINK) + { + laddr += sizeof (hdr); + while (1) + { + char buf[16]; + err = grub_disk_read (dir->data->disk, + laddr >> GRUB_DISK_SECTOR_BITS, + laddr & (GRUB_DISK_SECTOR_SIZE - 1), + 16, buf); + if (err) + return 1; + for (i = 0; i < 16; i++) + if (!buf[i]) + break; + if (i != 16) + break; + laddr += 16; + } + node->data_addr = laddr + 16; + } + if ((grub_be_to_cpu32 (node->file.next_file) + & GRUB_ROMFS_TYPE_MASK) == GRUB_ROMFS_TYPE_REGULAR) + filetype = GRUB_FSHELP_REG; + if ((grub_be_to_cpu32 (node->file.next_file) + & GRUB_ROMFS_TYPE_MASK) == GRUB_ROMFS_TYPE_SYMLINK) + filetype = GRUB_FSHELP_SYMLINK; + if ((grub_be_to_cpu32 (node->file.next_file) & GRUB_ROMFS_TYPE_MASK) + == GRUB_ROMFS_TYPE_DIRECTORY) + { + node->data_addr = grub_be_to_cpu32 (node->file.spec); + filetype = GRUB_FSHELP_DIR; + } + + break; + } + } + + if (hook ((char *) name, filetype, node, hook_data)) + { + grub_free (name); + return 1; + } + } + grub_free (name); + return 0; +} + +/* Context for grub_romfs_dir. */ +struct grub_romfs_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_romfs_dir. */ +static int +grub_romfs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_romfs_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); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_romfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_romfs_dir_ctx ctx = { hook, hook_data }; + struct grub_romfs_data *data = 0; + struct grub_fshelp_node *fdiro = 0, start; + + data = grub_romfs_mount (device); + if (! data) + goto fail; + + start.addr = data->first_file; + start.data_addr = data->first_file; + start.data = data; + grub_fshelp_find_file (path, &start, &fdiro, grub_romfs_iterate_dir, + grub_romfs_read_symlink, GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + + grub_romfs_iterate_dir (fdiro, grub_romfs_dir_iter, &ctx); + + fail: + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_romfs_open (struct grub_file *file, const char *name) +{ + struct grub_romfs_data *data = 0; + struct grub_fshelp_node *fdiro = 0, start; + + data = grub_romfs_mount (file->device); + if (! data) + goto fail; + + start.addr = data->first_file; + start.data_addr = data->first_file; + start.data = data; + + grub_fshelp_find_file (name, &start, &fdiro, grub_romfs_iterate_dir, + grub_romfs_read_symlink, GRUB_FSHELP_REG); + if (grub_errno) + goto fail; + + file->size = grub_be_to_cpu32 (fdiro->file.size); + file->data = fdiro; + return GRUB_ERR_NONE; + + fail: + grub_free (data); + + return grub_errno; +} + +static grub_ssize_t +grub_romfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_fshelp_node *data = file->data; + + /* XXX: The file is stored in as a single extent. */ + data->data->disk->read_hook = file->read_hook; + data->data->disk->read_hook_data = file->read_hook_data; + grub_disk_read (data->data->disk, + (data->data_addr + file->offset) >> GRUB_DISK_SECTOR_BITS, + (data->data_addr + file->offset) & (GRUB_DISK_SECTOR_SIZE - 1), + len, buf); + data->data->disk->read_hook = NULL; + + if (grub_errno) + return -1; + + return len; +} + +static grub_err_t +grub_romfs_close (grub_file_t file) +{ + struct grub_fshelp_node *data = file->data; + + grub_free (data->data); + grub_free (data); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_romfs_label (grub_device_t device, char **label) +{ + struct grub_romfs_data *data; + grub_err_t err; + + *label = NULL; + + data = grub_romfs_mount (device); + if (!data) + return grub_errno; + *label = grub_malloc (data->first_file + 1 + - sizeof (struct grub_romfs_superblock)); + if (!*label) + { + grub_free (data); + return grub_errno; + } + err = grub_disk_read (device->disk, 0, sizeof (struct grub_romfs_superblock), + data->first_file + - sizeof (struct grub_romfs_superblock), + *label); + if (err) + { + grub_free (data); + grub_free (*label); + *label = NULL; + return err; + } + (*label)[data->first_file - sizeof (struct grub_romfs_superblock)] = 0; + grub_free (data); + return GRUB_ERR_NONE; +} + + +static struct grub_fs grub_romfs_fs = + { + .name = "romfs", + .fs_dir = grub_romfs_dir, + .fs_open = grub_romfs_open, + .fs_read = grub_romfs_read, + .fs_close = grub_romfs_close, + .fs_label = grub_romfs_label, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, + .blocklist_install = 0, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(romfs) +{ + grub_fs_register (&grub_romfs_fs); +} + +GRUB_MOD_FINI(romfs) +{ + grub_fs_unregister (&grub_romfs_fs); +} diff --git a/grub-core/fs/sfs.c b/grub-core/fs/sfs.c new file mode 100644 index 0000000..983e880 --- /dev/null +++ b/grub-core/fs/sfs.c @@ -0,0 +1,789 @@ +/* sfs.c - Amiga Smart 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/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* The common header for a block. */ +struct grub_sfs_bheader +{ + grub_uint8_t magic[4]; + grub_uint32_t chksum; + grub_uint32_t ipointtomyself; +} GRUB_PACKED; + +/* The sfs rootblock. */ +struct grub_sfs_rblock +{ + struct grub_sfs_bheader header; + grub_uint32_t version; + grub_uint32_t createtime; + grub_uint8_t flags; + grub_uint8_t unused1[31]; + grub_uint32_t blocksize; + grub_uint8_t unused2[40]; + grub_uint8_t unused3[8]; + grub_uint32_t rootobject; + grub_uint32_t btree; +} GRUB_PACKED; + +enum + { + FLAGS_CASE_SENSITIVE = 0x80 + }; + +/* A SFS object container. */ +struct grub_sfs_obj +{ + grub_uint8_t unused1[4]; + grub_uint32_t nodeid; + grub_uint8_t unused2[4]; + union + { + struct + { + grub_uint32_t first_block; + grub_uint32_t size; + } GRUB_PACKED file; + struct + { + grub_uint32_t hashtable; + grub_uint32_t dir_objc; + } GRUB_PACKED dir; + } file_dir; + grub_uint32_t mtime; + grub_uint8_t type; + grub_uint8_t filename[1]; + grub_uint8_t comment[1]; +} GRUB_PACKED; + +#define GRUB_SFS_TYPE_DELETED 32 +#define GRUB_SFS_TYPE_SYMLINK 64 +#define GRUB_SFS_TYPE_DIR 128 + +/* A SFS object container. */ +struct grub_sfs_objc +{ + struct grub_sfs_bheader header; + grub_uint32_t parent; + grub_uint32_t next; + grub_uint32_t prev; + /* The amount of objects depends on the blocksize. */ + struct grub_sfs_obj objects[1]; +} GRUB_PACKED; + +struct grub_sfs_btree_node +{ + grub_uint32_t key; + grub_uint32_t data; +} GRUB_PACKED; + +struct grub_sfs_btree_extent +{ + grub_uint32_t key; + grub_uint32_t next; + grub_uint32_t prev; + grub_uint16_t size; +} GRUB_PACKED; + +struct grub_sfs_btree +{ + struct grub_sfs_bheader header; + grub_uint16_t nodes; + grub_uint8_t leaf; + grub_uint8_t nodesize; + /* Normally this can be kind of node, but just extents are + supported. */ + struct grub_sfs_btree_node node[1]; +} GRUB_PACKED; + + + +struct cache_entry +{ + grub_uint32_t off; + grub_uint32_t block; +}; + +struct grub_fshelp_node +{ + struct grub_sfs_data *data; + grub_uint32_t block; + grub_uint32_t size; + grub_uint32_t mtime; + grub_uint32_t cache_off; + grub_uint32_t next_extent; + grub_size_t cache_allocated; + grub_size_t cache_size; + struct cache_entry *cache; +}; + +/* Information about a "mounted" sfs filesystem. */ +struct grub_sfs_data +{ + struct grub_sfs_rblock rblock; + struct grub_fshelp_node diropen; + grub_disk_t disk; + + /* Log of blocksize in sectors. */ + int log_blocksize; + + int fshelp_flags; + + /* Label of the filesystem. */ + char *label; +}; + +static grub_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 grub_err_t +grub_sfs_read_extent (struct grub_sfs_data *data, unsigned int block, + grub_uint32_t *size, grub_uint32_t *nextext) +{ + char *treeblock; + struct grub_sfs_btree *tree; + int i; + grub_uint32_t next; + grub_size_t blocksize = GRUB_DISK_SECTOR_SIZE << data->log_blocksize; + + treeblock = grub_malloc (blocksize); + if (!treeblock) + return grub_errno; + + next = grub_be_to_cpu32 (data->rblock.btree); + tree = (struct grub_sfs_btree *) treeblock; + + /* Handle this level in the btree. */ + do + { + grub_uint16_t nnodes; + grub_disk_read (data->disk, + ((grub_disk_addr_t) next) << data->log_blocksize, + 0, blocksize, treeblock); + if (grub_errno) + { + grub_free (treeblock); + return grub_errno; + } + + nnodes = grub_be_to_cpu16 (tree->nodes); + if (nnodes * (grub_uint32_t) (tree)->nodesize > blocksize) + break; + + for (i = (int) nnodes - 1; i >= 0; i--) + { + +#define EXTNODE(tree, index) \ + ((struct grub_sfs_btree_node *) (((char *) &(tree)->node[0]) \ + + (index) * (tree)->nodesize)) + + /* Follow the tree down to the leaf level. */ + if ((grub_be_to_cpu32 (EXTNODE(tree, i)->key) <= block) + && !tree->leaf) + { + next = grub_be_to_cpu32 (EXTNODE (tree, i)->data); + break; + } + + /* If the leaf level is reached, just find the correct extent. */ + if (grub_be_to_cpu32 (EXTNODE (tree, i)->key) == block && tree->leaf) + { + struct grub_sfs_btree_extent *extent; + extent = (struct grub_sfs_btree_extent *) EXTNODE (tree, i); + + /* We found a correct leaf. */ + *size = grub_be_to_cpu16 (extent->size); + *nextext = grub_be_to_cpu32 (extent->next); + + grub_free (treeblock); + return 0; + } + +#undef EXTNODE + + } + } while (!tree->leaf); + + grub_free (treeblock); + + return grub_error (GRUB_ERR_FILE_READ_ERROR, "SFS extent not found"); +} + +static grub_disk_addr_t +grub_sfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +{ + grub_uint32_t blk; + grub_uint32_t size = 0; + grub_uint32_t next = 0; + grub_disk_addr_t off; + struct grub_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) + { + grub_size_t cache_size; + /* Assume half-max extents (32768 sectors). */ + cache_size = ((node->size >> (data->log_blocksize + GRUB_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 = grub_calloc (cache_size, sizeof (node->cache[0])); + if (!node->cache) + { + grub_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) + { + grub_err_t err; + + err = grub_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; + grub_size_t sz; + + if (grub_mul (node->cache_allocated, 2 * sizeof (e[0]), &sz)) + goto fail; + + e = grub_realloc (node->cache, sz); + if (!e) + { + fail: + grub_errno = 0; + grub_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; + } + + grub_error (GRUB_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 grub_ssize_t +grub_sfs_read_file (grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + return grub_fshelp_read_file (node->data->disk, node, + read_hook, read_hook_data, + pos, len, buf, grub_sfs_read_block, + node->size, node->data->log_blocksize, 0); +} + + +static struct grub_sfs_data * +grub_sfs_mount (grub_disk_t disk) +{ + struct grub_sfs_data *data; + struct grub_sfs_objc *rootobjc; + char *rootobjc_data = 0; + grub_uint32_t blk; + unsigned int max_len; + + data = grub_malloc (sizeof (*data)); + if (!data) + return 0; + + /* Read the rootblock. */ + grub_disk_read (disk, 0, 0, sizeof (struct grub_sfs_rblock), + &data->rblock); + if (grub_errno) + goto fail; + + /* Make sure this is a sfs filesystem. */ + if (grub_strncmp ((char *) (data->rblock.header.magic), "SFS", 4) + || data->rblock.blocksize == 0 + || (data->rblock.blocksize & (data->rblock.blocksize - 1)) != 0 + || (data->rblock.blocksize & grub_cpu_to_be32_compile_time (0xf00001ff))) + { + grub_error (GRUB_ERR_BAD_FS, "not a SFS filesystem"); + goto fail; + } + + for (data->log_blocksize = 9; + (1U << data->log_blocksize) < grub_be_to_cpu32 (data->rblock.blocksize); + data->log_blocksize++); + data->log_blocksize -= GRUB_DISK_SECTOR_BITS; + if (data->rblock.flags & FLAGS_CASE_SENSITIVE) + data->fshelp_flags = 0; + else + data->fshelp_flags = GRUB_FSHELP_CASE_INSENSITIVE; + rootobjc_data = grub_malloc (GRUB_DISK_SECTOR_SIZE << data->log_blocksize); + if (! rootobjc_data) + goto fail; + + /* Read the root object container. */ + grub_disk_read (disk, ((grub_disk_addr_t) grub_be_to_cpu32 (data->rblock.rootobject)) + << data->log_blocksize, 0, + GRUB_DISK_SECTOR_SIZE << data->log_blocksize, rootobjc_data); + if (grub_errno) + goto fail; + + rootobjc = (struct grub_sfs_objc *) rootobjc_data; + + blk = grub_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 = ((GRUB_DISK_SECTOR_SIZE << data->log_blocksize) + - 24 /* offsetof (struct grub_sfs_objc, objects) */ + - 25); /* offsetof (struct grub_sfs_obj, filename) */ + data->label = grub_zalloc (max_len + 1); + grub_strncpy (data->label, (char *) rootobjc->objects[0].filename, max_len); + + grub_free (rootobjc_data); + return data; + + fail: + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not an SFS filesystem"); + + grub_free (data); + grub_free (rootobjc_data); + return 0; +} + + +static char * +grub_sfs_read_symlink (grub_fshelp_node_t node) +{ + struct grub_sfs_data *data = node->data; + char *symlink; + char *block; + + block = grub_malloc (GRUB_DISK_SECTOR_SIZE << data->log_blocksize); + if (!block) + return 0; + + grub_disk_read (data->disk, ((grub_disk_addr_t) node->block) + << data->log_blocksize, + 0, GRUB_DISK_SECTOR_SIZE << data->log_blocksize, block); + if (grub_errno) + { + grub_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 = grub_malloc (((GRUB_DISK_SECTOR_SIZE << data->log_blocksize) + - 24) * GRUB_MAX_UTF8_PER_LATIN1 + 1); + if (!symlink) + { + grub_free (block); + return 0; + } + *grub_latin1_to_utf8 ((grub_uint8_t *) symlink, (grub_uint8_t *) &block[24], + (GRUB_DISK_SECTOR_SIZE << data->log_blocksize) - 24) = '\0'; + grub_free (block); + return symlink; +} + +/* Helper for grub_sfs_iterate_dir. */ +static int +grub_sfs_create_node (struct grub_fshelp_node **node, + struct grub_sfs_data *data, + const char *name, + grub_uint32_t block, grub_uint32_t size, int type, + grub_uint32_t mtime, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + grub_size_t len = grub_strlen (name); + grub_uint8_t *name_u8; + int ret; + grub_size_t sz; + + if (grub_mul (len, GRUB_MAX_UTF8_PER_LATIN1, &sz) || + grub_add (sz, 1, &sz)) + return 1; + + *node = grub_malloc (sizeof (**node)); + if (!*node) + return 1; + name_u8 = grub_malloc (sz); + if (!name_u8) + { + grub_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; + + *grub_latin1_to_utf8 (name_u8, (const grub_uint8_t *) name, len) = '\0'; + + ret = hook ((char *) name_u8, type | data->fshelp_flags, *node, hook_data); + grub_free (name_u8); + return ret; +} + +static int +grub_sfs_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + struct grub_fshelp_node *node = 0; + struct grub_sfs_data *data = dir->data; + char *objc_data; + struct grub_sfs_objc *objc; + unsigned int next = dir->block; + grub_uint32_t pos; + + objc_data = grub_malloc (GRUB_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) + { + grub_disk_read (data->disk, ((grub_disk_addr_t) next) + << data->log_blocksize, 0, + GRUB_DISK_SECTOR_SIZE << data->log_blocksize, objc_data); + if (grub_errno) + goto fail; + + objc = (struct grub_sfs_objc *) objc_data; + + pos = (char *) &objc->objects[0] - (char *) objc; + + /* Iterate over all entries in this block. */ + while (pos + sizeof (struct grub_sfs_obj) + < (1U << (GRUB_DISK_SECTOR_BITS + data->log_blocksize))) + { + struct grub_sfs_obj *obj; + obj = (struct grub_sfs_obj *) ((char *) objc + pos); + const char *filename = (const char *) obj->filename; + grub_size_t len; + enum grub_fshelp_filetype type; + grub_uint32_t block; + + /* The filename and comment dynamically increase the size of + the object. */ + len = grub_strlen (filename); + len += grub_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 & GRUB_SFS_TYPE_DELETED) + continue; + else if (obj->type & GRUB_SFS_TYPE_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + else if (obj->type & GRUB_SFS_TYPE_DIR) + type = GRUB_FSHELP_DIR; + else + type = GRUB_FSHELP_REG; + + if (type == GRUB_FSHELP_DIR) + block = grub_be_to_cpu32 (obj->file_dir.dir.dir_objc); + else + block = grub_be_to_cpu32 (obj->file_dir.file.first_block); + + if (grub_sfs_create_node (&node, data, filename, block, + grub_be_to_cpu32 (obj->file_dir.file.size), + type, grub_be_to_cpu32 (obj->mtime), + hook, hook_data)) + { + grub_free (objc_data); + return 1; + } + } + + next = grub_be_to_cpu32 (objc->next); + } + + fail: + grub_free (objc_data); + return 0; +} + + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_sfs_open (struct grub_file *file, const char *name) +{ + struct grub_sfs_data *data; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_sfs_mount (file->device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_sfs_iterate_dir, + grub_sfs_read_symlink, GRUB_FSHELP_REG); + if (grub_errno) + goto fail; + + file->size = fdiro->size; + data->diropen = *fdiro; + grub_free (fdiro); + + file->data = data; + file->offset = 0; + + return 0; + + fail: + if (data && fdiro != &data->diropen) + grub_free (fdiro); + if (data) + grub_free (data->label); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +static grub_err_t +grub_sfs_close (grub_file_t file) +{ + struct grub_sfs_data *data = (struct grub_sfs_data *) file->data; + + grub_free (data->diropen.cache); + grub_free (data->label); + grub_free (data); + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + + +/* Read LEN bytes data from FILE into BUF. */ +static grub_ssize_t +grub_sfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_sfs_data *data = (struct grub_sfs_data *) file->data; + + return grub_sfs_read_file (&data->diropen, + file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + + +/* Context for grub_sfs_dir. */ +struct grub_sfs_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_sfs_dir. */ +static int +grub_sfs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_sfs_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.mtime = node->mtime + 8 * 365 * 86400 + 86400 * 2; + info.mtimeset = 1; + grub_free (node->cache); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_sfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_sfs_dir_ctx ctx = { hook, hook_data }; + struct grub_sfs_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_sfs_mount (device->disk); + if (!data) + goto fail; + + grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_sfs_iterate_dir, + grub_sfs_read_symlink, GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + + grub_sfs_iterate_dir (fdiro, grub_sfs_dir_iter, &ctx); + + fail: + if (data && fdiro != &data->diropen) + grub_free (fdiro); + if (data) + grub_free (data->label); + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +static grub_err_t +grub_sfs_label (grub_device_t device, char **label) +{ + struct grub_sfs_data *data; + grub_disk_t disk = device->disk; + + data = grub_sfs_mount (disk); + if (data) + { + grub_size_t sz, len = grub_strlen (data->label); + + if (grub_mul (len, GRUB_MAX_UTF8_PER_LATIN1, &sz) || + grub_add (sz, 1, &sz)) + return GRUB_ERR_OUT_OF_RANGE; + + *label = grub_malloc (sz); + if (*label) + *grub_latin1_to_utf8 ((grub_uint8_t *) *label, + (const grub_uint8_t *) data->label, + len) = '\0'; + grub_free (data->label); + } + grub_free (data); + + return grub_errno; +} + + +static struct grub_fs grub_sfs_fs = + { + .name = "sfs", + .fs_dir = grub_sfs_dir, + .fs_open = grub_sfs_open, + .fs_read = grub_sfs_read, + .fs_close = grub_sfs_close, + .fs_label = grub_sfs_label, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(sfs) +{ + grub_fs_register (&grub_sfs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(sfs) +{ + grub_fs_unregister (&grub_sfs_fs); +} diff --git a/grub-core/fs/squash4.c b/grub-core/fs/squash4.c new file mode 100644 index 0000000..6dd731e --- /dev/null +++ b/grub-core/fs/squash4.c @@ -0,0 +1,1042 @@ +/* squash4.c - SquashFS */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010 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/deflate.h> +#include <grub/safemath.h> +#include <minilzo.h> + +#include "xz.h" +#include "xz_stream.h" + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* + object format Pointed by + superblock RAW Fixed offset (0) + data RAW ? Fixed offset (60) + inode table Chunk superblock + dir table Chunk superblock + fragment table Chunk unk1 + unk1 RAW, Chunk superblock + unk2 RAW superblock + UID/GID Chunk exttblptr + exttblptr RAW superblock + + UID/GID table is the array ot uint32_t + unk1 contains pointer to fragment table followed by some chunk. + unk2 containts one uint64_t +*/ + +struct grub_squash_super +{ + grub_uint32_t magic; +#define SQUASH_MAGIC 0x73717368 + grub_uint32_t dummy1; + grub_uint32_t creation_time; + grub_uint32_t block_size; + grub_uint32_t dummy2; + grub_uint16_t compression; + grub_uint16_t dummy3; + grub_uint64_t dummy4; + grub_uint16_t root_ino_offset; + grub_uint32_t root_ino_chunk; + grub_uint16_t dummy5; + grub_uint64_t total_size; + grub_uint64_t exttbloffset; + grub_uint64_t dummy6; + grub_uint64_t inodeoffset; + grub_uint64_t diroffset; + grub_uint64_t unk1offset; + grub_uint64_t unk2offset; +} GRUB_PACKED; + +/* Chunk-based */ +struct grub_squash_inode +{ + /* Same values as direlem types. */ + grub_uint16_t type; + grub_uint16_t dummy[3]; + grub_uint32_t mtime; + grub_uint32_t dummy2; + union + { + struct { + grub_uint32_t chunk; + grub_uint32_t fragment; + grub_uint32_t offset; + grub_uint32_t size; + grub_uint32_t block_size[0]; + } GRUB_PACKED file; + struct { + grub_uint64_t chunk; + grub_uint64_t size; + grub_uint32_t dummy1[3]; + grub_uint32_t fragment; + grub_uint32_t offset; + grub_uint32_t dummy3; + grub_uint32_t block_size[0]; + } GRUB_PACKED long_file; + struct { + grub_uint32_t chunk; + grub_uint32_t dummy; + grub_uint16_t size; + grub_uint16_t offset; + } GRUB_PACKED dir; + struct { + grub_uint32_t dummy1; + grub_uint32_t size; + grub_uint32_t chunk; + grub_uint32_t dummy2; + grub_uint16_t dummy3; + grub_uint16_t offset; + } GRUB_PACKED long_dir; + struct { + grub_uint32_t dummy; + grub_uint32_t namelen; + char name[0]; + } GRUB_PACKED symlink; + } GRUB_PACKED; +} GRUB_PACKED; + +struct grub_squash_cache_inode +{ + struct grub_squash_inode ino; + grub_disk_addr_t ino_chunk; + grub_uint16_t ino_offset; + grub_uint32_t *block_sizes; + grub_disk_addr_t *cumulated_block_sizes; +}; + +/* Chunk-based. */ +struct grub_squash_dirent_header +{ + /* Actually the value is the number of elements - 1. */ + grub_uint32_t nelems; + grub_uint32_t ino_chunk; + grub_uint32_t dummy; +} GRUB_PACKED; + +struct grub_squash_dirent +{ + grub_uint16_t ino_offset; + grub_uint16_t dummy; + grub_uint16_t type; + /* Actually the value is the length of name - 1. */ + grub_uint16_t namelen; + char name[0]; +} GRUB_PACKED; + +enum + { + SQUASH_TYPE_DIR = 1, + SQUASH_TYPE_REGULAR = 2, + SQUASH_TYPE_SYMLINK = 3, + SQUASH_TYPE_LONG_DIR = 8, + SQUASH_TYPE_LONG_REGULAR = 9, + }; + + +struct grub_squash_frag_desc +{ + grub_uint64_t offset; + grub_uint32_t size; + grub_uint32_t dummy; +} GRUB_PACKED; + +enum + { + SQUASH_CHUNK_FLAGS = 0x8000, + SQUASH_CHUNK_UNCOMPRESSED = 0x8000 + }; + +enum + { + SQUASH_BLOCK_FLAGS = 0x1000000, + SQUASH_BLOCK_UNCOMPRESSED = 0x1000000 + }; + +enum + { + COMPRESSION_ZLIB = 1, + COMPRESSION_LZO = 3, + COMPRESSION_XZ = 4, + }; + + +#define SQUASH_CHUNK_SIZE 0x2000 +#define XZBUFSIZ 0x2000 + +struct grub_squash_data +{ + grub_disk_t disk; + struct grub_squash_super sb; + struct grub_squash_cache_inode ino; + grub_uint64_t fragments; + int log2_blksz; + grub_size_t blksz; + grub_ssize_t (*decompress) (char *inbuf, grub_size_t insize, grub_off_t off, + char *outbuf, grub_size_t outsize, + struct grub_squash_data *data); + struct xz_dec *xzdec; + char *xzbuf; +}; + +struct grub_fshelp_node +{ + struct grub_squash_data *data; + struct grub_squash_inode ino; + grub_size_t stsize; + struct + { + grub_disk_addr_t ino_chunk; + grub_uint16_t ino_offset; + } stack[1]; +}; + +static grub_err_t +read_chunk (struct grub_squash_data *data, void *buf, grub_size_t len, + grub_uint64_t chunk_start, grub_off_t offset) +{ + while (len > 0) + { + grub_uint64_t csize; + grub_uint16_t d; + grub_err_t err; + while (1) + { + err = grub_disk_read (data->disk, + chunk_start >> GRUB_DISK_SECTOR_BITS, + chunk_start & (GRUB_DISK_SECTOR_SIZE - 1), + sizeof (d), &d); + if (err) + return err; + if (offset < SQUASH_CHUNK_SIZE) + break; + offset -= SQUASH_CHUNK_SIZE; + chunk_start += 2 + (grub_le_to_cpu16 (d) & ~SQUASH_CHUNK_FLAGS); + } + + csize = SQUASH_CHUNK_SIZE - offset; + if (csize > len) + csize = len; + + if (grub_le_to_cpu16 (d) & SQUASH_CHUNK_UNCOMPRESSED) + { + grub_disk_addr_t a = chunk_start + 2 + offset; + err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS), + a & (GRUB_DISK_SECTOR_SIZE - 1), + csize, buf); + if (err) + return err; + } + else + { + char *tmp; + grub_size_t bsize = grub_le_to_cpu16 (d) & ~SQUASH_CHUNK_FLAGS; + grub_disk_addr_t a = chunk_start + 2; + tmp = grub_malloc (bsize); + if (!tmp) + return grub_errno; + /* FIXME: buffer uncompressed data. */ + err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS), + a & (GRUB_DISK_SECTOR_SIZE - 1), + bsize, tmp); + if (err) + { + grub_free (tmp); + return err; + } + + if (data->decompress (tmp, bsize, offset, + buf, csize, data) < 0) + { + grub_free (tmp); + return grub_errno; + } + grub_free (tmp); + } + len -= csize; + offset += csize; + buf = (char *) buf + csize; + } + return GRUB_ERR_NONE; +} + +static grub_ssize_t +zlib_decompress (char *inbuf, grub_size_t insize, grub_off_t off, + char *outbuf, grub_size_t outsize, + struct grub_squash_data *data __attribute__ ((unused))) +{ + return grub_zlib_decompress (inbuf, insize, off, outbuf, outsize); +} + +static grub_ssize_t +lzo_decompress (char *inbuf, grub_size_t insize, grub_off_t off, + char *outbuf, grub_size_t len, struct grub_squash_data *data) +{ + lzo_uint usize = data->blksz; + grub_uint8_t *udata; + + if (usize < 8192) + usize = 8192; + + udata = grub_malloc (usize); + if (!udata) + return -1; + + if (lzo1x_decompress_safe ((grub_uint8_t *) inbuf, + insize, udata, &usize, NULL) != LZO_E_OK) + { + grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk"); + grub_free (udata); + return -1; + } + grub_memcpy (outbuf, udata + off, len); + grub_free (udata); + return len; +} + +static grub_ssize_t +xz_decompress (char *inbuf, grub_size_t insize, grub_off_t off, + char *outbuf, grub_size_t len, struct grub_squash_data *data) +{ + grub_size_t ret = 0; + grub_off_t pos = 0; + struct xz_buf buf; + + xz_dec_reset (data->xzdec); + buf.in = (grub_uint8_t *) inbuf; + buf.in_pos = 0; + buf.in_size = insize; + buf.out = (grub_uint8_t *) data->xzbuf; + buf.out_pos = 0; + buf.out_size = XZBUFSIZ; + + while (len) + { + enum xz_ret xzret; + + buf.out_pos = 0; + + xzret = xz_dec_run (data->xzdec, &buf); + + if (xzret != XZ_OK && xzret != XZ_STREAM_END) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "invalid xz chunk"); + return -1; + } + if (pos + buf.out_pos >= off) + { + grub_ssize_t outoff = pos - off; + grub_size_t l; + if (outoff >= 0) + { + l = buf.out_pos; + if (l > len) + l = len; + grub_memcpy (outbuf + outoff, buf.out, l); + } + else + { + outoff = -outoff; + l = buf.out_pos - outoff; + if (l > len) + l = len; + grub_memcpy (outbuf, buf.out + outoff, l); + } + ret += l; + len -= l; + } + pos += buf.out_pos; + if (xzret == XZ_STREAM_END) + break; + } + return ret; +} + +static struct grub_squash_data * +squash_mount (grub_disk_t disk) +{ + struct grub_squash_super sb; + grub_err_t err; + struct grub_squash_data *data; + grub_uint64_t frag; + + err = grub_disk_read (disk, 0, 0, sizeof (sb), &sb); + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a squash4"); + if (err) + return NULL; + if (sb.magic != grub_cpu_to_le32_compile_time (SQUASH_MAGIC) + || sb.block_size == 0 + || ((sb.block_size - 1) & sb.block_size)) + { + grub_error (GRUB_ERR_BAD_FS, "not squash4"); + return NULL; + } + + err = grub_disk_read (disk, + grub_le_to_cpu64 (sb.unk1offset) + >> GRUB_DISK_SECTOR_BITS, + grub_le_to_cpu64 (sb.unk1offset) + & (GRUB_DISK_SECTOR_SIZE - 1), sizeof (frag), &frag); + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a squash4"); + if (err) + return NULL; + + data = grub_zalloc (sizeof (*data)); + if (!data) + return NULL; + data->sb = sb; + data->disk = disk; + data->fragments = grub_le_to_cpu64 (frag); + + switch (sb.compression) + { + case grub_cpu_to_le16_compile_time (COMPRESSION_ZLIB): + data->decompress = zlib_decompress; + break; + case grub_cpu_to_le16_compile_time (COMPRESSION_LZO): + data->decompress = lzo_decompress; + break; + case grub_cpu_to_le16_compile_time (COMPRESSION_XZ): + data->decompress = xz_decompress; + data->xzbuf = grub_malloc (XZBUFSIZ); + if (!data->xzbuf) + { + grub_free (data); + return NULL; + } + data->xzdec = xz_dec_init (1 << 16); + if (!data->xzdec) + { + grub_free (data->xzbuf); + grub_free (data); + return NULL; + } + break; + default: + grub_free (data); + grub_error (GRUB_ERR_BAD_FS, "unsupported compression %d", + grub_le_to_cpu16 (sb.compression)); + return NULL; + } + + data->blksz = grub_le_to_cpu32 (data->sb.block_size); + for (data->log2_blksz = 0; + (1U << data->log2_blksz) < data->blksz; + data->log2_blksz++); + + return data; +} + +static char * +grub_squash_read_symlink (grub_fshelp_node_t node) +{ + char *ret; + grub_err_t err; + grub_size_t sz; + + if (grub_add (grub_le_to_cpu32 (node->ino.symlink.namelen), 1, &sz)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected")); + return NULL; + } + + ret = grub_malloc (sz); + if (!ret) + return NULL; + + err = read_chunk (node->data, ret, + grub_le_to_cpu32 (node->ino.symlink.namelen), + grub_le_to_cpu64 (node->data->sb.inodeoffset) + + node->stack[node->stsize - 1].ino_chunk, + node->stack[node->stsize - 1].ino_offset + + (node->ino.symlink.name - (char *) &node->ino)); + if (err) + { + grub_free (ret); + return NULL; + } + ret[grub_le_to_cpu32 (node->ino.symlink.namelen)] = 0; + return ret; +} + +static int +grub_squash_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + grub_uint32_t off; + grub_uint32_t endoff; + grub_uint64_t chunk; + unsigned i; + + /* FIXME: why - 3 ? */ + switch (dir->ino.type) + { + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_DIR): + off = grub_le_to_cpu16 (dir->ino.dir.offset); + endoff = grub_le_to_cpu16 (dir->ino.dir.size) + off - 3; + chunk = grub_le_to_cpu32 (dir->ino.dir.chunk); + break; + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_DIR): + off = grub_le_to_cpu16 (dir->ino.long_dir.offset); + endoff = grub_le_to_cpu16 (dir->ino.long_dir.size) + off - 3; + chunk = grub_le_to_cpu32 (dir->ino.long_dir.chunk); + break; + default: + grub_error (GRUB_ERR_BAD_FS, "unexpected ino type 0x%x", + grub_le_to_cpu16 (dir->ino.type)); + return 0; + } + + { + grub_fshelp_node_t node; + grub_size_t sz; + + if (grub_mul (dir->stsize, sizeof (dir->stack[0]), &sz) || + grub_add (sz, sizeof (*node), &sz)) + return 0; + + node = grub_malloc (sz); + if (!node) + return 0; + grub_memcpy (node, dir, sz); + if (hook (".", GRUB_FSHELP_DIR, node, hook_data)) + return 1; + + if (dir->stsize != 1) + { + grub_err_t err; + + if (grub_mul (dir->stsize, sizeof (dir->stack[0]), &sz) || + grub_add (sz, sizeof (*node), &sz)) + return 0; + + node = grub_malloc (sz); + if (!node) + return 0; + + grub_memcpy (node, dir, sz); + + node->stsize--; + err = read_chunk (dir->data, &node->ino, sizeof (node->ino), + grub_le_to_cpu64 (dir->data->sb.inodeoffset) + + node->stack[node->stsize - 1].ino_chunk, + node->stack[node->stsize - 1].ino_offset); + if (err) + return 0; + + if (hook ("..", GRUB_FSHELP_DIR, node, hook_data)) + return 1; + } + } + + while (off < endoff) + { + struct grub_squash_dirent_header dh; + grub_err_t err; + + err = read_chunk (dir->data, &dh, sizeof (dh), + grub_le_to_cpu64 (dir->data->sb.diroffset) + + chunk, off); + if (err) + return 0; + off += sizeof (dh); + for (i = 0; i < (unsigned) grub_le_to_cpu32 (dh.nelems) + 1; i++) + { + char *buf; + int r; + struct grub_fshelp_node *node; + enum grub_fshelp_filetype filetype = GRUB_FSHELP_REG; + struct grub_squash_dirent di; + struct grub_squash_inode ino; + grub_size_t sz; + + err = read_chunk (dir->data, &di, sizeof (di), + grub_le_to_cpu64 (dir->data->sb.diroffset) + + chunk, off); + if (err) + return 0; + off += sizeof (di); + + err = read_chunk (dir->data, &ino, sizeof (ino), + grub_le_to_cpu64 (dir->data->sb.inodeoffset) + + grub_le_to_cpu32 (dh.ino_chunk), + grub_cpu_to_le16 (di.ino_offset)); + if (err) + return 0; + + buf = grub_malloc (grub_le_to_cpu16 (di.namelen) + 2); + if (!buf) + return 0; + err = read_chunk (dir->data, buf, + grub_le_to_cpu16 (di.namelen) + 1, + grub_le_to_cpu64 (dir->data->sb.diroffset) + + chunk, off); + if (err) + return 0; + + off += grub_le_to_cpu16 (di.namelen) + 1; + buf[grub_le_to_cpu16 (di.namelen) + 1] = 0; + if (grub_le_to_cpu16 (di.type) == SQUASH_TYPE_DIR) + filetype = GRUB_FSHELP_DIR; + if (grub_le_to_cpu16 (di.type) == SQUASH_TYPE_SYMLINK) + filetype = GRUB_FSHELP_SYMLINK; + + if (grub_add (dir->stsize, 1, &sz) || + grub_mul (sz, sizeof (dir->stack[0]), &sz) || + grub_add (sz, sizeof (*node), &sz)) + return 0; + + node = grub_malloc (sz); + if (! node) + return 0; + + grub_memcpy (node, dir, sz - sizeof(dir->stack[0])); + + node->ino = ino; + node->stack[node->stsize].ino_chunk = grub_le_to_cpu32 (dh.ino_chunk); + node->stack[node->stsize].ino_offset = grub_le_to_cpu16 (di.ino_offset); + node->stsize++; + r = hook (buf, filetype, node, hook_data); + + grub_free (buf); + if (r) + return r; + } + } + return 0; +} + +static grub_err_t +make_root_node (struct grub_squash_data *data, struct grub_fshelp_node *root) +{ + grub_memset (root, 0, sizeof (*root)); + root->data = data; + root->stsize = 1; + root->stack[0].ino_chunk = grub_le_to_cpu32 (data->sb.root_ino_chunk); + root->stack[0].ino_offset = grub_cpu_to_le16 (data->sb.root_ino_offset); + return read_chunk (data, &root->ino, sizeof (root->ino), + grub_le_to_cpu64 (data->sb.inodeoffset) + + root->stack[0].ino_chunk, + root->stack[0].ino_offset); +} + +static void +squash_unmount (struct grub_squash_data *data) +{ + if (data->xzdec) + xz_dec_end (data->xzdec); + grub_free (data->xzbuf); + grub_free (data->ino.cumulated_block_sizes); + grub_free (data->ino.block_sizes); + grub_free (data); +} + + +/* Context for grub_squash_dir. */ +struct grub_squash_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_squash_dir. */ +static int +grub_squash_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_squash_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 = grub_le_to_cpu32 (node->ino.mtime); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_squash_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_squash_dir_ctx ctx = { hook, hook_data }; + struct grub_squash_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + struct grub_fshelp_node root; + grub_err_t err; + + data = squash_mount (device->disk); + if (! data) + return grub_errno; + + err = make_root_node (data, &root); + if (err) + return err; + + grub_fshelp_find_file (path, &root, &fdiro, grub_squash_iterate_dir, + grub_squash_read_symlink, GRUB_FSHELP_DIR); + if (!grub_errno) + grub_squash_iterate_dir (fdiro, grub_squash_dir_iter, &ctx); + + squash_unmount (data); + + return grub_errno; +} + +static grub_err_t +grub_squash_open (struct grub_file *file, const char *name) +{ + struct grub_squash_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + struct grub_fshelp_node root; + grub_err_t err; + + data = squash_mount (file->device->disk); + if (! data) + return grub_errno; + + err = make_root_node (data, &root); + if (err) + return err; + + grub_fshelp_find_file (name, &root, &fdiro, grub_squash_iterate_dir, + grub_squash_read_symlink, GRUB_FSHELP_REG); + if (grub_errno) + { + squash_unmount (data); + return grub_errno; + } + + file->data = data; + data->ino.ino = fdiro->ino; + data->ino.block_sizes = NULL; + data->ino.cumulated_block_sizes = NULL; + data->ino.ino_chunk = fdiro->stack[fdiro->stsize - 1].ino_chunk; + data->ino.ino_offset = fdiro->stack[fdiro->stsize - 1].ino_offset; + + switch (fdiro->ino.type) + { + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): + file->size = grub_le_to_cpu64 (fdiro->ino.long_file.size); + break; + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): + file->size = grub_le_to_cpu32 (fdiro->ino.file.size); + break; + default: + { + grub_uint16_t type = grub_le_to_cpu16 (fdiro->ino.type); + grub_free (fdiro); + squash_unmount (data); + return grub_error (GRUB_ERR_BAD_FS, "unexpected ino type 0x%x", type); + } + } + + grub_free (fdiro); + + return GRUB_ERR_NONE; +} + +static grub_ssize_t +direct_read (struct grub_squash_data *data, + struct grub_squash_cache_inode *ino, + grub_off_t off, char *buf, grub_size_t len) +{ + grub_err_t err = GRUB_ERR_NONE; + grub_off_t cumulated_uncompressed_size = 0; + grub_uint64_t a = 0; + grub_size_t i; + grub_size_t origlen = len; + + switch (ino->ino.type) + { + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): + a = grub_le_to_cpu64 (ino->ino.long_file.chunk); + break; + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): + a = grub_le_to_cpu32 (ino->ino.file.chunk); + break; + } + + if (!ino->block_sizes) + { + grub_off_t total_size = 0; + grub_size_t total_blocks; + grub_size_t block_offset = 0; + switch (ino->ino.type) + { + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): + total_size = grub_le_to_cpu64 (ino->ino.long_file.size); + block_offset = ((char *) &ino->ino.long_file.block_size + - (char *) &ino->ino); + break; + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): + total_size = grub_le_to_cpu32 (ino->ino.file.size); + block_offset = ((char *) &ino->ino.file.block_size + - (char *) &ino->ino); + break; + } + total_blocks = ((total_size + data->blksz - 1) >> data->log2_blksz); + ino->block_sizes = grub_malloc (total_blocks + * sizeof (ino->block_sizes[0])); + ino->cumulated_block_sizes = grub_malloc (total_blocks + * sizeof (ino->cumulated_block_sizes[0])); + if (!ino->block_sizes || !ino->cumulated_block_sizes) + { + grub_free (ino->block_sizes); + grub_free (ino->cumulated_block_sizes); + ino->block_sizes = 0; + ino->cumulated_block_sizes = 0; + return -1; + } + err = read_chunk (data, ino->block_sizes, + total_blocks * sizeof (ino->block_sizes[0]), + grub_le_to_cpu64 (data->sb.inodeoffset) + + ino->ino_chunk, + ino->ino_offset + block_offset); + if (err) + { + grub_free (ino->block_sizes); + grub_free (ino->cumulated_block_sizes); + ino->block_sizes = 0; + ino->cumulated_block_sizes = 0; + return -1; + } + ino->cumulated_block_sizes[0] = 0; + for (i = 1; i < total_blocks; i++) + ino->cumulated_block_sizes[i] = ino->cumulated_block_sizes[i - 1] + + (grub_le_to_cpu32 (ino->block_sizes[i - 1]) & ~SQUASH_BLOCK_FLAGS); + } + + if (a == 0) + a = sizeof (struct grub_squash_super); + i = off >> data->log2_blksz; + cumulated_uncompressed_size = data->blksz * (grub_disk_addr_t) i; + while (cumulated_uncompressed_size < off + len) + { + grub_size_t boff, curread; + boff = off - cumulated_uncompressed_size; + curread = data->blksz - boff; + if (curread > len) + curread = len; + if (!ino->block_sizes[i]) + { + /* Sparse block */ + grub_memset (buf, '\0', curread); + } + else if (!(ino->block_sizes[i] + & grub_cpu_to_le32_compile_time (SQUASH_BLOCK_UNCOMPRESSED))) + { + char *block; + grub_size_t csize; + csize = grub_le_to_cpu32 (ino->block_sizes[i]) & ~SQUASH_BLOCK_FLAGS; + block = grub_malloc (csize); + if (!block) + return -1; + err = grub_disk_read (data->disk, + (ino->cumulated_block_sizes[i] + a) + >> GRUB_DISK_SECTOR_BITS, + (ino->cumulated_block_sizes[i] + a) + & (GRUB_DISK_SECTOR_SIZE - 1), + csize, block); + if (err) + { + grub_free (block); + return -1; + } + if (data->decompress (block, csize, boff, buf, curread, data) + != (grub_ssize_t) curread) + { + grub_free (block); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk"); + return -1; + } + grub_free (block); + } + else + err = grub_disk_read (data->disk, + (ino->cumulated_block_sizes[i] + a + boff) + >> GRUB_DISK_SECTOR_BITS, + (ino->cumulated_block_sizes[i] + a + boff) + & (GRUB_DISK_SECTOR_SIZE - 1), + curread, buf); + if (err) + return -1; + off += curread; + len -= curread; + buf += curread; + cumulated_uncompressed_size += grub_le_to_cpu32 (data->sb.block_size); + i++; + } + return origlen; +} + + +static grub_ssize_t +grub_squash_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_squash_data *data = file->data; + struct grub_squash_cache_inode *ino = &data->ino; + grub_off_t off = file->offset; + grub_err_t err; + grub_uint64_t a, b; + grub_uint32_t fragment = 0; + int compressed = 0; + struct grub_squash_frag_desc frag; + grub_off_t direct_len; + grub_uint64_t mask = grub_le_to_cpu32 (data->sb.block_size) - 1; + grub_size_t orig_len = len; + + switch (ino->ino.type) + { + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): + fragment = grub_le_to_cpu32 (ino->ino.long_file.fragment); + break; + case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): + fragment = grub_le_to_cpu32 (ino->ino.file.fragment); + break; + } + + /* Squash may pack file tail as fragment. So read initial part directly and + get tail from fragments */ + direct_len = fragment == 0xffffffff ? file->size : file->size & ~mask; + if (off < direct_len) + { + grub_size_t read_len = direct_len - off; + grub_ssize_t res; + + if (read_len > len) + read_len = len; + res = direct_read (data, ino, off, buf, read_len); + if ((grub_size_t) res != read_len) + return -1; /* FIXME: is short read possible here? */ + len -= read_len; + if (!len) + return read_len; + buf += read_len; + off = 0; + } + else + off -= direct_len; + + err = read_chunk (data, &frag, sizeof (frag), + data->fragments, sizeof (frag) * fragment); + if (err) + return -1; + a = grub_le_to_cpu64 (frag.offset); + compressed = !(frag.size & grub_cpu_to_le32_compile_time (SQUASH_BLOCK_UNCOMPRESSED)); + if (ino->ino.type == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR)) + b = grub_le_to_cpu32 (ino->ino.long_file.offset) + off; + else + b = grub_le_to_cpu32 (ino->ino.file.offset) + off; + + /* FIXME: cache uncompressed chunks. */ + if (compressed) + { + char *block; + block = grub_malloc (grub_le_to_cpu32 (frag.size)); + if (!block) + return -1; + err = grub_disk_read (data->disk, + a >> GRUB_DISK_SECTOR_BITS, + a & (GRUB_DISK_SECTOR_SIZE - 1), + grub_le_to_cpu32 (frag.size), block); + if (err) + { + grub_free (block); + return -1; + } + if (data->decompress (block, grub_le_to_cpu32 (frag.size), + b, buf, len, data) + != (grub_ssize_t) len) + { + grub_free (block); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk"); + return -1; + } + grub_free (block); + } + else + { + err = grub_disk_read (data->disk, (a + b) >> GRUB_DISK_SECTOR_BITS, + (a + b) & (GRUB_DISK_SECTOR_SIZE - 1), len, buf); + if (err) + return -1; + } + return orig_len; +} + +static grub_err_t +grub_squash_close (grub_file_t file) +{ + squash_unmount (file->data); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_squash_mtime (grub_device_t dev, grub_int64_t *tm) +{ + struct grub_squash_data *data = 0; + + data = squash_mount (dev->disk); + if (! data) + return grub_errno; + *tm = grub_le_to_cpu32 (data->sb.creation_time); + squash_unmount (data); + return GRUB_ERR_NONE; +} + +static struct grub_fs grub_squash_fs = + { + .name = "squash4", + .fs_dir = grub_squash_dir, + .fs_open = grub_squash_open, + .fs_read = grub_squash_read, + .fs_close = grub_squash_close, + .fs_mtime = grub_squash_mtime, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, + .blocklist_install = 0, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(squash4) +{ + grub_fs_register (&grub_squash_fs); +} + +GRUB_MOD_FINI(squash4) +{ + grub_fs_unregister (&grub_squash_fs); +} + diff --git a/grub-core/fs/tar.c b/grub-core/fs/tar.c new file mode 100644 index 0000000..c551ed6 --- /dev/null +++ b/grub-core/fs/tar.c @@ -0,0 +1,345 @@ +/* cpio.c - cpio and tar filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009,2013 Free Software Foundation, Inc. + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> +#include <grub/disk.h> +#include <grub/archelp.h> + +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/dl.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* tar support */ +#define MAGIC "ustar" +struct head +{ + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char typeflag; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; +} GRUB_PACKED; + +static inline unsigned long long +read_number (const char *str, grub_size_t size) +{ + unsigned long long ret = 0; + while (size-- && *str >= '0' && *str <= '7') + ret = (ret << 3) | (*str++ & 0xf); + return ret; +} + +struct grub_archelp_data +{ + grub_disk_t disk; + grub_off_t hofs, next_hofs; + grub_off_t dofs; + grub_off_t size; + char *linkname; + grub_size_t linkname_alloc; +}; + +static grub_err_t +grub_cpio_find_file (struct grub_archelp_data *data, char **name, + grub_int32_t *mtime, + grub_uint32_t *mode) +{ + struct head hd; + int reread = 0, have_longname = 0, have_longlink = 0; + + data->hofs = data->next_hofs; + + for (reread = 0; reread < 3; reread++) + { + if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd)) + return grub_errno; + + if (!hd.name[0] && !hd.prefix[0]) + { + *mode = GRUB_ARCHELP_ATTR_END; + return GRUB_ERR_NONE; + } + + if (grub_memcmp (hd.magic, MAGIC, sizeof (MAGIC) - 1)) + return grub_error (GRUB_ERR_BAD_FS, "invalid tar archive"); + + if (hd.typeflag == 'L') + { + grub_err_t err; + grub_size_t namesize = read_number (hd.size, sizeof (hd.size)); + *name = grub_malloc (namesize + 1); + if (*name == NULL) + return grub_errno; + err = grub_disk_read (data->disk, 0, + data->hofs + GRUB_DISK_SECTOR_SIZE, namesize, + *name); + (*name)[namesize] = 0; + if (err) + return err; + data->hofs += GRUB_DISK_SECTOR_SIZE + + ((namesize + GRUB_DISK_SECTOR_SIZE - 1) & + ~(GRUB_DISK_SECTOR_SIZE - 1)); + have_longname = 1; + continue; + } + + if (hd.typeflag == 'K') + { + grub_err_t err; + grub_size_t linksize = read_number (hd.size, sizeof (hd.size)); + if (data->linkname_alloc < linksize + 1) + { + char *n; + n = grub_calloc (2, linksize + 1); + if (!n) + return grub_errno; + grub_free (data->linkname); + data->linkname = n; + data->linkname_alloc = 2 * (linksize + 1); + } + + err = grub_disk_read (data->disk, 0, + data->hofs + GRUB_DISK_SECTOR_SIZE, linksize, + data->linkname); + if (err) + return err; + data->linkname[linksize] = 0; + data->hofs += GRUB_DISK_SECTOR_SIZE + + ((linksize + GRUB_DISK_SECTOR_SIZE - 1) & + ~(GRUB_DISK_SECTOR_SIZE - 1)); + have_longlink = 1; + continue; + } + + if (!have_longname) + { + grub_size_t extra_size = 0; + + while (extra_size < sizeof (hd.prefix) + && hd.prefix[extra_size]) + extra_size++; + *name = grub_malloc (sizeof (hd.name) + extra_size + 2); + if (*name == NULL) + return grub_errno; + if (hd.prefix[0]) + { + grub_memcpy (*name, hd.prefix, extra_size); + (*name)[extra_size++] = '/'; + } + grub_memcpy (*name + extra_size, hd.name, sizeof (hd.name)); + (*name)[extra_size + sizeof (hd.name)] = 0; + } + + data->size = read_number (hd.size, sizeof (hd.size)); + data->dofs = data->hofs + GRUB_DISK_SECTOR_SIZE; + data->next_hofs = data->dofs + ((data->size + GRUB_DISK_SECTOR_SIZE - 1) & + ~(GRUB_DISK_SECTOR_SIZE - 1)); + if (mtime) + *mtime = read_number (hd.mtime, sizeof (hd.mtime)); + if (mode) + { + *mode = read_number (hd.mode, sizeof (hd.mode)); + switch (hd.typeflag) + { + /* Hardlink. */ + case '1': + /* Symlink. */ + case '2': + *mode |= GRUB_ARCHELP_ATTR_LNK; + break; + case '0': + *mode |= GRUB_ARCHELP_ATTR_FILE; + break; + case '5': + *mode |= GRUB_ARCHELP_ATTR_DIR; + break; + } + } + if (!have_longlink) + { + if (data->linkname_alloc < 101) + { + char *n; + n = grub_malloc (101); + if (!n) + return grub_errno; + grub_free (data->linkname); + data->linkname = n; + data->linkname_alloc = 101; + } + grub_memcpy (data->linkname, hd.linkname, sizeof (hd.linkname)); + data->linkname[100] = 0; + } + return GRUB_ERR_NONE; + } + return GRUB_ERR_NONE; +} + +static char * +grub_cpio_get_link_target (struct grub_archelp_data *data) +{ + return grub_strdup (data->linkname); +} + +static void +grub_cpio_rewind (struct grub_archelp_data *data) +{ + data->next_hofs = 0; +} + +static struct grub_archelp_ops arcops = + { + .find_file = grub_cpio_find_file, + .get_link_target = grub_cpio_get_link_target, + .rewind = grub_cpio_rewind + }; + +static struct grub_archelp_data * +grub_cpio_mount (grub_disk_t disk) +{ + struct head hd; + struct grub_archelp_data *data; + + if (grub_disk_read (disk, 0, 0, sizeof (hd), &hd)) + goto fail; + + if (grub_memcmp (hd.magic, MAGIC, sizeof (MAGIC) - 1)) + goto fail; + + data = (struct grub_archelp_data *) grub_zalloc (sizeof (*data)); + if (!data) + goto fail; + + data->disk = disk; + + return data; + +fail: + grub_error (GRUB_ERR_BAD_FS, "not a tarfs filesystem"); + return 0; +} + +static grub_err_t +grub_cpio_dir (grub_device_t device, const char *path_in, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_archelp_data *data; + grub_err_t err; + + data = grub_cpio_mount (device->disk); + if (!data) + return grub_errno; + + err = grub_archelp_dir (data, &arcops, + path_in, hook, hook_data); + + grub_free (data->linkname); + grub_free (data); + + return err; +} + +static grub_err_t +grub_cpio_open (grub_file_t file, const char *name_in) +{ + struct grub_archelp_data *data; + grub_err_t err; + + data = grub_cpio_mount (file->device->disk); + if (!data) + return grub_errno; + + err = grub_archelp_open (data, &arcops, name_in); + if (err) + { + grub_free (data->linkname); + grub_free (data); + } + else + { + file->data = data; + file->size = data->size; + } + return err; +} + +static grub_ssize_t +grub_cpio_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_archelp_data *data; + grub_ssize_t ret; + + data = file->data; + + data->disk->read_hook = file->read_hook; + data->disk->read_hook_data = file->read_hook_data; + ret = (grub_disk_read (data->disk, 0, data->dofs + file->offset, + len, buf)) ? -1 : (grub_ssize_t) len; + data->disk->read_hook = 0; + + return ret; +} + +static grub_err_t +grub_cpio_close (grub_file_t file) +{ + struct grub_archelp_data *data; + + data = file->data; + grub_free (data->linkname); + grub_free (data); + + return grub_errno; +} + +static struct grub_fs grub_cpio_fs = { + .name = "tarfs", + .fs_dir = grub_cpio_dir, + .fs_open = grub_cpio_open, + .fs_read = grub_cpio_read, + .fs_close = grub_cpio_close, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, + .blocklist_install = 0, +#endif +}; + +GRUB_MOD_INIT (tar) +{ + grub_fs_register (&grub_cpio_fs); +} + +GRUB_MOD_FINI (tar) +{ + grub_fs_unregister (&grub_cpio_fs); +} diff --git a/grub-core/fs/udf.c b/grub-core/fs/udf.c new file mode 100644 index 0000000..2ac5c1d --- /dev/null +++ b/grub-core/fs/udf.c @@ -0,0 +1,1392 @@ +/* udf.c - Universal Disk Format filesystem. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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/datetime.h> +#include <grub/udf.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_UDF_MAX_PDS 2 +#define GRUB_UDF_MAX_PMS 6 + +#define U16 grub_le_to_cpu16 +#define U32 grub_le_to_cpu32 +#define U64 grub_le_to_cpu64 + +#define GRUB_UDF_TAG_IDENT_PVD 0x0001 +#define GRUB_UDF_TAG_IDENT_AVDP 0x0002 +#define GRUB_UDF_TAG_IDENT_VDP 0x0003 +#define GRUB_UDF_TAG_IDENT_IUVD 0x0004 +#define GRUB_UDF_TAG_IDENT_PD 0x0005 +#define GRUB_UDF_TAG_IDENT_LVD 0x0006 +#define GRUB_UDF_TAG_IDENT_USD 0x0007 +#define GRUB_UDF_TAG_IDENT_TD 0x0008 +#define GRUB_UDF_TAG_IDENT_LVID 0x0009 + +#define GRUB_UDF_TAG_IDENT_FSD 0x0100 +#define GRUB_UDF_TAG_IDENT_FID 0x0101 +#define GRUB_UDF_TAG_IDENT_AED 0x0102 +#define GRUB_UDF_TAG_IDENT_IE 0x0103 +#define GRUB_UDF_TAG_IDENT_TE 0x0104 +#define GRUB_UDF_TAG_IDENT_FE 0x0105 +#define GRUB_UDF_TAG_IDENT_EAHD 0x0106 +#define GRUB_UDF_TAG_IDENT_USE 0x0107 +#define GRUB_UDF_TAG_IDENT_SBD 0x0108 +#define GRUB_UDF_TAG_IDENT_PIE 0x0109 +#define GRUB_UDF_TAG_IDENT_EFE 0x010A + +#define GRUB_UDF_ICBTAG_TYPE_UNDEF 0x00 +#define GRUB_UDF_ICBTAG_TYPE_USE 0x01 +#define GRUB_UDF_ICBTAG_TYPE_PIE 0x02 +#define GRUB_UDF_ICBTAG_TYPE_IE 0x03 +#define GRUB_UDF_ICBTAG_TYPE_DIRECTORY 0x04 +#define GRUB_UDF_ICBTAG_TYPE_REGULAR 0x05 +#define GRUB_UDF_ICBTAG_TYPE_BLOCK 0x06 +#define GRUB_UDF_ICBTAG_TYPE_CHAR 0x07 +#define GRUB_UDF_ICBTAG_TYPE_EA 0x08 +#define GRUB_UDF_ICBTAG_TYPE_FIFO 0x09 +#define GRUB_UDF_ICBTAG_TYPE_SOCKET 0x0A +#define GRUB_UDF_ICBTAG_TYPE_TE 0x0B +#define GRUB_UDF_ICBTAG_TYPE_SYMLINK 0x0C +#define GRUB_UDF_ICBTAG_TYPE_STREAMDIR 0x0D + +#define GRUB_UDF_ICBTAG_FLAG_AD_MASK 0x0007 +#define GRUB_UDF_ICBTAG_FLAG_AD_SHORT 0x0000 +#define GRUB_UDF_ICBTAG_FLAG_AD_LONG 0x0001 +#define GRUB_UDF_ICBTAG_FLAG_AD_EXT 0x0002 +#define GRUB_UDF_ICBTAG_FLAG_AD_IN_ICB 0x0003 + +#define GRUB_UDF_EXT_NORMAL 0x00000000 +#define GRUB_UDF_EXT_NREC_ALLOC 0x40000000 +#define GRUB_UDF_EXT_NREC_NALLOC 0x80000000 +#define GRUB_UDF_EXT_MASK 0xC0000000 + +#define GRUB_UDF_FID_CHAR_HIDDEN 0x01 +#define GRUB_UDF_FID_CHAR_DIRECTORY 0x02 +#define GRUB_UDF_FID_CHAR_DELETED 0x04 +#define GRUB_UDF_FID_CHAR_PARENT 0x08 +#define GRUB_UDF_FID_CHAR_METADATA 0x10 + +#define GRUB_UDF_STD_IDENT_BEA01 "BEA01" +#define GRUB_UDF_STD_IDENT_BOOT2 "BOOT2" +#define GRUB_UDF_STD_IDENT_CD001 "CD001" +#define GRUB_UDF_STD_IDENT_CDW02 "CDW02" +#define GRUB_UDF_STD_IDENT_NSR02 "NSR02" +#define GRUB_UDF_STD_IDENT_NSR03 "NSR03" +#define GRUB_UDF_STD_IDENT_TEA01 "TEA01" + +#define GRUB_UDF_CHARSPEC_TYPE_CS0 0x00 +#define GRUB_UDF_CHARSPEC_TYPE_CS1 0x01 +#define GRUB_UDF_CHARSPEC_TYPE_CS2 0x02 +#define GRUB_UDF_CHARSPEC_TYPE_CS3 0x03 +#define GRUB_UDF_CHARSPEC_TYPE_CS4 0x04 +#define GRUB_UDF_CHARSPEC_TYPE_CS5 0x05 +#define GRUB_UDF_CHARSPEC_TYPE_CS6 0x06 +#define GRUB_UDF_CHARSPEC_TYPE_CS7 0x07 +#define GRUB_UDF_CHARSPEC_TYPE_CS8 0x08 + +#define GRUB_UDF_PARTMAP_TYPE_1 1 +#define GRUB_UDF_PARTMAP_TYPE_2 2 + +struct grub_udf_lb_addr +{ + grub_uint32_t block_num; + grub_uint16_t part_ref; +} GRUB_PACKED; + +struct grub_udf_short_ad +{ + grub_uint32_t length; + grub_uint32_t position; +} GRUB_PACKED; + +struct grub_udf_long_ad +{ + grub_uint32_t length; + struct grub_udf_lb_addr block; + grub_uint8_t imp_use[6]; +} GRUB_PACKED; + +struct grub_udf_extent_ad +{ + grub_uint32_t length; + grub_uint32_t start; +} GRUB_PACKED; + +struct grub_udf_charspec +{ + grub_uint8_t charset_type; + grub_uint8_t charset_info[63]; +} GRUB_PACKED; + +struct grub_udf_timestamp +{ + grub_uint16_t type_and_timezone; + grub_uint16_t year; + grub_uint8_t month; + grub_uint8_t day; + grub_uint8_t hour; + grub_uint8_t minute; + grub_uint8_t second; + grub_uint8_t centi_seconds; + grub_uint8_t hundreds_of_micro_seconds; + grub_uint8_t micro_seconds; +} GRUB_PACKED; + +struct grub_udf_regid +{ + grub_uint8_t flags; + grub_uint8_t ident[23]; + grub_uint8_t ident_suffix[8]; +} GRUB_PACKED; + +struct grub_udf_tag +{ + grub_uint16_t tag_ident; + grub_uint16_t desc_version; + grub_uint8_t tag_checksum; + grub_uint8_t reserved; + grub_uint16_t tag_serial_number; + grub_uint16_t desc_crc; + grub_uint16_t desc_crc_length; + grub_uint32_t tag_location; +} GRUB_PACKED; + +struct grub_udf_fileset +{ + struct grub_udf_tag tag; + struct grub_udf_timestamp datetime; + grub_uint16_t interchange_level; + grub_uint16_t max_interchange_level; + grub_uint32_t charset_list; + grub_uint32_t max_charset_list; + grub_uint32_t fileset_num; + grub_uint32_t fileset_desc_num; + struct grub_udf_charspec vol_charset; + grub_uint8_t vol_ident[128]; + struct grub_udf_charspec fileset_charset; + grub_uint8_t fileset_ident[32]; + grub_uint8_t copyright_file_ident[32]; + grub_uint8_t abstract_file_ident[32]; + struct grub_udf_long_ad root_icb; + struct grub_udf_regid domain_ident; + struct grub_udf_long_ad next_ext; + struct grub_udf_long_ad streamdir_icb; +} GRUB_PACKED; + +struct grub_udf_icbtag +{ + grub_uint32_t prior_recorded_num_direct_entries; + grub_uint16_t strategy_type; + grub_uint16_t strategy_parameter; + grub_uint16_t num_entries; + grub_uint8_t reserved; + grub_uint8_t file_type; + struct grub_udf_lb_addr parent_idb; + grub_uint16_t flags; +} GRUB_PACKED; + +struct grub_udf_file_ident +{ + struct grub_udf_tag tag; + grub_uint16_t version_num; + grub_uint8_t characteristics; +#define MAX_FILE_IDENT_LENGTH 256 + grub_uint8_t file_ident_length; + struct grub_udf_long_ad icb; + grub_uint16_t imp_use_length; +} GRUB_PACKED; + +struct grub_udf_file_entry +{ + struct grub_udf_tag tag; + struct grub_udf_icbtag icbtag; + grub_uint32_t uid; + grub_uint32_t gid; + grub_uint32_t permissions; + grub_uint16_t link_count; + grub_uint8_t record_format; + grub_uint8_t record_display_attr; + grub_uint32_t record_length; + grub_uint64_t file_size; + grub_uint64_t blocks_recorded; + struct grub_udf_timestamp access_time; + struct grub_udf_timestamp modification_time; + struct grub_udf_timestamp attr_time; + grub_uint32_t checkpoint; + struct grub_udf_long_ad extended_attr_idb; + struct grub_udf_regid imp_ident; + grub_uint64_t unique_id; + grub_uint32_t ext_attr_length; + grub_uint32_t alloc_descs_length; + grub_uint8_t ext_attr[0]; +} GRUB_PACKED; + +struct grub_udf_extended_file_entry +{ + struct grub_udf_tag tag; + struct grub_udf_icbtag icbtag; + grub_uint32_t uid; + grub_uint32_t gid; + grub_uint32_t permissions; + grub_uint16_t link_count; + grub_uint8_t record_format; + grub_uint8_t record_display_attr; + grub_uint32_t record_length; + grub_uint64_t file_size; + grub_uint64_t object_size; + grub_uint64_t blocks_recorded; + struct grub_udf_timestamp access_time; + struct grub_udf_timestamp modification_time; + struct grub_udf_timestamp create_time; + struct grub_udf_timestamp attr_time; + grub_uint32_t checkpoint; + grub_uint32_t reserved; + struct grub_udf_long_ad extended_attr_icb; + struct grub_udf_long_ad streamdir_icb; + struct grub_udf_regid imp_ident; + grub_uint64_t unique_id; + grub_uint32_t ext_attr_length; + grub_uint32_t alloc_descs_length; + grub_uint8_t ext_attr[0]; +} GRUB_PACKED; + +struct grub_udf_vrs +{ + grub_uint8_t type; + grub_uint8_t magic[5]; + grub_uint8_t version; +} GRUB_PACKED; + +struct grub_udf_avdp +{ + struct grub_udf_tag tag; + struct grub_udf_extent_ad vds; +} GRUB_PACKED; + +struct grub_udf_pd +{ + struct grub_udf_tag tag; + grub_uint32_t seq_num; + grub_uint16_t flags; + grub_uint16_t part_num; + struct grub_udf_regid contents; + grub_uint8_t contents_use[128]; + grub_uint32_t access_type; + grub_uint32_t start; + grub_uint32_t length; +} GRUB_PACKED; + +struct grub_udf_partmap +{ + grub_uint8_t type; + grub_uint8_t length; + union + { + struct + { + grub_uint16_t seq_num; + grub_uint16_t part_num; + } type1; + + struct + { + grub_uint8_t ident[62]; + } type2; + }; +} GRUB_PACKED; + +struct grub_udf_pvd +{ + struct grub_udf_tag tag; + grub_uint32_t seq_num; + grub_uint32_t pvd_num; + grub_uint8_t ident[32]; + grub_uint16_t vol_seq_num; + grub_uint16_t max_vol_seq_num; + grub_uint16_t interchange_level; + grub_uint16_t max_interchange_level; + grub_uint32_t charset_list; + grub_uint32_t max_charset_list; + grub_uint8_t volset_ident[128]; + struct grub_udf_charspec desc_charset; + struct grub_udf_charspec expl_charset; + struct grub_udf_extent_ad vol_abstract; + struct grub_udf_extent_ad vol_copyright; + struct grub_udf_regid app_ident; + struct grub_udf_timestamp recording_time; + struct grub_udf_regid imp_ident; + grub_uint8_t imp_use[64]; + grub_uint32_t pred_vds_loc; + grub_uint16_t flags; + grub_uint8_t reserved[22]; +} GRUB_PACKED; + +struct grub_udf_lvd +{ + struct grub_udf_tag tag; + grub_uint32_t seq_num; + struct grub_udf_charspec charset; + grub_uint8_t ident[128]; + grub_uint32_t bsize; + struct grub_udf_regid domain_ident; + struct grub_udf_long_ad root_fileset; + grub_uint32_t map_table_length; + grub_uint32_t num_part_maps; + struct grub_udf_regid imp_ident; + grub_uint8_t imp_use[128]; + struct grub_udf_extent_ad integrity_seq_ext; + grub_uint8_t part_maps[1608]; +} GRUB_PACKED; + +struct grub_udf_aed +{ + struct grub_udf_tag tag; + grub_uint32_t prev_ae; + grub_uint32_t ae_len; +} GRUB_PACKED; + +struct grub_udf_data +{ + grub_disk_t disk; + struct grub_udf_pvd pvd; + struct grub_udf_lvd lvd; + struct grub_udf_pd pds[GRUB_UDF_MAX_PDS]; + struct grub_udf_partmap *pms[GRUB_UDF_MAX_PMS]; + struct grub_udf_long_ad root_icb; + int npd, npm, lbshift; +}; + +struct grub_fshelp_node +{ + struct grub_udf_data *data; + int part_ref; + union + { + struct grub_udf_file_entry fe; + struct grub_udf_extended_file_entry efe; + char raw[0]; + } block; +}; + +static inline grub_size_t +get_fshelp_size (struct grub_udf_data *data) +{ + struct grub_fshelp_node *x = NULL; + return sizeof (*x) + + (1 << (GRUB_DISK_SECTOR_BITS + + data->lbshift)) - sizeof (x->block); +} + +static grub_dl_t my_mod; + +static grub_uint32_t +grub_udf_get_block (struct grub_udf_data *data, + grub_uint16_t part_ref, grub_uint32_t block) +{ + part_ref = U16 (part_ref); + + if (part_ref >= data->npm) + { + grub_error (GRUB_ERR_BAD_FS, "invalid part ref"); + return 0; + } + + return (U32 (data->pds[data->pms[part_ref]->type1.part_num].start) + + U32 (block)); +} + +static grub_err_t +grub_udf_read_icb (struct grub_udf_data *data, + struct grub_udf_long_ad *icb, + struct grub_fshelp_node *node) +{ + grub_uint32_t block; + + block = grub_udf_get_block (data, + icb->block.part_ref, + icb->block.block_num); + + if (grub_errno) + return grub_errno; + + if (grub_disk_read (data->disk, block << data->lbshift, 0, + 1 << (GRUB_DISK_SECTOR_BITS + + data->lbshift), + &node->block)) + return grub_errno; + + if ((U16 (node->block.fe.tag.tag_ident) != GRUB_UDF_TAG_IDENT_FE) && + (U16 (node->block.fe.tag.tag_ident) != GRUB_UDF_TAG_IDENT_EFE)) + return grub_error (GRUB_ERR_BAD_FS, "invalid fe/efe descriptor"); + + node->part_ref = icb->block.part_ref; + node->data = data; + return 0; +} + +static grub_disk_addr_t +grub_udf_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +{ + char *buf = NULL; + char *ptr; + grub_ssize_t len; + grub_disk_addr_t filebytes; + + switch (U16 (node->block.fe.tag.tag_ident)) + { + case GRUB_UDF_TAG_IDENT_FE: + ptr = (char *) &node->block.fe.ext_attr[0] + U32 (node->block.fe.ext_attr_length); + len = U32 (node->block.fe.alloc_descs_length); + break; + + case GRUB_UDF_TAG_IDENT_EFE: + ptr = (char *) &node->block.efe.ext_attr[0] + U32 (node->block.efe.ext_attr_length); + len = U32 (node->block.efe.alloc_descs_length); + break; + + default: + grub_error (GRUB_ERR_BAD_FS, "invalid file entry"); + return 0; + } + + if ((U16 (node->block.fe.icbtag.flags) & GRUB_UDF_ICBTAG_FLAG_AD_MASK) + == GRUB_UDF_ICBTAG_FLAG_AD_SHORT) + { + struct grub_udf_short_ad *ad = (struct grub_udf_short_ad *) ptr; + + filebytes = fileblock * U32 (node->data->lvd.bsize); + while (len >= (grub_ssize_t) sizeof (struct grub_udf_short_ad)) + { + grub_uint32_t adlen = U32 (ad->length) & 0x3fffffff; + grub_uint32_t adtype = U32 (ad->length) >> 30; + if (adtype == 3) + { + struct grub_udf_aed *extension; + grub_disk_addr_t sec = grub_udf_get_block(node->data, + node->part_ref, + ad->position); + if (!buf) + { + buf = grub_malloc (U32 (node->data->lvd.bsize)); + if (!buf) + return 0; + } + if (grub_disk_read (node->data->disk, sec << node->data->lbshift, + 0, adlen, buf)) + goto fail; + + extension = (struct grub_udf_aed *) buf; + if (U16 (extension->tag.tag_ident) != GRUB_UDF_TAG_IDENT_AED) + { + grub_error (GRUB_ERR_BAD_FS, "invalid aed tag"); + goto fail; + } + + len = U32 (extension->ae_len); + ad = (struct grub_udf_short_ad *) + (buf + sizeof (struct grub_udf_aed)); + continue; + } + + if (filebytes < adlen) + { + grub_uint32_t ad_pos = ad->position; + grub_free (buf); + return ((U32 (ad_pos) & GRUB_UDF_EXT_MASK) ? 0 : + (grub_udf_get_block (node->data, node->part_ref, ad_pos) + + (filebytes >> (GRUB_DISK_SECTOR_BITS + + node->data->lbshift)))); + } + + filebytes -= adlen; + ad++; + len -= sizeof (struct grub_udf_short_ad); + } + } + else + { + struct grub_udf_long_ad *ad = (struct grub_udf_long_ad *) ptr; + + filebytes = fileblock * U32 (node->data->lvd.bsize); + while (len >= (grub_ssize_t) sizeof (struct grub_udf_long_ad)) + { + grub_uint32_t adlen = U32 (ad->length) & 0x3fffffff; + grub_uint32_t adtype = U32 (ad->length) >> 30; + if (adtype == 3) + { + struct grub_udf_aed *extension; + grub_disk_addr_t sec = grub_udf_get_block(node->data, + ad->block.part_ref, + ad->block.block_num); + if (!buf) + { + buf = grub_malloc (U32 (node->data->lvd.bsize)); + if (!buf) + return 0; + } + if (grub_disk_read (node->data->disk, sec << node->data->lbshift, + 0, adlen, buf)) + goto fail; + + extension = (struct grub_udf_aed *) buf; + if (U16 (extension->tag.tag_ident) != GRUB_UDF_TAG_IDENT_AED) + { + grub_error (GRUB_ERR_BAD_FS, "invalid aed tag"); + goto fail; + } + + len = U32 (extension->ae_len); + ad = (struct grub_udf_long_ad *) + (buf + sizeof (struct grub_udf_aed)); + continue; + } + + if (filebytes < adlen) + { + grub_uint32_t ad_block_num = ad->block.block_num; + grub_uint32_t ad_part_ref = ad->block.part_ref; + grub_free (buf); + return ((U32 (ad_block_num) & GRUB_UDF_EXT_MASK) ? 0 : + (grub_udf_get_block (node->data, ad_part_ref, + ad_block_num) + + (filebytes >> (GRUB_DISK_SECTOR_BITS + + node->data->lbshift)))); + } + + filebytes -= adlen; + ad++; + len -= sizeof (struct grub_udf_long_ad); + } + } + +fail: + grub_free (buf); + + return 0; +} + +static grub_ssize_t +grub_udf_read_file (grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + switch (U16 (node->block.fe.icbtag.flags) & GRUB_UDF_ICBTAG_FLAG_AD_MASK) + { + case GRUB_UDF_ICBTAG_FLAG_AD_IN_ICB: + { + char *ptr; + + ptr = ((U16 (node->block.fe.tag.tag_ident) == GRUB_UDF_TAG_IDENT_FE) ? + ((char *) &node->block.fe.ext_attr[0] + + U32 (node->block.fe.ext_attr_length)) : + ((char *) &node->block.efe.ext_attr[0] + + U32 (node->block.efe.ext_attr_length))); + + grub_memcpy (buf, ptr + pos, len); + + return len; + } + + case GRUB_UDF_ICBTAG_FLAG_AD_EXT: + grub_error (GRUB_ERR_BAD_FS, "invalid extent type"); + return 0; + } + + return grub_fshelp_read_file (node->data->disk, node, + read_hook, read_hook_data, + pos, len, buf, grub_udf_read_block, + U64 (node->block.fe.file_size), + node->data->lbshift, 0); +} + +static unsigned sblocklist[] = { 256, 512, 0 }; + +static struct grub_udf_data * +grub_udf_mount (grub_disk_t disk) +{ + struct grub_udf_data *data = 0; + struct grub_udf_fileset root_fs; + unsigned *sblklist; + grub_uint32_t block, vblock; + int i, lbshift; + + data = grub_malloc (sizeof (struct grub_udf_data)); + if (!data) + return 0; + + data->disk = disk; + + /* Search for Anchor Volume Descriptor Pointer (AVDP) + * and determine logical block size. */ + block = 0; + for (lbshift = 0; lbshift < 4; lbshift++) + { + for (sblklist = sblocklist; *sblklist; sblklist++) + { + struct grub_udf_avdp avdp; + + if (grub_disk_read (disk, *sblklist << lbshift, 0, + sizeof (struct grub_udf_avdp), &avdp)) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + + if (U16 (avdp.tag.tag_ident) == GRUB_UDF_TAG_IDENT_AVDP && + U32 (avdp.tag.tag_location) == *sblklist) + { + block = U32 (avdp.vds.start); + break; + } + } + + if (block) + break; + } + + if (!block) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + data->lbshift = lbshift; + + /* Search for Volume Recognition Sequence (VRS). */ + for (vblock = (32767 >> (lbshift + GRUB_DISK_SECTOR_BITS)) + 1;; + vblock += (2047 >> (lbshift + GRUB_DISK_SECTOR_BITS)) + 1) + { + struct grub_udf_vrs vrs; + + if (grub_disk_read (disk, vblock << lbshift, 0, + sizeof (struct grub_udf_vrs), &vrs)) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + + if ((!grub_memcmp (vrs.magic, GRUB_UDF_STD_IDENT_NSR03, 5)) || + (!grub_memcmp (vrs.magic, GRUB_UDF_STD_IDENT_NSR02, 5))) + break; + + if ((grub_memcmp (vrs.magic, GRUB_UDF_STD_IDENT_BEA01, 5)) && + (grub_memcmp (vrs.magic, GRUB_UDF_STD_IDENT_BOOT2, 5)) && + (grub_memcmp (vrs.magic, GRUB_UDF_STD_IDENT_CD001, 5)) && + (grub_memcmp (vrs.magic, GRUB_UDF_STD_IDENT_CDW02, 5)) && + (grub_memcmp (vrs.magic, GRUB_UDF_STD_IDENT_TEA01, 5))) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + } + + data->npd = data->npm = 0; + /* Locate Partition Descriptor (PD) and Logical Volume Descriptor (LVD). */ + while (1) + { + struct grub_udf_tag tag; + + if (grub_disk_read (disk, block << lbshift, 0, + sizeof (struct grub_udf_tag), &tag)) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + + tag.tag_ident = U16 (tag.tag_ident); + if (tag.tag_ident == GRUB_UDF_TAG_IDENT_PVD) + { + if (grub_disk_read (disk, block << lbshift, 0, + sizeof (struct grub_udf_pvd), + &data->pvd)) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + } + else if (tag.tag_ident == GRUB_UDF_TAG_IDENT_PD) + { + if (data->npd >= GRUB_UDF_MAX_PDS) + { + grub_error (GRUB_ERR_BAD_FS, "too many PDs"); + goto fail; + } + + if (grub_disk_read (disk, block << lbshift, 0, + sizeof (struct grub_udf_pd), + &data->pds[data->npd])) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + + data->npd++; + } + else if (tag.tag_ident == GRUB_UDF_TAG_IDENT_LVD) + { + int k; + + struct grub_udf_partmap *ppm; + + if (grub_disk_read (disk, block << lbshift, 0, + sizeof (struct grub_udf_lvd), + &data->lvd)) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + + if (data->npm + U32 (data->lvd.num_part_maps) > GRUB_UDF_MAX_PMS) + { + grub_error (GRUB_ERR_BAD_FS, "too many partition maps"); + goto fail; + } + + ppm = (struct grub_udf_partmap *) &data->lvd.part_maps; + for (k = U32 (data->lvd.num_part_maps); k > 0; k--) + { + if (ppm->type != GRUB_UDF_PARTMAP_TYPE_1) + { + grub_error (GRUB_ERR_BAD_FS, "partmap type not supported"); + goto fail; + } + + data->pms[data->npm++] = ppm; + ppm = (struct grub_udf_partmap *) ((char *) ppm + + U32 (ppm->length)); + } + } + else if (tag.tag_ident > GRUB_UDF_TAG_IDENT_TD) + { + grub_error (GRUB_ERR_BAD_FS, "invalid tag ident"); + goto fail; + } + else if (tag.tag_ident == GRUB_UDF_TAG_IDENT_TD) + break; + + block++; + } + + for (i = 0; i < data->npm; i++) + { + int j; + + for (j = 0; j < data->npd; j++) + if (data->pms[i]->type1.part_num == data->pds[j].part_num) + { + data->pms[i]->type1.part_num = j; + break; + } + + if (j == data->npd) + { + grub_error (GRUB_ERR_BAD_FS, "can\'t find PD"); + goto fail; + } + } + + block = grub_udf_get_block (data, + data->lvd.root_fileset.block.part_ref, + data->lvd.root_fileset.block.block_num); + + if (grub_errno) + goto fail; + + if (grub_disk_read (disk, block << lbshift, 0, + sizeof (struct grub_udf_fileset), &root_fs)) + { + grub_error (GRUB_ERR_BAD_FS, "not an UDF filesystem"); + goto fail; + } + + if (U16 (root_fs.tag.tag_ident) != GRUB_UDF_TAG_IDENT_FSD) + { + grub_error (GRUB_ERR_BAD_FS, "invalid fileset descriptor"); + goto fail; + } + + data->root_icb = root_fs.root_icb; + + return data; + +fail: + grub_free (data); + return 0; +} + +#ifdef GRUB_UTIL +grub_disk_addr_t +grub_udf_get_cluster_sector (grub_disk_t disk, grub_uint64_t *sec_per_lcn) +{ + grub_disk_addr_t ret; + static struct grub_udf_data *data; + + data = grub_udf_mount (disk); + if (!data) + return 0; + + ret = U32 (data->pds[data->pms[0]->type1.part_num].start); + *sec_per_lcn = 1ULL << data->lbshift; + grub_free (data); + return ret; +} +#endif + +static char * +read_string (const grub_uint8_t *raw, grub_size_t sz, char *outbuf) +{ + grub_uint16_t *utf16 = NULL; + grub_size_t utf16len = 0; + + if (sz == 0) + return NULL; + + if (raw[0] != 8 && raw[0] != 16) + return NULL; + + if (raw[0] == 8) + { + unsigned i; + utf16len = sz - 1; + utf16 = grub_calloc (utf16len, sizeof (utf16[0])); + if (!utf16) + return NULL; + for (i = 0; i < utf16len; i++) + utf16[i] = raw[i + 1]; + } + if (raw[0] == 16) + { + unsigned i; + utf16len = (sz - 1) / 2; + utf16 = grub_calloc (utf16len, sizeof (utf16[0])); + if (!utf16) + return NULL; + for (i = 0; i < utf16len; i++) + utf16[i] = (raw[2 * i + 1] << 8) | raw[2*i + 2]; + } + if (!outbuf) + { + grub_size_t size; + + if (grub_mul (utf16len, GRUB_MAX_UTF8_PER_UTF16, &size) || + grub_add (size, 1, &size)) + goto fail; + + outbuf = grub_malloc (size); + } + if (outbuf) + *grub_utf16_to_utf8 ((grub_uint8_t *) outbuf, utf16, utf16len) = '\0'; + + fail: + grub_free (utf16); + return outbuf; +} + +static char * +read_dstring (const grub_uint8_t *raw, grub_size_t sz) +{ + grub_size_t len; + + if (raw[0] == 0) { + char *outbuf = grub_malloc (1); + if (!outbuf) + return NULL; + outbuf[0] = 0; + return outbuf; + } + + len = raw[sz - 1]; + if (len > sz - 1) + len = sz - 1; + return read_string (raw, len, NULL); +} + +static int +grub_udf_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + grub_fshelp_node_t child; + struct grub_udf_file_ident dirent; + grub_off_t offset = 0; + + child = grub_malloc (get_fshelp_size (dir->data)); + if (!child) + return 0; + + /* The current directory is not stored. */ + grub_memcpy (child, dir, get_fshelp_size (dir->data)); + + if (hook (".", GRUB_FSHELP_DIR, child, hook_data)) + return 1; + + while (offset < U64 (dir->block.fe.file_size)) + { + if (grub_udf_read_file (dir, 0, 0, offset, sizeof (dirent), + (char *) &dirent) != sizeof (dirent)) + return 0; + + if (U16 (dirent.tag.tag_ident) != GRUB_UDF_TAG_IDENT_FID) + { + grub_error (GRUB_ERR_BAD_FS, "invalid fid tag"); + return 0; + } + + offset += sizeof (dirent) + U16 (dirent.imp_use_length); + if (!(dirent.characteristics & GRUB_UDF_FID_CHAR_DELETED)) + { + child = grub_malloc (get_fshelp_size (dir->data)); + if (!child) + return 0; + + if (grub_udf_read_icb (dir->data, &dirent.icb, child)) + { + grub_free (child); + return 0; + } + if (dirent.characteristics & GRUB_UDF_FID_CHAR_PARENT) + { + /* This is the parent directory. */ + if (hook ("..", GRUB_FSHELP_DIR, child, hook_data)) + return 1; + } + else + { + enum grub_fshelp_filetype type; + char *filename; + grub_uint8_t raw[MAX_FILE_IDENT_LENGTH]; + + type = ((dirent.characteristics & GRUB_UDF_FID_CHAR_DIRECTORY) ? + (GRUB_FSHELP_DIR) : (GRUB_FSHELP_REG)); + if (child->block.fe.icbtag.file_type == GRUB_UDF_ICBTAG_TYPE_SYMLINK) + type = GRUB_FSHELP_SYMLINK; + + if ((grub_udf_read_file (dir, 0, 0, offset, + dirent.file_ident_length, + (char *) raw)) + != dirent.file_ident_length) + { + grub_free (child); + return 0; + } + + filename = read_string (raw, dirent.file_ident_length, 0); + if (!filename) + { + /* As the hook won't get called. */ + grub_free (child); + grub_print_error (); + } + + if (filename && hook (filename, type, child, hook_data)) + { + grub_free (filename); + return 1; + } + grub_free (filename); + } + } + + /* Align to dword boundary. */ + offset = (offset + dirent.file_ident_length + 3) & (~3); + } + + return 0; +} + +static char * +grub_udf_read_symlink (grub_fshelp_node_t node) +{ + grub_size_t sz = U64 (node->block.fe.file_size); + grub_uint8_t *raw; + const grub_uint8_t *ptr; + char *out = NULL, *optr; + + if (sz < 4) + return NULL; + raw = grub_malloc (sz); + if (!raw) + return NULL; + if (grub_udf_read_file (node, NULL, NULL, 0, sz, (char *) raw) < 0) + goto fail_1; + + if (grub_mul (sz, 2, &sz) || + grub_add (sz, 1, &sz)) + goto fail_0; + + out = grub_malloc (sz); + if (!out) + { + fail_0: + grub_free (raw); + return NULL; + } + + optr = out; + + for (ptr = raw; ptr < raw + sz; ) + { + grub_size_t s; + if ((grub_size_t) (ptr - raw + 4) > sz) + goto fail_1; + if (!(ptr[2] == 0 && ptr[3] == 0)) + goto fail_1; + s = 4 + ptr[1]; + if ((grub_size_t) (ptr - raw + s) > sz) + goto fail_1; + switch (*ptr) + { + case 1: + if (ptr[1]) + goto fail_1; + /* Fallthrough. */ + case 2: + /* in 4 bytes. out: 1 byte. */ + optr = out; + *optr++ = '/'; + break; + case 3: + /* in 4 bytes. out: 3 bytes. */ + if (optr != out) + *optr++ = '/'; + *optr++ = '.'; + *optr++ = '.'; + break; + case 4: + /* in 4 bytes. out: 2 bytes. */ + if (optr != out) + *optr++ = '/'; + *optr++ = '.'; + break; + case 5: + /* in 4 + n bytes. out, at most: 1 + 2 * n bytes. */ + if (optr != out) + *optr++ = '/'; + if (!read_string (ptr + 4, s - 4, optr)) + goto fail_1; + optr += grub_strlen (optr); + break; + default: + goto fail_1; + } + ptr += s; + } + *optr = 0; + grub_free (raw); + return out; + + fail_1: + grub_free (raw); + grub_free (out); + grub_error (GRUB_ERR_BAD_FS, "invalid symlink"); + return NULL; +} + +/* Context for grub_udf_dir. */ +struct grub_udf_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Helper for grub_udf_dir. */ +static int +grub_udf_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_udf_dir_ctx *ctx = data; + struct grub_dirhook_info info; + const struct grub_udf_timestamp *tstamp = NULL; + + grub_memset (&info, 0, sizeof (info)); + info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); + if (U16 (node->block.fe.tag.tag_ident) == GRUB_UDF_TAG_IDENT_FE) + tstamp = &node->block.fe.modification_time; + else if (U16 (node->block.fe.tag.tag_ident) == GRUB_UDF_TAG_IDENT_EFE) + tstamp = &node->block.efe.modification_time; + + if (tstamp && (U16 (tstamp->type_and_timezone) & 0xf000) == 0x1000) + { + grub_int16_t tz; + struct grub_datetime datetime; + + datetime.year = U16 (tstamp->year); + datetime.month = tstamp->month; + datetime.day = tstamp->day; + datetime.hour = tstamp->hour; + datetime.minute = tstamp->minute; + datetime.second = tstamp->second; + + tz = U16 (tstamp->type_and_timezone) & 0xfff; + if (tz & 0x800) + tz |= 0xf000; + if (tz == -2047) + tz = 0; + + info.mtimeset = !!grub_datetime2unixtime (&datetime, &info.mtime); + + info.mtime -= 60 * tz; + } + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_udf_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_udf_dir_ctx ctx = { hook, hook_data }; + struct grub_udf_data *data = 0; + struct grub_fshelp_node *rootnode = 0; + struct grub_fshelp_node *foundnode = 0; + + grub_dl_ref (my_mod); + + data = grub_udf_mount (device->disk); + if (!data) + goto fail; + + rootnode = grub_malloc (get_fshelp_size (data)); + if (!rootnode) + goto fail; + + if (grub_udf_read_icb (data, &data->root_icb, rootnode)) + goto fail; + + if (grub_fshelp_find_file (path, rootnode, + &foundnode, + grub_udf_iterate_dir, grub_udf_read_symlink, + GRUB_FSHELP_DIR)) + goto fail; + + grub_udf_iterate_dir (foundnode, grub_udf_dir_iter, &ctx); + + if (foundnode != rootnode) + grub_free (foundnode); + +fail: + grub_free (rootnode); + + grub_free (data); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_err_t +grub_udf_open (struct grub_file *file, const char *name) +{ + struct grub_udf_data *data; + struct grub_fshelp_node *rootnode = 0; + struct grub_fshelp_node *foundnode; + + grub_dl_ref (my_mod); + + data = grub_udf_mount (file->device->disk); + if (!data) + goto fail; + + rootnode = grub_malloc (get_fshelp_size (data)); + if (!rootnode) + goto fail; + + if (grub_udf_read_icb (data, &data->root_icb, rootnode)) + goto fail; + + if (grub_fshelp_find_file (name, rootnode, + &foundnode, + grub_udf_iterate_dir, grub_udf_read_symlink, + GRUB_FSHELP_REG)) + goto fail; + + file->data = foundnode; + file->offset = 0; + file->size = U64 (foundnode->block.fe.file_size); + + grub_free (rootnode); + + return 0; + +fail: + grub_dl_unref (my_mod); + + grub_free (data); + grub_free (rootnode); + + return grub_errno; +} + +static grub_ssize_t +grub_udf_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_fshelp_node *node = (struct grub_fshelp_node *) file->data; + + return grub_udf_read_file (node, file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + +static grub_err_t +grub_udf_close (grub_file_t file) +{ + if (file->data) + { + struct grub_fshelp_node *node = (struct grub_fshelp_node *) file->data; + + grub_free (node->data); + grub_free (node); + } + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_udf_label (grub_device_t device, char **label) +{ + struct grub_udf_data *data; + data = grub_udf_mount (device->disk); + + if (data) + { + *label = read_dstring (data->lvd.ident, sizeof (data->lvd.ident)); + grub_free (data); + } + else + *label = 0; + + return grub_errno; +} + +static char * +gen_uuid_from_volset (char *volset_ident) +{ + grub_size_t i; + grub_size_t len; + grub_size_t nonhexpos; + grub_uint8_t buf[17]; + char *uuid; + + len = grub_strlen (volset_ident); + if (len < 8) + return NULL; + + uuid = grub_malloc (17); + if (!uuid) + return NULL; + + if (len > 16) + len = 16; + + grub_memset (buf, 0, sizeof (buf)); + grub_memcpy (buf, volset_ident, len); + + nonhexpos = 16; + for (i = 0; i < 16; ++i) + { + if (!grub_isxdigit (buf[i])) + { + nonhexpos = i; + break; + } + } + + if (nonhexpos < 8) + { + grub_snprintf (uuid, 17, "%02x%02x%02x%02x%02x%02x%02x%02x", + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7]); + } + else if (nonhexpos < 16) + { + for (i = 0; i < 8; ++i) + uuid[i] = grub_tolower (buf[i]); + grub_snprintf (uuid+8, 9, "%02x%02x%02x%02x", + buf[8], buf[9], buf[10], buf[11]); + } + else + { + for (i = 0; i < 16; ++i) + uuid[i] = grub_tolower (buf[i]); + uuid[16] = 0; + } + + return uuid; +} + +static grub_err_t +grub_udf_uuid (grub_device_t device, char **uuid) +{ + char *volset_ident; + struct grub_udf_data *data; + data = grub_udf_mount (device->disk); + + if (data) + { + volset_ident = read_dstring (data->pvd.volset_ident, sizeof (data->pvd.volset_ident)); + if (volset_ident) + { + *uuid = gen_uuid_from_volset (volset_ident); + grub_free (volset_ident); + } + else + *uuid = 0; + grub_free (data); + } + else + *uuid = 0; + + return grub_errno; +} + +static struct grub_fs grub_udf_fs = { + .name = "udf", + .fs_dir = grub_udf_dir, + .fs_open = grub_udf_open, + .fs_read = grub_udf_read, + .fs_close = grub_udf_close, + .fs_label = grub_udf_label, + .fs_uuid = grub_udf_uuid, +#ifdef GRUB_UTIL + .reserved_first_sector = 1, + .blocklist_install = 1, +#endif + .next = 0 +}; + +GRUB_MOD_INIT (udf) +{ + grub_fs_register (&grub_udf_fs); + my_mod = mod; +} + +GRUB_MOD_FINI (udf) +{ + grub_fs_unregister (&grub_udf_fs); +} diff --git a/grub-core/fs/ufs.c b/grub-core/fs/ufs.c new file mode 100644 index 0000000..34a698b --- /dev/null +++ b/grub-core/fs/ufs.c @@ -0,0 +1,918 @@ +/* ufs.c - Unix File System */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,2005,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/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#ifdef MODE_UFS2 +#define GRUB_UFS_MAGIC 0x19540119 +#else +#define GRUB_UFS_MAGIC 0x11954 +#endif + +#define GRUB_UFS_INODE 2 +#define GRUB_UFS_FILETYPE_DIR 4 +#define GRUB_UFS_FILETYPE_LNK 10 +#define GRUB_UFS_MAX_SYMLNK_CNT 8 + +#define GRUB_UFS_DIRBLKS 12 +#define GRUB_UFS_INDIRBLKS 3 + +#define GRUB_UFS_ATTR_TYPE 0160000 +#define GRUB_UFS_ATTR_FILE 0100000 +#define GRUB_UFS_ATTR_DIR 0040000 +#define GRUB_UFS_ATTR_LNK 0120000 + +#define GRUB_UFS_VOLNAME_LEN 32 + +#ifdef MODE_BIGENDIAN +#define grub_ufs_to_cpu16 grub_be_to_cpu16 +#define grub_ufs_to_cpu32 grub_be_to_cpu32 +#define grub_ufs_to_cpu64 grub_be_to_cpu64 +#define grub_cpu_to_ufs32_compile_time grub_cpu_to_be32_compile_time +#else +#define grub_ufs_to_cpu16 grub_le_to_cpu16 +#define grub_ufs_to_cpu32 grub_le_to_cpu32 +#define grub_ufs_to_cpu64 grub_le_to_cpu64 +#define grub_cpu_to_ufs32_compile_time grub_cpu_to_le32_compile_time +#endif + +#ifdef MODE_UFS2 +typedef grub_uint64_t grub_ufs_blk_t; +static inline grub_disk_addr_t +grub_ufs_to_cpu_blk (grub_ufs_blk_t blk) +{ + return grub_ufs_to_cpu64 (blk); +} +#else +typedef grub_uint32_t grub_ufs_blk_t; +static inline grub_disk_addr_t +grub_ufs_to_cpu_blk (grub_ufs_blk_t blk) +{ + return grub_ufs_to_cpu32 (blk); +} +#endif + +/* Calculate in which group the inode can be found. */ +#define UFS_BLKSZ(sblock) (grub_ufs_to_cpu32 (sblock->bsize)) +#define UFS_LOG_BLKSZ(sblock) (data->log2_blksz) + +#ifdef MODE_UFS2 +#define INODE_ENDIAN(data,field,bits1,bits2) grub_ufs_to_cpu##bits2 (data->inode.field) +#else +#define INODE_ENDIAN(data,field,bits1,bits2) grub_ufs_to_cpu##bits1 (data->inode.field) +#endif + +#define INODE_SIZE(data) grub_ufs_to_cpu64 (data->inode.size) +#define INODE_MODE(data) grub_ufs_to_cpu16 (data->inode.mode) +#ifdef MODE_UFS2 +#define LOG_INODE_BLKSZ 3 +#else +#define LOG_INODE_BLKSZ 2 +#endif +#ifdef MODE_UFS2 +#define UFS_INODE_PER_BLOCK 2 +#else +#define UFS_INODE_PER_BLOCK 4 +#endif +#define INODE_DIRBLOCKS(data,blk) INODE_ENDIAN \ + (data,blocks.dir_blocks[blk],32,64) +#define INODE_INDIRBLOCKS(data,blk) INODE_ENDIAN \ + (data,blocks.indir_blocks[blk],32,64) + +/* The blocks on which the superblock can be found. */ +static int sblocklist[] = { 128, 16, 0, 512, -1 }; + +struct grub_ufs_sblock +{ + grub_uint8_t unused[16]; + /* The offset of the inodes in the cylinder group. */ + grub_uint32_t inoblk_offs; + + grub_uint8_t unused2[4]; + + /* The start of the cylinder group. */ + grub_uint32_t cylg_offset; + grub_uint32_t cylg_mask; + + grub_uint32_t mtime; + grub_uint8_t unused4[12]; + + /* The size of a block in bytes. */ + grub_int32_t bsize; + grub_uint8_t unused5[48]; + + /* The size of filesystem blocks to disk blocks. */ + grub_uint32_t log2_blksz; + grub_uint8_t unused6[40]; + grub_uint32_t uuidhi; + grub_uint32_t uuidlow; + grub_uint8_t unused7[32]; + + /* Inodes stored per cylinder group. */ + grub_uint32_t ino_per_group; + + /* The frags per cylinder group. */ + grub_uint32_t frags_per_group; + + grub_uint8_t unused8[488]; + + /* Volume name for UFS2. */ + grub_uint8_t volume_name[GRUB_UFS_VOLNAME_LEN]; + grub_uint8_t unused9[360]; + + grub_uint64_t mtime2; + grub_uint8_t unused10[292]; + + /* Magic value to check if this is really a UFS filesystem. */ + grub_uint32_t magic; +}; + +#ifdef MODE_UFS2 +/* UFS inode. */ +struct grub_ufs_inode +{ + grub_uint16_t mode; + grub_uint16_t nlinks; + grub_uint32_t uid; + grub_uint32_t gid; + grub_uint32_t blocksize; + grub_uint64_t size; + grub_int64_t nblocks; + grub_uint64_t atime; + grub_uint64_t mtime; + grub_uint64_t ctime; + grub_uint64_t create_time; + grub_uint32_t atime_usec; + grub_uint32_t mtime_usec; + grub_uint32_t ctime_usec; + grub_uint32_t create_time_sec; + grub_uint32_t gen; + grub_uint32_t kernel_flags; + grub_uint32_t flags; + grub_uint32_t extsz; + grub_uint64_t ext[2]; + union + { + struct + { + grub_uint64_t dir_blocks[GRUB_UFS_DIRBLKS]; + grub_uint64_t indir_blocks[GRUB_UFS_INDIRBLKS]; + } blocks; + grub_uint8_t symlink[(GRUB_UFS_DIRBLKS + GRUB_UFS_INDIRBLKS) * 8]; + }; + + grub_uint8_t unused[24]; +} GRUB_PACKED; +#else +/* UFS inode. */ +struct grub_ufs_inode +{ + grub_uint16_t mode; + grub_uint16_t nlinks; + grub_uint16_t uid; + grub_uint16_t gid; + grub_uint64_t size; + grub_uint32_t atime; + grub_uint32_t atime_usec; + grub_uint32_t mtime; + grub_uint32_t mtime_usec; + grub_uint32_t ctime; + grub_uint32_t ctime_usec; + union + { + struct + { + grub_uint32_t dir_blocks[GRUB_UFS_DIRBLKS]; + grub_uint32_t indir_blocks[GRUB_UFS_INDIRBLKS]; + } blocks; + grub_uint8_t symlink[(GRUB_UFS_DIRBLKS + GRUB_UFS_INDIRBLKS) * 4]; + }; + grub_uint32_t flags; + grub_uint32_t nblocks; + grub_uint32_t gen; + grub_uint32_t unused; + grub_uint8_t pad[12]; +} GRUB_PACKED; +#endif + +/* Directory entry. */ +struct grub_ufs_dirent +{ + grub_uint32_t ino; + grub_uint16_t direntlen; + union + { + grub_uint16_t namelen; + struct + { + grub_uint8_t filetype_bsd; + grub_uint8_t namelen_bsd; + }; + }; +} GRUB_PACKED; + +/* Information about a "mounted" ufs filesystem. */ +struct grub_ufs_data +{ + struct grub_ufs_sblock sblock; + grub_disk_t disk; + struct grub_ufs_inode inode; + int ino; + int linknest; + int log2_blksz; +}; + +static grub_dl_t my_mod; + +/* Forward declaration. */ +static grub_err_t grub_ufs_find_file (struct grub_ufs_data *data, + const char *path); + + +static grub_disk_addr_t +grub_ufs_get_file_block (struct grub_ufs_data *data, grub_disk_addr_t blk) +{ + unsigned long indirsz; + int log2_blksz, log_indirsz; + + /* Direct. */ + if (blk < GRUB_UFS_DIRBLKS) + return INODE_DIRBLOCKS (data, blk); + + log2_blksz = grub_ufs_to_cpu32 (data->sblock.log2_blksz); + + blk -= GRUB_UFS_DIRBLKS; + + log_indirsz = data->log2_blksz - LOG_INODE_BLKSZ; + indirsz = 1 << log_indirsz; + + /* Single indirect block. */ + if (blk < indirsz) + { + grub_ufs_blk_t indir; + grub_disk_read (data->disk, + ((grub_disk_addr_t) INODE_INDIRBLOCKS (data, 0)) + << log2_blksz, + blk * sizeof (indir), sizeof (indir), &indir); + return indir; + } + blk -= indirsz; + + /* Double indirect block. */ + if (blk < (grub_disk_addr_t) indirsz * (grub_disk_addr_t) indirsz) + { + grub_ufs_blk_t indir; + + grub_disk_read (data->disk, + ((grub_disk_addr_t) INODE_INDIRBLOCKS (data, 1)) + << log2_blksz, + (blk >> log_indirsz) * sizeof (indir), + sizeof (indir), &indir); + grub_disk_read (data->disk, + grub_ufs_to_cpu_blk (indir) << log2_blksz, + (blk & ((1 << log_indirsz) - 1)) * sizeof (indir), + sizeof (indir), &indir); + + return indir; + } + + blk -= (grub_disk_addr_t) indirsz * (grub_disk_addr_t) indirsz; + + /* Triple indirect block. */ + if (!(blk >> (3 * log_indirsz))) + { + grub_ufs_blk_t indir; + + grub_disk_read (data->disk, + ((grub_disk_addr_t) INODE_INDIRBLOCKS (data, 2)) + << log2_blksz, + (blk >> (2 * log_indirsz)) * sizeof (indir), + sizeof (indir), &indir); + grub_disk_read (data->disk, + grub_ufs_to_cpu_blk (indir) << log2_blksz, + ((blk >> log_indirsz) + & ((1 << log_indirsz) - 1)) * sizeof (indir), + sizeof (indir), &indir); + + grub_disk_read (data->disk, + grub_ufs_to_cpu_blk (indir) << log2_blksz, + (blk & ((1 << log_indirsz) - 1)) * sizeof (indir), + sizeof (indir), &indir); + + return indir; + } + + grub_error (GRUB_ERR_BAD_FS, + "ufs does not support quadruple indirect blocks"); + return 0; +} + + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +static grub_ssize_t +grub_ufs_read_file (struct grub_ufs_data *data, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf) +{ + struct grub_ufs_sblock *sblock = &data->sblock; + grub_off_t i; + grub_off_t blockcnt; + + /* Adjust len so it we can't read past the end of the file. */ + if (len + pos > INODE_SIZE (data)) + len = INODE_SIZE (data) - pos; + + blockcnt = (len + pos + UFS_BLKSZ (sblock) - 1) >> UFS_LOG_BLKSZ (sblock); + + for (i = pos >> UFS_LOG_BLKSZ (sblock); i < blockcnt; i++) + { + grub_disk_addr_t blknr; + grub_off_t blockoff; + grub_off_t blockend = UFS_BLKSZ (sblock); + + int skipfirst = 0; + + blockoff = pos & (UFS_BLKSZ (sblock) - 1); + + blknr = grub_ufs_get_file_block (data, i); + if (grub_errno) + return -1; + + /* Last block. */ + if (i == blockcnt - 1) + { + blockend = (len + pos) & (UFS_BLKSZ (sblock) - 1); + + if (!blockend) + blockend = UFS_BLKSZ (sblock); + } + + /* First block. */ + if (i == (pos >> UFS_LOG_BLKSZ (sblock))) + { + skipfirst = blockoff; + blockend -= skipfirst; + } + + /* XXX: If the block number is 0 this block is not stored on + disk but is zero filled instead. */ + if (blknr) + { + data->disk->read_hook = read_hook; + data->disk->read_hook_data = read_hook_data; + grub_disk_read (data->disk, + blknr << grub_ufs_to_cpu32 (data->sblock.log2_blksz), + skipfirst, blockend, buf); + data->disk->read_hook = 0; + if (grub_errno) + return -1; + } + else + grub_memset (buf, 0, blockend); + + buf += UFS_BLKSZ (sblock) - skipfirst; + } + + return len; +} + +/* Read inode INO from the mounted filesystem described by DATA. This + inode is used by default now. */ +static grub_err_t +grub_ufs_read_inode (struct grub_ufs_data *data, int ino, char *inode) +{ + struct grub_ufs_sblock *sblock = &data->sblock; + + /* Determine the group the inode is in. */ + int group = ino / grub_ufs_to_cpu32 (sblock->ino_per_group); + + /* Determine the inode within the group. */ + int grpino = ino % grub_ufs_to_cpu32 (sblock->ino_per_group); + + /* The first block of the group. */ + int grpblk = group * (grub_ufs_to_cpu32 (sblock->frags_per_group)); + +#ifndef MODE_UFS2 + grpblk += grub_ufs_to_cpu32 (sblock->cylg_offset) + * (group & (~grub_ufs_to_cpu32 (sblock->cylg_mask))); +#endif + + if (!inode) + { + inode = (char *) &data->inode; + data->ino = ino; + } + + grub_disk_read (data->disk, + ((grub_ufs_to_cpu32 (sblock->inoblk_offs) + grpblk) + << grub_ufs_to_cpu32 (data->sblock.log2_blksz)) + + grpino / UFS_INODE_PER_BLOCK, + (grpino % UFS_INODE_PER_BLOCK) + * sizeof (struct grub_ufs_inode), + sizeof (struct grub_ufs_inode), + inode); + + return grub_errno; +} + + +/* Lookup the symlink the current inode points to. INO is the inode + number of the directory the symlink is relative to. */ +static grub_err_t +grub_ufs_lookup_symlink (struct grub_ufs_data *data, int ino) +{ + char *symlink; + grub_size_t sz = INODE_SIZE (data); + + if (++data->linknest > GRUB_UFS_MAX_SYMLNK_CNT) + return grub_error (GRUB_ERR_SYMLINK_LOOP, N_("too deep nesting of symlinks")); + + symlink = grub_malloc (sz + 1); + if (!symlink) + return grub_errno; + /* Normally we should just check that data->inode.nblocks == 0. + However old Linux doesn't maintain nblocks correctly and so it's always + 0. If size is bigger than inline space then the symlink is surely not + inline. */ + /* Check against zero is paylindromic, no need to swap. */ + if (data->inode.nblocks == 0 + && INODE_SIZE (data) <= sizeof (data->inode.symlink)) + grub_strcpy (symlink, (char *) data->inode.symlink); + else + { + if (grub_ufs_read_file (data, 0, 0, 0, sz, symlink) < 0) + { + grub_free(symlink); + return grub_errno; + } + } + symlink[sz] = '\0'; + + /* The symlink is an absolute path, go back to the root inode. */ + if (symlink[0] == '/') + ino = GRUB_UFS_INODE; + + /* Now load in the old inode. */ + if (grub_ufs_read_inode (data, ino, 0)) + { + grub_free (symlink); + return grub_errno; + } + + grub_ufs_find_file (data, symlink); + + grub_free (symlink); + + return grub_errno; +} + + +/* Find the file with the pathname PATH on the filesystem described by + DATA. */ +static grub_err_t +grub_ufs_find_file (struct grub_ufs_data *data, const char *path) +{ + const char *name; + const char *next = path; + unsigned int pos = 0; + int dirino; + char *filename; + + /* We reject filenames longer than the one we're looking + for without reading, so this allocation is enough. */ + filename = grub_malloc (grub_strlen (path) + 2); + if (!filename) + return grub_errno; + + while (1) + { + struct grub_ufs_dirent dirent; + + name = next; + /* Skip the first slash. */ + while (*name == '/') + name++; + if (*name == 0) + { + grub_free (filename); + return GRUB_ERR_NONE; + } + + if ((INODE_MODE(data) & GRUB_UFS_ATTR_TYPE) + != GRUB_UFS_ATTR_DIR) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, + N_("not a directory")); + goto fail; + } + + /* Extract the actual part from the pathname. */ + for (next = name; *next && *next != '/'; next++); + for (pos = 0; ; pos += grub_ufs_to_cpu16 (dirent.direntlen)) + { + int namelen; + + if (pos >= INODE_SIZE (data)) + { + grub_error (GRUB_ERR_FILE_NOT_FOUND, + N_("file `%s' not found"), + path); + goto fail; + } + + if (grub_ufs_read_file (data, 0, 0, pos, sizeof (dirent), + (char *) &dirent) < 0) + goto fail; + +#ifdef MODE_UFS2 + namelen = dirent.namelen_bsd; +#else + namelen = grub_ufs_to_cpu16 (dirent.namelen); +#endif + if (namelen < next - name) + continue; + + if (grub_ufs_read_file (data, 0, 0, pos + sizeof (dirent), + next - name + (namelen != next - name), + filename) < 0) + goto fail; + + if (grub_strncmp (name, filename, next - name) == 0 + && (namelen == next - name || filename[next - name] == '\0')) + { + dirino = data->ino; + grub_ufs_read_inode (data, grub_ufs_to_cpu32 (dirent.ino), 0); + + if ((INODE_MODE(data) & GRUB_UFS_ATTR_TYPE) + == GRUB_UFS_ATTR_LNK) + { + grub_ufs_lookup_symlink (data, dirino); + if (grub_errno) + goto fail; + } + + break; + } + } + } + fail: + grub_free (filename); + return grub_errno; +} + + +/* Mount the filesystem on the disk DISK. */ +static struct grub_ufs_data * +grub_ufs_mount (grub_disk_t disk) +{ + struct grub_ufs_data *data; + int *sblklist = sblocklist; + + data = grub_malloc (sizeof (struct grub_ufs_data)); + if (!data) + return 0; + + /* Find a UFS sblock. */ + while (*sblklist != -1) + { + grub_disk_read (disk, *sblklist, 0, sizeof (struct grub_ufs_sblock), + &data->sblock); + if (grub_errno) + goto fail; + + /* No need to byteswap bsize in this check. It works the same on both + endiannesses. */ + if (data->sblock.magic == grub_cpu_to_ufs32_compile_time (GRUB_UFS_MAGIC) + && data->sblock.bsize != 0 + && ((data->sblock.bsize & (data->sblock.bsize - 1)) == 0) + && data->sblock.ino_per_group != 0) + { + for (data->log2_blksz = 0; + (1U << data->log2_blksz) < grub_ufs_to_cpu32 (data->sblock.bsize); + data->log2_blksz++); + + data->disk = disk; + data->linknest = 0; + return data; + } + sblklist++; + } + + fail: + + if (grub_errno == GRUB_ERR_NONE || grub_errno == GRUB_ERR_OUT_OF_RANGE) + { +#ifdef MODE_UFS2 + grub_error (GRUB_ERR_BAD_FS, "not an ufs2 filesystem"); +#else + grub_error (GRUB_ERR_BAD_FS, "not an ufs1 filesystem"); +#endif + } + + grub_free (data); + + return 0; +} + + +static grub_err_t +grub_ufs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_ufs_data *data; + unsigned int pos = 0; + + data = grub_ufs_mount (device->disk); + if (!data) + return grub_errno; + + grub_ufs_read_inode (data, GRUB_UFS_INODE, 0); + if (grub_errno) + return grub_errno; + + if (!path || path[0] != '/') + { + grub_error (GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), path); + return grub_errno; + } + + grub_ufs_find_file (data, path); + if (grub_errno) + goto fail; + + if ((INODE_MODE (data) & GRUB_UFS_ATTR_TYPE) != GRUB_UFS_ATTR_DIR) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + goto fail; + } + + while (pos < INODE_SIZE (data)) + { + struct grub_ufs_dirent dirent; + int namelen; + + if (grub_ufs_read_file (data, 0, 0, pos, sizeof (dirent), + (char *) &dirent) < 0) + break; + + if (dirent.direntlen == 0) + break; + +#ifdef MODE_UFS2 + namelen = dirent.namelen_bsd; +#else + namelen = grub_ufs_to_cpu16 (dirent.namelen); +#endif + + char *filename = grub_malloc (namelen + 1); + if (!filename) + goto fail; + struct grub_dirhook_info info; + struct grub_ufs_inode inode; + + grub_memset (&info, 0, sizeof (info)); + + if (grub_ufs_read_file (data, 0, 0, pos + sizeof (dirent), + namelen, filename) < 0) + { + grub_free (filename); + break; + } + + filename[namelen] = '\0'; + grub_ufs_read_inode (data, grub_ufs_to_cpu32 (dirent.ino), + (char *) &inode); + + info.dir = ((grub_ufs_to_cpu16 (inode.mode) & GRUB_UFS_ATTR_TYPE) + == GRUB_UFS_ATTR_DIR); +#ifdef MODE_UFS2 + info.mtime = grub_ufs_to_cpu64 (inode.mtime); +#else + info.mtime = grub_ufs_to_cpu32 (inode.mtime); +#endif + info.mtimeset = 1; + + if (hook (filename, &info, hook_data)) + { + grub_free (filename); + break; + } + + grub_free (filename); + + pos += grub_ufs_to_cpu16 (dirent.direntlen); + } + + fail: + grub_free (data); + + return grub_errno; +} + + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_ufs_open (struct grub_file *file, const char *name) +{ + struct grub_ufs_data *data; + data = grub_ufs_mount (file->device->disk); + if (!data) + return grub_errno; + + grub_ufs_read_inode (data, 2, 0); + if (grub_errno) + { + grub_free (data); + return grub_errno; + } + + if (!name || name[0] != '/') + { + grub_error (GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), name); + return grub_errno; + } + + grub_ufs_find_file (data, name); + if (grub_errno) + { + grub_free (data); + return grub_errno; + } + + file->data = data; + file->size = INODE_SIZE (data); + + return GRUB_ERR_NONE; +} + + +static grub_ssize_t +grub_ufs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_ufs_data *data = + (struct grub_ufs_data *) file->data; + + return grub_ufs_read_file (data, file->read_hook, file->read_hook_data, + file->offset, len, buf); +} + + +static grub_err_t +grub_ufs_close (grub_file_t file) +{ + grub_free (file->data); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_ufs_label (grub_device_t device, char **label) +{ + struct grub_ufs_data *data = 0; + + grub_dl_ref (my_mod); + + *label = 0; + + data = grub_ufs_mount (device->disk); + if (data) + *label = grub_strdup ((char *) data->sblock.volume_name); + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_ufs_uuid (grub_device_t device, char **uuid) +{ + struct grub_ufs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_ufs_mount (disk); + if (data && (data->sblock.uuidhi != 0 || data->sblock.uuidlow != 0)) + *uuid = grub_xasprintf ("%08x%08x", + (unsigned) grub_ufs_to_cpu32 (data->sblock.uuidhi), + (unsigned) grub_ufs_to_cpu32 (data->sblock.uuidlow)); + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + +/* Get mtime. */ +static grub_err_t +grub_ufs_mtime (grub_device_t device, grub_int64_t *tm) +{ + struct grub_ufs_data *data = 0; + + grub_dl_ref (my_mod); + + data = grub_ufs_mount (device->disk); + if (!data) + *tm = 0; + else + { + *tm = grub_ufs_to_cpu32 (data->sblock.mtime); +#ifdef MODE_UFS2 + if (*tm < (grub_int64_t) grub_ufs_to_cpu64 (data->sblock.mtime2)) + *tm = grub_ufs_to_cpu64 (data->sblock.mtime2); +#endif + } + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + + +static struct grub_fs grub_ufs_fs = + { +#ifdef MODE_UFS2 + .name = "ufs2", +#else +#ifdef MODE_BIGENDIAN + .name = "ufs1_be", +#else + .name = "ufs1", +#endif +#endif + .fs_dir = grub_ufs_dir, + .fs_open = grub_ufs_open, + .fs_read = grub_ufs_read, + .fs_close = grub_ufs_close, + .fs_label = grub_ufs_label, + .fs_uuid = grub_ufs_uuid, + .fs_mtime = grub_ufs_mtime, + /* FIXME: set reserved_first_sector. */ +#ifdef GRUB_UTIL + .blocklist_install = 1, +#endif + .next = 0 + }; + +#ifdef MODE_UFS2 +GRUB_MOD_INIT(ufs2) +#else +#ifdef MODE_BIGENDIAN +GRUB_MOD_INIT(ufs1_be) +#else +GRUB_MOD_INIT(ufs1) +#endif +#endif +{ + grub_fs_register (&grub_ufs_fs); + my_mod = mod; +} + +#ifdef MODE_UFS2 +GRUB_MOD_FINI(ufs2) +#else +#ifdef MODE_BIGENDIAN +GRUB_MOD_FINI(ufs1_be) +#else +GRUB_MOD_FINI(ufs1) +#endif +#endif +{ + grub_fs_unregister (&grub_ufs_fs); +} + diff --git a/grub-core/fs/ufs2.c b/grub-core/fs/ufs2.c new file mode 100644 index 0000000..7f4eb95 --- /dev/null +++ b/grub-core/fs/ufs2.c @@ -0,0 +1,3 @@ +/* ufs2.c - Unix File System 2 */ +#define MODE_UFS2 1 +#include "ufs.c" diff --git a/grub-core/fs/ufs_be.c b/grub-core/fs/ufs_be.c new file mode 100644 index 0000000..a58f75a --- /dev/null +++ b/grub-core/fs/ufs_be.c @@ -0,0 +1,2 @@ +#define MODE_BIGENDIAN 1 +#include "ufs.c" diff --git a/grub-core/fs/xfs.c b/grub-core/fs/xfs.c new file mode 100644 index 0000000..0f524c3 --- /dev/null +++ b/grub-core/fs/xfs.c @@ -0,0 +1,1211 @@ +/* xfs.c - XFS. */ +/* + * 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/time.h> +#include <grub/types.h> +#include <grub/fshelp.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define XFS_INODE_EXTENTS 9 + +#define XFS_INODE_FORMAT_INO 1 +#define XFS_INODE_FORMAT_EXT 2 +#define XFS_INODE_FORMAT_BTREE 3 + +/* Superblock version field flags */ +#define XFS_SB_VERSION_NUMBITS 0x000f +#define XFS_SB_VERSION_ATTRBIT 0x0010 +#define XFS_SB_VERSION_NLINKBIT 0x0020 +#define XFS_SB_VERSION_QUOTABIT 0x0040 +#define XFS_SB_VERSION_ALIGNBIT 0x0080 +#define XFS_SB_VERSION_DALIGNBIT 0x0100 +#define XFS_SB_VERSION_LOGV2BIT 0x0400 +#define XFS_SB_VERSION_SECTORBIT 0x0800 +#define XFS_SB_VERSION_EXTFLGBIT 0x1000 +#define XFS_SB_VERSION_DIRV2BIT 0x2000 +#define XFS_SB_VERSION_MOREBITSBIT 0x8000 +#define XFS_SB_VERSION_BITS_SUPPORTED \ + (XFS_SB_VERSION_NUMBITS | \ + XFS_SB_VERSION_ATTRBIT | \ + XFS_SB_VERSION_NLINKBIT | \ + XFS_SB_VERSION_QUOTABIT | \ + XFS_SB_VERSION_ALIGNBIT | \ + XFS_SB_VERSION_DALIGNBIT | \ + XFS_SB_VERSION_LOGV2BIT | \ + XFS_SB_VERSION_SECTORBIT | \ + XFS_SB_VERSION_EXTFLGBIT | \ + XFS_SB_VERSION_DIRV2BIT | \ + XFS_SB_VERSION_MOREBITSBIT) + +/* Recognized xfs format versions */ +#define XFS_SB_VERSION_4 4 /* Good old XFS filesystem */ +#define XFS_SB_VERSION_5 5 /* CRC enabled filesystem */ + +/* features2 field flags */ +#define XFS_SB_VERSION2_LAZYSBCOUNTBIT 0x00000002 /* Superblk counters */ +#define XFS_SB_VERSION2_ATTR2BIT 0x00000008 /* Inline attr rework */ +#define XFS_SB_VERSION2_PROJID32BIT 0x00000080 /* 32-bit project ids */ +#define XFS_SB_VERSION2_FTYPE 0x00000200 /* inode type in dir */ +#define XFS_SB_VERSION2_BITS_SUPPORTED \ + (XFS_SB_VERSION2_LAZYSBCOUNTBIT | \ + XFS_SB_VERSION2_ATTR2BIT | \ + XFS_SB_VERSION2_PROJID32BIT | \ + XFS_SB_VERSION2_FTYPE) + +/* Inode flags2 flags */ +#define XFS_DIFLAG2_BIGTIME_BIT 3 +#define XFS_DIFLAG2_BIGTIME (1 << XFS_DIFLAG2_BIGTIME_BIT) + +/* incompat feature flags */ +#define XFS_SB_FEAT_INCOMPAT_FTYPE (1 << 0) /* filetype in dirent */ +#define XFS_SB_FEAT_INCOMPAT_SPINODES (1 << 1) /* sparse inode chunks */ +#define XFS_SB_FEAT_INCOMPAT_META_UUID (1 << 2) /* metadata UUID */ +#define XFS_SB_FEAT_INCOMPAT_BIGTIME (1 << 3) /* large timestamps */ +#define XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR (1 << 4) /* needs xfs_repair */ + +/* + * Directory entries with ftype are explicitly handled by GRUB code. + * + * We do not currently read the inode btrees, so it is safe to read filesystems + * with the XFS_SB_FEAT_INCOMPAT_SPINODES feature. + * + * We do not currently verify metadata UUID, so it is safe to read filesystems + * with the XFS_SB_FEAT_INCOMPAT_META_UUID feature. + */ +#define XFS_SB_FEAT_INCOMPAT_SUPPORTED \ + (XFS_SB_FEAT_INCOMPAT_FTYPE | \ + XFS_SB_FEAT_INCOMPAT_SPINODES | \ + XFS_SB_FEAT_INCOMPAT_META_UUID | \ + XFS_SB_FEAT_INCOMPAT_BIGTIME | \ + XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR) + +struct grub_xfs_sblock +{ + grub_uint8_t magic[4]; + grub_uint32_t bsize; + grub_uint8_t unused1[24]; + grub_uint16_t uuid[8]; + grub_uint8_t unused2[8]; + grub_uint64_t rootino; + grub_uint8_t unused3[20]; + grub_uint32_t agsize; + grub_uint8_t unused4[12]; + grub_uint16_t version; + grub_uint8_t unused5[6]; + grub_uint8_t label[12]; + grub_uint8_t log2_bsize; + grub_uint8_t log2_sect; + grub_uint8_t log2_inode; + grub_uint8_t log2_inop; + grub_uint8_t log2_agblk; + grub_uint8_t unused6[67]; + grub_uint8_t log2_dirblk; + grub_uint8_t unused7[7]; + grub_uint32_t features2; + grub_uint8_t unused8[4]; + grub_uint32_t sb_features_compat; + grub_uint32_t sb_features_ro_compat; + grub_uint32_t sb_features_incompat; + grub_uint32_t sb_features_log_incompat; +} GRUB_PACKED; + +struct grub_xfs_dir_header +{ + grub_uint8_t count; + grub_uint8_t largeino; + union + { + grub_uint32_t i4; + grub_uint64_t i8; + } GRUB_PACKED parent; +} GRUB_PACKED; + +/* Structure for directory entry inlined in the inode */ +struct grub_xfs_dir_entry +{ + grub_uint8_t len; + grub_uint16_t offset; + char name[1]; + /* Inode number follows, 32 / 64 bits. */ +} GRUB_PACKED; + +/* Structure for directory entry in a block */ +struct grub_xfs_dir2_entry +{ + grub_uint64_t inode; + grub_uint8_t len; +} GRUB_PACKED; + +struct grub_xfs_extent +{ + /* This should be a bitfield but bietfields are unportable, so just have + a raw array and functions extracting useful info from it. + */ + grub_uint32_t raw[4]; +} GRUB_PACKED; + +struct grub_xfs_btree_node +{ + grub_uint8_t magic[4]; + grub_uint16_t level; + grub_uint16_t numrecs; + grub_uint64_t left; + grub_uint64_t right; + /* In V5 here follow crc, uuid, etc. */ + /* Then follow keys and block pointers */ +} GRUB_PACKED; + +struct grub_xfs_btree_root +{ + grub_uint16_t level; + grub_uint16_t numrecs; + grub_uint64_t keys[1]; +} GRUB_PACKED; + +struct grub_xfs_time_legacy +{ + grub_uint32_t sec; + grub_uint32_t nanosec; +} GRUB_PACKED; + +struct grub_xfs_inode +{ + grub_uint8_t magic[2]; + grub_uint16_t mode; + grub_uint8_t version; + grub_uint8_t format; + grub_uint8_t unused2[26]; + grub_uint64_t atime; + grub_uint64_t mtime; + grub_uint64_t ctime; + grub_uint64_t size; + grub_uint64_t nblocks; + grub_uint32_t extsize; + grub_uint32_t nextents; + grub_uint16_t unused3; + grub_uint8_t fork_offset; + grub_uint8_t unused4[37]; + grub_uint64_t flags2; + grub_uint8_t unused5[48]; +} GRUB_PACKED; + +#define XFS_V3_INODE_SIZE sizeof(struct grub_xfs_inode) +/* Size of struct grub_xfs_inode until fork_offset (included). */ +#define XFS_V2_INODE_SIZE (XFS_V3_INODE_SIZE - 92) + +struct grub_xfs_dirblock_tail +{ + grub_uint32_t leaf_count; + grub_uint32_t leaf_stale; +} GRUB_PACKED; + +struct grub_fshelp_node +{ + struct grub_xfs_data *data; + grub_uint64_t ino; + int inode_read; + struct grub_xfs_inode inode; +}; + +struct grub_xfs_data +{ + struct grub_xfs_sblock sblock; + grub_disk_t disk; + int pos; + int bsize; + grub_uint32_t agsize; + unsigned int hasftype:1; + unsigned int hascrc:1; + struct grub_fshelp_node diropen; +}; + +static grub_dl_t my_mod; + + + +static int grub_xfs_sb_hascrc(struct grub_xfs_data *data) +{ + return (data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) == + grub_cpu_to_be16_compile_time(XFS_SB_VERSION_5); +} + +static int grub_xfs_sb_hasftype(struct grub_xfs_data *data) +{ + if ((data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) == + grub_cpu_to_be16_compile_time(XFS_SB_VERSION_5) && + data->sblock.sb_features_incompat & grub_cpu_to_be32_compile_time(XFS_SB_FEAT_INCOMPAT_FTYPE)) + return 1; + if (data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_MOREBITSBIT) && + data->sblock.features2 & grub_cpu_to_be32_compile_time(XFS_SB_VERSION2_FTYPE)) + return 1; + return 0; +} + +static int grub_xfs_sb_valid(struct grub_xfs_data *data) +{ + grub_dprintf("xfs", "Validating superblock\n"); + if (grub_strncmp ((char *) (data->sblock.magic), "XFSB", 4) + || data->sblock.log2_bsize < GRUB_DISK_SECTOR_BITS + || ((int) data->sblock.log2_bsize + + (int) data->sblock.log2_dirblk) >= 27) + { + grub_error (GRUB_ERR_BAD_FS, "not a XFS filesystem"); + return 0; + } + if ((data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) == + grub_cpu_to_be16_compile_time(XFS_SB_VERSION_5)) + { + grub_dprintf("xfs", "XFS v5 superblock detected\n"); + if (data->sblock.sb_features_incompat & + grub_cpu_to_be32_compile_time(~XFS_SB_FEAT_INCOMPAT_SUPPORTED)) + { + grub_error (GRUB_ERR_BAD_FS, "XFS filesystem has unsupported " + "incompatible features"); + return 0; + } + return 1; + } + else if ((data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) == + grub_cpu_to_be16_compile_time(XFS_SB_VERSION_4)) + { + grub_dprintf("xfs", "XFS v4 superblock detected\n"); + if (!(data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_DIRV2BIT))) + { + grub_error (GRUB_ERR_BAD_FS, "XFS filesystem without V2 directories " + "is unsupported"); + return 0; + } + if (data->sblock.version & grub_cpu_to_be16_compile_time(~XFS_SB_VERSION_BITS_SUPPORTED) || + (data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_MOREBITSBIT) && + data->sblock.features2 & grub_cpu_to_be16_compile_time(~XFS_SB_VERSION2_BITS_SUPPORTED))) + { + grub_error (GRUB_ERR_BAD_FS, "XFS filesystem has unsupported version " + "bits"); + return 0; + } + return 1; + } + return 0; +} + +static int +grub_xfs_sb_needs_repair (struct grub_xfs_data *data) +{ + return ((data->sblock.version & + grub_cpu_to_be16_compile_time (XFS_SB_VERSION_NUMBITS)) == + grub_cpu_to_be16_compile_time (XFS_SB_VERSION_5) && + (data->sblock.sb_features_incompat & + grub_cpu_to_be32_compile_time (XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR))); +} + +/* Filetype information as used in inodes. */ +#define FILETYPE_INO_MASK 0170000 +#define FILETYPE_INO_REG 0100000 +#define FILETYPE_INO_DIRECTORY 0040000 +#define FILETYPE_INO_SYMLINK 0120000 + +static inline int +GRUB_XFS_INO_AGBITS(struct grub_xfs_data *data) +{ + return ((data)->sblock.log2_agblk + (data)->sblock.log2_inop); +} + +static inline grub_uint64_t +GRUB_XFS_INO_INOINAG (struct grub_xfs_data *data, + grub_uint64_t ino) +{ + return (ino & ((1LL << GRUB_XFS_INO_AGBITS (data)) - 1)); +} + +static inline grub_uint64_t +GRUB_XFS_INO_AG (struct grub_xfs_data *data, + grub_uint64_t ino) +{ + return (ino >> GRUB_XFS_INO_AGBITS (data)); +} + +static inline grub_disk_addr_t +GRUB_XFS_FSB_TO_BLOCK (struct grub_xfs_data *data, grub_disk_addr_t fsb) +{ + return ((fsb >> data->sblock.log2_agblk) * data->agsize + + (fsb & ((1LL << data->sblock.log2_agblk) - 1))); +} + +static inline grub_uint64_t +GRUB_XFS_EXTENT_OFFSET (struct grub_xfs_extent *exts, int ex) +{ + return ((grub_be_to_cpu32 (exts[ex].raw[0]) & ~(1 << 31)) << 23 + | grub_be_to_cpu32 (exts[ex].raw[1]) >> 9); +} + +static inline grub_uint64_t +GRUB_XFS_EXTENT_BLOCK (struct grub_xfs_extent *exts, int ex) +{ + return ((grub_uint64_t) (grub_be_to_cpu32 (exts[ex].raw[1]) + & (0x1ff)) << 43 + | (grub_uint64_t) grub_be_to_cpu32 (exts[ex].raw[2]) << 11 + | grub_be_to_cpu32 (exts[ex].raw[3]) >> 21); +} + +static inline grub_uint64_t +GRUB_XFS_EXTENT_SIZE (struct grub_xfs_extent *exts, int ex) +{ + return (grub_be_to_cpu32 (exts[ex].raw[3]) & ((1 << 21) - 1)); +} + + +static inline grub_uint64_t +grub_xfs_inode_block (struct grub_xfs_data *data, + grub_uint64_t ino) +{ + long long int inoinag = GRUB_XFS_INO_INOINAG (data, ino); + long long ag = GRUB_XFS_INO_AG (data, ino); + long long block; + + block = (inoinag >> data->sblock.log2_inop) + ag * data->agsize; + block <<= (data->sblock.log2_bsize - GRUB_DISK_SECTOR_BITS); + return block; +} + + +static inline int +grub_xfs_inode_offset (struct grub_xfs_data *data, + grub_uint64_t ino) +{ + int inoag = GRUB_XFS_INO_INOINAG (data, ino); + return ((inoag & ((1 << data->sblock.log2_inop) - 1)) << + data->sblock.log2_inode); +} + +static inline grub_size_t +grub_xfs_inode_size(struct grub_xfs_data *data) +{ + return (grub_size_t)1 << data->sblock.log2_inode; +} + +/* + * Returns size occupied by XFS inode stored in memory - we store struct + * grub_fshelp_node there but on disk inode size may be actually larger than + * struct grub_xfs_inode so we need to account for that so that we can read + * from disk directly into in-memory structure. + */ +static inline grub_size_t +grub_xfs_fshelp_size(struct grub_xfs_data *data) +{ + return sizeof (struct grub_fshelp_node) - sizeof (struct grub_xfs_inode) + + grub_xfs_inode_size(data); +} + +/* This should return void * but XFS code is error-prone with alignment, so + return char to retain cast-align. + */ +static char * +grub_xfs_inode_data(struct grub_xfs_inode *inode) +{ + if (inode->version <= 2) + return ((char *)inode) + XFS_V2_INODE_SIZE; + return ((char *)inode) + XFS_V3_INODE_SIZE; +} + +static struct grub_xfs_dir_entry * +grub_xfs_inline_de(struct grub_xfs_dir_header *head) +{ + /* + With small inode numbers the header is 4 bytes smaller because of + smaller parent pointer + */ + return (struct grub_xfs_dir_entry *) + (((char *) head) + sizeof(struct grub_xfs_dir_header) - + (head->largeino ? 0 : sizeof(grub_uint32_t))); +} + +static grub_uint8_t * +grub_xfs_inline_de_inopos(struct grub_xfs_data *data, + struct grub_xfs_dir_entry *de) +{ + return ((grub_uint8_t *)(de + 1)) + de->len - 1 + (data->hasftype ? 1 : 0); +} + +static struct grub_xfs_dir_entry * +grub_xfs_inline_next_de(struct grub_xfs_data *data, + struct grub_xfs_dir_header *head, + struct grub_xfs_dir_entry *de) +{ + char *p = (char *)de + sizeof(struct grub_xfs_dir_entry) - 1 + de->len; + + p += head->largeino ? sizeof(grub_uint64_t) : sizeof(grub_uint32_t); + if (data->hasftype) + p++; + + return (struct grub_xfs_dir_entry *)p; +} + +static struct grub_xfs_dirblock_tail * +grub_xfs_dir_tail(struct grub_xfs_data *data, void *dirblock) +{ + int dirblksize = 1 << (data->sblock.log2_bsize + data->sblock.log2_dirblk); + + return (struct grub_xfs_dirblock_tail *) + ((char *)dirblock + dirblksize - sizeof (struct grub_xfs_dirblock_tail)); +} + +static struct grub_xfs_dir2_entry * +grub_xfs_first_de(struct grub_xfs_data *data, void *dirblock) +{ + if (data->hascrc) + return (struct grub_xfs_dir2_entry *)((char *)dirblock + 64); + return (struct grub_xfs_dir2_entry *)((char *)dirblock + 16); +} + +static struct grub_xfs_dir2_entry * +grub_xfs_next_de(struct grub_xfs_data *data, struct grub_xfs_dir2_entry *de) +{ + int size = sizeof (struct grub_xfs_dir2_entry) + de->len + 2 /* Tag */; + + if (data->hasftype) + size++; /* File type */ + return (struct grub_xfs_dir2_entry *)(((char *)de) + ALIGN_UP(size, 8)); +} + +/* This should return void * but XFS code is error-prone with alignment, so + return char to retain cast-align. + */ +static char * +grub_xfs_btree_keys(struct grub_xfs_data *data, + struct grub_xfs_btree_node *leaf) +{ + char *keys = (char *)(leaf + 1); + + if (data->hascrc) + keys += 48; /* skip crc, uuid, ... */ + return keys; +} + +static grub_err_t +grub_xfs_read_inode (struct grub_xfs_data *data, grub_uint64_t ino, + struct grub_xfs_inode *inode) +{ + grub_uint64_t block = grub_xfs_inode_block (data, ino); + int offset = grub_xfs_inode_offset (data, ino); + + grub_dprintf("xfs", "Reading inode (%" PRIuGRUB_UINT64_T ") - %" PRIuGRUB_UINT64_T ", %d\n", + ino, block, offset); + /* Read the inode. */ + if (grub_disk_read (data->disk, block, offset, grub_xfs_inode_size(data), + inode)) + return grub_errno; + + if (grub_strncmp ((char *) inode->magic, "IN", 2)) + return grub_error (GRUB_ERR_BAD_FS, "not a correct XFS inode"); + + return 0; +} + +static grub_uint64_t +get_fsb (const void *keys, int idx) +{ + const char *p = (const char *) keys + sizeof(grub_uint64_t) * idx; + return grub_be_to_cpu64 (grub_get_unaligned64 (p)); +} + +static grub_disk_addr_t +grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +{ + struct grub_xfs_btree_node *leaf = 0; + int ex, nrec; + struct grub_xfs_extent *exts; + grub_uint64_t ret = 0; + + if (node->inode.format == XFS_INODE_FORMAT_BTREE) + { + struct grub_xfs_btree_root *root; + const char *keys; + int recoffset; + + leaf = grub_malloc (node->data->bsize); + if (leaf == 0) + return 0; + + root = (struct grub_xfs_btree_root *) grub_xfs_inode_data(&node->inode); + nrec = grub_be_to_cpu16 (root->numrecs); + keys = (char *) &root->keys[0]; + if (node->inode.fork_offset) + recoffset = (node->inode.fork_offset - 1) / 2; + else + recoffset = (grub_xfs_inode_size(node->data) + - ((char *) keys - (char *) &node->inode)) + / (2 * sizeof (grub_uint64_t)); + do + { + int i; + + for (i = 0; i < nrec; i++) + { + if (fileblock < get_fsb(keys, i)) + break; + } + + /* Sparse block. */ + if (i == 0) + { + grub_free (leaf); + return 0; + } + + if (grub_disk_read (node->data->disk, + GRUB_XFS_FSB_TO_BLOCK (node->data, get_fsb (keys, i - 1 + recoffset)) << (node->data->sblock.log2_bsize - GRUB_DISK_SECTOR_BITS), + 0, node->data->bsize, leaf)) + return 0; + + if ((!node->data->hascrc && + grub_strncmp ((char *) leaf->magic, "BMAP", 4)) || + (node->data->hascrc && + grub_strncmp ((char *) leaf->magic, "BMA3", 4))) + { + grub_free (leaf); + grub_error (GRUB_ERR_BAD_FS, "not a correct XFS BMAP node"); + return 0; + } + + nrec = grub_be_to_cpu16 (leaf->numrecs); + keys = grub_xfs_btree_keys(node->data, leaf); + recoffset = ((node->data->bsize - ((char *) keys + - (char *) leaf)) + / (2 * sizeof (grub_uint64_t))); + } + while (leaf->level); + exts = (struct grub_xfs_extent *) keys; + } + else if (node->inode.format == XFS_INODE_FORMAT_EXT) + { + nrec = grub_be_to_cpu32 (node->inode.nextents); + exts = (struct grub_xfs_extent *) grub_xfs_inode_data(&node->inode); + } + else + { + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "XFS does not support inode format %d yet", + node->inode.format); + return 0; + } + + /* Iterate over each extent to figure out which extent has + the block we are looking for. */ + for (ex = 0; ex < nrec; ex++) + { + grub_uint64_t start = GRUB_XFS_EXTENT_BLOCK (exts, ex); + grub_uint64_t offset = GRUB_XFS_EXTENT_OFFSET (exts, ex); + grub_uint64_t size = GRUB_XFS_EXTENT_SIZE (exts, ex); + + /* Sparse block. */ + if (fileblock < offset) + break; + else if (fileblock < offset + size) + { + ret = (fileblock - offset + start); + break; + } + } + + grub_free (leaf); + + return GRUB_XFS_FSB_TO_BLOCK(node->data, ret); +} + + +/* Read LEN bytes from the file described by DATA starting with byte + POS. Return the amount of read bytes in READ. */ +static grub_ssize_t +grub_xfs_read_file (grub_fshelp_node_t node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf, grub_uint32_t header_size) +{ + return grub_fshelp_read_file (node->data->disk, node, + read_hook, read_hook_data, + pos, len, buf, grub_xfs_read_block, + grub_be_to_cpu64 (node->inode.size) + header_size, + node->data->sblock.log2_bsize + - GRUB_DISK_SECTOR_BITS, 0); +} + + +static char * +grub_xfs_read_symlink (grub_fshelp_node_t node) +{ + grub_ssize_t size = grub_be_to_cpu64 (node->inode.size); + + if (size < 0) + { + grub_error (GRUB_ERR_BAD_FS, "invalid symlink"); + return 0; + } + + switch (node->inode.format) + { + case XFS_INODE_FORMAT_INO: + return grub_strndup (grub_xfs_inode_data(&node->inode), size); + + case XFS_INODE_FORMAT_EXT: + { + char *symlink; + grub_ssize_t numread; + int off = 0; + + if (node->data->hascrc) + off = 56; + + symlink = grub_malloc (size + 1); + if (!symlink) + return 0; + + node->inode.size = grub_be_to_cpu64 (size + off); + numread = grub_xfs_read_file (node, 0, 0, off, size, symlink, off); + if (numread != size) + { + grub_free (symlink); + return 0; + } + symlink[size] = '\0'; + return symlink; + } + } + + return 0; +} + + +static enum grub_fshelp_filetype +grub_xfs_mode_to_filetype (grub_uint16_t mode) +{ + if ((grub_be_to_cpu16 (mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_DIRECTORY) + return GRUB_FSHELP_DIR; + else if ((grub_be_to_cpu16 (mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_SYMLINK) + return GRUB_FSHELP_SYMLINK; + else if ((grub_be_to_cpu16 (mode) + & FILETYPE_INO_MASK) == FILETYPE_INO_REG) + return GRUB_FSHELP_REG; + return GRUB_FSHELP_UNKNOWN; +} + + +/* Context for grub_xfs_iterate_dir. */ +struct grub_xfs_iterate_dir_ctx +{ + grub_fshelp_iterate_dir_hook_t hook; + void *hook_data; + struct grub_fshelp_node *diro; +}; + +/* Helper for grub_xfs_iterate_dir. */ +static int iterate_dir_call_hook (grub_uint64_t ino, const char *filename, + struct grub_xfs_iterate_dir_ctx *ctx) +{ + struct grub_fshelp_node *fdiro; + grub_err_t err; + + fdiro = grub_malloc (grub_xfs_fshelp_size(ctx->diro->data) + 1); + if (!fdiro) + { + grub_print_error (); + return 0; + } + + /* The inode should be read, otherwise the filetype can + not be determined. */ + fdiro->ino = ino; + fdiro->inode_read = 1; + fdiro->data = ctx->diro->data; + err = grub_xfs_read_inode (ctx->diro->data, ino, &fdiro->inode); + if (err) + { + grub_print_error (); + return 0; + } + + return ctx->hook (filename, grub_xfs_mode_to_filetype (fdiro->inode.mode), + fdiro, ctx->hook_data); +} + +static int +grub_xfs_iterate_dir (grub_fshelp_node_t dir, + grub_fshelp_iterate_dir_hook_t hook, void *hook_data) +{ + struct grub_fshelp_node *diro = (struct grub_fshelp_node *) dir; + struct grub_xfs_iterate_dir_ctx ctx = { + .hook = hook, + .hook_data = hook_data, + .diro = diro + }; + + switch (diro->inode.format) + { + case XFS_INODE_FORMAT_INO: + { + struct grub_xfs_dir_header *head = (struct grub_xfs_dir_header *) grub_xfs_inode_data(&diro->inode); + struct grub_xfs_dir_entry *de = grub_xfs_inline_de(head); + int smallino = !head->largeino; + int i; + grub_uint64_t parent; + + /* If small inode numbers are used to pack the direntry, the + parent inode number is small too. */ + if (smallino) + parent = grub_be_to_cpu32 (head->parent.i4); + else + parent = grub_be_to_cpu64 (head->parent.i8); + + /* Synthesize the direntries for `.' and `..'. */ + if (iterate_dir_call_hook (diro->ino, ".", &ctx)) + return 1; + + if (iterate_dir_call_hook (parent, "..", &ctx)) + return 1; + + for (i = 0; i < head->count; i++) + { + grub_uint64_t ino; + grub_uint8_t *inopos = grub_xfs_inline_de_inopos(dir->data, de); + grub_uint8_t c; + + /* inopos might be unaligned. */ + if (smallino) + ino = (((grub_uint32_t) inopos[0]) << 24) + | (((grub_uint32_t) inopos[1]) << 16) + | (((grub_uint32_t) inopos[2]) << 8) + | (((grub_uint32_t) inopos[3]) << 0); + else + ino = (((grub_uint64_t) inopos[0]) << 56) + | (((grub_uint64_t) inopos[1]) << 48) + | (((grub_uint64_t) inopos[2]) << 40) + | (((grub_uint64_t) inopos[3]) << 32) + | (((grub_uint64_t) inopos[4]) << 24) + | (((grub_uint64_t) inopos[5]) << 16) + | (((grub_uint64_t) inopos[6]) << 8) + | (((grub_uint64_t) inopos[7]) << 0); + + c = de->name[de->len]; + de->name[de->len] = '\0'; + if (iterate_dir_call_hook (ino, de->name, &ctx)) + { + de->name[de->len] = c; + return 1; + } + de->name[de->len] = c; + + de = grub_xfs_inline_next_de(dir->data, head, de); + } + break; + } + + case XFS_INODE_FORMAT_BTREE: + case XFS_INODE_FORMAT_EXT: + { + grub_ssize_t numread; + char *dirblock; + grub_uint64_t blk; + int dirblk_size, dirblk_log2; + + dirblk_log2 = (dir->data->sblock.log2_bsize + + dir->data->sblock.log2_dirblk); + dirblk_size = 1 << dirblk_log2; + + dirblock = grub_malloc (dirblk_size); + if (! dirblock) + return 0; + + /* Iterate over every block the directory has. */ + for (blk = 0; + blk < (grub_be_to_cpu64 (dir->inode.size) + >> dirblk_log2); + blk++) + { + struct grub_xfs_dir2_entry *direntry = + grub_xfs_first_de(dir->data, dirblock); + int entries; + struct grub_xfs_dirblock_tail *tail = + grub_xfs_dir_tail(dir->data, dirblock); + + numread = grub_xfs_read_file (dir, 0, 0, + blk << dirblk_log2, + dirblk_size, dirblock, 0); + if (numread != dirblk_size) + return 0; + + entries = (grub_be_to_cpu32 (tail->leaf_count) + - grub_be_to_cpu32 (tail->leaf_stale)); + + if (!entries) + continue; + + /* Iterate over all entries within this block. */ + while ((char *)direntry < (char *)tail) + { + grub_uint8_t *freetag; + char *filename; + + freetag = (grub_uint8_t *) direntry; + + if (grub_get_unaligned16 (freetag) == 0XFFFF) + { + grub_uint8_t *skip = (freetag + sizeof (grub_uint16_t)); + + /* This entry is not used, go to the next one. */ + direntry = (struct grub_xfs_dir2_entry *) + (((char *)direntry) + + grub_be_to_cpu16 (grub_get_unaligned16 (skip))); + + continue; + } + + filename = (char *)(direntry + 1); + /* The byte after the filename is for the filetype, padding, or + tag, which is not used by GRUB. So it can be overwritten. */ + filename[direntry->len] = '\0'; + + if (iterate_dir_call_hook (grub_be_to_cpu64(direntry->inode), + filename, &ctx)) + { + grub_free (dirblock); + return 1; + } + + /* Check if last direntry in this block is + reached. */ + entries--; + if (!entries) + break; + + /* Select the next directory entry. */ + direntry = grub_xfs_next_de(dir->data, direntry); + } + } + grub_free (dirblock); + break; + } + + default: + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "XFS does not support inode format %d yet", + diro->inode.format); + } + return 0; +} + + +static struct grub_xfs_data * +grub_xfs_mount (grub_disk_t disk) +{ + struct grub_xfs_data *data = 0; + grub_size_t sz; + + data = grub_zalloc (sizeof (struct grub_xfs_data)); + if (!data) + return 0; + + grub_dprintf("xfs", "Reading sb\n"); + /* Read the superblock. */ + if (grub_disk_read (disk, 0, 0, + sizeof (struct grub_xfs_sblock), &data->sblock)) + goto fail; + + if (!grub_xfs_sb_valid(data)) + goto fail; + + if (grub_xfs_sb_needs_repair (data)) + grub_dprintf ("xfs", "XFS filesystem needs repair, boot may fail\n"); + + if (grub_add (grub_xfs_inode_size (data), + sizeof (struct grub_xfs_data) - sizeof (struct grub_xfs_inode) + 1, &sz)) + goto fail; + + data = grub_realloc (data, sz); + + if (! data) + goto fail; + + data->diropen.data = data; + data->diropen.ino = grub_be_to_cpu64(data->sblock.rootino); + data->diropen.inode_read = 1; + data->bsize = grub_be_to_cpu32 (data->sblock.bsize); + data->agsize = grub_be_to_cpu32 (data->sblock.agsize); + data->hasftype = grub_xfs_sb_hasftype(data); + data->hascrc = grub_xfs_sb_hascrc(data); + + data->disk = disk; + data->pos = 0; + grub_dprintf("xfs", "Reading root ino %" PRIuGRUB_UINT64_T "\n", + grub_cpu_to_be64(data->sblock.rootino)); + + grub_xfs_read_inode (data, data->diropen.ino, &data->diropen.inode); + + return data; + fail: + + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not an XFS filesystem"); + + grub_free (data); + + return 0; +} + + +/* Context for grub_xfs_dir. */ +struct grub_xfs_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; +}; + +/* Bigtime inodes helpers. */ +#define XFS_BIGTIME_EPOCH_OFFSET (-(grub_int64_t) GRUB_INT32_MIN) + +static int grub_xfs_inode_has_bigtime (const struct grub_xfs_inode *inode) +{ + return inode->version >= 3 && + (inode->flags2 & grub_cpu_to_be64_compile_time (XFS_DIFLAG2_BIGTIME)); +} + +static grub_int64_t +grub_xfs_get_inode_time (struct grub_xfs_inode *inode) +{ + struct grub_xfs_time_legacy *lts; + + if (grub_xfs_inode_has_bigtime (inode)) + return grub_divmod64 (grub_be_to_cpu64 (inode->mtime), NSEC_PER_SEC, NULL) - XFS_BIGTIME_EPOCH_OFFSET; + + lts = (struct grub_xfs_time_legacy *) &inode->mtime; + return grub_be_to_cpu32 (lts->sec); +} + +/* Helper for grub_xfs_dir. */ +static int +grub_xfs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node, void *data) +{ + struct grub_xfs_dir_ctx *ctx = data; + struct grub_dirhook_info info; + + grub_memset (&info, 0, sizeof (info)); + if (node->inode_read) + { + info.mtimeset = 1; + info.mtime = grub_xfs_get_inode_time (&node->inode); + } + info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); + grub_free (node); + return ctx->hook (filename, &info, ctx->hook_data); +} + +static grub_err_t +grub_xfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_xfs_dir_ctx ctx = { hook, hook_data }; + struct grub_xfs_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_xfs_mount (device->disk); + if (!data) + goto mount_fail; + + grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_xfs_iterate_dir, + grub_xfs_read_symlink, GRUB_FSHELP_DIR); + if (grub_errno) + goto fail; + + grub_xfs_iterate_dir (fdiro, grub_xfs_dir_iter, &ctx); + + fail: + if (fdiro != &data->diropen) + grub_free (fdiro); + grub_free (data); + + mount_fail: + + grub_dl_unref (my_mod); + + return grub_errno; +} + + +/* Open a file named NAME and initialize FILE. */ +static grub_err_t +grub_xfs_open (struct grub_file *file, const char *name) +{ + struct grub_xfs_data *data; + struct grub_fshelp_node *fdiro = 0; + + grub_dl_ref (my_mod); + + data = grub_xfs_mount (file->device->disk); + if (!data) + goto mount_fail; + + grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_xfs_iterate_dir, + grub_xfs_read_symlink, GRUB_FSHELP_REG); + if (grub_errno) + goto fail; + + if (!fdiro->inode_read) + { + grub_xfs_read_inode (data, fdiro->ino, &fdiro->inode); + if (grub_errno) + goto fail; + } + + if (fdiro != &data->diropen) + { + grub_memcpy (&data->diropen, fdiro, grub_xfs_fshelp_size(data)); + grub_free (fdiro); + } + + file->size = grub_be_to_cpu64 (data->diropen.inode.size); + file->data = data; + file->offset = 0; + + return 0; + + fail: + if (fdiro != &data->diropen) + grub_free (fdiro); + grub_free (data); + + mount_fail: + grub_dl_unref (my_mod); + + return grub_errno; +} + + +static grub_ssize_t +grub_xfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_xfs_data *data = + (struct grub_xfs_data *) file->data; + + return grub_xfs_read_file (&data->diropen, + file->read_hook, file->read_hook_data, + file->offset, len, buf, 0); +} + + +static grub_err_t +grub_xfs_close (grub_file_t file) +{ + grub_free (file->data); + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + + +static grub_err_t +grub_xfs_label (grub_device_t device, char **label) +{ + struct grub_xfs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_xfs_mount (disk); + if (data) + *label = grub_strndup ((char *) (data->sblock.label), 12); + else + *label = 0; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_xfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_xfs_data *data; + grub_disk_t disk = device->disk; + + grub_dl_ref (my_mod); + + data = grub_xfs_mount (disk); + if (data) + { + *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + grub_be_to_cpu16 (data->sblock.uuid[0]), + grub_be_to_cpu16 (data->sblock.uuid[1]), + grub_be_to_cpu16 (data->sblock.uuid[2]), + grub_be_to_cpu16 (data->sblock.uuid[3]), + grub_be_to_cpu16 (data->sblock.uuid[4]), + grub_be_to_cpu16 (data->sblock.uuid[5]), + grub_be_to_cpu16 (data->sblock.uuid[6]), + grub_be_to_cpu16 (data->sblock.uuid[7])); + } + else + *uuid = NULL; + + grub_dl_unref (my_mod); + + grub_free (data); + + return grub_errno; +} + + + +static struct grub_fs grub_xfs_fs = + { + .name = "xfs", + .fs_dir = grub_xfs_dir, + .fs_open = grub_xfs_open, + .fs_read = grub_xfs_read, + .fs_close = grub_xfs_close, + .fs_label = grub_xfs_label, + .fs_uuid = grub_xfs_uuid, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, + .blocklist_install = 1, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(xfs) +{ + grub_fs_register (&grub_xfs_fs); + my_mod = mod; +} + +GRUB_MOD_FINI(xfs) +{ + grub_fs_unregister (&grub_xfs_fs); +} diff --git a/grub-core/fs/zfs/zfs.c b/grub-core/fs/zfs/zfs.c new file mode 100644 index 0000000..cf4d2ab --- /dev/null +++ b/grub-core/fs/zfs/zfs.c @@ -0,0 +1,4406 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2009,2010,2011 Free Software Foundation, Inc. + * Copyright 2010 Sun Microsystems, Inc. + * Copyright (c) 2012 by Delphix. All rights reserved. + * + * 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/>. + */ +/* + * The zfs plug-in routines for GRUB are: + * + * zfs_mount() - locates a valid uberblock of the root pool and reads + * in its MOS at the memory address MOS. + * + * zfs_open() - locates a plain file object by following the MOS + * and places its dnode at the memory address DNODE. + * + * zfs_read() - read in the data blocks pointed by the DNODE. + * + */ + +#include <grub/err.h> +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/disk.h> +#include <grub/partition.h> +#include <grub/dl.h> +#include <grub/types.h> +#include <grub/zfs/zfs.h> +#include <grub/zfs/zio.h> +#include <grub/zfs/dnode.h> +#include <grub/zfs/uberblock_impl.h> +#include <grub/zfs/vdev_impl.h> +#include <grub/zfs/zio_checksum.h> +#include <grub/zfs/zap_impl.h> +#include <grub/zfs/zap_leaf.h> +#include <grub/zfs/zfs_znode.h> +#include <grub/zfs/dmu.h> +#include <grub/zfs/dmu_objset.h> +#include <grub/zfs/sa_impl.h> +#include <grub/zfs/dsl_dir.h> +#include <grub/zfs/dsl_dataset.h> +#include <grub/deflate.h> +#include <grub/crypto.h> +#include <grub/i18n.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define ZPOOL_PROP_BOOTFS "bootfs" + +/* + * For nvlist manipulation. (from nvpair.h) + */ +#define NV_ENCODE_NATIVE 0 +#define NV_ENCODE_XDR 1 +#define NV_BIG_ENDIAN 0 +#define NV_LITTLE_ENDIAN 1 +#define DATA_TYPE_UINT64 8 +#define DATA_TYPE_STRING 9 +#define DATA_TYPE_NVLIST 19 +#define DATA_TYPE_NVLIST_ARRAY 20 + +#ifndef GRUB_UTIL +static grub_dl_t my_mod; +#endif + +#define P2PHASE(x, align) ((x) & ((align) - 1)) + +static inline grub_disk_addr_t +DVA_OFFSET_TO_PHYS_SECTOR (grub_disk_addr_t offset) +{ + return ((offset + VDEV_LABEL_START_SIZE) >> SPA_MINBLOCKSHIFT); +} + +/* + * FAT ZAP data structures + */ +#define ZFS_CRC64_POLY 0xC96C5795D7870F42ULL /* ECMA-182, reflected form */ +static inline grub_uint64_t +ZAP_HASH_IDX (grub_uint64_t hash, grub_uint64_t n) +{ + return (((n) == 0) ? 0 : ((hash) >> (64 - (n)))); +} + +#define CHAIN_END 0xffff /* end of the chunk chain */ + +/* + * The amount of space within the chunk available for the array is: + * chunk size - space for type (1) - space for next pointer (2) + */ +#define ZAP_LEAF_ARRAY_BYTES (ZAP_LEAF_CHUNKSIZE - 3) + +static inline int +ZAP_LEAF_HASH_SHIFT (int bs) +{ + return bs - 5; +} + +static inline int +ZAP_LEAF_HASH_NUMENTRIES (int bs) +{ + return 1 << ZAP_LEAF_HASH_SHIFT(bs); +} + +static inline grub_size_t +LEAF_HASH (int bs, grub_uint64_t h, zap_leaf_phys_t *l) +{ + return ((ZAP_LEAF_HASH_NUMENTRIES (bs)-1) + & ((h) >> (64 - ZAP_LEAF_HASH_SHIFT (bs) - l->l_hdr.lh_prefix_len))); +} + +/* + * The amount of space available for chunks is: + * block size shift - hash entry size (2) * number of hash + * entries - header space (2*chunksize) + */ +static inline int +ZAP_LEAF_NUMCHUNKS (int bs) +{ + return (((1U << bs) - 2 * ZAP_LEAF_HASH_NUMENTRIES (bs)) / + ZAP_LEAF_CHUNKSIZE - 2); +} + +/* + * The chunks start immediately after the hash table. The end of the + * hash table is at l_hash + HASH_NUMENTRIES, which we simply cast to a + * chunk_t. + */ +static inline zap_leaf_chunk_t * +ZAP_LEAF_CHUNK (zap_leaf_phys_t *l, int bs, int idx) +{ + grub_properly_aligned_t *l_entries; + + l_entries = (grub_properly_aligned_t *) ALIGN_UP((grub_addr_t)l->l_hash, sizeof (grub_properly_aligned_t)); + return &((zap_leaf_chunk_t *) (l_entries + + (ZAP_LEAF_HASH_NUMENTRIES(bs) * 2) + / sizeof (grub_properly_aligned_t)))[idx]; +} + +static inline struct zap_leaf_entry * +ZAP_LEAF_ENTRY(zap_leaf_phys_t *l, int bs, int idx) +{ + return &ZAP_LEAF_CHUNK(l, bs, idx)->l_entry; +} + + +/* + * Decompression Entry - lzjb & lz4 + */ + +extern grub_err_t lzjb_decompress (void *, void *, grub_size_t, grub_size_t); + +extern grub_err_t lz4_decompress (void *, void *, grub_size_t, grub_size_t); + +typedef grub_err_t zfs_decomp_func_t (void *s_start, void *d_start, + grub_size_t s_len, grub_size_t d_len); +typedef struct decomp_entry +{ + const char *name; + zfs_decomp_func_t *decomp_func; +} decomp_entry_t; + +/* + * Signature for checksum functions. + */ +typedef void zio_checksum_t(const void *data, grub_uint64_t size, + grub_zfs_endian_t endian, zio_cksum_t *zcp); + +/* + * Information about each checksum function. + */ +typedef struct zio_checksum_info { + zio_checksum_t *ci_func; /* checksum function for each byteorder */ + int ci_correctable; /* number of correctable bits */ + int ci_eck; /* uses zio embedded checksum? */ + const char *ci_name; /* descriptive name */ +} zio_checksum_info_t; + +typedef struct dnode_end +{ + dnode_phys_t dn; + grub_zfs_endian_t endian; +} dnode_end_t; + +struct grub_zfs_device_desc +{ + enum { DEVICE_LEAF, DEVICE_MIRROR, DEVICE_RAIDZ } type; + grub_uint64_t id; + grub_uint64_t guid; + unsigned ashift; + unsigned max_children_ashift; + + /* Valid only for non-leafs. */ + unsigned n_children; + struct grub_zfs_device_desc *children; + + /* Valid only for RAIDZ. */ + unsigned nparity; + + /* Valid only for leaf devices. */ + grub_device_t dev; + grub_disk_addr_t vdev_phys_sector; + uberblock_t current_uberblock; + int original; +}; + +struct subvolume +{ + dnode_end_t mdn; + grub_uint64_t obj; + grub_uint64_t case_insensitive; + grub_size_t nkeys; + struct + { + grub_crypto_cipher_handle_t cipher; + grub_uint64_t txg; + grub_uint64_t algo; + } *keyring; +}; + +struct grub_zfs_data +{ + /* cache for a file block of the currently zfs_open()-ed file */ + char *file_buf; + grub_uint64_t file_start; + grub_uint64_t file_end; + + /* cache for a dnode block */ + dnode_phys_t *dnode_buf; + dnode_phys_t *dnode_mdn; + grub_uint64_t dnode_start; + grub_uint64_t dnode_end; + grub_zfs_endian_t dnode_endian; + + dnode_end_t mos; + dnode_end_t dnode; + struct subvolume subvol; + + struct grub_zfs_device_desc *devices_attached; + unsigned n_devices_attached; + unsigned n_devices_allocated; + struct grub_zfs_device_desc *device_original; + + uberblock_t current_uberblock; + + grub_uint64_t guid; +}; + +/* Context for grub_zfs_dir. */ +struct grub_zfs_dir_ctx +{ + grub_fs_dir_hook_t hook; + void *hook_data; + struct grub_zfs_data *data; +}; + +grub_err_t (*grub_zfs_decrypt) (grub_crypto_cipher_handle_t cipher, + grub_uint64_t algo, + void *nonce, + char *buf, grub_size_t size, + const grub_uint32_t *expected_mac, + grub_zfs_endian_t endian) = NULL; +grub_crypto_cipher_handle_t (*grub_zfs_load_key) (const struct grub_zfs_key *key, + grub_size_t keysize, + grub_uint64_t salt, + grub_uint64_t algo) = NULL; +/* + * List of pool features that the grub implementation of ZFS supports for + * read. Note that features that are only required for write do not need + * to be listed here since grub opens pools in read-only mode. + */ +#define MAX_SUPPORTED_FEATURE_STRLEN 50 +static const char *spa_feature_names[] = { + "org.illumos:lz4_compress", + "com.delphix:hole_birth", + "com.delphix:embedded_data", + "com.delphix:extensible_dataset", + "org.open-zfs:large_blocks", + NULL +}; + +static int +check_feature(const char *name, grub_uint64_t val, struct grub_zfs_dir_ctx *ctx); +static grub_err_t +check_mos_features(dnode_phys_t *mosmdn_phys,grub_zfs_endian_t endian,struct grub_zfs_data* data ); + +static grub_err_t +zlib_decompress (void *s, void *d, + grub_size_t slen, grub_size_t dlen) +{ + if (grub_zlib_decompress (s, slen, 0, d, dlen) == (grub_ssize_t) dlen) + return GRUB_ERR_NONE; + + if (!grub_errno) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "premature end of compressed"); + return grub_errno; +} + +static grub_err_t +zle_decompress (void *s, void *d, + grub_size_t slen, grub_size_t dlen) +{ + grub_uint8_t *iptr, *optr; + grub_size_t clen; + for (iptr = s, optr = d; iptr < (grub_uint8_t *) s + slen + && optr < (grub_uint8_t *) d + dlen;) + { + if (*iptr & 0x80) + clen = ((*iptr) & 0x7f) + 0x41; + else + clen = ((*iptr) & 0x3f) + 1; + if ((grub_ssize_t) clen > (grub_uint8_t *) d + dlen - optr) + clen = (grub_uint8_t *) d + dlen - optr; + if (*iptr & 0x40 || *iptr & 0x80) + { + grub_memset (optr, 0, clen); + iptr++; + optr += clen; + continue; + } + if ((grub_ssize_t) clen > (grub_uint8_t *) s + slen - iptr - 1) + clen = (grub_uint8_t *) s + slen - iptr - 1; + grub_memcpy (optr, iptr + 1, clen); + optr += clen; + iptr += clen + 1; + } + if (optr < (grub_uint8_t *) d + dlen) + grub_memset (optr, 0, (grub_uint8_t *) d + dlen - optr); + return GRUB_ERR_NONE; +} + +static decomp_entry_t decomp_table[ZIO_COMPRESS_FUNCTIONS] = { + {"inherit", NULL}, /* ZIO_COMPRESS_INHERIT */ + {"on", lzjb_decompress}, /* ZIO_COMPRESS_ON */ + {"off", NULL}, /* ZIO_COMPRESS_OFF */ + {"lzjb", lzjb_decompress}, /* ZIO_COMPRESS_LZJB */ + {"empty", NULL}, /* ZIO_COMPRESS_EMPTY */ + {"gzip-1", zlib_decompress}, /* ZIO_COMPRESS_GZIP1 */ + {"gzip-2", zlib_decompress}, /* ZIO_COMPRESS_GZIP2 */ + {"gzip-3", zlib_decompress}, /* ZIO_COMPRESS_GZIP3 */ + {"gzip-4", zlib_decompress}, /* ZIO_COMPRESS_GZIP4 */ + {"gzip-5", zlib_decompress}, /* ZIO_COMPRESS_GZIP5 */ + {"gzip-6", zlib_decompress}, /* ZIO_COMPRESS_GZIP6 */ + {"gzip-7", zlib_decompress}, /* ZIO_COMPRESS_GZIP7 */ + {"gzip-8", zlib_decompress}, /* ZIO_COMPRESS_GZIP8 */ + {"gzip-9", zlib_decompress}, /* ZIO_COMPRESS_GZIP9 */ + {"zle", zle_decompress}, /* ZIO_COMPRESS_ZLE */ + {"lz4", lz4_decompress}, /* ZIO_COMPRESS_LZ4 */ +}; + +static grub_err_t zio_read_data (blkptr_t * bp, grub_zfs_endian_t endian, + void *buf, struct grub_zfs_data *data); + +/* + * Our own version of log2(). Same thing as highbit()-1. + */ +static int +zfs_log2 (grub_uint64_t num) +{ + int i = 0; + + while (num > 1) + { + i++; + num = num >> 1; + } + + return i; +} + +/* Checksum Functions */ +static void +zio_checksum_off (const void *buf __attribute__ ((unused)), + grub_uint64_t size __attribute__ ((unused)), + grub_zfs_endian_t endian __attribute__ ((unused)), + zio_cksum_t * zcp) +{ + ZIO_SET_CHECKSUM (zcp, 0, 0, 0, 0); +} + +/* Checksum Table and Values */ +static zio_checksum_info_t zio_checksum_table[ZIO_CHECKSUM_FUNCTIONS] = { + {NULL, 0, 0, "inherit"}, + {NULL, 0, 0, "on"}, + {zio_checksum_off, 0, 0, "off"}, + {zio_checksum_SHA256, 1, 1, "label"}, + {zio_checksum_SHA256, 1, 1, "gang_header"}, + {NULL, 0, 0, "zilog"}, + {fletcher_2, 0, 0, "fletcher2"}, + {fletcher_4, 1, 0, "fletcher4"}, + {zio_checksum_SHA256, 1, 0, "SHA256"}, + {NULL, 0, 0, "zilog2"}, + {zio_checksum_SHA256, 1, 0, "SHA256+MAC"}, +}; + +/* + * zio_checksum_verify: Provides support for checksum verification. + * + * Fletcher2, Fletcher4, and SHA256 are supported. + * + */ +static grub_err_t +zio_checksum_verify (zio_cksum_t zc, grub_uint32_t checksum, + grub_zfs_endian_t endian, + char *buf, grub_size_t size) +{ + zio_eck_t *zec = (zio_eck_t *) (buf + size) - 1; + zio_checksum_info_t *ci = &zio_checksum_table[checksum]; + zio_cksum_t actual_cksum, expected_cksum; + + if (checksum >= ZIO_CHECKSUM_FUNCTIONS || ci->ci_func == NULL) + { + grub_dprintf ("zfs", "unknown checksum function %d\n", checksum); + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "unknown checksum function %d", checksum); + } + + if (ci->ci_eck) + { + expected_cksum = zec->zec_cksum; + zec->zec_cksum = zc; + ci->ci_func (buf, size, endian, &actual_cksum); + zec->zec_cksum = expected_cksum; + zc = expected_cksum; + } + else + ci->ci_func (buf, size, endian, &actual_cksum); + + if (grub_memcmp (&actual_cksum, &zc, + checksum != ZIO_CHECKSUM_SHA256_MAC ? 32 : 20) != 0) + { + grub_dprintf ("zfs", "checksum %s verification failed\n", ci->ci_name); + grub_dprintf ("zfs", "actual checksum %016llx %016llx %016llx %016llx\n", + (unsigned long long) actual_cksum.zc_word[0], + (unsigned long long) actual_cksum.zc_word[1], + (unsigned long long) actual_cksum.zc_word[2], + (unsigned long long) actual_cksum.zc_word[3]); + grub_dprintf ("zfs", "expected checksum %016llx %016llx %016llx %016llx\n", + (unsigned long long) zc.zc_word[0], + (unsigned long long) zc.zc_word[1], + (unsigned long long) zc.zc_word[2], + (unsigned long long) zc.zc_word[3]); + return grub_error (GRUB_ERR_BAD_FS, N_("checksum verification failed")); + } + + return GRUB_ERR_NONE; +} + +/* + * vdev_uberblock_compare takes two uberblock structures and returns an integer + * indicating the more recent of the two. + * Return Value = 1 if ub2 is more recent + * Return Value = -1 if ub1 is more recent + * The most recent uberblock is determined using its transaction number and + * timestamp. The uberblock with the highest transaction number is + * considered "newer". If the transaction numbers of the two blocks match, the + * timestamps are compared to determine the "newer" of the two. + */ +static int +vdev_uberblock_compare (uberblock_t * ub1, uberblock_t * ub2) +{ + grub_zfs_endian_t ub1_endian, ub2_endian; + if (grub_zfs_to_cpu64 (ub1->ub_magic, GRUB_ZFS_LITTLE_ENDIAN) + == UBERBLOCK_MAGIC) + ub1_endian = GRUB_ZFS_LITTLE_ENDIAN; + else + ub1_endian = GRUB_ZFS_BIG_ENDIAN; + if (grub_zfs_to_cpu64 (ub2->ub_magic, GRUB_ZFS_LITTLE_ENDIAN) + == UBERBLOCK_MAGIC) + ub2_endian = GRUB_ZFS_LITTLE_ENDIAN; + else + ub2_endian = GRUB_ZFS_BIG_ENDIAN; + + if (grub_zfs_to_cpu64 (ub1->ub_txg, ub1_endian) + < grub_zfs_to_cpu64 (ub2->ub_txg, ub2_endian)) + return -1; + if (grub_zfs_to_cpu64 (ub1->ub_txg, ub1_endian) + > grub_zfs_to_cpu64 (ub2->ub_txg, ub2_endian)) + return 1; + + if (grub_zfs_to_cpu64 (ub1->ub_timestamp, ub1_endian) + < grub_zfs_to_cpu64 (ub2->ub_timestamp, ub2_endian)) + return -1; + if (grub_zfs_to_cpu64 (ub1->ub_timestamp, ub1_endian) + > grub_zfs_to_cpu64 (ub2->ub_timestamp, ub2_endian)) + return 1; + + return 0; +} + +/* + * Three pieces of information are needed to verify an uberblock: the magic + * number, the version number, and the checksum. + * + * Currently Implemented: version number, magic number, checksum + * + */ +static grub_err_t +uberblock_verify (uberblock_phys_t * ub, grub_uint64_t offset, + grub_size_t s) +{ + uberblock_t *uber = &ub->ubp_uberblock; + grub_err_t err; + grub_zfs_endian_t endian = GRUB_ZFS_UNKNOWN_ENDIAN; + zio_cksum_t zc; + + if (grub_zfs_to_cpu64 (uber->ub_magic, GRUB_ZFS_LITTLE_ENDIAN) + == UBERBLOCK_MAGIC + && SPA_VERSION_IS_SUPPORTED(grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_LITTLE_ENDIAN))) + endian = GRUB_ZFS_LITTLE_ENDIAN; + + if (grub_zfs_to_cpu64 (uber->ub_magic, GRUB_ZFS_BIG_ENDIAN) == UBERBLOCK_MAGIC + && SPA_VERSION_IS_SUPPORTED(grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_BIG_ENDIAN))) + endian = GRUB_ZFS_BIG_ENDIAN; + + if (endian == GRUB_ZFS_UNKNOWN_ENDIAN) + return grub_error (GRUB_ERR_BAD_FS, "invalid uberblock magic"); + + grub_memset (&zc, 0, sizeof (zc)); + + zc.zc_word[0] = grub_cpu_to_zfs64 (offset, endian); + err = zio_checksum_verify (zc, ZIO_CHECKSUM_LABEL, endian, + (char *) ub, s); + + return err; +} + +/* + * Find the best uberblock. + * Return: + * Success - Pointer to the best uberblock. + * Failure - NULL + */ +static uberblock_phys_t * +find_bestub (uberblock_phys_t * ub_array, + const struct grub_zfs_device_desc *desc) +{ + uberblock_phys_t *ubbest = NULL, *ubptr; + int i; + grub_disk_addr_t offset; + grub_err_t err = GRUB_ERR_NONE; + int ub_shift; + + ub_shift = desc->ashift; + if (ub_shift < VDEV_UBERBLOCK_SHIFT) + ub_shift = VDEV_UBERBLOCK_SHIFT; + + for (i = 0; i < (VDEV_UBERBLOCK_RING >> ub_shift); i++) + { + offset = (desc->vdev_phys_sector << SPA_MINBLOCKSHIFT) + VDEV_PHYS_SIZE + + (i << ub_shift); + + ubptr = (uberblock_phys_t *) ((grub_properly_aligned_t *) ub_array + + ((i << ub_shift) + / sizeof (grub_properly_aligned_t))); + err = uberblock_verify (ubptr, offset, (grub_size_t) 1 << ub_shift); + if (err) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + if (ubbest == NULL + || vdev_uberblock_compare (&(ubptr->ubp_uberblock), + &(ubbest->ubp_uberblock)) > 0) + ubbest = ubptr; + } + if (!ubbest) + grub_errno = err; + + return ubbest; +} + +static inline grub_size_t +get_psize (blkptr_t * bp, grub_zfs_endian_t endian) +{ + return ((((grub_zfs_to_cpu64 ((bp)->blk_prop, endian) >> 16) & 0xffff) + 1) + << SPA_MINBLOCKSHIFT); +} + +static grub_uint64_t +dva_get_offset (const dva_t *dva, grub_zfs_endian_t endian) +{ + grub_dprintf ("zfs", "dva=%llx, %llx\n", + (unsigned long long) dva->dva_word[0], + (unsigned long long) dva->dva_word[1]); + return grub_zfs_to_cpu64 ((dva)->dva_word[1], + endian) << SPA_MINBLOCKSHIFT; +} + +static grub_err_t +zfs_fetch_nvlist (struct grub_zfs_device_desc *diskdesc, char **nvlist) +{ + grub_err_t err; + + *nvlist = 0; + + if (!diskdesc->dev) + return grub_error (GRUB_ERR_BUG, "member drive unknown"); + + *nvlist = grub_malloc (VDEV_PHYS_SIZE); + + /* Read in the vdev name-value pair list (112K). */ + err = grub_disk_read (diskdesc->dev->disk, diskdesc->vdev_phys_sector, 0, + VDEV_PHYS_SIZE, *nvlist); + if (err) + { + grub_free (*nvlist); + *nvlist = 0; + return err; + } + return GRUB_ERR_NONE; +} + +static grub_err_t +fill_vdev_info_real (struct grub_zfs_data *data, + const char *nvlist, + struct grub_zfs_device_desc *fill, + struct grub_zfs_device_desc *insert, + int *inserted, + unsigned ashift) +{ + char *type; + + type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE); + + if (!type) + return grub_errno; + + if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "id", &(fill->id))) + { + grub_free (type); + return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id"); + } + + if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "guid", &(fill->guid))) + { + grub_free (type); + return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id"); + } + + { + grub_uint64_t par; + if (grub_zfs_nvlist_lookup_uint64 (nvlist, "ashift", &par)) + fill->ashift = par; + else if (ashift != 0xffffffff) + fill->ashift = ashift; + else + { + grub_free (type); + return grub_error (GRUB_ERR_BAD_FS, "couldn't find ashift"); + } + } + + fill->max_children_ashift = 0; + + if (grub_strcmp (type, VDEV_TYPE_DISK) == 0 + || grub_strcmp (type, VDEV_TYPE_FILE) == 0) + { + fill->type = DEVICE_LEAF; + + if (!fill->dev && fill->guid == insert->guid) + { + fill->dev = insert->dev; + fill->vdev_phys_sector = insert->vdev_phys_sector; + fill->current_uberblock = insert->current_uberblock; + fill->original = insert->original; + if (!data->device_original) + data->device_original = fill; + insert->ashift = fill->ashift; + *inserted = 1; + } + + grub_free (type); + + return GRUB_ERR_NONE; + } + + if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0 + || grub_strcmp (type, VDEV_TYPE_RAIDZ) == 0) + { + int nelm, i; + + if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0) + fill->type = DEVICE_MIRROR; + else + { + grub_uint64_t par; + fill->type = DEVICE_RAIDZ; + if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "nparity", &par)) + { + grub_free (type); + return grub_error (GRUB_ERR_BAD_FS, "couldn't find raidz parity"); + } + fill->nparity = par; + } + + nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm (nvlist, + ZPOOL_CONFIG_CHILDREN); + + if (nelm <= 0) + { + grub_free (type); + return grub_error (GRUB_ERR_BAD_FS, "incorrect mirror VDEV"); + } + + if (!fill->children) + { + fill->n_children = nelm; + + fill->children = grub_zalloc (fill->n_children + * sizeof (fill->children[0])); + } + + for (i = 0; i < nelm; i++) + { + char *child; + grub_err_t err; + + child = grub_zfs_nvlist_lookup_nvlist_array + (nvlist, ZPOOL_CONFIG_CHILDREN, i); + + err = fill_vdev_info_real (data, child, &fill->children[i], insert, + inserted, fill->ashift); + + grub_free (child); + + if (err) + { + grub_free (type); + return err; + } + if (fill->children[i].ashift > fill->max_children_ashift) + fill->max_children_ashift = fill->children[i].ashift; + } + grub_free (type); + return GRUB_ERR_NONE; + } + + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "vdev %s isn't supported", type); + grub_free (type); + return grub_errno; +} + +static grub_err_t +fill_vdev_info (struct grub_zfs_data *data, + char *nvlist, struct grub_zfs_device_desc *diskdesc, + int *inserted) +{ + grub_uint64_t id; + unsigned i; + + *inserted = 0; + + if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "id", &id)) + return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id"); + + for (i = 0; i < data->n_devices_attached; i++) + if (data->devices_attached[i].id == id) + return fill_vdev_info_real (data, nvlist, &data->devices_attached[i], + diskdesc, inserted, 0xffffffff); + + data->n_devices_attached++; + if (data->n_devices_attached > data->n_devices_allocated) + { + void *tmp; + grub_size_t sz; + + if (grub_mul (data->n_devices_attached, 2, &data->n_devices_allocated) || + grub_add (data->n_devices_allocated, 1, &data->n_devices_allocated) || + grub_mul (data->n_devices_allocated, sizeof (data->devices_attached[0]), &sz)) + return GRUB_ERR_OUT_OF_RANGE; + + data->devices_attached = grub_realloc (tmp = data->devices_attached, sz); + if (!data->devices_attached) + { + data->devices_attached = tmp; + return grub_errno; + } + } + + grub_memset (&data->devices_attached[data->n_devices_attached - 1], + 0, sizeof (data->devices_attached[data->n_devices_attached - 1])); + + return fill_vdev_info_real (data, nvlist, + &data->devices_attached[data->n_devices_attached - 1], + diskdesc, inserted, 0xffffffff); +} + +/* + * For a given XDR packed nvlist, verify the first 4 bytes and move on. + * + * An XDR packed nvlist is encoded as (comments from nvs_xdr_create) : + * + * encoding method/host endian (4 bytes) + * nvl_version (4 bytes) + * nvl_nvflag (4 bytes) + * encoded nvpairs: + * encoded size of the nvpair (4 bytes) + * decoded size of the nvpair (4 bytes) + * name string size (4 bytes) + * name string data (sizeof(NV_ALIGN4(string)) + * data type (4 bytes) + * # of elements in the nvpair (4 bytes) + * data + * 2 zero's for the last nvpair + * (end of the entire list) (8 bytes) + * + */ + +/* + * The nvlist_next_nvpair() function returns a handle to the next nvpair in the + * list following nvpair. If nvpair is NULL, the first pair is returned. If + * nvpair is the last pair in the nvlist, NULL is returned. + */ +static const char * +nvlist_next_nvpair (const char *nvl, const char *nvpair) +{ + const char *nvp; + int encode_size; + int name_len; + if (nvl == NULL) + return NULL; + + if (nvpair == NULL) + { + /* skip over header, nvl_version and nvl_nvflag */ + nvpair = nvl + 4 * 3; + } + else + { + /* skip to the next nvpair */ + encode_size = grub_be_to_cpu32 (grub_get_unaligned32(nvpair)); + nvpair += encode_size; + /*If encode_size equals 0 nvlist_next_nvpair would return + * the same pair received in input, leading to an infinite loop. + * If encode_size is less than 0, this will move the pointer + * backwards, *possibly* examinining two times the same nvpair + * and potentially getting into an infinite loop. */ + if(encode_size <= 0) + { + grub_dprintf ("zfs", "nvpair with size <= 0\n"); + grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); + return NULL; + } + } + /* 8 bytes of 0 marks the end of the list */ + if (grub_get_unaligned64 (nvpair) == 0) + return NULL; + /*consistency checks*/ + if (nvpair + 4 * 3 >= nvl + VDEV_PHYS_SIZE) + { + grub_dprintf ("zfs", "nvlist overflow\n"); + grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); + return NULL; + } + encode_size = grub_be_to_cpu32 (grub_get_unaligned32(nvpair)); + + nvp = nvpair + 4*2; + name_len = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); + nvp += 4; + + nvp = nvp + ((name_len + 3) & ~3); // align + if (nvp + 4 >= nvl + VDEV_PHYS_SIZE + || encode_size < 0 + || nvp + 4 + encode_size > nvl + VDEV_PHYS_SIZE) + { + grub_dprintf ("zfs", "nvlist overflow\n"); + grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); + return NULL; + } + /* end consistency checks */ + + return nvpair; +} + +/* + * This function returns 0 on success and 1 on failure. On success, a string + * containing the name of nvpair is saved in buf. + */ +static int +nvpair_name (const char *nvp, char **buf, grub_size_t *buflen) +{ + /* skip over encode/decode size */ + nvp += 4 * 2; + + *buf = (char *) (nvp + 4); + *buflen = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); + + return 0; +} + +/* + * This function retrieves the value of the nvpair in the form of enumerated + * type data_type_t. + */ +static int +nvpair_type (const char *nvp) +{ + int name_len, type; + + /* skip over encode/decode size */ + nvp += 4 * 2; + + /* skip over name_len */ + name_len = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); + nvp += 4; + + /* skip over name */ + nvp = nvp + ((name_len + 3) & ~3); /* align */ + + type = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); + + return type; +} + +static int +nvpair_value (const char *nvp,char **val, + grub_size_t *size_out, grub_size_t *nelm_out) +{ + int name_len,nelm,encode_size; + + /* skip over encode/decode size */ + encode_size = grub_be_to_cpu32 (grub_get_unaligned32(nvp)); + nvp += 8; + + /* skip over name_len */ + name_len = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); + nvp += 4; + + /* skip over name */ + nvp = nvp + ((name_len + 3) & ~3); /* align */ + + /* skip over type */ + nvp += 4; + nelm = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); + nvp +=4; + if (nelm < 1) + { + grub_error (GRUB_ERR_BAD_FS, "empty nvpair"); + return 0; + } + *val = (char *) nvp; + *size_out = encode_size; + if (nelm_out) + *nelm_out = nelm; + + return 1; +} + +/* + * Check the disk label information and retrieve needed vdev name-value pairs. + * + */ +static grub_err_t +check_pool_label (struct grub_zfs_data *data, + struct grub_zfs_device_desc *diskdesc, + int *inserted, int original) +{ + grub_uint64_t pool_state, txg = 0; + char *nvlist,*features; +#if 0 + char *nv; +#endif + grub_uint64_t poolguid; + grub_uint64_t version; + int found; + grub_err_t err; + grub_zfs_endian_t endian; + vdev_phys_t *phys; + zio_cksum_t emptycksum; + + *inserted = 0; + + err = zfs_fetch_nvlist (diskdesc, &nvlist); + if (err) + return err; + + phys = (vdev_phys_t*) nvlist; + if (grub_zfs_to_cpu64 (phys->vp_zbt.zec_magic, + GRUB_ZFS_LITTLE_ENDIAN) + == ZEC_MAGIC) + endian = GRUB_ZFS_LITTLE_ENDIAN; + else if (grub_zfs_to_cpu64 (phys->vp_zbt.zec_magic, + GRUB_ZFS_BIG_ENDIAN) + == ZEC_MAGIC) + endian = GRUB_ZFS_BIG_ENDIAN; + else + { + grub_free (nvlist); + return grub_error (GRUB_ERR_BAD_FS, + "bad vdev_phys_t.vp_zbt.zec_magic number"); + } + /* Now check the integrity of the vdev_phys_t structure though checksum. */ + ZIO_SET_CHECKSUM(&emptycksum, diskdesc->vdev_phys_sector << 9, 0, 0, 0); + err = zio_checksum_verify (emptycksum, ZIO_CHECKSUM_LABEL, endian, + nvlist, VDEV_PHYS_SIZE); + if (err) + return err; + + grub_dprintf ("zfs", "check 2 passed\n"); + + found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_STATE, + &pool_state); + if (! found) + { + grub_free (nvlist); + if (! grub_errno) + grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_STATE " not found"); + return grub_errno; + } + grub_dprintf ("zfs", "check 3 passed\n"); + + if (pool_state == POOL_STATE_DESTROYED) + { + grub_free (nvlist); + return grub_error (GRUB_ERR_BAD_FS, "zpool is marked as destroyed"); + } + grub_dprintf ("zfs", "check 4 passed\n"); + + found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_TXG, &txg); + if (!found) + { + grub_free (nvlist); + if (! grub_errno) + grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_TXG " not found"); + return grub_errno; + } + grub_dprintf ("zfs", "check 6 passed\n"); + + /* not an active device */ + if (txg == 0) + { + grub_free (nvlist); + return grub_error (GRUB_ERR_BAD_FS, "zpool isn't active"); + } + grub_dprintf ("zfs", "check 7 passed\n"); + + found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_VERSION, + &version); + if (! found) + { + grub_free (nvlist); + if (! grub_errno) + grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_VERSION " not found"); + return grub_errno; + } + grub_dprintf ("zfs", "check 8 passed\n"); + + if (!SPA_VERSION_IS_SUPPORTED(version)) + { + grub_free (nvlist); + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "too new version %llu > %llu", + (unsigned long long) version, + (unsigned long long) SPA_VERSION_BEFORE_FEATURES); + } + grub_dprintf ("zfs", "check 9 passed\n"); + + found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_GUID, + &(diskdesc->guid)); + if (! found) + { + grub_free (nvlist); + if (! grub_errno) + grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_GUID " not found"); + return grub_errno; + } + + found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, + &poolguid); + if (! found) + { + grub_free (nvlist); + if (! grub_errno) + grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_GUID " not found"); + return grub_errno; + } + + grub_dprintf ("zfs", "check 11 passed\n"); + + if (original) + data->guid = poolguid; + + if (data->guid != poolguid) + return grub_error (GRUB_ERR_BAD_FS, "another zpool"); + + { + char *nv; + nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE); + + if (!nv) + { + grub_free (nvlist); + return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev tree"); + } + err = fill_vdev_info (data, nv, diskdesc, inserted); + if (err) + { + grub_free (nv); + grub_free (nvlist); + return err; + } + grub_free (nv); + } + grub_dprintf ("zfs", "check 10 passed\n"); + features = grub_zfs_nvlist_lookup_nvlist(nvlist, + ZPOOL_CONFIG_FEATURES_FOR_READ); + if (features) + { + const char *nvp=NULL; + char name[MAX_SUPPORTED_FEATURE_STRLEN + 1]; + char *nameptr; + grub_size_t namelen; + while ((nvp = nvlist_next_nvpair(features, nvp)) != NULL) + { + nvpair_name (nvp, &nameptr, &namelen); + if(namelen > MAX_SUPPORTED_FEATURE_STRLEN) + namelen = MAX_SUPPORTED_FEATURE_STRLEN; + grub_memcpy (name, nameptr, namelen); + name[namelen] = '\0'; + grub_dprintf("zfs","str=%s\n",name); + if (check_feature(name,1, NULL) != 0) + { + grub_dprintf("zfs","feature missing in check_pool_label:%s\n",name); + err= grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET," check_pool_label missing feature '%s' for read",name); + return err; + } + } + } + grub_dprintf ("zfs", "check 12 passed (feature flags)\n"); + grub_free (nvlist); + + return GRUB_ERR_NONE; +} + +static grub_err_t +scan_disk (grub_device_t dev, struct grub_zfs_data *data, + int original, int *inserted) +{ + int label = 0; + uberblock_phys_t *ub_array, *ubbest = NULL; + vdev_boot_header_t *bh; + grub_err_t err; + int vdevnum; + struct grub_zfs_device_desc desc; + + ub_array = grub_malloc (VDEV_UBERBLOCK_RING); + if (!ub_array) + return grub_errno; + + bh = grub_malloc (VDEV_BOOT_HEADER_SIZE); + if (!bh) + { + grub_free (ub_array); + return grub_errno; + } + + vdevnum = VDEV_LABELS; + + desc.dev = dev; + desc.original = original; + + /* Don't check back labels on CDROM. */ + if (grub_disk_native_sectors (dev->disk) == GRUB_DISK_SIZE_UNKNOWN) + vdevnum = VDEV_LABELS / 2; + + for (label = 0; ubbest == NULL && label < vdevnum; label++) + { + desc.vdev_phys_sector + = label * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT) + + ((VDEV_SKIP_SIZE + VDEV_BOOT_HEADER_SIZE) >> SPA_MINBLOCKSHIFT) + + (label < VDEV_LABELS / 2 ? 0 : + ALIGN_DOWN (grub_disk_native_sectors (dev->disk), sizeof (vdev_label_t)) + - VDEV_LABELS * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT)); + + /* Read in the uberblock ring (128K). */ + err = grub_disk_read (dev->disk, desc.vdev_phys_sector + + (VDEV_PHYS_SIZE >> SPA_MINBLOCKSHIFT), + 0, VDEV_UBERBLOCK_RING, (char *) ub_array); + if (err) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + grub_dprintf ("zfs", "label ok %d\n", label); + + err = check_pool_label (data, &desc, inserted, original); + if (err || !*inserted) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + + ubbest = find_bestub (ub_array, &desc); + if (!ubbest) + { + grub_dprintf ("zfs", "No uberblock found\n"); + grub_errno = GRUB_ERR_NONE; + continue; + } + + grub_memmove (&(desc.current_uberblock), + &ubbest->ubp_uberblock, sizeof (uberblock_t)); + if (original) + grub_memmove (&(data->current_uberblock), + &ubbest->ubp_uberblock, sizeof (uberblock_t)); + +#if 0 + if (find_best_root && + vdev_uberblock_compare (&ubbest->ubp_uberblock, + &(current_uberblock)) <= 0) + continue; +#endif + grub_free (ub_array); + grub_free (bh); + return GRUB_ERR_NONE; + } + + grub_free (ub_array); + grub_free (bh); + + return grub_error (GRUB_ERR_BAD_FS, "couldn't find a valid label"); +} + +/* Helper for scan_devices. */ +static int +scan_devices_iter (const char *name, void *hook_data) +{ + struct grub_zfs_data *data = hook_data; + grub_device_t dev; + grub_err_t err; + int inserted; + + dev = grub_device_open (name); + if (!dev) + return 0; + if (!dev->disk) + { + grub_device_close (dev); + return 0; + } + err = scan_disk (dev, data, 0, &inserted); + if (err == GRUB_ERR_BAD_FS) + { + grub_device_close (dev); + grub_errno = GRUB_ERR_NONE; + return 0; + } + if (err) + { + grub_device_close (dev); + grub_print_error (); + return 0; + } + + if (!inserted) + grub_device_close (dev); + + return 0; +} + +static grub_err_t +scan_devices (struct grub_zfs_data *data) +{ + grub_device_iterate (scan_devices_iter, data); + return GRUB_ERR_NONE; +} + +/* x**y. */ +static grub_uint8_t powx[255 * 2]; +/* Such an s that x**s = y */ +static int powx_inv[256]; +static const grub_uint8_t poly = 0x1d; + +/* perform the operation a ^= b * (x ** (known_idx * recovery_pow) ) */ +static inline void +xor_out (grub_uint8_t *a, const grub_uint8_t *b, grub_size_t s, + unsigned known_idx, unsigned recovery_pow) +{ + unsigned add; + + /* Simple xor. */ + if (known_idx == 0 || recovery_pow == 0) + { + grub_crypto_xor (a, a, b, s); + return; + } + add = (known_idx * recovery_pow) % 255; + for (;s--; b++, a++) + if (*b) + *a ^= powx[powx_inv[*b] + add]; +} + +static inline grub_uint8_t +gf_mul (grub_uint8_t a, grub_uint8_t b) +{ + if (a == 0 || b == 0) + return 0; + return powx[powx_inv[a] + powx_inv[b]]; +} + +#define MAX_NBUFS 4 + +static grub_err_t +recovery (grub_uint8_t *bufs[4], grub_size_t s, const int nbufs, + const unsigned *powers, + const unsigned *idx) +{ + grub_dprintf ("zfs", "recovering %u buffers\n", nbufs); + /* Now we have */ + /* b_i = sum (r_j* (x ** (powers[i] * idx[j])))*/ + /* Let's invert the matrix in question. */ + switch (nbufs) + { + /* Easy: r_0 = bufs[0] / (x << (powers[i] * idx[j])). */ + case 1: + { + int add; + grub_uint8_t *a; + if (powers[0] == 0 || idx[0] == 0) + return GRUB_ERR_NONE; + add = 255 - ((powers[0] * idx[0]) % 255); + for (a = bufs[0]; s--; a++) + if (*a) + *a = powx[powx_inv[*a] + add]; + return GRUB_ERR_NONE; + } + /* Case 2x2: Let's use the determinant formula. */ + case 2: + { + grub_uint8_t det, det_inv; + grub_uint8_t matrixinv[2][2]; + unsigned i; + /* The determinant is: */ + det = (powx[(powers[0] * idx[0] + powers[1] * idx[1]) % 255] + ^ powx[(powers[0] * idx[1] + powers[1] * idx[0]) % 255]); + if (det == 0) + return grub_error (GRUB_ERR_BAD_FS, "singular recovery matrix"); + det_inv = powx[255 - powx_inv[det]]; + matrixinv[0][0] = gf_mul (powx[(powers[1] * idx[1]) % 255], det_inv); + matrixinv[1][1] = gf_mul (powx[(powers[0] * idx[0]) % 255], det_inv); + matrixinv[0][1] = gf_mul (powx[(powers[0] * idx[1]) % 255], det_inv); + matrixinv[1][0] = gf_mul (powx[(powers[1] * idx[0]) % 255], det_inv); + for (i = 0; i < s; i++) + { + grub_uint8_t b0, b1; + b0 = bufs[0][i]; + b1 = bufs[1][i]; + + bufs[0][i] = (gf_mul (b0, matrixinv[0][0]) + ^ gf_mul (b1, matrixinv[0][1])); + bufs[1][i] = (gf_mul (b0, matrixinv[1][0]) + ^ gf_mul (b1, matrixinv[1][1])); + } + return GRUB_ERR_NONE; + } + /* Otherwise use Gauss. */ + case 3: + { + grub_uint8_t matrix1[MAX_NBUFS][MAX_NBUFS], matrix2[MAX_NBUFS][MAX_NBUFS]; + int i, j, k; + + for (i = 0; i < nbufs; i++) + for (j = 0; j < nbufs; j++) + matrix1[i][j] = powx[(powers[i] * idx[j]) % 255]; + for (i = 0; i < nbufs; i++) + for (j = 0; j < nbufs; j++) + matrix2[i][j] = 0; + for (i = 0; i < nbufs; i++) + matrix2[i][i] = 1; + + for (i = 0; i < nbufs; i++) + { + grub_uint8_t mul; + for (j = i; j < nbufs; j++) + if (matrix1[i][j]) + break; + if (j == nbufs) + return grub_error (GRUB_ERR_BAD_FS, "singular recovery matrix"); + if (j != i) + { + int xchng; + xchng = j; + for (j = 0; j < nbufs; j++) + { + grub_uint8_t t; + t = matrix1[xchng][j]; + matrix1[xchng][j] = matrix1[i][j]; + matrix1[i][j] = t; + } + for (j = 0; j < nbufs; j++) + { + grub_uint8_t t; + t = matrix2[xchng][j]; + matrix2[xchng][j] = matrix2[i][j]; + matrix2[i][j] = t; + } + } + mul = powx[255 - powx_inv[matrix1[i][i]]]; + for (j = 0; j < nbufs; j++) + matrix1[i][j] = gf_mul (matrix1[i][j], mul); + for (j = 0; j < nbufs; j++) + matrix2[i][j] = gf_mul (matrix2[i][j], mul); + for (j = i + 1; j < nbufs; j++) + { + mul = matrix1[j][i]; + for (k = 0; k < nbufs; k++) + matrix1[j][k] ^= gf_mul (matrix1[i][k], mul); + for (k = 0; k < nbufs; k++) + matrix2[j][k] ^= gf_mul (matrix2[i][k], mul); + } + } + for (i = nbufs - 1; i >= 0; i--) + { + for (j = 0; j < i; j++) + { + grub_uint8_t mul; + mul = matrix1[j][i]; + for (k = 0; k < nbufs; k++) + matrix1[j][k] ^= gf_mul (matrix1[i][k], mul); + for (k = 0; k < nbufs; k++) + matrix2[j][k] ^= gf_mul (matrix2[i][k], mul); + } + } + + for (i = 0; i < (int) s; i++) + { + grub_uint8_t b[MAX_NBUFS]; + for (j = 0; j < nbufs; j++) + b[j] = bufs[j][i]; + for (j = 0; j < nbufs; j++) + { + bufs[j][i] = 0; + for (k = 0; k < nbufs; k++) + bufs[j][i] ^= gf_mul (matrix2[j][k], b[k]); + } + } + return GRUB_ERR_NONE; + } + default: + return grub_error (GRUB_ERR_BUG, "too big matrix"); + } +} + +static grub_err_t +read_device (grub_uint64_t offset, struct grub_zfs_device_desc *desc, + grub_size_t len, void *buf) +{ + switch (desc->type) + { + case DEVICE_LEAF: + { + grub_uint64_t sector; + sector = DVA_OFFSET_TO_PHYS_SECTOR (offset); + if (!desc->dev) + { + return grub_error (GRUB_ERR_BAD_FS, + N_("couldn't find a necessary member device " + "of multi-device filesystem")); + } + /* read in a data block */ + return grub_disk_read (desc->dev->disk, sector, 0, len, buf); + } + case DEVICE_MIRROR: + { + grub_err_t err = GRUB_ERR_NONE; + unsigned i; + if (desc->n_children <= 0) + return grub_error (GRUB_ERR_BAD_FS, + "non-positive number of mirror children"); + for (i = 0; i < desc->n_children; i++) + { + err = read_device (offset, &desc->children[i], + len, buf); + if (!err) + break; + grub_errno = GRUB_ERR_NONE; + } + grub_errno = err; + + return err; + } + case DEVICE_RAIDZ: + { + unsigned c = 0; + grub_uint64_t high; + grub_uint64_t devn; + grub_uint64_t m; + grub_uint32_t s, orig_s; + void *orig_buf = buf; + grub_size_t orig_len = len; + grub_uint8_t *recovery_buf[4]; + grub_size_t recovery_len[4]; + unsigned recovery_idx[4]; + unsigned failed_devices = 0; + int idx, orig_idx; + + if (desc->nparity < 1 || desc->nparity > 3) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "raidz%d is not supported", desc->nparity); + + if (desc->n_children <= desc->nparity || desc->n_children < 1) + return grub_error(GRUB_ERR_BAD_FS, "too little devices for given parity"); + + orig_s = (((len + (1 << desc->ashift) - 1) >> desc->ashift) + + (desc->n_children - desc->nparity) - 1); + s = orig_s; + + high = grub_divmod64 ((offset >> desc->ashift), + desc->n_children, &m); + if (desc->nparity == 2) + c = 2; + if (desc->nparity == 3) + c = 3; + if (((len + (1 << desc->ashift) - 1) >> desc->ashift) + >= (desc->n_children - desc->nparity)) + idx = (desc->n_children - desc->nparity - 1); + else + idx = ((len + (1 << desc->ashift) - 1) >> desc->ashift) - 1; + orig_idx = idx; + while (len > 0) + { + grub_size_t csize; + grub_uint32_t bsize; + grub_err_t err; + bsize = s / (desc->n_children - desc->nparity); + + if (desc->nparity == 1 + && ((offset >> (desc->ashift + 20 - desc->max_children_ashift)) + & 1) == c) + c++; + + high = grub_divmod64 ((offset >> desc->ashift) + c, + desc->n_children, &devn); + csize = (grub_size_t) bsize << desc->ashift; + if (csize > len) + csize = len; + + grub_dprintf ("zfs", "RAIDZ mapping 0x%" PRIxGRUB_UINT64_T + "+%u (%" PRIxGRUB_SIZE ", %" PRIxGRUB_UINT32_T + ") -> (0x%" PRIxGRUB_UINT64_T ", 0x%" + PRIxGRUB_UINT64_T ")\n", + offset >> desc->ashift, c, len, bsize, high, + devn); + err = read_device ((high << desc->ashift) + | (offset & ((1 << desc->ashift) - 1)), + &desc->children[devn], + csize, buf); + if (err && failed_devices < desc->nparity) + { + recovery_buf[failed_devices] = buf; + recovery_len[failed_devices] = csize; + recovery_idx[failed_devices] = idx; + failed_devices++; + grub_errno = err = 0; + } + if (err) + return err; + + c++; + idx--; + s--; + buf = (char *) buf + csize; + len -= csize; + } + if (failed_devices) + { + unsigned redundancy_pow[4]; + unsigned cur_redundancy_pow = 0; + unsigned n_redundancy = 0; + unsigned i, j; + grub_err_t err; + + /* Compute mul. x**s has a period of 255. */ + if (powx[0] == 0) + { + grub_uint8_t cur = 1; + for (i = 0; i < 255; i++) + { + powx[i] = cur; + powx[i + 255] = cur; + powx_inv[cur] = i; + if (cur & 0x80) + cur = (cur << 1) ^ poly; + else + cur <<= 1; + } + } + + /* Read redundancy data. */ + for (n_redundancy = 0, cur_redundancy_pow = 0; + n_redundancy < failed_devices; + cur_redundancy_pow++) + { + high = grub_divmod64 ((offset >> desc->ashift) + + cur_redundancy_pow + + ((desc->nparity == 1) + && ((offset >> (desc->ashift + 20 + - desc->max_children_ashift)) + & 1)), + desc->n_children, &devn); + err = read_device ((high << desc->ashift) + | (offset & ((1 << desc->ashift) - 1)), + &desc->children[devn], + recovery_len[n_redundancy], + recovery_buf[n_redundancy]); + /* Ignore error if we may still have enough devices. */ + if (err && n_redundancy + desc->nparity - cur_redundancy_pow - 1 + >= failed_devices) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + if (err) + return err; + redundancy_pow[n_redundancy] = cur_redundancy_pow; + n_redundancy++; + } + /* Now xor-our the parts we already know. */ + buf = orig_buf; + len = orig_len; + s = orig_s; + idx = orig_idx; + + while (len > 0) + { + grub_size_t csize = s; + csize = ((csize / (desc->n_children - desc->nparity)) + << desc->ashift); + if (csize > len) + csize = len; + + for (j = 0; j < failed_devices; j++) + if (buf == recovery_buf[j]) + break; + + if (j == failed_devices) + for (j = 0; j < failed_devices; j++) + xor_out (recovery_buf[j], buf, + csize < recovery_len[j] ? csize : recovery_len[j], + idx, redundancy_pow[j]); + + s--; + buf = (char *) buf + csize; + len -= csize; + idx--; + } + for (i = 0; i < failed_devices + && recovery_len[i] == recovery_len[0]; + i++); + /* Since the chunks have variable length handle the last block + separately. */ + if (i != failed_devices) + { + grub_uint8_t *tmp_recovery_buf[4]; + for (j = 0; j < i; j++) + tmp_recovery_buf[j] = recovery_buf[j] + recovery_len[failed_devices - 1]; + err = recovery (tmp_recovery_buf, recovery_len[0] - recovery_len[failed_devices - 1], i, redundancy_pow, + recovery_idx); + if (err) + return err; + } + err = recovery (recovery_buf, recovery_len[failed_devices - 1], + failed_devices, redundancy_pow, recovery_idx); + if (err) + return err; + } + return GRUB_ERR_NONE; + } + } + return grub_error (GRUB_ERR_BAD_FS, "unsupported device type"); +} + +static grub_err_t +read_dva (const dva_t *dva, + grub_zfs_endian_t endian, struct grub_zfs_data *data, + void *buf, grub_size_t len) +{ + grub_uint64_t offset; + unsigned i; + grub_err_t err = 0; + int try = 0; + offset = dva_get_offset (dva, endian); + + for (try = 0; try < 2; try++) + { + for (i = 0; i < data->n_devices_attached; i++) + if (data->devices_attached[i].id == DVA_GET_VDEV (dva)) + { + err = read_device (offset, &data->devices_attached[i], len, buf); + if (!err) + return GRUB_ERR_NONE; + break; + } + if (try == 1) + break; + err = scan_devices (data); + if (err) + return err; + } + if (!err) + return grub_error (GRUB_ERR_BAD_FS, "unknown device %d", + (int) DVA_GET_VDEV (dva)); + return err; +} + +/* + * Read a block of data based on the gang block address dva, + * and put its data in buf. + * + */ +static grub_err_t +zio_read_gang (blkptr_t * bp, grub_zfs_endian_t endian, dva_t * dva, void *buf, + struct grub_zfs_data *data) +{ + zio_gbh_phys_t *zio_gb; + unsigned i; + grub_err_t err; + zio_cksum_t zc; + + grub_memset (&zc, 0, sizeof (zc)); + + zio_gb = grub_malloc (SPA_GANGBLOCKSIZE); + if (!zio_gb) + return grub_errno; + grub_dprintf ("zfs", endian == GRUB_ZFS_LITTLE_ENDIAN ? "little-endian gang\n" + :"big-endian gang\n"); + + err = read_dva (dva, endian, data, zio_gb, SPA_GANGBLOCKSIZE); + if (err) + { + grub_free (zio_gb); + return err; + } + + /* XXX */ + /* self checksuming the gang block header */ + ZIO_SET_CHECKSUM (&zc, DVA_GET_VDEV (dva), + dva_get_offset (dva, endian), bp->blk_birth, 0); + err = zio_checksum_verify (zc, ZIO_CHECKSUM_GANG_HEADER, endian, + (char *) zio_gb, SPA_GANGBLOCKSIZE); + if (err) + { + grub_free (zio_gb); + return err; + } + + endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; + + for (i = 0; i < SPA_GBH_NBLKPTRS; i++) + { + if (BP_IS_HOLE(&zio_gb->zg_blkptr[i])) + continue; + + err = zio_read_data (&zio_gb->zg_blkptr[i], endian, buf, data); + if (err) + { + grub_free (zio_gb); + return err; + } + buf = (char *) buf + get_psize (&zio_gb->zg_blkptr[i], endian); + } + grub_free (zio_gb); + return GRUB_ERR_NONE; +} + +/* + * Read in a block of raw data to buf. + */ +static grub_err_t +zio_read_data (blkptr_t * bp, grub_zfs_endian_t endian, void *buf, + struct grub_zfs_data *data) +{ + int i, psize; + grub_err_t err = GRUB_ERR_NONE; + + psize = get_psize (bp, endian); + + /* pick a good dva from the block pointer */ + for (i = 0; i < SPA_DVAS_PER_BP; i++) + { + if (bp->blk_dva[i].dva_word[0] == 0 && bp->blk_dva[i].dva_word[1] == 0) + continue; + + if ((grub_zfs_to_cpu64 (bp->blk_dva[i].dva_word[1], endian)>>63) & 1) + err = zio_read_gang (bp, endian, &bp->blk_dva[i], buf, data); + else + err = read_dva (&bp->blk_dva[i], endian, data, buf, psize); + if (!err) + return GRUB_ERR_NONE; + grub_errno = GRUB_ERR_NONE; + } + + if (!err) + err = grub_error (GRUB_ERR_BAD_FS, "couldn't find a valid DVA"); + grub_errno = err; + + return err; +} + +/* + * buf must be at least BPE_GET_PSIZE(bp) bytes long (which will never be + * more than BPE_PAYLOAD_SIZE bytes). + */ +static grub_err_t +decode_embedded_bp_compressed(const blkptr_t *bp, void *buf) +{ + grub_size_t psize, i; + grub_uint8_t *buf8 = buf; + grub_uint64_t w = 0; + const grub_uint64_t *bp64 = (const grub_uint64_t *)bp; + + psize = BPE_GET_PSIZE(bp); + + /* + * Decode the words of the block pointer into the byte array. + * Low bits of first word are the first byte (little endian). + */ + for (i = 0; i < psize; i++) + { + if (i % sizeof (w) == 0) + { + /* beginning of a word */ + w = *bp64; + bp64++; + if (!BPE_IS_PAYLOADWORD(bp, bp64)) + bp64++; + } + buf8[i] = BF64_GET(w, (i % sizeof (w)) * 8, 8); + } + return GRUB_ERR_NONE; +} + +/* + * Read in a block of data, verify its checksum, decompress if needed, + * and put the uncompressed data in buf. + */ +static grub_err_t +zio_read (blkptr_t *bp, grub_zfs_endian_t endian, void **buf, + grub_size_t *size, struct grub_zfs_data *data) +{ + grub_size_t lsize, psize; + unsigned int comp, encrypted; + char *compbuf = NULL; + grub_err_t err; + zio_cksum_t zc = bp->blk_cksum; + grub_uint32_t checksum; + + *buf = NULL; + + checksum = (grub_zfs_to_cpu64((bp)->blk_prop, endian) >> 40) & 0xff; + comp = (grub_zfs_to_cpu64((bp)->blk_prop, endian)>>32) & 0x7f; + encrypted = ((grub_zfs_to_cpu64((bp)->blk_prop, endian) >> 60) & 3); + if (BP_IS_EMBEDDED(bp)) + { + if (BPE_GET_ETYPE(bp) != BP_EMBEDDED_TYPE_DATA) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "unsupported embedded BP (type=%llu)\n", + (long long unsigned int) BPE_GET_ETYPE(bp)); + lsize = BPE_GET_LSIZE(bp); + psize = BF64_GET_SB(grub_zfs_to_cpu64 ((bp)->blk_prop, endian), 25, 7, 0, 1); + } + else + { + lsize = (BP_IS_HOLE(bp) ? 0 : + (((grub_zfs_to_cpu64 ((bp)->blk_prop, endian) & 0xffff) + 1) + << SPA_MINBLOCKSHIFT)); + psize = get_psize (bp, endian); + } + grub_dprintf("zfs", "zio_read: E %d: size %" PRIdGRUB_SSIZE "/%" + PRIdGRUB_SSIZE "\n", (int)BP_IS_EMBEDDED(bp), lsize, psize); + + if (size) + *size = lsize; + + if (comp >= ZIO_COMPRESS_FUNCTIONS) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "compression algorithm %u not supported\n", (unsigned int) comp); + + if (comp != ZIO_COMPRESS_OFF && decomp_table[comp].decomp_func == NULL) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "compression algorithm %s not supported\n", decomp_table[comp].name); + + if (comp != ZIO_COMPRESS_OFF) + /* It's not really necessary to align to 16, just for safety. */ + compbuf = grub_malloc (ALIGN_UP (psize, 16)); + else + compbuf = *buf = grub_malloc (lsize); + if (! compbuf) + return grub_errno; + + grub_dprintf ("zfs", "endian = %d\n", endian); + if (BP_IS_EMBEDDED(bp)) + err = decode_embedded_bp_compressed(bp, compbuf); + else + { + err = zio_read_data (bp, endian, compbuf, data); + /* FIXME is it really necessary? */ + if (comp != ZIO_COMPRESS_OFF) + grub_memset (compbuf + psize, 0, ALIGN_UP (psize, 16) - psize); + } + if (err) + { + grub_free (compbuf); + *buf = NULL; + return err; + } + + if (!BP_IS_EMBEDDED(bp)) + { + err = zio_checksum_verify (zc, checksum, endian, + compbuf, psize); + if (err) + { + grub_dprintf ("zfs", "incorrect checksum\n"); + grub_free (compbuf); + *buf = NULL; + return err; + } + } + + if (encrypted) + { + if (!grub_zfs_decrypt) + err = grub_error (GRUB_ERR_BAD_FS, + N_("module `%s' isn't loaded"), + "zfscrypt"); + else + { + unsigned i, besti = 0; + grub_uint64_t bestval = 0; + for (i = 0; i < data->subvol.nkeys; i++) + if (data->subvol.keyring[i].txg <= grub_zfs_to_cpu64 (bp->blk_birth, + endian) + && data->subvol.keyring[i].txg > bestval) + { + besti = i; + bestval = data->subvol.keyring[i].txg; + } + if (bestval == 0) + { + grub_free (compbuf); + *buf = NULL; + grub_dprintf ("zfs", "no key for txg %" PRIxGRUB_UINT64_T "\n", + grub_zfs_to_cpu64 (bp->blk_birth, + endian)); + return grub_error (GRUB_ERR_BAD_FS, "no key found in keychain"); + } + grub_dprintf ("zfs", "using key %u (%" PRIxGRUB_UINT64_T + ", %p) for txg %" PRIxGRUB_UINT64_T "\n", + besti, data->subvol.keyring[besti].txg, + data->subvol.keyring[besti].cipher, + grub_zfs_to_cpu64 (bp->blk_birth, + endian)); + err = grub_zfs_decrypt (data->subvol.keyring[besti].cipher, + data->subvol.keyring[besti].algo, + &(bp)->blk_dva[encrypted], + compbuf, psize, zc.zc_mac, + endian); + } + if (err) + { + grub_free (compbuf); + *buf = NULL; + return err; + } + } + + if (comp != ZIO_COMPRESS_OFF) + { + *buf = grub_malloc (lsize); + if (!*buf) + { + grub_free (compbuf); + return grub_errno; + } + + err = decomp_table[comp].decomp_func (compbuf, *buf, psize, lsize); + grub_free (compbuf); + if (err) + { + grub_free (*buf); + *buf = NULL; + return err; + } + } + + return GRUB_ERR_NONE; +} + +/* + * Get the block from a block id. + * push the block onto the stack. + * + */ +static grub_err_t +dmu_read (dnode_end_t * dn, grub_uint64_t blkid, void **buf, + grub_zfs_endian_t *endian_out, struct grub_zfs_data *data) +{ + int level; + grub_off_t idx; + blkptr_t *bp_array = dn->dn.dn_blkptr; + int epbs = dn->dn.dn_indblkshift - SPA_BLKPTRSHIFT; + blkptr_t *bp; + void *tmpbuf = 0; + grub_zfs_endian_t endian; + grub_err_t err = GRUB_ERR_NONE; + + bp = grub_malloc (sizeof (blkptr_t)); + if (!bp) + return grub_errno; + + endian = dn->endian; + for (level = dn->dn.dn_nlevels - 1; level >= 0; level--) + { + grub_dprintf ("zfs", "endian = %d\n", endian); + idx = (blkid >> (epbs * level)) & ((1 << epbs) - 1); + *bp = bp_array[idx]; + if (bp_array != dn->dn.dn_blkptr) + { + grub_free (bp_array); + bp_array = 0; + } + + if (BP_IS_HOLE (bp)) + { + grub_size_t size = grub_zfs_to_cpu16 (dn->dn.dn_datablkszsec, + dn->endian) + << SPA_MINBLOCKSHIFT; + *buf = grub_malloc (size); + if (!*buf) + { + err = grub_errno; + break; + } + grub_memset (*buf, 0, size); + endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; + break; + } + if (level == 0) + { + grub_dprintf ("zfs", "endian = %d\n", endian); + err = zio_read (bp, endian, buf, 0, data); + endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; + break; + } + grub_dprintf ("zfs", "endian = %d\n", endian); + err = zio_read (bp, endian, &tmpbuf, 0, data); + endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; + if (err) + break; + bp_array = tmpbuf; + } + if (bp_array != dn->dn.dn_blkptr) + grub_free (bp_array); + if (endian_out) + *endian_out = endian; + + grub_free (bp); + return err; +} + +/* + * mzap_lookup: Looks up property described by "name" and returns the value + * in "value". + */ +static grub_err_t +mzap_lookup (mzap_phys_t * zapobj, grub_zfs_endian_t endian, + grub_uint32_t objsize, const char *name, grub_uint64_t * value, + int case_insensitive) +{ + grub_uint32_t i, chunks; + mzap_ent_phys_t *mzap_ent = zapobj->mz_chunk; + + if (objsize < MZAP_ENT_LEN) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); + chunks = objsize / MZAP_ENT_LEN - 1; + for (i = 0; i < chunks; i++) + { + if (case_insensitive ? (grub_strcasecmp (mzap_ent[i].mze_name, name) == 0) + : (grub_strcmp (mzap_ent[i].mze_name, name) == 0)) + { + *value = grub_zfs_to_cpu64 (mzap_ent[i].mze_value, endian); + return GRUB_ERR_NONE; + } + } + + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); +} + +static int +mzap_iterate (mzap_phys_t * zapobj, grub_zfs_endian_t endian, int objsize, + int (*hook) (const char *name, grub_uint64_t val, + struct grub_zfs_dir_ctx *ctx), + struct grub_zfs_dir_ctx *ctx) +{ + int i, chunks; + mzap_ent_phys_t *mzap_ent = zapobj->mz_chunk; + + chunks = objsize / MZAP_ENT_LEN - 1; + for (i = 0; i < chunks; i++) + { + grub_dprintf ("zfs", "zap: name = %s, value = %llx, cd = %x\n", + mzap_ent[i].mze_name, (long long)mzap_ent[i].mze_value, + (int)mzap_ent[i].mze_cd); + if (hook (mzap_ent[i].mze_name, + grub_zfs_to_cpu64 (mzap_ent[i].mze_value, endian), ctx)) + return 1; + } + + return 0; +} + +static grub_uint64_t +zap_hash (grub_uint64_t salt, const char *name, + int case_insensitive) +{ + static grub_uint64_t table[256]; + const grub_uint8_t *cp; + grub_uint8_t c; + grub_uint64_t crc = salt; + + if (table[128] == 0) + { + grub_uint64_t *ct; + int i, j; + for (i = 0; i < 256; i++) + { + for (ct = table + i, *ct = i, j = 8; j > 0; j--) + *ct = (*ct >> 1) ^ (-(*ct & 1) & ZFS_CRC64_POLY); + } + } + + if (case_insensitive) + for (cp = (const grub_uint8_t *) name; (c = *cp) != '\0'; cp++) + crc = (crc >> 8) ^ table[(crc ^ grub_toupper (c)) & 0xFF]; + else + for (cp = (const grub_uint8_t *) name; (c = *cp) != '\0'; cp++) + crc = (crc >> 8) ^ table[(crc ^ c) & 0xFF]; + + /* + * Only use 28 bits, since we need 4 bits in the cookie for the + * collision differentiator. We MUST use the high bits, since + * those are the onces that we first pay attention to when + * chosing the bucket. + */ + crc &= ~((1ULL << (64 - ZAP_HASHBITS)) - 1); + + return crc; +} + +/* + * Only to be used on 8-bit arrays. + * array_len is actual len in bytes (not encoded le_value_length). + * buf is null-terminated. + */ + +static inline int +name_cmp (const char *s1, const char *s2, grub_size_t n, + int case_insensitive) +{ + const char *t1 = (const char *) s1; + const char *t2 = (const char *) s2; + + if (!case_insensitive) + return grub_memcmp (t1, t2, n); + + while (n--) + { + if (grub_toupper (*t1) != grub_toupper (*t2)) + return (int) grub_toupper (*t1) - (int) grub_toupper (*t2); + + t1++; + t2++; + } + + return 0; +} + +/* XXX */ +static int +zap_leaf_array_equal (zap_leaf_phys_t * l, grub_zfs_endian_t endian, + int blksft, int chunk, grub_size_t array_len, + const char *buf, int case_insensitive) +{ + grub_size_t bseen = 0; + + while (bseen < array_len) + { + struct zap_leaf_array *la = &ZAP_LEAF_CHUNK (l, blksft, chunk)->l_array; + grub_size_t toread = array_len - bseen; + + if (toread > ZAP_LEAF_ARRAY_BYTES) + toread = ZAP_LEAF_ARRAY_BYTES; + + if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft)) + return 0; + + if (name_cmp ((char *) la->la_array, buf + bseen, toread, + case_insensitive) != 0) + break; + chunk = grub_zfs_to_cpu16 (la->la_next, endian); + bseen += toread; + } + return (bseen == array_len); +} + +/* XXX */ +static grub_err_t +zap_leaf_array_get (zap_leaf_phys_t * l, grub_zfs_endian_t endian, int blksft, + int chunk, grub_size_t array_len, char *buf) +{ + grub_size_t bseen = 0; + + while (bseen < array_len) + { + struct zap_leaf_array *la = &ZAP_LEAF_CHUNK (l, blksft, chunk)->l_array; + grub_size_t toread = array_len - bseen; + + if (toread > ZAP_LEAF_ARRAY_BYTES) + toread = ZAP_LEAF_ARRAY_BYTES; + + if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft)) + /* Don't use grub_error because this error is to be ignored. */ + return GRUB_ERR_BAD_FS; + + grub_memcpy (buf + bseen,la->la_array, toread); + chunk = grub_zfs_to_cpu16 (la->la_next, endian); + bseen += toread; + } + return GRUB_ERR_NONE; +} + + +/* + * Given a zap_leaf_phys_t, walk thru the zap leaf chunks to get the + * value for the property "name". + * + */ +/* XXX */ +static grub_err_t +zap_leaf_lookup (zap_leaf_phys_t * l, grub_zfs_endian_t endian, + int blksft, grub_uint64_t h, + const char *name, grub_uint64_t * value, + int case_insensitive) +{ + grub_uint16_t chunk; + struct zap_leaf_entry *le; + + /* Verify if this is a valid leaf block */ + if (grub_zfs_to_cpu64 (l->l_hdr.lh_block_type, endian) != ZBT_LEAF) + return grub_error (GRUB_ERR_BAD_FS, "invalid leaf type"); + if (grub_zfs_to_cpu32 (l->l_hdr.lh_magic, endian) != ZAP_LEAF_MAGIC) + return grub_error (GRUB_ERR_BAD_FS, "invalid leaf magic"); + + for (chunk = grub_zfs_to_cpu16 (l->l_hash[LEAF_HASH (blksft, h, l)], endian); + chunk != CHAIN_END; chunk = grub_zfs_to_cpu16 (le->le_next, endian)) + { + + if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft)) + return grub_error (GRUB_ERR_BAD_FS, "invalid chunk number"); + + le = ZAP_LEAF_ENTRY (l, blksft, chunk); + + /* Verify the chunk entry */ + if (le->le_type != ZAP_CHUNK_ENTRY) + return grub_error (GRUB_ERR_BAD_FS, "invalid chunk entry"); + + if (grub_zfs_to_cpu64 (le->le_hash,endian) != h) + continue; + + grub_dprintf ("zfs", "fzap: length %d\n", (int) le->le_name_length); + + if (zap_leaf_array_equal (l, endian, blksft, + grub_zfs_to_cpu16 (le->le_name_chunk,endian), + grub_zfs_to_cpu16 (le->le_name_length, endian), + name, case_insensitive)) + { + struct zap_leaf_array *la; + + if (le->le_int_size != 8 || grub_zfs_to_cpu16 (le->le_value_length, + endian) != 1) + return grub_error (GRUB_ERR_BAD_FS, "invalid leaf chunk entry"); + + /* get the uint64_t property value */ + la = &ZAP_LEAF_CHUNK (l, blksft, le->le_value_chunk)->l_array; + + *value = grub_be_to_cpu64 (la->la_array64); + + return GRUB_ERR_NONE; + } + } + + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); +} + + +/* Verify if this is a fat zap header block */ +static grub_err_t +zap_verify (zap_phys_t *zap, grub_zfs_endian_t endian) +{ + if (grub_zfs_to_cpu64 (zap->zap_magic, endian) != (grub_uint64_t) ZAP_MAGIC) + return grub_error (GRUB_ERR_BAD_FS, "bad ZAP magic"); + + if (zap->zap_salt == 0) + return grub_error (GRUB_ERR_BAD_FS, "bad ZAP salt"); + + return GRUB_ERR_NONE; +} + +/* + * Fat ZAP lookup + * + */ +/* XXX */ +static grub_err_t +fzap_lookup (dnode_end_t * zap_dnode, zap_phys_t * zap, + const char *name, grub_uint64_t * value, + struct grub_zfs_data *data, int case_insensitive) +{ + void *l; + grub_uint64_t hash, idx, blkid; + int blksft = zfs_log2 (grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, + zap_dnode->endian) << DNODE_SHIFT); + grub_err_t err; + grub_zfs_endian_t leafendian; + + err = zap_verify (zap, zap_dnode->endian); + if (err) + return err; + + hash = zap_hash (zap->zap_salt, name, case_insensitive); + + /* get block id from index */ + if (zap->zap_ptrtbl.zt_numblks != 0) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "external pointer tables not supported"); + idx = ZAP_HASH_IDX (hash, zap->zap_ptrtbl.zt_shift); + blkid = grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))], zap_dnode->endian); + + /* Get the leaf block */ + if ((1U << blksft) < sizeof (zap_leaf_phys_t)) + return grub_error (GRUB_ERR_BAD_FS, "ZAP leaf is too small"); + err = dmu_read (zap_dnode, blkid, &l, &leafendian, data); + if (err) + return err; + + err = zap_leaf_lookup (l, leafendian, blksft, hash, name, value, + case_insensitive); + grub_free (l); + return err; +} + +/* XXX */ +static int +fzap_iterate (dnode_end_t * zap_dnode, zap_phys_t * zap, + grub_size_t name_elem_length, + int (*hook) (const void *name, grub_size_t name_length, + const void *val_in, + grub_size_t nelem, grub_size_t elemsize, + void *data), + void *hook_data, struct grub_zfs_data *data) +{ + zap_leaf_phys_t *l; + void *l_in; + grub_uint64_t idx, idx2, blkid; + grub_uint16_t chunk; + int blksft = zfs_log2 (grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, + zap_dnode->endian) << DNODE_SHIFT); + grub_err_t err; + grub_zfs_endian_t endian; + + if (zap_verify (zap, zap_dnode->endian)) + return 0; + + /* get block id from index */ + if (zap->zap_ptrtbl.zt_numblks != 0) + { + grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "external pointer tables not supported"); + return 0; + } + /* Get the leaf block */ + if ((1U << blksft) < sizeof (zap_leaf_phys_t)) + { + grub_error (GRUB_ERR_BAD_FS, "ZAP leaf is too small"); + return 0; + } + for (idx = 0; idx < (1ULL << zap->zap_ptrtbl.zt_shift); idx++) + { + blkid = grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))], + zap_dnode->endian); + + for (idx2 = 0; idx2 < idx; idx2++) + if (blkid == grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx2 + (1 << (blksft - 3 - 1))], + zap_dnode->endian)) + break; + if (idx2 != idx) + continue; + + err = dmu_read (zap_dnode, blkid, &l_in, &endian, data); + l = l_in; + if (err) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + + /* Verify if this is a valid leaf block */ + if (grub_zfs_to_cpu64 (l->l_hdr.lh_block_type, endian) != ZBT_LEAF) + { + grub_free (l); + continue; + } + if (grub_zfs_to_cpu32 (l->l_hdr.lh_magic, endian) != ZAP_LEAF_MAGIC) + { + grub_free (l); + continue; + } + + for (chunk = 0; chunk < ZAP_LEAF_NUMCHUNKS (blksft); chunk++) + { + char *buf; + struct zap_leaf_entry *le; + char *val; + grub_size_t val_length; + le = ZAP_LEAF_ENTRY (l, blksft, chunk); + + /* Verify the chunk entry */ + if (le->le_type != ZAP_CHUNK_ENTRY) + continue; + + buf = grub_malloc (grub_zfs_to_cpu16 (le->le_name_length, endian) + * name_elem_length + 1); + if (zap_leaf_array_get (l, endian, blksft, + grub_zfs_to_cpu16 (le->le_name_chunk, + endian), + grub_zfs_to_cpu16 (le->le_name_length, + endian) + * name_elem_length, buf)) + { + grub_free (buf); + continue; + } + buf[le->le_name_length * name_elem_length] = 0; + + val_length = ((int) le->le_value_length + * (int) le->le_int_size); + val = grub_malloc (grub_zfs_to_cpu16 (val_length, endian)); + if (zap_leaf_array_get (l, endian, blksft, + grub_zfs_to_cpu16 (le->le_value_chunk, + endian), + val_length, val)) + { + grub_free (buf); + grub_free (val); + continue; + } + + if (hook (buf, le->le_name_length, + val, le->le_value_length, le->le_int_size, hook_data)) + { + grub_free (l); + return 1; + } + grub_free (buf); + grub_free (val); + } + grub_free (l); + } + return 0; +} + +/* + * Read in the data of a zap object and find the value for a matching + * property name. + * + */ +static grub_err_t +zap_lookup (dnode_end_t * zap_dnode, const char *name, grub_uint64_t *val, + struct grub_zfs_data *data, int case_insensitive) +{ + grub_uint64_t block_type; + grub_uint32_t size; + void *zapbuf; + grub_err_t err; + grub_zfs_endian_t endian; + + grub_dprintf ("zfs", "looking for '%s'\n", name); + + /* Read in the first block of the zap object data. */ + size = (grub_uint32_t) grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, + zap_dnode->endian) << SPA_MINBLOCKSHIFT; + err = dmu_read (zap_dnode, 0, &zapbuf, &endian, data); + if (err) + return err; + block_type = grub_zfs_to_cpu64 (*((grub_uint64_t *) zapbuf), endian); + + grub_dprintf ("zfs", "zap read\n"); + + if (block_type == ZBT_MICRO) + { + grub_dprintf ("zfs", "micro zap\n"); + err = mzap_lookup (zapbuf, endian, size, name, val, + case_insensitive); + grub_dprintf ("zfs", "returned %d\n", err); + grub_free (zapbuf); + return err; + } + else if (block_type == ZBT_HEADER) + { + grub_dprintf ("zfs", "fat zap\n"); + /* this is a fat zap */ + err = fzap_lookup (zap_dnode, zapbuf, name, val, data, + case_insensitive); + grub_dprintf ("zfs", "returned %d\n", err); + grub_free (zapbuf); + return err; + } + + return grub_error (GRUB_ERR_BAD_FS, "unknown ZAP type"); +} + +/* Context for zap_iterate_u64. */ +struct zap_iterate_u64_ctx +{ + int (*hook) (const char *, grub_uint64_t, struct grub_zfs_dir_ctx *); + struct grub_zfs_dir_ctx *dir_ctx; +}; + +/* Helper for zap_iterate_u64. */ +static int +zap_iterate_u64_transform (const void *name, + grub_size_t namelen __attribute__ ((unused)), + const void *val_in, + grub_size_t nelem, + grub_size_t elemsize, + void *data) +{ + struct zap_iterate_u64_ctx *ctx = data; + + if (elemsize != sizeof (grub_uint64_t) || nelem != 1) + return 0; + return ctx->hook (name, grub_be_to_cpu64 (*(const grub_uint64_t *) val_in), + ctx->dir_ctx); +} + +static int +zap_iterate_u64 (dnode_end_t * zap_dnode, + int (*hook) (const char *name, grub_uint64_t val, + struct grub_zfs_dir_ctx *ctx), + struct grub_zfs_data *data, struct grub_zfs_dir_ctx *ctx) +{ + grub_uint64_t block_type; + int size; + void *zapbuf; + grub_err_t err; + int ret; + grub_zfs_endian_t endian; + + /* Read in the first block of the zap object data. */ + size = grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, zap_dnode->endian) << SPA_MINBLOCKSHIFT; + err = dmu_read (zap_dnode, 0, &zapbuf, &endian, data); + if (err) + return 0; + block_type = grub_zfs_to_cpu64 (*((grub_uint64_t *) zapbuf), endian); + + grub_dprintf ("zfs", "zap iterate\n"); + + if (block_type == ZBT_MICRO) + { + grub_dprintf ("zfs", "micro zap\n"); + ret = mzap_iterate (zapbuf, endian, size, hook, ctx); + grub_free (zapbuf); + return ret; + } + else if (block_type == ZBT_HEADER) + { + struct zap_iterate_u64_ctx transform_ctx = { + .hook = hook, + .dir_ctx = ctx + }; + + grub_dprintf ("zfs", "fat zap\n"); + /* this is a fat zap */ + ret = fzap_iterate (zap_dnode, zapbuf, 1, + zap_iterate_u64_transform, &transform_ctx, data); + grub_free (zapbuf); + return ret; + } + grub_error (GRUB_ERR_BAD_FS, "unknown ZAP type"); + return 0; +} + +static int +zap_iterate (dnode_end_t * zap_dnode, + grub_size_t nameelemlen, + int (*hook) (const void *name, grub_size_t namelen, + const void *val_in, + grub_size_t nelem, grub_size_t elemsize, + void *data), + void *hook_data, struct grub_zfs_data *data) +{ + grub_uint64_t block_type; + void *zapbuf; + grub_err_t err; + int ret; + grub_zfs_endian_t endian; + + /* Read in the first block of the zap object data. */ + err = dmu_read (zap_dnode, 0, &zapbuf, &endian, data); + if (err) + return 0; + block_type = grub_zfs_to_cpu64 (*((grub_uint64_t *) zapbuf), endian); + + grub_dprintf ("zfs", "zap iterate\n"); + + if (block_type == ZBT_MICRO) + { + grub_error (GRUB_ERR_BAD_FS, "micro ZAP where FAT ZAP expected"); + return 0; + } + if (block_type == ZBT_HEADER) + { + grub_dprintf ("zfs", "fat zap\n"); + /* this is a fat zap */ + ret = fzap_iterate (zap_dnode, zapbuf, nameelemlen, hook, hook_data, + data); + grub_free (zapbuf); + return ret; + } + grub_error (GRUB_ERR_BAD_FS, "unknown ZAP type"); + return 0; +} + + +/* + * Get the dnode of an object number from the metadnode of an object set. + * + * Input + * mdn - metadnode to get the object dnode + * objnum - object number for the object dnode + * buf - data buffer that holds the returning dnode + */ +static grub_err_t +dnode_get (dnode_end_t * mdn, grub_uint64_t objnum, grub_uint8_t type, + dnode_end_t * buf, struct grub_zfs_data *data) +{ + grub_uint64_t blkid, blksz; /* the block id this object dnode is in */ + int epbs; /* shift of number of dnodes in a block */ + int idx; /* index within a block */ + void *dnbuf; + grub_err_t err; + grub_zfs_endian_t endian; + + blksz = grub_zfs_to_cpu16 (mdn->dn.dn_datablkszsec, + mdn->endian) << SPA_MINBLOCKSHIFT; + epbs = zfs_log2 (blksz) - DNODE_SHIFT; + + /* While this should never happen, we should check that epbs is not negative. */ + if (epbs < 0) + epbs = 0; + + blkid = objnum >> epbs; + idx = objnum & ((1 << epbs) - 1); + + if (data->dnode_buf != NULL && grub_memcmp (data->dnode_mdn, mdn, + sizeof (*mdn)) == 0 + && objnum >= data->dnode_start && objnum < data->dnode_end) + { + grub_memmove (&(buf->dn), &(data->dnode_buf)[idx], DNODE_SIZE); + buf->endian = data->dnode_endian; + if (type && buf->dn.dn_type != type) + return grub_error(GRUB_ERR_BAD_FS, "incorrect dnode type"); + return GRUB_ERR_NONE; + } + + grub_dprintf ("zfs", "endian = %d, blkid=%llx\n", mdn->endian, + (unsigned long long) blkid); + err = dmu_read (mdn, blkid, &dnbuf, &endian, data); + if (err) + return err; + grub_dprintf ("zfs", "alive\n"); + + grub_free (data->dnode_buf); + grub_free (data->dnode_mdn); + data->dnode_mdn = grub_malloc (sizeof (*mdn)); + if (! data->dnode_mdn) + { + grub_errno = GRUB_ERR_NONE; + data->dnode_buf = 0; + } + else + { + grub_memcpy (data->dnode_mdn, mdn, sizeof (*mdn)); + data->dnode_buf = dnbuf; + data->dnode_start = blkid << epbs; + data->dnode_end = (blkid + 1) << epbs; + data->dnode_endian = endian; + } + + grub_memmove (&(buf->dn), (dnode_phys_t *) dnbuf + idx, DNODE_SIZE); + buf->endian = endian; + if (type && buf->dn.dn_type != type) + return grub_error(GRUB_ERR_BAD_FS, "incorrect dnode type"); + + return GRUB_ERR_NONE; +} + +#pragma GCC diagnostic ignored "-Wstrict-aliasing" + +/* + * Get the file dnode for a given file name where mdn is the meta dnode + * for this ZFS object set. When found, place the file dnode in dn. + * The 'path' argument will be mangled. + * + */ +static grub_err_t +dnode_get_path (struct subvolume *subvol, const char *path_in, dnode_end_t *dn, + struct grub_zfs_data *data) +{ + grub_uint64_t objnum, version; + char *cname, ch; + grub_err_t err = GRUB_ERR_NONE; + char *path, *path_buf; + struct dnode_chain + { + struct dnode_chain *next; + dnode_end_t dn; + }; + struct dnode_chain *dnode_path = 0, *dn_new, *root; + + dn_new = grub_malloc (sizeof (*dn_new)); + if (! dn_new) + return grub_errno; + dn_new->next = 0; + dnode_path = root = dn_new; + + err = dnode_get (&subvol->mdn, MASTER_NODE_OBJ, DMU_OT_MASTER_NODE, + &(dnode_path->dn), data); + if (err) + { + grub_free (dn_new); + return err; + } + + err = zap_lookup (&(dnode_path->dn), ZPL_VERSION_STR, &version, + data, 0); + if (err) + { + grub_free (dn_new); + return err; + } + + if (version > ZPL_VERSION) + { + grub_free (dn_new); + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "too new ZPL version"); + } + + err = zap_lookup (&(dnode_path->dn), "casesensitivity", + &subvol->case_insensitive, + data, 0); + if (err == GRUB_ERR_FILE_NOT_FOUND) + { + grub_errno = GRUB_ERR_NONE; + subvol->case_insensitive = 0; + } + + err = zap_lookup (&(dnode_path->dn), ZFS_ROOT_OBJ, &objnum, data, 0); + if (err) + { + grub_free (dn_new); + return err; + } + + err = dnode_get (&subvol->mdn, objnum, 0, &(dnode_path->dn), data); + if (err) + { + grub_free (dn_new); + return err; + } + + path = path_buf = grub_strdup (path_in); + if (!path_buf) + { + grub_free (dn_new); + return grub_errno; + } + + while (1) + { + /* skip leading slashes */ + while (*path == '/') + path++; + if (!*path) + break; + /* get the next component name */ + cname = path; + while (*path && *path != '/') + path++; + /* Skip dot. */ + if (cname + 1 == path && cname[0] == '.') + continue; + /* Handle double dot. */ + if (cname + 2 == path && cname[0] == '.' && cname[1] == '.') + { + if (dn_new->next) + { + dn_new = dnode_path; + dnode_path = dn_new->next; + grub_free (dn_new); + } + else + { + err = grub_error (GRUB_ERR_FILE_NOT_FOUND, + "can't resolve .."); + break; + } + continue; + } + + ch = *path; + *path = 0; /* ensure null termination */ + + if (dnode_path->dn.dn.dn_type != DMU_OT_DIRECTORY_CONTENTS) + { + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + break; + } + err = zap_lookup (&(dnode_path->dn), cname, &objnum, + data, subvol->case_insensitive); + if (err) + break; + + dn_new = grub_malloc (sizeof (*dn_new)); + if (! dn_new) + { + err = grub_errno; + break; + } + dn_new->next = dnode_path; + dnode_path = dn_new; + + objnum = ZFS_DIRENT_OBJ (objnum); + err = dnode_get (&subvol->mdn, objnum, 0, &(dnode_path->dn), data); + if (err) + break; + + *path = ch; + if (dnode_path->dn.dn.dn_bonustype == DMU_OT_ZNODE + && ((grub_zfs_to_cpu64(((znode_phys_t *) DN_BONUS (&dnode_path->dn.dn))->zp_mode, dnode_path->dn.endian) >> 12) & 0xf) == 0xa) + { + char *sym_value; + grub_size_t sym_sz; + int free_symval = 0; + char *oldpath = path, *oldpathbuf = path_buf; + sym_value = ((char *) DN_BONUS (&dnode_path->dn.dn) + sizeof (struct znode_phys)); + + sym_sz = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dnode_path->dn.dn))->zp_size, dnode_path->dn.endian); + + if (dnode_path->dn.dn.dn_flags & 1) + { + grub_size_t block; + grub_size_t blksz; + blksz = (grub_zfs_to_cpu16 (dnode_path->dn.dn.dn_datablkszsec, + dnode_path->dn.endian) + << SPA_MINBLOCKSHIFT); + + if (blksz == 0) + { + err = grub_error (GRUB_ERR_BAD_FS, "0-sized block"); + break; + } + + sym_value = grub_malloc (sym_sz); + if (!sym_value) + { + err = grub_errno; + break; + } + + for (block = 0; block < (sym_sz + blksz - 1) / blksz; block++) + { + void *t; + grub_size_t movesize; + + err = dmu_read (&(dnode_path->dn), block, &t, 0, data); + if (err) + { + grub_free (sym_value); + break; + } + + movesize = sym_sz - block * blksz; + if (movesize > blksz) + movesize = blksz; + + grub_memcpy (sym_value + block * blksz, t, movesize); + grub_free (t); + } + if (err) + break; + free_symval = 1; + } + path = path_buf = grub_malloc (sym_sz + grub_strlen (oldpath) + 1); + if (!path_buf) + { + grub_free (oldpathbuf); + if (free_symval) + grub_free (sym_value); + err = grub_errno; + break; + } + grub_memcpy (path, sym_value, sym_sz); + if (free_symval) + grub_free (sym_value); + path [sym_sz] = 0; + grub_memcpy (path + grub_strlen (path), oldpath, + grub_strlen (oldpath) + 1); + + grub_free (oldpathbuf); + if (path[0] != '/') + { + dn_new = dnode_path; + dnode_path = dn_new->next; + grub_free (dn_new); + } + else while (dnode_path != root) + { + dn_new = dnode_path; + dnode_path = dn_new->next; + grub_free (dn_new); + } + } + if (dnode_path->dn.dn.dn_bonustype == DMU_OT_SA) + { + void *sahdrp; + int hdrsize; + + if (dnode_path->dn.dn.dn_bonuslen != 0) + { + sahdrp = DN_BONUS (&dnode_path->dn.dn); + } + else if (dnode_path->dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) + { + blkptr_t *bp = &dnode_path->dn.dn.dn_spill; + + err = zio_read (bp, dnode_path->dn.endian, &sahdrp, NULL, data); + if (err) + break; + } + else + { + err = grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); + break; + } + + hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); + + if (((grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + + hdrsize + + SA_TYPE_OFFSET), + dnode_path->dn.endian) >> 12) & 0xf) == 0xa) + { + char *sym_value = (char *) sahdrp + hdrsize + SA_SYMLINK_OFFSET; + grub_size_t sym_sz = + grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + + hdrsize + + SA_SIZE_OFFSET), + dnode_path->dn.endian); + char *oldpath = path, *oldpathbuf = path_buf; + path = path_buf = grub_malloc (sym_sz + grub_strlen (oldpath) + 1); + if (!path_buf) + { + grub_free (oldpathbuf); + err = grub_errno; + break; + } + grub_memcpy (path, sym_value, sym_sz); + path [sym_sz] = 0; + grub_memcpy (path + grub_strlen (path), oldpath, + grub_strlen (oldpath) + 1); + + grub_free (oldpathbuf); + if (path[0] != '/') + { + dn_new = dnode_path; + dnode_path = dn_new->next; + grub_free (dn_new); + } + else while (dnode_path != root) + { + dn_new = dnode_path; + dnode_path = dn_new->next; + grub_free (dn_new); + } + } + } + } + + if (!err) + grub_memcpy (dn, &(dnode_path->dn), sizeof (*dn)); + + while (dnode_path) + { + dn_new = dnode_path->next; + grub_free (dnode_path); + dnode_path = dn_new; + } + grub_free (path_buf); + return err; +} + +#if 0 +/* + * Get the default 'bootfs' property value from the rootpool. + * + */ +static grub_err_t +get_default_bootfsobj (dnode_phys_t * mosmdn, grub_uint64_t * obj, + struct grub_zfs_data *data) +{ + grub_uint64_t objnum = 0; + dnode_phys_t *dn; + if (!dn) + return grub_errno; + + if ((grub_errno = dnode_get (mosmdn, DMU_POOL_DIRECTORY_OBJECT, + DMU_OT_OBJECT_DIRECTORY, dn, data))) + { + grub_free (dn); + return (grub_errno); + } + + /* + * find the object number for 'pool_props', and get the dnode + * of the 'pool_props'. + */ + if (zap_lookup (dn, DMU_POOL_PROPS, &objnum, data)) + { + grub_free (dn); + return (GRUB_ERR_BAD_FS); + } + if ((grub_errno = dnode_get (mosmdn, objnum, DMU_OT_POOL_PROPS, dn, data))) + { + grub_free (dn); + return (grub_errno); + } + if (zap_lookup (dn, ZPOOL_PROP_BOOTFS, &objnum, data)) + { + grub_free (dn); + return (GRUB_ERR_BAD_FS); + } + + if (!objnum) + { + grub_free (dn); + return (GRUB_ERR_BAD_FS); + } + + *obj = objnum; + return (0); +} +#endif +/* + * Given a MOS metadnode, get the metadnode of a given filesystem name (fsname), + * e.g. pool/rootfs, or a given object number (obj), e.g. the object number + * of pool/rootfs. + * + * If no fsname and no obj are given, return the DSL_DIR metadnode. + * If fsname is given, return its metadnode and its matching object number. + * If only obj is given, return the metadnode for this object number. + * + */ +static grub_err_t +get_filesystem_dnode (dnode_end_t * mosmdn, char *fsname, + dnode_end_t * mdn, struct grub_zfs_data *data) +{ + grub_uint64_t objnum; + grub_err_t err; + + grub_dprintf ("zfs", "endian = %d\n", mosmdn->endian); + + err = dnode_get (mosmdn, DMU_POOL_DIRECTORY_OBJECT, + DMU_OT_OBJECT_DIRECTORY, mdn, data); + if (err) + return err; + + grub_dprintf ("zfs", "alive\n"); + + err = zap_lookup (mdn, DMU_POOL_ROOT_DATASET, &objnum, data, 0); + if (err) + return err; + + grub_dprintf ("zfs", "alive\n"); + + err = dnode_get (mosmdn, objnum, 0, mdn, data); + if (err) + return err; + + grub_dprintf ("zfs", "alive\n"); + + while (*fsname) + { + grub_uint64_t childobj; + char *cname, ch; + + while (*fsname == '/') + fsname++; + + if (! *fsname || *fsname == '@') + break; + + cname = fsname; + while (*fsname && *fsname != '/') + fsname++; + ch = *fsname; + *fsname = 0; + + childobj = grub_zfs_to_cpu64 ((((dsl_dir_phys_t *) DN_BONUS (&mdn->dn)))->dd_child_dir_zapobj, mdn->endian); + err = dnode_get (mosmdn, childobj, + DMU_OT_DSL_DIR_CHILD_MAP, mdn, data); + if (err) + return err; + + err = zap_lookup (mdn, cname, &objnum, data, 0); + if (err) + return err; + + err = dnode_get (mosmdn, objnum, 0, mdn, data); + if (err) + return err; + + *fsname = ch; + } + return GRUB_ERR_NONE; +} + +static grub_err_t +make_mdn (dnode_end_t * mdn, struct grub_zfs_data *data) +{ + void *osp; + blkptr_t *bp; + grub_size_t ospsize = 0; + grub_err_t err; + + grub_dprintf ("zfs", "endian = %d\n", mdn->endian); + + bp = &(((dsl_dataset_phys_t *) DN_BONUS (&mdn->dn))->ds_bp); + err = zio_read (bp, mdn->endian, &osp, &ospsize, data); + if (err) + return err; + if (ospsize < OBJSET_PHYS_SIZE_V14) + { + grub_free (osp); + return grub_error (GRUB_ERR_BAD_FS, "too small osp"); + } + + mdn->endian = (grub_zfs_to_cpu64 (bp->blk_prop, mdn->endian)>>63) & 1; + grub_memmove ((char *) &(mdn->dn), + (char *) &((objset_phys_t *) osp)->os_meta_dnode, DNODE_SIZE); + grub_free (osp); + return GRUB_ERR_NONE; +} + +/* Context for dnode_get_fullpath. */ +struct dnode_get_fullpath_ctx +{ + struct subvolume *subvol; + grub_uint64_t salt; + int keyn; +}; + +/* Helper for dnode_get_fullpath. */ +static int +count_zap_keys (const void *name __attribute__ ((unused)), + grub_size_t namelen __attribute__ ((unused)), + const void *val_in __attribute__ ((unused)), + grub_size_t nelem __attribute__ ((unused)), + grub_size_t elemsize __attribute__ ((unused)), + void *data) +{ + struct dnode_get_fullpath_ctx *ctx = data; + + ctx->subvol->nkeys++; + return 0; +} + +/* Helper for dnode_get_fullpath. */ +static int +load_zap_key (const void *name, grub_size_t namelen, const void *val_in, + grub_size_t nelem, grub_size_t elemsize, void *data) +{ + struct dnode_get_fullpath_ctx *ctx = data; + + if (namelen != 1) + { + grub_dprintf ("zfs", "Unexpected key index size %" PRIuGRUB_SIZE "\n", + namelen); + return 0; + } + + if (elemsize != 1) + { + grub_dprintf ("zfs", "Unexpected key element size %" PRIuGRUB_SIZE "\n", + elemsize); + return 0; + } + + ctx->subvol->keyring[ctx->keyn].txg = + grub_be_to_cpu64 (*(grub_uint64_t *) name); + ctx->subvol->keyring[ctx->keyn].algo = + grub_le_to_cpu64 (*(grub_uint64_t *) val_in); + ctx->subvol->keyring[ctx->keyn].cipher = + grub_zfs_load_key (val_in, nelem, ctx->salt, + ctx->subvol->keyring[ctx->keyn].algo); + ctx->keyn++; + return 0; +} + +static grub_err_t +dnode_get_fullpath (const char *fullpath, struct subvolume *subvol, + dnode_end_t * dn, int *isfs, + struct grub_zfs_data *data) +{ + char *fsname, *snapname; + const char *ptr_at, *filename; + grub_uint64_t headobj; + grub_uint64_t keychainobj; + grub_err_t err; + + ptr_at = grub_strchr (fullpath, '@'); + if (! ptr_at) + { + *isfs = 1; + filename = 0; + snapname = 0; + fsname = grub_strdup (fullpath); + } + else + { + const char *ptr_slash = grub_strchr (ptr_at, '/'); + + *isfs = 0; + fsname = grub_malloc (ptr_at - fullpath + 1); + if (!fsname) + return grub_errno; + grub_memcpy (fsname, fullpath, ptr_at - fullpath); + fsname[ptr_at - fullpath] = 0; + if (ptr_at[1] && ptr_at[1] != '/') + { + snapname = grub_malloc (ptr_slash - ptr_at); + if (!snapname) + { + grub_free (fsname); + return grub_errno; + } + grub_memcpy (snapname, ptr_at + 1, ptr_slash - ptr_at - 1); + snapname[ptr_slash - ptr_at - 1] = 0; + } + else + snapname = 0; + if (ptr_slash) + filename = ptr_slash; + else + filename = "/"; + grub_dprintf ("zfs", "fsname = '%s' snapname='%s' filename = '%s'\n", + fsname, snapname, filename); + } + grub_dprintf ("zfs", "alive\n"); + err = get_filesystem_dnode (&(data->mos), fsname, dn, data); + if (err) + { + grub_free (fsname); + grub_free (snapname); + return err; + } + + grub_dprintf ("zfs", "alive\n"); + + headobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&dn->dn))->dd_head_dataset_obj, dn->endian); + + grub_dprintf ("zfs", "endian = %d\n", subvol->mdn.endian); + + err = dnode_get (&(data->mos), headobj, 0, &subvol->mdn, data); + if (err) + { + grub_free (fsname); + grub_free (snapname); + return err; + } + grub_dprintf ("zfs", "endian = %d\n", subvol->mdn.endian); + + keychainobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&dn->dn))->keychain, dn->endian); + if (grub_zfs_load_key && keychainobj) + { + struct dnode_get_fullpath_ctx ctx = { + .subvol = subvol, + .keyn = 0 + }; + dnode_end_t keychain_dn, props_dn; + grub_uint64_t propsobj; + propsobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&dn->dn))->dd_props_zapobj, dn->endian); + + err = dnode_get (&(data->mos), propsobj, DMU_OT_DSL_PROPS, + &props_dn, data); + if (err) + { + grub_free (fsname); + grub_free (snapname); + return err; + } + + err = zap_lookup (&props_dn, "salt", &ctx.salt, data, 0); + if (err == GRUB_ERR_FILE_NOT_FOUND) + { + err = 0; + grub_errno = 0; + ctx.salt = 0; + } + if (err) + { + grub_dprintf ("zfs", "failed here\n"); + return err; + } + + err = dnode_get (&(data->mos), keychainobj, DMU_OT_DSL_KEYCHAIN, + &keychain_dn, data); + if (err) + { + grub_free (fsname); + grub_free (snapname); + return err; + } + subvol->nkeys = 0; + zap_iterate (&keychain_dn, 8, count_zap_keys, &ctx, data); + subvol->keyring = grub_calloc (subvol->nkeys, sizeof (subvol->keyring[0])); + if (!subvol->keyring) + { + grub_free (fsname); + grub_free (snapname); + return err; + } + zap_iterate (&keychain_dn, 8, load_zap_key, &ctx, data); + } + + if (snapname) + { + grub_uint64_t snapobj; + + snapobj = grub_zfs_to_cpu64 (((dsl_dataset_phys_t *) DN_BONUS (&subvol->mdn.dn))->ds_snapnames_zapobj, subvol->mdn.endian); + + err = dnode_get (&(data->mos), snapobj, + DMU_OT_DSL_DS_SNAP_MAP, &subvol->mdn, data); + if (!err) + err = zap_lookup (&subvol->mdn, snapname, &headobj, data, 0); + if (!err) + err = dnode_get (&(data->mos), headobj, DMU_OT_DSL_DATASET, + &subvol->mdn, data); + if (err) + { + grub_free (fsname); + grub_free (snapname); + return err; + } + } + + subvol->obj = headobj; + + make_mdn (&subvol->mdn, data); + + grub_dprintf ("zfs", "endian = %d\n", subvol->mdn.endian); + + if (*isfs) + { + grub_free (fsname); + grub_free (snapname); + return GRUB_ERR_NONE; + } + err = dnode_get_path (subvol, filename, dn, data); + grub_free (fsname); + grub_free (snapname); + return err; +} + +static int +nvlist_find_value (const char *nvlist_in, const char *name, + int valtype, char **val, + grub_size_t *size_out, grub_size_t *nelm_out) +{ + grub_size_t nvp_name_len, name_len = grub_strlen(name); + int type; + const char *nvpair=NULL,*nvlist=nvlist_in; + char *nvp_name; + + /* Verify if the 1st and 2nd byte in the nvlist are valid. */ + /* NOTE: independently of what endianness header announces all + subsequent values are big-endian. */ + if (nvlist[0] != NV_ENCODE_XDR || (nvlist[1] != NV_LITTLE_ENDIAN + && nvlist[1] != NV_BIG_ENDIAN)) + { + grub_dprintf ("zfs", "incorrect nvlist header\n"); + grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); + return 0; + } + + /* + * Loop thru the nvpair list + * The XDR representation of an integer is in big-endian byte order. + */ + while ((nvpair=nvlist_next_nvpair(nvlist,nvpair))) + { + nvpair_name(nvpair,&nvp_name, &nvp_name_len); + type = nvpair_type(nvpair); + if (type == valtype + && (nvp_name_len == name_len + || (nvp_name_len > name_len && nvp_name[name_len] == '\0')) + && grub_memcmp (nvp_name, name, name_len) == 0) + { + return nvpair_value(nvpair,val,size_out,nelm_out); + } + } + return 0; +} + +int +grub_zfs_nvlist_lookup_uint64 (const char *nvlist, const char *name, + grub_uint64_t * out) +{ + char *nvpair; + grub_size_t size; + int found; + + found = nvlist_find_value (nvlist, name, DATA_TYPE_UINT64, &nvpair, &size, 0); + if (!found) + return 0; + if (size < sizeof (grub_uint64_t)) + { + grub_error (GRUB_ERR_BAD_FS, "invalid uint64"); + return 0; + } + + *out = grub_be_to_cpu64 (grub_get_unaligned64 (nvpair)); + return 1; +} + +char * +grub_zfs_nvlist_lookup_string (const char *nvlist, const char *name) +{ + char *nvpair; + char *ret; + grub_size_t slen; + grub_size_t size; + int found; + + found = nvlist_find_value (nvlist, name, DATA_TYPE_STRING, &nvpair, &size, 0); + if (!found) + return 0; + if (size < 4) + { + grub_error (GRUB_ERR_BAD_FS, "invalid string"); + return 0; + } + slen = grub_be_to_cpu32 (grub_get_unaligned32 (nvpair)); + if (slen > size - 4) + slen = size - 4; + ret = grub_malloc (slen + 1); + if (!ret) + return 0; + grub_memcpy (ret, nvpair + 4, slen); + ret[slen] = 0; + return ret; +} + +char * +grub_zfs_nvlist_lookup_nvlist (const char *nvlist, const char *name) +{ + char *nvpair; + char *ret; + grub_size_t size, sz; + int found; + + found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST, &nvpair, + &size, 0); + if (!found) + return 0; + + if (grub_add (size, 3 * sizeof (grub_uint32_t), &sz)) + return 0; + + ret = grub_zalloc (sz); + if (!ret) + return 0; + grub_memcpy (ret, nvlist, sizeof (grub_uint32_t)); + + grub_memcpy (ret + sizeof (grub_uint32_t), nvpair, size); + return ret; +} + +int +grub_zfs_nvlist_lookup_nvlist_array_get_nelm (const char *nvlist, + const char *name) +{ + char *nvpair; + grub_size_t nelm, size; + int found; + + found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST_ARRAY, &nvpair, + &size, &nelm); + if (! found) + return -1; + return nelm; +} + +static int +get_nvlist_size (const char *beg, const char *limit) +{ + const char *ptr; + grub_uint32_t encode_size; + + ptr = beg + 8; + + while (ptr < limit + && (encode_size = grub_be_to_cpu32 (grub_get_unaligned32 (ptr)))) + ptr += encode_size; /* goto the next nvpair */ + ptr += 8; + return (ptr > limit) ? -1 : (ptr - beg); +} + +char * +grub_zfs_nvlist_lookup_nvlist_array (const char *nvlist, const char *name, + grub_size_t index) +{ + char *nvpair, *nvpairptr; + int found; + char *ret; + grub_size_t size; + unsigned i; + grub_size_t nelm; + int elemsize = 0; + + found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST_ARRAY, &nvpair, + &size, &nelm); + if (!found) + return 0; + if (index >= nelm) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "trying to lookup past nvlist array"); + return 0; + } + + nvpairptr = nvpair; + + for (i = 0; i < index; i++) + { + int r; + r = get_nvlist_size (nvpairptr, nvpair + size); + + if (r < 0) + { + grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist array"); + return NULL; + } + nvpairptr += r; + } + + elemsize = get_nvlist_size (nvpairptr, nvpair + size); + + if (elemsize < 0) + { + grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist array"); + return 0; + } + + ret = grub_zalloc (elemsize + sizeof (grub_uint32_t)); + if (!ret) + return 0; + grub_memcpy (ret, nvlist, sizeof (grub_uint32_t)); + + grub_memcpy (ret + sizeof (grub_uint32_t), nvpairptr, elemsize); + return ret; +} + +static void +unmount_device (struct grub_zfs_device_desc *desc) +{ + unsigned i; + switch (desc->type) + { + case DEVICE_LEAF: + if (!desc->original && desc->dev) + grub_device_close (desc->dev); + return; + case DEVICE_RAIDZ: + case DEVICE_MIRROR: + for (i = 0; i < desc->n_children; i++) + unmount_device (&desc->children[i]); + grub_free (desc->children); + return; + } +} + +static void +zfs_unmount (struct grub_zfs_data *data) +{ + unsigned i; + for (i = 0; i < data->n_devices_attached; i++) + unmount_device (&data->devices_attached[i]); + grub_free (data->devices_attached); + grub_free (data->dnode_buf); + grub_free (data->dnode_mdn); + grub_free (data->file_buf); + for (i = 0; i < data->subvol.nkeys; i++) + grub_crypto_cipher_close (data->subvol.keyring[i].cipher); + grub_free (data->subvol.keyring); + grub_free (data); +} + +/* + * zfs_mount() locates a valid uberblock of the root pool and read in its MOS + * to the memory address MOS. + * + */ +static struct grub_zfs_data * +zfs_mount (grub_device_t dev) +{ + struct grub_zfs_data *data = 0; + grub_err_t err; + void *osp = 0; + grub_size_t ospsize; + grub_zfs_endian_t ub_endian = GRUB_ZFS_UNKNOWN_ENDIAN; + uberblock_t *ub; + int inserted; + + if (! dev->disk) + { + grub_error (GRUB_ERR_BAD_DEVICE, "not a disk"); + return 0; + } + + data = grub_zalloc (sizeof (*data)); + if (!data) + return 0; +#if 0 + /* if it's our first time here, zero the best uberblock out */ + if (data->best_drive == 0 && data->best_part == 0 && find_best_root) + grub_memset (¤t_uberblock, 0, sizeof (uberblock_t)); +#endif + + data->n_devices_allocated = 16; + data->devices_attached = grub_malloc (sizeof (data->devices_attached[0]) + * data->n_devices_allocated); + data->n_devices_attached = 0; + err = scan_disk (dev, data, 1, &inserted); + if (err) + { + zfs_unmount (data); + return NULL; + } + + ub = &(data->current_uberblock); + ub_endian = (grub_zfs_to_cpu64 (ub->ub_magic, + GRUB_ZFS_LITTLE_ENDIAN) == UBERBLOCK_MAGIC + ? GRUB_ZFS_LITTLE_ENDIAN : GRUB_ZFS_BIG_ENDIAN); + + err = zio_read (&ub->ub_rootbp, ub_endian, + &osp, &ospsize, data); + if (err) + { + zfs_unmount (data); + return NULL; + } + + if (ospsize < OBJSET_PHYS_SIZE_V14) + { + grub_error (GRUB_ERR_BAD_FS, "OSP too small"); + grub_free (osp); + zfs_unmount (data); + return NULL; + } + + if (ub->ub_version >= SPA_VERSION_FEATURES && + check_mos_features(&((objset_phys_t *) osp)->os_meta_dnode,ub_endian, + data) != 0) + { + grub_error (GRUB_ERR_BAD_FS, "Unsupported features in pool"); + grub_free (osp); + zfs_unmount (data); + return NULL; + } + + /* Got the MOS. Save it at the memory addr MOS. */ + grub_memmove (&(data->mos.dn), &((objset_phys_t *) osp)->os_meta_dnode, + DNODE_SIZE); + data->mos.endian = (grub_zfs_to_cpu64 (ub->ub_rootbp.blk_prop, + ub_endian) >> 63) & 1; + grub_free (osp); + + return data; +} + +grub_err_t +grub_zfs_fetch_nvlist (grub_device_t dev, char **nvlist) +{ + struct grub_zfs_data *zfs; + grub_err_t err; + + zfs = zfs_mount (dev); + if (!zfs) + return grub_errno; + err = zfs_fetch_nvlist (zfs->device_original, nvlist); + zfs_unmount (zfs); + return err; +} + +static grub_err_t +zfs_label (grub_device_t device, char **label) +{ + char *nvlist; + grub_err_t err; + struct grub_zfs_data *data; + + data = zfs_mount (device); + if (! data) + return grub_errno; + + err = zfs_fetch_nvlist (data->device_original, &nvlist); + if (err) + { + zfs_unmount (data); + return err; + } + + *label = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME); + grub_free (nvlist); + zfs_unmount (data); + return grub_errno; +} + +static grub_err_t +zfs_uuid (grub_device_t device, char **uuid) +{ + struct grub_zfs_data *data; + + *uuid = 0; + + data = zfs_mount (device); + if (! data) + return grub_errno; + + *uuid = grub_xasprintf ("%016llx", (long long unsigned) data->guid); + zfs_unmount (data); + if (! *uuid) + return grub_errno; + return GRUB_ERR_NONE; +} + +static grub_err_t +zfs_mtime (grub_device_t device, grub_int64_t *mt) +{ + struct grub_zfs_data *data; + grub_zfs_endian_t ub_endian = GRUB_ZFS_UNKNOWN_ENDIAN; + uberblock_t *ub; + + *mt = 0; + + data = zfs_mount (device); + if (! data) + return grub_errno; + + ub = &(data->current_uberblock); + ub_endian = (grub_zfs_to_cpu64 (ub->ub_magic, + GRUB_ZFS_LITTLE_ENDIAN) == UBERBLOCK_MAGIC + ? GRUB_ZFS_LITTLE_ENDIAN : GRUB_ZFS_BIG_ENDIAN); + + *mt = grub_zfs_to_cpu64 (ub->ub_timestamp, ub_endian); + zfs_unmount (data); + return GRUB_ERR_NONE; +} + +/* + * zfs_open() locates a file in the rootpool by following the + * MOS and places the dnode of the file in the memory address DNODE. + */ +static grub_err_t +grub_zfs_open (struct grub_file *file, const char *fsfilename) +{ + struct grub_zfs_data *data; + grub_err_t err; + int isfs; + + data = zfs_mount (file->device); + if (! data) + return grub_errno; + + err = dnode_get_fullpath (fsfilename, &(data->subvol), + &(data->dnode), &isfs, data); + if (err) + { + zfs_unmount (data); + return err; + } + + if (isfs) + { + zfs_unmount (data); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("missing `%c' symbol"), '@'); + } + + /* We found the dnode for this file. Verify if it is a plain file. */ + if (data->dnode.dn.dn_type != DMU_OT_PLAIN_FILE_CONTENTS) + { + zfs_unmount (data); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file")); + } + + /* get the file size and set the file position to 0 */ + + /* + * For DMU_OT_SA we will need to locate the SIZE attribute + * attribute, which could be either in the bonus buffer + * or the "spill" block. + */ + if (data->dnode.dn.dn_bonustype == DMU_OT_SA) + { + void *sahdrp; + int hdrsize; + + if (data->dnode.dn.dn_bonuslen != 0) + { + sahdrp = (sa_hdr_phys_t *) DN_BONUS (&data->dnode.dn); + } + else if (data->dnode.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) + { + blkptr_t *bp = &data->dnode.dn.dn_spill; + + err = zio_read (bp, data->dnode.endian, &sahdrp, NULL, data); + if (err) + return err; + } + else + { + return grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); + } + + hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); + file->size = grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + hdrsize + SA_SIZE_OFFSET), data->dnode.endian); + } + else if (data->dnode.dn.dn_bonustype == DMU_OT_ZNODE) + { + file->size = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&data->dnode.dn))->zp_size, data->dnode.endian); + } + else + return grub_error (GRUB_ERR_BAD_FS, "bad bonus type"); + + file->data = data; + file->offset = 0; + +#ifndef GRUB_UTIL + grub_dl_ref (my_mod); +#endif + + return GRUB_ERR_NONE; +} + +static grub_ssize_t +grub_zfs_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_zfs_data *data = (struct grub_zfs_data *) file->data; + grub_size_t blksz, movesize; + grub_size_t length; + grub_size_t read; + grub_err_t err; + + /* + * If offset is in memory, move it into the buffer provided and return. + */ + if (file->offset >= data->file_start + && file->offset + len <= data->file_end) + { + grub_memmove (buf, data->file_buf + file->offset - data->file_start, + len); + return len; + } + + blksz = grub_zfs_to_cpu16 (data->dnode.dn.dn_datablkszsec, + data->dnode.endian) << SPA_MINBLOCKSHIFT; + + if (blksz == 0) + { + grub_error (GRUB_ERR_BAD_FS, "0-sized block"); + return -1; + } + + /* + * Entire Dnode is too big to fit into the space available. We + * will need to read it in chunks. This could be optimized to + * read in as large a chunk as there is space available, but for + * now, this only reads in one data block at a time. + */ + length = len; + read = 0; + while (length) + { + void *t; + /* + * Find requested blkid and the offset within that block. + */ + grub_uint64_t blkid = grub_divmod64 (file->offset + read, blksz, 0); + grub_free (data->file_buf); + data->file_buf = 0; + + err = dmu_read (&(data->dnode), blkid, &t, + 0, data); + data->file_buf = t; + if (err) + { + data->file_buf = NULL; + data->file_start = data->file_end = 0; + return -1; + } + + data->file_start = blkid * blksz; + data->file_end = data->file_start + blksz; + + movesize = data->file_end - file->offset - read; + if (movesize > length) + movesize = length; + + grub_memmove (buf, data->file_buf + file->offset + read + - data->file_start, movesize); + buf += movesize; + length -= movesize; + read += movesize; + } + + return len; +} + +static grub_err_t +grub_zfs_close (grub_file_t file) +{ + zfs_unmount ((struct grub_zfs_data *) file->data); + +#ifndef GRUB_UTIL + grub_dl_unref (my_mod); +#endif + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_zfs_getmdnobj (grub_device_t dev, const char *fsfilename, + grub_uint64_t *mdnobj) +{ + struct grub_zfs_data *data; + grub_err_t err; + int isfs; + + data = zfs_mount (dev); + if (! data) + return grub_errno; + + err = dnode_get_fullpath (fsfilename, &(data->subvol), + &(data->dnode), &isfs, data); + *mdnobj = data->subvol.obj; + zfs_unmount (data); + return err; +} + +static grub_err_t +fill_fs_info (struct grub_dirhook_info *info, + dnode_end_t mdn, struct grub_zfs_data *data) +{ + grub_err_t err; + dnode_end_t dn; + grub_uint64_t objnum; + grub_uint64_t headobj; + + grub_memset (info, 0, sizeof (*info)); + + info->dir = 1; + + if (mdn.dn.dn_type == DMU_OT_DSL_DIR) + { + headobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&mdn.dn))->dd_head_dataset_obj, mdn.endian); + + err = dnode_get (&(data->mos), headobj, 0, &mdn, data); + if (err) + { + grub_dprintf ("zfs", "failed here\n"); + return err; + } + } + err = make_mdn (&mdn, data); + if (err) + return err; + err = dnode_get (&mdn, MASTER_NODE_OBJ, DMU_OT_MASTER_NODE, + &dn, data); + if (err) + { + grub_dprintf ("zfs", "failed here\n"); + return err; + } + + err = zap_lookup (&dn, ZFS_ROOT_OBJ, &objnum, data, 0); + if (err) + { + grub_dprintf ("zfs", "failed here\n"); + return err; + } + + err = dnode_get (&mdn, objnum, 0, &dn, data); + if (err) + { + grub_dprintf ("zfs", "failed here\n"); + return err; + } + + if (dn.dn.dn_bonustype == DMU_OT_SA) + { + void *sahdrp; + int hdrsize; + + if (dn.dn.dn_bonuslen != 0) + { + sahdrp = (sa_hdr_phys_t *) DN_BONUS (&dn.dn); + } + else if (dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) + { + blkptr_t *bp = &dn.dn.dn_spill; + + err = zio_read (bp, dn.endian, &sahdrp, NULL, data); + if (err) + return err; + } + else + { + grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); + return grub_errno; + } + + hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); + info->mtimeset = 1; + info->mtime = grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + hdrsize + SA_MTIME_OFFSET), dn.endian); + } + + if (dn.dn.dn_bonustype == DMU_OT_ZNODE) + { + info->mtimeset = 1; + info->mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0], dn.endian); + } + return 0; +} + +/* Helper for grub_zfs_dir. */ +static int +iterate_zap (const char *name, grub_uint64_t val, struct grub_zfs_dir_ctx *ctx) +{ + grub_err_t err; + struct grub_dirhook_info info; + + dnode_end_t dn; + grub_memset (&info, 0, sizeof (info)); + + err = dnode_get (&(ctx->data->subvol.mdn), val, 0, &dn, ctx->data); + if (err) + { + grub_print_error (); + return 0; + } + + if (dn.dn.dn_bonustype == DMU_OT_SA) + { + void *sahdrp; + int hdrsize; + + if (dn.dn.dn_bonuslen != 0) + { + sahdrp = (sa_hdr_phys_t *) DN_BONUS (&ctx->data->dnode.dn); + } + else if (dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) + { + blkptr_t *bp = &dn.dn.dn_spill; + + err = zio_read (bp, dn.endian, &sahdrp, NULL, ctx->data); + if (err) + { + grub_print_error (); + return 0; + } + } + else + { + grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); + grub_print_error (); + return 0; + } + + hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); + info.mtimeset = 1; + info.mtime = grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + hdrsize + SA_MTIME_OFFSET), dn.endian); + info.case_insensitive = ctx->data->subvol.case_insensitive; + } + + if (dn.dn.dn_bonustype == DMU_OT_ZNODE) + { + info.mtimeset = 1; + info.mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0], + dn.endian); + } + info.dir = (dn.dn.dn_type == DMU_OT_DIRECTORY_CONTENTS); + grub_dprintf ("zfs", "type=%d, name=%s\n", + (int)dn.dn.dn_type, (char *)name); + return ctx->hook (name, &info, ctx->hook_data); +} + +/* Helper for grub_zfs_dir. */ +static int +iterate_zap_fs (const char *name, grub_uint64_t val, + struct grub_zfs_dir_ctx *ctx) +{ + grub_err_t err; + struct grub_dirhook_info info; + + dnode_end_t mdn; + err = dnode_get (&(ctx->data->mos), val, 0, &mdn, ctx->data); + if (err) + { + grub_errno = 0; + return 0; + } + if (mdn.dn.dn_type != DMU_OT_DSL_DIR) + return 0; + + err = fill_fs_info (&info, mdn, ctx->data); + if (err) + { + grub_errno = 0; + return 0; + } + return ctx->hook (name, &info, ctx->hook_data); +} + +/* Helper for grub_zfs_dir. */ +static int +iterate_zap_snap (const char *name, grub_uint64_t val, + struct grub_zfs_dir_ctx *ctx) +{ + grub_err_t err; + struct grub_dirhook_info info; + char *name2; + int ret; + + dnode_end_t mdn; + + err = dnode_get (&(ctx->data->mos), val, 0, &mdn, ctx->data); + if (err) + { + grub_errno = 0; + return 0; + } + + if (mdn.dn.dn_type != DMU_OT_DSL_DATASET) + return 0; + + err = fill_fs_info (&info, mdn, ctx->data); + if (err) + { + grub_errno = 0; + return 0; + } + + name2 = grub_malloc (grub_strlen (name) + 2); + name2[0] = '@'; + grub_memcpy (name2 + 1, name, grub_strlen (name) + 1); + ret = ctx->hook (name2, &info, ctx->hook_data); + grub_free (name2); + return ret; +} + +static grub_err_t +grub_zfs_dir (grub_device_t device, const char *path, + grub_fs_dir_hook_t hook, void *hook_data) +{ + struct grub_zfs_dir_ctx ctx = { + .hook = hook, + .hook_data = hook_data + }; + struct grub_zfs_data *data; + grub_err_t err; + int isfs; + + data = zfs_mount (device); + if (! data) + return grub_errno; + err = dnode_get_fullpath (path, &(data->subvol), &(data->dnode), &isfs, data); + if (err) + { + zfs_unmount (data); + return err; + } + ctx.data = data; + + if (isfs) + { + grub_uint64_t childobj, headobj; + grub_uint64_t snapobj; + dnode_end_t dn; + struct grub_dirhook_info info; + + err = fill_fs_info (&info, data->dnode, data); + if (err) + { + zfs_unmount (data); + return err; + } + if (hook ("@", &info, hook_data)) + { + zfs_unmount (data); + return GRUB_ERR_NONE; + } + + childobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&data->dnode.dn))->dd_child_dir_zapobj, data->dnode.endian); + headobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&data->dnode.dn))->dd_head_dataset_obj, data->dnode.endian); + err = dnode_get (&(data->mos), childobj, + DMU_OT_DSL_DIR_CHILD_MAP, &dn, data); + if (err) + { + zfs_unmount (data); + return err; + } + + zap_iterate_u64 (&dn, iterate_zap_fs, data, &ctx); + + err = dnode_get (&(data->mos), headobj, DMU_OT_DSL_DATASET, &dn, data); + if (err) + { + zfs_unmount (data); + return err; + } + + snapobj = grub_zfs_to_cpu64 (((dsl_dataset_phys_t *) DN_BONUS (&dn.dn))->ds_snapnames_zapobj, dn.endian); + + err = dnode_get (&(data->mos), snapobj, + DMU_OT_DSL_DS_SNAP_MAP, &dn, data); + if (err) + { + zfs_unmount (data); + return err; + } + + zap_iterate_u64 (&dn, iterate_zap_snap, data, &ctx); + } + else + { + if (data->dnode.dn.dn_type != DMU_OT_DIRECTORY_CONTENTS) + { + zfs_unmount (data); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); + } + zap_iterate_u64 (&(data->dnode), iterate_zap, data, &ctx); + } + zfs_unmount (data); + return grub_errno; +} + +static int +check_feature (const char *name, grub_uint64_t val, + struct grub_zfs_dir_ctx *ctx __attribute__((unused))) +{ + int i; + if (val == 0) + return 0; + if (name[0] == 0) + return 0; + for (i = 0; spa_feature_names[i] != NULL; i++) + if (grub_strcmp (name, spa_feature_names[i]) == 0) + return 0; + return 1; +} + +/* + * Checks whether the MOS features that are active are supported by this + * (GRUB's) implementation of ZFS. + * + * Return: + * 0: Success. + * errnum: Failure. + */ + +static grub_err_t +check_mos_features(dnode_phys_t *mosmdn_phys,grub_zfs_endian_t endian,struct grub_zfs_data* data ) +{ + grub_uint64_t objnum; + grub_err_t errnum = 0; + dnode_end_t dn,mosmdn; + mzap_phys_t* mzp; + grub_zfs_endian_t endianzap; + int size; + grub_memmove(&(mosmdn.dn),mosmdn_phys,sizeof(dnode_phys_t)); + mosmdn.endian=endian; + errnum = dnode_get(&mosmdn, DMU_POOL_DIRECTORY_OBJECT, + DMU_OT_OBJECT_DIRECTORY, &dn,data); + if (errnum != 0) + return errnum; + + /* + * Find the object number for 'features_for_read' and retrieve its + * corresponding dnode. Note that we don't check features_for_write + * because GRUB is not opening the pool for write. + */ + errnum = zap_lookup(&dn, DMU_POOL_FEATURES_FOR_READ, &objnum, data,0); + if (errnum != 0) + return errnum; + + errnum = dnode_get(&mosmdn, objnum, DMU_OTN_ZAP_METADATA, &dn, data); + if (errnum != 0) + return errnum; + + errnum = dmu_read(&dn, 0, (void**)&mzp, &endianzap,data); + if (errnum != 0) + return errnum; + + size = grub_zfs_to_cpu16 (dn.dn.dn_datablkszsec, dn.endian) << SPA_MINBLOCKSHIFT; + return mzap_iterate (mzp,endianzap, size, check_feature,NULL); +} + + +#ifdef GRUB_UTIL +static grub_err_t +grub_zfs_embed (grub_device_t device __attribute__ ((unused)), + unsigned int *nsectors, + unsigned int max_nsectors, + grub_embed_type_t embed_type, + grub_disk_addr_t **sectors) +{ + unsigned i; + + if (embed_type != GRUB_EMBED_PCBIOS) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "ZFS currently supports only PC-BIOS embedding"); + + if ((VDEV_BOOT_SIZE >> GRUB_DISK_SECTOR_BITS) < *nsectors) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("your core.img is unusually large. " + "It won't fit in the embedding area")); + + *nsectors = (VDEV_BOOT_SIZE >> GRUB_DISK_SECTOR_BITS); + if (*nsectors > max_nsectors) + *nsectors = max_nsectors; + *sectors = grub_calloc (*nsectors, sizeof (**sectors)); + if (!*sectors) + return grub_errno; + for (i = 0; i < *nsectors; i++) + (*sectors)[i] = i + (VDEV_BOOT_OFFSET >> GRUB_DISK_SECTOR_BITS); + + return GRUB_ERR_NONE; +} +#endif + +static struct grub_fs grub_zfs_fs = { + .name = "zfs", + .fs_dir = grub_zfs_dir, + .fs_open = grub_zfs_open, + .fs_read = grub_zfs_read, + .fs_close = grub_zfs_close, + .fs_label = zfs_label, + .fs_uuid = zfs_uuid, + .fs_mtime = zfs_mtime, +#ifdef GRUB_UTIL + .fs_embed = grub_zfs_embed, + .reserved_first_sector = 1, + .blocklist_install = 0, +#endif + .next = 0 +}; + +GRUB_MOD_INIT (zfs) +{ + COMPILE_TIME_ASSERT (sizeof (zap_leaf_chunk_t) == ZAP_LEAF_CHUNKSIZE); + grub_fs_register (&grub_zfs_fs); +#ifndef GRUB_UTIL + my_mod = mod; +#endif +} + +GRUB_MOD_FINI (zfs) +{ + grub_fs_unregister (&grub_zfs_fs); +} diff --git a/grub-core/fs/zfs/zfs_fletcher.c b/grub-core/fs/zfs/zfs_fletcher.c new file mode 100644 index 0000000..7d27b05 --- /dev/null +++ b/grub-core/fs/zfs/zfs_fletcher.c @@ -0,0 +1,84 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2009 Free Software Foundation, Inc. + * Copyright 2007 Sun Microsystems, 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/zfs/zfs.h> +#include <grub/zfs/zio.h> +#include <grub/zfs/dnode.h> +#include <grub/zfs/uberblock_impl.h> +#include <grub/zfs/vdev_impl.h> +#include <grub/zfs/zio_checksum.h> +#include <grub/zfs/zap_impl.h> +#include <grub/zfs/zap_leaf.h> +#include <grub/zfs/zfs_znode.h> +#include <grub/zfs/dmu.h> +#include <grub/zfs/dmu_objset.h> +#include <grub/zfs/dsl_dir.h> +#include <grub/zfs/dsl_dataset.h> + +void +fletcher_2(const void *buf, grub_uint64_t size, grub_zfs_endian_t endian, + zio_cksum_t *zcp) +{ + const grub_uint64_t *ip = buf; + const grub_uint64_t *ipend = ip + (size / sizeof (grub_uint64_t)); + grub_uint64_t a0, b0, a1, b1; + + for (a0 = b0 = a1 = b1 = 0; ip < ipend; ip += 2) + { + a0 += grub_zfs_to_cpu64 (ip[0], endian); + a1 += grub_zfs_to_cpu64 (ip[1], endian); + b0 += a0; + b1 += a1; + } + + zcp->zc_word[0] = grub_cpu_to_zfs64 (a0, endian); + zcp->zc_word[1] = grub_cpu_to_zfs64 (a1, endian); + zcp->zc_word[2] = grub_cpu_to_zfs64 (b0, endian); + zcp->zc_word[3] = grub_cpu_to_zfs64 (b1, endian); +} + +void +fletcher_4 (const void *buf, grub_uint64_t size, grub_zfs_endian_t endian, + zio_cksum_t *zcp) +{ + const grub_uint32_t *ip = buf; + const grub_uint32_t *ipend = ip + (size / sizeof (grub_uint32_t)); + grub_uint64_t a, b, c, d; + + for (a = b = c = d = 0; ip < ipend; ip++) + { + a += grub_zfs_to_cpu32 (ip[0], endian);; + b += a; + c += b; + d += c; + } + + zcp->zc_word[0] = grub_cpu_to_zfs64 (a, endian); + zcp->zc_word[1] = grub_cpu_to_zfs64 (b, endian); + zcp->zc_word[2] = grub_cpu_to_zfs64 (c, endian); + zcp->zc_word[3] = grub_cpu_to_zfs64 (d, endian); +} + diff --git a/grub-core/fs/zfs/zfs_lz4.c b/grub-core/fs/zfs/zfs_lz4.c new file mode 100644 index 0000000..5453822 --- /dev/null +++ b/grub-core/fs/zfs/zfs_lz4.c @@ -0,0 +1,285 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2013, Yann Collet. + * BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You can contact the author at : + * - LZ4 homepage : http://fastcompression.blogspot.com/p/lz4.html + * - LZ4 source repository : http://code.google.com/p/lz4/ + */ + +#include <grub/err.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/types.h> + +static int LZ4_uncompress_unknownOutputSize(const char *source, char *dest, + int isize, int maxOutputSize); + +/* + * CPU Feature Detection + */ + +/* 32 or 64 bits ? */ +#if (GRUB_CPU_SIZEOF_VOID_P == 8) +#define LZ4_ARCH64 1 +#else +#define LZ4_ARCH64 0 +#endif + +/* + * Compiler Options + */ + + +#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) + +#if (GCC_VERSION >= 302) || (defined (__INTEL_COMPILER) && __INTEL_COMPILER >= 800) || defined(__clang__) +#define expect(expr, value) (__builtin_expect((expr), (value))) +#else +#define expect(expr, value) (expr) +#endif + +#define likely(expr) expect((expr) != 0, 1) +#define unlikely(expr) expect((expr) != 0, 0) + +/* Basic types */ +#define BYTE grub_uint8_t +#define U16 grub_uint16_t +#define U32 grub_uint32_t +#define S32 grub_int32_t +#define U64 grub_uint64_t + +typedef struct _U16_S { + U16 v; +} GRUB_PACKED U16_S; +typedef struct _U32_S { + U32 v; +} GRUB_PACKED U32_S; +typedef struct _U64_S { + U64 v; +} GRUB_PACKED U64_S; + +#define A64(x) (((U64_S *)(x))->v) +#define A32(x) (((U32_S *)(x))->v) +#define A16(x) (((U16_S *)(x))->v) + +/* + * Constants + */ +#define MINMATCH 4 + +#define COPYLENGTH 8 +#define LASTLITERALS 5 + +#define ML_BITS 4 +#define ML_MASK ((1U<<ML_BITS)-1) +#define RUN_BITS (8-ML_BITS) +#define RUN_MASK ((1U<<RUN_BITS)-1) + +/* + * Architecture-specific macros + */ +#if LZ4_ARCH64 +#define STEPSIZE 8 +#define UARCH U64 +#define AARCH A64 +#define LZ4_COPYSTEP(s, d) A64(d) = A64(s); d += 8; s += 8; +#define LZ4_COPYPACKET(s, d) LZ4_COPYSTEP(s, d) +#define LZ4_SECURECOPY(s, d, e) if (d < e) LZ4_WILDCOPY(s, d, e) +#define HTYPE U32 +#define INITBASE(base) const BYTE* const base = ip +#else +#define STEPSIZE 4 +#define UARCH U32 +#define AARCH A32 +#define LZ4_COPYSTEP(s, d) A32(d) = A32(s); d += 4; s += 4; +#define LZ4_COPYPACKET(s, d) LZ4_COPYSTEP(s, d); LZ4_COPYSTEP(s, d); +#define LZ4_SECURECOPY LZ4_WILDCOPY +#define HTYPE const BYTE* +#define INITBASE(base) const int base = 0 +#endif + +#define LZ4_READ_LITTLEENDIAN_16(d, s, p) { d = (s) - grub_le_to_cpu16 (A16 (p)); } +#define LZ4_WRITE_LITTLEENDIAN_16(p, v) { A16(p) = grub_cpu_to_le16 (v); p += 2; } + +/* Macros */ +#define LZ4_WILDCOPY(s, d, e) do { LZ4_COPYPACKET(s, d) } while (d < e); + +/* Decompression functions */ +grub_err_t +lz4_decompress(void *s_start, void *d_start, grub_size_t s_len, grub_size_t d_len); + +grub_err_t +lz4_decompress(void *s_start, void *d_start, grub_size_t s_len, grub_size_t d_len) +{ + const BYTE *src = s_start; + U32 bufsiz = (src[0] << 24) | (src[1] << 16) | (src[2] << 8) | + src[3]; + + /* invalid compressed buffer size encoded at start */ + if (bufsiz + 4 > s_len) + return grub_error(GRUB_ERR_BAD_FS,"lz4 decompression failed."); + + /* + * Returns 0 on success (decompression function returned non-negative) + * and appropriate error on failure (decompression function returned negative). + */ + return (LZ4_uncompress_unknownOutputSize((char*)s_start + 4, d_start, bufsiz, + d_len) < 0)?grub_error(GRUB_ERR_BAD_FS,"lz4 decompression failed."):0; +} + +static int +LZ4_uncompress_unknownOutputSize(const char *source, + char *dest, int isize, int maxOutputSize) +{ + /* Local Variables */ + const BYTE * ip = (const BYTE *) source; + const BYTE *const iend = ip + isize; + const BYTE * ref; + + BYTE * op = (BYTE *) dest; + BYTE *const oend = op + maxOutputSize; + BYTE *cpy; + + grub_size_t dec[] = { 0, 3, 2, 3, 0, 0, 0, 0 }; + + /* Main Loop */ + while (ip < iend) { + BYTE token; + int length; + + /* get runlength */ + token = *ip++; + if ((length = (token >> ML_BITS)) == RUN_MASK) { + int s = 255; + while ((ip < iend) && (s == 255)) { + s = *ip++; + length += s; + } + } + /* copy literals */ + if ((grub_addr_t) length > ~(grub_addr_t)op) + goto _output_error; + cpy = op + length; + if ((cpy > oend - COPYLENGTH) || + (ip + length > iend - COPYLENGTH)) { + if (cpy > oend) + /* + * Error: request to write beyond destination + * buffer. + */ + goto _output_error; + if (ip + length > iend) + /* + * Error : request to read beyond source + * buffer. + */ + goto _output_error; + grub_memcpy(op, ip, length); + op += length; + ip += length; + if (ip < iend) + /* Error : LZ4 format violation */ + goto _output_error; + /* Necessarily EOF, due to parsing restrictions. */ + break; + } + LZ4_WILDCOPY(ip, op, cpy); + ip -= (op - cpy); + op = cpy; + + /* get offset */ + LZ4_READ_LITTLEENDIAN_16(ref, cpy, ip); + ip += 2; + if (ref < (BYTE * const) dest) + /* + * Error: offset creates reference outside of + * destination buffer. + */ + goto _output_error; + + /* get matchlength */ + if ((length = (token & ML_MASK)) == ML_MASK) { + while (ip < iend) { + int s = *ip++; + length += s; + if (s == 255) + continue; + break; + } + } + /* copy repeated sequence */ + if unlikely(op - ref < STEPSIZE) { +#if LZ4_ARCH64 + grub_size_t dec2table[] = { 0, 0, 0, -1, 0, 1, 2, 3 }; + grub_size_t dec2 = dec2table[op - ref]; +#else + const int dec2 = 0; +#endif + *op++ = *ref++; + *op++ = *ref++; + *op++ = *ref++; + *op++ = *ref++; + ref -= dec[op - ref]; + A32(op) = A32(ref); + op += STEPSIZE - 4; + ref -= dec2; + } else { + LZ4_COPYSTEP(ref, op); + } + cpy = op + length - (STEPSIZE - 4); + if (cpy > oend - COPYLENGTH) { + if (cpy > oend) + /* + * Error: request to write outside of + * destination buffer. + */ + goto _output_error; + LZ4_SECURECOPY(ref, op, (oend - COPYLENGTH)); + while (op < cpy) + *op++ = *ref++; + op = cpy; + if (op == oend) + /* + * Check EOF (should never happen, since last + * 5 bytes are supposed to be literals). + */ + break; + continue; + } + LZ4_SECURECOPY(ref, op, cpy); + op = cpy; /* correction */ + } + + /* end of decoding */ + return (int)(((char *)op) - dest); + + /* write overflow error detected */ + _output_error: + return (int)(-(((char *)ip) - source)); +} diff --git a/grub-core/fs/zfs/zfs_lzjb.c b/grub-core/fs/zfs/zfs_lzjb.c new file mode 100644 index 0000000..62b5ea6 --- /dev/null +++ b/grub-core/fs/zfs/zfs_lzjb.c @@ -0,0 +1,93 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2009 Free Software Foundation, Inc. + * Copyright 2007 Sun Microsystems, 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/zfs/zfs.h> +#include <grub/zfs/zio.h> +#include <grub/zfs/dnode.h> +#include <grub/zfs/uberblock_impl.h> +#include <grub/zfs/vdev_impl.h> +#include <grub/zfs/zio_checksum.h> +#include <grub/zfs/zap_impl.h> +#include <grub/zfs/zap_leaf.h> +#include <grub/zfs/zfs_znode.h> +#include <grub/zfs/dmu.h> +#include <grub/zfs/dmu_objset.h> +#include <grub/zfs/dsl_dir.h> +#include <grub/zfs/dsl_dataset.h> + +#define MATCH_BITS 6 +#define MATCH_MIN 3 +#define OFFSET_MASK ((1 << (16 - MATCH_BITS)) - 1) + +/* + * Decompression Entry - lzjb + */ +#ifndef NBBY +#define NBBY 8 +#endif + +grub_err_t +lzjb_decompress (void *s_start, void *d_start, grub_size_t s_len, + grub_size_t d_len); + +grub_err_t +lzjb_decompress (void *s_start, void *d_start, grub_size_t s_len, + grub_size_t d_len) +{ + grub_uint8_t *src = s_start; + grub_uint8_t *dst = d_start; + grub_uint8_t *d_end = (grub_uint8_t *) d_start + d_len; + grub_uint8_t *s_end = (grub_uint8_t *) s_start + s_len; + grub_uint8_t *cpy, copymap = 0; + int copymask = 1 << (NBBY - 1); + + while (dst < d_end && src < s_end) + { + if ((copymask <<= 1) == (1 << NBBY)) + { + copymask = 1; + copymap = *src++; + } + if (src >= s_end) + return grub_error (GRUB_ERR_BAD_FS, "lzjb decompression failed"); + if (copymap & copymask) + { + int mlen = (src[0] >> (NBBY - MATCH_BITS)) + MATCH_MIN; + int offset = ((src[0] << NBBY) | src[1]) & OFFSET_MASK; + src += 2; + cpy = dst - offset; + if (src > s_end || cpy < (grub_uint8_t *) d_start) + return grub_error (GRUB_ERR_BAD_FS, "lzjb decompression failed"); + while (--mlen >= 0 && dst < d_end) + *dst++ = *cpy++; + } + else + *dst++ = *src++; + } + if (dst < d_end) + return grub_error (GRUB_ERR_BAD_FS, "lzjb decompression failed"); + return GRUB_ERR_NONE; +} diff --git a/grub-core/fs/zfs/zfs_sha256.c b/grub-core/fs/zfs/zfs_sha256.c new file mode 100644 index 0000000..a181f07 --- /dev/null +++ b/grub-core/fs/zfs/zfs_sha256.c @@ -0,0 +1,143 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2009 Free Software Foundation, Inc. + * Copyright 2007 Sun Microsystems, 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/zfs/zfs.h> +#include <grub/zfs/zio.h> +#include <grub/zfs/dnode.h> +#include <grub/zfs/uberblock_impl.h> +#include <grub/zfs/vdev_impl.h> +#include <grub/zfs/zio_checksum.h> +#include <grub/zfs/zap_impl.h> +#include <grub/zfs/zap_leaf.h> +#include <grub/zfs/zfs_znode.h> +#include <grub/zfs/dmu.h> +#include <grub/zfs/dmu_objset.h> +#include <grub/zfs/dsl_dir.h> +#include <grub/zfs/dsl_dataset.h> + +/* + * SHA-256 checksum, as specified in FIPS 180-2, available at: + * http://csrc.nist.gov/cryptval + * + * This is a very compact implementation of SHA-256. + * It is designed to be simple and portable, not to be fast. + */ + +/* + * The literal definitions according to FIPS180-2 would be: + * + * Ch(x, y, z) (((x) & (y)) ^ ((~(x)) & (z))) + * Maj(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) + * + * We use logical equivalents which require one less op. + */ +#define Ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define Maj(x, y, z) (((x) & (y)) ^ ((z) & ((x) ^ (y)))) +#define Rot32(x, s) (((x) >> s) | ((x) << (32 - s))) +#define SIGMA0(x) (Rot32(x, 2) ^ Rot32(x, 13) ^ Rot32(x, 22)) +#define SIGMA1(x) (Rot32(x, 6) ^ Rot32(x, 11) ^ Rot32(x, 25)) +#define sigma0(x) (Rot32(x, 7) ^ Rot32(x, 18) ^ ((x) >> 3)) +#define sigma1(x) (Rot32(x, 17) ^ Rot32(x, 19) ^ ((x) >> 10)) + +static const grub_uint32_t SHA256_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static void +SHA256Transform(grub_uint32_t *H, const grub_uint8_t *cp) +{ + grub_uint32_t a, b, c, d, e, f, g, h, t, T1, T2, W[64]; + + for (t = 0; t < 16; t++, cp += 4) + W[t] = (cp[0] << 24) | (cp[1] << 16) | (cp[2] << 8) | cp[3]; + + for (t = 16; t < 64; t++) + W[t] = sigma1(W[t - 2]) + W[t - 7] + + sigma0(W[t - 15]) + W[t - 16]; + + a = H[0]; b = H[1]; c = H[2]; d = H[3]; + e = H[4]; f = H[5]; g = H[6]; h = H[7]; + + for (t = 0; t < 64; t++) { + T1 = h + SIGMA1(e) + Ch(e, f, g) + SHA256_K[t] + W[t]; + T2 = SIGMA0(a) + Maj(a, b, c); + h = g; g = f; f = e; e = d + T1; + d = c; c = b; b = a; a = T1 + T2; + } + + H[0] += a; H[1] += b; H[2] += c; H[3] += d; + H[4] += e; H[5] += f; H[6] += g; H[7] += h; +} + +void +zio_checksum_SHA256(const void *buf, grub_uint64_t size, + grub_zfs_endian_t endian, zio_cksum_t *zcp) +{ + grub_uint32_t H[8] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; + grub_uint8_t pad[128]; + unsigned padsize = size & 63; + unsigned i; + + for (i = 0; i < size - padsize; i += 64) + SHA256Transform(H, (grub_uint8_t *)buf + i); + + for (i = 0; i < padsize; i++) + pad[i] = ((grub_uint8_t *)buf)[i]; + + for (pad[padsize++] = 0x80; (padsize & 63) != 56; padsize++) + pad[padsize] = 0; + + for (i = 0; i < 8; i++) + pad[padsize++] = (size << 3) >> (56 - 8 * i); + + for (i = 0; i < padsize && i <= 64; i += 64) + SHA256Transform(H, pad + i); + + zcp->zc_word[0] = grub_cpu_to_zfs64 ((grub_uint64_t)H[0] << 32 | H[1], + endian); + zcp->zc_word[1] = grub_cpu_to_zfs64 ((grub_uint64_t)H[2] << 32 | H[3], + endian); + zcp->zc_word[2] = grub_cpu_to_zfs64 ((grub_uint64_t)H[4] << 32 | H[5], + endian); + zcp->zc_word[3] = grub_cpu_to_zfs64 ((grub_uint64_t)H[6] << 32 | H[7], + endian); +} diff --git a/grub-core/fs/zfs/zfscrypt.c b/grub-core/fs/zfs/zfscrypt.c new file mode 100644 index 0000000..de3b015 --- /dev/null +++ b/grub-core/fs/zfs/zfscrypt.c @@ -0,0 +1,491 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2011 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/partition.h> +#include <grub/safemath.h> +#include <grub/dl.h> +#include <grub/types.h> +#include <grub/zfs/zfs.h> +#include <grub/zfs/zio.h> +#include <grub/zfs/dnode.h> +#include <grub/zfs/uberblock_impl.h> +#include <grub/zfs/vdev_impl.h> +#include <grub/zfs/zio_checksum.h> +#include <grub/zfs/zap_impl.h> +#include <grub/zfs/zap_leaf.h> +#include <grub/zfs/zfs_znode.h> +#include <grub/zfs/dmu.h> +#include <grub/zfs/dmu_objset.h> +#include <grub/zfs/sa_impl.h> +#include <grub/zfs/dsl_dir.h> +#include <grub/zfs/dsl_dataset.h> +#include <grub/crypto.h> +#include <grub/extcmd.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* + Mostly based on following article: + https://blogs.oracle.com/darren/entry/zfs_encryption_what_is_on + */ + +enum grub_zfs_algo + { + GRUB_ZFS_ALGO_CCM, + GRUB_ZFS_ALGO_GCM, + }; + +struct grub_zfs_key +{ + grub_uint64_t algo; + grub_uint8_t enc_nonce[13]; + grub_uint8_t unused[3]; + grub_uint8_t enc_key[48]; + grub_uint8_t unknown_purpose_nonce[13]; + grub_uint8_t unused2[3]; + grub_uint8_t unknown_purpose_key[48]; +}; + +struct grub_zfs_wrap_key +{ + struct grub_zfs_wrap_key *next; + grub_size_t keylen; + int is_passphrase; + grub_uint64_t key[0]; +}; + +static struct grub_zfs_wrap_key *zfs_wrap_keys; + +grub_err_t +grub_zfs_add_key (grub_uint8_t *key_in, + grub_size_t keylen, + int passphrase) +{ + struct grub_zfs_wrap_key *key; + grub_size_t sz; + + if (!passphrase && keylen > 32) + keylen = 32; + if (grub_add (sizeof (*key), keylen, &sz)) + return GRUB_ERR_OUT_OF_RANGE; + key = grub_malloc (sz); + if (!key) + return grub_errno; + key->is_passphrase = passphrase; + key->keylen = keylen; + grub_memcpy (key->key, key_in, keylen); + key->next = zfs_wrap_keys; + zfs_wrap_keys = key; + return GRUB_ERR_NONE; +} + +static gcry_err_code_t +grub_ccm_decrypt (grub_crypto_cipher_handle_t cipher, + grub_uint8_t *out, const grub_uint8_t *in, + grub_size_t psize, + void *mac_out, const void *nonce, + unsigned l, unsigned m) +{ + grub_uint8_t iv[16]; + grub_uint8_t mul[16]; + grub_uint32_t mac[4]; + unsigned i, j; + gcry_err_code_t err; + + grub_memcpy (iv + 1, nonce, 15 - l); + + iv[0] = (l - 1) | (((m-2) / 2) << 3); + for (j = 0; j < l; j++) + iv[15 - j] = psize >> (8 * j); + err = grub_crypto_ecb_encrypt (cipher, mac, iv, 16); + if (err) + return err; + + iv[0] = l - 1; + + for (i = 0; i < (psize + 15) / 16; i++) + { + grub_size_t csize; + csize = 16; + if (csize > psize - 16 * i) + csize = psize - 16 * i; + for (j = 0; j < l; j++) + iv[15 - j] = (i + 1) >> (8 * j); + err = grub_crypto_ecb_encrypt (cipher, mul, iv, 16); + if (err) + return err; + grub_crypto_xor (out + 16 * i, in + 16 * i, mul, csize); + grub_crypto_xor (mac, mac, out + 16 * i, csize); + err = grub_crypto_ecb_encrypt (cipher, mac, mac, 16); + if (err) + return err; + } + for (j = 0; j < l; j++) + iv[15 - j] = 0; + err = grub_crypto_ecb_encrypt (cipher, mul, iv, 16); + if (err) + return err; + if (mac_out) + grub_crypto_xor (mac_out, mac, mul, m); + return GPG_ERR_NO_ERROR; +} + +static void +grub_gcm_mul_x (grub_uint8_t *a) +{ + int i; + int c = 0, d = 0; + for (i = 0; i < 16; i++) + { + c = a[i] & 0x1; + a[i] = (a[i] >> 1) | (d << 7); + d = c; + } + if (d) + a[0] ^= 0xe1; +} + +static void +grub_gcm_mul (grub_uint8_t *a, const grub_uint8_t *b) +{ + grub_uint8_t res[16], bs[16]; + int i; + grub_memcpy (bs, b, 16); + grub_memset (res, 0, 16); + for (i = 0; i < 128; i++) + { + if ((a[i / 8] << (i % 8)) & 0x80) + grub_crypto_xor (res, res, bs, 16); + grub_gcm_mul_x (bs); + } + + grub_memcpy (a, res, 16); +} + +static gcry_err_code_t +grub_gcm_decrypt (grub_crypto_cipher_handle_t cipher, + grub_uint8_t *out, const grub_uint8_t *in, + grub_size_t psize, + void *mac_out, const void *nonce, + unsigned nonce_len, unsigned m) +{ + grub_uint8_t iv[16]; + grub_uint8_t mul[16]; + grub_uint8_t mac[16], h[16], mac_xor[16]; + unsigned i, j; + gcry_err_code_t err; + + grub_memset (mac, 0, sizeof (mac)); + + err = grub_crypto_ecb_encrypt (cipher, h, mac, 16); + if (err) + return err; + + if (nonce_len == 12) + { + grub_memcpy (iv, nonce, 12); + iv[12] = 0; + iv[13] = 0; + iv[14] = 0; + iv[15] = 1; + } + else + { + grub_memset (iv, 0, sizeof (iv)); + grub_memcpy (iv, nonce, nonce_len); + grub_gcm_mul (iv, h); + iv[15] ^= nonce_len * 8; + grub_gcm_mul (iv, h); + } + + err = grub_crypto_ecb_encrypt (cipher, mac_xor, iv, 16); + if (err) + return err; + + for (i = 0; i < (psize + 15) / 16; i++) + { + grub_size_t csize; + csize = 16; + if (csize > psize - 16 * i) + csize = psize - 16 * i; + for (j = 0; j < 4; j++) + { + iv[15 - j]++; + if (iv[15 - j] != 0) + break; + } + grub_crypto_xor (mac, mac, in + 16 * i, csize); + grub_gcm_mul (mac, h); + err = grub_crypto_ecb_encrypt (cipher, mul, iv, 16); + if (err) + return err; + grub_crypto_xor (out + 16 * i, in + 16 * i, mul, csize); + } + for (j = 0; j < 8; j++) + mac[15 - j] ^= ((((grub_uint64_t) psize) * 8) >> (8 * j)); + grub_gcm_mul (mac, h); + + if (mac_out) + grub_crypto_xor (mac_out, mac, mac_xor, m); + + return GPG_ERR_NO_ERROR; +} + + +static gcry_err_code_t +algo_decrypt (grub_crypto_cipher_handle_t cipher, grub_uint64_t algo, + grub_uint8_t *out, const grub_uint8_t *in, + grub_size_t psize, + void *mac_out, const void *nonce, + unsigned l, unsigned m) +{ + switch (algo) + { + case 0: + return grub_ccm_decrypt (cipher, out, in, psize, + mac_out, nonce, l, m); + case 1: + return grub_gcm_decrypt (cipher, out, in, psize, + mac_out, nonce, + 15 - l, m); + default: + return GPG_ERR_CIPHER_ALGO; + } +} + +static grub_err_t +grub_zfs_decrypt_real (grub_crypto_cipher_handle_t cipher, + grub_uint64_t algo, + void *nonce, + char *buf, grub_size_t size, + const grub_uint32_t *expected_mac, + grub_zfs_endian_t endian) +{ + grub_uint32_t mac[4]; + unsigned i; + grub_uint32_t sw[4]; + gcry_err_code_t err; + + grub_memcpy (sw, nonce, 16); + if (endian != GRUB_ZFS_BIG_ENDIAN) + for (i = 0; i < 4; i++) + sw[i] = grub_swap_bytes32 (sw[i]); + + if (!cipher) + return grub_error (GRUB_ERR_ACCESS_DENIED, + N_("no decryption key available")); + err = algo_decrypt (cipher, algo, + (grub_uint8_t *) buf, + (grub_uint8_t *) buf, + size, mac, + sw + 1, 3, 12); + if (err) + return grub_crypto_gcry_error (err); + + for (i = 0; i < 3; i++) + if (grub_zfs_to_cpu32 (expected_mac[i], endian) + != grub_be_to_cpu32 (mac[i])) + return grub_error (GRUB_ERR_BAD_FS, N_("MAC verification failed")); + return GRUB_ERR_NONE; +} + +static grub_crypto_cipher_handle_t +grub_zfs_load_key_real (const struct grub_zfs_key *key, + grub_size_t keysize, + grub_uint64_t salt, + grub_uint64_t algo) +{ + unsigned keylen; + struct grub_zfs_wrap_key *wrap_key; + grub_crypto_cipher_handle_t ret = NULL; + + if (keysize != sizeof (*key)) + { + grub_dprintf ("zfs", "Unexpected key size %" PRIuGRUB_SIZE "\n", keysize); + return 0; + } + + if (grub_memcmp (key->enc_key + 32, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) + == 0) + keylen = 16; + else if (grub_memcmp (key->enc_key + 40, "\0\0\0\0\0\0\0\0", 8) == 0) + keylen = 24; + else + keylen = 32; + + for (wrap_key = zfs_wrap_keys; wrap_key; wrap_key = wrap_key->next) + { + grub_crypto_cipher_handle_t cipher; + grub_uint8_t decrypted[32], mac[32], wrap_key_real[32]; + gcry_err_code_t err; + cipher = grub_crypto_cipher_open (GRUB_CIPHER_AES); + if (!cipher) + { + grub_errno = GRUB_ERR_NONE; + return 0; + } + grub_memset (wrap_key_real, 0, sizeof (wrap_key_real)); + err = 0; + if (!wrap_key->is_passphrase) + grub_memcpy(wrap_key_real, wrap_key->key, + wrap_key->keylen < keylen ? wrap_key->keylen : keylen); + else + err = grub_crypto_pbkdf2 (GRUB_MD_SHA1, + (const grub_uint8_t *) wrap_key->key, + wrap_key->keylen, + (const grub_uint8_t *) &salt, sizeof (salt), + 1000, wrap_key_real, keylen); + if (err) + { + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + + err = grub_crypto_cipher_set_key (cipher, wrap_key_real, + keylen); + if (err) + { + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + + err = algo_decrypt (cipher, algo, decrypted, key->unknown_purpose_key, 32, + mac, key->unknown_purpose_nonce, 2, 16); + if (err || (grub_crypto_memcmp (mac, key->unknown_purpose_key + 32, 16) + != 0)) + { + grub_dprintf ("zfs", "key loading failed\n"); + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + + err = algo_decrypt (cipher, algo, decrypted, key->enc_key, keylen, mac, + key->enc_nonce, 2, 16); + if (err || grub_crypto_memcmp (mac, key->enc_key + keylen, 16) != 0) + { + grub_dprintf ("zfs", "key loading failed\n"); + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + ret = grub_crypto_cipher_open (GRUB_CIPHER_AES); + if (!ret) + { + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + err = grub_crypto_cipher_set_key (ret, decrypted, keylen); + if (err) + { + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (ret); + grub_crypto_cipher_close (cipher); + continue; + } + grub_crypto_cipher_close (cipher); + return ret; + } + return NULL; +} + +static const struct grub_arg_option options[] = + { + {"raw", 'r', 0, N_("Assume input is raw."), 0, 0}, + {"hex", 'h', 0, N_("Assume input is hex."), 0, 0}, + {"passphrase", 'p', 0, N_("Assume input is passphrase."), 0, 0}, + {0, 0, 0, 0, 0, 0} + }; + +static grub_err_t +grub_cmd_zfs_key (grub_extcmd_context_t ctxt, int argc, char **args) +{ + grub_uint8_t buf[1024]; + grub_ssize_t real_size; + + if (argc > 0) + { + grub_file_t file; + file = grub_file_open (args[0], GRUB_FILE_TYPE_ZFS_ENCRYPTION_KEY); + if (!file) + return grub_errno; + real_size = grub_file_read (file, buf, 1024); + if (real_size < 0) + return grub_errno; + } + else + { + grub_xputs (_("Enter ZFS password: ")); + if (!grub_password_get ((char *) buf, 1023)) + return grub_errno; + real_size = grub_strlen ((char *) buf); + } + + if (ctxt->state[1].set) + { + int i; + grub_err_t err; + for (i = 0; i < real_size / 2; i++) + { + char c1 = grub_tolower (buf[2 * i]) - '0'; + char c2 = grub_tolower (buf[2 * i + 1]) - '0'; + if (c1 > 9) + c1 += '0' - 'a' + 10; + if (c2 > 9) + c2 += '0' - 'a' + 10; + buf[i] = (c1 << 4) | c2; + } + err = grub_zfs_add_key (buf, real_size / 2, 0); + if (err) + return err; + return GRUB_ERR_NONE; + } + + return grub_zfs_add_key (buf, real_size, + ctxt->state[2].set + || (argc == 0 && !ctxt->state[0].set + && !ctxt->state[1].set)); +} + +static grub_extcmd_t cmd_key; + +GRUB_MOD_INIT(zfscrypt) +{ + grub_zfs_decrypt = grub_zfs_decrypt_real; + grub_zfs_load_key = grub_zfs_load_key_real; + cmd_key = grub_register_extcmd ("zfskey", grub_cmd_zfs_key, 0, + N_("[-h|-p|-r] [FILE]"), + N_("Import ZFS wrapping key stored in FILE."), + options); +} + +GRUB_MOD_FINI(zfscrypt) +{ + grub_zfs_decrypt = 0; + grub_zfs_load_key = 0; + grub_unregister_extcmd (cmd_key); +} diff --git a/grub-core/fs/zfs/zfsinfo.c b/grub-core/fs/zfs/zfsinfo.c new file mode 100644 index 0000000..bf29180 --- /dev/null +++ b/grub-core/fs/zfs/zfsinfo.c @@ -0,0 +1,441 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2009 Free Software Foundation, Inc. + * Copyright 2008 Sun Microsystems, 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/zfs/zfs.h> +#include <grub/device.h> +#include <grub/file.h> +#include <grub/command.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/dl.h> +#include <grub/env.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static inline void +print_tabs (int n) +{ + int i; + + for (i = 0; i < n; i++) + grub_printf (" "); +} + +static grub_err_t +print_state (char *nvlist, int tab) +{ + grub_uint64_t ival; + int isok = 1; + + print_tabs (tab); + + if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_REMOVED, &ival)) + { + grub_puts_ (N_("Virtual device is removed")); + isok = 0; + } + + if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival)) + { + grub_puts_ (N_("Virtual device is faulted")); + isok = 0; + } + + if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_OFFLINE, &ival)) + { + grub_puts_ (N_("Virtual device is offline")); + isok = 0; + } + + if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival)) + /* TRANSLATORS: degraded doesn't mean broken but that some of + component are missing but virtual device as whole is still usable. */ + grub_puts_ (N_("Virtual device is degraded")); + + if (isok) + grub_puts_ (N_("Virtual device is online")); + grub_xputs ("\n"); + + return GRUB_ERR_NONE; +} + +static grub_err_t +print_vdev_info (char *nvlist, int tab) +{ + char *type = 0; + + type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE); + + if (!type) + { + print_tabs (tab); + grub_puts_ (N_("Incorrect virtual device: no type available")); + return grub_errno; + } + + if (grub_strcmp (type, VDEV_TYPE_DISK) == 0) + { + char *bootpath = 0; + char *path = 0; + char *devid = 0; + + print_tabs (tab); + /* TRANSLATORS: The virtual devices form a tree (in graph-theoretical + sense). The nodes like mirror or raidz have children: member devices. + The "real" devices which actually store data are called "leafs" + (again borrowed from graph theory) and can be either disks + (or partitions) or files. */ + grub_puts_ (N_("Leaf virtual device (file or disk)")); + + print_state (nvlist, tab); + + bootpath = + grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_PHYS_PATH); + print_tabs (tab); + if (!bootpath) + grub_puts_ (N_("Bootpath: unavailable\n")); + else + grub_printf_ (N_("Bootpath: %s\n"), bootpath); + + path = grub_zfs_nvlist_lookup_string (nvlist, "path"); + print_tabs (tab); + if (!path) + grub_puts_ (N_("Path: unavailable")); + else + grub_printf_ (N_("Path: %s\n"), path); + + devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID); + print_tabs (tab); + if (!devid) + grub_puts_ (N_("Devid: unavailable")); + else + grub_printf_ (N_("Devid: %s\n"), devid); + grub_free (bootpath); + grub_free (devid); + grub_free (path); + grub_free (type); + return GRUB_ERR_NONE; + } + char is_mirror=(grub_strcmp(type,VDEV_TYPE_MIRROR) == 0); + char is_raidz=(grub_strcmp(type,VDEV_TYPE_RAIDZ) == 0); + grub_free (type); + + if (is_mirror || is_raidz) + { + int nelm, i; + + nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm + (nvlist, ZPOOL_CONFIG_CHILDREN); + + if(is_mirror){ + grub_puts_ (N_("This VDEV is a mirror")); + } + else if(is_raidz){ + grub_uint64_t parity; + grub_zfs_nvlist_lookup_uint64(nvlist,"nparity",&parity); + grub_printf_ (N_("This VDEV is a RAIDZ%llu\n"),(unsigned long long)parity); + } + print_tabs (tab); + if (nelm <= 0) + { + grub_puts_ (N_("Incorrect VDEV")); + return GRUB_ERR_NONE; + } + grub_printf_ (N_("VDEV with %d children\n"), nelm); + print_state (nvlist, tab); + for (i = 0; i < nelm; i++) + { + char *child; + + child = grub_zfs_nvlist_lookup_nvlist_array + (nvlist, ZPOOL_CONFIG_CHILDREN, i); + + print_tabs (tab); + if (!child) + { + /* TRANSLATORS: it's the element carying the number %d, not + total element number. And the number itself is fine, + only the element isn't. + */ + grub_printf_ (N_("VDEV element number %d isn't correct\n"), i); + continue; + } + + /* TRANSLATORS: it's the element carying the number %d, not + total element number. This is used in enumeration + "Element number 1", "Element number 2", ... */ + grub_printf_ (N_("VDEV element number %d:\n"), i); + print_vdev_info (child, tab + 1); + + grub_free (child); + } + return GRUB_ERR_NONE; + } + + print_tabs (tab); + grub_printf_ (N_("Unknown virtual device type: %s\n"), type); + + return GRUB_ERR_NONE; +} + +static grub_err_t +get_bootpath (char *nvlist, char **bootpath, char **devid) +{ + char *type = 0; + + type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE); + + if (!type) + return grub_errno; + + if (grub_strcmp (type, VDEV_TYPE_DISK) == 0) + { + *bootpath = grub_zfs_nvlist_lookup_string (nvlist, + ZPOOL_CONFIG_PHYS_PATH); + *devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID); + if (!*bootpath || !*devid) + { + grub_free (*bootpath); + grub_free (*devid); + *bootpath = 0; + *devid = 0; + } + return GRUB_ERR_NONE; + } + + if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0) + { + int nelm, i; + + nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm + (nvlist, ZPOOL_CONFIG_CHILDREN); + + for (i = 0; i < nelm; i++) + { + char *child; + + child = grub_zfs_nvlist_lookup_nvlist_array (nvlist, + ZPOOL_CONFIG_CHILDREN, + i); + + get_bootpath (child, bootpath, devid); + + grub_free (child); + + if (*bootpath && *devid) + return GRUB_ERR_NONE; + } + } + + return GRUB_ERR_NONE; +} + +static const char *poolstates[] = { + /* TRANSLATORS: Here we speak about ZFS pools it's semi-marketing, + semi-technical term by Sun/Oracle and should be translated in sync with + other ZFS-related software and documentation. */ + [POOL_STATE_ACTIVE] = N_("Pool state: active"), + [POOL_STATE_EXPORTED] = N_("Pool state: exported"), + [POOL_STATE_DESTROYED] = N_("Pool state: destroyed"), + [POOL_STATE_SPARE] = N_("Pool state: reserved for hot spare"), + [POOL_STATE_L2CACHE] = N_("Pool state: level 2 ARC device"), + [POOL_STATE_UNINITIALIZED] = N_("Pool state: uninitialized"), + [POOL_STATE_UNAVAIL] = N_("Pool state: unavailable"), + [POOL_STATE_POTENTIALLY_ACTIVE] = N_("Pool state: potentially active") +}; + +static grub_err_t +grub_cmd_zfsinfo (grub_command_t cmd __attribute__ ((unused)), int argc, + char **args) +{ + grub_device_t dev; + char *devname; + grub_err_t err; + char *nvlist = 0; + char *nv = 0; + char *poolname; + grub_uint64_t guid; + grub_uint64_t pool_state; + int found; + + if (argc < 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); + + if (args[0][0] == '(' && args[0][grub_strlen (args[0]) - 1] == ')') + { + devname = grub_strdup (args[0] + 1); + if (devname) + devname[grub_strlen (devname) - 1] = 0; + } + else + devname = grub_strdup (args[0]); + if (!devname) + return grub_errno; + + dev = grub_device_open (devname); + grub_free (devname); + if (!dev) + return grub_errno; + + err = grub_zfs_fetch_nvlist (dev, &nvlist); + + grub_device_close (dev); + + if (err) + return err; + + poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME); + if (!poolname) + grub_puts_ (N_("Pool name: unavailable")); + else + grub_printf_ (N_("Pool name: %s\n"), poolname); + + found = + grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, &guid); + if (!found) + grub_puts_ (N_("Pool GUID: unavailable")); + else + grub_printf_ (N_("Pool GUID: %016llx\n"), (long long unsigned) guid); + + found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_STATE, + &pool_state); + if (!found) + grub_puts_ (N_("Unable to retrieve pool state")); + else if (pool_state >= ARRAY_SIZE (poolstates)) + grub_puts_ (N_("Unrecognized pool state")); + else + grub_puts_ (poolstates[pool_state]); + + nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE); + + if (!nv) + /* TRANSLATORS: There are undetermined number of virtual devices + in a device tree, not just one. + */ + grub_puts_ (N_("No virtual device tree available")); + else + print_vdev_info (nv, 1); + + grub_free (nv); + grub_free (nvlist); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_zfs_bootfs (grub_command_t cmd __attribute__ ((unused)), int argc, + char **args) +{ + grub_device_t dev; + char *devname; + grub_err_t err; + char *nvlist = 0; + char *nv = 0; + char *bootpath = 0, *devid = 0; + char *fsname; + char *bootfs; + char *poolname; + grub_uint64_t mdnobj; + + if (argc < 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); + + devname = grub_file_get_device_name (args[0]); + if (devname == NULL) + return GRUB_ERR_OUT_OF_MEMORY; + + dev = grub_device_open (devname); + grub_free (devname); + if (!dev) + return grub_errno; + + err = grub_zfs_fetch_nvlist (dev, &nvlist); + + fsname = grub_strchr (args[0], ')'); + if (fsname) + fsname++; + else + fsname = args[0]; + + if (!err) + err = grub_zfs_getmdnobj (dev, fsname, &mdnobj); + + grub_device_close (dev); + + if (err) + return err; + + poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME); + if (!poolname) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_FS, "No poolname found"); + return grub_errno; + } + + nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE); + + if (nv) + get_bootpath (nv, &bootpath, &devid); + + grub_free (nv); + grub_free (nvlist); + + bootfs = grub_xasprintf ("zfs-bootfs=%s/%llu%s%s%s%s%s%s", + poolname, (unsigned long long) mdnobj, + bootpath ? ",bootpath=\"" : "", + bootpath ? : "", + bootpath ? "\"" : "", + devid ? ",diskdevid=\"" : "", + devid ? : "", + devid ? "\"" : ""); + if (!bootfs) + return grub_errno; + if (argc >= 2) + grub_env_set (args[1], bootfs); + else + grub_printf ("%s\n", bootfs); + + grub_free (bootfs); + grub_free (poolname); + grub_free (bootpath); + grub_free (devid); + + return GRUB_ERR_NONE; +} + + +static grub_command_t cmd_info, cmd_bootfs; + +GRUB_MOD_INIT (zfsinfo) +{ + cmd_info = grub_register_command ("zfsinfo", grub_cmd_zfsinfo, + N_("DEVICE"), + N_("Print ZFS info about DEVICE.")); + cmd_bootfs = grub_register_command ("zfs-bootfs", grub_cmd_zfs_bootfs, + N_("FILESYSTEM [VARIABLE]"), + N_("Print ZFS-BOOTFSOBJ or store it into VARIABLE")); +} + +GRUB_MOD_FINI (zfsinfo) +{ + grub_unregister_command (cmd_info); + grub_unregister_command (cmd_bootfs); +} |