aboutsummaryrefslogtreecommitdiffstats
path: root/grub-core/fs
diff options
context:
space:
mode:
authorJames <james.mckenzie@citrix.com>2012-11-16 10:41:01 +0000
committerJames <james.mckenzie@citrix.com>2012-11-16 10:41:01 +0000
commit041d1ea37802bf7178a31a53f96c26efa6b8fb7b (patch)
treec193e84ad1237f25a79d0f6a267722e44c73f56a /grub-core/fs
downloadgrub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.tar.gz
grub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.tar.bz2
grub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.zip
fish
Diffstat (limited to 'grub-core/fs')
-rw-r--r--grub-core/fs/affs.c555
-rw-r--r--grub-core/fs/afs.c720
-rw-r--r--grub-core/fs/afs_be.c2
-rw-r--r--grub-core/fs/befs.c3
-rw-r--r--grub-core/fs/befs_be.c4
-rw-r--r--grub-core/fs/btrfs.c1491
-rw-r--r--grub-core/fs/cpio.c381
-rw-r--r--grub-core/fs/ext2.c991
-rw-r--r--grub-core/fs/fat.c878
-rw-r--r--grub-core/fs/fshelp.c317
-rw-r--r--grub-core/fs/hfs.c1124
-rw-r--r--grub-core/fs/hfsplus.c1050
-rw-r--r--grub-core/fs/i386/pc/pxe.c652
-rw-r--r--grub-core/fs/iso9660.c908
-rw-r--r--grub-core/fs/jfs.c908
-rw-r--r--grub-core/fs/minix.c590
-rw-r--r--grub-core/fs/minix2.c2
-rw-r--r--grub-core/fs/nilfs2.c1186
-rw-r--r--grub-core/fs/ntfs.c1117
-rw-r--r--grub-core/fs/ntfscomp.c376
-rw-r--r--grub-core/fs/reiserfs.c1384
-rw-r--r--grub-core/fs/sfs.c599
-rw-r--r--grub-core/fs/tar.c2
-rw-r--r--grub-core/fs/udf.c1061
-rw-r--r--grub-core/fs/ufs.c812
-rw-r--r--grub-core/fs/ufs2.c3
-rw-r--r--grub-core/fs/xfs.c837
-rw-r--r--grub-core/fs/zfs/zfs.c2550
-rw-r--r--grub-core/fs/zfs/zfs_fletcher.c84
-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/zfsinfo.c409
32 files changed, 21232 insertions, 0 deletions
diff --git a/grub-core/fs/affs.c b/grub-core/fs/affs.c
new file mode 100644
index 0000000..1c4f80e
--- /dev/null
+++ b/grub-core/fs/affs.c
@@ -0,0 +1,555 @@
+/* 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>
+
+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;
+} __attribute__ ((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_uint8_t type[4];
+ grub_uint8_t unused1[8];
+ grub_uint32_t htsize;
+ grub_uint32_t unused2;
+ grub_uint32_t checksum;
+ grub_uint32_t hashtable[1];
+} __attribute__ ((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[104];
+ grub_uint8_t namelen;
+ grub_uint8_t name[30];
+ grub_uint8_t unused3[33];
+ grub_uint32_t next;
+ grub_uint32_t parent;
+ grub_uint32_t extension;
+ grub_int32_t type;
+} __attribute__ ((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
+
+#define GRUB_AFFS_SYMLINK_SIZE(blocksize) ((blocksize) - 225)
+
+#define GRUB_AFFS_FILETYPE_DIR -3
+#define GRUB_AFFS_FILETYPE_REG 2
+#define GRUB_AFFS_FILETYPE_SYMLINK 3
+
+
+struct grub_fshelp_node
+{
+ struct grub_affs_data *data;
+ int block;
+ int size;
+ int parent;
+};
+
+/* Information about a "mounted" affs filesystem. */
+struct grub_affs_data
+{
+ struct grub_affs_bblock bblock;
+ struct grub_fshelp_node diropen;
+ grub_disk_t disk;
+
+ /* Blocksize in sectors. */
+ int blocksize;
+
+ /* The number of entries in the hashtable. */
+ 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)
+{
+ int links;
+ grub_uint32_t pos;
+ int block = node->block;
+ struct grub_affs_file file;
+ struct grub_affs_data *data = node->data;
+ grub_uint32_t mod;
+
+ /* Find the block that points to the fileblock we are looking up by
+ following the chain until the right table is reached. */
+ for (links = grub_divmod64 (fileblock, data->htsize, &mod); links; links--)
+ {
+ grub_disk_read (data->disk, block + data->blocksize - 1,
+ data->blocksize * (GRUB_DISK_SECTOR_SIZE
+ - GRUB_AFFS_FILE_LOCATION),
+ sizeof (file), &file);
+ if (grub_errno)
+ return 0;
+
+ block = grub_be_to_cpu32 (file.extension);
+ }
+
+ /* Translate the fileblock to the block within the right table. */
+ fileblock = mod;
+ grub_disk_read (data->disk, block,
+ GRUB_AFFS_BLOCKPTR_OFFSET
+ + (data->htsize - fileblock - 1) * sizeof (pos),
+ sizeof (pos), &pos);
+ if (grub_errno)
+ return 0;
+
+ return grub_be_to_cpu32 (pos);
+}
+
+
+/* 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_affs_read_file (grub_fshelp_node_t node,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ return grub_fshelp_read_file (node->data->disk, node, read_hook,
+ pos, len, buf, grub_affs_read_block,
+ node->size, 0);
+}
+
+
+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;
+
+ int checksum = 0;
+ int checksumr = 0;
+ int blocksize = 0;
+
+ data = grub_malloc (sizeof (struct grub_affs_data));
+ if (!data)
+ return 0;
+
+ /* Read the bootblock. */
+ grub_disk_read (disk, 0, 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))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "not an AFFS filesystem");
+ goto fail;
+ }
+
+ /* Test if the filesystem is a OFS filesystem. */
+ if (! (data->bblock.flags & GRUB_AFFS_FLAG_FFS))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "OFS not yet supported");
+ goto fail;
+ }
+
+ /* Read the bootblock. */
+ grub_disk_read (disk, 0, 0, sizeof (struct grub_affs_bblock),
+ &data->bblock);
+ if (grub_errno)
+ goto fail;
+
+ /* 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. */
+ rootblock = grub_malloc (GRUB_DISK_SECTOR_SIZE * 16);
+ if (!rootblock)
+ goto fail;
+
+ rblock = (struct grub_affs_rblock *) rootblock;
+
+ /* Read the rootblock. */
+ grub_disk_read (disk, grub_be_to_cpu32 (data->bblock.rootblock), 0,
+ GRUB_DISK_SECTOR_SIZE * 16, rootblock);
+ if (grub_errno)
+ goto fail;
+
+ /* The filesystem blocksize is not stored anywhere in the filesystem
+ itself. One way to determine it is reading blocks for the
+ rootblock until the checksum is correct. */
+ checksumr = grub_be_to_cpu32 (rblock->checksum);
+ rblock->checksum = 0;
+ for (blocksize = 0; blocksize < 8; blocksize++)
+ {
+ grub_uint32_t *currblock = rootblock + GRUB_DISK_SECTOR_SIZE * blocksize;
+ unsigned int i;
+
+ for (i = 0; i < GRUB_DISK_SECTOR_SIZE / sizeof (*currblock); i++)
+ checksum += grub_be_to_cpu32 (currblock[i]);
+
+ if (checksumr == -checksum)
+ break;
+ }
+ if (-checksum != checksumr)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "AFFS blocksize couldn't be determined");
+ goto fail;
+ }
+ blocksize++;
+
+ data->blocksize = 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);
+
+ grub_free (rootblock);
+
+ return data;
+
+ fail:
+ if (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;
+ char *symlink;
+
+ symlink = grub_malloc (GRUB_AFFS_SYMLINK_SIZE (data->blocksize));
+ if (!symlink)
+ return 0;
+
+ grub_disk_read (data->disk, node->block, GRUB_AFFS_SYMLINK_OFFSET,
+ GRUB_AFFS_SYMLINK_SIZE (data->blocksize), symlink);
+ if (grub_errno)
+ {
+ grub_free (symlink);
+ return 0;
+ }
+ grub_dprintf ("affs", "Symlink: `%s'\n", symlink);
+ return symlink;
+}
+
+
+static int
+grub_affs_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ int i;
+ struct grub_affs_file file;
+ struct grub_fshelp_node *node = 0;
+ struct grub_affs_data *data = dir->data;
+ grub_uint32_t *hashtable;
+
+ auto int NESTED_FUNC_ATTR grub_affs_create_node (const char *name, int block,
+ int size, int type);
+
+ int NESTED_FUNC_ATTR grub_affs_create_node (const char *name, int block,
+ int size, int type)
+ {
+ node = grub_malloc (sizeof (*node));
+ if (!node)
+ {
+ grub_free (hashtable);
+ return 1;
+ }
+
+ node->data = data;
+ node->size = size;
+ node->block = block;
+ node->parent = grub_be_to_cpu32 (file.parent);
+
+ if (hook (name, type, node))
+ {
+ grub_free (hashtable);
+ return 1;
+ }
+ return 0;
+ }
+
+ hashtable = grub_malloc (data->htsize * sizeof (*hashtable));
+ if (!hashtable)
+ return 1;
+
+ grub_disk_read (data->disk, dir->block, GRUB_AFFS_HASHTABLE_OFFSET,
+ data->htsize * sizeof (*hashtable), (char *) hashtable);
+ if (grub_errno)
+ goto fail;
+
+ /* Create the directory entries for `.' and `..'. */
+ if (grub_affs_create_node (".", dir->block, dir->size, GRUB_FSHELP_DIR))
+ return 1;
+ if (grub_affs_create_node ("..", dir->parent ? dir->parent : dir->block,
+ dir->size, GRUB_FSHELP_DIR))
+ return 1;
+
+ for (i = 0; i < data->htsize; i++)
+ {
+ enum grub_fshelp_filetype type;
+ grub_uint64_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, next + data->blocksize - 1,
+ data->blocksize * GRUB_DISK_SECTOR_SIZE
+ - GRUB_AFFS_FILE_LOCATION,
+ sizeof (file), (char *) &file);
+ if (grub_errno)
+ goto fail;
+
+ file.name[file.namelen] = '\0';
+
+ if ((int) grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_DIR)
+ type = GRUB_FSHELP_REG;
+ else if (grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_REG)
+ type = GRUB_FSHELP_DIR;
+ else if (grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_SYMLINK)
+ type = GRUB_FSHELP_SYMLINK;
+ else
+ type = GRUB_FSHELP_UNKNOWN;
+
+ if (grub_affs_create_node ((char *) (file.name), next,
+ grub_be_to_cpu32 (file.size), type))
+ return 1;
+
+ next = grub_be_to_cpu32 (file.next);
+ }
+ }
+
+ grub_free (hashtable);
+ return 0;
+
+ fail:
+ grub_free (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 = 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);
+ grub_free (data);
+
+ grub_dl_unref (my_mod);
+
+ return grub_errno;
+}
+
+
+static grub_err_t
+grub_affs_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_affs_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ struct grub_affs_data *data =
+ (struct grub_affs_data *) file->data;
+
+ int size = grub_affs_read_file (&data->diropen, file->read_hook,
+ file->offset, len, buf);
+
+ return size;
+}
+
+
+static grub_err_t
+grub_affs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_affs_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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 hook (filename, &info);
+ }
+
+ 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, iterate);
+
+ 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)
+ {
+ /* The rootblock maps quite well on a file header block, it's
+ something we can use here. */
+ grub_disk_read (data->disk, grub_be_to_cpu32 (data->bblock.rootblock),
+ data->blocksize * (GRUB_DISK_SECTOR_SIZE
+ - GRUB_AFFS_FILE_LOCATION),
+ sizeof (file), &file);
+ if (grub_errno)
+ return 0;
+
+ *label = grub_strndup ((char *) (file.name), file.namelen);
+ }
+ else
+ *label = 0;
+
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+
+static struct grub_fs grub_affs_fs =
+ {
+ .name = "affs",
+ .dir = grub_affs_dir,
+ .open = grub_affs_open,
+ .read = grub_affs_read,
+ .close = grub_affs_close,
+ .label = grub_affs_label,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 0,
+#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..35ef499
--- /dev/null
+++ b/grub-core/fs/afs.c
@@ -0,0 +1,720 @@
+/* afs.c - The native AtheOS file-system. */
+/*
+ * 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>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#ifdef MODE_BIGENDIAN
+#define GRUB_AFS_FSNAME_SUFFIX "_be"
+#else
+#define GRUB_AFS_FSNAME_SUFFIX ""
+#endif
+
+#ifdef MODE_BFS
+#define GRUB_AFS_FSNAME "befs" GRUB_AFS_FSNAME_SUFFIX
+#else
+#define GRUB_AFS_FSNAME "afs" GRUB_AFS_FSNAME_SUFFIX
+#endif
+
+#define GRUB_AFS_DIRECT_BLOCK_COUNT 12
+#define GRUB_AFS_BLOCKS_PER_DI_RUN 4
+
+#ifdef MODE_BFS
+#define GRUB_AFS_SBLOCK_SECTOR 1
+#define GRUB_AFS_SBLOCK_MAGIC1 0x42465331 /* BFS1. */
+#else
+#define GRUB_AFS_SBLOCK_SECTOR 2
+#define GRUB_AFS_SBLOCK_MAGIC1 0x41465331 /* AFS1. */
+#endif
+
+#define GRUB_AFS_SBLOCK_MAGIC2 0xdd121031
+#define GRUB_AFS_SBLOCK_MAGIC3 0x15b6830e
+
+#define GRUB_AFS_INODE_MAGIC 0x64358428
+
+#ifdef MODE_BFS
+#define GRUB_AFS_BTREE_MAGIC 0x69f6c2e8
+#else
+#define GRUB_AFS_BTREE_MAGIC 0x65768995
+#endif
+
+#define GRUB_AFS_BNODE_SIZE 1024
+
+#define GRUB_AFS_S_IFMT 00170000
+#define GRUB_AFS_S_IFLNK 0120000
+
+#define GRUB_AFS_S_IFREG 0100000
+#define GRUB_AFS_S_IFDIR 0040000
+#define GRUB_AFS_S_IFIFO 0010000
+
+#define GRUB_AFS_NULL_VAL ((grub_afs_bvalue_t)-1)
+
+#ifdef MODE_BIGENDIAN
+#define grub_afs_to_cpu16(x) grub_be_to_cpu16 (x)
+#define grub_afs_to_cpu32(x) grub_be_to_cpu32 (x)
+#define grub_afs_to_cpu64(x) grub_be_to_cpu64 (x)
+#else
+#define grub_afs_to_cpu16(x) grub_le_to_cpu16 (x)
+#define grub_afs_to_cpu32(x) grub_le_to_cpu32 (x)
+#define grub_afs_to_cpu64(x) grub_le_to_cpu64 (x)
+#endif
+
+#ifdef MODE_BFS
+#define B_KEY_INDEX_ALIGN 8
+#else
+#define B_KEY_INDEX_ALIGN 4
+#endif
+
+#define B_KEY_INDEX_OFFSET(node) ((grub_uint16_t *) \
+ ((char *) (node) \
+ + ALIGN_UP (sizeof (struct grub_afs_bnode) \
+ + node->key_size, \
+ B_KEY_INDEX_ALIGN)))
+
+#define B_KEY_VALUE_OFFSET(node) ((grub_afs_bvalue_t *) \
+ ((char *) B_KEY_INDEX_OFFSET (node) + \
+ node->key_count * 2))
+
+typedef grub_uint64_t grub_afs_off_t;
+typedef grub_uint64_t grub_afs_bigtime;
+typedef grub_uint64_t grub_afs_bvalue_t;
+
+struct grub_afs_blockrun
+{
+ grub_uint32_t group;
+ grub_uint16_t start;
+ grub_uint16_t len;
+} __attribute__ ((packed));
+
+struct grub_afs_datastream
+{
+ struct grub_afs_blockrun direct[GRUB_AFS_DIRECT_BLOCK_COUNT];
+ grub_afs_off_t max_direct_range;
+ struct grub_afs_blockrun indirect;
+ grub_afs_off_t max_indirect_range;
+ struct grub_afs_blockrun double_indirect;
+ grub_afs_off_t max_double_indirect_range;
+ grub_afs_off_t size;
+} __attribute__ ((packed));
+
+struct grub_afs_bnode
+{
+ grub_afs_bvalue_t left;
+ grub_afs_bvalue_t right;
+ grub_afs_bvalue_t overflow;
+#ifdef MODE_BFS
+ grub_uint16_t key_count;
+ grub_uint16_t key_size;
+#else
+ grub_uint32_t key_count;
+ grub_uint32_t key_size;
+#endif
+ char key_data[0];
+} __attribute__ ((packed));
+
+#ifdef MODE_BFS
+struct grub_afs_btree
+{
+ grub_uint32_t magic;
+ grub_uint32_t unused1;
+ grub_uint32_t tree_depth;
+ grub_uint32_t unused2;
+ grub_afs_bvalue_t root;
+ grub_uint32_t unused3[4];
+} __attribute__ ((packed));
+#else
+struct grub_afs_btree
+{
+ grub_uint32_t magic;
+ grub_afs_bvalue_t root;
+ grub_uint32_t tree_depth;
+ grub_afs_bvalue_t last_node;
+ grub_afs_bvalue_t first_free;
+} __attribute__ ((packed));
+#endif
+
+/* Beware that following structure describes AtheFS and if you write code
+ which uses currently unused fields check it with both AtheFS and BeFS.
+ */
+struct grub_afs_sblock
+{
+ char name[32];
+ grub_uint32_t magic1;
+ grub_uint32_t byte_order;
+ grub_uint32_t block_size;
+ grub_uint32_t block_shift;
+ grub_afs_off_t num_blocks;
+ grub_afs_off_t used_blocks;
+ grub_uint32_t inode_size;
+ grub_uint32_t magic2;
+ grub_uint32_t block_per_group; /* Number of blocks per allocation
+ group. (Max 65536) */
+ grub_uint32_t alloc_group_shift; /* Number of bits to shift a group
+ number to get a byte address. */
+ grub_uint32_t alloc_group_count;
+ grub_uint32_t flags;
+ struct grub_afs_blockrun log_block;
+ grub_afs_off_t log_start;
+ grub_uint32_t valid_log_blocks;
+ grub_uint32_t log_size;
+ grub_uint32_t magic3;
+ struct grub_afs_blockrun root_dir; /* Root dir inode. */
+ struct grub_afs_blockrun deleted_files; /* Directory containing files
+ scheduled for deletion. */
+ struct grub_afs_blockrun index_dir; /* Directory of index files. */
+ grub_uint32_t boot_loader_size;
+ grub_uint32_t pad[7];
+} __attribute__ ((packed));
+
+struct grub_afs_inode
+{
+ grub_uint32_t magic1;
+ struct grub_afs_blockrun inode_num;
+ grub_uint32_t uid;
+ grub_uint32_t gid;
+ grub_uint32_t mode;
+ grub_uint32_t flags;
+#ifndef MODE_BFS
+ grub_uint32_t link_count;
+#endif
+ grub_afs_bigtime create_time;
+ grub_afs_bigtime modified_time;
+ struct grub_afs_blockrun parent;
+ struct grub_afs_blockrun attrib_dir;
+ grub_uint32_t index_type; /* Key data-key only used for index files. */
+ grub_uint32_t inode_size;
+ grub_uint32_t unused;
+ struct grub_afs_datastream stream;
+ grub_uint32_t pad[4];
+ grub_uint32_t small_data[1];
+} __attribute__ ((packed));
+
+struct grub_fshelp_node
+{
+ struct grub_afs_data *data;
+ struct grub_afs_inode inode;
+};
+
+struct grub_afs_data
+{
+ grub_disk_t disk;
+ struct grub_afs_sblock sblock;
+ struct grub_afs_inode *inode;
+ struct grub_fshelp_node diropen;
+};
+
+static grub_dl_t my_mod;
+
+static grub_afs_off_t
+grub_afs_run_to_num (struct grub_afs_sblock *sb,
+ struct grub_afs_blockrun *run)
+{
+ return ((grub_afs_off_t) grub_afs_to_cpu32 (run->group)
+ * sb->block_per_group + grub_afs_to_cpu16 (run->start));
+}
+
+static grub_err_t
+grub_afs_read_inode (struct grub_afs_data *data,
+ grub_uint32_t ino, struct grub_afs_inode *inode)
+{
+ return grub_disk_read (data->disk,
+ ino *
+ (data->sblock.block_size >> GRUB_DISK_SECTOR_BITS),
+ 0, sizeof (struct grub_afs_inode),
+ inode);
+}
+
+static grub_disk_addr_t
+grub_afs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
+{
+ struct grub_afs_sblock *sb = &node->data->sblock;
+ struct grub_afs_datastream *ds = &node->inode.stream;
+
+ if (fileblock < grub_afs_to_cpu64 (ds->max_direct_range))
+ {
+ int i;
+
+ for (i = 0; i < GRUB_AFS_DIRECT_BLOCK_COUNT; i++)
+ {
+ if (fileblock < grub_afs_to_cpu16 (ds->direct[i].len))
+ return grub_afs_run_to_num (sb, &ds->direct[i]) + fileblock;
+ fileblock -= grub_afs_to_cpu16 (ds->direct[i].len);
+ }
+ }
+ else if (fileblock < grub_afs_to_cpu64 (ds->max_indirect_range))
+ {
+ int ptrs_per_blk = sb->block_size / sizeof (struct grub_afs_blockrun);
+ struct grub_afs_blockrun indir[ptrs_per_blk];
+ grub_afs_off_t blk = grub_afs_run_to_num (sb, &ds->indirect);
+ int i;
+
+ fileblock -= grub_afs_to_cpu64 (ds->max_direct_range);
+ for (i = 0; i < ds->indirect.len; i++, blk++)
+ {
+ int j;
+
+ if (grub_disk_read (node->data->disk,
+ blk * (sb->block_size >> GRUB_DISK_SECTOR_BITS),
+ 0, sizeof (indir),
+ indir))
+ return 0;
+
+ for (j = 0; j < ptrs_per_blk; j++)
+ {
+ if (fileblock < grub_afs_to_cpu16 (indir[j].len))
+ return grub_afs_run_to_num (sb, &indir[j]) + fileblock;
+
+ fileblock -= grub_afs_to_cpu16 (indir[j].len);
+ }
+ }
+ }
+ else
+ {
+ int ptrs_per_blk = sb->block_size / sizeof (struct grub_afs_blockrun);
+ struct grub_afs_blockrun indir[ptrs_per_blk];
+
+ /* ([idblk][idptr]) ([dblk][dptr]) [blk] */
+ int cur_pos = fileblock - grub_afs_to_cpu64 (ds->max_indirect_range);
+
+ int dptr_size = GRUB_AFS_BLOCKS_PER_DI_RUN;
+ int dblk_size = dptr_size * ptrs_per_blk;
+ int idptr_size = dblk_size * GRUB_AFS_BLOCKS_PER_DI_RUN;
+ int idblk_size = idptr_size * ptrs_per_blk;
+
+ int off = cur_pos % GRUB_AFS_BLOCKS_PER_DI_RUN;
+ int dptr = (cur_pos / dptr_size) % ptrs_per_blk;
+ int dblk = (cur_pos / dblk_size) % GRUB_AFS_BLOCKS_PER_DI_RUN;
+ int idptr = (cur_pos / idptr_size) % ptrs_per_blk;
+ int idblk = (cur_pos / idblk_size);
+
+ if (grub_disk_read (node->data->disk,
+ (grub_afs_run_to_num (sb, &ds->double_indirect)
+ + idblk) *
+ (sb->block_size >> GRUB_DISK_SECTOR_BITS),
+ 0, sizeof (indir),
+ indir))
+ return 0;
+
+ if (grub_disk_read (node->data->disk,
+ (grub_afs_run_to_num (sb, &indir[idptr]) + dblk) *
+ (sb->block_size >> GRUB_DISK_SECTOR_BITS),
+ 0, sizeof (indir),
+ indir))
+ return 0;
+
+ return grub_afs_run_to_num (sb, &indir[dptr]) + off;
+ }
+
+ return 0;
+}
+
+static grub_ssize_t
+grub_afs_read_file (grub_fshelp_node_t node,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ return grub_fshelp_read_file (node->data->disk, node, read_hook,
+ pos, len, buf, grub_afs_read_block,
+ grub_afs_to_cpu64 (node->inode.stream.size),
+ node->data->sblock.block_shift
+ - GRUB_DISK_SECTOR_BITS);
+}
+
+static char *
+grub_afs_read_symlink (grub_fshelp_node_t node)
+{
+ char *ret;
+ grub_afs_off_t size = grub_afs_to_cpu64 (node->inode.stream.size);
+
+ if (size == 0)
+ {
+ size = sizeof (node->inode.stream);
+ ret = grub_zalloc (size + 1);
+ if (! ret)
+ return 0;
+ grub_memcpy (ret, (char *) &(node->inode.stream),
+ sizeof (node->inode.stream));
+ return ret;
+ }
+ ret = grub_zalloc (size + 1);
+ if (! ret)
+ return 0;
+ grub_afs_read_file (node, 0, 0, size, ret);
+ return ret;
+}
+
+static int
+grub_afs_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ struct grub_afs_btree head;
+ char node_data [GRUB_AFS_BNODE_SIZE];
+ struct grub_afs_bnode *node = (struct grub_afs_bnode *) node_data;
+ int i;
+
+ if ((dir->inode.stream.size == 0)
+ || ((grub_afs_to_cpu32 (dir->inode.mode) & GRUB_AFS_S_IFMT)
+ != GRUB_AFS_S_IFDIR))
+ return 0;
+
+ grub_afs_read_file (dir, 0, 0, sizeof (head), (char *) &head);
+ if (grub_errno)
+ return 0;
+
+ grub_afs_read_file (dir, 0, grub_afs_to_cpu64 (head.root),
+ GRUB_AFS_BNODE_SIZE, (char *) node);
+ if (grub_errno)
+ return 0;
+
+ for (i = 0; i < (int) grub_afs_to_cpu32 (head.tree_depth) - 1; i++)
+ {
+ grub_afs_bvalue_t blk;
+
+ blk = grub_afs_to_cpu64(B_KEY_VALUE_OFFSET (node) [0]);
+ grub_afs_read_file (dir, 0, blk, GRUB_AFS_BNODE_SIZE, (char *) node);
+ if (grub_errno)
+ return 0;
+ }
+
+ if (node->key_count)
+ {
+ grub_uint32_t cur_key = 0;
+
+ while (1)
+ {
+ int key_start, key_size;
+ grub_uint16_t *index;
+
+ index = B_KEY_INDEX_OFFSET (node);
+
+ key_start = (cur_key > 0)
+ ? grub_afs_to_cpu16 (index[cur_key - 1]) : 0;
+ key_size = grub_afs_to_cpu16 (index[cur_key]) - key_start;
+ if (key_size > 0)
+ {
+ char filename [key_size + 1];
+ struct grub_fshelp_node *fdiro;
+ int mode, type;
+
+ fdiro = grub_malloc (sizeof (struct grub_fshelp_node));
+ if (! fdiro)
+ return 0;
+
+ fdiro->data = dir->data;
+ if (grub_afs_read_inode (dir->data,
+ grub_afs_to_cpu64
+ (B_KEY_VALUE_OFFSET (node) [cur_key]),
+ &fdiro->inode))
+ return 0;
+
+ grub_memcpy (filename, &node->key_data[key_start], key_size);
+ filename [key_size] = 0;
+
+ mode = (grub_afs_to_cpu32 (fdiro->inode.mode) & GRUB_AFS_S_IFMT);
+ if (mode == GRUB_AFS_S_IFDIR)
+ type = GRUB_FSHELP_DIR;
+ else if (mode == GRUB_AFS_S_IFREG)
+ type = GRUB_FSHELP_REG;
+ else if (mode == GRUB_AFS_S_IFLNK)
+ type = GRUB_FSHELP_SYMLINK;
+ else
+ type = GRUB_FSHELP_UNKNOWN;
+
+ if (hook (filename, type, fdiro))
+ return 1;
+ }
+
+ cur_key++;
+ if (cur_key >= grub_afs_to_cpu32 (node->key_count))
+ {
+ if (node->right == GRUB_AFS_NULL_VAL)
+ break;
+
+ grub_afs_read_file (dir, 0, grub_afs_to_cpu64 (node->right),
+ GRUB_AFS_BNODE_SIZE, (char *) node);
+ if (grub_errno)
+ return 0;
+
+ cur_key = 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+grub_afs_validate_sblock (struct grub_afs_sblock *sb)
+{
+ if (grub_afs_to_cpu32 (sb->magic1) == GRUB_AFS_SBLOCK_MAGIC1)
+ {
+ sb->magic2 = grub_afs_to_cpu32 (sb->magic2);
+ sb->magic3 = grub_afs_to_cpu32 (sb->magic3);
+ sb->block_shift = grub_afs_to_cpu32 (sb->block_shift);
+ sb->block_size = grub_afs_to_cpu32 (sb->block_size);
+ sb->used_blocks = grub_afs_to_cpu64 (sb->used_blocks);
+ sb->num_blocks = grub_afs_to_cpu64 (sb->num_blocks);
+ sb->inode_size = grub_afs_to_cpu32 (sb->inode_size);
+ sb->alloc_group_count = grub_afs_to_cpu32 (sb->alloc_group_count);
+ sb->alloc_group_shift = grub_afs_to_cpu32 (sb->alloc_group_shift);
+ sb->block_per_group = grub_afs_to_cpu32 (sb->block_per_group);
+ sb->alloc_group_count = grub_afs_to_cpu32 (sb->alloc_group_count);
+ sb->log_size = grub_afs_to_cpu32 (sb->log_size);
+ }
+ else
+ return 0;
+
+ if ((sb->magic2 != GRUB_AFS_SBLOCK_MAGIC2) ||
+ (sb->magic3 != GRUB_AFS_SBLOCK_MAGIC3))
+ return 0;
+
+#ifdef MODE_BFS
+ sb->block_per_group = 1 << (sb->alloc_group_shift);
+#endif
+
+ if (((grub_uint32_t) (1 << sb->block_shift) != sb->block_size)
+ || (sb->used_blocks > sb->num_blocks )
+ || (sb->inode_size != sb->block_size)
+ || (0 == sb->block_size)
+#ifndef MODE_BFS
+ || ((grub_uint32_t) (1 << sb->alloc_group_shift) !=
+ sb->block_per_group * sb->block_size)
+ || (sb->alloc_group_count * sb->block_per_group < sb->num_blocks)
+ || (grub_afs_to_cpu16 (sb->log_block.len) != sb->log_size)
+ || (grub_afs_to_cpu32 (sb->valid_log_blocks) > sb->log_size)
+#endif
+ )
+ return 0;
+
+ return 1;
+}
+
+static struct grub_afs_data *
+grub_afs_mount (grub_disk_t disk)
+{
+ struct grub_afs_data *data = 0;
+
+ data = grub_malloc (sizeof (struct grub_afs_data));
+ if (!data)
+ return 0;
+
+ /* Read the superblock. */
+ if (grub_disk_read (disk, GRUB_AFS_SBLOCK_SECTOR, 0,
+ sizeof (struct grub_afs_sblock), &data->sblock))
+ goto fail;
+
+ if (! grub_afs_validate_sblock (&data->sblock))
+ goto fail;
+
+ data->diropen.data = data;
+ data->inode = &data->diropen.inode;
+ data->disk = disk;
+
+ if (grub_afs_read_inode (data,
+ grub_afs_run_to_num (&data->sblock,
+ &data->sblock.root_dir),
+ data->inode))
+ goto fail;
+
+ return data;
+
+fail:
+ grub_error (GRUB_ERR_BAD_FS, "not an " GRUB_AFS_FSNAME " filesystem");
+
+ grub_free (data);
+ return 0;
+}
+
+static grub_err_t
+grub_afs_open (struct grub_file *file, const char *name)
+{
+ struct grub_afs_data *data;
+ struct grub_fshelp_node *fdiro = 0;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_afs_mount (file->device->disk);
+ if (! data)
+ goto fail;
+
+ grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_afs_iterate_dir,
+ grub_afs_read_symlink, GRUB_FSHELP_REG);
+ if (grub_errno)
+ goto fail;
+
+ grub_memcpy (data->inode, &fdiro->inode, sizeof (struct grub_afs_inode));
+ grub_free (fdiro);
+
+ file->size = grub_afs_to_cpu64 (data->inode->stream.size);
+ file->data = data;
+ file->offset = 0;
+
+ return 0;
+
+fail:
+ grub_free (data);
+
+ grub_dl_unref (my_mod);
+
+ return grub_errno;
+}
+
+static grub_ssize_t
+grub_afs_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ struct grub_afs_data *data = (struct grub_afs_data *) file->data;
+
+ return grub_afs_read_file (&data->diropen, file->read_hook,
+ file->offset, len, buf);
+}
+
+static grub_err_t
+grub_afs_close (grub_file_t file)
+{
+ grub_free (file->data);
+
+ grub_dl_unref (my_mod);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_afs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_afs_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ struct grub_dirhook_info info;
+ grub_memset (&info, 0, sizeof (info));
+ info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
+ info.mtimeset = 1;
+#ifdef MODE_BFS
+ info.mtime = grub_afs_to_cpu64 (node->inode.modified_time) >> 16;
+#else
+ info.mtime = grub_divmod64 (grub_afs_to_cpu64 (node->inode.modified_time),
+ 1000000, 0);
+#endif
+ grub_free (node);
+ return hook (filename, &info);
+ }
+
+ grub_dl_ref (my_mod);
+
+ data = grub_afs_mount (device->disk);
+ if (! data)
+ goto fail;
+
+ grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_afs_iterate_dir,
+ grub_afs_read_symlink, GRUB_FSHELP_DIR);
+ if (grub_errno)
+ goto fail;
+
+ grub_afs_iterate_dir (fdiro, iterate);
+
+ if (fdiro != &data->diropen)
+ grub_free (fdiro);
+
+ fail:
+ grub_free (data);
+
+ grub_dl_unref (my_mod);
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_afs_label (grub_device_t device, char **label)
+{
+ struct grub_afs_data *data;
+ grub_disk_t disk = device->disk;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_afs_mount (disk);
+ if (data)
+ *label = grub_strndup (data->sblock.name, sizeof (data->sblock.name));
+ else
+ *label = NULL;
+
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+
+static struct grub_fs grub_afs_fs = {
+ .name = GRUB_AFS_FSNAME,
+ .dir = grub_afs_dir,
+ .open = grub_afs_open,
+ .read = grub_afs_read,
+ .close = grub_afs_close,
+ .label = grub_afs_label,
+ .next = 0
+};
+
+#if defined (MODE_BIGENDIAN) && defined (MODE_BFS)
+GRUB_MOD_INIT (befs_be)
+#elif defined (MODE_BFS)
+GRUB_MOD_INIT (befs)
+#elif defined (MODE_BIGENDIAN)
+GRUB_MOD_INIT (afs_be)
+#else
+GRUB_MOD_INIT (afs)
+#endif
+{
+ grub_fs_register (&grub_afs_fs);
+ my_mod = mod;
+}
+
+#if defined (MODE_BIGENDIAN) && defined (MODE_BFS)
+GRUB_MOD_FINI (befs_be)
+#elif defined (MODE_BFS)
+GRUB_MOD_FINI (befs)
+#elif defined (MODE_BIGENDIAN)
+GRUB_MOD_FINI (afs_be)
+#else
+GRUB_MOD_FINI (afs)
+#endif
+{
+ grub_fs_unregister (&grub_afs_fs);
+}
diff --git a/grub-core/fs/afs_be.c b/grub-core/fs/afs_be.c
new file mode 100644
index 0000000..1f1f48f
--- /dev/null
+++ b/grub-core/fs/afs_be.c
@@ -0,0 +1,2 @@
+#define MODE_BIGENDIAN 1
+#include "afs.c"
diff --git a/grub-core/fs/befs.c b/grub-core/fs/befs.c
new file mode 100644
index 0000000..c54d8e1
--- /dev/null
+++ b/grub-core/fs/befs.c
@@ -0,0 +1,3 @@
+/* befs.c - The native BeOS/Haiku file-system. */
+#define MODE_BFS 1
+#include "afs.c"
diff --git a/grub-core/fs/befs_be.c b/grub-core/fs/befs_be.c
new file mode 100644
index 0000000..f6e8179
--- /dev/null
+++ b/grub-core/fs/befs_be.c
@@ -0,0 +1,4 @@
+/* befs.c - The native BeOS/Haiku file-system. */
+#define MODE_BFS 1
+#define MODE_BIGENDIAN 1
+#include "afs.c"
diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c
new file mode 100644
index 0000000..42aa257
--- /dev/null
+++ b/grub-core/fs/btrfs.c
@@ -0,0 +1,1491 @@
+/* btrfs.c - B-tree file system. */
+/*
+ * 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/lib/crc.h>
+#include <grub/deflate.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define GRUB_BTRFS_SIGNATURE "_BHRfS_M"
+
+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_uint8_t dummy[0x62 - 8];
+} __attribute__ ((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];
+} __attribute__ ((packed));
+
+struct btrfs_header
+{
+ grub_btrfs_checksum_t checksum;
+ grub_btrfs_uuid_t uuid;
+ grub_uint8_t dummy[0x30];
+ grub_uint32_t nitems;
+ grub_uint8_t level;
+} __attribute__ ((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_key
+{
+ grub_uint64_t object_id;
+#define GRUB_BTRFS_ITEM_TYPE_INODE_ITEM 0x01
+#define GRUB_BTRFS_ITEM_TYPE_DIR_ITEM 0x54
+#define GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM 0x6c
+#define GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM 0x84
+#define GRUB_BTRFS_ITEM_TYPE_DEVICE 0xd8
+#define GRUB_BTRFS_ITEM_TYPE_CHUNK 0xe4
+ grub_uint8_t type;
+ grub_uint64_t offset;
+} __attribute__ ((packed));
+
+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
+ grub_uint8_t dummy2[0xc];
+ grub_uint16_t nstripes;
+ grub_uint16_t nsubstripes;
+} __attribute__ ((packed));
+
+struct grub_btrfs_chunk_stripe
+{
+ grub_uint64_t device_id;
+ grub_uint64_t offset;
+ grub_btrfs_uuid_t device_uuid;
+} __attribute__ ((packed));
+
+struct grub_btrfs_leaf_node
+{
+ struct grub_btrfs_key key;
+ grub_uint32_t offset;
+ grub_uint32_t size;
+} __attribute__ ((packed));
+
+struct grub_btrfs_internal_node
+{
+ struct grub_btrfs_key key;
+ grub_uint64_t addr;
+ grub_uint64_t dummy;
+} __attribute__ ((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];
+} __attribute__ ((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_root_item
+{
+ grub_uint8_t dummy[0xb0];
+ grub_uint64_t tree;
+ grub_uint64_t inode;
+};
+
+struct grub_btrfs_time
+{
+ grub_int64_t sec;
+ grub_uint32_t nanosec;
+} __attribute__ ((aligned(4)));
+
+struct grub_btrfs_inode
+{
+ grub_uint8_t dummy1[0x10];
+ grub_uint64_t size;
+ grub_uint8_t dummy2[0x70];
+ struct grub_btrfs_time mtime;
+} __attribute__ ((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;
+ };
+ };
+} __attribute__ ((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_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);
+
+static grub_err_t
+read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb)
+{
+ unsigned i;
+ grub_err_t err = GRUB_ERR_NONE;
+ for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++)
+ {
+ struct grub_btrfs_superblock sblock;
+ 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_cpu_to_le64 (a->object_id) < grub_cpu_to_le64 (b->object_id))
+ return -1;
+ if (grub_cpu_to_le64 (a->object_id) > grub_cpu_to_le64 (b->object_id))
+ return +1;
+
+ if (a->type < b->type)
+ return -1;
+ if (a->type > b->type)
+ return +1;
+
+ if (grub_cpu_to_le64 (a->offset) < grub_cpu_to_le64 (b->offset))
+ return -1;
+ if (grub_cpu_to_le64 (a->offset) > grub_cpu_to_le64 (b->offset))
+ return +1;
+ return 0;
+}
+
+static void
+free_iterator (struct grub_btrfs_leaf_descriptor *desc)
+{
+ grub_free (desc->data);
+}
+
+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;
+ desc->allocated *= 2;
+ newdata = grub_realloc (desc->data, sizeof (desc->data[0])
+ * desc->allocated);
+ 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));
+ if (err)
+ return -err;
+
+ err = grub_btrfs_read_logical (data, grub_le_to_cpu64 (node.addr), &head,
+ sizeof (head));
+ if (err)
+ return -err;
+
+ 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));
+ 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_disk_addr_t root,
+ grub_disk_addr_t *outaddr, grub_size_t *outsize,
+ struct grub_btrfs_leaf_descriptor *desc)
+{
+ grub_disk_addr_t addr = root;
+ int depth = -1;
+
+ if (desc)
+ {
+ desc->allocated = 16;
+ desc->depth = 0;
+ desc->data = grub_malloc (sizeof (desc->data[0]) * desc->allocated);
+ if (!desc->data)
+ return grub_errno;
+ }
+
+ 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));
+ if (err)
+ return err;
+ 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));
+ 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));
+ 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;
+ }
+ }
+}
+
+static grub_device_t
+find_device (struct grub_btrfs_data *data, grub_uint64_t id,
+ int do_rescan)
+{
+ grub_device_t dev_found = NULL;
+ auto int hook (const char *name);
+ int hook (const char *name)
+ {
+ 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 (data->sblock.uuid, sb.uuid, sizeof (sb.uuid)) != 0
+ || sb.this_device.device_id != id)
+ {
+ grub_device_close (dev);
+ return 0;
+ }
+
+ dev_found = dev;
+ return 1;
+ }
+
+ unsigned i;
+
+ for (i = 0; i < data->n_devices_attached; i++)
+ if (id == data->devices_attached[i].id)
+ return data->devices_attached[i].dev;
+ if (do_rescan)
+ grub_device_iterate (hook);
+ if (!dev_found)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "couldn't find a member device");
+ return NULL;
+ }
+ data->n_devices_attached++;
+ if (data->n_devices_attached > data->n_devices_allocated)
+ {
+ void *tmp;
+ data->n_devices_allocated = 2 * data->n_devices_attached + 1;
+ data->devices_attached
+ = grub_realloc (tmp = data->devices_attached,
+ data->n_devices_allocated
+ * sizeof (data->devices_attached[0]));
+ if (!data->devices_attached)
+ {
+ grub_device_close (dev_found);
+ data->devices_attached = tmp;
+ return NULL;
+ }
+ }
+ data->devices_attached[data->n_devices_attached - 1].id = id;
+ data->devices_attached[data->n_devices_attached - 1].dev = dev_found;
+ return dev_found;
+}
+
+static grub_err_t
+grub_btrfs_read_logical (struct grub_btrfs_data *data,
+ grub_disk_addr_t addr,
+ void *buf, grub_size_t size)
+{
+ 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;
+ struct grub_btrfs_key key_out;
+ int challoc = 0;
+ grub_device_t dev;
+ 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);
+ }
+ struct grub_btrfs_key key_in;
+ grub_size_t chsize;
+ grub_disk_addr_t chaddr;
+ key_in.object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
+ key_in.type = GRUB_BTRFS_ITEM_TYPE_CHUNK;
+ key_in.offset = addr;
+ err = lower_bound (data, &key_in, &key_out,
+ grub_le_to_cpu64 (data->sblock.chunk_tree),
+ &chaddr, &chsize, NULL);
+ 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);
+ if (err)
+ {
+ grub_free (chunk);
+ return err;
+ }
+
+ chunk_found:
+ {
+ grub_uint32_t stripen;
+ grub_uint64_t stripe_offset;
+ grub_uint64_t off = addr - grub_le_to_cpu64 (key->offset);
+ unsigned redundancy = 1;
+ unsigned i, j;
+
+ 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");
+ }
+
+ 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));
+
+ 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_full (grub_le_to_cpu64 (chunk->size),
+ grub_le_to_cpu16 (chunk->nstripes),
+ NULL);
+ stripen = grub_divmod64_full (off, stripe_length, &stripe_offset);
+ csize = (stripen + 1) * stripe_length - off;
+ break;
+ }
+ case GRUB_BTRFS_CHUNK_TYPE_DUPLICATED:
+ case GRUB_BTRFS_CHUNK_TYPE_RAID1:
+ {
+ grub_dprintf ("btrfs", "RAID1\n");
+ stripen = 0;
+ stripe_offset = off;
+ csize = grub_le_to_cpu64 (chunk->size) - off;
+ redundancy = 2;
+ break;
+ }
+ case GRUB_BTRFS_CHUNK_TYPE_RAID0:
+ {
+ grub_uint64_t middle, high;
+ grub_uint32_t low;
+ grub_dprintf ("btrfs", "RAID0\n");
+ middle = grub_divmod64 (off,
+ grub_le_to_cpu64 (chunk->stripe_length),
+ &low);
+
+ high = grub_divmod64 (middle, grub_le_to_cpu16 (chunk->nstripes),
+ &stripen);
+ stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length)
+ * high;
+ csize = grub_le_to_cpu64 (chunk->stripe_length) - low;
+ break;
+ }
+ case GRUB_BTRFS_CHUNK_TYPE_RAID10:
+ {
+ grub_uint64_t middle, high;
+ grub_uint32_t low;
+ middle = grub_divmod64 (off,
+ grub_le_to_cpu64 (chunk->stripe_length),
+ &low);
+
+ high = grub_divmod64 (middle,
+ grub_le_to_cpu16 (chunk->nsubstripes),
+ &stripen);
+ stripen *= grub_le_to_cpu16 (chunk->nstripes)
+ / grub_le_to_cpu16 (chunk->nsubstripes);
+ redundancy = grub_le_to_cpu16 (chunk->nstripes)
+ / grub_le_to_cpu16 (chunk->nsubstripes);
+ stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length)
+ * high;
+ csize = grub_le_to_cpu64 (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 ((grub_size_t) csize > size)
+ csize = size;
+
+ for (j = 0; j < 2; j++)
+ {
+ for (i = 0; i < redundancy; i++)
+ {
+ struct grub_btrfs_chunk_stripe *stripe;
+ grub_disk_addr_t paddr;
+
+ 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 + i;
+
+ paddr = stripe->offset + stripe_offset;
+
+ grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
+ "+0x%" PRIxGRUB_UINT64_T " (%d stripes (%d substripes) of %"
+ PRIxGRUB_UINT64_T ") stripe %" PRIxGRUB_UINT32_T
+ " maps to 0x%" 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),
+ stripen, stripe->offset);
+ grub_dprintf ("btrfs", "reading paddr 0x%" PRIxGRUB_UINT64_T
+ " for laddr 0x%" PRIxGRUB_UINT64_T"\n", paddr,
+ addr);
+
+ dev = find_device (data, stripe->device_id, j);
+ if (!dev)
+ {
+ err = grub_errno;
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+
+ err = grub_disk_read (dev->disk, paddr >> GRUB_DISK_SECTOR_BITS,
+ paddr & (GRUB_DISK_SECTOR_SIZE - 1),
+ csize, buf);
+ if (!err)
+ break;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ if (i != redundancy)
+ 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++)
+ 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);
+ 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));
+}
+
+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);
+ 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;
+ }
+ 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);
+ 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", "extent 0x%" PRIxGRUB_UINT64_T "+0x%"
+ PRIxGRUB_UINT64_T " (0x%"
+ PRIxGRUB_UINT64_T ")\n",
+ grub_le_to_cpu64 (key_out.offset),
+ grub_le_to_cpu64 (data->extent->size),
+ grub_le_to_cpu64 (data->extent->filled));
+ 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)
+ {
+ 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)
+ 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_ZLIB)
+ {
+ char *tmp;
+ grub_uint64_t zsize;
+ 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);
+ if (err)
+ {
+ grub_free (tmp);
+ return -1;
+ }
+ if (grub_zlib_decompress (tmp, zsize, extoff
+ + grub_le_to_cpu64 (data->extent->offset),
+ buf, csize) != (grub_ssize_t) csize)
+ {
+ grub_free (tmp);
+ return -1;
+ }
+ grub_free (tmp);
+ break;
+ }
+ err = grub_btrfs_read_logical (data,
+ grub_le_to_cpu64 (data->extent->laddr)
+ + grub_le_to_cpu64 (data->extent->offset)
+ + extoff,
+ buf, csize);
+ 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
+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;
+ int skip_default;
+ const char *ctoken;
+ grub_size_t ctokenlen;
+ char *path_alloc = NULL;
+ unsigned symlinks_max = 32;
+
+ *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
+ *tree = data->sblock.root_tree;
+ key->object_id = data->sblock.root_dir_objectid;
+ key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
+ key->offset = 0;
+ skip_default = 1;
+
+ while (1)
+ {
+ if (!skip_default)
+ {
+ while (path[0] == '/')
+ path++;
+ if (!path[0])
+ break;
+ slash = grub_strchr (path, '/');
+ if (!slash)
+ slash = path + grub_strlen (path);
+ ctoken = path;
+ ctokenlen = slash - path;
+ }
+ else
+ {
+ ctoken = "default";
+ ctokenlen = sizeof ("default") - 1;
+ }
+
+ if (*type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
+ {
+ grub_free (path_alloc);
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ }
+
+ 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);
+ if (err)
+ {
+ grub_free (direl);
+ grub_free (path_alloc);
+ return err;
+ }
+ if (key_cmp (key, &key_out) != 0)
+ {
+ grub_free (direl);
+ grub_free (path_alloc);
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ }
+
+ 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);
+ return grub_errno;
+ }
+ }
+
+ err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize);
+ if (err)
+ {
+ grub_free (direl);
+ grub_free (path_alloc);
+ 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);
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ }
+
+ if (!skip_default)
+ path = slash;
+ skip_default = 0;
+ 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);
+ return grub_error (GRUB_ERR_SYMLINK_LOOP,
+ "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);
+ return err;
+ }
+ tmp = grub_malloc (grub_le_to_cpu64 (inode.size)
+ + grub_strlen (path) + 1);
+ if (!tmp)
+ {
+ grub_free (direl);
+ grub_free (path_alloc);
+ 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 (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] == '/')
+ {
+ *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
+ *tree = data->sblock.root_tree;
+ key->object_id = data->sblock.root_dir_objectid;
+ key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
+ key->offset = 0;
+ skip_default = 1;
+ }
+ 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);
+ if (err)
+ {
+ grub_free (direl);
+ grub_free (path_alloc);
+ return err;
+ }
+ if (cdirel->key.object_id != key_out.object_id
+ || cdirel->key.type != key_out.type)
+ {
+ grub_free (direl);
+ grub_free (path_alloc);
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ }
+ err = grub_btrfs_read_logical (data, elemaddr,
+ &ri, sizeof (ri));
+ if (err)
+ {
+ grub_free (direl);
+ grub_free (path_alloc);
+ return err;
+ }
+ key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
+ key->offset = 0;
+ key->object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
+ *tree = grub_le_to_cpu64 (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);
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ }
+ *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 (direl);
+ return grub_error (GRUB_ERR_BAD_FS, "unrecognised object type 0x%x",
+ cdirel->key.type);
+ }
+ }
+
+ grub_free (direl);
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_btrfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ 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)
+ return err;
+ if (type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+
+ err = lower_bound (data, &key_in, &key_out, tree,
+ &elemaddr, &elemsize, &desc);
+ if (err)
+ 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)
+ {
+ free_iterator (&desc);
+ return -r;
+ }
+ }
+ 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)
+ {
+ free_iterator (&desc);
+ return grub_errno;
+ }
+ }
+
+ err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize);
+ if (err)
+ 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)))
+ {
+ 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 = 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))
+ 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, "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;
+}
+
+static struct grub_fs grub_btrfs_fs =
+ {
+ .name = "btrfs",
+ .dir = grub_btrfs_dir,
+ .open = grub_btrfs_open,
+ .read = grub_btrfs_read,
+ .close = grub_btrfs_close,
+ .uuid = grub_btrfs_uuid,
+ .label = grub_btrfs_label,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 1,
+#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/cpio.c b/grub-core/fs/cpio.c
new file mode 100644
index 0000000..a7ccfbd
--- /dev/null
+++ b/grub-core/fs/cpio.c
@@ -0,0 +1,381 @@
+/* cpio.c - cpio and tar 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/>.
+ */
+
+#include <grub/file.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/disk.h>
+#include <grub/dl.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#ifndef MODE_USTAR
+/* cpio support */
+#define MAGIC_BCPIO 070707
+struct head
+{
+ grub_uint16_t magic;
+ grub_uint16_t dev;
+ grub_uint16_t ino;
+ grub_uint16_t mode;
+ grub_uint16_t uid;
+ grub_uint16_t gid;
+ grub_uint16_t nlink;
+ grub_uint16_t rdev;
+ grub_uint16_t mtime_1;
+ grub_uint16_t mtime_2;
+ grub_uint16_t namesize;
+ grub_uint16_t filesize_1;
+ grub_uint16_t filesize_2;
+} __attribute__ ((packed));
+#else
+/* tar support */
+#define MAGIC_USTAR "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];
+} __attribute__ ((packed));
+#endif
+
+struct grub_cpio_data
+{
+ grub_disk_t disk;
+ grub_uint32_t hofs;
+ grub_uint32_t dofs;
+ grub_uint32_t size;
+};
+
+static grub_dl_t my_mod;
+
+static grub_err_t
+grub_cpio_find_file (struct grub_cpio_data *data, char **name,
+ grub_uint32_t * ofs)
+{
+#ifndef MODE_USTAR
+ struct head hd;
+
+ if (grub_disk_read
+ (data->disk, 0, data->hofs, sizeof (hd), &hd))
+ return grub_errno;
+
+ if (hd.magic != MAGIC_BCPIO)
+ return grub_error (GRUB_ERR_BAD_FS, "invalid cpio archive");
+
+ data->size = (((grub_uint32_t) hd.filesize_1) << 16) + hd.filesize_2;
+
+ if (hd.namesize & 1)
+ hd.namesize++;
+
+ if ((*name = grub_malloc (hd.namesize)) == NULL)
+ return grub_errno;
+
+ if (grub_disk_read (data->disk, 0, data->hofs + sizeof (hd),
+ hd.namesize, *name))
+ {
+ grub_free (*name);
+ return grub_errno;
+ }
+
+ if (data->size == 0 && hd.mode == 0 && hd.namesize == 11 + 1
+ && ! grub_memcmp(*name, "TRAILER!!!", 11))
+ {
+ *ofs = 0;
+ return GRUB_ERR_NONE;
+ }
+
+ data->dofs = data->hofs + sizeof (hd) + hd.namesize;
+ *ofs = data->dofs + data->size;
+ if (data->size & 1)
+ (*ofs)++;
+#else
+ struct head hd;
+
+ if (grub_disk_read
+ (data->disk, 0, data->hofs, sizeof (hd), &hd))
+ return grub_errno;
+
+ if (!hd.name[0])
+ {
+ *ofs = 0;
+ return GRUB_ERR_NONE;
+ }
+
+ if (grub_memcmp (hd.magic, MAGIC_USTAR, sizeof (MAGIC_USTAR) - 1))
+ return grub_error (GRUB_ERR_BAD_FS, "invalid tar archive");
+
+ if ((*name = grub_strdup (hd.name)) == NULL)
+ return grub_errno;
+
+ data->size = grub_strtoul (hd.size, NULL, 8);
+ data->dofs = data->hofs + GRUB_DISK_SECTOR_SIZE;
+ *ofs = data->dofs + ((data->size + GRUB_DISK_SECTOR_SIZE - 1) &
+ ~(GRUB_DISK_SECTOR_SIZE - 1));
+#endif
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_cpio_data *
+grub_cpio_mount (grub_disk_t disk)
+{
+ struct head hd;
+ struct grub_cpio_data *data;
+
+ if (grub_disk_read (disk, 0, 0, sizeof (hd), &hd))
+ goto fail;
+
+#ifndef MODE_USTAR
+ if (hd.magic != MAGIC_BCPIO)
+#else
+ if (grub_memcmp (hd.magic, MAGIC_USTAR,
+ sizeof (MAGIC_USTAR) - 1))
+#endif
+ goto fail;
+
+ data = (struct grub_cpio_data *) grub_malloc (sizeof (*data));
+ if (!data)
+ goto fail;
+
+ data->disk = disk;
+
+ return data;
+
+fail:
+ grub_error (GRUB_ERR_BAD_FS, "not a "
+#ifdef MODE_USTAR
+ "tar"
+#else
+ "cpio"
+#endif
+ " filesystem");
+ return 0;
+}
+
+static grub_err_t
+grub_cpio_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_cpio_data *data;
+ grub_uint32_t ofs;
+ char *prev, *name;
+ const char *np;
+ int len;
+
+ grub_dl_ref (my_mod);
+
+ prev = 0;
+
+ data = grub_cpio_mount (device->disk);
+ if (!data)
+ goto fail;
+
+ np = path + 1;
+ len = grub_strlen (path) - 1;
+
+ data->hofs = 0;
+ while (1)
+ {
+ if (grub_cpio_find_file (data, &name, &ofs))
+ goto fail;
+
+ if (!ofs)
+ break;
+
+ if (grub_memcmp (np, name, len) == 0)
+ {
+ char *p, *n;
+
+ n = name + len;
+ if (*n == '/')
+ n++;
+
+ p = grub_strchr (name + len, '/');
+ if (p)
+ *p = 0;
+
+ if ((!prev) || (grub_strcmp (prev, name) != 0))
+ {
+ struct grub_dirhook_info info;
+ grub_memset (&info, 0, sizeof (info));
+ info.dir = (p != NULL);
+
+ hook (name + len, &info);
+ if (prev)
+ grub_free (prev);
+ prev = name;
+ }
+ else
+ grub_free (name);
+ }
+ data->hofs = ofs;
+ }
+
+fail:
+
+ if (prev)
+ grub_free (prev);
+
+ if (data)
+ grub_free (data);
+
+ grub_dl_unref (my_mod);
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_cpio_open (grub_file_t file, const char *name)
+{
+ struct grub_cpio_data *data;
+ grub_uint32_t ofs;
+ char *fn;
+ int i, j;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_cpio_mount (file->device->disk);
+ if (!data)
+ goto fail;
+
+ data->hofs = 0;
+ while (1)
+ {
+ if (grub_cpio_find_file (data, &fn, &ofs))
+ goto fail;
+
+ if (!ofs)
+ {
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ break;
+ }
+
+ /* Compare NAME and FN by hand in order to cope with duplicate
+ slashes. */
+ i = 0;
+ j = 0;
+ while (name[i] == '/')
+ i++;
+ while (1)
+ {
+ if (name[i] != fn[j])
+ goto no_match;
+
+ if (name[i] == '\0')
+ break;
+
+ while (name[i] == '/' && name[i+1] == '/')
+ i++;
+
+ i++;
+ j++;
+ }
+
+ if (name[i] != fn[j])
+ goto no_match;
+
+ file->data = data;
+ file->size = data->size;
+ grub_free (fn);
+
+ return GRUB_ERR_NONE;
+
+ no_match:
+
+ grub_free (fn);
+ data->hofs = ofs;
+ }
+
+fail:
+
+ if (data)
+ grub_free (data);
+
+ grub_dl_unref (my_mod);
+
+ return grub_errno;
+}
+
+static grub_ssize_t
+grub_cpio_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ struct grub_cpio_data *data;
+
+ data = file->data;
+ return (grub_disk_read (data->disk, 0, data->dofs + file->offset,
+ len, buf)) ? -1 : (grub_ssize_t) len;
+}
+
+static grub_err_t
+grub_cpio_close (grub_file_t file)
+{
+ grub_free (file->data);
+
+ grub_dl_unref (my_mod);
+
+ return grub_errno;
+}
+
+static struct grub_fs grub_cpio_fs = {
+#ifdef MODE_USTAR
+ .name = "tarfs",
+#else
+ .name = "cpiofs",
+#endif
+ .dir = grub_cpio_dir,
+ .open = grub_cpio_open,
+ .read = grub_cpio_read,
+ .close = grub_cpio_close,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 0,
+#endif
+};
+
+#ifdef MODE_USTAR
+GRUB_MOD_INIT (tar)
+#else
+GRUB_MOD_INIT (cpio)
+#endif
+{
+ grub_fs_register (&grub_cpio_fs);
+ my_mod = mod;
+}
+
+#ifdef MODE_USTAR
+GRUB_MOD_FINI (tar)
+#else
+GRUB_MOD_FINI (cpio)
+#endif
+{
+ grub_fs_unregister (&grub_cpio_fs);
+}
diff --git a/grub-core/fs/ext2.c b/grub-core/fs/ext2.c
new file mode 100644
index 0000000..0fdf151
--- /dev/null
+++ b/grub-core/fs/ext2.c
@@ -0,0 +1,991 @@
+/* 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
+/* Maximum length of a pathname. */
+#define EXT2_PATH_MAX 4096
+/* Maximum nesting of symlinks, used to prevent a loop. */
+#define EXT2_MAX_SYMLINKCNT 8
+
+/* 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>
+
+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) (1 << 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) \
+ (EXT2_REVISION (data) == 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_FLEX_BG 0x0200
+
+/* 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 )
+/* 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. */
+#define EXT2_DRIVER_IGNORED_INCOMPAT ( EXT3_FEATURE_INCOMPAT_RECOVER )
+
+
+#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_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 reserved_word_pad;
+ 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];
+};
+
+/* 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;
+ 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;
+ grub_disk_t disk;
+ struct grub_ext2_inode *inode;
+ struct grub_fshelp_node diropen;
+};
+
+static grub_dl_t my_mod;
+
+
+
+/* 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, int group,
+ struct grub_ext2_block_group *blkgrp)
+{
+ return grub_disk_read (data->disk,
+ ((grub_le_to_cpu32 (data->sblock.first_data_block) + 1)
+ << LOG2_EXT2_BLOCK_SIZE (data)),
+ group * sizeof (struct grub_ext2_block_group),
+ sizeof (struct grub_ext2_block_group), blkgrp);
+}
+
+static struct grub_ext4_extent_header *
+grub_ext4_find_leaf (struct grub_ext2_data *data, char *buf,
+ struct grub_ext4_extent_header *ext_block,
+ grub_uint32_t fileblock)
+{
+ struct grub_ext4_extent_idx *index;
+
+ while (1)
+ {
+ int i;
+ grub_disk_addr_t block;
+
+ index = (struct grub_ext4_extent_idx *) (ext_block + 1);
+
+ if (grub_le_to_cpu16(ext_block->magic) != EXT4_EXT_MAGIC)
+ return 0;
+
+ 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)
+ return 0;
+
+ block = grub_le_to_cpu16 (index[i].leaf_hi);
+ block = (block << 32) + grub_le_to_cpu32 (index[i].leaf);
+ if (grub_disk_read (data->disk,
+ block << LOG2_EXT2_BLOCK_SIZE (data),
+ 0, EXT2_BLOCK_SIZE(data), buf))
+ return 0;
+
+ ext_block = (struct grub_ext4_extent_header *) buf;
+ }
+}
+
+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;
+ int blknr = -1;
+ unsigned int blksz = EXT2_BLOCK_SIZE (data);
+ int log2_blksz = LOG2_EXT2_BLOCK_SIZE (data);
+
+ if (grub_le_to_cpu32(inode->flags) & EXT4_EXTENTS_FLAG)
+ {
+ char buf[EXT2_BLOCK_SIZE(data)];
+ struct grub_ext4_extent_header *leaf;
+ struct grub_ext4_extent *ext;
+ int i;
+
+ leaf = grub_ext4_find_leaf (data, buf,
+ (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))
+ return 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);
+
+ return fileblock + start;
+ }
+ }
+ else
+ {
+ grub_error (GRUB_ERR_BAD_FS, "something wrong with extent");
+ return -1;
+ }
+ }
+ /* Direct blocks. */
+ if (fileblock < INDIRECT_BLOCKS)
+ blknr = grub_le_to_cpu32 (inode->blocks.dir_blocks[fileblock]);
+ /* Indirect. */
+ else if (fileblock < INDIRECT_BLOCKS + blksz / 4)
+ {
+ grub_uint32_t indir[blksz / 4];
+
+ if (grub_disk_read (data->disk,
+ ((grub_disk_addr_t)
+ grub_le_to_cpu32 (inode->blocks.indir_block))
+ << log2_blksz,
+ 0, blksz, indir))
+ return grub_errno;
+
+ blknr = grub_le_to_cpu32 (indir[fileblock - INDIRECT_BLOCKS]);
+ }
+ /* Double indirect. */
+ else if (fileblock < INDIRECT_BLOCKS + blksz / 4 * (blksz / 4 + 1))
+ {
+ unsigned int perblock = blksz / 4;
+ unsigned int rblock = fileblock - (INDIRECT_BLOCKS
+ + blksz / 4);
+ grub_uint32_t indir[blksz / 4];
+
+ if (grub_disk_read (data->disk,
+ ((grub_disk_addr_t)
+ grub_le_to_cpu32 (inode->blocks.double_indir_block))
+ << log2_blksz,
+ 0, blksz, indir))
+ return grub_errno;
+
+ if (grub_disk_read (data->disk,
+ ((grub_disk_addr_t)
+ grub_le_to_cpu32 (indir[rblock / perblock]))
+ << log2_blksz,
+ 0, blksz, indir))
+ return grub_errno;
+
+
+ blknr = grub_le_to_cpu32 (indir[rblock % perblock]);
+ }
+ /* triple indirect. */
+ else if (fileblock < INDIRECT_BLOCKS + blksz / 4 * (blksz / 4 + 1)
+ + (blksz / 4) * (blksz / 4) * (blksz / 4 + 1))
+ {
+ unsigned int perblock = blksz / 4;
+ unsigned int rblock = fileblock - (INDIRECT_BLOCKS + blksz / 4
+ * (blksz / 4 + 1));
+ grub_uint32_t indir[blksz / 4];
+
+ if (grub_disk_read (data->disk,
+ ((grub_disk_addr_t)
+ grub_le_to_cpu32 (inode->blocks.triple_indir_block))
+ << log2_blksz,
+ 0, blksz, indir))
+ return grub_errno;
+
+ if (grub_disk_read (data->disk,
+ ((grub_disk_addr_t)
+ grub_le_to_cpu32 (indir[(rblock / perblock) / perblock]))
+ << log2_blksz,
+ 0, blksz, indir))
+ return grub_errno;
+
+ if (grub_disk_read (data->disk,
+ ((grub_disk_addr_t)
+ grub_le_to_cpu32 (indir[(rblock / perblock) % perblock]))
+ << log2_blksz,
+ 0, blksz, indir))
+ return grub_errno;
+
+ blknr = grub_le_to_cpu32 (indir[rblock % perblock]);
+ }
+ else
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "ext2fs doesn't support quadruple indirect blocks");
+ }
+
+ return blknr;
+}
+
+/* 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ grub_off_t pos, grub_size_t len, char *buf)
+{
+ return grub_fshelp_read_file (node->data->disk, node, read_hook,
+ 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));
+
+}
+
+
+/* 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;
+
+ /* 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;
+
+ /* Read the inode. */
+ if (grub_disk_read (data->disk,
+ (((grub_disk_addr_t) grub_le_to_cpu32 (blkgrp.inode_table_id) + 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 (grub_le_to_cpu16 (data->sblock.magic) != EXT2_MAGIC)
+ {
+ 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 (grub_le_to_cpu32 (data->sblock.feature_incompat)
+ & ~(EXT2_DRIVER_SUPPORTED_INCOMPAT | EXT2_DRIVER_IGNORED_INCOMPAT))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "filesystem has unsupported incompatible features");
+ goto fail;
+ }
+
+
+ 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;
+
+ if (! diro->inode_read)
+ {
+ grub_ext2_read_inode (diro->data, diro->ino, &diro->inode);
+ if (grub_errno)
+ return 0;
+ }
+
+ symlink = grub_malloc (grub_le_to_cpu32 (diro->inode.size) + 1);
+ if (! symlink)
+ return 0;
+
+ /* If the filesize of the symlink is 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) <= 60)
+ grub_strncpy (symlink,
+ diro->inode.symlink,
+ grub_le_to_cpu32 (diro->inode.size));
+ else
+ {
+ grub_ext2_read_file (diro, 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,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ 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;
+ }
+
+ /* Search the file. */
+ while (fpos < grub_le_to_cpu32 (diro->inode.size))
+ {
+ struct ext2_dirent dirent;
+
+ grub_ext2_read_file (diro, 0, fpos, sizeof (struct ext2_dirent),
+ (char *) &dirent);
+ if (grub_errno)
+ return 0;
+
+ if (dirent.direntlen == 0)
+ return 0;
+
+ if (dirent.namelen != 0)
+ {
+ char filename[dirent.namelen + 1];
+ struct grub_fshelp_node *fdiro;
+ enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;
+
+ grub_ext2_read_file (diro, 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))
+ 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;
+ }
+
+ 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->offset, len, buf);
+}
+
+
+static grub_err_t
+grub_ext2_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_ext2_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ struct grub_dirhook_info info;
+ grub_memset (&info, 0, sizeof (info));
+ if (! node->inode_read)
+ {
+ grub_ext2_read_inode (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 hook (filename, &info);
+ }
+
+ grub_dl_ref (my_mod);
+
+ data = grub_ext2_mount (device->disk);
+ if (! data)
+ goto fail;
+
+ grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_ext2_iterate_dir,
+ grub_ext2_read_symlink, GRUB_FSHELP_DIR);
+ if (grub_errno)
+ goto fail;
+
+ grub_ext2_iterate_dir (fdiro, iterate);
+
+ fail:
+ if (fdiro != &data->diropen)
+ grub_free (fdiro);
+ grub_free (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, 14);
+ 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_int32_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",
+ .dir = grub_ext2_dir,
+ .open = grub_ext2_open,
+ .read = grub_ext2_read,
+ .close = grub_ext2_close,
+ .label = grub_ext2_label,
+ .uuid = grub_ext2_uuid,
+ .mtime = grub_ext2_mtime,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 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/fat.c b/grub-core/fs/fat.c
new file mode 100644
index 0000000..76b9c52
--- /dev/null
+++ b/grub-core/fs/fat.c
@@ -0,0 +1,878 @@
+/* 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>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define GRUB_FAT_DIR_ENTRY_SIZE 32
+
+#define GRUB_FAT_ATTR_READ_ONLY 0x01
+#define GRUB_FAT_ATTR_HIDDEN 0x02
+#define GRUB_FAT_ATTR_SYSTEM 0x04
+#define GRUB_FAT_ATTR_VOLUME_ID 0x08
+#define GRUB_FAT_ATTR_DIRECTORY 0x10
+#define GRUB_FAT_ATTR_ARCHIVE 0x20
+
+#define GRUB_FAT_MAXFILE 256
+
+#define GRUB_FAT_ATTR_LONG_NAME (GRUB_FAT_ATTR_READ_ONLY \
+ | GRUB_FAT_ATTR_HIDDEN \
+ | GRUB_FAT_ATTR_SYSTEM \
+ | GRUB_FAT_ATTR_VOLUME_ID)
+#define 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 \
+ | GRUB_FAT_ATTR_VOLUME_ID)
+
+struct grub_fat_bpb
+{
+ grub_uint8_t jmp_boot[3];
+ grub_uint8_t oem_name[8];
+ grub_uint16_t bytes_per_sector;
+ grub_uint8_t sectors_per_cluster;
+ grub_uint16_t num_reserved_sectors;
+ grub_uint8_t num_fats;
+ grub_uint16_t num_root_entries;
+ grub_uint16_t num_total_sectors_16;
+ grub_uint8_t media;
+ grub_uint16_t sectors_per_fat_16;
+ grub_uint16_t sectors_per_track;
+ grub_uint16_t num_heads;
+ grub_uint32_t num_hidden_sectors;
+ grub_uint32_t num_total_sectors_32;
+ union
+ {
+ struct
+ {
+ grub_uint8_t num_ph_drive;
+ grub_uint8_t reserved;
+ grub_uint8_t boot_sig;
+ grub_uint32_t num_serial;
+ grub_uint8_t label[11];
+ grub_uint8_t fstype[8];
+ } __attribute__ ((packed)) fat12_or_fat16;
+ struct
+ {
+ grub_uint32_t sectors_per_fat_32;
+ grub_uint16_t extended_flags;
+ grub_uint16_t fs_version;
+ grub_uint32_t root_cluster;
+ grub_uint16_t fs_info;
+ grub_uint16_t backup_boot_sector;
+ grub_uint8_t reserved[12];
+ grub_uint8_t num_ph_drive;
+ grub_uint8_t reserved1;
+ grub_uint8_t boot_sig;
+ grub_uint32_t num_serial;
+ grub_uint8_t label[11];
+ grub_uint8_t fstype[8];
+ } __attribute__ ((packed)) fat32;
+ } __attribute__ ((packed)) version_specific;
+} __attribute__ ((packed));
+
+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;
+} __attribute__ ((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];
+} __attribute__ ((packed));
+
+struct grub_fat_data
+{
+ int logical_sector_bits;
+ grub_uint32_t num_sectors;
+
+ grub_uint16_t fat_sector;
+ grub_uint32_t sectors_per_fat;
+ int fat_size;
+
+ grub_uint32_t root_cluster;
+ grub_uint32_t root_sector;
+ grub_uint32_t num_root_sectors;
+
+ int cluster_bits;
+ grub_uint32_t cluster_eof_mark;
+ grub_uint32_t cluster_sector;
+ grub_uint32_t num_clusters;
+
+ grub_uint8_t attr;
+ grub_ssize_t file_size;
+ grub_uint32_t file_cluster;
+ grub_uint32_t cur_cluster_num;
+ grub_uint32_t cur_cluster;
+
+ grub_uint32_t uuid;
+};
+
+static grub_dl_t my_mod;
+
+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;
+}
+
+static struct grub_fat_data *
+grub_fat_mount (grub_disk_t disk)
+{
+ struct grub_fat_bpb 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;
+
+ if (grub_strncmp((const char *) bpb.version_specific.fat12_or_fat16.fstype, "FAT12", 5)
+ && grub_strncmp((const char *) bpb.version_specific.fat12_or_fat16.fstype, "FAT16", 5)
+ && grub_strncmp((const char *) bpb.version_specific.fat32.fstype, "FAT32", 5))
+ goto fail;
+
+ /* Get the sizes of logical sectors and clusters. */
+ data->logical_sector_bits =
+ fat_log2 (grub_le_to_cpu16 (bpb.bytes_per_sector));
+ if (data->logical_sector_bits < GRUB_DISK_SECTOR_BITS)
+ goto fail;
+ data->logical_sector_bits -= GRUB_DISK_SECTOR_BITS;
+
+ data->cluster_bits = fat_log2 (bpb.sectors_per_cluster);
+ if (data->cluster_bits < 0)
+ goto fail;
+ data->cluster_bits += data->logical_sector_bits;
+
+ /* Get information about FATs. */
+ data->fat_sector = (grub_le_to_cpu16 (bpb.num_reserved_sectors)
+ << data->logical_sector_bits);
+ if (data->fat_sector == 0)
+ goto fail;
+
+ 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);
+ if (data->sectors_per_fat == 0)
+ goto fail;
+
+ /* Get the number of sectors in this volume. */
+ 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);
+ if (data->num_sectors == 0)
+ goto fail;
+
+ /* Get information about the root directory. */
+ if (bpb.num_fats == 0)
+ goto fail;
+
+ 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)
+ * GRUB_FAT_DIR_ENTRY_SIZE
+ + grub_le_to_cpu16 (bpb.bytes_per_sector) - 1)
+ >> (data->logical_sector_bits + GRUB_DISK_SECTOR_BITS))
+ << (data->logical_sector_bits));
+
+ data->cluster_sector = data->root_sector + data->num_root_sectors;
+ data->num_clusters = (((data->num_sectors - data->cluster_sector)
+ >> (data->cluster_bits + data->logical_sector_bits))
+ + 2);
+
+ if (data->num_clusters <= 2)
+ goto fail;
+
+ 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;
+ }
+ }
+
+ /* 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. */
+ 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);
+
+ /* 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;
+
+ /* Start from the root directory. */
+ data->file_cluster = data->root_cluster;
+ data->cur_cluster_num = ~0U;
+ data->attr = GRUB_FAT_ATTR_DIRECTORY;
+ 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, struct grub_fat_data *data,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ 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;
+
+ /* This is a special case. FAT12 and FAT16 doesn't have the root directory
+ in clusters. */
+ if (data->file_cluster == ~0U)
+ {
+ size = (data->num_root_sectors << GRUB_DISK_SECTOR_BITS) - offset;
+ if (size > len)
+ size = len;
+
+ if (grub_disk_read (disk, data->root_sector, offset, size, buf))
+ return -1;
+
+ return size;
+ }
+
+ /* Calculate the logical cluster number and offset. */
+ logical_cluster_bits = (data->cluster_bits
+ + data->logical_sector_bits
+ + GRUB_DISK_SECTOR_BITS);
+ logical_cluster = offset >> logical_cluster_bits;
+ offset &= (1 << logical_cluster_bits) - 1;
+
+ if (logical_cluster < data->cur_cluster_num)
+ {
+ data->cur_cluster_num = 0;
+ data->cur_cluster = data->file_cluster;
+ }
+
+ while (len)
+ {
+ while (logical_cluster > data->cur_cluster_num)
+ {
+ /* Find next cluster. */
+ grub_uint32_t next_cluster;
+ unsigned long fat_offset;
+
+ switch (data->fat_size)
+ {
+ case 32:
+ fat_offset = data->cur_cluster << 2;
+ break;
+ case 16:
+ fat_offset = data->cur_cluster << 1;
+ break;
+ default:
+ /* case 12: */
+ fat_offset = data->cur_cluster + (data->cur_cluster >> 1);
+ break;
+ }
+
+ /* Read the FAT. */
+ if (grub_disk_read (disk, data->fat_sector, fat_offset,
+ (data->fat_size + 7) >> 3,
+ (char *) &next_cluster))
+ return -1;
+
+ next_cluster = grub_le_to_cpu32 (next_cluster);
+ switch (data->fat_size)
+ {
+ case 16:
+ next_cluster &= 0xFFFF;
+ break;
+ case 12:
+ if (data->cur_cluster & 1)
+ next_cluster >>= 4;
+
+ next_cluster &= 0x0FFF;
+ break;
+ }
+
+ grub_dprintf ("fat", "fat_size=%d, next_cluster=%u\n",
+ data->fat_size, next_cluster);
+
+ /* Check the end. */
+ if (next_cluster >= data->cluster_eof_mark)
+ return ret;
+
+ if (next_cluster < 2 || next_cluster >= data->num_clusters)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "invalid cluster %u",
+ next_cluster);
+ return -1;
+ }
+
+ data->cur_cluster = next_cluster;
+ data->cur_cluster_num++;
+ }
+
+ /* Read the data here. */
+ sector = (data->cluster_sector
+ + ((data->cur_cluster - 2)
+ << (data->cluster_bits + data->logical_sector_bits)));
+ size = (1 << logical_cluster_bits) - offset;
+ if (size > len)
+ size = len;
+
+ disk->read_hook = read_hook;
+ 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;
+}
+
+static grub_err_t
+grub_fat_iterate_dir (grub_disk_t disk, struct grub_fat_data *data,
+ int (*hook) (const char *filename,
+ struct grub_fat_dir_entry *dir))
+{
+ struct grub_fat_dir_entry dir;
+ char *filename, *filep = 0;
+ grub_uint16_t *unibuf;
+ int slot = -1, slots = -1;
+ int checksum = -1;
+ grub_ssize_t offset = -sizeof(dir);
+
+ if (! (data->attr & GRUB_FAT_ATTR_DIRECTORY))
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+
+ /* Allocate space enough to hold a long name. */
+ filename = grub_malloc (0x40 * 13 * 4 + 1);
+ unibuf = (grub_uint16_t *) grub_malloc (0x40 * 13 * 2);
+ if (! filename || ! unibuf)
+ {
+ grub_free (filename);
+ grub_free (unibuf);
+ return 0;
+ }
+
+ while (1)
+ {
+ unsigned i;
+
+ /* Adjust the offset. */
+ offset += sizeof (dir);
+
+ /* Read a directory entry. */
+ if ((grub_fat_read_data (disk, data, 0,
+ offset, sizeof (dir), (char *) &dir)
+ != sizeof (dir) || dir.name[0] == 0))
+ break;
+ /* Handle long name entries. */
+ if (dir.attr == GRUB_FAT_ATTR_LONG_NAME)
+ {
+ struct grub_fat_long_name_entry *long_name
+ = (struct grub_fat_long_name_entry *) &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 (unibuf + slot * 13, long_name->name1, 5 * 2);
+ grub_memcpy (unibuf + slot * 13 + 5, long_name->name2, 6 * 2);
+ grub_memcpy (unibuf + slot * 13 + 11, long_name->name3, 2 * 2);
+ continue;
+ }
+
+ /* Check if this entry is valid. */
+ if (dir.name[0] == 0xe5 || (dir.attr & ~GRUB_FAT_ATTR_VALID))
+ continue;
+
+ /* This is a workaround for Japanese. */
+ if (dir.name[0] == 0x05)
+ dir.name[0] = 0xe5;
+
+ if (checksum != -1 && slot == 0)
+ {
+ grub_uint8_t sum;
+
+ for (sum = 0, i = 0; i < sizeof (dir.name); i++)
+ sum = ((sum >> 1) | (sum << 7)) + dir.name[i];
+
+ if (sum == checksum)
+ {
+ int u;
+
+ for (u = 0; u < slots * 13; u++)
+ unibuf[u] = grub_le_to_cpu16 (unibuf[u]);
+
+ *grub_utf16_to_utf8 ((grub_uint8_t *) filename, unibuf,
+ slots * 13) = '\0';
+
+ if (hook (filename, &dir))
+ break;
+
+ checksum = -1;
+ continue;
+ }
+
+ checksum = -1;
+ }
+
+ /* Convert the 8.3 file name. */
+ filep = filename;
+ if (dir.attr & GRUB_FAT_ATTR_VOLUME_ID)
+ {
+ for (i = 0; i < sizeof (dir.name) && dir.name[i]
+ && ! grub_isspace (dir.name[i]); i++)
+ *filep++ = dir.name[i];
+ }
+ else
+ {
+ for (i = 0; i < 8 && dir.name[i] && ! grub_isspace (dir.name[i]); i++)
+ *filep++ = grub_tolower (dir.name[i]);
+
+ *filep = '.';
+
+ for (i = 8; i < 11 && dir.name[i] && ! grub_isspace (dir.name[i]); i++)
+ *++filep = grub_tolower (dir.name[i]);
+
+ if (*filep != '.')
+ filep++;
+ }
+ *filep = '\0';
+
+ if (hook (filename, &dir))
+ break;
+ }
+
+ grub_free (filename);
+ grub_free (unibuf);
+
+ return grub_errno;
+}
+
+
+/* Find the underlying directory or file in PATH and return the
+ next path. If there is no next path or an error occurs, return NULL.
+ If HOOK is specified, call it with each file name. */
+static char *
+grub_fat_find_dir (grub_disk_t disk, struct grub_fat_data *data,
+ const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ char *dirname, *dirp;
+ int call_hook;
+ int found = 0;
+
+ auto int iter_hook (const char *filename, struct grub_fat_dir_entry *dir);
+ int iter_hook (const char *filename, struct grub_fat_dir_entry *dir)
+ {
+ struct grub_dirhook_info info;
+ grub_memset (&info, 0, sizeof (info));
+
+ info.dir = !! (dir->attr & GRUB_FAT_ATTR_DIRECTORY);
+ info.case_insensitive = 1;
+
+ if (dir->attr & GRUB_FAT_ATTR_VOLUME_ID)
+ return 0;
+ if (*dirname == '\0' && call_hook)
+ return hook (filename, &info);
+
+ if (grub_strcasecmp (dirname, filename) == 0)
+ {
+ found = 1;
+ data->attr = dir->attr;
+ data->file_size = grub_le_to_cpu32 (dir->file_size);
+ data->file_cluster = ((grub_le_to_cpu16 (dir->first_cluster_high) << 16)
+ | grub_le_to_cpu16 (dir->first_cluster_low));
+ data->cur_cluster_num = ~0U;
+
+ if (call_hook)
+ hook (filename, &info);
+
+ return 1;
+ }
+ return 0;
+ }
+
+ if (! (data->attr & GRUB_FAT_ATTR_DIRECTORY))
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ return 0;
+ }
+
+ /* Extract a directory name. */
+ while (*path == '/')
+ path++;
+
+ dirp = grub_strchr (path, '/');
+ if (dirp)
+ {
+ unsigned len = dirp - path;
+
+ dirname = grub_malloc (len + 1);
+ if (! dirname)
+ return 0;
+
+ grub_memcpy (dirname, path, len);
+ dirname[len] = '\0';
+ }
+ else
+ /* This is actually a file. */
+ dirname = grub_strdup (path);
+
+ call_hook = (! dirp && hook);
+
+ grub_fat_iterate_dir (disk, data, iter_hook);
+ if (grub_errno == GRUB_ERR_NONE && ! found && !call_hook)
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+
+ grub_free (dirname);
+
+ return found ? dirp : 0;
+}
+
+static grub_err_t
+grub_fat_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_fat_data *data = 0;
+ grub_disk_t disk = device->disk;
+ grub_size_t len;
+ char *dirname = 0;
+ char *p;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_fat_mount (disk);
+ if (! data)
+ goto fail;
+
+ /* Make sure that DIRNAME terminates with '/'. */
+ len = grub_strlen (path);
+ dirname = grub_malloc (len + 1 + 1);
+ if (! dirname)
+ goto fail;
+ grub_memcpy (dirname, path, len);
+ p = dirname + len;
+ if (path[len - 1] != '/')
+ *p++ = '/';
+ *p = '\0';
+ p = dirname;
+
+ do
+ {
+ p = grub_fat_find_dir (disk, data, p, hook);
+ }
+ while (p && grub_errno == GRUB_ERR_NONE);
+
+ fail:
+
+ grub_free (dirname);
+ 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;
+ char *p = (char *) name;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_fat_mount (file->device->disk);
+ if (! data)
+ goto fail;
+
+ do
+ {
+ p = grub_fat_find_dir (file->device->disk, data, p, 0);
+ if (grub_errno != GRUB_ERR_NONE)
+ goto fail;
+ }
+ while (p);
+
+ if (data->attr & GRUB_FAT_ATTR_DIRECTORY)
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a file");
+ goto fail;
+ }
+
+ file->data = data;
+ file->size = data->file_size;
+
+ return GRUB_ERR_NONE;
+
+ fail:
+
+ 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->offset, len, buf);
+}
+
+static grub_err_t
+grub_fat_close (grub_file_t file)
+{
+ grub_free (file->data);
+
+ grub_dl_unref (my_mod);
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_fat_label (grub_device_t device, char **label)
+{
+ struct grub_fat_data *data;
+ grub_disk_t disk = device->disk;
+
+ auto int iter_hook (const char *filename, struct grub_fat_dir_entry *dir);
+ int iter_hook (const char *filename, struct grub_fat_dir_entry *dir)
+ {
+ if (dir->attr == GRUB_FAT_ATTR_VOLUME_ID)
+ {
+ *label = grub_strdup (filename);
+ return 1;
+ }
+ return 0;
+ }
+
+ grub_dl_ref (my_mod);
+
+ data = grub_fat_mount (disk);
+ if (! data)
+ goto fail;
+
+ if (! (data->attr & GRUB_FAT_ATTR_DIRECTORY))
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ return 0;
+ }
+
+ *label = 0;
+
+ grub_fat_iterate_dir (disk, data, iter_hook);
+
+ fail:
+
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+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)
+ {
+ *uuid = grub_xasprintf ("%04x-%04x",
+ (grub_uint16_t) (data->uuid >> 16),
+ (grub_uint16_t) data->uuid);
+ }
+ else
+ *uuid = NULL;
+
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+static struct grub_fs grub_fat_fs =
+ {
+ .name = "fat",
+ .dir = grub_fat_dir,
+ .open = grub_fat_open,
+ .read = grub_fat_read,
+ .close = grub_fat_close,
+ .label = grub_fat_label,
+ .uuid = grub_fat_uuid,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 1,
+#endif
+ .next = 0
+ };
+
+GRUB_MOD_INIT(fat)
+{
+ grub_fs_register (&grub_fat_fs);
+ my_mod = mod;
+}
+
+GRUB_MOD_FINI(fat)
+{
+ 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..f879885
--- /dev/null
+++ b/grub-core/fs/fshelp.c
@@ -0,0 +1,317 @@
+/* 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>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* 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. Make
+ sure you use the NESTED_FUNC_ATTR macro for HOOK, this is required
+ because GCC has a nasty bug when using regparm=3. */
+grub_err_t
+grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode,
+ grub_fshelp_node_t *foundnode,
+ int (*iterate_dir) (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR (*hook)
+ (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)),
+ char *(*read_symlink) (grub_fshelp_node_t node),
+ enum grub_fshelp_filetype expecttype)
+{
+ grub_err_t err;
+ enum grub_fshelp_filetype foundtype = GRUB_FSHELP_DIR;
+ int symlinknest = 0;
+
+ auto grub_err_t NESTED_FUNC_ATTR find_file (const char *currpath,
+ grub_fshelp_node_t currroot,
+ grub_fshelp_node_t *currfound);
+
+ grub_err_t NESTED_FUNC_ATTR find_file (const char *currpath,
+ grub_fshelp_node_t currroot,
+ grub_fshelp_node_t *currfound)
+ {
+ char fpath[grub_strlen (currpath) + 1];
+ char *name = fpath;
+ char *next;
+ // unsigned int pos = 0;
+ enum grub_fshelp_filetype type = GRUB_FSHELP_DIR;
+ grub_fshelp_node_t currnode = currroot;
+ grub_fshelp_node_t oldnode = currroot;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ auto void free_node (grub_fshelp_node_t node);
+
+ void free_node (grub_fshelp_node_t node)
+ {
+ if (node != rootnode && node != currroot)
+ grub_free (node);
+ }
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ if (filetype == GRUB_FSHELP_UNKNOWN ||
+ (grub_strcmp (name, filename) &&
+ (! (filetype & GRUB_FSHELP_CASE_INSENSITIVE) ||
+ grub_strncasecmp (name, filename, GRUB_LONG_MAX))))
+ {
+ grub_free (node);
+ return 0;
+ }
+
+ /* The node is found, stop iterating over the nodes. */
+ type = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE;
+ oldnode = currnode;
+ currnode = node;
+
+ return 1;
+ }
+
+ grub_strncpy (fpath, currpath, grub_strlen (currpath) + 1);
+
+ /* Remove all leading slashes. */
+ while (*name == '/')
+ name++;
+
+ if (! *name)
+ {
+ *currfound = currnode;
+ return 0;
+ }
+
+ for (;;)
+ {
+ int found;
+
+ /* Extract the actual part from the pathname. */
+ next = grub_strchr (name, '/');
+ if (next)
+ {
+ /* Remove all leading slashes. */
+ while (*next == '/')
+ *(next++) = '\0';
+ }
+
+ /* At this point it is expected that the current node is a
+ directory, check if this is true. */
+ if (type != GRUB_FSHELP_DIR)
+ {
+ free_node (currnode);
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ }
+
+ /* Iterate over the directory. */
+ found = iterate_dir (currnode, iterate);
+ if (! found)
+ {
+ if (grub_errno)
+ return grub_errno;
+
+ break;
+ }
+
+ /* Read in the symlink and follow it. */
+ if (type == GRUB_FSHELP_SYMLINK)
+ {
+ char *symlink;
+
+ /* Test if the symlink does not loop. */
+ if (++symlinknest == 8)
+ {
+ free_node (currnode);
+ free_node (oldnode);
+ return grub_error (GRUB_ERR_SYMLINK_LOOP,
+ "too deep nesting of symlinks");
+ }
+
+ symlink = read_symlink (currnode);
+ free_node (currnode);
+
+ if (!symlink)
+ {
+ free_node (oldnode);
+ return grub_errno;
+ }
+
+ /* The symlink is an absolute path, go back to the root inode. */
+ if (symlink[0] == '/')
+ {
+ free_node (oldnode);
+ oldnode = rootnode;
+ }
+
+ /* Lookup the node the symlink points to. */
+ find_file (symlink, oldnode, &currnode);
+ type = foundtype;
+ grub_free (symlink);
+
+ if (grub_errno)
+ {
+ free_node (oldnode);
+ return grub_errno;
+ }
+ }
+
+ free_node (oldnode);
+
+ /* Found the node! */
+ if (! next || *next == '\0')
+ {
+ *currfound = currnode;
+ foundtype = type;
+ return 0;
+ }
+
+ name = next;
+ }
+
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ }
+
+ if (!path || path[0] != '/')
+ {
+ grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
+ return grub_errno;
+ }
+
+ err = find_file (path, rootnode, foundnode);
+ if (err)
+ return err;
+
+ /* 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, "not a regular file");
+ else if (expecttype == GRUB_FSHELP_DIR && foundtype != expecttype)
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+
+ return 0;
+}
+
+/* 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. 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset,
+ unsigned length),
+ 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 i, blockcnt;
+ int blocksize = 1 << (log2blocksize + GRUB_DISK_SECTOR_BITS);
+
+ /* 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;
+
+ grub_disk_read (disk, blknr, skipfirst,
+ blockend, buf);
+ disk->read_hook = 0;
+ if (grub_errno)
+ return -1;
+ }
+ else
+ grub_memset (buf, 0, blockend);
+
+ buf += blocksize - skipfirst;
+ }
+
+ return len;
+}
+
+unsigned int
+grub_fshelp_log2blksize (unsigned int blksize, unsigned int *pow)
+{
+ int mod;
+
+ *pow = 0;
+ while (blksize > 1)
+ {
+ mod = blksize - ((blksize >> 1) << 1);
+ blksize >>= 1;
+
+ /* Check if it really is a power of two. */
+ if (mod)
+ return grub_error (GRUB_ERR_BAD_NUMBER,
+ "the blocksize is not a power of two");
+ (*pow)++;
+ }
+
+ return GRUB_ERR_NONE;
+}
diff --git a/grub-core/fs/hfs.c b/grub-core/fs/hfs.c
new file mode 100644
index 0000000..1f67ea1
--- /dev/null
+++ b/grub-core/fs/hfs.c
@@ -0,0 +1,1124 @@
+/* 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>
+
+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;
+} __attribute__ ((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];
+} __attribute__ ((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];
+} __attribute__ ((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;
+} __attribute__ ((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;
+} __attribute__ ((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[44];
+
+ /* The first 3 extents of the file. The other extents can be found
+ in the extent overflow file. */
+ grub_hfs_datarecord_t extents;
+} __attribute__ ((packed));
+
+/* A record descriptor, both key and data, used to pass to call back
+ functions. */
+struct grub_hfs_record
+{
+ void *key;
+ int keylen;
+ void *data;
+ int datalen;
+};
+
+static grub_dl_t my_mod;
+
+static int grub_hfs_find_node (struct grub_hfs_data *, char *,
+ grub_uint32_t, int, char *, int);
+
+/* 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ int i;
+ int blockcnt;
+
+ blockcnt = ((len + pos)
+ + data->blksz - 1) / data->blksz;
+
+ for (i = pos / data->blksz; i < blockcnt; i++)
+ {
+ int blknr;
+ int blockoff = pos % data->blksz;
+ int blockend = data->blksz;
+
+ int skipfirst = 0;
+
+ 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;
+ 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)
+ {
+ 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);
+
+ /* Lookup the root directory node in the catalog tree using the
+ volume name. */
+ key.parent_dir = grub_cpu_to_be32 (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 (struct grub_hfs_catalog_key *k1,
+ 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 (struct grub_hfs_extent_key *k1,
+ 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 *))
+{
+ int nodesize = type == 0 ? data->cat_size : data->ext_size;
+
+ union
+ {
+ struct grub_hfs_node node;
+ char rawnode[nodesize];
+ grub_uint16_t offsets[nodesize / 2];
+ } node;
+
+ do
+ {
+ int i;
+ struct grub_hfs_extent *dat;
+ int blk;
+
+ 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)
+ return grub_errno;
+
+ if (grub_disk_read (data->disk, blk, 0,
+ sizeof (node), &node))
+ return grub_errno;
+
+ /* Iterate over all records in this node. */
+ for (i = 0; i < grub_be_to_cpu16 (node.node.reccnt); i++)
+ {
+ int pos = (nodesize >> 1) - 1 - i;
+ struct pointer
+ {
+ grub_uint8_t keylen;
+ grub_uint8_t key;
+ } __attribute__ ((packed)) *pnt;
+ pnt = (struct pointer *) (grub_be_to_cpu16 (node.offsets[pos])
+ + node.rawnode);
+
+ struct grub_hfs_record rec =
+ {
+ &pnt->key,
+ pnt->keylen,
+ &pnt->key + pnt->keylen +(pnt->keylen + 1) % 2,
+ nodesize - grub_be_to_cpu16 (node.offsets[pos])
+ - pnt->keylen - 1
+ };
+
+ if (node_hook (&node.node, &rec))
+ return 0;
+ }
+
+ idx = grub_be_to_cpu32 (node.node.next);
+ } while (idx && this);
+
+ 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, int datalen)
+{
+ int found = -1;
+ int isleaf = 0;
+ int done = 0;
+
+ auto int node_found (struct grub_hfs_node *, struct grub_hfs_record *);
+
+ int node_found (struct grub_hfs_node *hnd, struct grub_hfs_record *rec)
+ {
+ int cmp = 1;
+
+ if (type == 0)
+ cmp = grub_hfs_cmp_catkeys (rec->key, (void *) key);
+ else
+ cmp = grub_hfs_cmp_extkeys (rec->key, (void *) 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)
+ {
+ grub_uint32_t *node = (grub_uint32_t *) rec->data;
+ found = grub_be_to_cpu32 (*node);
+ }
+ 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)
+ {
+ isleaf = 1;
+
+ /* Found it!!!! */
+ if (cmp == 0)
+ {
+ done = 1;
+
+ grub_memcpy (datar, rec->data,
+ rec->datalen < datalen ? rec->datalen : datalen);
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ do
+ {
+ found = -1;
+
+ if (grub_hfs_iterate_records (data, type, idx, 0, node_found))
+ return 0;
+
+ if (found == -1)
+ return 0;
+
+ idx = found;
+ } while (! isleaf);
+
+ return done;
+}
+
+
+/* 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,
+ unsigned int dir, int (*hook) (struct grub_hfs_record *))
+{
+ int found = -1;
+ int isleaf = 0;
+ int next = 0;
+
+ /* The lowest key possible with DIR as root directory. */
+ struct grub_hfs_catalog_key key = {0, grub_cpu_to_be32 (dir), 0, ""};
+
+ auto int node_found (struct grub_hfs_node *, struct grub_hfs_record *);
+ auto int it_dir (struct grub_hfs_node * __attribute ((unused)),
+ struct grub_hfs_record *);
+
+
+ int node_found (struct grub_hfs_node *hnd, struct grub_hfs_record *rec)
+ {
+ struct grub_hfs_catalog_key *ckey = rec->key;
+
+ if (grub_hfs_cmp_catkeys (rec->key, (void *) &key) <= 0)
+ found = grub_be_to_cpu32 (*(grub_uint32_t *) rec->data);
+
+ if (hnd->type == 0xFF && ckey->strlen > 0)
+ {
+ isleaf = 1;
+ next = grub_be_to_cpu32 (hnd->next);
+
+ /* An entry was found. */
+ if (grub_be_to_cpu32 (ckey->parent_dir) == dir)
+ return hook (rec);
+ }
+
+ return 0;
+ }
+
+ int it_dir (struct grub_hfs_node *hnd __attribute ((unused)),
+ struct grub_hfs_record *rec)
+ {
+ struct grub_hfs_catalog_key *ckey = rec->key;
+ struct grub_hfs_catalog_key *origkey = &key;
+
+ /* Stop when the entries do not match anymore. */
+ if (grub_be_to_cpu32 (ckey->parent_dir)
+ != grub_be_to_cpu32 ((origkey)->parent_dir))
+ return 1;
+
+ return hook (rec);
+ }
+
+ do
+ {
+ found = -1;
+
+ if (grub_hfs_iterate_records (data, 0, root_idx, 0, node_found))
+ return grub_errno;
+
+ if (found == -1)
+ return 0;
+
+ root_idx = found;
+ } while (! 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, next, 1, it_dir);
+ return grub_errno;
+}
+
+
+/* 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,
+ struct grub_hfs_filerec *retdata, int *retinode)
+{
+ int inode = data->rootdir;
+ char *next;
+ char *origpath;
+ union {
+ struct grub_hfs_filerec frec;
+ struct grub_hfs_dirrec dir;
+ } fdrec;
+
+ fdrec.frec.type = GRUB_HFS_FILETYPE_DIR;
+
+ if (path[0] != '/')
+ {
+ grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
+ return 0;
+ }
+
+ origpath = grub_strdup (path);
+ if (!origpath)
+ return grub_errno;
+
+ path = origpath;
+ while (*path == '/')
+ path++;
+
+ while (path && grub_strlen (path))
+ {
+ if (fdrec.frec.type != GRUB_HFS_FILETYPE_DIR)
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ goto fail;
+ }
+
+ /* Isolate a part of the path. */
+ next = grub_strchr (path, '/');
+ if (next)
+ {
+ while (*next == '/')
+ *(next++) = '\0';
+ }
+
+ struct grub_hfs_catalog_key key;
+
+ key.parent_dir = grub_cpu_to_be32 (inode);
+ key.strlen = grub_strlen (path);
+ grub_strcpy ((char *) (key.str), path);
+
+ /* Lookup this node. */
+ if (! grub_hfs_find_node (data, (char *) &key, data->cat_root,
+ 0, (char *) &fdrec.frec, sizeof (fdrec.frec)))
+ {
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ goto fail;
+ }
+
+ if (grub_errno)
+ goto fail;
+
+ inode = grub_be_to_cpu32 (fdrec.dir.dirid);
+ path = next;
+ }
+
+ if (retdata)
+ grub_memcpy (retdata, &fdrec.frec, sizeof (fdrec.frec));
+
+ if (retinode)
+ *retinode = inode;
+
+ fail:
+ grub_free (origpath);
+ return grub_errno;
+}
+
+
+
+static grub_err_t
+grub_hfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ int inode;
+
+ auto int dir_hook (struct grub_hfs_record *rec);
+
+ int dir_hook (struct grub_hfs_record *rec)
+ {
+ char fname[32] = { 0 };
+ char *filetype = rec->data;
+ struct grub_hfs_catalog_key *ckey = rec->key;
+ struct grub_dirhook_info info;
+ grub_memset (&info, 0, sizeof (info));
+
+ grub_strncpy (fname, (char *) (ckey->str), ckey->strlen);
+
+ if (*filetype == GRUB_HFS_FILETYPE_DIR
+ || *filetype == GRUB_HFS_FILETYPE_FILE)
+ {
+ info.dir = (*filetype == GRUB_HFS_FILETYPE_DIR);
+ return hook (fname, &info);
+ }
+ return 0;
+ }
+
+ struct grub_hfs_data *data;
+ struct grub_hfs_filerec frec;
+
+ 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, &frec, &inode))
+ goto fail;
+
+ if (frec.type != GRUB_HFS_FILETYPE_DIR)
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ goto fail;
+ }
+
+ grub_hfs_iterate_dir (data, data->cat_root, inode, dir_hook);
+
+ 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_hfs_open (struct grub_file *file, const char *name)
+{
+ struct grub_hfs_data *data;
+ struct grub_hfs_filerec frec;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_hfs_mount (file->device->disk);
+
+ if (grub_hfs_find_dir (data, name, &frec, 0))
+ {
+ grub_free (data);
+ grub_dl_unref (my_mod);
+ return grub_errno;
+ }
+
+ if (frec.type != GRUB_HFS_FILETYPE_FILE)
+ {
+ grub_free (data);
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a file");
+ grub_dl_unref (my_mod);
+ return grub_errno;
+ }
+
+ grub_memcpy (data->extents, frec.extents, sizeof (grub_hfs_datarecord_t));
+ file->size = grub_be_to_cpu32 (frec.size);
+ data->size = grub_be_to_cpu32 (frec.size);
+ data->fileid = grub_be_to_cpu32 (frec.fileid);
+ file->offset = 0;
+
+ file->data = data;
+
+ 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->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)
+ *label = grub_strndup ((char *) (data->sblock.volname + 1),
+ *data->sblock.volname);
+ else
+ *label = 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",
+ .dir = grub_hfs_dir,
+ .open = grub_hfs_open,
+ .read = grub_hfs_read,
+ .close = grub_hfs_close,
+ .label = grub_hfs_label,
+ .uuid = grub_hfs_uuid,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(hfs)
+{
+ grub_fs_register (&grub_hfs_fs);
+ my_mod = mod;
+}
+
+GRUB_MOD_FINI(hfs)
+{
+ 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..304b321
--- /dev/null
+++ b/grub-core/fs/hfsplus.c
@@ -0,0 +1,1050 @@
+/* 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 */
+
+#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>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define GRUB_HFSPLUS_MAGIC 0x482B
+#define GRUB_HFSPLUSX_MAGIC 0x4858
+#define GRUB_HFSPLUS_SBLOCK 2
+
+/* A HFS+ extent. */
+struct grub_hfsplus_extent
+{
+ /* The first block of a file on disk. */
+ grub_uint32_t start;
+ /* The amount of blocks described by this extent. */
+ grub_uint32_t count;
+} __attribute__ ((packed));
+
+/* The descriptor of a fork. */
+struct grub_hfsplus_forkdata
+{
+ grub_uint64_t size;
+ grub_uint32_t clumpsize;
+ grub_uint32_t blocks;
+ struct grub_hfsplus_extent extents[8];
+} __attribute__ ((packed));
+
+/* The HFS+ Volume Header. */
+struct grub_hfsplus_volheader
+{
+ grub_uint16_t magic;
+ grub_uint16_t version;
+ grub_uint32_t attributes;
+ grub_uint8_t unused1[12];
+ grub_uint32_t utime;
+ grub_uint8_t unused2[16];
+ grub_uint32_t blksize;
+ grub_uint8_t unused3[60];
+ grub_uint64_t num_serial;
+ struct grub_hfsplus_forkdata allocations_file;
+ struct grub_hfsplus_forkdata extents_file;
+ struct grub_hfsplus_forkdata catalog_file;
+ struct grub_hfsplus_forkdata attrib_file;
+ struct grub_hfsplus_forkdata startup_file;
+} __attribute__ ((packed));
+
+/* 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,
+ };
+
+struct grub_hfsplus_btnode
+{
+ grub_uint32_t next;
+ grub_uint32_t prev;
+ grub_int8_t type;
+ grub_uint8_t height;
+ grub_uint16_t count;
+ grub_uint16_t unused;
+} __attribute__ ((packed));
+
+/* 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;
+} __attribute__ ((packed));
+
+/* The on disk layout of a catalog key. */
+struct grub_hfsplus_catkey
+{
+ grub_uint16_t keylen;
+ grub_uint32_t parent;
+ grub_uint16_t namelen;
+ grub_uint16_t name[30];
+} __attribute__ ((packed));
+
+/* The on disk layout of an extent overflow file key. */
+struct grub_hfsplus_extkey
+{
+ grub_uint16_t keylen;
+ grub_uint8_t type;
+ grub_uint8_t unused;
+ grub_uint32_t fileid;
+ grub_uint32_t start;
+} __attribute__ ((packed));
+
+struct grub_hfsplus_key
+{
+ union
+ {
+ struct grub_hfsplus_extkey extkey;
+ struct grub_hfsplus_catkey catkey;
+ grub_uint16_t keylen;
+ };
+} __attribute__ ((packed));
+
+struct grub_hfsplus_catfile
+{
+ grub_uint16_t type;
+ grub_uint16_t flags;
+ grub_uint32_t reserved;
+ 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;
+} __attribute__ ((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. */
+#define GRUB_HFSPLUS_FILEID_ROOTDIR 2
+#define GRUB_HFSPLUS_FILEID_OVERFLOW 3
+#define GRUB_HFSPLUS_FILEID_CATALOG 4
+
+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
+
+/* Internal representation of a catalog key. */
+struct grub_hfsplus_catkey_internal
+{
+ grub_uint32_t parent;
+ char *name;
+};
+
+/* Internal representation of an extent overflow key. */
+struct grub_hfsplus_extkey_internal
+{
+ grub_uint32_t fileid;
+ grub_uint32_t start;
+};
+
+struct grub_hfsplus_key_internal
+{
+ union
+ {
+ struct grub_hfsplus_extkey_internal extkey;
+ struct grub_hfsplus_catkey_internal catkey;
+ };
+};
+
+
+
+struct grub_fshelp_node
+{
+ struct grub_hfsplus_data *data;
+ struct grub_hfsplus_extent extents[8];
+ grub_uint64_t size;
+ grub_uint32_t fileid;
+ grub_int32_t mtime;
+};
+
+struct grub_hfsplus_btree
+{
+ grub_uint32_t root;
+ int nodesize;
+
+ /* Catalog file node. */
+ struct grub_fshelp_node file;
+};
+
+/* Information about a "mounted" HFS+ filesystem. */
+struct grub_hfsplus_data
+{
+ struct grub_hfsplus_volheader volheader;
+ grub_disk_t disk;
+
+ unsigned int log2blksize;
+
+ struct grub_hfsplus_btree catalog_tree;
+ struct grub_hfsplus_btree extoverflow_tree;
+
+ struct grub_fshelp_node dirroot;
+ struct grub_fshelp_node opened_file;
+
+ /* This is the offset into the physical disk for an embedded HFS+
+ filesystem (one inside a plain HFS wrapper). */
+ int embedded_offset;
+ int case_sensitive;
+};
+
+static grub_dl_t my_mod;
+
+
+/* Return the offset of the record with the index INDEX, in the node
+ NODE which is part of the B+ tree BTREE. */
+static inline unsigned int
+grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree,
+ struct grub_hfsplus_btnode *node, int index)
+{
+ char *cnode = (char *) node;
+ grub_uint16_t *recptr;
+ recptr = (grub_uint16_t *) (&cnode[btree->nodesize
+ - index * sizeof (grub_uint16_t) - 2]);
+ return grub_be_to_cpu16 (*recptr);
+}
+
+/* Return a pointer to the record with the index INDEX, in the node
+ NODE which is part of the B+ tree BTREE. */
+static inline struct grub_hfsplus_key *
+grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree,
+ struct grub_hfsplus_btnode *node, int index)
+{
+ char *cnode = (char *) node;
+ unsigned int offset;
+ offset = grub_hfsplus_btree_recoffset (btree, node, index);
+ return (struct grub_hfsplus_key *) &cnode[offset];
+}
+
+
+/* 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 int
+grub_hfsplus_find_block (struct grub_hfsplus_extent *extent,
+ int *fileblock)
+{
+ int i;
+ grub_size_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 -1;
+}
+
+static 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, int *keyoffset);
+
+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;
+ int blksleft = fileblock;
+ struct grub_hfsplus_extent *extents = &node->extents[0];
+
+ while (1)
+ {
+ struct grub_hfsplus_extkey *key;
+ struct grub_hfsplus_extkey_internal extoverflow;
+ int blk;
+ int 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 != -1)
+ return (blk
+ + (node->data->embedded_offset >> (node->data->log2blksize
+ - GRUB_DISK_SECTOR_BITS)));
+
+ /* 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;
+ }
+
+ /* Set up the key to look for in the extent overflow file. */
+ extoverflow.fileid = node->fileid;
+ extoverflow.start = fileblock - blksleft;
+
+ if (grub_hfsplus_btree_search (&node->data->extoverflow_tree,
+ (struct grub_hfsplus_key_internal *) &extoverflow,
+ grub_hfsplus_cmp_extkey, &nnode, &ptr))
+ {
+ grub_error (GRUB_ERR_READ_ERROR,
+ "no block found for the file id 0x%x and the block offset 0x%x",
+ 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. */
+static grub_ssize_t
+grub_hfsplus_read_file (grub_fshelp_node_t node,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ return grub_fshelp_read_file (node->data->disk, node, read_hook,
+ pos, len, buf, grub_hfsplus_read_block,
+ node->size,
+ node->data->log2blksize - GRUB_DISK_SECTOR_BITS);
+}
+
+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;
+
+ /* 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)
+ {
+ int extent_start;
+ int ablk_size;
+ int 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))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
+ goto fail;
+ }
+
+ grub_memcpy (&data->volheader, &volheader.hfsplus,
+ sizeof (volheader.hfsplus));
+
+ if (grub_fshelp_log2blksize (grub_be_to_cpu32 (data->volheader.blksize),
+ &data->log2blksize))
+ goto fail;
+
+ /* Make a new node for the catalog tree. */
+ data->catalog_tree.file.data = data;
+ data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG;
+ 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);
+
+ /* Make a new node for the extent overflow file. */
+ data->extoverflow_tree.file.data = data;
+ data->extoverflow_tree.file.fileid = GRUB_HFSPLUS_FILEID_OVERFLOW;
+ 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,
+ 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 (grub_hfsplus_read_file (&data->extoverflow_tree.file, 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,
+ 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);
+
+ 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;
+ char *filename;
+ int i;
+ int diff;
+
+ /* 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;
+
+ /* Change the filename in keya so the endianness is correct. */
+ for (i = 0; i < grub_be_to_cpu16 (catkey_a->namelen); i++)
+ catkey_a->name[i] = grub_be_to_cpu16 (catkey_a->name[i]);
+
+ filename = grub_malloc (grub_be_to_cpu16 (catkey_a->namelen) + 1);
+
+ if (! grub_utf16_to_utf8 ((grub_uint8_t *) filename, catkey_a->name,
+ grub_be_to_cpu16 (catkey_a->namelen)))
+ return -1; /* XXX: This error never occurs, but in case it happens
+ just skip this entry. */
+
+ diff = grub_strncmp (filename, catkey_b->name,
+ grub_be_to_cpu16 (catkey_a->namelen));
+
+ grub_free (filename);
+
+ /* The endianness was changed to host format, change it back to
+ whatever it was. */
+ for (i = 0; i < grub_be_to_cpu16 (catkey_a->namelen); i++)
+ catkey_a->name[i] = grub_cpu_to_be16 (catkey_a->name[i]);
+ return diff;
+}
+
+/* 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;
+
+ 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;
+
+ symlink = grub_malloc (node->size + 1);
+ if (!symlink)
+ return 0;
+
+ numread = grub_hfsplus_read_file (node, 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,
+ int first_rec,
+ int (*hook) (void *record))
+{
+ int rec;
+
+ 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)))
+ return 1;
+ }
+
+ if (! first_node->next)
+ break;
+
+ if (grub_hfsplus_read_file (&btree->file, 0,
+ (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. */
+static 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, int *keyoffset)
+{
+ grub_uint64_t currnode;
+ char *node;
+ struct grub_hfsplus_btnode *nodedesc;
+ int rec;
+
+ node = grub_malloc (btree->nodesize);
+ if (! node)
+ return grub_errno;
+
+ currnode = btree->root;
+ while (1)
+ {
+ int match = 0;
+
+ /* Read a node. */
+ if (grub_hfsplus_read_file (&btree->file, 0,
+ (long)currnode * (long)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)
+ {
+ grub_uint32_t *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 = (grub_uint32_t *) ((char *) currkey
+ + grub_be_to_cpu16 (currkey->keylen)
+ + 2);
+ currnode = grub_be_to_cpu32 (*pointer);
+ match = 1;
+ }
+ }
+
+ /* No match is found, no record with this key exists in the
+ tree. */
+ if (! match)
+ {
+ *matchnode = 0;
+ grub_free (node);
+ return 1;
+ }
+ }
+}
+
+static int
+grub_hfsplus_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ int ret = 0;
+
+ auto int list_nodes (void *record);
+ int list_nodes (void *record)
+ {
+ struct grub_hfsplus_catkey *catkey;
+ char *filename;
+ int i;
+ struct grub_fshelp_node *node;
+ struct grub_hfsplus_catfile *fileinfo;
+ enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;
+
+ 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) != dir->fileid)
+ return 1;
+
+ /* Determine the type of the node that is found. */
+ if (grub_be_to_cpu16 (fileinfo->type) == 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;
+ }
+ else if (grub_be_to_cpu16 (fileinfo->type) == GRUB_HFSPLUS_FILETYPE_DIR)
+ type = GRUB_FSHELP_DIR;
+
+ if (type == GRUB_FSHELP_UNKNOWN)
+ return 0;
+
+ /* Make sure the byte order of the UTF16 string is correct. */
+ for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
+ {
+ catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
+
+ /* If the name is obviously invalid, skip this node. */
+ if (catkey->name[i] == 0)
+ return 0;
+ }
+
+ filename = grub_malloc (grub_be_to_cpu16 (catkey->namelen) + 1);
+ if (! filename)
+ return 0;
+
+ if (! grub_utf16_to_utf8 ((grub_uint8_t *) filename, catkey->name,
+ grub_be_to_cpu16 (catkey->namelen)))
+ {
+ grub_free (filename);
+ return 0;
+ }
+
+ filename[grub_be_to_cpu16 (catkey->namelen)] = '\0';
+
+ /* Restore the byte order to what it was previously. */
+ for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
+ catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
+
+ /* hfs+ is case insensitive. */
+ if (! dir->data->case_sensitive)
+ type |= GRUB_FSHELP_CASE_INSENSITIVE;
+
+ /* Only accept valid nodes. */
+ if (grub_strlen (filename) == grub_be_to_cpu16 (catkey->namelen))
+ {
+ /* A valid node is found; setup the node and call the
+ callback function. */
+ node = grub_malloc (sizeof (*node));
+ node->data = dir->data;
+
+ grub_memcpy (node->extents, fileinfo->data.extents,
+ sizeof (node->extents));
+ node->mtime = grub_be_to_cpu32 (fileinfo->mtime) - 2082844800;
+ node->size = grub_be_to_cpu64 (fileinfo->data.size);
+ node->fileid = grub_be_to_cpu32 (fileinfo->fileid);
+
+ ret = hook (filename, type, node);
+ }
+
+ grub_free (filename);
+
+ return ret;
+ }
+
+ struct grub_hfsplus_key_internal intern;
+ struct grub_hfsplus_btnode *node;
+ int ptr;
+
+ /* Create a key that points to the first entry in the directory. */
+ intern.catkey.parent = dir->fileid;
+ intern.catkey.name = "";
+
+ /* First lookup the first entry. */
+ if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern,
+ grub_hfsplus_cmp_catkey, &node, &ptr))
+ return 0;
+
+ /* Iterate over all entries in this directory. */
+ grub_hfsplus_btree_iterate_node (&dir->data->catalog_tree, node, ptr,
+ list_nodes);
+
+ grub_free (node);
+
+ return 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;
+
+ 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)
+{
+ 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_hfsplus_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ struct grub_hfsplus_data *data =
+ (struct grub_hfsplus_data *) file->data;
+
+ int size = grub_hfsplus_read_file (&data->opened_file, file->read_hook,
+ file->offset, len, buf);
+
+ return size;
+}
+
+
+static grub_err_t
+grub_hfsplus_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_hfsplus_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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.case_insensitive = !! (filetype & GRUB_FSHELP_CASE_INSENSITIVE);
+ grub_free (node);
+ return hook (filename, &info);
+ }
+
+ 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, iterate);
+
+ 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 __attribute__((unused))
+ , char **label __attribute__((unused)))
+{
+ /* XXX: It's not documented how to read a label. */
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "reading the label of a HFS+ "
+ "partition is not implemented");
+}
+
+/* Get mtime. */
+static grub_err_t
+grub_hfsplus_mtime (grub_device_t device, grub_int32_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",
+ .dir = grub_hfsplus_dir,
+ .open = grub_hfsplus_open,
+ .read = grub_hfsplus_read,
+ .close = grub_hfsplus_close,
+ .label = grub_hfsplus_label,
+ .mtime = grub_hfsplus_mtime,
+ .uuid = grub_hfsplus_uuid,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 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/i386/pc/pxe.c b/grub-core/fs/i386/pc/pxe.c
new file mode 100644
index 0000000..4304881
--- /dev/null
+++ b/grub-core/fs/i386/pc/pxe.c
@@ -0,0 +1,652 @@
+/* pxe.c - Driver to provide access to the pxe 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/dl.h>
+#include <grub/fs.h>
+#include <grub/mm.h>
+#include <grub/disk.h>
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/bufio.h>
+#include <grub/env.h>
+
+#include <grub/machine/pxe.h>
+#include <grub/machine/int.h>
+#include <grub/machine/memory.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define SEGMENT(x) ((x) >> 4)
+#define OFFSET(x) ((x) & 0xF)
+#define SEGOFS(x) ((SEGMENT(x) << 16) + OFFSET(x))
+#define LINEAR(x) (void *) (((x >> 16) <<4) + (x & 0xFFFF))
+
+struct grub_pxe_disk_data
+{
+ grub_uint32_t server_ip;
+ grub_uint32_t gateway_ip;
+};
+
+struct grub_pxe_bangpxe *grub_pxe_pxenv;
+static grub_uint32_t grub_pxe_your_ip;
+static grub_uint32_t grub_pxe_default_server_ip;
+static grub_uint32_t grub_pxe_default_gateway_ip;
+static unsigned grub_pxe_blksize = GRUB_PXE_MIN_BLKSIZE;
+
+static grub_file_t curr_file = 0;
+
+struct grub_pxe_data
+{
+ grub_uint32_t packet_number;
+ grub_uint32_t block_size;
+ char filename[0];
+};
+
+static grub_uint32_t pxe_rm_entry = 0;
+
+static struct grub_pxe_bangpxe *
+grub_pxe_scan (void)
+{
+ struct grub_bios_int_registers regs;
+ struct grub_pxenv *pxenv;
+ struct grub_pxe_bangpxe *bangpxe;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ regs.ebx = 0;
+ regs.ecx = 0;
+ regs.eax = 0x5650;
+ regs.es = 0;
+
+ grub_bios_interrupt (0x1a, &regs);
+
+ if ((regs.eax & 0xffff) != 0x564e)
+ return NULL;
+
+ pxenv = (struct grub_pxenv *) ((regs.es << 4) + (regs.ebx & 0xffff));
+ if (grub_memcmp (pxenv->signature, GRUB_PXE_SIGNATURE,
+ sizeof (pxenv->signature))
+ != 0)
+ return NULL;
+
+ if (pxenv->version < 0x201)
+ return NULL;
+
+ bangpxe = (void *) ((((pxenv->pxe_ptr & 0xffff0000) >> 16) << 4)
+ + (pxenv->pxe_ptr & 0xffff));
+
+ if (!bangpxe)
+ return NULL;
+
+ if (grub_memcmp (bangpxe->signature, GRUB_PXE_BANGPXE_SIGNATURE,
+ sizeof (bangpxe->signature)) != 0)
+ return NULL;
+
+ pxe_rm_entry = bangpxe->rm_entry;
+
+ return bangpxe;
+}
+
+static int
+grub_pxe_iterate (int (*hook) (const char *name))
+{
+ if (hook ("pxe"))
+ return 1;
+ return 0;
+}
+
+static grub_err_t
+parse_ip (const char *val, grub_uint32_t *ip, const char **rest)
+{
+ grub_uint32_t newip = 0;
+ unsigned long t;
+ int i;
+ const char *ptr = val;
+
+ for (i = 0; i < 4; i++)
+ {
+ t = grub_strtoul (ptr, (char **) &ptr, 0);
+ if (grub_errno)
+ return grub_errno;
+ if (t & ~0xff)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, "Invalid IP.");
+ newip >>= 8;
+ newip |= (t << 24);
+ if (i != 3 && *ptr != '.')
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, "Invalid IP.");
+ ptr++;
+ }
+ *ip = newip;
+ if (rest)
+ *rest = ptr - 1;
+ return 0;
+}
+
+static grub_err_t
+grub_pxe_open (const char *name, grub_disk_t disk)
+{
+ struct grub_pxe_disk_data *data;
+
+ if (grub_strcmp (name, "pxe") != 0
+ && grub_strncmp (name, "pxe:", sizeof ("pxe:") - 1) != 0)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a pxe disk");
+
+ data = grub_malloc (sizeof (*data));
+ if (!data)
+ return grub_errno;
+
+ if (grub_strncmp (name, "pxe:", sizeof ("pxe:") - 1) == 0)
+ {
+ const char *ptr;
+ grub_err_t err;
+
+ ptr = name + sizeof ("pxe:") - 1;
+ err = parse_ip (ptr, &(data->server_ip), &ptr);
+ if (err)
+ return err;
+ if (*ptr == ':')
+ {
+ err = parse_ip (ptr + 1, &(data->gateway_ip), 0);
+ if (err)
+ return err;
+ }
+ else
+ data->gateway_ip = grub_pxe_default_gateway_ip;
+ }
+ else
+ {
+ data->server_ip = grub_pxe_default_server_ip;
+ data->gateway_ip = grub_pxe_default_gateway_ip;
+ }
+
+ disk->total_sectors = 0;
+ disk->id = (unsigned long) data;
+
+ disk->data = data;
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_pxe_close (grub_disk_t disk)
+{
+ grub_free (disk->data);
+}
+
+static grub_err_t
+grub_pxe_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_pxe_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;
+}
+
+static struct grub_disk_dev grub_pxe_dev =
+ {
+ .name = "pxe",
+ .id = GRUB_DISK_DEVICE_PXE_ID,
+ .iterate = grub_pxe_iterate,
+ .open = grub_pxe_open,
+ .close = grub_pxe_close,
+ .read = grub_pxe_read,
+ .write = grub_pxe_write,
+ .next = 0
+ };
+
+static grub_err_t
+grub_pxefs_dir (grub_device_t device,
+ const char *path __attribute__ ((unused)),
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info)
+ __attribute__ ((unused)))
+{
+ if (device->disk->dev->id != GRUB_DISK_DEVICE_PXE_ID)
+ return grub_error (GRUB_ERR_BAD_FS, "not a pxe disk");
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_pxefs_open (struct grub_file *file, const char *name)
+{
+ union
+ {
+ struct grub_pxenv_tftp_get_fsize c1;
+ struct grub_pxenv_tftp_open c2;
+ } c;
+ struct grub_pxe_data *data;
+ struct grub_pxe_disk_data *disk_data = file->device->disk->data;
+ grub_file_t file_int, bufio;
+
+ if (file->device->disk->dev->id != GRUB_DISK_DEVICE_PXE_ID)
+ return grub_error (GRUB_ERR_BAD_FS, "not a pxe disk");
+
+ if (curr_file != 0)
+ {
+ grub_pxe_call (GRUB_PXENV_TFTP_CLOSE, &c.c2, pxe_rm_entry);
+ curr_file = 0;
+ }
+
+ c.c1.server_ip = disk_data->server_ip;
+ c.c1.gateway_ip = disk_data->gateway_ip;
+ grub_strcpy ((char *)&c.c1.filename[0], name);
+ grub_pxe_call (GRUB_PXENV_TFTP_GET_FSIZE, &c.c1, pxe_rm_entry);
+ if (c.c1.status)
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+
+ file->size = c.c1.file_size;
+
+ c.c2.tftp_port = grub_cpu_to_be16 (GRUB_PXE_TFTP_PORT);
+ c.c2.packet_size = grub_pxe_blksize;
+ grub_pxe_call (GRUB_PXENV_TFTP_OPEN, &c.c2, pxe_rm_entry);
+ if (c.c2.status)
+ return grub_error (GRUB_ERR_BAD_FS, "open fails");
+
+ data = grub_zalloc (sizeof (struct grub_pxe_data) + grub_strlen (name) + 1);
+ if (! data)
+ return grub_errno;
+
+ data->block_size = c.c2.packet_size;
+ grub_strcpy (data->filename, name);
+
+ file_int = grub_malloc (sizeof (*file_int));
+ if (! file_int)
+ {
+ grub_free (data);
+ return grub_errno;
+ }
+
+ file->data = data;
+ file->not_easily_seekable = 1;
+ grub_memcpy (file_int, file, sizeof (struct grub_file));
+ curr_file = file_int;
+
+ bufio = grub_bufio_open (file_int, data->block_size);
+ if (! bufio)
+ {
+ grub_free (file_int);
+ grub_free (data);
+ return grub_errno;
+ }
+
+ grub_memcpy (file, bufio, sizeof (struct grub_file));
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_ssize_t
+grub_pxefs_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ struct grub_pxenv_tftp_read c;
+ struct grub_pxe_data *data;
+ struct grub_pxe_disk_data *disk_data = file->device->disk->data;
+ grub_uint32_t pn, r;
+
+ data = file->data;
+
+ pn = grub_divmod64 (file->offset, data->block_size, &r);
+ if (r)
+ {
+ grub_error (GRUB_ERR_BAD_FS,
+ "read access must be aligned to packet size");
+ return -1;
+ }
+
+ if ((curr_file != file) || (data->packet_number > pn))
+ {
+ struct grub_pxenv_tftp_open o;
+
+ if (curr_file != 0)
+ grub_pxe_call (GRUB_PXENV_TFTP_CLOSE, &o, pxe_rm_entry);
+
+ o.server_ip = disk_data->server_ip;
+ o.gateway_ip = disk_data->gateway_ip;
+ grub_strcpy ((char *)&o.filename[0], data->filename);
+ o.tftp_port = grub_cpu_to_be16 (GRUB_PXE_TFTP_PORT);
+ o.packet_size = data->block_size;
+ grub_pxe_call (GRUB_PXENV_TFTP_OPEN, &o, pxe_rm_entry);
+ if (o.status)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "open fails");
+ return -1;
+ }
+ data->block_size = o.packet_size;
+ data->packet_number = 0;
+ curr_file = file;
+ }
+
+ c.buffer = SEGOFS (GRUB_MEMORY_MACHINE_SCRATCH_ADDR);
+ while (pn >= data->packet_number)
+ {
+ c.buffer_size = data->block_size;
+ grub_pxe_call (GRUB_PXENV_TFTP_READ, &c, pxe_rm_entry);
+ if (c.status)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "read fails");
+ return -1;
+ }
+ data->packet_number++;
+ }
+
+ grub_memcpy (buf, (char *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, len);
+
+ return len;
+}
+
+static grub_err_t
+grub_pxefs_close (grub_file_t file)
+{
+ struct grub_pxenv_tftp_close c;
+
+ if (curr_file == file)
+ {
+ grub_pxe_call (GRUB_PXENV_TFTP_CLOSE, &c, pxe_rm_entry);
+ curr_file = 0;
+ }
+
+ grub_free (file->data);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_pxefs_label (grub_device_t device __attribute ((unused)),
+ char **label __attribute ((unused)))
+{
+ *label = 0;
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_fs grub_pxefs_fs =
+ {
+ .name = "pxefs",
+ .dir = grub_pxefs_dir,
+ .open = grub_pxefs_open,
+ .read = grub_pxefs_read,
+ .close = grub_pxefs_close,
+ .label = grub_pxefs_label,
+ .next = 0
+ };
+
+static char *
+grub_env_write_readonly (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val __attribute__ ((unused)))
+{
+ return NULL;
+}
+
+static void
+set_mac_env (grub_uint8_t *mac_addr, grub_size_t mac_len)
+{
+ char buf[(sizeof ("XX:") - 1) * mac_len + 1];
+ char *ptr = buf;
+ unsigned i;
+
+ for (i = 0; i < mac_len; i++)
+ {
+ grub_snprintf (ptr, sizeof (buf) - (ptr - buf),
+ "%02x:", mac_addr[i] & 0xff);
+ ptr += (sizeof ("XX:") - 1);
+ }
+ if (mac_len)
+ *(ptr - 1) = 0;
+ else
+ buf[0] = 0;
+
+ grub_env_set ("net_pxe_mac", buf);
+ /* XXX: Is it possible to change MAC in PXE? */
+ grub_register_variable_hook ("net_pxe_mac", 0, grub_env_write_readonly);
+ grub_env_export ("net_pxe_mac");
+}
+
+static void
+set_env_limn_ro (const char *varname, char *value, grub_size_t len)
+{
+ char c;
+ c = value[len];
+ value[len] = 0;
+ grub_env_set (varname, value);
+ value[len] = c;
+ grub_register_variable_hook (varname, 0, grub_env_write_readonly);
+ grub_env_export (varname);
+}
+
+static void
+parse_dhcp_vendor (void *vend, int limit)
+{
+ grub_uint8_t *ptr, *ptr0;
+
+ ptr = ptr0 = vend;
+
+ if (grub_be_to_cpu32 (*(grub_uint32_t *) ptr) != 0x63825363)
+ return;
+ ptr = ptr + sizeof (grub_uint32_t);
+ while (ptr - ptr0 < limit)
+ {
+ grub_uint8_t tagtype;
+ grub_uint8_t taglength;
+
+ tagtype = *ptr++;
+
+ /* Pad tag. */
+ if (tagtype == 0)
+ continue;
+
+ /* End tag. */
+ if (tagtype == 0xff)
+ return;
+
+ taglength = *ptr++;
+
+ switch (tagtype)
+ {
+ case 12:
+ set_env_limn_ro ("net_pxe_hostname", (char *) ptr, taglength);
+ break;
+
+ case 15:
+ set_env_limn_ro ("net_pxe_domain", (char *) ptr, taglength);
+ break;
+
+ case 17:
+ set_env_limn_ro ("net_pxe_rootpath", (char *) ptr, taglength);
+ break;
+
+ case 18:
+ set_env_limn_ro ("net_pxe_extensionspath", (char *) ptr, taglength);
+ break;
+
+ /* If you need any other options please contact GRUB
+ development team. */
+ }
+
+ ptr += taglength;
+ }
+}
+
+static void
+grub_pxe_detect (void)
+{
+ struct grub_pxe_bangpxe *pxenv;
+ struct grub_pxenv_get_cached_info ci;
+ struct grub_pxenv_boot_player *bp;
+
+ pxenv = grub_pxe_scan ();
+ if (! pxenv)
+ return;
+
+ ci.packet_type = GRUB_PXENV_PACKET_TYPE_DHCP_ACK;
+ ci.buffer = 0;
+ ci.buffer_size = 0;
+ grub_pxe_call (GRUB_PXENV_GET_CACHED_INFO, &ci, pxe_rm_entry);
+ if (ci.status)
+ return;
+
+ bp = LINEAR (ci.buffer);
+
+ grub_pxe_your_ip = bp->your_ip;
+ grub_pxe_default_server_ip = bp->server_ip;
+ grub_pxe_default_gateway_ip = bp->gateway_ip;
+ set_mac_env (bp->mac_addr, bp->hw_len < sizeof (bp->mac_addr) ? bp->hw_len
+ : sizeof (bp->mac_addr));
+ set_env_limn_ro ("net_pxe_boot_file", (char *) bp->boot_file,
+ sizeof (bp->boot_file));
+ set_env_limn_ro ("net_pxe_dhcp_server_name", (char *) bp->server_name,
+ sizeof (bp->server_name));
+ parse_dhcp_vendor (&bp->vendor, sizeof (bp->vendor));
+ grub_pxe_pxenv = pxenv;
+}
+
+void
+grub_pxe_unload (void)
+{
+ if (grub_pxe_pxenv)
+ {
+ grub_fs_unregister (&grub_pxefs_fs);
+ grub_disk_dev_unregister (&grub_pxe_dev);
+
+ grub_pxe_pxenv = 0;
+ }
+}
+
+static void
+set_ip_env (char *varname, grub_uint32_t ip)
+{
+ char buf[sizeof ("XXX.XXX.XXX.XXX")];
+
+ grub_snprintf (buf, sizeof (buf), "%d.%d.%d.%d", (ip & 0xff),
+ (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff);
+ grub_env_set (varname, buf);
+}
+
+static char *
+write_ip_env (grub_uint32_t *ip, const char *val)
+{
+ char *buf;
+ grub_err_t err;
+ grub_uint32_t newip;
+
+ err = parse_ip (val, &newip, 0);
+ if (err)
+ return 0;
+
+ /* Normalize the IP. */
+ buf = grub_xasprintf ("%d.%d.%d.%d", (newip & 0xff), (newip >> 8) & 0xff,
+ (newip >> 16) & 0xff, (newip >> 24) & 0xff);
+ if (!buf)
+ return 0;
+
+ *ip = newip;
+
+ return buf;
+}
+
+static char *
+grub_env_write_pxe_default_server (struct grub_env_var *var
+ __attribute__ ((unused)),
+ const char *val)
+{
+ return write_ip_env (&grub_pxe_default_server_ip, val);
+}
+
+static char *
+grub_env_write_pxe_default_gateway (struct grub_env_var *var
+ __attribute__ ((unused)),
+ const char *val)
+{
+ return write_ip_env (&grub_pxe_default_gateway_ip, val);
+}
+
+static char *
+grub_env_write_pxe_blocksize (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val)
+{
+ unsigned size;
+ char *buf;
+
+ size = grub_strtoul (val, 0, 0);
+ if (grub_errno)
+ return 0;
+
+ if (size < GRUB_PXE_MIN_BLKSIZE)
+ size = GRUB_PXE_MIN_BLKSIZE;
+ else if (size > GRUB_PXE_MAX_BLKSIZE)
+ size = GRUB_PXE_MAX_BLKSIZE;
+
+ buf = grub_xasprintf ("%d", size);
+ if (!buf)
+ return 0;
+
+ grub_pxe_blksize = size;
+
+ return buf;
+}
+
+
+GRUB_MOD_INIT(pxe)
+{
+ grub_pxe_detect ();
+ if (grub_pxe_pxenv)
+ {
+ char *buf;
+
+ buf = grub_xasprintf ("%d", grub_pxe_blksize);
+ if (buf)
+ grub_env_set ("pxe_blksize", buf);
+ grub_free (buf);
+
+ set_ip_env ("pxe_default_server", grub_pxe_default_server_ip);
+ set_ip_env ("pxe_default_gateway", grub_pxe_default_gateway_ip);
+ set_ip_env ("net_pxe_ip", grub_pxe_your_ip);
+ grub_register_variable_hook ("pxe_default_server", 0,
+ grub_env_write_pxe_default_server);
+ grub_register_variable_hook ("pxe_default_gateway", 0,
+ grub_env_write_pxe_default_gateway);
+
+
+ /* XXX: Is it possible to change IP in PXE? */
+ grub_register_variable_hook ("net_pxe_ip", 0,
+ grub_env_write_readonly);
+ grub_register_variable_hook ("pxe_blksize", 0,
+ grub_env_write_pxe_blocksize);
+
+ grub_env_export ("pxe_default_server");
+ grub_env_export ("pxe_default_gateway");
+ grub_env_export ("net_pxe_ip");
+ grub_env_export ("pxe_blksize");
+
+ grub_disk_dev_register (&grub_pxe_dev);
+ grub_fs_register (&grub_pxefs_fs);
+ }
+}
+
+GRUB_MOD_FINI(pxe)
+{
+ grub_pxe_unload ();
+}
diff --git a/grub-core/fs/iso9660.c b/grub-core/fs/iso9660.c
new file mode 100644
index 0000000..a9a17fe
--- /dev/null
+++ b/grub-core/fs/iso9660.c
@@ -0,0 +1,908 @@
+/* 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>
+
+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;
+} __attribute__ ((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;
+ grub_uint8_t unused1[7];
+ grub_uint8_t flags;
+ grub_uint8_t unused2[6];
+ grub_uint8_t namelen;
+} __attribute__ ((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;
+} __attribute__ ((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;
+} __attribute__ ((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];
+} __attribute__ ((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];
+} __attribute__ ((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;
+} __attribute__ ((packed));
+
+struct grub_iso9660_data
+{
+ struct grub_iso9660_primary_voldesc voldesc;
+ grub_disk_t disk;
+ unsigned int first_sector;
+ int rockridge;
+ int susp_skip;
+ int joliet;
+};
+
+struct grub_fshelp_node
+{
+ struct grub_iso9660_data *data;
+ unsigned int size;
+ unsigned int blk;
+ unsigned int dir_blk;
+ unsigned int dir_off;
+};
+
+static grub_dl_t my_mod;
+
+
+/* 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 (struct grub_iso9660_data *data,
+ int sua_block, int sua_pos, int sua_size,
+ grub_err_t (*hook)
+ (struct grub_iso9660_susp_entry *entry))
+{
+ char *sua;
+ struct grub_iso9660_susp_entry *entry;
+
+ auto grub_err_t load_sua (void);
+
+ /* Load a part of the System Usage Area. */
+ grub_err_t load_sua (void)
+ {
+ sua = grub_malloc (sua_size);
+ if (!sua)
+ return grub_errno;
+
+ if (grub_disk_read (data->disk, sua_block, sua_pos,
+ sua_size, sua))
+ return grub_errno;
+
+ entry = (struct grub_iso9660_susp_entry *) sua;
+ return 0;
+ }
+
+ if (load_sua ())
+ return grub_errno;
+
+ for (; (char *) entry < (char *) sua + sua_size - 1;
+ 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;
+
+ ce = (struct grub_iso9660_susp_ce *) entry;
+ sua_size = grub_le_to_cpu32 (ce->len);
+ sua_pos = grub_le_to_cpu32 (ce->off);
+ sua_block = grub_le_to_cpu32 (ce->blk) << GRUB_ISO9660_LOG2_BLKSZ;
+
+ grub_free (sua);
+ if (load_sua ())
+ return grub_errno;
+ }
+
+ if (hook (entry))
+ {
+ grub_free (sua);
+ return 0;
+ }
+ }
+
+ grub_free (sua);
+ return 0;
+}
+
+static char *
+grub_iso9660_convert_string (grub_uint16_t *us, int len)
+{
+ char *p;
+ int i;
+
+ p = grub_malloc (len * 4 + 1);
+ if (! p)
+ return p;
+
+ for (i=0; i<len; i++)
+ us[i] = grub_be_to_cpu16 (us[i]);
+
+ *grub_utf16_to_utf8 ((grub_uint8_t *) p, us, len) = '\0';
+
+ return p;
+}
+
+static struct grub_iso9660_data *
+grub_iso9660_mount (grub_disk_t disk)
+{
+ struct grub_iso9660_data *data = 0;
+ struct grub_iso9660_dir rootdir;
+ int sua_pos;
+ int sua_size;
+ char *sua;
+ struct grub_iso9660_susp_entry *entry;
+ struct grub_iso9660_primary_voldesc voldesc;
+ int block;
+
+ auto grub_err_t susp_iterate (struct grub_iso9660_susp_entry *);
+
+ grub_err_t susp_iterate (struct grub_iso9660_susp_entry *susp_entry)
+ {
+ /* 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;
+ }
+
+ 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 ((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));
+
+ block++;
+ } while (voldesc.voldesc.type != GRUB_ISO9660_VOLDESC_END);
+
+ /* Read the system use area and test it to see if SUSP is
+ supported. */
+ if (grub_disk_read (disk, (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector)
+ << GRUB_ISO9660_LOG2_BLKSZ), 0,
+ sizeof (rootdir), (char *) &rootdir))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem");
+ goto fail;
+ }
+
+ sua_pos = (sizeof (rootdir) + rootdir.namelen
+ + (rootdir.namelen % 2) - 1);
+ sua_size = rootdir.len - sua_pos;
+
+ sua = grub_malloc (sua_size);
+ if (! sua)
+ goto fail;
+
+ if (grub_disk_read (disk, (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector)
+ << GRUB_ISO9660_LOG2_BLKSZ), sua_pos,
+ sua_size, sua))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem");
+ goto fail;
+ }
+
+ 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)
+ {
+ /* 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 (data,
+ (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector)
+ << GRUB_ISO9660_LOG2_BLKSZ),
+ sua_pos, sua_size, susp_iterate))
+ goto fail;
+ }
+
+ return data;
+
+ fail:
+ grub_free (data);
+ return 0;
+}
+
+
+static char *
+grub_iso9660_read_symlink (grub_fshelp_node_t node)
+{
+ struct grub_iso9660_dir dirent;
+ int sua_off;
+ int sua_size;
+ char *symlink = 0;
+ int addslash = 0;
+
+ auto void add_part (const char *part, int len);
+ auto grub_err_t susp_iterate_sl (struct grub_iso9660_susp_entry *);
+
+ /* Extend the symlink. */
+ void add_part (const char *part, int len)
+ {
+ int size = grub_strlen (symlink);
+
+ symlink = grub_realloc (symlink, size + len + 1);
+ if (! symlink)
+ return;
+
+ grub_strncat (symlink, part, len);
+ }
+
+ /* Read in a symlink. */
+ grub_err_t susp_iterate_sl (struct grub_iso9660_susp_entry *entry)
+ {
+ 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 < grub_le_to_cpu32 (entry->len))
+ {
+ if (addslash)
+ {
+ add_part ("/", 1);
+ addslash = 0;
+ }
+
+ /* 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'. */
+ add_part ((char *) &entry->data[pos + 2],
+ entry->data[pos + 1]);
+ if ((entry->data[pos] & 1))
+ addslash = 1;
+
+ break;
+ }
+
+ case 2:
+ add_part ("./", 2);
+ break;
+
+ case 4:
+ add_part ("../", 3);
+ break;
+
+ case 8:
+ add_part ("/", 1);
+ break;
+ }
+ /* 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;
+ }
+
+ if (grub_disk_read (node->data->disk, node->dir_blk, node->dir_off,
+ sizeof (dirent), (char *) &dirent))
+ return 0;
+
+ sua_off = (sizeof (dirent) + dirent.namelen + 1 - (dirent.namelen % 2)
+ + node->data->susp_skip);
+ sua_size = dirent.len - sua_off;
+
+ symlink = grub_malloc (1);
+ if (!symlink)
+ return 0;
+
+ *symlink = '\0';
+
+ if (grub_iso9660_susp_iterate (node->data, node->dir_blk,
+ node->dir_off + sua_off,
+ sua_size, susp_iterate_sl))
+ {
+ grub_free (symlink);
+ return 0;
+ }
+
+ return symlink;
+}
+
+
+static int
+grub_iso9660_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ struct grub_iso9660_dir dirent;
+ unsigned int offset = 0;
+ char *filename;
+ int filename_alloc = 0;
+ enum grub_fshelp_filetype type;
+
+ auto grub_err_t susp_iterate_dir (struct grub_iso9660_susp_entry *);
+
+ grub_err_t susp_iterate_dir (struct grub_iso9660_susp_entry *entry)
+ {
+ /* 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. */
+ if (entry->data[0] & GRUB_ISO9660_RR_DOT)
+ filename = ".";
+ else if (entry->data[0] & GRUB_ISO9660_RR_DOTDOT)
+ filename = "..";
+ else
+ {
+ int size = 1;
+ if (filename)
+ {
+ size += grub_strlen (filename);
+ grub_realloc (filename,
+ grub_strlen (filename)
+ + entry->len);
+ }
+ else
+ {
+ size = entry->len - 5;
+ filename = grub_zalloc (size + 1);
+ }
+ filename_alloc = 1;
+ grub_strncpy (filename, (char *) &entry->data[1], size);
+ filename[size] = '\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:
+ type = GRUB_FSHELP_DIR;
+ break;
+ case GRUB_ISO9660_FSTYPE_REG:
+ type = GRUB_FSHELP_REG;
+ break;
+ case GRUB_ISO9660_FSTYPE_SYMLINK:
+ type = GRUB_FSHELP_SYMLINK;
+ break;
+ default:
+ type = GRUB_FSHELP_UNKNOWN;
+ }
+ }
+
+ return 0;
+ }
+
+ while (offset < dir->size)
+ {
+ if (grub_disk_read (dir->data->disk,
+ (dir->blk << GRUB_ISO9660_LOG2_BLKSZ)
+ + offset / GRUB_DISK_SECTOR_SIZE,
+ offset % GRUB_DISK_SECTOR_SIZE,
+ 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[dirent.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;
+
+ filename = 0;
+ filename_alloc = 0;
+ type = GRUB_FSHELP_UNKNOWN;
+
+ if (dir->data->rockridge
+ && grub_iso9660_susp_iterate (dir->data,
+ (dir->blk << GRUB_ISO9660_LOG2_BLKSZ)
+ + (sua_off
+ / GRUB_DISK_SECTOR_SIZE),
+ sua_off % GRUB_DISK_SECTOR_SIZE,
+ sua_size, susp_iterate_dir))
+ return 0;
+
+ /* Read the name. */
+ if (grub_disk_read (dir->data->disk,
+ (dir->blk << GRUB_ISO9660_LOG2_BLKSZ)
+ + nameoffset / GRUB_DISK_SECTOR_SIZE,
+ nameoffset % GRUB_DISK_SECTOR_SIZE,
+ dirent.namelen, (char *) name))
+ return 0;
+
+ node = grub_malloc (sizeof (struct grub_fshelp_node));
+ if (!node)
+ return 0;
+
+ /* Setup a new node. */
+ node->data = dir->data;
+ node->size = grub_le_to_cpu32 (dirent.size);
+ node->blk = grub_le_to_cpu32 (dirent.first_sector);
+ node->dir_blk = ((dir->blk << GRUB_ISO9660_LOG2_BLKSZ)
+ + offset / GRUB_DISK_SECTOR_SIZE);
+ node->dir_off = offset % GRUB_DISK_SECTOR_SIZE;
+
+ /* If the filetype was not stored using rockridge, use
+ whatever is stored in the iso9660 filesystem. */
+ if (type == GRUB_FSHELP_UNKNOWN)
+ {
+ if ((dirent.flags & 3) == 2)
+ type = GRUB_FSHELP_DIR;
+ else
+ type = GRUB_FSHELP_REG;
+ }
+
+ /* The filename was not stored in a rock ridge entry. Read it
+ from the iso9660 filesystem. */
+ if (!filename)
+ {
+ name[dirent.namelen] = '\0';
+ filename = grub_strrchr (name, ';');
+ if (filename)
+ *filename = '\0';
+
+ if (dirent.namelen == 1 && name[0] == 0)
+ filename = ".";
+ else if (dirent.namelen == 1 && name[0] == 1)
+ filename = "..";
+ else
+ filename = name;
+ }
+
+ if (dir->data->joliet)
+ {
+ char *oldname, *semicolon;
+
+ oldname = filename;
+ filename = grub_iso9660_convert_string
+ ((grub_uint16_t *) oldname, dirent.namelen >> 1);
+
+ semicolon = grub_strrchr (filename, ';');
+ if (semicolon)
+ *semicolon = '\0';
+
+ if (filename_alloc)
+ grub_free (oldname);
+
+ filename_alloc = 1;
+ }
+
+ if (hook (filename, type, node))
+ {
+ if (filename_alloc)
+ grub_free (filename);
+ return 1;
+ }
+ if (filename_alloc)
+ grub_free (filename);
+ }
+
+ offset += dirent.len;
+ }
+
+ return 0;
+}
+
+
+
+static grub_err_t
+grub_iso9660_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_iso9660_data *data = 0;
+ struct grub_fshelp_node rootnode;
+ struct grub_fshelp_node *foundnode;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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 hook (filename, &info);
+ }
+
+ grub_dl_ref (my_mod);
+
+ data = grub_iso9660_mount (device->disk);
+ if (! data)
+ goto fail;
+
+ rootnode.data = data;
+ rootnode.blk = grub_le_to_cpu32 (data->voldesc.rootdir.first_sector);
+ rootnode.size = grub_le_to_cpu32 (data->voldesc.rootdir.size);
+
+ /* 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, iterate);
+
+ 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.blk = grub_le_to_cpu32 (data->voldesc.rootdir.first_sector);
+ rootnode.size = grub_le_to_cpu32 (data->voldesc.rootdir.size);
+
+ /* 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->first_sector = foundnode->blk;
+
+ file->data = data;
+ file->size = foundnode->size;
+ 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;
+
+ /* XXX: The file is stored in as a single extent. */
+ data->disk->read_hook = file->read_hook;
+ grub_disk_read (data->disk,
+ data->first_sector << GRUB_ISO9660_LOG2_BLKSZ,
+ file->offset,
+ len, buf);
+ data->disk->read_hook = NULL;
+
+ if (grub_errno)
+ return -1;
+
+ return len;
+}
+
+
+static grub_err_t
+grub_iso9660_close (grub_file_t file)
+{
+ grub_free (file->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
+ ((grub_uint16_t *) &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;
+}
+
+
+
+static struct grub_fs grub_iso9660_fs =
+ {
+ .name = "iso9660",
+ .dir = grub_iso9660_dir,
+ .open = grub_iso9660_open,
+ .read = grub_iso9660_read,
+ .close = grub_iso9660_close,
+ .label = grub_iso9660_label,
+ .uuid = grub_iso9660_uuid,
+ .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..72e6adc
--- /dev/null
+++ b/grub-core/fs/jfs.c
@@ -0,0 +1,908 @@
+/* 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>
+
+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[71];
+ grub_uint8_t volname[11];
+ grub_uint8_t unused2[32];
+ grub_uint8_t uuid[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;
+} __attribute__ ((packed));
+
+struct grub_jfs_iag
+{
+ grub_uint8_t unused[3072];
+ struct grub_jfs_extent inodes[128];
+} __attribute__ ((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];
+} __attribute__ ((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;
+} __attribute__ ((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];
+} __attribute__ ((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];
+} __attribute__ ((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;
+} __attribute__ ((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];
+} __attribute__ ((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;
+ grub_uint8_t unused3[72];
+ grub_uint8_t unused4[96];
+
+ union
+ {
+ /* The tree describing the extents of the file. */
+ struct __attribute__ ((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];
+ } dir __attribute__ ((packed));
+ /* Fast symlink. */
+ struct
+ {
+ grub_uint8_t unused[32];
+ grub_uint8_t path[128];
+ } symlink;
+ } __attribute__ ((packed));
+} __attribute__ ((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 pos;
+ int linknest;
+} __attribute__ ((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];
+ } *dirpage __attribute__ ((packed));
+ 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. */
+ char name[255];
+ grub_uint32_t ino;
+} __attribute__ ((packed));
+
+
+static grub_dl_t my_mod;
+
+static grub_err_t grub_jfs_lookup_symlink (struct grub_jfs_data *data, grub_uint32_t ino);
+
+/* 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)
+{
+ auto int getblk (struct grub_jfs_treehead *treehead,
+ struct grub_jfs_tree_extent *extents);
+
+ int getblk (struct grub_jfs_treehead *treehead,
+ struct grub_jfs_tree_extent *extents)
+ {
+ int found = -1;
+ int i;
+
+ for (i = 0; i < grub_le_to_cpu16 (treehead->count) - 2; 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 << 8)
+ + 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)
+ {
+ struct
+ {
+ struct grub_jfs_treehead treehead;
+ struct grub_jfs_tree_extent extents[254];
+ } tree;
+
+ if (grub_disk_read (data->disk,
+ 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))
+ return -1;
+
+ return getblk (&tree.treehead, &tree.extents[0]);
+ }
+
+ return -1;
+ }
+
+ return getblk (&inode->file.tree, &inode->file.extents[0]);
+}
+
+
+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_iag iag;
+ 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), 0,
+ sizeof (struct grub_jfs_iag), &iag))
+ 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 (grub_le_to_cpu32 (data->sblock.blksz)
+ != (1U << grub_le_to_cpu16 (data->sblock.log2_blksz)))
+ {
+ 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;
+
+ 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, "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);
+}
+
+
+/* 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[255];
+
+ auto void addstr (grub_uint16_t *uname, int ulen);
+
+ /* Add the unicode string to the utf16 filename buffer. */
+ void addstr (grub_uint16_t *name, int ulen)
+ {
+ while (ulen--)
+ filename[strpos++] = *(name++);
+ }
+
+ /* 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);
+ }
+
+ addstr (leaf->namepart, len < 11 ? len : 11);
+ diro->ino = grub_le_to_cpu32 (leaf->inode);
+ len -= 11;
+
+ /* Move down to the leaf level. */
+ nextent = leaf->next;
+ if (leaf->next != 255)
+ do
+ {
+ next_leaf = &diro->next_leaf[nextent];
+ addstr (next_leaf->namepart, 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;
+}
+
+
+/* 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ grub_uint64_t pos, grub_size_t len, char *buf)
+{
+ grub_uint64_t i;
+ grub_uint64_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;
+ 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)
+{
+ char fpath[grub_strlen (path)];
+ char *name = fpath;
+ char *next;
+ struct grub_jfs_diropen *diro;
+
+ grub_strncpy (fpath, path, grub_strlen (path) + 1);
+
+ if (grub_jfs_read_inode (data, GRUB_JFS_AGGR_INODE, &data->currinode))
+ return grub_errno;
+
+ /* Skip the first slashes. */
+ while (*name == '/')
+ {
+ name++;
+ if (!*name)
+ return 0;
+ }
+
+ /* Extract the actual part from the pathname. */
+ next = grub_strchr (name, '/');
+ if (next)
+ {
+ while (*next == '/')
+ {
+ next[0] = '\0';
+ next++;
+ }
+ }
+ diro = grub_jfs_opendir (data, &data->currinode);
+ if (!diro)
+ return grub_errno;
+
+ for (;;)
+ {
+ if (grub_strlen (name) == 0)
+ return GRUB_ERR_NONE;
+
+ if (grub_jfs_getent (diro) == GRUB_ERR_OUT_OF_RANGE)
+ break;
+
+ /* Check if the current direntry matches the current part of the
+ pathname. */
+ if (!grub_strcmp (name, diro->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;
+ }
+
+ if (!next)
+ return 0;
+
+ name = next;
+ next = grub_strchr (name, '/');
+ if (next)
+ {
+ next[0] = '\0';
+ next++;
+ }
+
+ /* Open this directory for reading dirents. */
+ diro = grub_jfs_opendir (data, &data->currinode);
+ if (!diro)
+ return grub_errno;
+
+ continue;
+ }
+ }
+
+ grub_jfs_closedir (diro);
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ return grub_errno;
+}
+
+
+static grub_err_t
+grub_jfs_lookup_symlink (struct grub_jfs_data *data, grub_uint32_t ino)
+{
+ grub_uint64_t size = grub_le_to_cpu64 (data->currinode.size);
+ char symlink[size + 1];
+
+ if (++data->linknest > GRUB_JFS_MAX_SYMLNK_CNT)
+ return grub_error (GRUB_ERR_SYMLINK_LOOP, "too deep nesting of symlinks");
+
+ if (size <= 128)
+ grub_strncpy (symlink, (char *) (data->currinode.symlink.path), 128);
+ else if (grub_jfs_read_file (data, 0, 0, size, symlink) < 0)
+ return grub_errno;
+
+ symlink[size] = '\0';
+
+ /* The symlink is an absolute path, go back to the root inode. */
+ if (symlink[0] == '/')
+ ino = 2;
+
+ /* Now load in the old inode. */
+ if (grub_jfs_read_inode (data, ino, &data->currinode))
+ return grub_errno;
+
+ grub_jfs_find_file (data, symlink);
+ if (grub_errno)
+ grub_error (grub_errno, "cannot follow symlink `%s'", symlink);
+
+ return grub_errno;
+}
+
+
+static grub_err_t
+grub_jfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ 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))
+ 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;
+ if (hook (diro->name, &info))
+ 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);
+ 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, "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->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)
+ *label = grub_strndup ((char *) (data->sblock.volname), 11);
+ else
+ *label = 0;
+
+ return grub_errno;
+}
+
+
+static struct grub_fs grub_jfs_fs =
+ {
+ .name = "jfs",
+ .dir = grub_jfs_dir,
+ .open = grub_jfs_open,
+ .read = grub_jfs_read,
+ .close = grub_jfs_close,
+ .label = grub_jfs_label,
+ .uuid = grub_jfs_uuid,
+ .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..523e6e6
--- /dev/null
+++ b/grub-core/fs/minix.c
@@ -0,0 +1,590 @@
+/* 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>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#ifdef 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 GRUB_MINIX_BSIZE 1024U
+#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_MINIX2
+typedef grub_uint32_t grub_minix_uintn_t;
+#define grub_minix_le_to_cpu_n grub_le_to_cpu32
+#else
+typedef grub_uint16_t grub_minix_uintn_t;
+#define grub_minix_le_to_cpu_n grub_le_to_cpu16
+#endif
+
+#define GRUB_MINIX_INODE_BLKSZ(data) sizeof (grub_minix_uintn_t)
+
+#define GRUB_MINIX_INODE_SIZE(data) (grub_minix_le_to_cpu_n (data->inode.size))
+#define GRUB_MINIX_INODE_MODE(data) (grub_le_to_cpu16 (data->inode.mode))
+#define GRUB_MINIX_INODE_DIR_ZONES(data,blk) (grub_minix_le_to_cpu_n \
+ (data->inode.dir_zones[blk]))
+#define GRUB_MINIX_INODE_INDIR_ZONE(data) (grub_minix_le_to_cpu_n \
+ (data->inode.indir_zone))
+#define GRUB_MINIX_INODE_DINDIR_ZONE(data) (grub_minix_le_to_cpu_n \
+ (data->inode.double_indir_zone))
+
+#define GRUB_MINIX_LOG2_ZONESZ (GRUB_MINIX_LOG2_BSIZE \
+ + grub_le_to_cpu16 (sblock->log2_zone_size))
+#define GRUB_MINIX_ZONESZ (GRUB_MINIX_BSIZE \
+ << grub_le_to_cpu16 (sblock->log2_zone_size))
+
+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;
+};
+
+#ifndef MODE_MINIX2
+struct grub_minix_inode
+{
+ grub_uint16_t mode;
+ grub_uint16_t uid;
+ grub_uint16_t size;
+ grub_uint32_t ctime;
+ 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;
+};
+
+#else
+
+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 unused;
+
+};
+
+#endif
+
+/* Information about a "mounted" minix filesystem. */
+struct grub_minix_data
+{
+ struct grub_minix_sblock sblock;
+ struct grub_minix_inode inode;
+ int ino;
+ int linknest;
+ grub_disk_t disk;
+ int filename_size;
+};
+
+static grub_dl_t my_mod;
+
+static grub_err_t grub_minix_find_file (struct grub_minix_data *data,
+ const char *path);
+
+static int
+grub_minix_get_file_block (struct grub_minix_data *data, unsigned int blk)
+{
+ struct grub_minix_sblock *sblock = &data->sblock;
+ int indir;
+
+ auto int grub_get_indir (int, int);
+
+ /* Read the block pointer in ZONE, on the offset NUM. */
+ int grub_get_indir (int zone, int num)
+ {
+ grub_minix_uintn_t indirn;
+ grub_disk_read (data->disk,
+ zone << GRUB_MINIX_LOG2_ZONESZ,
+ sizeof (grub_minix_uintn_t) * num,
+ sizeof (grub_minix_uintn_t), (char *) &indirn);
+ return grub_minix_le_to_cpu_n (indirn);
+ }
+
+ /* Direct block. */
+ if (blk < 7)
+ return GRUB_MINIX_INODE_DIR_ZONES (data, blk);
+
+ /* Indirect block. */
+ blk -= 7;
+ if (blk < GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))
+ {
+ indir = grub_get_indir (GRUB_MINIX_INODE_INDIR_ZONE (data), blk);
+ return indir;
+ }
+
+ /* Double indirect block. */
+ blk -= GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data);
+ if (blk < (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))
+ * (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data)))
+ {
+ indir = grub_get_indir (GRUB_MINIX_INODE_DINDIR_ZONE (data),
+ blk / GRUB_MINIX_ZONESZ);
+
+ indir = grub_get_indir (indir, blk % GRUB_MINIX_ZONESZ);
+
+ return indir;
+ }
+
+ /* 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_disk_addr_t len, char *buf)
+{
+ struct grub_minix_sblock *sblock = &data->sblock;
+ int i;
+ int blockcnt;
+
+ /* 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;
+
+ blockcnt = (len + pos + GRUB_MINIX_BSIZE - 1) / GRUB_MINIX_BSIZE;
+
+ for (i = pos / GRUB_MINIX_BSIZE; i < blockcnt; i++)
+ {
+ int blknr;
+ int blockoff = pos % GRUB_MINIX_BSIZE;
+ int blockend = GRUB_MINIX_BSIZE;
+
+ int skipfirst = 0;
+
+ blknr = grub_minix_get_file_block (data, i);
+ if (grub_errno)
+ return -1;
+
+ /* Last block. */
+ if (i == blockcnt - 1)
+ {
+ blockend = (len + pos) % GRUB_MINIX_BSIZE;
+
+ if (!blockend)
+ blockend = GRUB_MINIX_BSIZE;
+ }
+
+ /* First block. */
+ if (i == (pos / (int) GRUB_MINIX_BSIZE))
+ {
+ skipfirst = blockoff;
+ blockend -= skipfirst;
+ }
+
+ data->disk->read_hook = read_hook;
+ grub_disk_read (data->disk, blknr << GRUB_MINIX_LOG2_ZONESZ,
+ skipfirst, blockend, buf);
+
+ data->disk->read_hook = 0;
+ if (grub_errno)
+ return -1;
+
+ buf += GRUB_MINIX_BSIZE - 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, int ino)
+{
+ struct grub_minix_sblock *sblock = &data->sblock;
+
+ /* Block in which the inode is stored. */
+ int block;
+ data->ino = ino;
+
+ /* The first inode in minix is inode 1. */
+ ino--;
+
+ block = ((2 + grub_le_to_cpu16 (sblock->inode_bmap_size)
+ + grub_le_to_cpu16 (sblock->zone_bmap_size))
+ << GRUB_MINIX_LOG2_BSIZE);
+
+ 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, int ino)
+{
+ char symlink[GRUB_MINIX_INODE_SIZE (data) + 1];
+
+ if (++data->linknest > GRUB_MINIX_MAX_SYMLNK_CNT)
+ return grub_error (GRUB_ERR_SYMLINK_LOOP, "too deep nesting of symlinks");
+
+ if (grub_minix_read_file (data, 0, 0,
+ GRUB_MINIX_INODE_SIZE (data), symlink) < 0)
+ return grub_errno;
+
+ symlink[GRUB_MINIX_INODE_SIZE (data)] = '\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);
+ if (grub_errno)
+ grub_error (grub_errno, "cannot follow symlink `%s'", 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)
+{
+ char fpath[grub_strlen (path) + 1];
+ char *name = fpath;
+ char *next;
+ unsigned int pos = 0;
+ int dirino;
+
+ grub_strcpy (fpath, path);
+
+ /* Skip the first slash. */
+ if (name[0] == '/')
+ {
+ name++;
+ if (!*name)
+ return 0;
+ }
+
+ /* Extract the actual part from the pathname. */
+ next = grub_strchr (name, '/');
+ if (next)
+ {
+ next[0] = '\0';
+ next++;
+ }
+
+ do
+ {
+ grub_uint16_t ino;
+ char filename[data->filename_size + 1];
+
+ if (grub_strlen (name) == 0)
+ return GRUB_ERR_NONE;
+
+ if (grub_minix_read_file (data, 0, pos, sizeof (ino),
+ (char *) &ino) < 0)
+ return grub_errno;
+ if (grub_minix_read_file (data, 0, pos + sizeof (ino),
+ data->filename_size, (char *) filename)< 0)
+ return grub_errno;
+
+ filename[data->filename_size] = '\0';
+
+ /* Check if the current direntry matches the current part of the
+ pathname. */
+ if (!grub_strcmp (name, filename))
+ {
+ dirino = data->ino;
+ grub_minix_read_inode (data, grub_le_to_cpu16 (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;
+ }
+
+ if (!next)
+ return 0;
+
+ pos = 0;
+
+ name = next;
+ next = grub_strchr (name, '/');
+ if (next)
+ {
+ next[0] = '\0';
+ next++;
+ }
+
+ if ((GRUB_MINIX_INODE_MODE (data)
+ & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR)
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+
+ continue;
+ }
+
+ pos += sizeof (ino) + data->filename_size;
+ } while (pos < GRUB_MINIX_INODE_SIZE (data));
+
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ return grub_errno;
+}
+
+
+/* Mount the filesystem on the disk DISK. */
+static struct grub_minix_data *
+grub_minix_mount (grub_disk_t disk)
+{
+ struct grub_minix_data *data;
+
+ 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 (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC)
+ data->filename_size = 14;
+ else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC_30)
+ data->filename_size = 30;
+ else
+ goto fail;
+
+ data->disk = disk;
+ data->linknest = 0;
+
+ return data;
+
+ fail:
+ grub_free (data);
+#ifdef 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,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ 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, "not a directory");
+ goto fail;
+ }
+
+ while (pos < GRUB_MINIX_INODE_SIZE (data))
+ {
+ grub_uint16_t ino;
+ char filename[data->filename_size + 1];
+ int dirino = data->ino;
+ struct grub_dirhook_info info;
+ grub_memset (&info, 0, sizeof (info));
+
+
+ if (grub_minix_read_file (data, 0, pos, sizeof (ino),
+ (char *) &ino) < 0)
+ return grub_errno;
+
+ if (grub_minix_read_file (data, 0, pos + sizeof (ino),
+ data->filename_size,
+ (char *) filename) < 0)
+ return grub_errno;
+ filename[data->filename_size] = '\0';
+
+ /* The filetype is not stored in the dirent. Read the inode to
+ find out the filetype. This *REALLY* sucks. */
+ grub_minix_read_inode (data, grub_le_to_cpu16 (ino));
+ info.dir = ((GRUB_MINIX_INODE_MODE (data)
+ & GRUB_MINIX_IFDIR) == GRUB_MINIX_IFDIR);
+ if (hook (filename, &info) ? 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, "bad filename");
+ 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->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_MINIX2
+ .name = "minix2",
+#else
+ .name = "minix",
+#endif
+ .dir = grub_minix_dir,
+ .open = grub_minix_open,
+ .read = grub_minix_read,
+ .close = grub_minix_close,
+ .next = 0
+ };
+
+#ifdef MODE_MINIX2
+GRUB_MOD_INIT(minix2)
+#else
+GRUB_MOD_INIT(minix)
+#endif
+{
+ grub_fs_register (&grub_minix_fs);
+ my_mod = mod;
+}
+
+#ifdef MODE_MINIX2
+GRUB_MOD_FINI(minix2)
+#else
+GRUB_MOD_FINI(minix)
+#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/nilfs2.c b/grub-core/fs/nilfs2.c
new file mode 100644
index 0000000..4c8d763
--- /dev/null
+++ b/grub-core/fs/nilfs2.c
@@ -0,0 +1,1186 @@
+/*
+ * 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)
+#define NILFS_BTREE_LEVEL_MAX 14
+
+/* 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)
+
+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[16];
+ char s_last_mounted[64];
+ 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;
+ grub_uint8_t name_len;
+ grub_uint8_t file_type;
+#if 0 /* followed by file name. */
+ char name[NILFS_NAME_LEN];
+ char pad;
+#endif
+} __attribute__ ((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;
+};
+
+struct grub_nilfs2_palloc_group_desc
+{
+ grub_uint32_t pg_nfrees;
+};
+
+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_palloc_entries_per_group (struct grub_nilfs2_data *data)
+{
+ return 1UL << (LOG2_BLOCK_SIZE (data) + 3);
+}
+
+static inline grub_uint64_t
+grub_nilfs2_palloc_group (struct grub_nilfs2_data *data,
+ grub_uint64_t nr, grub_uint32_t * offset)
+{
+ return grub_divmod64 (nr, grub_nilfs2_palloc_entries_per_group (data),
+ offset);
+}
+
+static inline grub_uint32_t
+grub_nilfs2_palloc_groups_per_desc_block (struct grub_nilfs2_data *data)
+{
+ return NILFS2_BLOCK_SIZE (data) /
+ sizeof (struct grub_nilfs2_palloc_group_desc);
+}
+
+static inline grub_uint32_t
+grub_nilfs2_entries_per_block (struct grub_nilfs2_data *data,
+ unsigned long entry_size)
+{
+ return NILFS2_BLOCK_SIZE (data) / entry_size;
+}
+
+
+static inline grub_uint32_t
+grub_nilfs2_blocks_per_group (struct grub_nilfs2_data *data,
+ unsigned long entry_size)
+{
+ return grub_div_roundup (grub_nilfs2_palloc_entries_per_group (data),
+ grub_nilfs2_entries_per_block (data,
+ entry_size)) + 1;
+}
+
+static inline grub_uint32_t
+grub_nilfs2_blocks_per_desc_block (struct grub_nilfs2_data *data,
+ unsigned long entry_size)
+{
+ return grub_nilfs2_palloc_groups_per_desc_block (data) *
+ grub_nilfs2_blocks_per_group (data, entry_size) + 1;
+}
+
+static inline grub_uint32_t
+grub_nilfs2_palloc_desc_block_offset (struct grub_nilfs2_data *data,
+ unsigned long group,
+ unsigned long entry_size)
+{
+ grub_uint32_t desc_block =
+ group / grub_nilfs2_palloc_groups_per_desc_block (data);
+ return desc_block * grub_nilfs2_blocks_per_desc_block (data, entry_size);
+}
+
+static inline grub_uint32_t
+grub_nilfs2_palloc_bitmap_block_offset (struct grub_nilfs2_data *data,
+ unsigned long group,
+ unsigned long entry_size)
+{
+ unsigned long desc_offset = group %
+ grub_nilfs2_palloc_groups_per_desc_block (data);
+
+ return grub_nilfs2_palloc_desc_block_offset (data, group, entry_size) + 1 +
+ desc_offset * grub_nilfs2_blocks_per_group (data, entry_size);
+}
+
+static inline grub_uint32_t
+grub_nilfs2_palloc_entry_offset (struct grub_nilfs2_data *data,
+ grub_uint64_t nr, unsigned long entry_size)
+{
+ unsigned long group;
+ grub_uint32_t group_offset;
+
+ group = grub_nilfs2_palloc_group (data, nr, &group_offset);
+
+ return grub_nilfs2_palloc_bitmap_block_offset (data, group,
+ entry_size) + 1 +
+ group_offset / grub_nilfs2_entries_per_block (data, 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 (grub_uint64_t *) ((char *) (node + 1) +
+ ((node->bn_flags & NILFS_BTREE_NODE_ROOT) ?
+ 0 : NILFS_BTREE_NODE_EXTRA_PAD_SIZE));
+}
+
+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_lookup (struct grub_nilfs2_btree_node *node,
+ grub_uint64_t key, int *indexp)
+{
+ grub_uint64_t nkey;
+ int index, low, high, s;
+
+ low = 0;
+ high = grub_le_to_cpu16 (node->bn_nchildren) - 1;
+ index = 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 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 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;
+ unsigned char block[NILFS2_BLOCK_SIZE (data)];
+ grub_uint64_t ptr;
+ int level, found, index;
+
+ node = grub_nilfs2_btree_get_root (inode);
+ level = grub_nilfs2_btree_get_level (node);
+
+ found = grub_nilfs2_btree_node_lookup (node, key, &index);
+ 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)
+ {
+ return -1;
+ }
+ node = (struct grub_nilfs2_btree_node *) block;
+
+ if (node->bn_level != level)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "btree level mismatch\n");
+ return -1;
+ }
+
+ if (!found)
+ found = grub_nilfs2_btree_node_lookup (node, key, &index);
+ else
+ index = 0;
+
+ if (index < grub_nilfs2_btree_node_nchildren_max (data, node))
+ {
+ 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");
+ return -1;
+ }
+ }
+
+ if (!found)
+ return -1;
+
+ return ptr;
+}
+
+static inline grub_uint64_t
+grub_nilfs2_direct_lookup (struct grub_nilfs2_inode *inode, grub_uint64_t key)
+{
+ 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 (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_uint32_t blockno, offset;
+ unsigned int nilfs2_block_count = (1 << LOG2_NILFS2_BLOCK_SIZE (data));
+
+ blockno = grub_nilfs2_palloc_entry_offset (data, key,
+ sizeof (struct
+ grub_nilfs2_dat_entry));
+
+ grub_divmod64 (key * sizeof (struct grub_nilfs2_dat_entry),
+ NILFS2_BLOCK_SIZE (data), &offset);
+
+ 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t
+ sector,
+ unsigned offset,
+ unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ return grub_fshelp_read_file (node->data->disk, node, read_hook,
+ pos, len, buf, grub_nilfs2_read_block,
+ grub_le_to_cpu64 (node->inode.i_size),
+ LOG2_NILFS2_BLOCK_SIZE (node->data));
+
+}
+
+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_uint32_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;
+ unsigned int 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 (data, ino,
+ sizeof (struct
+ grub_nilfs2_inode));
+
+ grub_divmod64 (sizeof (struct grub_nilfs2_inode) * ino,
+ NILFS2_BLOCK_SIZE (data), &offset);
+ 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;
+
+ 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);
+
+ partition_size = grub_disk_get_size (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,
+ 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,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ unsigned int 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, 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[dirent.name_len + 1];
+ struct grub_fshelp_node *fdiro;
+ enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;
+
+ grub_nilfs2_read_file (diro, 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))
+ 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->offset, len, buf);
+}
+
+static grub_err_t
+grub_nilfs2_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info * info))
+{
+ struct grub_nilfs2_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ struct grub_dirhook_info info;
+ grub_memset (&info, 0, sizeof (info));
+ if (!node->inode_read)
+ {
+ grub_nilfs2_read_inode (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 hook (filename, &info);
+ }
+
+ grub_dl_ref (my_mod);
+
+ data = grub_nilfs2_mount (device->disk);
+ if (!data)
+ goto fail;
+
+ grub_fshelp_find_file (path, &data->diropen, &fdiro,
+ grub_nilfs2_iterate_dir, grub_nilfs2_read_symlink,
+ GRUB_FSHELP_DIR);
+ if (grub_errno)
+ goto fail;
+
+ grub_nilfs2_iterate_dir (fdiro, iterate);
+
+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_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, 14);
+ 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%0x-%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_int32_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_mtime);
+
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+
+
+static struct grub_fs grub_nilfs2_fs = {
+ .name = "nilfs2",
+ .dir = grub_nilfs2_dir,
+ .open = grub_nilfs2_open,
+ .read = grub_nilfs2_read,
+ .close = grub_nilfs2_close,
+ .label = grub_nilfs2_label,
+ .uuid = grub_nilfs2_uuid,
+ .mtime = grub_nilfs2_mtime,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 1,
+#endif
+ .next = 0
+};
+
+GRUB_MOD_INIT (nilfs2)
+{
+ 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..e01ce34
--- /dev/null
+++ b/grub-core/fs/ntfs.c
@@ -0,0 +1,1117 @@
+/* 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/>.
+ */
+
+#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;
+
+ntfscomp_func_t grub_ntfscomp_func;
+
+static grub_err_t
+fixup (struct grub_ntfs_data *data, char *buf, int len, char *magic)
+{
+ int ss;
+ char *pu;
+ grub_uint16_t us;
+
+ 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 * (int) data->blocksize != len * GRUB_DISK_SECTOR_SIZE)
+ return grub_error (GRUB_ERR_BAD_FS, "size not match",
+ ss * (int) data->blocksize,
+ len * GRUB_DISK_SECTOR_SIZE);
+ pu = buf + u16at (buf, 4);
+ us = u16at (pu, 0);
+ buf -= 2;
+ while (ss > 0)
+ {
+ buf += data->blocksize;
+ pu += 2;
+ if (u16at (buf, 0) != us)
+ return grub_error (GRUB_ERR_BAD_FS, "fixup signature not match");
+ v16at (buf, 0) = v16at (pu, 0);
+ ss--;
+ }
+
+ return 0;
+}
+
+static grub_err_t read_mft (struct grub_ntfs_data *data, char *buf,
+ grub_uint32_t mftno);
+static grub_err_t read_attr (struct grub_ntfs_attr *at, char *dest,
+ grub_disk_addr_t ofs, grub_size_t len,
+ int cached,
+ void
+ NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t
+ sector,
+ unsigned offset,
+ unsigned length));
+
+static grub_err_t read_data (struct grub_ntfs_attr *at, char *pa, char *dest,
+ grub_disk_addr_t ofs, grub_size_t len,
+ int cached,
+ void
+ NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t
+ sector,
+ unsigned offset,
+ unsigned length));
+
+static void
+init_attr (struct grub_ntfs_attr *at, struct grub_ntfs_file *mft)
+{
+ at->mft = mft;
+ at->flags = (mft == &mft->data->mmft) ? 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 char *
+find_attr (struct grub_ntfs_attr *at, unsigned char attr)
+{
+ if (at->flags & 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 (((unsigned char) *at->attr_cur == attr) || (attr == 0))
+ {
+ char *new_pos;
+
+ if (at->flags & AF_MMFT)
+ {
+ if ((grub_disk_read
+ (at->mft->data->disk, v32at (at->attr_cur, 0x10), 0,
+ 512, at->emft_buf))
+ ||
+ (grub_disk_read
+ (at->mft->data->disk, v32at (at->attr_cur, 0x14), 0,
+ 512, at->emft_buf + 512)))
+ return NULL;
+
+ if (fixup
+ (at->mft->data, at->emft_buf, at->mft->data->mft_size,
+ "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 ((unsigned char) *new_pos != 0xFF)
+ {
+ if (((unsigned char) *new_pos ==
+ (unsigned char) *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 ((unsigned char) *at->attr_cur != 0xFF)
+ {
+ at->attr_nxt += u16at (at->attr_cur, 4);
+ if ((unsigned char) *at->attr_cur == AT_ATTRIBUTE_LIST)
+ at->attr_end = at->attr_cur;
+ if (((unsigned char) *at->attr_cur == attr) || (attr == 0))
+ return at->attr_cur;
+ at->attr_cur = at->attr_nxt;
+ }
+ if (at->attr_end)
+ {
+ char *pa;
+
+ at->emft_buf = grub_malloc (at->mft->data->mft_size << BLK_SHR);
+ if (at->emft_buf == NULL)
+ return NULL;
+
+ pa = at->attr_end;
+ if (pa[8])
+ {
+ int 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))
+ {
+ 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 |= AF_ALST;
+ while (at->attr_nxt < at->attr_end)
+ {
+ if (((unsigned char) *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 & AF_MMFT) && (attr == AT_DATA))
+ {
+ at->flags |= AF_GPOS;
+ at->attr_cur = at->attr_nxt;
+ pa = at->attr_cur;
+ v32at (pa, 0x10) = at->mft->data->mft_start;
+ v32at (pa, 0x14) = at->mft->data->mft_start + 1;
+ pa = at->attr_nxt + u16at (pa, 4);
+ while (pa < at->attr_end)
+ {
+ if ((unsigned char) *pa != attr)
+ break;
+ if (read_attr
+ (at, pa + 0x10,
+ u32at (pa, 0x10) * (at->mft->data->mft_size << BLK_SHR),
+ at->mft->data->mft_size << BLK_SHR, 0, 0))
+ return NULL;
+ pa += u16at (pa, 4);
+ }
+ at->attr_nxt = at->attr_cur;
+ at->flags &= ~AF_GPOS;
+ }
+ goto retry;
+ }
+ return NULL;
+}
+
+static char *
+locate_attr (struct grub_ntfs_attr *at, struct grub_ntfs_file *mft,
+ unsigned char attr)
+{
+ char *pa;
+
+ init_attr (at, mft);
+ if ((pa = find_attr (at, attr)) == NULL)
+ return NULL;
+ if ((at->flags & AF_ALST) == 0)
+ {
+ while (1)
+ {
+ if ((pa = find_attr (at, attr)) == NULL)
+ break;
+ if (at->flags & AF_ALST)
+ return pa;
+ }
+ grub_errno = GRUB_ERR_NONE;
+ free_attr (at);
+ init_attr (at, mft);
+ pa = find_attr (at, attr);
+ }
+ return pa;
+}
+
+static char *
+read_run_data (char *run, int nn, grub_disk_addr_t * val, int sig)
+{
+ grub_disk_addr_t r, v;
+
+ r = 0;
+ v = 1;
+
+ while (nn--)
+ {
+ r += v * (*(unsigned char *) (run++));
+ v <<= 8;
+ }
+
+ if ((sig) && (r & (v >> 1)))
+ r -= v;
+
+ *val = r;
+ return run;
+}
+
+grub_err_t
+grub_ntfs_read_run_list (struct grub_ntfs_rlst * ctx)
+{
+ int c1, c2;
+ grub_disk_addr_t val;
+ char *run;
+
+ run = ctx->cur_run;
+retry:
+ c1 = ((unsigned char) (*run) & 0xF);
+ c2 = ((unsigned char) (*run) >> 4);
+ if (!c1)
+ {
+ if ((ctx->attr) && (ctx->attr->flags & AF_ALST))
+ {
+ void NESTED_FUNC_ATTR (*save_hook) (grub_disk_addr_t sector,
+ unsigned offset,
+ unsigned length);
+
+ save_hook = ctx->comp.disk->read_hook;
+ ctx->comp.disk->read_hook = 0;
+ run = find_attr (ctx->attr, (unsigned char) *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");
+ }
+ run = read_run_data (run + 1, c1, &val, 0); /* length of current VCN */
+ ctx->curr_vcn = ctx->next_vcn;
+ ctx->next_vcn += val;
+ run = read_run_data (run, c2, &val, 1); /* offset to previous LCN */
+ ctx->curr_lcn += val;
+ if (val == 0)
+ ctx->flags |= RF_BLNK;
+ else
+ ctx->flags &= ~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 & RF_BLNK) ? 0 : (block -
+ ctx->curr_vcn + ctx->curr_lcn);
+}
+
+static grub_err_t
+read_data (struct grub_ntfs_attr *at, char *pa, char *dest,
+ grub_disk_addr_t ofs, grub_size_t len, int cached,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset,
+ unsigned length))
+{
+ grub_disk_addr_t vcn;
+ struct grub_ntfs_rlst cc, *ctx;
+
+ if (len == 0)
+ return 0;
+
+ grub_memset (&cc, 0, sizeof (cc));
+ ctx = &cc;
+ ctx->attr = at;
+ ctx->comp.spc = at->mft->data->spc;
+ ctx->comp.disk = at->mft->data->disk;
+
+ 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;
+ }
+
+ if (u16at (pa, 0xC) & FLAG_COMPRESSED)
+ ctx->flags |= RF_COMP;
+ else
+ ctx->flags &= ~RF_COMP;
+ ctx->cur_run = pa + u16at (pa, 0x20);
+
+ if (ctx->flags & RF_COMP)
+ {
+ if (!cached)
+ return grub_error (GRUB_ERR_BAD_FS, "attribute can\'t be compressed");
+
+ if (at->sbuf)
+ {
+ if ((ofs & (~(COM_LEN - 1))) == at->save_pos)
+ {
+ grub_disk_addr_t n;
+
+ n = COM_LEN - (ofs - at->save_pos);
+ if (n > len)
+ n = len;
+
+ grub_memcpy (dest, at->sbuf + ofs - at->save_pos, n);
+ if (n == len)
+ return 0;
+
+ dest += n;
+ len -= n;
+ ofs += n;
+ }
+ }
+ else
+ {
+ at->sbuf = grub_malloc (COM_LEN);
+ if (at->sbuf == NULL)
+ return grub_errno;
+ at->save_pos = 1;
+ }
+
+ vcn = ctx->target_vcn = (ofs >> COM_LOG_LEN) * (COM_SEC / ctx->comp.spc);
+ ctx->target_vcn &= ~0xF;
+ }
+ else
+ vcn = ctx->target_vcn = grub_divmod64 (ofs >> BLK_SHR, ctx->comp.spc, 0);
+
+ ctx->next_vcn = u32at (pa, 0x10);
+ ctx->curr_lcn = 0;
+ while (ctx->next_vcn <= ctx->target_vcn)
+ {
+ if (grub_ntfs_read_run_list (ctx))
+ return grub_errno;
+ }
+
+ if (at->flags & AF_GPOS)
+ {
+ grub_disk_addr_t st0, st1;
+ grub_uint32_t m;
+
+ grub_divmod64 (ofs >> BLK_SHR, ctx->comp.spc, &m);
+
+ st0 =
+ (ctx->target_vcn - ctx->curr_vcn + ctx->curr_lcn) * ctx->comp.spc + m;
+ st1 = st0 + 1;
+ if (st1 ==
+ (ctx->next_vcn - ctx->curr_vcn + ctx->curr_lcn) * ctx->comp.spc)
+ {
+ if (grub_ntfs_read_run_list (ctx))
+ return grub_errno;
+ st1 = ctx->curr_lcn * ctx->comp.spc;
+ }
+ v32at (dest, 0) = st0;
+ v32at (dest, 4) = st1;
+ return 0;
+ }
+
+ if (!(ctx->flags & RF_COMP))
+ {
+ unsigned int pow;
+
+ if (!grub_fshelp_log2blksize (ctx->comp.spc, &pow))
+ grub_fshelp_read_file (ctx->comp.disk, (grub_fshelp_node_t) ctx,
+ read_hook, ofs, len, dest,
+ grub_ntfs_read_block, ofs + len, pow);
+ return grub_errno;
+ }
+
+ return (grub_ntfscomp_func) ? grub_ntfscomp_func (at, dest, ofs, len, ctx,
+ vcn) :
+ grub_error (GRUB_ERR_BAD_FS, "ntfscomp module not loaded");
+}
+
+static grub_err_t
+read_attr (struct grub_ntfs_attr *at, char *dest, grub_disk_addr_t ofs,
+ grub_size_t len, int cached,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset,
+ unsigned length))
+{
+ char *save_cur;
+ unsigned char attr;
+ char *pp;
+ grub_err_t ret;
+
+ save_cur = at->attr_cur;
+ at->attr_nxt = at->attr_cur;
+ attr = (unsigned char) *at->attr_nxt;
+ if (at->flags & AF_ALST)
+ {
+ char *pa;
+ grub_disk_addr_t vcn;
+
+ vcn = grub_divmod64 (ofs, at->mft->data->spc << BLK_SHR, 0);
+ pa = at->attr_nxt + u16at (at->attr_nxt, 4);
+ while (pa < at->attr_end)
+ {
+ if ((unsigned char) *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);
+ 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, char *buf, grub_uint32_t mftno)
+{
+ if (read_attr
+ (&data->mmft.attr, buf, mftno * ((grub_disk_addr_t) data->mft_size << BLK_SHR),
+ data->mft_size << BLK_SHR, 0, 0))
+ return grub_error (GRUB_ERR_BAD_FS, "read MFT 0x%X fails", mftno);
+ return fixup (data, buf, data->mft_size, "FILE");
+}
+
+static grub_err_t
+init_file (struct grub_ntfs_file *mft, grub_uint32_t mftno)
+{
+ unsigned short flag;
+
+ mft->inode_read = 1;
+
+ mft->buf = grub_malloc (mft->data->mft_size << 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%X is not in use", mftno);
+
+ if ((flag & 2) == 0)
+ {
+ char *pa;
+
+ pa = locate_attr (&mft->attr, mft, AT_DATA);
+ if (pa == NULL)
+ return grub_error (GRUB_ERR_BAD_FS, "no $DATA in MFT 0x%X", mftno);
+
+ if (!pa[8])
+ mft->size = u32at (pa, 0x10);
+ else
+ mft->size = u64at (pa, 0x30);
+
+ if ((mft->attr.flags & 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 int
+list_file (struct grub_ntfs_file *diro, char *pos,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ char *np;
+ int ns;
+
+ while (1)
+ {
+ char *ustr, namespace;
+
+ if (pos[0xC] & 2) /* end signature */
+ break;
+
+ np = pos + 0x50;
+ ns = (unsigned char) *(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;
+
+ if (u16at (pos, 4))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "64-bit MFT number");
+ return 0;
+ }
+
+ type =
+ (u32at (pos, 0x48) & ATTR_DIRECTORY) ? GRUB_FSHELP_DIR :
+ GRUB_FSHELP_REG;
+
+ fdiro = grub_zalloc (sizeof (struct grub_ntfs_file));
+ if (!fdiro)
+ return 0;
+
+ fdiro->data = diro->data;
+ fdiro->ino = u32at (pos, 0);
+
+ ustr = grub_malloc (ns * 4 + 1);
+ if (ustr == NULL)
+ return 0;
+ *grub_utf16_to_utf8 ((grub_uint8_t *) ustr, (grub_uint16_t *) np,
+ ns) = '\0';
+
+ if (namespace)
+ type |= GRUB_FSHELP_CASE_INSENSITIVE;
+
+ if (hook (ustr, type, fdiro))
+ {
+ grub_free (ustr);
+ return 1;
+ }
+
+ grub_free (ustr);
+ }
+ pos += u16at (pos, 8);
+ }
+ return 0;
+}
+
+static int
+grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ unsigned char *bitmap;
+ struct grub_ntfs_attr attr, *at;
+ char *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)
+ {
+ if ((cur_pos = find_attr (at, AT_INDEX_ROOT)) == 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);
+ if (ret)
+ goto done;
+
+ bitmap = NULL;
+ bitmap_len = 0;
+ free_attr (at);
+ init_attr (at, mft);
+ while ((cur_pos = find_attr (at, AT_BITMAP)) != NULL)
+ {
+ int ofs;
+
+ ofs = (unsigned char) 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, (char *) (cur_pos + u16at (cur_pos, 0x14)),
+ bitmap_len);
+ }
+ else
+ {
+ if (read_data (at, cur_pos, bmp, 0, bitmap_len, 0, 0))
+ {
+ grub_error (GRUB_ERR_BAD_FS,
+ "fails to read non-resident $BITMAP");
+ goto done;
+ }
+ bitmap_len = u32at (cur_pos, 0x30);
+ }
+
+ bitmap = (unsigned char *) bmp;
+ break;
+ }
+ }
+
+ free_attr (at);
+ cur_pos = locate_attr (at, mft, 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, 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 v, i;
+
+ indx = grub_malloc (mft->data->idx_size << 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 << BLK_SHR),
+ (mft->data->idx_size << BLK_SHR), 0, 0))
+ || (fixup (mft->data, indx, mft->data->idx_size, "INDX")))
+ goto done;
+ ret = list_file (mft, &indx[0x18 + u16at (indx, 0x18)], hook);
+ if (ret)
+ goto done;
+ }
+ v <<= 1;
+ if (v >= 0x100)
+ {
+ 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;
+
+ 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))
+ goto fail;
+
+ data->blocksize = grub_le_to_cpu16 (bpb.bytes_per_sector);
+ data->spc = bpb.sectors_per_cluster * (data->blocksize >> BLK_SHR);
+
+ if (bpb.clusters_per_mft > 0)
+ data->mft_size = data->spc * bpb.clusters_per_mft;
+ else
+ data->mft_size = 1 << (-bpb.clusters_per_mft - BLK_SHR);
+
+ if (bpb.clusters_per_index > 0)
+ data->idx_size = data->spc * bpb.clusters_per_index;
+ else
+ data->idx_size = 1 << (-bpb.clusters_per_index - BLK_SHR);
+
+ data->mft_start = grub_le_to_cpu64 (bpb.mft_lcn) * data->spc;
+
+ if ((data->mft_size > MAX_MFT) || (data->idx_size > MAX_IDX))
+ goto fail;
+
+ data->mmft.data = data;
+ data->cmft.data = data;
+
+ data->mmft.buf = grub_malloc (data->mft_size << BLK_SHR);
+ if (!data->mmft.buf)
+ goto fail;
+
+ if (grub_disk_read
+ (disk, data->mft_start, 0, data->mft_size << BLK_SHR, data->mmft.buf))
+ goto fail;
+
+ data->uuid = grub_le_to_cpu64 (bpb.num_serial);
+
+ if (fixup (data, data->mmft.buf, data->mft_size, "FILE"))
+ goto fail;
+
+ if (!locate_attr (&data->mmft.attr, &data->mmft, AT_DATA))
+ goto fail;
+
+ if (init_file (&data->cmft, 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;
+}
+
+static grub_err_t
+grub_ntfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_ntfs_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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 hook (filename, &info);
+ }
+
+ 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,
+ 0, GRUB_FSHELP_DIR);
+
+ if (grub_errno)
+ goto fail;
+
+ grub_ntfs_iterate_dir (fdiro, iterate);
+
+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,
+ 0, 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, buf, file->offset, len, 1, file->read_hook);
+ return (grub_errno) ? 0 : 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;
+ char *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 << 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, AT_VOLUME_NAME);
+ if ((pa) && (pa[8] == 0) && (u32at (pa, 0x10)))
+ {
+ char *buf;
+ int len;
+
+ len = u32at (pa, 0x10) / 2;
+ buf = grub_malloc (len * 4 + 1);
+ pa += u16at (pa, 0x14);
+ *grub_utf16_to_utf8 ((grub_uint8_t *) buf, (grub_uint16_t *) pa, len) =
+ '\0';
+ *label = buf;
+ }
+
+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);
+ }
+ else
+ *uuid = NULL;
+
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+static struct grub_fs grub_ntfs_fs =
+ {
+ .name = "ntfs",
+ .dir = grub_ntfs_dir,
+ .open = grub_ntfs_open,
+ .read = grub_ntfs_read,
+ .close = grub_ntfs_close,
+ .label = grub_ntfs_label,
+ .uuid = grub_ntfs_uuid,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 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..d2893cb
--- /dev/null
+++ b/grub-core/fs/ntfscomp.c
@@ -0,0 +1,376 @@
+/* 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/fshelp.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][1] -
+ (cc->comp_table[cc->comp_head][0] - cc->cbuf_vcn)) * cc->spc, 0,
+ cc->spc << BLK_SHR, cc->cbuf))
+ return grub_errno;
+ cc->cbuf_vcn++;
+ if ((cc->cbuf_vcn >= cc->comp_table[cc->comp_head][0]))
+ cc->comp_head++;
+ cc->cbuf_ofs = 0;
+ return 0;
+}
+
+static grub_err_t
+decomp_getch (struct grub_ntfs_comp *cc, unsigned char *res)
+{
+ if (cc->cbuf_ofs >= (cc->spc << BLK_SHR))
+ {
+ if (decomp_nextvcn (cc))
+ return grub_errno;
+ }
+ *res = (unsigned char) cc->cbuf[cc->cbuf_ofs++];
+ return 0;
+}
+
+static grub_err_t
+decomp_get16 (struct grub_ntfs_comp *cc, grub_uint16_t * res)
+{
+ unsigned char 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, char *dest)
+{
+ grub_uint16_t flg, cnt;
+
+ if (decomp_get16 (cc, &flg))
+ return grub_errno;
+ cnt = (flg & 0xFFF) + 1;
+
+ if (dest)
+ {
+ if (flg & 0x8000)
+ {
+ unsigned char tag;
+ grub_uint32_t bits, copied;
+
+ bits = copied = tag = 0;
+ while (cnt > 0)
+ {
+ if (copied > 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;
+
+ 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
+ {
+ unsigned char ch = 0;
+
+ if (decomp_getch (cc, &ch))
+ return grub_errno;
+ dest[copied++] = ch;
+ cnt--;
+ }
+ tag >>= 1;
+ bits--;
+ }
+ return 0;
+ }
+ else
+ {
+ if (cnt != COM_LEN)
+ return grub_error (GRUB_ERR_BAD_FS,
+ "invalid compression block size");
+ }
+ }
+
+ while (cnt > 0)
+ {
+ int n;
+
+ n = (cc->spc << 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, char *buf, int num)
+{
+ int cpb = COM_SEC / ctx->comp.spc;
+
+ while (num)
+ {
+ int nn;
+
+ if ((ctx->target_vcn & 0xF) == 0)
+ {
+
+ if (ctx->comp.comp_head != ctx->comp.comp_tail)
+ 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 = (ctx->comp.spc << 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 & RF_BLNK)
+ break;
+ ctx->comp.comp_table[ctx->comp.comp_tail][0] = ctx->next_vcn;
+ ctx->comp.comp_table[ctx->comp.comp_tail][1] =
+ 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)) / cpb;
+ if (nn > num)
+ nn = num;
+ num -= nn;
+
+ if (ctx->flags & RF_BLNK)
+ {
+ ctx->target_vcn += nn * cpb;
+ if (ctx->comp.comp_tail == 0)
+ {
+ if (buf)
+ {
+ grub_memset (buf, 0, nn * COM_LEN);
+ buf += nn * COM_LEN;
+ }
+ }
+ else
+ {
+ while (nn)
+ {
+ if (decomp_block (&ctx->comp, buf))
+ return grub_errno;
+ if (buf)
+ buf += COM_LEN;
+ nn--;
+ }
+ }
+ }
+ else
+ {
+ nn *= cpb;
+ while ((ctx->comp.comp_head < ctx->comp.comp_tail) && (nn))
+ {
+ int tt;
+
+ tt =
+ ctx->comp.comp_table[ctx->comp.comp_head][0] -
+ 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][1] -
+ (ctx->comp.comp_table[ctx->comp.comp_head][0] -
+ ctx->target_vcn)) * ctx->comp.spc, 0,
+ tt * (ctx->comp.spc << BLK_SHR), buf))
+ return grub_errno;
+ buf += tt * (ctx->comp.spc << BLK_SHR);
+ }
+ nn -= tt;
+ if (ctx->target_vcn >=
+ ctx->comp.comp_table[ctx->comp.comp_head][0])
+ 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.spc, 0,
+ nn * (ctx->comp.spc << BLK_SHR), buf))
+ return grub_errno;
+ buf += nn * (ctx->comp.spc << BLK_SHR);
+ }
+ ctx->target_vcn += nn;
+ }
+ }
+ }
+ return 0;
+}
+
+static grub_err_t
+ntfscomp (struct grub_ntfs_attr *at, char *dest, grub_uint32_t ofs,
+ grub_uint32_t len, struct grub_ntfs_rlst *ctx, grub_uint32_t vcn)
+{
+ grub_err_t ret;
+
+ ctx->comp.comp_head = ctx->comp.comp_tail = 0;
+ ctx->comp.cbuf = grub_malloc ((ctx->comp.spc) << BLK_SHR);
+ if (!ctx->comp.cbuf)
+ return 0;
+
+ ret = 0;
+
+ //ctx->comp.disk->read_hook = read_hook;
+
+ if ((vcn > ctx->target_vcn) &&
+ (read_block
+ (ctx, NULL, ((vcn - ctx->target_vcn) * ctx->comp.spc) / COM_SEC)))
+ {
+ ret = grub_errno;
+ goto quit;
+ }
+
+ if (ofs % COM_LEN)
+ {
+ grub_uint32_t t, n, o;
+
+ t = ctx->target_vcn * (ctx->comp.spc << BLK_SHR);
+ if (read_block (ctx, at->sbuf, 1))
+ {
+ ret = grub_errno;
+ goto quit;
+ }
+
+ at->save_pos = t;
+
+ o = ofs % COM_LEN;
+ n = COM_LEN - o;
+ if (n > len)
+ n = len;
+ grub_memcpy (dest, &at->sbuf[o], n);
+ if (n == len)
+ goto quit;
+ dest += n;
+ len -= n;
+ }
+
+ if (read_block (ctx, dest, len / COM_LEN))
+ {
+ ret = grub_errno;
+ goto quit;
+ }
+
+ dest += (len / COM_LEN) * COM_LEN;
+ len = len % COM_LEN;
+ if (len)
+ {
+ grub_uint32_t t;
+
+ t = ctx->target_vcn * (ctx->comp.spc << BLK_SHR);
+ if (read_block (ctx, at->sbuf, 1))
+ {
+ ret = grub_errno;
+ goto quit;
+ }
+
+ at->save_pos = t;
+
+ grub_memcpy (dest, at->sbuf, len);
+ }
+
+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/reiserfs.c b/grub-core/fs/reiserfs.c
new file mode 100644
index 0000000..f2984f8
--- /dev/null
+++ b/grub-core/fs/reiserfs.c
@@ -0,0 +1,1384 @@
+/* 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>
+
+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 REISERFS_MAX_LABEL_LENGTH 16
+#define REISERFS_LABEL_OFFSET 0x64
+
+#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];
+} __attribute__ ((packed));
+
+struct grub_reiserfs_journal_header
+{
+ grub_uint32_t last_flush_uid;
+ grub_uint32_t unflushed_offset;
+ grub_uint32_t mount_id;
+} __attribute__ ((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];
+} __attribute__ ((packed));
+
+struct grub_reiserfs_commit_block
+{
+ grub_uint32_t id;
+ grub_uint32_t len;
+ grub_uint32_t real_blocks[0];
+} __attribute__ ((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;
+} __attribute__ ((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;
+} __attribute__ ((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;
+ } v1 __attribute__ ((packed));
+ struct
+ {
+ grub_uint64_t offset_type;
+ } v2 __attribute__ ((packed));
+ } u;
+} __attribute__ ((packed));
+
+struct grub_reiserfs_item_header
+{
+ struct grub_reiserfs_key key;
+ union
+ {
+ grub_uint16_t free_space;
+ grub_uint16_t entry_count;
+ } u __attribute__ ((packed));
+ grub_uint16_t item_size;
+ grub_uint16_t item_location;
+ grub_uint16_t version;
+} __attribute__ ((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;
+} __attribute__ ((packed));
+
+struct grub_reiserfs_disk_child
+{
+ grub_uint32_t block_number;
+ grub_uint16_t size;
+ grub_uint16_t reserved;
+} __attribute__ ((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;
+} __attribute__ ((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;
+ 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;
+};
+
+/* 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 (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 (~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)
+{
+ 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 (! data)
+ {
+ grub_error (GRUB_ERR_TEST_FAILURE, "data is NULL");
+ goto fail;
+ }
+
+ if (! key)
+ {
+ grub_error (GRUB_ERR_TEST_FAILURE, "key is NULL");
+ goto fail;
+ }
+
+ if (! item)
+ {
+ grub_error (GRUB_ERR_TEST_FAILURE, "item is NULL");
+ goto fail;
+ }
+
+ 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_FILE_READ_ERROR, "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
+ && (grub_reiserfs_compare_keys (key, &(item_headers[i].key))
+ != 0);
+ i++)
+ {
+#ifdef GRUB_REISERFS_DEBUG
+ if (key->directory_id == item_headers[i].key.directory_id && \
+ key->object_id == item_headers[i].key.object_id)
+ grub_printf("C");
+ else
+ grub_printf(" ");
+ grub_printf(" %03d/%03d ", i + 1, item_count);
+ grub_reiserfs_print_key (&(item_headers[i].key));
+#endif
+ }
+ if (i < item_count)
+ block_key = &(item_headers[i].key);
+ }
+ }
+ while (current_level > 1);
+
+ item->data = data;
+
+ if (i == item_count || grub_reiserfs_compare_keys (key, 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_uint16_t block_size;
+ grub_disk_addr_t block;
+ grub_off_t offset;
+ grub_size_t len;
+ struct grub_fshelp_node found;
+ struct grub_reiserfs_key key;
+
+ grub_memcpy (&key, &(node->header.key), sizeof (key));
+ grub_reiserfs_set_key_offset (&key, 1);
+ grub_reiserfs_set_key_type (&key, GRUB_REISERFS_DIRECT,
+ grub_reiserfs_get_key_version (&key));
+
+ if (grub_reiserfs_get_item (node->data, &key, &found) != GRUB_ERR_NONE)
+ goto fail;
+
+ if (found.block_number == 0)
+ goto fail;
+
+ block_size = grub_le_to_cpu16 (node->data->superblock.block_size);
+ len = grub_le_to_cpu16 (found.header.item_size);
+ block = found.block_number * (block_size >> GRUB_DISK_SECTOR_BITS);
+ offset = grub_le_to_cpu16 (found.header.item_location);
+
+ symlink_buffer = grub_malloc (len + 1);
+ if (! symlink_buffer)
+ goto fail;
+
+ grub_disk_read (node->data->disk, block, offset, len, symlink_buffer);
+ if (grub_errno)
+ goto fail;
+
+ symlink_buffer[len] = 0;
+ return symlink_buffer;
+
+ fail:
+ grub_free (symlink_buffer);
+ return 0;
+}
+
+/* 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,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ 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,
+ "grub_reiserfs_iterate_dir called on a non-directory item");
+ goto fail;
+ }
+ block_size = grub_le_to_cpu16 (data->superblock.block_size);
+ block_header = grub_malloc (block_size);
+ 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;
+
+#if 0
+ if (grub_le_to_cpu16 (block_header->level) != 1)
+ {
+ grub_error (GRUB_ERR_TEST_FAILURE,
+ "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);
+
+ if (entry_state & GRUB_REISERFS_VISIBLE_MASK)
+ {
+ grub_fshelp_node_t entry_item;
+ struct grub_reiserfs_key entry_key;
+ enum grub_reiserfs_item_type entry_type;
+ char *entry_name;
+
+ entry_name = (((char *) directory_headers)
+ + grub_le_to_cpu16 (directory_header->location));
+ 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)
+ != 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)
+ != 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
+ if ((grub_le_to_cpu16 (entry_v1_stat.mode) & S_IFLNK)
+ == S_IFLNK)
+ entry_type = GRUB_FSHELP_SYMLINK;
+ else
+ entry_type = GRUB_FSHELP_REG;
+ }
+ 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
+ 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 (grub_strcmp (entry_name, ".."))
+ grub_dprintf ("reiserfs",
+ "Warning : %s has no stat block !\n",
+ entry_name);
+ grub_free (entry_item);
+ goto next;
+ }
+ }
+ if (hook (entry_name, entry_type, entry_item))
+ {
+ grub_dprintf ("reiserfs", "Found : %s, type=%d\n",
+ entry_name, entry_type);
+ ret = 1;
+ goto found;
+ }
+
+next:
+ *entry_name = 0; /* Make sure next entry name (which is just
+ before this one in disk order) stops before
+ the current one. */
+ }
+ }
+
+ 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) != 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, info;
+ struct grub_reiserfs_key key;
+ grub_uint32_t block_number;
+ grub_uint16_t entry_version, block_size, entry_location;
+
+ grub_dl_ref (my_mod);
+ data = grub_reiserfs_mount (file->device->disk);
+ if (! data)
+ goto fail;
+ block_size = grub_le_to_cpu16 (data->superblock.block_size);
+ key.directory_id = grub_cpu_to_le32 (1);
+ key.object_id = grub_cpu_to_le32 (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) != 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;
+ key.directory_id = found->header.key.directory_id;
+ key.object_id = found->header.key.object_id;
+ grub_reiserfs_set_key_type (&key, GRUB_REISERFS_STAT, 2);
+ grub_reiserfs_set_key_offset (&key, 0);
+ if (grub_reiserfs_get_item (data, &key, &info) != GRUB_ERR_NONE)
+ goto fail;
+ if (info.block_number == 0)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "unable to find searched item");
+ goto fail;
+ }
+ entry_version = grub_le_to_cpu16 (info.header.version);
+ entry_location = grub_le_to_cpu16 (info.header.item_location);
+ block_number = info.block_number;
+ if (entry_version == 0) /* Version 1 stat item. */
+ {
+ struct grub_reiserfs_stat_item_v1 entry_v1_stat;
+ grub_disk_read (data->disk,
+ block_number * (block_size >> GRUB_DISK_SECTOR_BITS),
+ entry_location
+ + (((grub_off_t) block_number * block_size)
+ & (GRUB_DISK_SECTOR_SIZE - 1)),
+ sizeof (entry_v1_stat), &entry_v1_stat);
+ if (grub_errno)
+ goto fail;
+ file->size = (grub_off_t) grub_le_to_cpu64 (entry_v1_stat.size);
+ }
+ else
+ {
+ struct grub_reiserfs_stat_item_v2 entry_v2_stat;
+ grub_disk_read (data->disk,
+ block_number * (block_size >> GRUB_DISK_SECTOR_BITS),
+ entry_location
+ + (((grub_off_t) block_number * block_size)
+ & (GRUB_DISK_SECTOR_SIZE - 1)),
+ sizeof (entry_v2_stat), &entry_v2_stat);
+ if (grub_errno)
+ goto fail;
+ file->size = (grub_off_t) grub_le_to_cpu64 (entry_v2_stat.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);
+ grub_free (found);
+ grub_free (data);
+ grub_dl_unref (my_mod);
+ return grub_errno;
+}
+
+static grub_ssize_t
+grub_reiserfs_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ unsigned int indirect_block, indirect_block_count;
+ struct grub_reiserfs_key key;
+ struct grub_fshelp_node *node = file->data;
+ 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 = file->offset;
+ current_position = 0;
+ final_position = MIN (len + initial_position, file->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);
+ while (current_position < final_position)
+ {
+ grub_reiserfs_set_key_offset (&key, current_key_offset);
+
+ if (grub_reiserfs_get_item (data, &key, &found) != 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 = 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 = file->read_hook;
+ 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 = file->read_hook;
+ 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 0;
+}
+
+/* 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;
+}
+
+/* Call HOOK with each file under DIR. */
+static grub_err_t
+grub_reiserfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_reiserfs_data *data = 0;
+ struct grub_fshelp_node root, *found;
+ struct grub_reiserfs_key root_key;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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 hook (filename, &info);
+ }
+ grub_dl_ref (my_mod);
+ data = grub_reiserfs_mount (device->disk);
+ if (! data)
+ goto fail;
+ root_key.directory_id = grub_cpu_to_le32 (1);
+ root_key.object_id = grub_cpu_to_le32 (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) != 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, iterate);
+ 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)
+{
+ *label = grub_malloc (REISERFS_MAX_LABEL_LENGTH);
+ if (*label)
+ {
+ grub_disk_read (device->disk,
+ REISERFS_SUPER_BLOCK_OFFSET / GRUB_DISK_SECTOR_SIZE,
+ REISERFS_LABEL_OFFSET, REISERFS_MAX_LABEL_LENGTH,
+ *label);
+ }
+ 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);
+
+ data = grub_reiserfs_mount (disk);
+ if (data)
+ {
+ *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]));
+ }
+ else
+ *uuid = NULL;
+
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+static struct grub_fs grub_reiserfs_fs =
+ {
+ .name = "reiserfs",
+ .dir = grub_reiserfs_dir,
+ .open = grub_reiserfs_open,
+ .read = grub_reiserfs_read,
+ .close = grub_reiserfs_close,
+ .label = grub_reiserfs_label,
+ .uuid = grub_reiserfs_uuid,
+ .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/sfs.c b/grub-core/fs/sfs.c
new file mode 100644
index 0000000..4557431
--- /dev/null
+++ b/grub-core/fs/sfs.c
@@ -0,0 +1,599 @@
+/* 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>
+
+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;
+} __attribute__ ((packed));
+
+/* The sfs rootblock. */
+struct grub_sfs_rblock
+{
+ struct grub_sfs_bheader header;
+ grub_uint32_t version;
+ grub_uint8_t unused1[36];
+ grub_uint32_t blocksize;
+ grub_uint8_t unused2[40];
+ grub_uint8_t unused3[8];
+ grub_uint32_t rootobject;
+ grub_uint32_t btree;
+} __attribute__ ((packed));
+
+/* 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;
+ } file __attribute__ ((packed));
+ struct
+ {
+ grub_uint32_t hashtable;
+ grub_uint32_t dir_objc;
+ } dir __attribute__ ((packed));
+ } file_dir;
+ grub_uint8_t unused3[4];
+ grub_uint8_t type;
+ grub_uint8_t filename[1];
+ grub_uint8_t comment[1];
+} __attribute__ ((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];
+} __attribute__ ((packed));
+
+struct grub_sfs_btree_node
+{
+ grub_uint32_t key;
+ grub_uint32_t data;
+} __attribute__ ((packed));
+
+struct grub_sfs_btree_extent
+{
+ grub_uint32_t key;
+ grub_uint32_t next;
+ grub_uint32_t prev;
+ grub_uint16_t size;
+} __attribute__ ((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];
+} __attribute__ ((packed));
+
+
+
+struct grub_fshelp_node
+{
+ struct grub_sfs_data *data;
+ int block;
+ int size;
+};
+
+/* Information about a "mounted" sfs filesystem. */
+struct grub_sfs_data
+{
+ struct grub_sfs_rblock rblock;
+ struct grub_fshelp_node diropen;
+ grub_disk_t disk;
+
+ /* Blocksize in sectors. */
+ unsigned int blocksize;
+
+ /* 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,
+ int *size, int *nextext)
+{
+ char *treeblock;
+ struct grub_sfs_btree *tree;
+ int i;
+ int next;
+
+ treeblock = grub_malloc (data->blocksize);
+ if (!block)
+ return 0;
+
+ next = grub_be_to_cpu32 (data->rblock.btree);
+ tree = (struct grub_sfs_btree *) treeblock;
+
+ /* Handle this level in the btree. */
+ do
+ {
+ grub_disk_read (data->disk, next, 0, data->blocksize, treeblock);
+ if (grub_errno)
+ {
+ grub_free (treeblock);
+ return grub_errno;
+ }
+
+ for (i = grub_be_to_cpu16 (tree->nodes) - 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)
+{
+ int blk = node->block;
+ int size = 0;
+ int next = 0;
+
+ while (blk)
+ {
+ grub_err_t err;
+
+ /* In case of the first block we don't have to lookup the
+ extent, the minimum size is always 1. */
+ if (fileblock == 0)
+ return blk;
+
+ err = grub_sfs_read_extent (node->data, blk, &size, &next);
+ if (err)
+ return 0;
+
+ if (fileblock < (unsigned int) size)
+ return fileblock + blk;
+
+ fileblock -= 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ return grub_fshelp_read_file (node->data->disk, node, read_hook,
+ pos, len, buf, grub_sfs_read_block,
+ node->size, 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;
+ unsigned int blk;
+
+ 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))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "not a SFS filesystem");
+ goto fail;
+ }
+
+ data->blocksize = grub_be_to_cpu32 (data->rblock.blocksize);
+ rootobjc_data = grub_malloc (data->blocksize);
+ if (! rootobjc_data)
+ goto fail;
+
+ /* Read the root object container. */
+ grub_disk_read (disk, grub_be_to_cpu32 (data->rblock.rootobject), 0,
+ data->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->disk = disk;
+ data->label = grub_strdup ((char *) (rootobjc->objects[0].filename));
+
+ 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 (data->blocksize);
+ if (!block)
+ return 0;
+
+ grub_disk_read (data->disk, node->block, 0, data->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_strdup (&block[24]);
+ grub_free (block);
+ if (!symlink)
+ return 0;
+
+ return symlink;
+}
+
+static int
+grub_sfs_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ 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;
+ int pos;
+
+ auto int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name, int block,
+ int size, int type);
+
+ int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name, int block,
+ int size, int type)
+ {
+ node = grub_malloc (sizeof (*node));
+ if (!node)
+ return 1;
+
+ node->data = data;
+ node->size = size;
+ node->block = block;
+
+ return hook (name, type, node);
+ }
+
+ objc_data = grub_malloc (data->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, next, 0, data->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) < data->blocksize)
+ {
+ struct grub_sfs_obj *obj;
+ obj = (struct grub_sfs_obj *) ((char *) objc + pos);
+ char *filename = (char *) (obj->filename);
+ int len;
+ enum grub_fshelp_filetype type;
+ unsigned int 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 (grub_strlen (filename) == 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 (filename, block,
+ grub_be_to_cpu32 (obj->file_dir.file.size),
+ type))
+ {
+ 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)
+{
+ 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_sfs_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ struct grub_sfs_data *data = (struct grub_sfs_data *) file->data;
+
+ int size = grub_sfs_read_file (&data->diropen, file->read_hook,
+ file->offset, len, buf);
+
+ return size;
+}
+
+
+static grub_err_t
+grub_sfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_sfs_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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 hook (filename, &info);
+ }
+
+ 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, iterate);
+
+ 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)
+ *label = data->label;
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
+
+static struct grub_fs grub_sfs_fs =
+ {
+ .name = "sfs",
+ .dir = grub_sfs_dir,
+ .open = grub_sfs_open,
+ .read = grub_sfs_read,
+ .close = grub_sfs_close,
+ .label = grub_sfs_label,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 0,
+#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/tar.c b/grub-core/fs/tar.c
new file mode 100644
index 0000000..6ab62bc
--- /dev/null
+++ b/grub-core/fs/tar.c
@@ -0,0 +1,2 @@
+#define MODE_USTAR 1
+#include "cpio.c"
diff --git a/grub-core/fs/udf.c b/grub-core/fs/udf.c
new file mode 100644
index 0000000..5842d5d
--- /dev/null
+++ b/grub-core/fs/udf.c
@@ -0,0 +1,1061 @@
+/* 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>
+
+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;
+} __attribute__ ((packed));
+
+struct grub_udf_short_ad
+{
+ grub_uint32_t length;
+ grub_uint32_t position;
+} __attribute__ ((packed));
+
+struct grub_udf_long_ad
+{
+ grub_uint32_t length;
+ struct grub_udf_lb_addr block;
+ grub_uint8_t imp_use[6];
+} __attribute__ ((packed));
+
+struct grub_udf_extent_ad
+{
+ grub_uint32_t length;
+ grub_uint32_t start;
+} __attribute__ ((packed));
+
+struct grub_udf_charspec
+{
+ grub_uint8_t charset_type;
+ grub_uint8_t charset_info[63];
+} __attribute__ ((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;
+} __attribute__ ((packed));
+
+struct grub_udf_regid
+{
+ grub_uint8_t flags;
+ grub_uint8_t ident[23];
+ grub_uint8_t ident_suffix[8];
+} __attribute__ ((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;
+} __attribute__ ((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;
+} __attribute__ ((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;
+} __attribute__ ((packed));
+
+struct grub_udf_file_ident
+{
+ struct grub_udf_tag tag;
+ grub_uint16_t version_num;
+ grub_uint8_t characteristics;
+ grub_uint8_t file_ident_length;
+ struct grub_udf_long_ad icb;
+ grub_uint16_t imp_use_length;
+} __attribute__ ((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[1872];
+} __attribute__ ((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[1832];
+} __attribute__ ((packed));
+
+struct grub_udf_vrs
+{
+ grub_uint8_t type;
+ grub_uint8_t magic[5];
+ grub_uint8_t version;
+} __attribute__ ((packed));
+
+struct grub_udf_avdp
+{
+ struct grub_udf_tag tag;
+ struct grub_udf_extent_ad vds;
+} __attribute__ ((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;
+} __attribute__ ((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;
+ };
+};
+
+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];
+} __attribute__ ((packed));
+
+struct grub_udf_aed
+{
+ struct grub_udf_tag tag;
+ grub_uint32_t prev_ae;
+ grub_uint32_t ae_len;
+} __attribute__ ((packed));
+
+struct grub_udf_data
+{
+ grub_disk_t disk;
+ 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;
+ union
+ {
+ struct grub_udf_file_entry fe;
+ struct grub_udf_extended_file_entry efe;
+ };
+ int part_ref;
+};
+
+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,
+ sizeof (struct grub_udf_file_entry),
+ &node->fe))
+ return grub_errno;
+
+ if ((U16 (node->fe.tag.tag_ident) != GRUB_UDF_TAG_IDENT_FE) &&
+ (U16 (node->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->fe.tag.tag_ident))
+ {
+ case GRUB_UDF_TAG_IDENT_FE:
+ ptr = (char *) &node->fe.ext_attr[0] + U32 (node->fe.ext_attr_length);
+ len = U32 (node->fe.alloc_descs_length);
+ break;
+
+ case GRUB_UDF_TAG_IDENT_EFE:
+ ptr = (char *) &node->efe.ext_attr[0] + U32 (node->efe.ext_attr_length);
+ len = U32 (node->efe.alloc_descs_length);
+ break;
+
+ default:
+ grub_error (GRUB_ERR_BAD_FS, "invalid file entry");
+ return 0;
+ }
+
+ if ((U16 (node->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:
+ if (buf)
+ grub_free (buf);
+
+ return 0;
+}
+
+static grub_ssize_t
+grub_udf_read_file (grub_fshelp_node_t node,
+ void NESTED_FUNC_ATTR
+ (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ grub_off_t pos, grub_size_t len, char *buf)
+{
+ switch (U16 (node->fe.icbtag.flags) & GRUB_UDF_ICBTAG_FLAG_AD_MASK)
+ {
+ case GRUB_UDF_ICBTAG_FLAG_AD_IN_ICB:
+ {
+ char *ptr;
+
+ ptr = ((U16 (node->fe.tag.tag_ident) == GRUB_UDF_TAG_IDENT_FE) ?
+ ((char *) &node->fe.ext_attr[0]
+ + U32 (node->fe.ext_attr_length)) :
+ ((char *) &node->efe.ext_attr[0]
+ + U32 (node->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,
+ pos, len, buf, grub_udf_read_block,
+ U64 (node->fe.file_size),
+ node->data->lbshift);
+}
+
+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_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;
+}
+
+static char *
+read_string (grub_uint8_t *raw, grub_size_t sz)
+{
+ grub_uint16_t *utf16 = NULL;
+ char *ret;
+ grub_size_t utf16len = 0;
+
+ if (raw[0] != 8 && raw[0] != 16)
+ return NULL;
+
+ if (raw[0] == 8)
+ {
+ unsigned i;
+ utf16len = sz - 1;
+ utf16 = grub_malloc (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_malloc (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];
+ }
+ ret = grub_malloc (utf16len * 3 + 1);
+ if (ret)
+ *grub_utf16_to_utf8 ((grub_uint8_t *) ret, utf16, utf16len) = '\0';
+ grub_free (utf16);
+ return ret;
+}
+
+static int
+grub_udf_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ grub_fshelp_node_t child;
+ struct grub_udf_file_ident dirent;
+ grub_off_t offset = 0;
+
+ child = grub_malloc (sizeof (struct grub_fshelp_node));
+ if (!child)
+ return 0;
+
+ /* The current directory is not stored. */
+ grub_memcpy ((char *) child, (char *) dir,
+ sizeof (struct grub_fshelp_node));
+
+ if (hook (".", GRUB_FSHELP_DIR, child))
+ return 1;
+
+ while (offset < U64 (dir->fe.file_size))
+ {
+ if (grub_udf_read_file (dir, 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 (sizeof (struct grub_fshelp_node));
+ if (!child)
+ return 0;
+
+ if (grub_udf_read_icb (dir->data, &dirent.icb, child))
+ return 0;
+
+ if (dirent.characteristics & GRUB_UDF_FID_CHAR_PARENT)
+ {
+ /* This is the parent directory. */
+ if (hook ("..", GRUB_FSHELP_DIR, child))
+ return 1;
+ }
+ else
+ {
+ enum grub_fshelp_filetype type;
+ char *filename;
+ grub_uint8_t raw[dirent.file_ident_length];
+
+ type = ((dirent.characteristics & GRUB_UDF_FID_CHAR_DIRECTORY) ?
+ (GRUB_FSHELP_DIR) : (GRUB_FSHELP_REG));
+
+ if ((grub_udf_read_file (dir, 0, offset,
+ dirent.file_ident_length,
+ (char *) raw))
+ != dirent.file_ident_length)
+ return 0;
+
+ filename = read_string (raw, dirent.file_ident_length);
+ if (!filename)
+ grub_print_error ();
+
+ if (filename && hook (filename, type, child))
+ {
+ grub_free (filename);
+ return 1;
+ }
+ grub_free (filename);
+ }
+ }
+
+ /* Align to dword boundary. */
+ offset = (offset + dirent.file_ident_length + 3) & (~3);
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_udf_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_udf_data *data = 0;
+ struct grub_fshelp_node rootnode;
+ struct grub_fshelp_node *foundnode;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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 hook (filename, &info);
+ }
+
+ grub_dl_ref (my_mod);
+
+ data = grub_udf_mount (device->disk);
+ if (!data)
+ 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, 0, GRUB_FSHELP_DIR))
+ goto fail;
+
+ grub_udf_iterate_dir (foundnode, iterate);
+
+ if (foundnode != &rootnode)
+ grub_free (foundnode);
+
+fail:
+ 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;
+ struct grub_fshelp_node *foundnode;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_udf_mount (file->device->disk);
+ if (!data)
+ 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, 0, GRUB_FSHELP_REG))
+ goto fail;
+
+ file->data = foundnode;
+ file->offset = 0;
+ file->size = U64 (foundnode->fe.file_size);
+
+ return 0;
+
+fail:
+ grub_dl_unref (my_mod);
+
+ grub_free (data);
+
+ 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->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_string (data->lvd.ident, sizeof (data->lvd.ident));
+ grub_free (data);
+ }
+ else
+ *label = 0;
+
+ return grub_errno;
+}
+
+static struct grub_fs grub_udf_fs = {
+ .name = "udf",
+ .dir = grub_udf_dir,
+ .open = grub_udf_open,
+ .read = grub_udf_read,
+ .close = grub_udf_close,
+ .label = grub_udf_label,
+ .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..86fe8af
--- /dev/null
+++ b/grub-core/fs/ufs.c
@@ -0,0 +1,812 @@
+/* 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>
+
+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
+
+/* Calculate in which group the inode can be found. */
+#define UFS_BLKSZ(sblock) (grub_le_to_cpu32 (sblock->bsize))
+
+#define INODE(data,field) data->inode. field
+#ifdef MODE_UFS2
+#define INODE_ENDIAN(data,field,bits1,bits2) grub_le_to_cpu##bits2 (data->inode.field)
+#else
+#define INODE_ENDIAN(data,field,bits1,bits2) grub_le_to_cpu##bits1 (data->inode.field)
+#endif
+
+#define INODE_SIZE(data) INODE_ENDIAN (data,size,32,64)
+#define INODE_NBLOCKS(data) INODE_ENDIAN (data,nblocks,32,64)
+
+#define INODE_MODE(data) INODE_ENDIAN (data,mode,16,16)
+#ifdef MODE_UFS2
+#define INODE_BLKSZ 8
+#else
+#define INODE_BLKSZ 4
+#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_int64_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_sec;
+ grub_uint32_t mtime_sec;
+ grub_uint32_t ctime_sec;
+ 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];
+} __attribute__ ((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_int64_t size;
+ grub_uint64_t atime;
+ grub_uint64_t mtime;
+ grub_uint64_t ctime;
+ 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];
+} __attribute__ ((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;
+ };
+ };
+} __attribute__ ((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;
+};
+
+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, unsigned int blk)
+{
+ struct grub_ufs_sblock *sblock = &data->sblock;
+ unsigned int indirsz;
+ int log2_blksz;
+
+ /* Direct. */
+ if (blk < GRUB_UFS_DIRBLKS)
+ return INODE_DIRBLOCKS (data, blk);
+
+ log2_blksz = grub_le_to_cpu32 (data->sblock.log2_blksz);
+
+ blk -= GRUB_UFS_DIRBLKS;
+
+ indirsz = UFS_BLKSZ (sblock) / INODE_BLKSZ;
+ /* Single indirect block. */
+ if (blk < indirsz)
+ {
+#ifdef MODE_UFS2
+ grub_uint64_t indir[UFS_BLKSZ (sblock) / sizeof (grub_uint64_t)];
+#else
+ grub_uint32_t indir[UFS_BLKSZ (sblock) / sizeof (grub_uint32_t)];
+#endif
+ grub_disk_read (data->disk, INODE_INDIRBLOCKS (data, 0) << log2_blksz,
+ 0, sizeof (indir), indir);
+ return indir[blk];
+ }
+ blk -= indirsz;
+
+ /* Double indirect block. */
+ if (blk < indirsz * indirsz)
+ {
+#ifdef MODE_UFS2
+ grub_uint64_t indir[UFS_BLKSZ (sblock) / sizeof (grub_uint64_t)];
+#else
+ grub_uint32_t indir[UFS_BLKSZ (sblock) / sizeof (grub_uint32_t)];
+#endif
+
+ grub_disk_read (data->disk, INODE_INDIRBLOCKS (data, 1) << log2_blksz,
+ 0, sizeof (indir), indir);
+ grub_disk_read (data->disk,
+ (indir [blk / indirsz])
+ << log2_blksz,
+ 0, sizeof (indir), indir);
+
+ return indir[blk % indirsz];
+ }
+
+
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "ufs does not support triple 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ struct grub_ufs_sblock *sblock = &data->sblock;
+ int i;
+ int 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_BLKSZ (sblock);
+
+ for (i = pos / UFS_BLKSZ (sblock); i < blockcnt; i++)
+ {
+ int blknr;
+ int blockoff = pos % UFS_BLKSZ (sblock);
+ int blockend = UFS_BLKSZ (sblock);
+
+ int skipfirst = 0;
+
+ 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);
+
+ if (!blockend)
+ blockend = UFS_BLKSZ (sblock);
+ }
+
+ /* First block. */
+ if (i == (pos / (int) UFS_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;
+ grub_disk_read (data->disk,
+ blknr << grub_le_to_cpu32 (data->sblock.log2_blksz),
+ skipfirst, blockend, buf);
+ data->disk->read_hook = 0;
+ if (grub_errno)
+ return -1;
+ }
+ else
+ grub_memset (buf, UFS_BLKSZ (sblock) - skipfirst, 0);
+
+ 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_le_to_cpu32 (sblock->ino_per_group);
+
+ /* Determine the inode within the group. */
+ int grpino = ino % grub_le_to_cpu32 (sblock->ino_per_group);
+
+ /* The first block of the group. */
+ int grpblk = group * (grub_le_to_cpu32 (sblock->frags_per_group));
+
+#ifndef MODE_UFS2
+ grpblk += grub_le_to_cpu32 (sblock->cylg_offset)
+ * (group & (~grub_le_to_cpu32 (sblock->cylg_mask)));
+#endif
+
+ if (!inode)
+ {
+ inode = (char *) &data->inode;
+ data->ino = ino;
+ }
+
+ grub_disk_read (data->disk,
+ ((grub_le_to_cpu32 (sblock->inoblk_offs) + grpblk)
+ << grub_le_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[INODE_SIZE (data)];
+
+ if (++data->linknest > GRUB_UFS_MAX_SYMLNK_CNT)
+ return grub_error (GRUB_ERR_SYMLINK_LOOP, "too deep nesting of symlinks");
+
+ if (INODE_NBLOCKS (data) == 0)
+ grub_strcpy (symlink, (char *) INODE (data, symlink));
+ else
+ {
+ grub_disk_read (data->disk,
+ (INODE_DIRBLOCKS (data, 0)
+ << grub_le_to_cpu32 (data->sblock.log2_blksz)),
+ 0, INODE_SIZE (data), symlink);
+ symlink[INODE_SIZE (data)] = '\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))
+ return grub_errno;
+
+ grub_ufs_find_file (data, symlink);
+ if (grub_errno)
+ grub_error (grub_errno, "cannot follow symlink `%s'", 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)
+{
+ char fpath[grub_strlen (path) + 1];
+ char *name = fpath;
+ char *next;
+ unsigned int pos = 0;
+ int dirino;
+
+ grub_strcpy (fpath, path);
+
+ /* Skip the first slash. */
+ if (name[0] == '/')
+ {
+ name++;
+ if (!*name)
+ return 0;
+ }
+
+ /* Extract the actual part from the pathname. */
+ next = grub_strchr (name, '/');
+ if (next)
+ {
+ next[0] = '\0';
+ next++;
+ }
+
+ do
+ {
+ struct grub_ufs_dirent dirent;
+ int namelen;
+
+ if (grub_strlen (name) == 0)
+ return GRUB_ERR_NONE;
+
+ if (grub_ufs_read_file (data, 0, pos, sizeof (dirent),
+ (char *) &dirent) < 0)
+ return grub_errno;
+
+#ifdef MODE_UFS2
+ namelen = dirent.namelen_bsd;
+#else
+ namelen = grub_le_to_cpu16 (dirent.namelen);
+#endif
+ {
+ char filename[namelen + 1];
+
+ if (grub_ufs_read_file (data, 0, pos + sizeof (dirent),
+ namelen, filename) < 0)
+ return grub_errno;
+
+ filename[namelen] = '\0';
+
+ if (!grub_strcmp (name, filename))
+ {
+ dirino = data->ino;
+ grub_ufs_read_inode (data, grub_le_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)
+ return grub_errno;
+ }
+
+ if (!next)
+ return 0;
+
+ pos = 0;
+
+ name = next;
+ next = grub_strchr (name, '/');
+ if (next)
+ {
+ next[0] = '\0';
+ next++;
+ }
+
+ if ((INODE_MODE(data) & GRUB_UFS_ATTR_TYPE) != GRUB_UFS_ATTR_DIR)
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+
+ continue;
+ }
+ }
+
+ pos += grub_le_to_cpu16 (dirent.direntlen);
+ } while (pos < INODE_SIZE (data));
+
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ 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;
+
+ if (grub_le_to_cpu32 (data->sblock.magic) == GRUB_UFS_MAGIC)
+ {
+ 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,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ 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, "bad filename");
+ 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, "not a directory");
+ goto fail;
+ }
+
+ while (pos < INODE_SIZE (data))
+ {
+ struct grub_ufs_dirent dirent;
+ int namelen;
+
+ if (grub_ufs_read_file (data, 0, pos, sizeof (dirent),
+ (char *) &dirent) < 0)
+ break;
+
+#ifdef MODE_UFS2
+ namelen = dirent.namelen_bsd;
+#else
+ namelen = grub_le_to_cpu16 (dirent.namelen);
+#endif
+
+ {
+ char filename[namelen + 1];
+ struct grub_dirhook_info info;
+ struct grub_ufs_inode inode;
+
+ grub_memset (&info, 0, sizeof (info));
+
+ if (grub_ufs_read_file (data, 0, pos + sizeof (dirent),
+ namelen, filename) < 0)
+ break;
+
+ filename[namelen] = '\0';
+ grub_ufs_read_inode (data, dirent.ino, (char *) &inode);
+
+ info.dir = ((grub_le_to_cpu16 (inode.mode) & GRUB_UFS_ATTR_TYPE)
+ == GRUB_UFS_ATTR_DIR);
+ info.mtime = grub_le_to_cpu64 (inode.mtime);
+ info.mtimeset = 1;
+
+ if (hook (filename, &info))
+ break;
+ }
+
+ pos += grub_le_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, "bad filename");
+ 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->offset, len, buf);
+}
+
+
+static grub_err_t
+grub_ufs_close (grub_file_t file)
+{
+ grub_free (file->data);
+
+ return GRUB_ERR_NONE;
+}
+
+
+#ifdef MODE_UFS2
+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;
+}
+#endif
+
+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_le_to_cpu32 (data->sblock.uuidhi),
+ (unsigned) grub_le_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_int32_t *tm)
+{
+ struct grub_ufs_data *data = 0;
+
+ grub_dl_ref (my_mod);
+
+ data = grub_ufs_mount (device->disk);
+ if (!data)
+ *tm = 0;
+ else
+#ifdef MODE_UFS2
+ *tm = grub_le_to_cpu64 (data->sblock.mtime2);
+#else
+ *tm = grub_le_to_cpu32 (data->sblock.mtime);
+#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
+ .name = "ufs1",
+#endif
+ .dir = grub_ufs_dir,
+ .open = grub_ufs_open,
+ .read = grub_ufs_read,
+ .close = grub_ufs_close,
+#ifdef MODE_UFS2
+ .label = grub_ufs_label,
+#endif
+ .uuid = grub_ufs_uuid,
+ .mtime = grub_ufs_mtime,
+ .next = 0
+ };
+
+#ifdef MODE_UFS2
+GRUB_MOD_INIT(ufs2)
+#else
+GRUB_MOD_INIT(ufs1)
+#endif
+{
+ grub_fs_register (&grub_ufs_fs);
+ my_mod = mod;
+}
+
+#ifdef MODE_UFS2
+GRUB_MOD_FINI(ufs2)
+#else
+GRUB_MOD_FINI(ufs1)
+#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/xfs.c b/grub-core/fs/xfs.c
new file mode 100644
index 0000000..2eadc37
--- /dev/null
+++ b/grub-core/fs/xfs.c
@@ -0,0 +1,837 @@
+/* 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/types.h>
+#include <grub/fshelp.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
+
+
+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[20];
+ 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;
+} __attribute__ ((packed));
+
+struct grub_xfs_dir_header
+{
+ grub_uint8_t count;
+ grub_uint8_t smallino;
+ union
+ {
+ grub_uint32_t i4;
+ grub_uint64_t i8;
+ } parent __attribute__ ((packed));
+} __attribute__ ((packed));
+
+struct grub_xfs_dir_entry
+{
+ grub_uint8_t len;
+ grub_uint16_t offset;
+ char name[1];
+ /* Inode number follows, 32 bits. */
+} __attribute__ ((packed));
+
+struct grub_xfs_dir2_entry
+{
+ grub_uint64_t inode;
+ grub_uint8_t len;
+} __attribute__ ((packed));
+
+typedef grub_uint32_t grub_xfs_extent[4];
+
+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;
+ grub_uint64_t keys[1];
+} __attribute__ ((packed));
+
+struct grub_xfs_btree_root
+{
+ grub_uint16_t level;
+ grub_uint16_t numrecs;
+ grub_uint64_t keys[1];
+} __attribute__ ((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[50];
+ grub_uint64_t size;
+ grub_uint64_t nblocks;
+ grub_uint32_t extsize;
+ grub_uint32_t nextents;
+ grub_uint8_t unused3[20];
+ union
+ {
+ char raw[156];
+ struct dir
+ {
+ struct grub_xfs_dir_header dirhead;
+ struct grub_xfs_dir_entry direntry[1];
+ } dir;
+ grub_xfs_extent extents[XFS_INODE_EXTENTS];
+ struct grub_xfs_btree_root btree;
+ } data __attribute__ ((packed));
+} __attribute__ ((packed));
+
+struct grub_xfs_dirblock_tail
+{
+ grub_uint32_t leaf_count;
+ grub_uint32_t leaf_stale;
+} __attribute__ ((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;
+ int agsize;
+ struct grub_fshelp_node diropen;
+};
+
+static grub_dl_t my_mod;
+
+
+
+/* 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
+
+#define GRUB_XFS_INO_AGBITS(data) \
+ ((data)->sblock.log2_agblk + (data)->sblock.log2_inop)
+#define GRUB_XFS_INO_INOINAG(data, ino) \
+ (grub_be_to_cpu64 (ino) & ((1LL << GRUB_XFS_INO_AGBITS (data)) - 1))
+#define GRUB_XFS_INO_AG(data,ino) \
+ (grub_be_to_cpu64 (ino) >> GRUB_XFS_INO_AGBITS (data))
+
+#define GRUB_XFS_FSB_TO_BLOCK(data, fsb) \
+ (((fsb) >> (data)->sblock.log2_agblk) * (data)->agsize \
+ + ((fsb) & ((1LL << (data)->sblock.log2_agblk) - 1)))
+
+#define GRUB_XFS_EXTENT_OFFSET(exts,ex) \
+ ((grub_be_to_cpu32 (exts[ex][0]) & ~(1 << 31)) << 23 \
+ | grub_be_to_cpu32 (exts[ex][1]) >> 9)
+
+#define GRUB_XFS_EXTENT_BLOCK(exts,ex) \
+ ((grub_uint64_t) (grub_be_to_cpu32 (exts[ex][1]) \
+ & (0x1ff)) << 43 \
+ | (grub_uint64_t) grub_be_to_cpu32 (exts[ex][2]) << 11 \
+ | grub_be_to_cpu32 (exts[ex][3]) >> 21)
+
+#define GRUB_XFS_EXTENT_SIZE(exts,ex) \
+ (grub_be_to_cpu32 (exts[ex][3]) & ((1 << 20) - 1))
+
+#define GRUB_XFS_ROUND_TO_DIRENT(pos) ((((pos) + 8 - 1) / 8) * 8)
+#define GRUB_XFS_NEXT_DIRENT(pos,len) \
+ (pos) + GRUB_XFS_ROUND_TO_DIRENT (8 + 1 + len + 2)
+
+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 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);
+
+ /* Read the inode. */
+ if (grub_disk_read (data->disk, block, offset,
+ 1 << data->sblock.log2_inode, 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_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;
+ grub_xfs_extent *exts;
+ grub_uint64_t ret = 0;
+
+ if (node->inode.format == XFS_INODE_FORMAT_BTREE)
+ {
+ grub_uint64_t *keys;
+
+ leaf = grub_malloc (node->data->sblock.bsize);
+ if (leaf == 0)
+ return 0;
+
+ nrec = grub_be_to_cpu16 (node->inode.data.btree.numrecs);
+ keys = &node->inode.data.btree.keys[0];
+ do
+ {
+ int i;
+
+ for (i = 0; i < nrec; i++)
+ {
+ if (fileblock < grub_be_to_cpu64 (keys[i]))
+ break;
+ }
+
+ /* Sparse block. */
+ if (i == 0)
+ {
+ grub_free (leaf);
+ return 0;
+ }
+
+ if (grub_disk_read (node->data->disk,
+ grub_be_to_cpu64 (keys[i - 1 + nrec])
+ << (node->data->sblock.log2_bsize
+ - GRUB_DISK_SECTOR_BITS),
+ 0, node->data->sblock.bsize, leaf))
+ return 0;
+
+ if (grub_strncmp ((char *) leaf->magic, "BMAP", 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 = &leaf->keys[0];
+ } while (leaf->level);
+ exts = (grub_xfs_extent *) keys;
+ }
+ else if (node->inode.format == XFS_INODE_FORMAT_EXT)
+ {
+ nrec = grub_be_to_cpu32 (node->inode.nextents);
+ exts = &node->inode.data.extents[0];
+ }
+ 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;
+ }
+ }
+
+ if (leaf)
+ 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,
+ void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
+ unsigned offset, unsigned length),
+ int pos, grub_size_t len, char *buf)
+{
+ return grub_fshelp_read_file (node->data->disk, node, read_hook,
+ pos, len, buf, grub_xfs_read_block,
+ grub_be_to_cpu64 (node->inode.size),
+ node->data->sblock.log2_bsize
+ - GRUB_DISK_SECTOR_BITS);
+}
+
+
+static char *
+grub_xfs_read_symlink (grub_fshelp_node_t node)
+{
+ int size = grub_be_to_cpu64 (node->inode.size);
+
+ switch (node->inode.format)
+ {
+ case XFS_INODE_FORMAT_INO:
+ return grub_strndup (node->inode.data.raw, size);
+
+ case XFS_INODE_FORMAT_EXT:
+ {
+ char *symlink;
+ grub_ssize_t numread;
+
+ symlink = grub_malloc (size + 1);
+ if (!symlink)
+ return 0;
+
+ numread = grub_xfs_read_file (node, 0, 0, size, symlink);
+ 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;
+}
+
+
+static int
+grub_xfs_iterate_dir (grub_fshelp_node_t dir,
+ int NESTED_FUNC_ATTR
+ (*hook) (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node))
+{
+ struct grub_fshelp_node *diro = (struct grub_fshelp_node *) dir;
+ auto int NESTED_FUNC_ATTR call_hook (grub_uint64_t ino, char *filename);
+
+ int NESTED_FUNC_ATTR call_hook (grub_uint64_t ino, char *filename)
+ {
+ struct grub_fshelp_node *fdiro;
+
+ fdiro = grub_malloc (sizeof (struct grub_fshelp_node)
+ - sizeof (struct grub_xfs_inode)
+ + (1 << diro->data->sblock.log2_inode));
+ if (!fdiro)
+ return 0;
+
+ /* The inode should be read, otherwise the filetype can
+ not be determined. */
+ fdiro->ino = ino;
+ fdiro->inode_read = 1;
+ fdiro->data = diro->data;
+ grub_xfs_read_inode (diro->data, ino, &fdiro->inode);
+
+ return hook (filename,
+ grub_xfs_mode_to_filetype (fdiro->inode.mode),
+ fdiro);
+ }
+
+ switch (diro->inode.format)
+ {
+ case XFS_INODE_FORMAT_INO:
+ {
+ struct grub_xfs_dir_entry *de = &diro->inode.data.dir.direntry[0];
+ int smallino = !diro->inode.data.dir.dirhead.smallino;
+ 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 (diro->inode.data.dir.dirhead.parent.i4);
+ parent = grub_cpu_to_be64 (parent);
+ /* The header is a bit smaller than usual. */
+ de = (struct grub_xfs_dir_entry *) ((char *) de - 4);
+ }
+ else
+ {
+ parent = diro->inode.data.dir.dirhead.parent.i8;
+ }
+
+ /* Synthesize the direntries for `.' and `..'. */
+ if (call_hook (diro->ino, "."))
+ return 1;
+
+ if (call_hook (parent, ".."))
+ return 1;
+
+ for (i = 0; i < diro->inode.data.dir.dirhead.count; i++)
+ {
+ grub_uint64_t ino;
+ grub_uint8_t *inopos = (((grub_uint8_t *) de)
+ + sizeof (struct grub_xfs_dir_entry)
+ + de->len - 1);
+ char name[de->len + 1];
+
+ /* 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);
+ ino = grub_cpu_to_be64 (ino);
+
+ grub_memcpy (name, de->name, de->len);
+ name[de->len] = '\0';
+ if (call_hook (ino, name))
+ return 1;
+
+ de = ((struct grub_xfs_dir_entry *)
+ (((char *) de)+ sizeof (struct grub_xfs_dir_entry) + de->len
+ + ((smallino ? sizeof (grub_uint32_t)
+ : sizeof (grub_uint64_t))) - 1));
+ }
+ 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++)
+ {
+ /* The header is skipped, the first direntry is stored
+ from byte 16. */
+ int pos = 16;
+ int entries;
+ int tail_start = (dirblk_size
+ - sizeof (struct grub_xfs_dirblock_tail));
+
+ struct grub_xfs_dirblock_tail *tail;
+ tail = (struct grub_xfs_dirblock_tail *) &dirblock[tail_start];
+
+ numread = grub_xfs_read_file (dir, 0,
+ blk << dirblk_log2,
+ dirblk_size, dirblock);
+ if (numread != dirblk_size)
+ return 0;
+
+ entries = (grub_be_to_cpu32 (tail->leaf_count)
+ - grub_be_to_cpu32 (tail->leaf_stale));
+
+ /* Iterate over all entries within this block. */
+ while (pos < (dirblk_size
+ - (int) sizeof (struct grub_xfs_dir2_entry)))
+ {
+ struct grub_xfs_dir2_entry *direntry;
+ grub_uint16_t *freetag;
+ char *filename;
+
+ direntry = (struct grub_xfs_dir2_entry *) &dirblock[pos];
+ freetag = (grub_uint16_t *) direntry;
+
+ if (*freetag == 0XFFFF)
+ {
+ grub_uint16_t *skip = (grub_uint16_t *) (freetag + 1);
+
+ /* This entry is not used, go to the next one. */
+ pos += grub_be_to_cpu16 (*skip);
+
+ continue;
+ }
+
+ filename = &dirblock[pos + sizeof (*direntry)];
+ /* The byte after the filename is for the tag, which
+ is not used by GRUB. So it can be overwritten. */
+ filename[direntry->len] = '\0';
+
+ if (call_hook (direntry->inode, filename))
+ {
+ grub_free (dirblock);
+ return 1;
+ }
+
+ /* Check if last direntry in this block is
+ reached. */
+ entries--;
+ if (!entries)
+ break;
+
+ /* Select the next directory entry. */
+ pos = GRUB_XFS_NEXT_DIRENT (pos, direntry->len);
+ pos = GRUB_XFS_ROUND_TO_DIRENT (pos);
+ }
+ }
+ 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;
+
+ data = grub_zalloc (sizeof (struct grub_xfs_data));
+ if (!data)
+ return 0;
+
+ /* Read the superblock. */
+ if (grub_disk_read (disk, 0, 0,
+ sizeof (struct grub_xfs_sblock), &data->sblock))
+ goto fail;
+
+ if (grub_strncmp ((char *) (data->sblock.magic), "XFSB", 4))
+ {
+ grub_error (GRUB_ERR_BAD_FS, "not a XFS filesystem");
+ goto fail;
+ }
+
+ data = grub_realloc (data,
+ sizeof (struct grub_xfs_data)
+ - sizeof (struct grub_xfs_inode)
+ + (1 << data->sblock.log2_inode));
+
+ if (! data)
+ goto fail;
+
+ data->diropen.data = data;
+ data->diropen.ino = 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->disk = disk;
+ data->pos = 0;
+
+ 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;
+}
+
+
+static grub_err_t
+grub_xfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *filename,
+ const struct grub_dirhook_info *info))
+{
+ struct grub_xfs_data *data = 0;
+ struct grub_fshelp_node *fdiro = 0;
+
+ auto int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node);
+
+ int NESTED_FUNC_ATTR iterate (const char *filename,
+ enum grub_fshelp_filetype filetype,
+ grub_fshelp_node_t node)
+ {
+ 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 hook (filename, &info);
+ }
+
+ 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, iterate);
+
+ 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,
+ sizeof (struct grub_fshelp_node)
+ - sizeof (struct grub_xfs_inode)
+ + (1 << data->sblock.log2_inode));
+
+ 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->offset, len, buf);
+}
+
+
+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",
+ .dir = grub_xfs_dir,
+ .open = grub_xfs_open,
+ .read = grub_xfs_read,
+ .close = grub_xfs_close,
+ .label = grub_xfs_label,
+ .uuid = grub_xfs_uuid,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 0,
+#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..8d86cf9
--- /dev/null
+++ b/grub-core/fs/zfs/zfs.c
@@ -0,0 +1,2550 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 1999,2000,2001,2002,2003,2004,2009,2010 Free Software Foundation, Inc.
+ * Copyright 2010 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/>.
+ */
+/*
+ * 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>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define ZPOOL_PROP_BOOTFS "bootfs"
+
+#define MIN(a,b) (((a) < (b)) ? (a) : (b))
+
+/*
+ * 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))
+#define DVA_OFFSET_TO_PHYS_SECTOR(offset) \
+ ((offset + VDEV_LABEL_START_SIZE) >> SPA_MINBLOCKSHIFT)
+
+/*
+ * FAT ZAP data structures
+ */
+#define ZFS_CRC64_POLY 0xC96C5795D7870F42ULL /* ECMA-182, reflected form */
+#define ZAP_HASH_IDX(hash, n) (((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)
+
+#define ZAP_LEAF_HASH_SHIFT(bs) (bs - 5)
+#define ZAP_LEAF_HASH_NUMENTRIES(bs) (1 << ZAP_LEAF_HASH_SHIFT(bs))
+#define LEAF_HASH(bs, h) \
+ ((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)
+ */
+#define ZAP_LEAF_NUMCHUNKS(bs) \
+ (((1<<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.
+ */
+#define ZAP_LEAF_CHUNK(l, bs, idx) \
+ ((zap_leaf_chunk_t *)(l->l_hash + ZAP_LEAF_HASH_NUMENTRIES(bs)))[idx]
+#define ZAP_LEAF_ENTRY(l, bs, idx) (&ZAP_LEAF_CHUNK(l, bs, idx).l_entry)
+
+
+/*
+ * Decompression Entry - lzjb
+ */
+#ifndef NBBY
+#define NBBY 8
+#endif
+
+extern grub_err_t lzjb_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
+{
+ char *name;
+ zfs_decomp_func_t *decomp_func;
+} decomp_entry_t;
+
+typedef struct dnode_end
+{
+ dnode_phys_t dn;
+ grub_zfs_endian_t endian;
+} dnode_end_t;
+
+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;
+
+ uberblock_t current_uberblock;
+ grub_disk_t disk;
+
+ dnode_end_t mos;
+ dnode_end_t mdn;
+ dnode_end_t dnode;
+
+ grub_disk_addr_t vdev_phys_sector;
+};
+
+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", NULL}, /* ZIO_COMPRESS_GZIP */
+};
+
+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_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, int 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 ((actual_cksum.zc_word[0] != zc.zc_word[0])
+ || (actual_cksum.zc_word[1] != zc.zc_word[1])
+ || (actual_cksum.zc_word[2] != zc.zc_word[2])
+ || (actual_cksum.zc_word[3] != zc.zc_word[3]))
+ {
+ grub_dprintf ("zfs", "checksum %d verification failed\n", checksum);
+ grub_dprintf ("zfs", "actual checksum %16llx %16llx %16llx %16llx\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 %16llx %16llx %16llx %16llx\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, "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, LITTLE_ENDIAN) == UBERBLOCK_MAGIC)
+ ub1_endian = LITTLE_ENDIAN;
+ else
+ ub1_endian = BIG_ENDIAN;
+ if (grub_zfs_to_cpu64 (ub2->ub_magic, LITTLE_ENDIAN) == UBERBLOCK_MAGIC)
+ ub2_endian = LITTLE_ENDIAN;
+ else
+ ub2_endian = 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
+ * Need to Implement: checksum
+ *
+ */
+static grub_err_t
+uberblock_verify (uberblock_phys_t * ub, int offset)
+{
+ uberblock_t *uber = &ub->ubp_uberblock;
+ grub_err_t err;
+ grub_zfs_endian_t endian = UNKNOWN_ENDIAN;
+ zio_cksum_t zc;
+
+ if (grub_zfs_to_cpu64 (uber->ub_magic, LITTLE_ENDIAN) == UBERBLOCK_MAGIC
+ && grub_zfs_to_cpu64 (uber->ub_version, LITTLE_ENDIAN) > 0
+ && grub_zfs_to_cpu64 (uber->ub_version, LITTLE_ENDIAN) <= SPA_VERSION)
+ endian = LITTLE_ENDIAN;
+
+ if (grub_zfs_to_cpu64 (uber->ub_magic, BIG_ENDIAN) == UBERBLOCK_MAGIC
+ && grub_zfs_to_cpu64 (uber->ub_version, BIG_ENDIAN) > 0
+ && grub_zfs_to_cpu64 (uber->ub_version, BIG_ENDIAN) <= SPA_VERSION)
+ endian = BIG_ENDIAN;
+
+ if (endian == 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, UBERBLOCK_SIZE);
+
+ 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, grub_disk_addr_t sector)
+{
+ uberblock_phys_t *ubbest = NULL;
+ int i;
+ grub_disk_addr_t offset;
+ grub_err_t err = GRUB_ERR_NONE;
+
+ for (i = 0; i < (VDEV_UBERBLOCK_RING >> VDEV_UBERBLOCK_SHIFT); i++)
+ {
+ offset = (sector << SPA_MINBLOCKSHIFT) + VDEV_PHYS_SIZE
+ + (i << VDEV_UBERBLOCK_SHIFT);
+
+ err = uberblock_verify (&ub_array[i], offset);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+ if (ubbest == NULL
+ || vdev_uberblock_compare (&(ub_array[i].ubp_uberblock),
+ &(ubbest->ubp_uberblock)) > 0)
+ ubbest = &ub_array[i];
+ }
+ 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 (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;
+}
+
+
+/*
+ * 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;
+ grub_uint64_t offset, sector;
+ 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 == LITTLE_ENDIAN ? "little-endian gang\n"
+ :"big-endian gang\n");
+ offset = dva_get_offset (dva, endian);
+ sector = DVA_OFFSET_TO_PHYS_SECTOR (offset);
+ grub_dprintf ("zfs", "offset=%llx\n", (unsigned long long) offset);
+
+ /* read in the gang block header */
+ err = grub_disk_read (data->disk, sector, 0, SPA_GANGBLOCKSIZE,
+ (char *) zio_gb);
+ 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 (zio_gb->zg_blkptr[i].blk_birth == 0)
+ 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++)
+ {
+ grub_uint64_t offset, sector;
+
+ 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
+ {
+ /* read in a data block */
+ offset = dva_get_offset (&bp->blk_dva[i], endian);
+ sector = DVA_OFFSET_TO_PHYS_SECTOR (offset);
+ err = grub_disk_read (data->disk, sector, 0, psize, buf);
+ }
+ 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;
+}
+
+/*
+ * 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;
+ 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) & 0x7;
+ lsize = (BP_IS_HOLE(bp) ? 0 :
+ (((grub_zfs_to_cpu64 ((bp)->blk_prop, endian) & 0xffff) + 1)
+ << SPA_MINBLOCKSHIFT));
+ psize = get_psize (bp, endian);
+
+ 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)
+ {
+ compbuf = grub_malloc (psize);
+ if (! compbuf)
+ return grub_errno;
+ }
+ else
+ compbuf = *buf = grub_malloc (lsize);
+
+ grub_dprintf ("zfs", "endian = %d\n", endian);
+ err = zio_read_data (bp, endian, compbuf, data);
+ if (err)
+ {
+ grub_free (compbuf);
+ *buf = NULL;
+ return err;
+ }
+
+ 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 (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 idx, level;
+ 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,
+ int objsize, char *name, grub_uint64_t * value)
+{
+ int i, chunks;
+ mzap_ent_phys_t *mzap_ent = zapobj->mz_chunk;
+
+ chunks = objsize / MZAP_ENT_LEN - 1;
+ for (i = 0; i < chunks; i++)
+ {
+ if (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, "couldn't find %s", name);
+}
+
+static int
+mzap_iterate (mzap_phys_t * zapobj, grub_zfs_endian_t endian, int objsize,
+ int NESTED_FUNC_ATTR (*hook) (const char *name,
+ grub_uint64_t val))
+{
+ 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)))
+ return 1;
+ }
+
+ return 0;
+}
+
+static grub_uint64_t
+zap_hash (grub_uint64_t salt, const char *name)
+{
+ 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);
+ }
+ }
+
+ 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.
+ */
+/* XXX */
+static int
+zap_leaf_array_equal (zap_leaf_phys_t * l, grub_zfs_endian_t endian,
+ int blksft, int chunk, int array_len, const char *buf)
+{
+ int bseen = 0;
+
+ while (bseen < array_len)
+ {
+ struct zap_leaf_array *la = &ZAP_LEAF_CHUNK (l, blksft, chunk).l_array;
+ int toread = MIN (array_len - bseen, ZAP_LEAF_ARRAY_BYTES);
+
+ if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft))
+ return (0);
+
+ if (grub_memcmp (la->la_array, buf + bseen, toread) != 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, int array_len, char *buf)
+{
+ int bseen = 0;
+
+ while (bseen < array_len)
+ {
+ struct zap_leaf_array *la = &ZAP_LEAF_CHUNK (l, blksft, chunk).l_array;
+ int toread = MIN (array_len - bseen, 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)
+{
+ 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)], endian);
+ chunk != CHAIN_END; chunk = le->le_next)
+ {
+
+ 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))
+ {
+ struct zap_leaf_array *la;
+
+ if (le->le_int_size != 8 || le->le_value_length != 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, "couldn't find %s", name);
+}
+
+
+/* Verify if this is a fat zap header block */
+static grub_err_t
+zap_verify (zap_phys_t *zap)
+{
+ if (zap->zap_magic != (grub_uint64_t) ZAP_MAGIC)
+ return grub_error (GRUB_ERR_BAD_FS, "bad ZAP magic");
+
+ if (zap->zap_flags != 0)
+ return grub_error (GRUB_ERR_BAD_FS, "bad ZAP flags");
+
+ 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,
+ char *name, grub_uint64_t * value, struct grub_zfs_data *data)
+{
+ 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);
+ if (err)
+ return err;
+
+ hash = zap_hash (zap->zap_salt, name);
+
+ /* 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_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))];
+
+ /* 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);
+ grub_free (l);
+ return err;
+}
+
+/* XXX */
+static int
+fzap_iterate (dnode_end_t * zap_dnode, zap_phys_t * zap,
+ int NESTED_FUNC_ATTR (*hook) (const char *name,
+ grub_uint64_t val),
+ struct grub_zfs_data *data)
+{
+ zap_leaf_phys_t *l;
+ void *l_in;
+ grub_uint64_t idx, 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))
+ 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 < zap->zap_ptrtbl.zt_numblks; idx++)
+ {
+ blkid = ((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))];
+
+ 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_array *la;
+ struct zap_leaf_entry *le;
+ grub_uint64_t val;
+ 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)
+ + 1);
+ if (zap_leaf_array_get (l, endian, blksft, le->le_name_chunk,
+ le->le_name_length, buf))
+ {
+ grub_free (buf);
+ continue;
+ }
+ buf[le->le_name_length] = 0;
+
+ if (le->le_int_size != 8
+ || grub_zfs_to_cpu16 (le->le_value_length, endian) != 1)
+ continue;
+
+ /* get the uint64_t property value */
+ la = &ZAP_LEAF_CHUNK (l, blksft, le->le_value_chunk).l_array;
+ val = grub_be_to_cpu64 (la->la_array64);
+ if (hook (buf, val))
+ return 1;
+ grub_free (buf);
+ }
+ }
+ 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, char *name, grub_uint64_t * val,
+ struct grub_zfs_data *data)
+{
+ grub_uint64_t block_type;
+ int 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_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));
+ 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));
+ grub_dprintf ("zfs", "returned %d\n", err);
+ grub_free (zapbuf);
+ return err;
+ }
+
+ return grub_error (GRUB_ERR_BAD_FS, "unknown ZAP type");
+}
+
+static int
+zap_iterate (dnode_end_t * zap_dnode,
+ int NESTED_FUNC_ATTR (*hook) (const char *name, grub_uint64_t val),
+ struct grub_zfs_data *data)
+{
+ 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 read\n");
+
+ if (block_type == ZBT_MICRO)
+ {
+ grub_dprintf ("zfs", "micro zap\n");
+ ret = mzap_iterate (zapbuf, endian, size, hook);
+ grub_free (zapbuf);
+ return ret;
+ }
+ else if (block_type == ZBT_HEADER)
+ {
+ grub_dprintf ("zfs", "fat zap\n");
+ /* this is a fat zap */
+ ret = fzap_iterate (zap_dnode, zapbuf, hook, 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;
+ 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;
+}
+
+/*
+ * 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 (dnode_end_t * mdn, 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 (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);
+ 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), ZFS_ROOT_OBJ, &objnum, data);
+ if (err)
+ {
+ grub_free (dn_new);
+ return err;
+ }
+
+ err = dnode_get (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)
+ {
+ grub_free (path_buf);
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ }
+ err = zap_lookup (&(dnode_path->dn), cname, &objnum, data);
+ 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 (mdn, objnum, 0, &(dnode_path->dn), data);
+ if (err)
+ break;
+
+ *path = ch;
+#if 0
+ if (((grub_zfs_to_cpu64(((znode_phys_t *) DN_BONUS (&dnode_path->dn.dn))->zp_mode, dnode_path->dn.endian) >> 12) & 0xf) == 0xa && ch)
+ {
+ char *oldpath = path, *oldpathbuf = path_buf;
+ path = path_buf
+ = grub_malloc (sizeof (dnode_path->dn.dn.dn_bonus)
+ - sizeof (znode_phys_t) + grub_strlen (oldpath) + 1);
+ if (!path_buf)
+ {
+ grub_free (oldpathbuf);
+ return grub_errno;
+ }
+ grub_memcpy (path,
+ (char *) DN_BONUS(&dnode_path->dn.dn) + sizeof (znode_phys_t),
+ sizeof (dnode_path->dn.dn.dn_bonus) - sizeof (znode_phys_t));
+ path [sizeof (dnode_path->dn.dn.dn_bonus) - sizeof (znode_phys_t)] = 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);
+ }
+ }
+#endif
+ }
+
+ 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);
+ if (err)
+ return err;
+
+ grub_dprintf ("zfs", "alive\n");
+
+ err = dnode_get (mosmdn, objnum, DMU_OT_DSL_DIR, 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 && !grub_isspace (*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);
+ if (err)
+ return err;
+
+ err = dnode_get (mosmdn, objnum, DMU_OT_DSL_DIR, 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;
+ 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;
+}
+
+static grub_err_t
+dnode_get_fullpath (const char *fullpath, dnode_end_t * mdn,
+ grub_uint64_t *mdnobj, dnode_end_t * dn, int *isfs,
+ struct grub_zfs_data *data)
+{
+ char *fsname, *snapname;
+ const char *ptr_at, *filename;
+ grub_uint64_t headobj;
+ 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", mdn->endian);
+
+ err = dnode_get (&(data->mos), headobj, DMU_OT_DSL_DATASET, mdn, data);
+ if (err)
+ {
+ grub_free (fsname);
+ grub_free (snapname);
+ return err;
+ }
+ grub_dprintf ("zfs", "endian = %d\n", mdn->endian);
+
+ if (snapname)
+ {
+ grub_uint64_t snapobj;
+
+ snapobj = grub_zfs_to_cpu64 (((dsl_dataset_phys_t *) DN_BONUS (&mdn->dn))->ds_snapnames_zapobj, mdn->endian);
+
+ err = dnode_get (&(data->mos), snapobj,
+ DMU_OT_DSL_DS_SNAP_MAP, mdn, data);
+ if (!err)
+ err = zap_lookup (mdn, snapname, &headobj, data);
+ if (!err)
+ err = dnode_get (&(data->mos), headobj, DMU_OT_DSL_DATASET, mdn, data);
+ if (err)
+ {
+ grub_free (fsname);
+ grub_free (snapname);
+ return err;
+ }
+ }
+
+ if (mdnobj)
+ *mdnobj = headobj;
+
+ make_mdn (mdn, data);
+
+ grub_dprintf ("zfs", "endian = %d\n", mdn->endian);
+
+ if (*isfs)
+ {
+ grub_free (fsname);
+ grub_free (snapname);
+ return GRUB_ERR_NONE;
+ }
+ err = dnode_get_path (mdn, filename, dn, data);
+ grub_free (fsname);
+ grub_free (snapname);
+ return err;
+}
+
+/*
+ * 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)
+ *
+ */
+
+static int
+nvlist_find_value (char *nvlist, char *name, int valtype, char **val,
+ grub_size_t *size_out, grub_size_t *nelm_out)
+{
+ int name_len, type, encode_size;
+ char *nvpair, *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;
+ }
+
+ /* skip the header, nvl_version, and nvl_nvflag */
+ nvlist = nvlist + 4 * 3;
+ /*
+ * Loop thru the nvpair list
+ * The XDR representation of an integer is in big-endian byte order.
+ */
+ while ((encode_size = grub_be_to_cpu32 (*(grub_uint32_t *) nvlist)))
+ {
+ int nelm;
+
+ nvpair = nvlist + 4 * 2; /* skip the encode/decode size */
+
+ name_len = grub_be_to_cpu32 (*(grub_uint32_t *) nvpair);
+ nvpair += 4;
+
+ nvp_name = nvpair;
+ nvpair = nvpair + ((name_len + 3) & ~3); /* align */
+
+ type = grub_be_to_cpu32 (*(grub_uint32_t *) nvpair);
+ nvpair += 4;
+
+ nelm = grub_be_to_cpu32 (*(grub_uint32_t *) nvpair);
+ if (nelm < 1)
+ return grub_error (GRUB_ERR_BAD_FS, "empty nvpair");
+
+ nvpair += 4;
+
+ if ((grub_strncmp (nvp_name, name, name_len) == 0) && type == valtype)
+ {
+ *val = nvpair;
+ *size_out = encode_size;
+ if (nelm_out)
+ *nelm_out = nelm;
+ return 1;
+ }
+
+ nvlist += encode_size; /* goto the next nvpair */
+ }
+ return 0;
+}
+
+int
+grub_zfs_nvlist_lookup_uint64 (char *nvlist, 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_uint64_t *) nvpair);
+ return 1;
+}
+
+char *
+grub_zfs_nvlist_lookup_string (char *nvlist, 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_uint32_t *) 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 (char *nvlist, char *name)
+{
+ char *nvpair;
+ char *ret;
+ grub_size_t size;
+ int found;
+
+ found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST, &nvpair,
+ &size, 0);
+ if (!found)
+ return 0;
+ ret = grub_zalloc (size + 3 * sizeof (grub_uint32_t));
+ 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 (char *nvlist, char *name)
+{
+ char *nvpair;
+ grub_size_t nelm, size;
+ int found;
+
+ found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST, &nvpair,
+ &size, &nelm);
+ if (! found)
+ return -1;
+ return nelm;
+}
+
+char *
+grub_zfs_nvlist_lookup_nvlist_array (char *nvlist, char *name,
+ grub_size_t index)
+{
+ char *nvpair, *nvpairptr;
+ int found;
+ char *ret;
+ grub_size_t size;
+ unsigned i;
+ grub_size_t nelm;
+
+ found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST, &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++)
+ {
+ grub_uint32_t encode_size;
+
+ /* skip the header, nvl_version, and nvl_nvflag */
+ nvpairptr = nvpairptr + 4 * 2;
+
+ while (nvpairptr < nvpair + size
+ && (encode_size = grub_be_to_cpu32 (*(grub_uint32_t *) nvpairptr)))
+ nvlist += encode_size; /* goto the next nvpair */
+
+ nvlist = nvlist + 4 * 2; /* skip the ending 2 zeros - 8 bytes */
+ }
+
+ if (nvpairptr >= nvpair + size
+ || nvpairptr + grub_be_to_cpu32 (*(grub_uint32_t *) (nvpairptr + 4 * 2))
+ >= nvpair + size)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist array");
+ return 0;
+ }
+
+ ret = grub_zalloc (grub_be_to_cpu32 (*(grub_uint32_t *) (nvpairptr + 4 * 2))
+ + 3 * sizeof (grub_uint32_t));
+ if (!ret)
+ return 0;
+ grub_memcpy (ret, nvlist, sizeof (grub_uint32_t));
+
+ grub_memcpy (ret + sizeof (grub_uint32_t), nvpairptr, size);
+ return ret;
+}
+
+static grub_err_t
+zfs_fetch_nvlist (struct grub_zfs_data * data, char **nvlist)
+{
+ grub_err_t err;
+
+ *nvlist = grub_malloc (VDEV_PHYS_SIZE);
+ /* Read in the vdev name-value pair list (112K). */
+ err = grub_disk_read (data->disk, data->vdev_phys_sector, 0,
+ VDEV_PHYS_SIZE, *nvlist);
+ if (err)
+ {
+ grub_free (*nvlist);
+ *nvlist = 0;
+ return err;
+ }
+ return GRUB_ERR_NONE;
+}
+
+/*
+ * Check the disk label information and retrieve needed vdev name-value pairs.
+ *
+ */
+static grub_err_t
+check_pool_label (struct grub_zfs_data *data)
+{
+ grub_uint64_t pool_state, txg = 0;
+ char *nvlist;
+#if 0
+ char *nv;
+#endif
+ grub_uint64_t diskguid;
+ grub_uint64_t version;
+ int found;
+ grub_err_t err;
+
+ err = zfs_fetch_nvlist (data, &nvlist);
+ 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 (version > SPA_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);
+ }
+ grub_dprintf ("zfs", "check 9 passed\n");
+#if 0
+ if (nvlist_lookup_value (nvlist, ZPOOL_CONFIG_VDEV_TREE, &nv,
+ DATA_TYPE_NVLIST, NULL))
+ {
+ grub_free (vdev);
+ return (GRUB_ERR_BAD_FS);
+ }
+ grub_dprintf ("zfs", "check 10 passed\n");
+#endif
+
+ found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_GUID, &diskguid);
+ if (! found)
+ {
+ grub_free (nvlist);
+ if (! grub_errno)
+ grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_GUID " not found");
+ return grub_errno;
+ }
+ grub_dprintf ("zfs", "check 11 passed\n");
+
+ grub_free (nvlist);
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+zfs_unmount (struct grub_zfs_data *data)
+{
+ grub_free (data->dnode_buf);
+ grub_free (data->dnode_mdn);
+ grub_free (data->file_buf);
+ 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;
+ int label = 0;
+ uberblock_phys_t *ub_array, *ubbest = NULL;
+ vdev_boot_header_t *bh;
+ void *osp = 0;
+ grub_size_t ospsize;
+ grub_err_t err;
+ int vdevnum;
+
+ if (! dev->disk)
+ {
+ grub_error (GRUB_ERR_BAD_DEVICE, "not a disk");
+ return 0;
+ }
+
+ data = grub_malloc (sizeof (*data));
+ if (!data)
+ return 0;
+ grub_memset (data, 0, sizeof (*data));
+#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->disk = dev->disk;
+
+ ub_array = grub_malloc (VDEV_UBERBLOCK_RING);
+ if (!ub_array)
+ {
+ zfs_unmount (data);
+ return 0;
+ }
+
+ bh = grub_malloc (VDEV_BOOT_HEADER_SIZE);
+ if (!bh)
+ {
+ zfs_unmount (data);
+ grub_free (ub_array);
+ return 0;
+ }
+
+ vdevnum = VDEV_LABELS;
+
+ /* Don't check back labels on CDROM. */
+ if (grub_disk_get_size (dev->disk) == GRUB_DISK_SIZE_UNKNOWN)
+ vdevnum = VDEV_LABELS / 2;
+
+ for (label = 0; ubbest == NULL && label < vdevnum; label++)
+ {
+ grub_zfs_endian_t ub_endian = UNKNOWN_ENDIAN;
+ grub_dprintf ("zfs", "label %d\n", label);
+
+ data->vdev_phys_sector
+ = label * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT)
+ + ((VDEV_SKIP_SIZE + VDEV_BOOT_HEADER_SIZE) >> SPA_MINBLOCKSHIFT)
+ + (label < VDEV_LABELS / 2 ? 0 : grub_disk_get_size (dev->disk)
+ - VDEV_LABELS * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT));
+
+ /* Read in the uberblock ring (128K). */
+ err = grub_disk_read (data->disk, data->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);
+
+ ubbest = find_bestub (ub_array, data->vdev_phys_sector);
+ if (!ubbest)
+ {
+ grub_dprintf ("zfs", "No uberblock found\n");
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+ ub_endian = (grub_zfs_to_cpu64 (ubbest->ubp_uberblock.ub_magic,
+ LITTLE_ENDIAN) == UBERBLOCK_MAGIC
+ ? LITTLE_ENDIAN : BIG_ENDIAN);
+ err = zio_read (&ubbest->ubp_uberblock.ub_rootbp,
+ ub_endian,
+ &osp, &ospsize, data);
+ if (err)
+ {
+ grub_dprintf ("zfs", "couldn't zio_read\n");
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+
+ if (ospsize < OBJSET_PHYS_SIZE_V14)
+ {
+ grub_dprintf ("zfs", "osp too small\n");
+ grub_free (osp);
+ continue;
+ }
+ grub_dprintf ("zfs", "ubbest %p\n", ubbest);
+
+ err = check_pool_label (data);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+#if 0
+ if (find_best_root &&
+ vdev_uberblock_compare (&ubbest->ubp_uberblock,
+ &(current_uberblock)) <= 0)
+ continue;
+#endif
+ /* 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 (ubbest->ubp_uberblock.ub_rootbp.blk_prop, ub_endian) >> 63) & 1;
+ grub_memmove (&(data->current_uberblock),
+ &ubbest->ubp_uberblock, sizeof (uberblock_t));
+ grub_free (ub_array);
+ grub_free (bh);
+ grub_free (osp);
+ return data;
+ }
+ grub_error (GRUB_ERR_BAD_FS, "couldn't find a valid label");
+ zfs_unmount (data);
+ grub_free (ub_array);
+ grub_free (bh);
+ grub_free (osp);
+
+ return 0;
+}
+
+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, 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, &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)
+{
+ char *nvlist;
+ int found;
+ struct grub_zfs_data *data;
+ grub_uint64_t guid;
+ grub_err_t err;
+
+ *uuid = 0;
+
+ data = zfs_mount (device);
+ if (! data)
+ return grub_errno;
+
+ err = zfs_fetch_nvlist (data, &nvlist);
+ if (err)
+ {
+ zfs_unmount (data);
+ return err;
+ }
+
+ found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, &guid);
+ if (! found)
+ return grub_errno;
+ grub_free (nvlist);
+ *uuid = grub_xasprintf ("%016llx", (long long unsigned) guid);
+ zfs_unmount (data);
+ if (! *uuid)
+ return grub_errno;
+ 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->mdn), 0,
+ &(data->dnode), &isfs, data);
+ if (err)
+ {
+ zfs_unmount (data);
+ return err;
+ }
+
+ if (isfs)
+ {
+ zfs_unmount (data);
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "Missing @ or / separator");
+ }
+
+ /* 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, "not a 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_uint64_t *) ((char *) sahdrp + hdrsize + SA_SIZE_OFFSET);
+ }
+ else
+ {
+ file->size = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&data->dnode.dn))->zp_size, data->dnode.endian);
+ }
+
+ 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;
+ int blksz, movesize;
+ grub_size_t length;
+ grub_size_t read;
+ grub_err_t err;
+
+ if (data->file_buf == NULL)
+ {
+ data->file_buf = grub_malloc (SPA_MAXBLOCKSIZE);
+ if (!data->file_buf)
+ return -1;
+ data->file_start = data->file_end = 0;
+ }
+
+ /*
+ * 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;
+
+ /*
+ * 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)
+ return -1;
+
+ data->file_start = blkid * blksz;
+ data->file_end = data->file_start + blksz;
+
+ movesize = MIN (length, data->file_end - (int) file->offset - read);
+
+ 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->mdn), mdnobj,
+ &(data->dnode), &isfs, data);
+ zfs_unmount (data);
+ return err;
+}
+
+static void
+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, DMU_OT_DSL_DATASET, &mdn, data);
+ if (err)
+ {
+ grub_dprintf ("zfs", "failed here\n");
+ return;
+ }
+ }
+ make_mdn (&mdn, data);
+ err = dnode_get (&mdn, MASTER_NODE_OBJ, DMU_OT_MASTER_NODE,
+ &dn, data);
+ if (err)
+ {
+ grub_dprintf ("zfs", "failed here\n");
+ return;
+ }
+
+ err = zap_lookup (&dn, ZFS_ROOT_OBJ, &objnum, data);
+ if (err)
+ {
+ grub_dprintf ("zfs", "failed here\n");
+ return;
+ }
+
+ err = dnode_get (&mdn, objnum, 0, &dn, data);
+ if (err)
+ {
+ grub_dprintf ("zfs", "failed here\n");
+ return;
+ }
+
+ info->mtimeset = 1;
+ info->mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0], dn.endian);
+ return;
+}
+
+static grub_err_t
+grub_zfs_dir (grub_device_t device, const char *path,
+ int (*hook) (const char *, const struct grub_dirhook_info *))
+{
+ struct grub_zfs_data *data;
+ grub_err_t err;
+ int isfs;
+ auto int NESTED_FUNC_ATTR iterate_zap (const char *name, grub_uint64_t val);
+ auto int NESTED_FUNC_ATTR iterate_zap_fs (const char *name,
+ grub_uint64_t val);
+ auto int NESTED_FUNC_ATTR iterate_zap_snap (const char *name,
+ grub_uint64_t val);
+
+ int NESTED_FUNC_ATTR iterate_zap (const char *name, grub_uint64_t val)
+ {
+ struct grub_dirhook_info info;
+ dnode_end_t dn;
+ grub_memset (&info, 0, sizeof (info));
+
+ dnode_get (&(data->mdn), val, 0, &dn, data);
+ 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 hook (name, &info);
+ }
+
+ int NESTED_FUNC_ATTR iterate_zap_fs (const char *name, grub_uint64_t val)
+ {
+ struct grub_dirhook_info info;
+ dnode_end_t mdn;
+ err = dnode_get (&(data->mos), val, 0, &mdn, data);
+ if (err)
+ return 0;
+ if (mdn.dn.dn_type != DMU_OT_DSL_DIR)
+ return 0;
+
+ fill_fs_info (&info, mdn, data);
+ return hook (name, &info);
+ }
+ int NESTED_FUNC_ATTR iterate_zap_snap (const char *name, grub_uint64_t val)
+ {
+ struct grub_dirhook_info info;
+ char *name2;
+ int ret;
+ dnode_end_t mdn;
+
+ err = dnode_get (&(data->mos), val, 0, &mdn, data);
+ if (err)
+ return 0;
+
+ if (mdn.dn.dn_type != DMU_OT_DSL_DATASET)
+ return 0;
+
+ fill_fs_info (&info, mdn, data);
+
+ name2 = grub_malloc (grub_strlen (name) + 2);
+ name2[0] = '@';
+ grub_memcpy (name2 + 1, name, grub_strlen (name) + 1);
+ ret = hook (name2, &info);
+ grub_free (name2);
+ return ret;
+ }
+
+ data = zfs_mount (device);
+ if (! data)
+ return grub_errno;
+ err = dnode_get_fullpath (path, &(data->mdn), 0, &(data->dnode), &isfs, data);
+ if (err)
+ {
+ zfs_unmount (data);
+ return err;
+ }
+ if (isfs)
+ {
+ grub_uint64_t childobj, headobj;
+ grub_uint64_t snapobj;
+ dnode_end_t dn;
+ struct grub_dirhook_info info;
+
+ fill_fs_info (&info, data->dnode, data);
+ hook ("@", &info);
+
+ 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 (&dn, iterate_zap_fs, data);
+
+ 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 (&dn, iterate_zap_snap, data);
+ }
+ else
+ {
+ if (data->dnode.dn.dn_type != DMU_OT_DIRECTORY_CONTENTS)
+ {
+ zfs_unmount (data);
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ }
+ zap_iterate (&(data->dnode), iterate_zap, data);
+ }
+ zfs_unmount (data);
+ return grub_errno;
+}
+
+static struct grub_fs grub_zfs_fs = {
+ .name = "zfs",
+ .dir = grub_zfs_dir,
+ .open = grub_zfs_open,
+ .read = grub_zfs_read,
+ .close = grub_zfs_close,
+ .label = zfs_label,
+ .uuid = zfs_uuid,
+ .mtime = 0,
+ .next = 0
+};
+
+GRUB_MOD_INIT (zfs)
+{
+ 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_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..ba510cf
--- /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)
+ 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/zfsinfo.c b/grub-core/fs/zfs/zfsinfo.c
new file mode 100644
index 0000000..1968ed5
--- /dev/null
+++ b/grub-core/fs/zfs/zfsinfo.c
@@ -0,0 +1,409 @@
+/*
+ * 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>
+
+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);
+ grub_printf ("State: ");
+
+ if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_REMOVED, &ival))
+ {
+ grub_printf ("removed ");
+ isok = 0;
+ }
+
+ if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival))
+ {
+ grub_printf ("faulted ");
+ isok = 0;
+ }
+
+ if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_OFFLINE, &ival))
+ {
+ grub_printf ("offline ");
+ isok = 0;
+ }
+
+ if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival))
+ grub_printf ("degraded ");
+
+ if (isok)
+ grub_printf ("online");
+ grub_printf ("\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_printf ("Incorrect VDEV: no type available\n");
+ return grub_errno;
+ }
+
+ if (grub_strcmp (type, VDEV_TYPE_DISK) == 0)
+ {
+ char *bootpath = 0;
+ char *path = 0;
+ char *devid = 0;
+
+ print_tabs (tab);
+ grub_printf ("Leaf VDEV\n");
+
+ print_state (nvlist, tab);
+
+ bootpath =
+ grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_PHYS_PATH);
+ print_tabs (tab);
+ if (!bootpath)
+ grub_printf ("Bootpath: unavailable\n");
+ else
+ grub_printf ("Bootpath: %s\n", bootpath);
+
+ path = grub_zfs_nvlist_lookup_string (nvlist, "path");
+ print_tabs (tab);
+ if (!path)
+ grub_printf ("Path: unavailable\n");
+ else
+ grub_printf ("Path: %s\n", path);
+
+ devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID);
+ print_tabs (tab);
+ if (!devid)
+ grub_printf ("Devid: unavailable\n");
+ else
+ grub_printf ("Devid: %s\n", devid);
+ grub_free (bootpath);
+ grub_free (devid);
+ grub_free (path);
+ 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);
+
+ print_tabs (tab);
+ if (nelm <= 0)
+ {
+ grub_printf ("Incorrect mirror VDEV\n");
+ return GRUB_ERR_NONE;
+ }
+ grub_printf ("Mirror 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)
+ {
+ grub_printf ("Mirror VDEV element %d isn't correct\n", i);
+ continue;
+ }
+
+ grub_printf ("Mirror VDEV element %d:\n", i);
+ print_vdev_info (child, tab + 1);
+
+ grub_free (child);
+ }
+ }
+
+ print_tabs (tab);
+ grub_printf ("Unknown VDEV 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 char *poolstates[] = {
+ [POOL_STATE_ACTIVE] = "active",
+ [POOL_STATE_EXPORTED] = "exported",
+ [POOL_STATE_DESTROYED] = "destroyed",
+ [POOL_STATE_SPARE] = "reserved for hot spare",
+ [POOL_STATE_L2CACHE] = "level 2 ARC device",
+ [POOL_STATE_UNINITIALIZED] = "uninitialized",
+ [POOL_STATE_UNAVAIL] = "unavailable",
+ [POOL_STATE_POTENTIALLY_ACTIVE] = "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, "device name required");
+
+ 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_printf ("Pool name: unavailable\n");
+ else
+ grub_printf ("Pool name: %s\n", poolname);
+
+ found =
+ grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, &guid);
+ if (!found)
+ grub_printf ("Pool GUID: unavailable\n");
+ else
+ grub_printf ("Pool GUID: %016llx\n", (long long unsigned) guid);
+
+ found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_STATE,
+ &pool_state);
+ if (!found)
+ grub_printf ("Unable to retrieve pool state\n");
+ else if (pool_state >= ARRAY_SIZE (poolstates))
+ grub_printf ("Unrecognized pool state\n");
+ else
+ grub_printf ("Pool state: %s\n", poolstates[pool_state]);
+
+ nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE);
+
+ if (!nv)
+ grub_printf ("No vdev tree available\n");
+ 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, "filesystem name required");
+
+ devname = grub_file_get_device_name (args[0]);
+ if (grub_errno)
+ return grub_errno;
+
+ 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,
+ "zfsinfo DEVICE",
+ "Print ZFS info about DEVICE.");
+ cmd_bootfs = grub_register_command ("zfs-bootfs", grub_cmd_zfs_bootfs,
+ "zfs-bootfs FILESYSTEM [VARIABLE]",
+ "Print ZFS-BOOTFSOBJ or set it to VARIABLE");
+}
+
+GRUB_MOD_FINI (zfsinfo)
+{
+ grub_unregister_command (cmd_info);
+ grub_unregister_command (cmd_bootfs);
+}