grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 7/7] Update editenv to support editing zfs envblock, fix whitespa


From: Paul Dagnelie
Subject: [PATCH 7/7] Update editenv to support editing zfs envblock, fix whitespace, and autodetect bootenv support in libzfs
Date: Wed, 11 Mar 2020 10:37:15 -0700

This patch updates the grub-editenv command to support editing the zfs
envblock by using specific libzfs functions. In order to ensure that GRUB will
continue to build against both old and new versions of libzfs, logic was also
added to the configure script to detect if the bootenv functions are present
in the version of libzfs present at build time.  There is also a small
whitespace fix in grub-editenv unrelated to the content of these changes.

Signed-off-by: Paul Dagnelie <address@hidden>
---
 configure.ac               |   8 ++
 include/grub/util/libzfs.h |   7 +
 util/grub-editenv.c        | 278 +++++++++++++++++++++++++++++++++++--
 3 files changed, 284 insertions(+), 9 deletions(-)

diff --git a/configure.ac b/configure.ac
index 7d74eba66..7107cc3b8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1853,6 +1853,14 @@ if test x"$libzfs_excuse" = x ; then
   LIBNVPAIR="-lnvpair"
   AC_DEFINE([HAVE_LIBNVPAIR], [1],
             [Define to 1 if you have the NVPAIR library.])
+
+  AC_CHECK_LIB([zfs], [zpool_get_bootenv],
+               [libzfs_have_bootenv="yes"],
+              [])
+  if test x"$libzfs_have_bootenv" = xyes ; then
+    AC_DEFINE([HAVE_LIBZFS_BOOTENV], [1],
+              [Define to 1 if you have a version of the ZFS library with 
bootenv support.])
+  fi
 fi
 
 AC_SUBST([LIBZFS])
diff --git a/include/grub/util/libzfs.h b/include/grub/util/libzfs.h
index a02caa335..5ebc085d8 100644
--- a/include/grub/util/libzfs.h
+++ b/include/grub/util/libzfs.h
@@ -40,6 +40,13 @@ extern int zpool_get_physpath (zpool_handle_t *, const char 
*);
 
 extern nvlist_t *zpool_get_config (zpool_handle_t *, nvlist_t **);
 
+extern libzfs_handle_t *zpool_get_handle (zpool_handle_t *);
+
+extern int zpool_set_bootenv (zpool_handle_t *, const char *);
+extern int zpool_get_bootenv (zpool_handle_t *, char *, size_t, off_t);
+
+extern int libzfs_errno (libzfs_handle_t *);
+
 #endif /* ! HAVE_LIBZFS_H */
 
 libzfs_handle_t *grub_get_libzfs_handle (void);
diff --git a/util/grub-editenv.c b/util/grub-editenv.c
index f3662c95b..a282f45ee 100644
--- a/util/grub-editenv.c
+++ b/util/grub-editenv.c
@@ -24,7 +24,12 @@
 #include <grub/lib/envblk.h>
 #include <grub/i18n.h>
 #include <grub/emu/hostfile.h>
+#include <grub/emu/hostdisk.h>
 #include <grub/util/install.h>
+#include <grub/util/libzfs.h>
+#include <grub/emu/getroot.h>
+#include <grub/fs.h>
+#include <grub/crypto.h>
 
 #include <stdio.h>
 #include <unistd.h>
@@ -36,7 +41,6 @@
 #pragma GCC diagnostic error "-Wmissing-prototypes"
 #pragma GCC diagnostic error "-Wmissing-declarations"
 
-
 #include "progname.h"
 
 #define DEFAULT_ENVBLK_PATH DEFAULT_DIRECTORY "/" GRUB_ENVBLK_DEFCFG
