Print this page
grub patch

@@ -1,9 +1,10 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
  *  Copyright (C) 1999,2000,2001,2002,2003,2004,2009,2010,2011  Free Software Foundation, Inc.
  *  Copyright 2010  Sun Microsystems, Inc.
+ *  Copyright 2012  Daniil Lunev
  *
  *  GRUB is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 3 of the License, or
  *  (at your option) any later version.

@@ -222,10 +223,24 @@
     grub_uint64_t txg;
     grub_uint64_t algo;
   } *keyring;
 };
 
+static const char * feature_list[] = {
+  "localhost:unknown_feature",
+  NULL,
+};
+
+typedef enum zfs_feature_id {
+  ZFS_FEATURE_UNKNOWN,
+} zfs_feature_id_t;
+
+struct enabled_feature_list {
+  struct enabled_feature_list * next;
+  zfs_feature_id_t id;
+};
+
 struct grub_zfs_data
 {
   /* cache for a file block of the currently zfs_open()-ed file */
   char *file_buf;
   grub_uint64_t file_start;

@@ -247,10 +262,12 @@
   unsigned n_devices_allocated;
   struct grub_zfs_device_desc *device_original;
 
   uberblock_t current_uberblock;
 
+  struct enabled_feature_list * feature_list;
+  
   int mounted;
   grub_uint64_t guid;
 };
 
 grub_err_t (*grub_zfs_decrypt) (grub_crypto_cipher_handle_t cipher,

@@ -480,19 +497,15 @@
   grub_zfs_endian_t endian = GRUB_ZFS_UNKNOWN_ENDIAN;
   zio_cksum_t zc;
 
   if (grub_zfs_to_cpu64 (uber->ub_magic, GRUB_ZFS_LITTLE_ENDIAN)
       == UBERBLOCK_MAGIC
-      && grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_LITTLE_ENDIAN) > 0 
-      && grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_LITTLE_ENDIAN)
-      <= SPA_VERSION)
+      && grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_LITTLE_ENDIAN) > 0)
     endian = GRUB_ZFS_LITTLE_ENDIAN;
 
   if (grub_zfs_to_cpu64 (uber->ub_magic, GRUB_ZFS_BIG_ENDIAN) == UBERBLOCK_MAGIC
-      && grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_BIG_ENDIAN) > 0 
-      && grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_BIG_ENDIAN)
-      <= SPA_VERSION)
+      && grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_BIG_ENDIAN) > 0)
     endian = GRUB_ZFS_BIG_ENDIAN;
 
   if (endian == GRUB_ZFS_UNKNOWN_ENDIAN)
     return grub_error (GRUB_ERR_BAD_FS, "invalid uberblock magic");
 

@@ -834,19 +847,10 @@
       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)

@@ -2668,60 +2672,53 @@
     }
   grub_free (path_buf);
   return err;
 }
 
-#if 0
+#if 1
 /*
  * Get the default 'bootfs' property value from the rootpool.
  *
  */
 static grub_err_t
-get_default_bootfsobj (dnode_phys_t * mosmdn, grub_uint64_t * obj,
+get_default_bootfsobj (dnode_end_t * mosmdn, grub_uint64_t * obj,
                        struct grub_zfs_data *data)
 {
   grub_uint64_t objnum = 0;
-  dnode_phys_t *dn;
-  if (!dn)
-    return grub_errno;
+  dnode_end_t dn;
 
   if ((grub_errno = dnode_get (mosmdn, DMU_POOL_DIRECTORY_OBJECT,
-                               DMU_OT_OBJECT_DIRECTORY, dn, data)))
+                               DMU_OT_OBJECT_DIRECTORY, &dn, data)))
     {
-      grub_free (dn);
       return (grub_errno);
     }
-
   /*
    * find the object number for 'pool_props', and get the dnode
    * of the 'pool_props'.
    */
-  if (zap_lookup (dn, DMU_POOL_PROPS, &objnum, data))
+  if (zap_lookup (&dn, DMU_POOL_PROPS, &objnum, data, 0))
     {
-      grub_free (dn);
       return (GRUB_ERR_BAD_FS);
     }
-  if ((grub_errno = dnode_get (mosmdn, objnum, DMU_OT_POOL_PROPS, dn, data)))
+  if ((grub_errno = dnode_get (mosmdn, objnum, DMU_OT_POOL_PROPS, &dn, data)))
     {
-      grub_free (dn);
       return (grub_errno);
     }
