diff --git a/grub-core/fs/zfs/zfs.c b/grub-core/fs/zfs/zfs.c index 3163e2e78..b5f25473f 100644 --- a/grub-core/fs/zfs/zfs.c +++ b/grub-core/fs/zfs/zfs.c @@ -140,11 +140,19 @@ typedef struct dnode_end 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 @@ -169,6 +177,8 @@ struct grub_zfs_data unsigned n_devices_attached; unsigned n_devices_allocated; + uberblock_t current_uberblock; + int mounted; grub_uint64_t guid; }; @@ -434,8 +444,11 @@ 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->disk, diskdesc->vdev_phys_sector, 0, + err = grub_disk_read (diskdesc->dev->disk, diskdesc->vdev_phys_sector, 0, VDEV_PHYS_SIZE, *nvlist); if (err) { @@ -447,41 +460,129 @@ zfs_fetch_nvlist (struct grub_zfs_device_desc *diskdesc, char **nvlist) } 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; @@ -549,25 +650,8 @@ check_pool_label (struct grub_zfs_data *data, } 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); @@ -592,34 +676,33 @@ check_pool_label (struct grub_zfs_data *data, 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); - 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; - } - } - - data->devices_attached[data->n_devices_attached - 1] = *diskdesc; - if (id) - *id = diskdesc->id; - 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; @@ -641,24 +724,23 @@ scan_disk (grub_disk_t disk, struct grub_zfs_data *data, 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) @@ -678,8 +760,11 @@ scan_disk (grub_disk_t disk, struct grub_zfs_data *data, 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; @@ -711,7 +796,6 @@ scan_devices (struct grub_zfs_data *data, grub_uint64_t id) { grub_device_t dev; grub_err_t err; - grub_uint64_t f_id = -1; dev = grub_device_open (name); if (!dev) return 0; @@ -720,7 +804,7 @@ scan_devices (struct grub_zfs_data *data, grub_uint64_t id) 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); @@ -733,11 +817,6 @@ scan_devices (struct grub_zfs_data *data, grub_uint64_t id) grub_print_error (); return 0; } - if (f_id != id) - { - grub_device_close (dev); - return 0; - } dev_found = dev; return 1; @@ -749,6 +828,36 @@ scan_devices (struct grub_zfs_data *data, grub_uint64_t id) 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, @@ -758,18 +867,15 @@ read_dva (const dva_t *dva, 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; @@ -2026,11 +2132,12 @@ dnode_get_fullpath (const char *fullpath, dnode_end_t * mdn, */ 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 @@ -2072,7 +2179,7 @@ nvlist_find_value (char *nvlist, char *name, int valtype, char **val, 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; @@ -2085,7 +2192,8 @@ nvlist_find_value (char *nvlist, char *name, int valtype, char **val, } 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; @@ -2105,7 +2213,7 @@ grub_zfs_nvlist_lookup_uint64 (char *nvlist, char *name, grub_uint64_t * out) } 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; @@ -2133,7 +2241,7 @@ grub_zfs_nvlist_lookup_string (char *nvlist, char *name) } 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; @@ -2154,7 +2262,8 @@ grub_zfs_nvlist_lookup_nvlist (char *nvlist, char *name) } 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; @@ -2168,9 +2277,9 @@ grub_zfs_nvlist_lookup_nvlist_array_get_nelm (char *nvlist, char *name) } 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; @@ -2183,7 +2292,7 @@ get_nvlist_size (char *beg, char *limit) } 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; @@ -2236,9 +2345,29 @@ grub_zfs_nvlist_lookup_nvlist_array (char *nvlist, char *name, 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); @@ -2279,14 +2408,14 @@ zfs_mount (grub_device_t dev) 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); diff --git a/include/grub/zfs/zfs.h b/include/grub/zfs/zfs.h index 057692573..e1759dbbd 100644 --- a/include/grub/zfs/zfs.h +++ b/include/grub/zfs/zfs.h @@ -112,12 +112,14 @@ grub_err_t grub_zfs_fetch_nvlist (grub_device_t dev, char **nvlist); 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 */