@@ -120,6 +124,79 @@ block, use `rm %s'."),
   NULL, help_filter, NULL
 };
 
+struct fs_envblk_spec {
+  const char *fs_name;
+  int (*fs_read) (void *, char *, size_t, off_t);
+  int (*fs_write) (void *, const char *);
+  void *(*fs_init) (grub_device_t);
+  void (*fs_fini) (void *);
+};
+typedef struct fs_envblk_spec fs_envblk_spec_t;
+
+struct fs_envblk {
+  struct fs_envblk_spec *spec;
+  grub_fs_t fs;
+  void *data;
+};
+typedef struct fs_envblk fs_envblk_t;
+
+fs_envblk_t *fs_envblk = NULL;
+
+#if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) && 
defined(HAVE_LIBZFS_BOOTENV)
+static void *
+grub_zfs_init (grub_device_t dev)
+{
+  libzfs_handle_t *g_zfs = libzfs_init();
+  int err;
+  char *name;
+  zpool_handle_t *zhp;
+
+  if (g_zfs == NULL)
+    return NULL;
+
+  err = fs_envblk->fs->fs_label(dev, &name);
+  if (err != GRUB_ERR_NONE) {
+    libzfs_fini(g_zfs);
+    return NULL;
+  }
+  zhp = zpool_open(g_zfs, name);
+  if (zhp == NULL)
+    {
+      libzfs_fini(g_zfs);
+      return NULL;
+    }
+  return zhp;
+}
+
+static void
+grub_zfs_fini (void *arg)
+{
+  zpool_handle_t *zhp = arg;
+  libzfs_handle_t *g_zfs = zpool_get_handle(zhp);
+  zpool_close(zhp);
+  libzfs_fini(g_zfs);
+}
+
+/* We need to convert ZFS's error returning pattern to the one we expect */
+static int
+grub_zfs_get_bootenv (void *arg, char *buf, size_t size, off_t offset)
+{
+  zpool_handle_t *zhp = arg;
+  int error = zpool_get_bootenv (zhp, buf, size, offset);
+  if (error != -1)
+    return error;
+  error = libzfs_errno(zpool_get_handle(zhp));
+  return error;
+}
+#endif
+
+fs_envblk_spec_t fs_envblk_table[] = {
+#if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) && 
defined(HAVE_LIBZFS_BOOTENV)
+  { "zfs", grub_zfs_get_bootenv, zpool_set_bootenv, grub_zfs_init, 
grub_zfs_fini},
+#endif
+  { NULL, NULL, NULL, NULL, NULL }
+};
+
 static grub_envblk_t
 open_envblk_file (const char *name)
 {
@@ -164,6 +241,58 @@ open_envblk_file (const char *name)
   return envblk;
 }
 
+static grub_envblk_t
+open_envblk (const char *name)
+{
+  char *buf = NULL;
+  off_t off = 0;
+  size_t size = GRUB_ENVBLK_DEFAULT_SIZE;
+  grub_envblk_t envblk;
+
+  if (fs_envblk == NULL)
+    return open_envblk_file(name);
+
+  /*
+   * For normal grubenv files, we can just use the size of the file to
+   * allocate our buffer, but envblks don't necessarily advertise their size
+   * directly. We need to instead read and realloc a larger buffer until we
+   * have read in the entire file.
+   */
+  while (1)
+    {
+      int rc;
+
+      buf = xrealloc(buf, size);
+      rc = fs_envblk->spec->fs_read(fs_envblk->data, buf + off, size, off);
+      if (rc < 0)
+       {
+         grub_util_error (_("cannot read envblock: %s"), strerror (errno));
+         free(buf);
+         return NULL;
+       }
+      if (rc < size)
+       {
+         envblk = grub_envblk_open (buf, rc + off);
+         if (! envblk)
+           grub_util_error ("%s", _("invalid environment block"));
+         return envblk;
+       }
+      off += size;
+      size *= 2;
+    }
+}
+
+static void
+close_envblk (grub_envblk_t envblk)
+{
+  grub_envblk_close (envblk);
+  if (fs_envblk != NULL)
+    {
+      fs_envblk->spec->fs_fini (fs_envblk->data);
+      fs_envblk->data = NULL;
+    }
+}
+
 static int
 print_var (const char *varname, const char *value,
            void *hook_data __attribute__ ((unused)))
@@ -177,13 +306,13 @@ list_variables (const char *name)
 {
   grub_envblk_t envblk;
 
-  envblk = open_envblk_file (name);
+  envblk = open_envblk (name);
   grub_envblk_iterate (envblk, NULL, print_var);
-  grub_envblk_close (envblk);
+  close_envblk (envblk);
 }
 
 static void
-write_envblk (const char *name, grub_envblk_t envblk)
+write_envblk_file (const char *name, grub_envblk_t envblk)
 {
   FILE *fp;
 
@@ -202,12 +331,38 @@ write_envblk (const char *name, grub_envblk_t envblk)
   fclose (fp);
 }
 
+static void
+write_envblk (const char *name, grub_envblk_t envblk)
+{
+  if (fs_envblk == NULL)
+    return write_envblk_file(name, envblk);
+
+  if (fs_envblk->spec->fs_write (fs_envblk->data, grub_envblk_buffer (envblk)) 
!= 0)
+    grub_util_error (_("cannot write to envblock: %s"), strerror (errno));
+}
+
+static void
+create_envblk (const char *name)
+{
+  char *buf;
+  grub_envblk_t envblk;
+
+  if (fs_envblk == NULL)
+    grub_util_create_envblk_file (name);
+  buf = xmalloc (GRUB_ENVBLK_DEFAULT_SIZE);
+  grub_util_create_envblk_buffer(buf, GRUB_ENVBLK_DEFAULT_SIZE);
+
+  envblk = grub_envblk_open (buf, GRUB_ENVBLK_DEFAULT_SIZE);
+  write_envblk (name, envblk);
+  close_envblk (envblk);
+}
+
 static void
 set_variables (const char *name, int argc, char *argv[])
 {
   grub_envblk_t envblk;
 
-  envblk = open_envblk_file (name);
+  envblk = open_envblk (name);
   while (argc)
     {
       char *p;
@@ -226,7 +381,7 @@ set_variables (const char *name, int argc, char *argv[])
     }
 
   write_envblk (name, envblk);
-  grub_envblk_close (envblk);
+  close_envblk (envblk);
 }
 
 static void
@@ -234,7 +389,7 @@ unset_variables (const char *name, int argc, char *argv[])
 {
   grub_envblk_t envblk;
 
-  envblk = open_envblk_file (name);
+  envblk = open_envblk (name);
   while (argc)
     {
       grub_envblk_delete (envblk, argv[0]);
@@ -244,7 +399,109 @@ unset_variables (const char *name, int argc, char *argv[])
     }
 
   write_envblk (name, envblk);
-  grub_envblk_close (envblk);
+  close_envblk (envblk);
+}
+
+static int
+probe_abstraction (grub_disk_t disk)
+{
+  if (disk->partition == NULL)
+    grub_util_info ("no partition map found for %s", disk->name);
+
+  if (disk->dev->id == GRUB_DISK_DEVICE_DISKFILTER_ID ||
+      disk->dev->id == GRUB_DISK_DEVICE_CRYPTODISK_ID)
+    return 1;
+  return 0;
+}
+
+static fs_envblk_t *
+probe_fs_envblk (fs_envblk_spec_t *spec)
+{
+  char **grub_devices;
+  char **curdev, **curdrive;
+  size_t ndev = 0;
+  char **grub_drives;
+  grub_device_t grub_dev = NULL;
+  grub_fs_t grub_fs;
+  const char *fs_envblk_device;
+  int have_abstraction = 0;
+  fs_envblk_spec_t *p;
+
+  grub_util_biosdisk_init (DEFAULT_DEVICE_MAP);
+  grub_init_all ();
+  grub_gcry_init_all ();
+
+  grub_lvm_fini ();
+  grub_mdraid09_fini ();
+  grub_mdraid1x_fini ();
+  grub_diskfilter_fini ();
+  grub_diskfilter_init ();
+  grub_mdraid09_init ();
+  grub_mdraid1x_init ();
+  grub_lvm_init ();
+
+  grub_devices = grub_guess_root_devices (DEFAULT_DIRECTORY);
+
+  if (!grub_devices || !grub_devices[0])
+    grub_util_error (_("cannot find a device for %s (is /dev mounted?)"), 
DEFAULT_DIRECTORY);
+
+  fs_envblk_device = grub_devices[0];
+
+  for (curdev = grub_devices; *curdev; curdev++)
+    {
+      grub_util_pull_device (*curdev);
+      ndev++;
+    }
+
+  grub_drives = xmalloc (sizeof (grub_drives[0]) * (ndev + 1));
+
+  for (curdev = grub_devices, curdrive = grub_drives; *curdev; curdev++,
+       curdrive++)
+    {
+      *curdrive = grub_util_get_grub_dev (*curdev);
+      if (! *curdrive)
+       grub_util_error (_("cannot find a GRUB drive for %s.  Check your 
device.map"),
+                        *curdev);
+    }
+  *curdrive = NULL;
+
+  grub_dev = grub_device_open (grub_drives[0]);
+
+  grub_fs = grub_fs_probe (grub_dev);
+
+  if (grub_dev->disk)
+    have_abstraction = probe_abstraction (grub_dev->disk);
+  for (curdrive = grub_drives + 1; *curdrive; curdrive++)
+    {
+      grub_device_t dev = grub_device_open (*curdrive);
+      if (!dev)
+       continue;
+      if (dev->disk)
+       have_abstraction |= probe_abstraction (dev->disk);
+      grub_device_close (dev);
+    }
+
+  free (grub_drives);
+  grub_gcry_fini_all ();
+  grub_fini_all ();
+  grub_util_biosdisk_fini ();
+
+  for (p = spec; p->fs_name; p++)
+    {
+      if (strcmp (grub_fs->name, p->fs_name) == 0 && !have_abstraction)
+       {
+         grub_util_info ("Detected envblock support in %s, leveraging", 
grub_fs->name);
+         fs_envblk = xmalloc (sizeof (fs_envblk_t));
+         fs_envblk->spec = p;
+         fs_envblk->fs = grub_fs;
+         fs_envblk->data = p->fs_init (grub_dev);
+         grub_device_close (grub_dev);
+         return fs_envblk;
+       }
+    }
+
+  grub_device_close (grub_dev);
+  return NULL;
 }
 
 int
@@ -278,8 +535,11 @@ main (int argc, char *argv[])
       command  = argv[curindex++];
     }
 
+  if (strcmp (filename, DEFAULT_ENVBLK_PATH) == 0)
+    fs_envblk = probe_fs_envblk (fs_envblk_table);
+
   if (strcmp (command, "create") == 0)
-    grub_util_create_envblk_file (filename);
+    create_envblk (filename);
   else if (strcmp (command, "list") == 0)
     list_variables (filename);
   else if (strcmp (command, "set") == 0)
-- 
2.19.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]