-  if (zap_lookup (dn, ZPOOL_PROP_BOOTFS, &objnum, data))
+  if (zap_lookup (&dn, ZPOOL_PROP_BOOTFS, &objnum, data,0))
     {
-      grub_free (dn);
       return (GRUB_ERR_BAD_FS);
     }
 
   if (!objnum)
     {
-      grub_free (dn);
       return (GRUB_ERR_BAD_FS);
     }
 
   *obj = objnum;
   return (0);
 }
+
 #endif
 /*
  * Given a MOS metadnode, get the metadnode of a given filesystem name (fsname),
  * e.g. pool/rootfs, or a given object number (obj), e.g. the object number
  * of pool/rootfs.

@@ -3318,13 +3315,121 @@
   grub_free (data->dnode_mdn);
   grub_free (data->file_buf);
   for (i = 0; i < data->subvol.nkeys; i++)
     grub_crypto_cipher_close (data->subvol.keyring[i].cipher);
   grub_free (data->subvol.keyring);
+  while (data->feature_list) {
+    struct enabled_feature_list * tmp = data->feature_list;
+    data->feature_list = data->feature_list->next;
+    grub_free(tmp);
+  }
   grub_free (data);
 }
 
+static int 
+add_feature (struct grub_zfs_data * data, zfs_feature_id_t id)
+{
+  struct enabled_feature_list * list = data->feature_list;
+  
+  while (list->next) {
+    if (list->next->id == id)
+      return id;
+    list = list->next;
+  }
+  
+  list->next = (struct enabled_feature_list *) grub_zalloc (sizeof (struct enabled_feature_list)); 
+  
+  if (! list->next)
+    return (-1);
+
+  list->next->id = id;
+  list->next->next = NULL;
+  
+  return 0;
+}
+
+static zfs_feature_id_t
+check_feature (const char * feature)
+{
+  int i = 0;
+  for ( ; i < ZFS_FEATURE_UNKNOWN; ++i)
+    if (! grub_strcmp (feature, feature_list[i]))
+          return i;
+
+  return ZFS_FEATURE_UNKNOWN;
+}
+
+/*
+ * zfs_get_features_list() get feature list and check compatability
+ */
+static grub_err_t
+zfs_get_features_list (struct grub_zfs_data * data)
+{
+  dnode_end_t dn;
+  dnode_end_t * mos = &(data->mos);
+  grub_err_t err;
+  grub_uint64_t objnum;
+  int ret;
+  
+  int NESTED_FUNC_ATTR feature_hook (const char * cname, grub_uint64_t val)
+  {
+    grub_err_t iret = 0;
+
+    zfs_feature_id_t id;
+
+    if (! (*cname))
+      goto out;
+
+    // retrieve feature number
+    id = check_feature (cname);
+
+    // if grub doesn't support such feature, return error
+    if (id == ZFS_FEATURE_UNKNOWN) {
+      if (val == 0) {
+        grub_error(GRUB_ERR_BAD_FS, 
+              "Unsupported feature %s was enabled,"
+              " but haven't been activated yet. You will not be able to boot"
+              " if this feature are activated.", cname);
+      } else {
+        grub_error(GRUB_ERR_BAD_FS, 
+            "Unsupported feature %s is activated. Booting failed",cname);
+        iret = 1;
+      }
+      goto out;
+    }
+        
+    // add feature to list
+    iret = add_feature(data, id);
+        
+  out:
+    return iret;
+  }
+  
+  // get object directory
+  err = dnode_get (mos, DMU_POOL_DIRECTORY_OBJECT,
+            DMU_OT_OBJECT_DIRECTORY, &dn, data);
+  if (err)
+    return err;
+
+  // retrieve pool properties object numer
+  err = zap_lookup (&dn, DMU_POOL_FEATURES_FOR_READ, &objnum, data, 0);
+  if (err)
+    return err;
+  
+  // get "features for read" zap dnode
+  err = dnode_get (mos, objnum, DMU_OTN_ZAP_METADATA, &dn, data);
+  if (err)
+    return err;
+  
+  // itterate zap to fetch feature list
+  ret = zap_iterate_u64 (&dn, feature_hook, data);
+  if (ret)
+    return grub_error(GRUB_ERR_BAD_FS, "There are enabled unsupported features");
+        
+  return GRUB_ERR_NONE;
+} 
+
 /*
  * zfs_mount() locates a valid uberblock of the root pool and read in its MOS
  * to the memory address MOS.
  *
  */

