]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
ZFS mirror support
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Mon, 6 Dec 2010 01:31:22 +0000 (02:31 +0100)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Mon, 6 Dec 2010 01:31:22 +0000 (02:31 +0100)
grub-core/fs/zfs/zfs.c
include/grub/zfs/zfs.h

index 3163e2e7843402c7ee5a2faadbe32305aded8cd6..b5f25473f2ce19bf9432c6f8533862ba8e4ddd49 100644 (file)
@@ -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;
-  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;
@@ -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);
index 0576925732708b3ded97ccce9370b3a7d2c3fe1e..e1759dbbdb7b27b5efddab3e321724e818ce196e 100644 (file)
@@ -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 */