summaryrefslogtreecommitdiffstats
path: root/master/debian/zfs_update.patch
diff options
context:
space:
mode:
Diffstat (limited to 'master/debian/zfs_update.patch')
-rw-r--r--master/debian/zfs_update.patch2415
1 files changed, 2415 insertions, 0 deletions
diff --git a/master/debian/zfs_update.patch b/master/debian/zfs_update.patch
new file mode 100644
index 0000000..12e4599
--- /dev/null
+++ b/master/debian/zfs_update.patch
@@ -0,0 +1,2415 @@
+
+Revisions:
+
+3340: ZFS zlib support
+3474: Fix 2G limit on ZFS
+3486: ZFS fixes
+3488: ZFS multi-device and version 33 support
+3494: Fix memory leak
+3518: Rewrite RAIDZ part based on reverse engineering
+3519: Fix RAIDZ(2) for >= 5 devices
+3520: Add ability to sustain a single drive failure on both raidz and raidz2
+3521: Support raidz3
+3533: Support second redundancy strip on raidz(2,3)
+3534: Support case-insensitive ZFS subvolumes
+3535: Support third redundancy strip on raidz3
+
+--- a/grub-core/fs/zfs/zfs.c
++++ b/grub-core/fs/zfs/zfs.c
+@@ -51,6 +51,7 @@
+ #include <grub/zfs/sa_impl.h>
+ #include <grub/zfs/dsl_dir.h>
+ #include <grub/zfs/dsl_dataset.h>
++#include <grub/deflate.h>
+
+ GRUB_MOD_LICENSE ("GPLv3+");
+
+@@ -139,6 +140,27 @@
+ grub_zfs_endian_t endian;
+ } dnode_end_t;
+
++struct grub_zfs_device_desc
++{
++ enum { DEVICE_LEAF, DEVICE_MIRROR, DEVICE_RAIDZ } type;
++ grub_uint64_t id;
++ grub_uint64_t guid;
++
++ /* Valid only for non-leafs. */
++ unsigned n_children;
++ struct grub_zfs_device_desc *children;
++
++ /* Valid only for RAIDZ. */
++ unsigned nparity;
++ unsigned ashift;
++
++ /* Valid only for leaf devices. */
++ grub_device_t dev;
++ grub_disk_addr_t vdev_phys_sector;
++ uberblock_t current_uberblock;
++ int original;
++};
++
+ struct grub_zfs_data
+ {
+ /* cache for a file block of the currently zfs_open()-ed file */
+@@ -153,23 +175,45 @@
+ 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;
++ struct grub_zfs_device_desc *devices_attached;
++ unsigned n_devices_attached;
++ unsigned n_devices_allocated;
++ struct grub_zfs_device_desc *device_original;
++
++ uberblock_t current_uberblock;
++
++ int mounted;
++ grub_uint64_t guid;
+ };
+
++static grub_err_t
++zlib_decompress (void *s, void *d,
++ grub_size_t slen, grub_size_t dlen)
++{
++ if (grub_zlib_decompress (s, slen, 0, d, dlen) < 0)
++ return grub_errno;
++ return GRUB_ERR_NONE;
++}
++
+ static decomp_entry_t decomp_table[ZIO_COMPRESS_FUNCTIONS] = {
+ {"inherit", NULL}, /* ZIO_COMPRESS_INHERIT */
+ {"on", lzjb_decompress}, /* ZIO_COMPRESS_ON */
+ {"off", NULL}, /* ZIO_COMPRESS_OFF */
+ {"lzjb", lzjb_decompress}, /* ZIO_COMPRESS_LZJB */
+ {"empty", NULL}, /* ZIO_COMPRESS_EMPTY */
+- {"gzip", NULL}, /* ZIO_COMPRESS_GZIP */
++ {"gzip-1", zlib_decompress}, /* ZIO_COMPRESS_GZIP1 */
++ {"gzip-2", zlib_decompress}, /* ZIO_COMPRESS_GZIP2 */
++ {"gzip-3", zlib_decompress}, /* ZIO_COMPRESS_GZIP3 */
++ {"gzip-4", zlib_decompress}, /* ZIO_COMPRESS_GZIP4 */
++ {"gzip-5", zlib_decompress}, /* ZIO_COMPRESS_GZIP5 */
++ {"gzip-6", zlib_decompress}, /* ZIO_COMPRESS_GZIP6 */
++ {"gzip-7", zlib_decompress}, /* ZIO_COMPRESS_GZIP7 */
++ {"gzip-8", zlib_decompress}, /* ZIO_COMPRESS_GZIP8 */
++ {"gzip-9", zlib_decompress}, /* ZIO_COMPRESS_GZIP9 */
+ };
+
+ static grub_err_t zio_read_data (blkptr_t * bp, grub_zfs_endian_t endian,
+@@ -224,7 +268,7 @@
+ */
+ static grub_err_t
+ zio_checksum_verify (zio_cksum_t zc, grub_uint32_t checksum,
+- grub_zfs_endian_t endian, char *buf, int size)
++ grub_zfs_endian_t endian, char *buf, grub_size_t size)
+ {
+ zio_eck_t *zec = (zio_eck_t *) (buf + size) - 1;
+ zio_checksum_info_t *ci = &zio_checksum_table[checksum];
+@@ -253,13 +297,13 @@
+ || (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",
++ grub_dprintf ("zfs", "checksum %s verification failed\n", ci->ci_name);
++ grub_dprintf ("zfs", "actual checksum %016llx %016llx %016llx %016llx\n",
+ (unsigned long long) actual_cksum.zc_word[0],
+ (unsigned long long) actual_cksum.zc_word[1],
+ (unsigned long long) actual_cksum.zc_word[2],
+ (unsigned long long) actual_cksum.zc_word[3]);
+- grub_dprintf ("zfs", "expected checksum %16llx %16llx %16llx %16llx\n",
++ grub_dprintf ("zfs", "expected checksum %016llx %016llx %016llx %016llx\n",
+ (unsigned long long) zc.zc_word[0],
+ (unsigned long long) zc.zc_word[1],
+ (unsigned long long) zc.zc_word[2],
+@@ -319,7 +363,7 @@
+ *
+ */
+ static grub_err_t
+-uberblock_verify (uberblock_phys_t * ub, int offset)
++uberblock_verify (uberblock_phys_t * ub, grub_uint64_t offset)
+ {
+ uberblock_t *uber = &ub->ubp_uberblock;
+ grub_err_t err;
+@@ -392,7 +436,7 @@
+ }
+
+ static grub_uint64_t
+-dva_get_offset (dva_t * dva, grub_zfs_endian_t endian)
++dva_get_offset (const dva_t *dva, grub_zfs_endian_t endian)
+ {
+ grub_dprintf ("zfs", "dva=%llx, %llx\n",
+ (unsigned long long) dva->dva_word[0],
+@@ -401,6 +445,842 @@
+ endian) << SPA_MINBLOCKSHIFT;
+ }
+
++static grub_err_t
++zfs_fetch_nvlist (struct grub_zfs_device_desc *diskdesc, char **nvlist)
++{
++ grub_err_t err;
++
++ *nvlist = grub_malloc (VDEV_PHYS_SIZE);
++ if (!diskdesc->dev)
++ return grub_error (GRUB_ERR_BAD_FS, "member drive unknown");
++
++ /* Read in the vdev name-value pair list (112K). */
++ err = grub_disk_read (diskdesc->dev->disk, diskdesc->vdev_phys_sector, 0,
++ VDEV_PHYS_SIZE, *nvlist);
++ if (err)
++ {
++ grub_free (*nvlist);
++ *nvlist = 0;
++ return err;
++ }
++ return GRUB_ERR_NONE;
++}
++
++static grub_err_t
++fill_vdev_info_real (struct grub_zfs_data *data,
++ const char *nvlist,
++ struct grub_zfs_device_desc *fill,
++ struct grub_zfs_device_desc *insert)
++{
++ char *type;
++
++ type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE);
++
++ if (!type)
++ return grub_errno;
++
++ if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "id", &(fill->id)))
++ return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id");
++
++ if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "guid", &(fill->guid)))
++ return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id");
++
++ if (grub_strcmp (type, VDEV_TYPE_DISK) == 0
++ || grub_strcmp (type, VDEV_TYPE_FILE) == 0)
++ {
++ fill->type = DEVICE_LEAF;
++
++ if (!fill->dev && fill->guid == insert->guid)
++ {
++ fill->dev = insert->dev;
++ fill->vdev_phys_sector = insert->vdev_phys_sector;
++ fill->current_uberblock = insert->current_uberblock;
++ fill->original = insert->original;
++ if (!data->device_original)
++ data->device_original = fill;
++ }
++
++ return GRUB_ERR_NONE;
++ }
++
++ if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0
++ || grub_strcmp (type, VDEV_TYPE_RAIDZ) == 0)
++ {
++ int nelm, i;
++
++ if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0)
++ fill->type = DEVICE_MIRROR;
++ else
++ {
++ grub_uint64_t par;
++ fill->type = DEVICE_RAIDZ;
++ if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "nparity", &par))
++ return grub_error (GRUB_ERR_BAD_FS, "couldn't find raidz parity");
++ fill->nparity = par;
++ if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "ashift", &par))
++ return grub_error (GRUB_ERR_BAD_FS, "couldn't find raidz ashift");
++ fill->ashift = par;
++ }
++
++ nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm (nvlist, ZPOOL_CONFIG_CHILDREN);
++
++ if (nelm <= 0)
++ return grub_error (GRUB_ERR_BAD_FS, "incorrect mirror VDEV");
++
++ if (!fill->children)
++ {
++ fill->n_children = nelm;
++
++ fill->children = grub_zalloc (fill->n_children
++ * sizeof (fill->children[0]));
++ }
++
++ for (i = 0; i < nelm; i++)
++ {
++ char *child;
++ grub_err_t err;
++
++ child = grub_zfs_nvlist_lookup_nvlist_array
++ (nvlist, ZPOOL_CONFIG_CHILDREN, i);
++
++ err = fill_vdev_info_real (data, child, &fill->children[i], insert);
++
++ grub_free (child);
++
++ if (err)
++ return err;
++ }
++ return GRUB_ERR_NONE;
++ }
++
++ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "vdev %s isn't supported",
++ type);
++}
++
++static grub_err_t
++fill_vdev_info (struct grub_zfs_data *data,
++ char *nvlist, struct grub_zfs_device_desc *diskdesc)
++{
++ grub_uint64_t id;
++ unsigned i;
++
++ if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "id", &id))
++ return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id");
++
++ for (i = 0; i < data->n_devices_attached; i++)
++ if (data->devices_attached[i].id == id)
++ return fill_vdev_info_real (data, nvlist, &data->devices_attached[i],
++ diskdesc);
++
++ 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)
++ {
++ data->devices_attached = tmp;
++ return grub_errno;
++ }
++ }
++
++ grub_memset (&data->devices_attached[data->n_devices_attached - 1],
++ 0, sizeof (data->devices_attached[data->n_devices_attached - 1]));
++
++ return fill_vdev_info_real (data, nvlist,
++ &data->devices_attached[data->n_devices_attached - 1],
++ diskdesc);
++}
++
++/*
++ * Check the disk label information and retrieve needed vdev name-value pairs.
++ *
++ */
++static grub_err_t
++check_pool_label (struct grub_zfs_data *data,
++ struct grub_zfs_device_desc *diskdesc)
++{
++ grub_uint64_t pool_state, txg = 0;
++ char *nvlist;
++#if 0
++ char *nv;
++#endif
++ grub_uint64_t poolguid;
++ grub_uint64_t version;
++ int found;
++ grub_err_t err;
++
++ err = zfs_fetch_nvlist (diskdesc, &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");
++
++ found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_GUID,
++ &(diskdesc->guid));
++ if (! found)
++ {
++ grub_free (nvlist);
++ if (! grub_errno)
++ grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_GUID " not found");
++ return grub_errno;
++ }
++
++ found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID,
++ &poolguid);
++ if (! found)
++ {
++ grub_free (nvlist);
++ if (! grub_errno)
++ grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_GUID " not found");
++ return grub_errno;
++ }
++
++ grub_dprintf ("zfs", "check 11 passed\n");
++
++ if (data->mounted && data->guid != poolguid)
++ return grub_error (GRUB_ERR_BAD_FS, "another zpool");
++ else
++ data->guid = poolguid;
++
++ {
++ char *nv;
++ nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE);
++
++ if (!nv)
++ {
++ grub_free (nvlist);
++ return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev tree");
++ }
++ err = fill_vdev_info (data, nv, diskdesc);
++ if (err)
++ {
++ grub_free (nvlist);
++ return err;
++ }
++ }
++ grub_dprintf ("zfs", "check 10 passed\n");
++
++ grub_free (nvlist);
++
++ return GRUB_ERR_NONE;
++}
++
++static grub_err_t
++scan_disk (grub_device_t dev, struct grub_zfs_data *data,
++ int original)
++{
++ int label = 0;
++ uberblock_phys_t *ub_array, *ubbest = NULL;
++ vdev_boot_header_t *bh;
++ grub_err_t err;
++ int vdevnum;
++ struct grub_zfs_device_desc desc;
++
++ ub_array = grub_malloc (VDEV_UBERBLOCK_RING);
++ if (!ub_array)
++ return grub_errno;
++
++ bh = grub_malloc (VDEV_BOOT_HEADER_SIZE);
++ if (!bh)
++ {
++ grub_free (ub_array);
++ return grub_errno;
++ }
++
++ vdevnum = VDEV_LABELS;
++
++ desc.dev = dev;
++ desc.original = original;
++
++ /* Don't check back labels on CDROM. */
++ if (grub_disk_get_size (dev->disk) == GRUB_DISK_SIZE_UNKNOWN)
++ vdevnum = VDEV_LABELS / 2;
++
++ for (label = 0; ubbest == NULL && label < vdevnum; label++)
++ {
++ desc.vdev_phys_sector
++ = label * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT)
++ + ((VDEV_SKIP_SIZE + VDEV_BOOT_HEADER_SIZE) >> SPA_MINBLOCKSHIFT)
++ + (label < VDEV_LABELS / 2 ? 0 : grub_disk_get_size (dev->disk)
++ - VDEV_LABELS * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT));
++
++ /* Read in the uberblock ring (128K). */
++ err = grub_disk_read (dev->disk, desc.vdev_phys_sector
++ + (VDEV_PHYS_SIZE >> SPA_MINBLOCKSHIFT),
++ 0, VDEV_UBERBLOCK_RING, (char *) ub_array);
++ if (err)
++ {
++ grub_errno = GRUB_ERR_NONE;
++ continue;
++ }
++ grub_dprintf ("zfs", "label ok %d\n", label);
++
++ ubbest = find_bestub (ub_array, desc.vdev_phys_sector);
++ if (!ubbest)
++ {
++ grub_dprintf ("zfs", "No uberblock found\n");
++ grub_errno = GRUB_ERR_NONE;
++ continue;
++ }
++
++ grub_memmove (&(desc.current_uberblock),
++ &ubbest->ubp_uberblock, sizeof (uberblock_t));
++ if (original)
++ grub_memmove (&(data->current_uberblock),
++ &ubbest->ubp_uberblock, sizeof (uberblock_t));
++
++ err = check_pool_label (data, &desc);
++ 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
++ grub_free (ub_array);
++ grub_free (bh);
++ return GRUB_ERR_NONE;
++ }
++
++ grub_free (ub_array);
++ grub_free (bh);
++
++ return grub_error (GRUB_ERR_BAD_FS, "couldn't find a valid label");
++}
++
++static grub_err_t
++scan_devices (struct grub_zfs_data *data)
++{
++ auto int hook (const char *name);
++ int hook (const char *name)
++ {
++ grub_device_t dev;
++ grub_err_t err;
++ dev = grub_device_open (name);
++ if (!dev)
++ return 0;
++ if (!dev->disk)
++ {
++ grub_device_close (dev);
++ return 0;
++ }
++ err = scan_disk (dev, data, 0);
++ 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;
++ }
++
++ return 0;
++ }
++ grub_device_iterate (hook);
++ return GRUB_ERR_NONE;
++}
++
++static inline void
++xor (grub_uint64_t *a, const grub_uint64_t *b, grub_size_t s)
++{
++ s /= sizeof (grub_uint64_t);
++ while (s--)
++ *a++ ^= *b++;
++}
++
++/* x**y. */
++static grub_uint8_t powx[255 * 2];
++/* Such an s that x**s = y */
++static int powx_inv[256];
++static const grub_uint8_t poly = 0x1d;
++
++/* perform the operation a ^= b * (x ** (known_idx * recovery_pow) ) */
++static inline void
++xor_out (void *a_in, const void *b_in, grub_size_t s,
++ int known_idx, int recovery_pow)
++{
++ int add;
++ grub_uint8_t *a = a_in;
++ const grub_uint8_t *b = b_in;
++
++ /* Simple xor. */
++ if (known_idx == 0 || recovery_pow == 0)
++ {
++ xor (a_in, b_in, s);
++ return;
++ }
++ add = (known_idx * recovery_pow) % 255;
++ for (;s--; b++, a++)
++ if (*b)
++ *a ^= powx[powx_inv[*b] + add];
++}
++
++static inline grub_uint8_t
++gf_mul (grub_uint8_t a, grub_uint8_t b)
++{
++ if (a == 0 || b == 0)
++ return 0;
++ return powx[powx_inv[a] + powx_inv[b]];
++}
++
++static inline grub_err_t
++recovery (grub_uint8_t *bufs[4], grub_size_t s, const int nbufs,
++ const unsigned *powers,
++ const int *idx)
++{
++ grub_dprintf ("zfs", "recovering %u bufers\n", nbufs);
++ /* Now we have */
++ /* b_i = sum (r_j* (x ** (powers[i] * idx[j])))*/
++ /* Let's invert the matrix in question. */
++ switch (nbufs)
++ {
++ /* Easy: r_0 = bufs[0] / (x << (powers[i] * idx[j])). */
++ case 1:
++ {
++ int add;
++ grub_uint8_t *a;
++ if (powers[0] == 0 || idx[0] == 0)
++ return GRUB_ERR_NONE;
++ add = 255 - ((powers[0] * idx[0]) % 255);
++ for (a = bufs[0]; s--; a++)
++ if (*a)
++ *a = powx[powx_inv[*a] + add];
++ return GRUB_ERR_NONE;
++ }
++ /* Case 2x2: Let's use the determinant formula. */
++ case 2:
++ {
++ grub_uint8_t det, det_inv;
++ grub_uint8_t matrixinv[2][2];
++ unsigned i;
++ /* The determinant is: */
++ det = (powx[(powers[0] * idx[0] + powers[1] * idx[1]) % 255]
++ ^ powx[(powers[0] * idx[1] + powers[1] * idx[0]) % 255]);
++ if (det == 0)
++ return grub_error (GRUB_ERR_BAD_FS, "singular recovery matrix");
++ det_inv = powx[255 - powx_inv[det]];
++ matrixinv[0][0] = gf_mul (powx[(powers[1] * idx[1]) % 255], det_inv);
++ matrixinv[1][1] = gf_mul (powx[(powers[0] * idx[0]) % 255], det_inv);
++ matrixinv[0][1] = gf_mul (powx[(powers[0] * idx[1]) % 255], det_inv);
++ matrixinv[1][0] = gf_mul (powx[(powers[1] * idx[0]) % 255], det_inv);
++ for (i = 0; i < s; i++)
++ {
++ grub_uint8_t b0, b1;
++ b0 = bufs[0][i];
++ b1 = bufs[1][i];
++
++ bufs[0][i] = (gf_mul (b0, matrixinv[0][0])
++ ^ gf_mul (b1, matrixinv[0][1]));
++ bufs[1][i] = (gf_mul (b0, matrixinv[1][0])
++ ^ gf_mul (b1, matrixinv[1][1]));
++ }
++ return GRUB_ERR_NONE;
++ }
++ /* Otherwise use Gauss. */
++ default:
++ {
++ grub_uint8_t matrix1[nbufs][nbufs], matrix2[nbufs][nbufs];
++ int i, j, k;
++
++ for (i = 0; i < nbufs; i++)
++ for (j = 0; j < nbufs; j++)
++ matrix1[i][j] = powx[(powers[i] * idx[j]) % 255];
++ for (i = 0; i < nbufs; i++)
++ for (j = 0; j < nbufs; j++)
++ matrix2[i][j] = 0;
++ for (i = 0; i < nbufs; i++)
++ matrix2[i][i] = 1;
++
++ for (i = 0; i < nbufs; i++)
++ {
++ grub_uint8_t mul;
++ for (j = i; j < nbufs; j++)
++ if (matrix1[i][j])
++ break;
++ if (j == nbufs)
++ return grub_error (GRUB_ERR_BAD_FS, "singular recovery matrix");
++ if (j != i)
++ {
++ int xchng;
++ xchng = j;
++ for (j = 0; j < nbufs; j++)
++ {
++ grub_uint8_t t;
++ t = matrix1[xchng][j];
++ matrix1[xchng][j] = matrix1[i][j];
++ matrix1[i][j] = t;
++ }
++ for (j = 0; j < nbufs; j++)
++ {
++ grub_uint8_t t;
++ t = matrix2[xchng][j];
++ matrix2[xchng][j] = matrix2[i][j];
++ matrix2[i][j] = t;
++ }
++ }
++ mul = powx[255 - powx_inv[matrix1[i][i]]];
++ for (j = 0; j < nbufs; j++)
++ matrix1[i][j] = gf_mul (matrix1[i][j], mul);
++ for (j = 0; j < nbufs; j++)
++ matrix2[i][j] = gf_mul (matrix2[i][j], mul);
++ for (j = i + 1; j < nbufs; j++)
++ {
++ mul = matrix1[j][i];
++ for (k = 0; k < nbufs; k++)
++ matrix1[j][k] ^= gf_mul (matrix1[i][k], mul);
++ for (k = 0; k < nbufs; k++)
++ matrix2[j][k] ^= gf_mul (matrix2[i][k], mul);
++ }
++ }
++ for (i = nbufs - 1; i >= 0; i--)
++ {
++ for (j = 0; j < i; j++)
++ {
++ grub_uint8_t mul;
++ mul = matrix1[j][i];
++ for (k = 0; k < nbufs; k++)
++ matrix1[j][k] ^= gf_mul (matrix1[i][k], mul);
++ for (k = 0; k < nbufs; k++)
++ matrix2[j][k] ^= gf_mul (matrix2[i][k], mul);
++ }
++ }
++
++ for (i = 0; i < (int) s; i++)
++ {
++ grub_uint8_t b[nbufs];
++ for (j = 0; j < nbufs; j++)
++ b[j] = bufs[j][i];
++ for (j = 0; j < nbufs; j++)
++ {
++ bufs[j][i] = 0;
++ for (k = 0; k < nbufs; k++)
++ bufs[j][i] ^= gf_mul (matrix2[j][k], b[k]);
++ }
++ }
++ return GRUB_ERR_NONE;
++ }
++ }
++}
++
++static grub_err_t
++read_device (grub_uint64_t offset, struct grub_zfs_device_desc *desc,
++ grub_size_t len, void *buf)
++{
++ switch (desc->type)
++ {
++ case DEVICE_LEAF:
++ {
++ grub_uint64_t sector;
++ sector = DVA_OFFSET_TO_PHYS_SECTOR (offset);
++ if (!desc->dev)
++ {
++ return grub_error (GRUB_ERR_BAD_FS, "member drive unknown");
++ }
++ /* read in a data block */
++ return grub_disk_read (desc->dev->disk, sector, 0, len, buf);
++ }
++ case DEVICE_MIRROR:
++ {
++ grub_err_t err = GRUB_ERR_NONE;
++ unsigned i;
++ if (desc->n_children <= 0)
++ return grub_error (GRUB_ERR_BAD_FS,
++ "non-positive number of mirror children");
++ for (i = 0; i < desc->n_children; i++)
++ {
++ err = read_device (offset, &desc->children[i],
++ len, buf);
++ if (!err)
++ break;
++ grub_errno = GRUB_ERR_NONE;
++ }
++ return (grub_errno = err);
++ }
++ case DEVICE_RAIDZ:
++ {
++ unsigned c = 0;
++ grub_uint64_t high;
++ grub_uint64_t devn;
++ grub_uint64_t m;
++ grub_uint32_t s, orig_s;
++ void *orig_buf = buf;
++ grub_size_t orig_len = len;
++ grub_uint8_t *recovery_buf[4];
++ grub_size_t recovery_len[4];
++ int recovery_idx[4];
++ unsigned failed_devices = 0;
++ int idx, orig_idx;
++
++ if (desc->nparity < 1 || desc->nparity > 3)
++ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
++ "raidz%d is not supported", desc->nparity);
++
++ orig_s = (((len + (1 << desc->ashift) - 1) >> desc->ashift)
++ + (desc->n_children - desc->nparity) - 1);
++ s = orig_s;
++
++ high = grub_divmod64_full ((offset >> desc->ashift),
++ desc->n_children, &m);
++ if (desc->nparity == 2)
++ c = 2;
++ if (desc->nparity == 3)
++ c = 3;
++ if (((len + (1 << desc->ashift) - 1) >> desc->ashift)
++ >= (desc->n_children - desc->nparity))
++ idx = (desc->n_children - desc->nparity - 1);
++ else
++ idx = ((len + (1 << desc->ashift) - 1) >> desc->ashift) - 1;
++ orig_idx = idx;
++ while (len > 0)
++ {
++ grub_size_t csize;
++ grub_uint32_t bsize;
++ grub_err_t err;
++ bsize = s / (desc->n_children - desc->nparity);
++
++ if (desc->nparity == 1
++ && ((offset >> (desc->ashift + 11)) & 1) == c)
++ c++;
++
++ high = grub_divmod64_full ((offset >> desc->ashift) + c,
++ desc->n_children, &devn);
++ csize = bsize << desc->ashift;
++ if (csize > len)
++ csize = len;
++
++ grub_dprintf ("zfs", "RAIDZ mapping 0x%" PRIxGRUB_UINT64_T
++ "+%u (%" PRIxGRUB_SIZE ", %" PRIxGRUB_UINT32_T
++ ") -> (0x%" PRIxGRUB_UINT64_T ", 0x%"
++ PRIxGRUB_UINT64_T ")\n",
++ offset >> desc->ashift, c, len, bsize, high,
++ devn);
++ err = read_device ((high << desc->ashift)
++ | (offset & ((1 << desc->ashift) - 1)),
++ &desc->children[devn],
++ csize, buf);
++ if (err && failed_devices < desc->nparity)
++ {
++ recovery_buf[failed_devices] = buf;
++ recovery_len[failed_devices] = csize;
++ recovery_idx[failed_devices] = idx;
++ failed_devices++;
++ grub_errno = err = 0;
++ }
++ if (err)
++ return err;
++
++ c++;
++ idx--;
++ s--;
++ buf = (char *) buf + csize;
++ len -= csize;
++ }
++ if (failed_devices)
++ {
++ unsigned redundancy_pow[4];
++ unsigned cur_redundancy_pow = 0;
++ unsigned n_redundancy = 0;
++ unsigned i, j;
++ grub_err_t err;
++
++ /* Compute mul. x**s has a period of 255. */
++ if (powx[0] == 0)
++ {
++ grub_uint8_t cur = 1;
++ for (i = 0; i < 255; i++)
++ {
++ powx[i] = cur;
++ powx[i + 255] = cur;
++ powx_inv[cur] = i;
++ if (cur & 0x80)
++ cur = (cur << 1) ^ poly;
++ else
++ cur <<= 1;
++ }
++ }
++
++ /* Read redundancy data. */
++ for (n_redundancy = 0, cur_redundancy_pow = 0;
++ n_redundancy < failed_devices;
++ cur_redundancy_pow++)
++ {
++ high = grub_divmod64_full ((offset >> desc->ashift)
++ + cur_redundancy_pow
++ + ((desc->nparity == 1)
++ && ((offset >> (desc->ashift + 11))
++ & 1)),
++ desc->n_children, &devn);
++ err = read_device ((high << desc->ashift)
++ | (offset & ((1 << desc->ashift) - 1)),
++ &desc->children[devn],
++ recovery_len[n_redundancy],
++ recovery_buf[n_redundancy]);
++ /* Ignore error if we may still have enough devices. */
++ if (err && n_redundancy + desc->nparity - cur_redundancy_pow - 1
++ >= failed_devices)
++ {
++ grub_errno = GRUB_ERR_NONE;
++ continue;
++ }
++ if (err)
++ return err;
++ redundancy_pow[n_redundancy] = cur_redundancy_pow;
++ n_redundancy++;
++ }
++ /* Now xor-our the parts we already know. */
++ buf = orig_buf;
++ len = orig_len;
++ s = orig_s;
++ idx = orig_idx;
++
++ while (len > 0)
++ {
++ grub_size_t csize;
++ csize = ((s / (desc->n_children - desc->nparity))
++ << desc->ashift);
++ if (csize > len)
++ csize = len;
++
++ for (j = 0; j < failed_devices; j++)
++ if (buf == recovery_buf[j])
++ break;
++
++ if (j == failed_devices)
++ for (j = 0; j < failed_devices; j++)
++ xor_out (recovery_buf[j], buf,
++ csize < recovery_len[j] ? csize : recovery_len[j],
++ idx, redundancy_pow[j]);
++
++ s--;
++ buf = (char *) buf + csize;
++ len -= csize;
++ idx--;
++ }
++ for (i = 0; i < failed_devices
++ && recovery_len[i] == recovery_len[0];
++ i++);
++ /* Since the chunks have variable length handle the last block
++ separately. */
++ if (i != failed_devices)
++ {
++ grub_uint8_t *tmp_recovery_buf[4];
++ for (j = 0; j < i; j++)
++ tmp_recovery_buf[j] = recovery_buf[j] + recovery_len[j] - 1;
++ err = recovery (tmp_recovery_buf, 1, i, redundancy_pow,
++ recovery_idx);
++ if (err)
++ return err;
++ }
++ err = recovery (recovery_buf, recovery_len[failed_devices - 1],
++ failed_devices, redundancy_pow, recovery_idx);
++ if (err)
++ return err;
++ }
++ return GRUB_ERR_NONE;
++ }
++ }
++ return grub_error (GRUB_ERR_BAD_FS, "unsupported device type");
++}
++
++static grub_err_t
++read_dva (const dva_t *dva,
++ grub_zfs_endian_t endian, struct grub_zfs_data *data,
++ void *buf, grub_size_t len)
++{
++ grub_uint64_t offset;
++ unsigned i;
++ grub_err_t err;
++ int try = 0;
++ offset = dva_get_offset (dva, endian);
++
++ for (try = 0; try < 2; try++)
++ {
++ for (i = 0; i < data->n_devices_attached; i++)
++ if (data->devices_attached[i].id == DVA_GET_VDEV (dva))
++ {
++ err = read_device (offset, &data->devices_attached[i], len, buf);
++ if (!err)
++ return GRUB_ERR_NONE;
++ break;
++ }
++ if (try == 1)
++ break;
++ err = scan_devices (data);
++ if (err)
++ return err;
++ }
++ return err;
++}
+
+ /*
+ * Read a block of data based on the gang block address dva,
+@@ -412,7 +1292,6 @@
+ 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;
+@@ -424,13 +1303,8 @@
+ 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);
++ err = read_dva (dva, endian, data, zio_gb, SPA_GANGBLOCKSIZE);
+ if (err)
+ {
+ grub_free (zio_gb);
+@@ -483,20 +1357,13 @@
+ /* 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);
+- }
++ err = read_dva (&bp->blk_dva[i], endian, data, buf, psize);
+ if (!err)
+ return GRUB_ERR_NONE;
+ grub_errno = GRUB_ERR_NONE;
+@@ -527,7 +1394,7 @@
+ *buf = NULL;
+
+ checksum = (grub_zfs_to_cpu64((bp)->blk_prop, endian) >> 40) & 0xff;
+- comp = (grub_zfs_to_cpu64((bp)->blk_prop, endian)>>32) & 0x7;
++ comp = (grub_zfs_to_cpu64((bp)->blk_prop, endian)>>32) & 0xff;
+ lsize = (BP_IS_HOLE(bp) ? 0 :
+ (((grub_zfs_to_cpu64 ((bp)->blk_prop, endian) & 0xffff) + 1)
+ << SPA_MINBLOCKSHIFT));
+@@ -602,7 +1469,8 @@
+ 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;
++ int level;
++ grub_off_t idx;
+ blkptr_t *bp_array = dn->dn.dn_blkptr;
+ int epbs = dn->dn.dn_indblkshift - SPA_BLKPTRSHIFT;
+ blkptr_t *bp;
+@@ -670,7 +1538,8 @@
+ */
+ static grub_err_t
+ mzap_lookup (mzap_phys_t * zapobj, grub_zfs_endian_t endian,
+- int objsize, char *name, grub_uint64_t * value)
++ int objsize, char *name, grub_uint64_t * value,
++ int case_insensitive)
+ {
+ int i, chunks;
+ mzap_ent_phys_t *mzap_ent = zapobj->mz_chunk;
+@@ -678,7 +1547,8 @@
+ chunks = objsize / MZAP_ENT_LEN - 1;
+ for (i = 0; i < chunks; i++)
+ {
+- if (grub_strcmp (mzap_ent[i].mze_name, name) == 0)
++ if (case_insensitive ? (grub_strcasecmp (mzap_ent[i].mze_name, name) == 0)
++ : (grub_strcmp (mzap_ent[i].mze_name, name) == 0))
+ {
+ *value = grub_zfs_to_cpu64 (mzap_ent[i].mze_value, endian);
+ return GRUB_ERR_NONE;
+@@ -711,7 +1581,8 @@
+ }
+
+ static grub_uint64_t
+-zap_hash (grub_uint64_t salt, const char *name)
++zap_hash (grub_uint64_t salt, const char *name,
++ int case_insensitive)
+ {
+ static grub_uint64_t table[256];
+ const grub_uint8_t *cp;
+@@ -729,8 +1600,12 @@
+ }
+ }
+
+- for (cp = (const grub_uint8_t *) name; (c = *cp) != '\0'; cp++)
+- crc = (crc >> 8) ^ table[(crc ^ c) & 0xFF];
++ if (case_insensitive)
++ for (cp = (const grub_uint8_t *) name; (c = *cp) != '\0'; cp++)
++ crc = (crc >> 8) ^ table[(crc ^ grub_toupper (c)) & 0xFF];
++ else
++ for (cp = (const grub_uint8_t *) name; (c = *cp) != '\0'; cp++)
++ crc = (crc >> 8) ^ table[(crc ^ c) & 0xFF];
+
+ /*
+ * Only use 28 bits, since we need 4 bits in the cookie for the
+@@ -748,10 +1623,34 @@
+ * array_len is actual len in bytes (not encoded le_value_length).
+ * buf is null-terminated.
+ */
++
++static inline int
++name_cmp (const char *s1, const char *s2, grub_size_t n,
++ int case_insensitive)
++{
++ const char *t1 = (const char *) s1;
++ const char *t2 = (const char *) s2;
++
++ if (!case_insensitive)
++ return grub_memcmp (t1, t2, n);
++
++ while (n--)
++ {
++ if (grub_toupper (*t1) != grub_toupper (*t2))
++ return (int) grub_toupper (*t1) - (int) grub_toupper (*t2);
++
++ t1++;
++ t2++;
++ }
++
++ return 0;
++}
++
+ /* XXX */
+ static int
+ zap_leaf_array_equal (zap_leaf_phys_t * l, grub_zfs_endian_t endian,
+- int blksft, int chunk, int array_len, const char *buf)
++ int blksft, int chunk, int array_len, const char *buf,
++ int case_insensitive)
+ {
+ int bseen = 0;
+
+@@ -763,7 +1662,8 @@
+ if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft))
+ return (0);
+
+- if (grub_memcmp (la->la_array, buf + bseen, toread) != 0)
++ if (name_cmp ((char *) la->la_array, buf + bseen, toread,
++ case_insensitive) != 0)
+ break;
+ chunk = grub_zfs_to_cpu16 (la->la_next, endian);
+ bseen += toread;
+@@ -804,7 +1704,8 @@
+ 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)
++ const char *name, grub_uint64_t * value,
++ int case_insensitive)
+ {
+ grub_uint16_t chunk;
+ struct zap_leaf_entry *le;
+@@ -816,7 +1717,7 @@
+ 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)
++ chunk != CHAIN_END; chunk = grub_zfs_to_cpu16 (le->le_next, endian))
+ {
+
+ if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft))
+@@ -836,11 +1737,12 @@
+ 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))
++ name, case_insensitive))
+ {
+ struct zap_leaf_array *la;
+
+- if (le->le_int_size != 8 || le->le_value_length != 1)
++ if (le->le_int_size != 8 || grub_zfs_to_cpu16 (le->le_value_length,
++ endian) != 1)
+ return grub_error (GRUB_ERR_BAD_FS, "invalid leaf chunk entry");
+
+ /* get the uint64_t property value */
+@@ -858,9 +1760,9 @@
+
+ /* Verify if this is a fat zap header block */
+ static grub_err_t
+-zap_verify (zap_phys_t *zap)
++zap_verify (zap_phys_t *zap, grub_zfs_endian_t endian)
+ {
+- if (zap->zap_magic != (grub_uint64_t) ZAP_MAGIC)
++ if (grub_zfs_to_cpu64 (zap->zap_magic, endian) != (grub_uint64_t) ZAP_MAGIC)
+ return grub_error (GRUB_ERR_BAD_FS, "bad ZAP magic");
+
+ if (zap->zap_flags != 0)
+@@ -879,7 +1781,8 @@
+ /* 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)
++ char *name, grub_uint64_t * value, struct grub_zfs_data *data,
++ int case_insensitive)
+ {
+ void *l;
+ grub_uint64_t hash, idx, blkid;
+@@ -888,18 +1791,18 @@
+ grub_err_t err;
+ grub_zfs_endian_t leafendian;
+
+- err = zap_verify (zap);
++ err = zap_verify (zap, zap_dnode->endian);
+ if (err)
+ return err;
+
+- hash = zap_hash (zap->zap_salt, name);
++ hash = zap_hash (zap->zap_salt, name, case_insensitive);
+
+ /* get block id from index */
+ if (zap->zap_ptrtbl.zt_numblks != 0)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "external pointer tables not supported");
+ idx = ZAP_HASH_IDX (hash, zap->zap_ptrtbl.zt_shift);
+- blkid = ((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))];
++ blkid = grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))], zap_dnode->endian);
+
+ /* Get the leaf block */
+ if ((1U << blksft) < sizeof (zap_leaf_phys_t))
+@@ -908,7 +1811,8 @@
+ if (err)
+ return err;
+
+- err = zap_leaf_lookup (l, leafendian, blksft, hash, name, value);
++ err = zap_leaf_lookup (l, leafendian, blksft, hash, name, value,
++ case_insensitive);
+ grub_free (l);
+ return err;
+ }
+@@ -922,14 +1826,14 @@
+ {
+ zap_leaf_phys_t *l;
+ void *l_in;
+- grub_uint64_t idx, blkid;
++ grub_uint64_t idx, idx2, blkid;
+ grub_uint16_t chunk;
+ int blksft = zfs_log2 (grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec,
+ zap_dnode->endian) << DNODE_SHIFT);
+ grub_err_t err;
+ grub_zfs_endian_t endian;
+
+- if (zap_verify (zap))
++ if (zap_verify (zap, zap_dnode->endian))
+ return 0;
+
+ /* get block id from index */
+@@ -945,9 +1849,17 @@
+ grub_error (GRUB_ERR_BAD_FS, "ZAP leaf is too small");
+ return 0;
+ }
+- for (idx = 0; idx < zap->zap_ptrtbl.zt_numblks; idx++)
++ for (idx = 0; idx < (1ULL << zap->zap_ptrtbl.zt_shift); idx++)
+ {
+- blkid = ((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))];
++ blkid = grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))],
++ zap_dnode->endian);
++
++ for (idx2 = 0; idx2 < idx; idx2++)
++ if (blkid == grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx2 + (1 << (blksft - 3 - 1))],
++ zap_dnode->endian))
++ break;
++ if (idx2 != idx)
++ continue;
+
+ err = dmu_read (zap_dnode, blkid, &l_in, &endian, data);
+ l = l_in;
+@@ -983,8 +1895,11 @@
+
+ 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))
++ if (zap_leaf_array_get (l, endian, blksft,
++ grub_zfs_to_cpu16 (le->le_name_chunk,
++ endian),
++ grub_zfs_to_cpu16 (le->le_name_length,
++ endian), buf))
+ {
+ grub_free (buf);
+ continue;
+@@ -996,7 +1911,9 @@
+ continue;
+
+ /* get the uint64_t property value */
+- la = &ZAP_LEAF_CHUNK (l, blksft, le->le_value_chunk).l_array;
++ la = &ZAP_LEAF_CHUNK (l, blksft,
++ grub_zfs_to_cpu16 (le->le_value_chunk,
++ endian)).l_array;
+ val = grub_be_to_cpu64 (la->la_array64);
+ if (hook (buf, val))
+ return 1;
+@@ -1014,7 +1931,7 @@
+ */
+ static grub_err_t
+ zap_lookup (dnode_end_t * zap_dnode, char *name, grub_uint64_t * val,
+- struct grub_zfs_data *data)
++ struct grub_zfs_data *data, int case_insensitive)
+ {
+ grub_uint64_t block_type;
+ int size;
+@@ -1037,7 +1954,8 @@
+ if (block_type == ZBT_MICRO)
+ {
+ grub_dprintf ("zfs", "micro zap\n");
+- err = (mzap_lookup (zapbuf, endian, size, name, val));
++ err = mzap_lookup (zapbuf, endian, size, name, val,
++ case_insensitive);
+ grub_dprintf ("zfs", "returned %d\n", err);
+ grub_free (zapbuf);
+ return err;
+@@ -1046,7 +1964,8 @@
+ {
+ grub_dprintf ("zfs", "fat zap\n");
+ /* this is a fat zap */
+- err = (fzap_lookup (zap_dnode, zapbuf, name, val, data));
++ err = fzap_lookup (zap_dnode, zapbuf, name, val, data,
++ case_insensitive);
+ grub_dprintf ("zfs", "returned %d\n", err);
+ grub_free (zapbuf);
+ return err;
+@@ -1074,7 +1993,7 @@
+ return 0;
+ block_type = grub_zfs_to_cpu64 (*((grub_uint64_t *) zapbuf), endian);
+
+- grub_dprintf ("zfs", "zap read\n");
++ grub_dprintf ("zfs", "zap iterate\n");
+
+ if (block_type == ZBT_MICRO)
+ {
+@@ -1172,9 +2091,9 @@
+ */
+ static grub_err_t
+ dnode_get_path (dnode_end_t * mdn, const char *path_in, dnode_end_t * dn,
+- struct grub_zfs_data *data)
++ struct grub_zfs_data *data, int *case_insensitive)
+ {
+- grub_uint64_t objnum, version;
++ grub_uint64_t objnum, version, insensitivity;
+ char *cname, ch;
+ grub_err_t err = GRUB_ERR_NONE;
+ char *path, *path_buf;
+@@ -1199,19 +2118,31 @@
+ return err;
+ }
+
+- err = zap_lookup (&(dnode_path->dn), ZPL_VERSION_STR, &version, data);
++ err = zap_lookup (&(dnode_path->dn), ZPL_VERSION_STR, &version,
++ data, 0);
+ if (err)
+ {
+ grub_free (dn_new);
+ return err;
+ }
++
+ if (version > ZPL_VERSION)
+ {
+ grub_free (dn_new);
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "too new ZPL version");
+ }
+-
+- err = zap_lookup (&(dnode_path->dn), ZFS_ROOT_OBJ, &objnum, data);
++
++ err = zap_lookup (&(dnode_path->dn), "casesensitivity", &insensitivity,
++ data, 0);
++ if (err == GRUB_ERR_FILE_NOT_FOUND)
++ {
++ grub_errno = GRUB_ERR_NONE;
++ insensitivity = 0;
++ }
++ if (case_insensitive)
++ *case_insensitive = insensitivity;
++
++ err = zap_lookup (&(dnode_path->dn), ZFS_ROOT_OBJ, &objnum, data, 0);
+ if (err)
+ {
+ grub_free (dn_new);
+@@ -1272,7 +2203,7 @@
+ grub_free (path_buf);
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+ }
+- err = zap_lookup (&(dnode_path->dn), cname, &objnum, data);
++ err = zap_lookup (&(dnode_path->dn), cname, &objnum, data, insensitivity);
+ if (err)
+ break;
+
+@@ -1291,22 +2222,54 @@
+ 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)
++ if (dnode_path->dn.dn.dn_bonustype == DMU_OT_ZNODE
++ && ((grub_zfs_to_cpu64(((znode_phys_t *) DN_BONUS (&dnode_path->dn.dn))->zp_mode, dnode_path->dn.endian) >> 12) & 0xf) == 0xa)
+ {
++ char *sym_value;
++ grub_size_t sym_sz;
++ int free_symval = 0;
+ char *oldpath = path, *oldpathbuf = path_buf;
+- path = path_buf
+- = grub_malloc (sizeof (dnode_path->dn.dn.dn_bonus)
+- - sizeof (znode_phys_t) + grub_strlen (oldpath) + 1);
++ sym_value = ((char *) DN_BONUS (&dnode_path->dn.dn) + sizeof (struct znode_phys));
++
++ sym_sz = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dnode_path->dn.dn))->zp_size, dnode_path->dn.endian);
++
++ if (dnode_path->dn.dn.dn_flags & 1)
++ {
++ grub_size_t block;
++ grub_size_t blksz;
++ blksz = (grub_zfs_to_cpu16 (dnode_path->dn.dn.dn_datablkszsec,
++ dnode_path->dn.endian)
++ << SPA_MINBLOCKSHIFT);
++
++ sym_value = grub_malloc (sym_sz);
++ if (!sym_value)
++ return grub_errno;
++ for (block = 0; block < (sym_sz + blksz - 1) / blksz; block++)
++ {
++ void *t;
++ grub_size_t movesize;
++
++ err = dmu_read (&(dnode_path->dn), block, &t, 0, data);
++ if (err)
++ return err;
++
++ movesize = MIN (sym_sz - block * blksz, blksz);
++
++ grub_memcpy (sym_value + block * blksz, t, movesize);
++ grub_free (t);
++ }
++ free_symval = 1;
++ }
++ path = path_buf = grub_malloc (sym_sz + 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, sym_value, sym_sz);
++ if (free_symval)
++ grub_free (sym_value);
++ path [sym_sz] = 0;
+ grub_memcpy (path + grub_strlen (path), oldpath,
+ grub_strlen (oldpath) + 1);
+
+@@ -1324,7 +2287,62 @@
+ grub_free (dn_new);
+ }
+ }
+-#endif
++ if (dnode_path->dn.dn.dn_bonustype == DMU_OT_SA)
++ {
++ void *sahdrp;
++ int hdrsize;
++
++ if (dnode_path->dn.dn.dn_bonuslen != 0)
++ {
++ sahdrp = DN_BONUS (&dnode_path->dn.dn);
++ }
++ else if (dnode_path->dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR)
++ {
++ blkptr_t *bp = &dnode_path->dn.dn.dn_spill;
++
++ err = zio_read (bp, dnode_path->dn.endian, &sahdrp, NULL, data);
++ if (err)
++ return err;
++ }
++ else
++ {
++ return grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt");
++ }
++
++ hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp));
++
++ if (((grub_zfs_to_cpu64 (*(grub_uint64_t *) ((char *) sahdrp + hdrsize + SA_TYPE_OFFSET), dnode_path->dn.endian) >> 12) & 0xf) == 0xa)
++ {
++ char *sym_value = (char *) sahdrp + hdrsize + SA_SYMLINK_OFFSET;
++ grub_size_t sym_sz =
++ grub_zfs_to_cpu64 (*(grub_uint64_t *) ((char *) sahdrp + hdrsize + SA_SIZE_OFFSET), dnode_path->dn.endian);
++ char *oldpath = path, *oldpathbuf = path_buf;
++ path = path_buf = grub_malloc (sym_sz + grub_strlen (oldpath) + 1);
++ if (!path_buf)
++ {
++ grub_free (oldpathbuf);
++ return grub_errno;
++ }
++ grub_memcpy (path, sym_value, sym_sz);
++ path [sym_sz] = 0;
++ grub_memcpy (path + grub_strlen (path), oldpath,
++ grub_strlen (oldpath) + 1);
++
++ grub_free (oldpathbuf);
++ if (path[0] != '/')
++ {
++ dn_new = dnode_path;
++ dnode_path = dn_new->next;
++ grub_free (dn_new);
++ }
++ else while (dnode_path != root)
++ {
++ dn_new = dnode_path;
++ dnode_path = dn_new->next;
++ grub_free (dn_new);
++ }
++ }
++ }
+ }
+
+ if (!err)
+@@ -1417,7 +2435,7 @@
+
+ grub_dprintf ("zfs", "alive\n");
+
+- err = zap_lookup (mdn, DMU_POOL_ROOT_DATASET, &objnum, data);
++ err = zap_lookup (mdn, DMU_POOL_ROOT_DATASET, &objnum, data, 0);
+ if (err)
+ return err;
+
+@@ -1452,7 +2470,7 @@
+ if (err)
+ return err;
+
+- err = zap_lookup (mdn, cname, &objnum, data);
++ err = zap_lookup (mdn, cname, &objnum, data, 0);
+ if (err)
+ return err;
+
+@@ -1495,7 +2513,7 @@
+ 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)
++ struct grub_zfs_data *data, int *case_insensitive)
+ {
+ char *fsname, *snapname;
+ const char *ptr_at, *filename;
+@@ -1573,7 +2591,7 @@
+ err = dnode_get (&(data->mos), snapobj,
+ DMU_OT_DSL_DS_SNAP_MAP, mdn, data);
+ if (!err)
+- err = zap_lookup (mdn, snapname, &headobj, data);
++ err = zap_lookup (mdn, snapname, &headobj, data, 0);
+ if (!err)
+ err = dnode_get (&(data->mos), headobj, DMU_OT_DSL_DATASET, mdn, data);
+ if (err)
+@@ -1597,7 +2615,7 @@
+ grub_free (snapname);
+ return GRUB_ERR_NONE;
+ }
+- err = dnode_get_path (mdn, filename, dn, data);
++ err = dnode_get_path (mdn, filename, dn, data, case_insensitive);
+ grub_free (fsname);
+ grub_free (snapname);
+ return err;
+@@ -1625,11 +2643,12 @@
+ */
+
+ static int
+-nvlist_find_value (char *nvlist, char *name, int valtype, char **val,
++nvlist_find_value (const char *nvlist, const 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;
++ const char *nvpair, *nvp_name;
+
+ /* Verify if the 1st and 2nd byte in the nvlist are valid. */
+ /* NOTE: independently of what endianness header announces all
+@@ -1671,7 +2690,7 @@
+
+ if ((grub_strncmp (nvp_name, name, name_len) == 0) && type == valtype)
+ {
+- *val = nvpair;
++ *val = (char *) nvpair;
+ *size_out = encode_size;
+ if (nelm_out)
+ *nelm_out = nelm;
+@@ -1684,7 +2703,8 @@
+ }
+
+ int
+-grub_zfs_nvlist_lookup_uint64 (char *nvlist, char *name, grub_uint64_t * out)
++grub_zfs_nvlist_lookup_uint64 (const char *nvlist, const char *name,
++ grub_uint64_t * out)
+ {
+ char *nvpair;
+ grub_size_t size;
+@@ -1704,7 +2724,7 @@
+ }
+
+ char *
+-grub_zfs_nvlist_lookup_string (char *nvlist, char *name)
++grub_zfs_nvlist_lookup_string (const char *nvlist, const char *name)
+ {
+ char *nvpair;
+ char *ret;
+@@ -1732,7 +2752,7 @@
+ }
+
+ char *
+-grub_zfs_nvlist_lookup_nvlist (char *nvlist, char *name)
++grub_zfs_nvlist_lookup_nvlist (const char *nvlist, const char *name)
+ {
+ char *nvpair;
+ char *ret;
+@@ -1753,199 +2773,114 @@
+ }
+
+ 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_zfs_nvlist_lookup_nvlist_array_get_nelm (const char *nvlist,
++ const char *name)
+ {
+- grub_uint64_t pool_state, txg = 0;
+- char *nvlist;
+-#if 0
+- char *nv;
+-#endif
+- grub_uint64_t diskguid;
+- grub_uint64_t version;
++ char *nvpair;
++ grub_size_t nelm, size;
+ int found;
+- grub_err_t err;
+
+- err = zfs_fetch_nvlist (data, &nvlist);
+- if (err)
+- return err;
++ found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST_ARRAY, &nvpair,
++ &size, &nelm);
++ if (! found)
++ return -1;
++ return nelm;
++}
+
+- grub_dprintf ("zfs", "check 2 passed\n");
++static int
++get_nvlist_size (const char *beg, const char *limit)
++{
++ const char *ptr;
++ grub_uint32_t encode_size;
++
++ ptr = beg + 8;
+
+- 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");
++ while (ptr < limit
++ && (encode_size = grub_be_to_cpu32 (*(grub_uint32_t *) ptr)))
++ ptr += encode_size; /* goto the next nvpair */
++ ptr += 8;
++ return (ptr > limit) ? -1 : (ptr - beg);
++}
+
+- 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");
++char *
++grub_zfs_nvlist_lookup_nvlist_array (const char *nvlist, const char *name,
++ grub_size_t index)
++{
++ char *nvpair, *nvpairptr;
++ int found;
++ char *ret;
++ grub_size_t size;
++ unsigned i;
++ grub_size_t nelm;
++ int elemsize = 0;
+
+- found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_TXG, &txg);
++ found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST_ARRAY, &nvpair,
++ &size, &nelm);
+ if (!found)
++ return 0;
++ if (index >= nelm)
+ {
+- grub_free (nvlist);
+- if (! grub_errno)
+- grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_TXG " not found");
+- return grub_errno;
++ grub_error (GRUB_ERR_OUT_OF_RANGE, "trying to lookup past nvlist array");
++ return 0;
+ }
+- 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");
++ nvpairptr = nvpair;
+
+- found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_VERSION,
+- &version);
+- if (! found)
++ for (i = 0; i < index; i++)
+ {
+- 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");
++ int r;
++ r = get_nvlist_size (nvpairptr, nvpair + size);
+
+- 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);
++ if (r < 0)
++ {
++ grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist array");
++ return NULL;
++ }
++ nvpairptr += r;
+ }
+- grub_dprintf ("zfs", "check 10 passed\n");
+-#endif
+
+- found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_GUID, &diskguid);
+- if (! found)
++ elemsize = get_nvlist_size (nvpairptr, nvpair + size);
++
++ if (elemsize < 0)
+ {
+- grub_free (nvlist);
+- if (! grub_errno)
+- grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_GUID " not found");
+- return grub_errno;
++ grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist array");
++ return 0;
+ }
+- grub_dprintf ("zfs", "check 11 passed\n");
+
+- grub_free (nvlist);
++ ret = grub_zalloc (elemsize + sizeof (grub_uint32_t));
++ if (!ret)
++ return 0;
++ grub_memcpy (ret, nvlist, sizeof (grub_uint32_t));
+
+- return GRUB_ERR_NONE;
++ grub_memcpy (ret + sizeof (grub_uint32_t), nvpairptr, elemsize);
++ return ret;
++}
++
++static void
++unmount_device (struct grub_zfs_device_desc *desc)
++{
++ unsigned i;
++ switch (desc->type)
++ {
++ case DEVICE_LEAF:
++ if (!desc->original && desc->dev)
++ grub_device_close (desc->dev);
++ return;
++ case DEVICE_RAIDZ:
++ case DEVICE_MIRROR:
++ for (i = 0; i < desc->n_children; i++)
++ unmount_device (&desc->children[i]);
++ return;
++ }
+ }
+
+ static void
+ zfs_unmount (struct grub_zfs_data *data)
+ {
++ unsigned i;
++ for (i = 0; i < data->n_devices_attached; i++)
++ unmount_device (&data->devices_attached[i]);
++ grub_free (data->devices_attached);
+ grub_free (data->dnode_buf);
+ grub_free (data->dnode_mdn);
+ grub_free (data->file_buf);
+@@ -1961,13 +2896,11 @@
+ 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;
++ objset_phys_t *osp = 0;
++ grub_size_t ospsize;
++ grub_zfs_endian_t ub_endian = UNKNOWN_ENDIAN;
++ uberblock_t *ub;
+
+ if (! dev->disk)
+ {
+@@ -1975,119 +2908,56 @@
+ return 0;
+ }
+
+- data = grub_malloc (sizeof (*data));
++ data = grub_zalloc (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)
++ data->n_devices_allocated = 16;
++ data->devices_attached = grub_malloc (sizeof (data->devices_attached[0])
++ * data->n_devices_allocated);
++ data->n_devices_attached = 0;
++ err = scan_disk (dev, data, 1);
++ if (err)
+ {
+ zfs_unmount (data);
+- return 0;
++ return NULL;
+ }
+
+- bh = grub_malloc (VDEV_BOOT_HEADER_SIZE);
+- if (!bh)
++ ub = &(data->current_uberblock);
++ ub_endian = (grub_zfs_to_cpu64 (ub->ub_magic,
++ LITTLE_ENDIAN) == UBERBLOCK_MAGIC
++ ? LITTLE_ENDIAN : BIG_ENDIAN);
++
++ err = zio_read (&ub->ub_rootbp, ub_endian,
++ (void **) &osp, &ospsize, data);
++ if (err)
+ {
+ zfs_unmount (data);
+- grub_free (ub_array);
+- return 0;
++ return NULL;
+ }
+
+- 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++)
++ if (ospsize < OBJSET_PHYS_SIZE_V14)
+ {
+- 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_error (GRUB_ERR_BAD_FS, "OSP too small");
+ grub_free (osp);
+- return data;
++ zfs_unmount (data);
++ return NULL;
+ }
+- grub_error (GRUB_ERR_BAD_FS, "couldn't find a valid label");
+- zfs_unmount (data);
+- grub_free (ub_array);
+- grub_free (bh);
++
++ /* Got the MOS. Save it at the memory addr MOS. */
++ grub_memmove (&(data->mos.dn), &osp->os_meta_dnode, DNODE_SIZE);
++ data->mos.endian = (grub_zfs_to_cpu64 (ub->ub_rootbp.blk_prop,
++ ub_endian) >> 63) & 1;
+ grub_free (osp);
+
+- return 0;
++ data->mounted = 1;
++
++ return data;
+ }
+
+ grub_err_t
+@@ -2099,7 +2969,7 @@
+ zfs = zfs_mount (dev);
+ if (!zfs)
+ return grub_errno;
+- err = zfs_fetch_nvlist (zfs, nvlist);
++ err = zfs_fetch_nvlist (zfs->device_original, nvlist);
+ zfs_unmount (zfs);
+ return err;
+ }
+@@ -2115,7 +2985,7 @@
+ if (! data)
+ return grub_errno;
+
+- err = zfs_fetch_nvlist (data, &nvlist);
++ err = zfs_fetch_nvlist (data->device_original, &nvlist);
+ if (err)
+ {
+ zfs_unmount (data);
+@@ -2131,11 +3001,7 @@
+ 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;
+
+@@ -2143,24 +3009,36 @@
+ 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);
++ *uuid = grub_xasprintf ("%016llx", (long long unsigned) data->guid);
+ zfs_unmount (data);
+ if (! *uuid)
+ return grub_errno;
+ return GRUB_ERR_NONE;
+ }
+
++static grub_err_t
++zfs_mtime (grub_device_t device, grub_int32_t *mt)
++{
++ struct grub_zfs_data *data;
++ grub_zfs_endian_t ub_endian = UNKNOWN_ENDIAN;
++ uberblock_t *ub;
++
++ *mt = 0;
++
++ data = zfs_mount (device);
++ if (! data)
++ return grub_errno;
++
++ ub = &(data->current_uberblock);
++ ub_endian = (grub_zfs_to_cpu64 (ub->ub_magic,
++ LITTLE_ENDIAN) == UBERBLOCK_MAGIC
++ ? LITTLE_ENDIAN : BIG_ENDIAN);
++
++ *mt = grub_zfs_to_cpu64 (ub->ub_timestamp, ub_endian);
++ zfs_unmount (data);
++ return GRUB_ERR_NONE;
++}
++
+ /*
+ * zfs_open() locates a file in the rootpool by following the
+ * MOS and places the dnode of the file in the memory address DNODE.
+@@ -2177,7 +3055,7 @@
+ return grub_errno;
+
+ err = dnode_get_fullpath (fsfilename, &(data->mdn), 0,
+- &(data->dnode), &isfs, data);
++ &(data->dnode), &isfs, data, NULL);
+ if (err)
+ {
+ zfs_unmount (data);
+@@ -2227,12 +3105,14 @@
+ }
+
+ hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp));
+- file->size = *(grub_uint64_t *) ((char *) sahdrp + hdrsize + SA_SIZE_OFFSET);
++ file->size = grub_zfs_to_cpu64 (*(grub_uint64_t *) ((char *) sahdrp + hdrsize + SA_SIZE_OFFSET), data->dnode.endian);
+ }
+- else
++ else if (data->dnode.dn.dn_bonustype == DMU_OT_ZNODE)
+ {
+ file->size = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&data->dnode.dn))->zp_size, data->dnode.endian);
+ }
++ else
++ return grub_error (GRUB_ERR_BAD_FS, "bad bonus type");
+
+ file->data = data;
+ file->offset = 0;
+@@ -2248,7 +3128,7 @@
+ 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 blksz, movesize;
+ grub_size_t length;
+ grub_size_t read;
+ grub_err_t err;
+@@ -2302,7 +3182,7 @@
+ data->file_start = blkid * blksz;
+ data->file_end = data->file_start + blksz;
+
+- movesize = MIN (length, data->file_end - (int) file->offset - read);
++ movesize = MIN (length, data->file_end - file->offset - read);
+
+ grub_memmove (buf, data->file_buf + file->offset + read
+ - data->file_start, movesize);
+@@ -2339,7 +3219,7 @@
+ return grub_errno;
+
+ err = dnode_get_fullpath (fsfilename, &(data->mdn), mdnobj,
+- &(data->dnode), &isfs, data);
++ &(data->dnode), &isfs, data, NULL);
+ zfs_unmount (data);
+ return err;
+ }
+@@ -2377,7 +3257,7 @@
+ return;
+ }
+
+- err = zap_lookup (&dn, ZFS_ROOT_OBJ, &objnum, data);
++ err = zap_lookup (&dn, ZFS_ROOT_OBJ, &objnum, data, 0);
+ if (err)
+ {
+ grub_dprintf ("zfs", "failed here\n");
+@@ -2391,8 +3271,39 @@
+ return;
+ }
+
+- info->mtimeset = 1;
+- info->mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0], dn.endian);
++ if (dn.dn.dn_bonustype == DMU_OT_SA)
++ {
++ void *sahdrp;
++ int hdrsize;
++
++ if (dn.dn.dn_bonuslen != 0)
++ {
++ sahdrp = (sa_hdr_phys_t *) DN_BONUS (&dn.dn);
++ }
++ else if (dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR)
++ {
++ blkptr_t *bp = &dn.dn.dn_spill;
++
++ err = zio_read (bp, dn.endian, &sahdrp, NULL, data);
++ if (err)
++ return;
++ }
++ else
++ {
++ grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt");
++ return;
++ }
++
++ hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp));
++ info->mtimeset = 1;
++ info->mtime = grub_zfs_to_cpu64 (*(grub_uint64_t *) ((char *) sahdrp + hdrsize + SA_MTIME_OFFSET), dn.endian);
++ }
++
++ if (dn.dn.dn_bonustype == DMU_OT_ZNODE)
++ {
++ info->mtimeset = 1;
++ info->mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0], dn.endian);
++ }
+ return;
+ }
+
+@@ -2403,6 +3314,7 @@
+ struct grub_zfs_data *data;
+ grub_err_t err;
+ int isfs;
++ int case_insensitive = 0;
+ 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);
+@@ -2416,10 +3328,48 @@
+ 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",
++
++ if (dn.dn.dn_bonustype == DMU_OT_SA)
++ {
++ void *sahdrp;
++ int hdrsize;
++
++ if (dn.dn.dn_bonuslen != 0)
++ {
++ sahdrp = (sa_hdr_phys_t *) DN_BONUS (&data->dnode.dn);
++ }
++ else if (dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR)
++ {
++ blkptr_t *bp = &dn.dn.dn_spill;
++
++ err = zio_read (bp, dn.endian, &sahdrp, NULL, data);
++ if (err)
++ {
++ grub_print_error ();
++ return 0;
++ }
++ }
++ else
++ {
++ grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt");
++ grub_print_error ();
++ return 0;
++ }
++
++ hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp));
++ info.mtimeset = 1;
++ info.mtime = grub_zfs_to_cpu64 (*(grub_uint64_t *) ((char *) sahdrp + hdrsize + SA_MTIME_OFFSET), dn.endian);
++ info.case_insensitive = case_insensitive;
++ }
++
++ if (dn.dn.dn_bonustype == DMU_OT_ZNODE)
++ {
++ info.mtimeset = 1;
++ info.mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0],
++ dn.endian);
++ }
++ info.dir = (dn.dn.dn_type == DMU_OT_DIRECTORY_CONTENTS);
++ grub_dprintf ("zfs", "type=%d, name=%s\n",
+ (int)dn.dn.dn_type, (char *)name);
+ return hook (name, &info);
+ }
+@@ -2464,7 +3414,8 @@
+ data = zfs_mount (device);
+ if (! data)
+ return grub_errno;
+- err = dnode_get_fullpath (path, &(data->mdn), 0, &(data->dnode), &isfs, data);
++ err = dnode_get_fullpath (path, &(data->mdn), 0, &(data->dnode), &isfs, data,
++ &case_insensitive);
+ if (err)
+ {
+ zfs_unmount (data);
+@@ -2532,12 +3483,13 @@
+ .close = grub_zfs_close,
+ .label = zfs_label,
+ .uuid = zfs_uuid,
+- .mtime = 0,
++ .mtime = zfs_mtime,
+ .next = 0
+ };
+
+ GRUB_MOD_INIT (zfs)
+ {
++ COMPILE_TIME_ASSERT (sizeof (zap_leaf_chunk_t) == ZAP_LEAF_CHUNKSIZE);
+ grub_fs_register (&grub_zfs_fs);
+ #ifndef GRUB_UTIL
+ my_mod = mod;
+--- a/include/grub/zfs/zio.h
++++ b/include/grub/zfs/zio.h
+@@ -77,7 +77,15 @@
+ ZIO_COMPRESS_OFF,
+ ZIO_COMPRESS_LZJB,
+ ZIO_COMPRESS_EMPTY,
+- ZIO_COMPRESS_GZIP,
++ ZIO_COMPRESS_GZIP1,
++ ZIO_COMPRESS_GZIP2,
++ ZIO_COMPRESS_GZIP3,
++ ZIO_COMPRESS_GZIP4,
++ ZIO_COMPRESS_GZIP5,
++ ZIO_COMPRESS_GZIP6,
++ ZIO_COMPRESS_GZIP7,
++ ZIO_COMPRESS_GZIP8,
++ ZIO_COMPRESS_GZIP9,
+ ZIO_COMPRESS_FUNCTIONS
+ };
+
+--- a/grub-core/fs/zfs/zfsinfo.c
++++ b/grub-core/fs/zfs/zfsinfo.c
+@@ -141,7 +141,6 @@
+ }
+ grub_printf ("Mirror VDEV with %d children\n", nelm);
+ print_state (nvlist, tab);
+-
+ for (i = 0; i < nelm; i++)
+ {
+ char *child;
+@@ -161,6 +160,7 @@
+
+ grub_free (child);
+ }
++ return GRUB_ERR_NONE;
+ }
+
+ print_tabs (tab);
+--- a/include/grub/zfs/sa_impl.h
++++ b/include/grub/zfs/sa_impl.h
+@@ -29,6 +29,9 @@
+ } sa_hdr_phys_t;
+
+ #define SA_HDR_SIZE(hdr) BF32_GET_SB(hdr->sa_layout_info, 10, 16, 3, 0)
++#define SA_TYPE_OFFSET 0x0
+ #define SA_SIZE_OFFSET 0x8
++#define SA_MTIME_OFFSET 0x38
++#define SA_SYMLINK_OFFSET 0xa0
+
+ #endif /* _SYS_SA_IMPL_H */
+--- a/include/grub/zfs/zfs.h
++++ b/include/grub/zfs/zfs.h
+@@ -28,7 +28,7 @@
+ /*
+ * On-disk version number.
+ */
+-#define SPA_VERSION 28ULL
++#define SPA_VERSION 33ULL
+
+ /*
+ * The following are configuration names used in the nvlist describing a pool's
+@@ -112,12 +112,14 @@
+ grub_err_t grub_zfs_getmdnobj (grub_device_t dev, const char *fsfilename,
+ grub_uint64_t *mdnobj);
+
+-char *grub_zfs_nvlist_lookup_string (char *nvlist, char *name);
+-char *grub_zfs_nvlist_lookup_nvlist (char *nvlist, char *name);
+-int grub_zfs_nvlist_lookup_uint64 (char *nvlist, char *name,
++char *grub_zfs_nvlist_lookup_string (const char *nvlist, const char *name);
++char *grub_zfs_nvlist_lookup_nvlist (const char *nvlist, const char *name);
++int grub_zfs_nvlist_lookup_uint64 (const char *nvlist, const char *name,
+ grub_uint64_t *out);
+-char *grub_zfs_nvlist_lookup_nvlist_array (char *nvlist, char *name,
++char *grub_zfs_nvlist_lookup_nvlist_array (const char *nvlist,
++ const char *name,
+ grub_size_t index);
+-int grub_zfs_nvlist_lookup_nvlist_array_get_nelm (char *nvlist, char *name);
++int grub_zfs_nvlist_lookup_nvlist_array_get_nelm (const char *nvlist,
++ const char *name);
+
+ #endif /* ! GRUB_ZFS_HEADER */