summaryrefslogtreecommitdiffstats
path: root/grub-core/fs
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/fs')
-rw-r--r--grub-core/fs/affs.c709
-rw-r--r--grub-core/fs/afs.c3
-rw-r--r--grub-core/fs/archelp.c301
-rw-r--r--grub-core/fs/bfs.c1117
-rw-r--r--grub-core/fs/btrfs.c2216
-rw-r--r--grub-core/fs/cbfs.c402
-rw-r--r--grub-core/fs/cpio.c61
-rw-r--r--grub-core/fs/cpio_be.c61
-rw-r--r--grub-core/fs/cpio_common.c253
-rw-r--r--grub-core/fs/exfat.c2
-rw-r--r--grub-core/fs/ext2.c1107
-rw-r--r--grub-core/fs/f2fs.c1328
-rw-r--r--grub-core/fs/fat.c1329
-rw-r--r--grub-core/fs/fshelp.c441
-rw-r--r--grub-core/fs/hfs.c1446
-rw-r--r--grub-core/fs/hfsplus.c1160
-rw-r--r--grub-core/fs/hfspluscomp.c317
-rw-r--r--grub-core/fs/iso9660.c1162
-rw-r--r--grub-core/fs/jfs.c973
-rw-r--r--grub-core/fs/minix.c758
-rw-r--r--grub-core/fs/minix2.c2
-rw-r--r--grub-core/fs/minix2_be.c3
-rw-r--r--grub-core/fs/minix3.c2
-rw-r--r--grub-core/fs/minix3_be.c3
-rw-r--r--grub-core/fs/minix_be.c2
-rw-r--r--grub-core/fs/newc.c73
-rw-r--r--grub-core/fs/nilfs2.c1241
-rw-r--r--grub-core/fs/ntfs.c1237
-rw-r--r--grub-core/fs/ntfscomp.c443
-rw-r--r--grub-core/fs/odc.c61
-rw-r--r--grub-core/fs/proc.c203
-rw-r--r--grub-core/fs/reiserfs.c1427
-rw-r--r--grub-core/fs/romfs.c484
-rw-r--r--grub-core/fs/sfs.c789
-rw-r--r--grub-core/fs/squash4.c1042
-rw-r--r--grub-core/fs/tar.c345
-rw-r--r--grub-core/fs/udf.c1392
-rw-r--r--grub-core/fs/ufs.c918
-rw-r--r--grub-core/fs/ufs2.c3
-rw-r--r--grub-core/fs/ufs_be.c2
-rw-r--r--grub-core/fs/xfs.c1211
-rw-r--r--grub-core/fs/zfs/zfs.c4406
-rw-r--r--grub-core/fs/zfs/zfs_fletcher.c84
-rw-r--r--grub-core/fs/zfs/zfs_lz4.c285
-rw-r--r--grub-core/fs/zfs/zfs_lzjb.c93
-rw-r--r--grub-core/fs/zfs/zfs_sha256.c143
-rw-r--r--grub-core/fs/zfs/zfscrypt.c491
-rw-r--r--grub-core/fs/zfs/zfsinfo.c441
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 (&current_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);
+}