struct grub_zfs_device_desc
{
- enum { DEVICE_LEAF } type;
- grub_disk_t disk;
+ enum { DEVICE_LEAF, DEVICE_MIRROR } 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 leaf devices. */
+ grub_device_t dev;
grub_disk_addr_t vdev_phys_sector;
uberblock_t current_uberblock;
+ int original;
};
struct grub_zfs_data
unsigned n_devices_attached;
unsigned n_devices_allocated;
+ uberblock_t current_uberblock;
+
int mounted;
grub_uint64_t guid;
};
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->disk, diskdesc->vdev_phys_sector, 0,
+ err = grub_disk_read (diskdesc->dev->disk, diskdesc->vdev_phys_sector, 0,
VDEV_PHYS_SIZE, *nvlist);
if (err)
{
}
static grub_err_t
-fill_vdev_info (char *nvlist, struct grub_zfs_device_desc *diskdesc)
+fill_vdev_info_real (const char *nvlist,
+ struct grub_zfs_device_desc *fill,
+ struct grub_zfs_device_desc *insert)
{
- char *type = 0;
+ 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)
{
- diskdesc->type = DEVICE_LEAF;
- if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "id", &diskdesc->id))
- return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id");
+ 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;
+ }
+
+ return GRUB_ERR_NONE;
+ }
+
+ if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0)
+ {
+ int nelm, i;
+ fill->type = DEVICE_MIRROR;
+
+ 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");
+
+ 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 (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 (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 (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 *id)
+ struct grub_zfs_device_desc *diskdesc)
{
grub_uint64_t pool_state, txg = 0;
char *nvlist;
#if 0
char *nv;
#endif
- grub_uint64_t diskguid, poolguid;
+ grub_uint64_t poolguid;
grub_uint64_t version;
int found;
grub_err_t err;
}
grub_dprintf ("zfs", "check 9 passed\n");
- {
- 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 (nv, diskdesc);
- if (err)
- {
- grub_free (nvlist);
- return err;
- }
- }
- grub_dprintf ("zfs", "check 10 passed\n");
-
- found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_GUID, &diskguid);
+ found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_GUID,
+ &(diskdesc->guid));
if (! found)
{
grub_free (nvlist);
return grub_error (GRUB_ERR_BAD_FS, "another zpool");
else
data->guid = poolguid;
- grub_free (nvlist);
- 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;
- }
- }
+ {
+ char *nv;
+ nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE);
- data->devices_attached[data->n_devices_attached - 1] = *diskdesc;
- if (id)
- *id = diskdesc->id;
+ 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_disk_t disk, struct grub_zfs_data *data,
- grub_uint64_t *id)
+scan_disk (grub_device_t dev, struct grub_zfs_data *data,
+ int original)
{
int label = 0;
uberblock_phys_t *ub_array, *ubbest = NULL;
vdevnum = VDEV_LABELS;
- desc.disk = disk;
+ desc.dev = dev;
+ desc.original = original;
/* Don't check back labels on CDROM. */
- if (grub_disk_get_size (disk) == GRUB_DISK_SIZE_UNKNOWN)
+ if (grub_disk_get_size (dev->disk) == GRUB_DISK_SIZE_UNKNOWN)
vdevnum = VDEV_LABELS / 2;
for (label = 0; ubbest == NULL && label < vdevnum; label++)
{
- grub_dprintf ("zfs", "label %d\n", 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 (disk)
+ + (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 (disk, desc.vdev_phys_sector
+ 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_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, id);
+ err = check_pool_label (data, &desc);
if (err)
{
grub_errno = GRUB_ERR_NONE;
{
grub_device_t dev;
grub_err_t err;
- grub_uint64_t f_id = -1;
dev = grub_device_open (name);
if (!dev)
return 0;
grub_device_close (dev);
return 0;
}
- err = scan_disk (dev->disk, data, &f_id);
+ err = scan_disk (dev, data, 0);
if (err == GRUB_ERR_BAD_FS)
{
grub_device_close (dev);
grub_print_error ();
return 0;
}
- if (f_id != id)
- {
- grub_device_close (dev);
- return 0;
- }
dev_found = dev;
return 1;
return GRUB_ERR_NONE;
}
+static grub_err_t
+read_device (grub_uint64_t sector, struct grub_zfs_device_desc *desc,
+ grub_size_t len, void *buf)
+{
+ switch (desc->type)
+ {
+ case DEVICE_LEAF:
+ 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;
+ for (i = 0; i < desc->n_children; i++)
+ {
+ err = read_device (sector, &desc->children[i],
+ len, buf);
+ if (!err)
+ break;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ return (grub_errno = err);
+ }
+ default:
+ 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,
unsigned i;
grub_err_t err;
int try = 0;
+ offset = dva_get_offset (dva, endian);
+ sector = DVA_OFFSET_TO_PHYS_SECTOR (offset);
for (try = 0; try < 1; try++)
{
for (i = 0; i < data->n_devices_attached; i++)
if (data->devices_attached[i].id == DVA_GET_VDEV (dva))
- {
- /* read in a data block */
- offset = dva_get_offset (dva, endian);
- sector = DVA_OFFSET_TO_PHYS_SECTOR (offset);
- return grub_disk_read (data->devices_attached[i].disk, sector,
- 0, len, buf);
- }
+ return read_device (sector, &data->devices_attached[i],
+ len, buf);
err = scan_devices (data, DVA_GET_VDEV (dva));
if (err)
return err;
*/
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
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;
}
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;
}
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;
}
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;
}
int
-grub_zfs_nvlist_lookup_nvlist_array_get_nelm (char *nvlist, char *name)
+grub_zfs_nvlist_lookup_nvlist_array_get_nelm (const char *nvlist,
+ const char *name)
{
char *nvpair;
grub_size_t nelm, size;
}
static int
-get_nvlist_size (char *beg, char *limit)
+get_nvlist_size (const char *beg, const char *limit)
{
- char *ptr;
+ const char *ptr;
grub_uint32_t encode_size;
ptr = beg + 8;
}
char *
-grub_zfs_nvlist_lookup_nvlist_array (char *nvlist, char *name,
+grub_zfs_nvlist_lookup_nvlist_array (const char *nvlist, const char *name,
grub_size_t index)
{
char *nvpair, *nvpairptr;
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_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->dnode_buf);
grub_free (data->dnode_mdn);
grub_free (data->file_buf);
data->devices_attached = grub_malloc (sizeof (data->devices_attached[0])
* data->n_devices_allocated);
data->n_devices_attached = 0;
- err = scan_disk (dev->disk, data, NULL);
+ err = scan_disk (dev, data, 1);
if (err)
{
zfs_unmount (data);
return NULL;
}
- ub = &(data->devices_attached[0].current_uberblock);
+ ub = &(data->current_uberblock);
ub_endian = (grub_zfs_to_cpu64 (ub->ub_magic,
LITTLE_ENDIAN) == UBERBLOCK_MAGIC
? LITTLE_ENDIAN : BIG_ENDIAN);