@@ -3352,10 +3457,11 @@
   /* 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->feature_list = NULL;
   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, &inserted);

@@ -3393,13 +3499,117 @@
                                          ub_endian) >> 63) & 1;
   grub_free (osp);
 
   data->mounted = 1;
 
+  if (grub_zfs_to_cpu64(ub->ub_version, ub_endian) == SPA_FEATURE_VERSION) {
+    data->feature_list = (struct enabled_feature_list*) grub_zalloc (
+                                                  sizeof (struct enabled_feature_list));
+    data->feature_list->next = NULL;
+    err = zfs_get_features_list(data);
+  
+    if (err) {
+      data->mounted = 0;
+      zfs_unmount (data);
+      return NULL;
+    }
+  }
+
   return data;
 }
 
+static grub_err_t
+find_default_dataset_path(char * path, grub_uint64_t * mdnobj, struct grub_zfs_data *data)
+{
+  grub_uint64_t obj, pobj, zobj;
+  grub_err_t err;
+  dnode_end_t mdn, *mosmdn;
+  char buf[512];
+
+  int NESTED_FUNC_ATTR hook (const char * name, grub_uint64_t val) {
+    if (val == obj) {
+      grub_strcpy(buf, name);
+      return 1;
+    }
+    return 0;
+  }
+
+  obj = *mdnobj;
+  mosmdn = &(data->mos);
+  buf[0] = '\0';
+  path[0] = '\0';
+
+  // get object's data dir
+  err = dnode_get(mosmdn, obj, DMU_OT_DSL_DATASET, &mdn, data);
+  if (err)
+    return err;
+  obj = grub_zfs_to_cpu64((((dsl_dataset_phys_t*)DN_BONUS (&mdn.dn)))->ds_dir_obj, mdn.endian);
+  
+  for (;;) {
+    // get object dnode
+    err = dnode_get(mosmdn, obj, DMU_OT_DSL_DIR, &mdn, data);
+    if (err)
+      return err;
+
+    // find out its parent's objnum
+    pobj = grub_zfs_to_cpu64((((dsl_dir_phys_t*)DN_BONUS (&mdn.dn)))->dd_parent_obj, mdn.endian);
+    if (obj == pobj)
+      break;
+
+    // if pobj is 0 then we have reached top dataset
+    if (! pobj)
+      break;
+
+    // get object's parent dnode
+    err = dnode_get(mosmdn, pobj, DMU_OT_DSL_DIR, &mdn, data);
+    if (err)
+      return err;
+
+    // find out parent's zap objnum
+    zobj = grub_zfs_to_cpu64((((dsl_dir_phys_t*)DN_BONUS (&mdn.dn)))->dd_child_dir_zapobj, mdn.endian);
+
+    // get zap's dnode
+    err = dnode_get(mosmdn, zobj, DMU_OT_DSL_DIR_CHILD_MAP, &mdn, data);
+    if (err)
+      return err;
+
+    // lookup zap to get name
+    err = zap_iterate_u64(&mdn, hook, data); 
+    if (err == 0)
+      return err;
+
+    // pobj becomes obj now
+    obj = pobj;
+
+    // append new intermediate dnode to path
+    grub_strcat(buf, path);
+    grub_strcpy(path, "/");
+    grub_strcat(path, buf);
+  } 
+        
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+get_default_bootfs_obj(grub_device_t dev, char * path, grub_uint64_t * mdnobj)
+{
+  struct grub_zfs_data * data;
+  grub_err_t err = 0;
+  data = zfs_mount(dev);
+  if (data) {
+    err = get_default_bootfsobj(&(data->mos), mdnobj, data);
+    if (err)
+      return err;
+    err = find_default_dataset_path(path, mdnobj, data);
+    zfs_unmount(data);
+    return err;
+  } else {
+    return grub_errno;
+  }
+  return 0;
+}
+
 grub_err_t
 grub_zfs_fetch_nvlist (grub_device_t dev, char **nvlist)
 {
   struct grub_zfs_data *zfs;
   grub_err_t err;