<< SPA_MINBLOCKSHIFT);
}
-static grub_uint64_t
-dva_get_offset (dva_t * dva, grub_zfs_endian_t endian)
+static grub_uint64_t
+dva_get_offset (const dva_t *dva, grub_zfs_endian_t endian)
+{
+ grub_dprintf ("zfs", "dva=%llx, %llx\n",
+ (unsigned long long) dva->dva_word[0],
+ (unsigned long long) dva->dva_word[1]);
+ return grub_zfs_to_cpu64 ((dva)->dva_word[1],
+ endian) << SPA_MINBLOCKSHIFT;
+}
+
+static grub_err_t
+zfs_fetch_nvlist (struct grub_zfs_device_desc *diskdesc, char **nvlist)
+{
+ grub_err_t err;
+
+ *nvlist = 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;
+ }
+
+ 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 grub_err_t
+read_device (grub_uint64_t offset, struct grub_zfs_device_desc *desc,
+ grub_uint32_t asize, 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;
+ 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], asize,
+ len, buf);
+ if (!err)
+ break;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ return (grub_errno = err);
+ }
+ case DEVICE_RAIDZ:
+ {
+ grub_uint64_t sector;
+ grub_uint32_t bsize;
+ unsigned c = 0;
+
+ bsize = (asize + desc->nparity) / desc->n_children;
+ sector = offset >> 9;
+ while (len > 0)
+ {
+ grub_size_t csize;
+ grub_uint64_t high;
- grub_uint32_t devn;
++ grub_uint64_t devn;
+ grub_err_t err;
+ high = grub_divmod64 (sector + (asize > 2) + c, desc->n_children,
+ &devn);
+ csize = bsize << 9;
+ if (csize > len)
+ csize = len;
+ grub_dprintf ("zfs", "RAIDZ mapping 0x%" PRIxGRUB_UINT64_T
- "+%d+%u -> (0x%" PRIxGRUB_UINT64_T ", 0x%x)\n",
++ "+%d+%u -> (0x%" PRIxGRUB_UINT64_T ", 0x%"
++ PRIxGRUB_UINT64_T ")\n",
+ sector,(asize > 2), c, high, devn);
+ err = read_device (high << 9, &desc->children[devn],
+ bsize, csize, buf);
+ if (err)
+ return err;
+ c++;
+ buf = (char *) buf + csize;
+ len -= csize;
+ }
+ return GRUB_ERR_NONE;
+ }
+ 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_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;
-}
+ 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],
+ dva->dva_word[0] & 0xffffff, 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,
name))
{
struct zap_leaf_array *la;
- grub_uint8_t *ip;
- 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 */
grub_error (GRUB_ERR_BAD_FS, "ZAP leaf is too small");
return 0;
}
- for (idx = 0; idx < grub_zfs_to_cpu64 (zap->zap_num_leafs,
- zap_dnode->endian); 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);
- err = dmu_read (zap_dnode, blkid, (void **) &l, &endian, data);
+ for (idx2 = 0; idx2 < idx; idx2++)
+ if (blkid == ((grub_uint64_t *) zap)[idx2 + (1 << (blksft - 3 - 1))])
+ break;
+ if (idx2 != idx)
+ continue;
+
+ err = dmu_read (zap_dnode, blkid, &l_in, &endian, data);
+ l = l_in;
if (err)
{
grub_errno = GRUB_ERR_NONE;