Blame SOURCES/0111-Add-blscfg-command-support-to-parse-BootLoaderSpec-c.patch

d9d99f
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
d9d99f
From: Peter Jones <pjones@redhat.com>
d9d99f
Date: Fri, 9 Dec 2016 15:40:58 -0500
d9d99f
Subject: [PATCH] Add blscfg command support to parse BootLoaderSpec config
d9d99f
 fragments
d9d99f
d9d99f
The BootLoaderSpec (BLS) defines a scheme where different bootloaders can
d9d99f
share a format for boot items and a configuration directory that accepts
d9d99f
these common configurations as drop-in files.
d9d99f
d9d99f
GRUB2 already has a blscfg modle that can parse the config snippets using
d9d99f
the bls_import command, change it to blscfg and improve the BLS support.
d9d99f
---
d9d99f
 grub-core/Makefile.core.def  |   4 +-
d9d99f
 grub-core/commands/blscfg.c  | 796 ++++++++++++++++++++++++++++++++++++++++---
d9d99f
 grub-core/commands/loadenv.c |  77 +----
d9d99f
 grub-core/commands/loadenv.h |  93 +++++
d9d99f
 include/grub/compiler.h      |   2 +
d9d99f
 5 files changed, 840 insertions(+), 132 deletions(-)
d9d99f
 create mode 100644 grub-core/commands/loadenv.h
d9d99f
d9d99f
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
b71686
index 27563743b..96ccb4021 100644
d9d99f
--- a/grub-core/Makefile.core.def
d9d99f
+++ b/grub-core/Makefile.core.def
d9d99f
@@ -129,6 +129,7 @@ kernel = {
d9d99f
   common = kern/rescue_parser.c;
d9d99f
   common = kern/rescue_reader.c;
d9d99f
   common = kern/term.c;
d9d99f
+  common = kern/qsort.c;
d9d99f
 
d9d99f
   noemu = kern/compiler-rt.c;
d9d99f
   noemu = kern/mm.c;
d9d99f
@@ -774,8 +775,7 @@ module = {
d9d99f
 module = {
d9d99f
   name = blscfg;
d9d99f
   common = commands/blscfg.c;
d9d99f
-  enable = i386_efi;
d9d99f
-  enable = x86_64_efi;
d9d99f
+  enable = efi;
d9d99f
   enable = i386_pc;
d9d99f
 };
d9d99f
 
d9d99f
diff --git a/grub-core/commands/blscfg.c b/grub-core/commands/blscfg.c
b71686
index 4274aca5a..86796c8cd 100644
d9d99f
--- a/grub-core/commands/blscfg.c
d9d99f
+++ b/grub-core/commands/blscfg.c
d9d99f
@@ -30,32 +30,405 @@
d9d99f
 #include <grub/env.h>
d9d99f
 #include <grub/file.h>
d9d99f
 #include <grub/normal.h>
d9d99f
+#include <grub/lib/envblk.h>
d9d99f
 
d9d99f
 GRUB_MOD_LICENSE ("GPLv3+");
d9d99f
 
d9d99f
+#include "loadenv.h"
d9d99f
+
d9d99f
+#define GRUB_BLS_CONFIG_PATH "/loader/entries/"
d9d99f
+#define GRUB_BOOT_DEVICE "($root)"
d9d99f
 #ifdef GRUB_MACHINE_EFI
d9d99f
 #define GRUB_LINUX_CMD "linuxefi"
d9d99f
 #define GRUB_INITRD_CMD "initrdefi"
d9d99f
-#define GRUB_BLS_CONFIG_PATH "/EFI/fedora/loader/entries/"
d9d99f
-#define GRUB_BOOT_DEVICE "($boot)"
d9d99f
 #else
d9d99f
 #define GRUB_LINUX_CMD "linux"
d9d99f
 #define GRUB_INITRD_CMD "initrd"
d9d99f
-#define GRUB_BLS_CONFIG_PATH "/loader/entries/"
d9d99f
-#define GRUB_BOOT_DEVICE "($root)"
d9d99f
 #endif
d9d99f
 
d9d99f
-static int parse_entry (
d9d99f
+#define grub_free(x) ({grub_dprintf("blscfg", "%s freeing %p\n", __func__, x); grub_free(x); })
d9d99f
+
d9d99f
+struct keyval
d9d99f
+{
d9d99f
+  const char *key;
d9d99f
+  char *val;
d9d99f
+};
d9d99f
+
d9d99f
+struct bls_entry
d9d99f
+{
d9d99f
+  struct keyval **keyvals;
d9d99f
+  int nkeyvals;
d9d99f
+};
d9d99f
+
d9d99f
+static struct bls_entry **entries;
d9d99f
+static int nentries;
d9d99f
+
d9d99f
+static struct bls_entry *bls_new_entry(void)
d9d99f
+{
d9d99f
+  struct bls_entry **new_entries;
d9d99f
+  struct bls_entry *entry;
d9d99f
+  int new_n = nentries + 1;
d9d99f
+
d9d99f
+  new_entries = grub_realloc (entries,  new_n * sizeof (struct bls_entry *));
d9d99f
+  if (!new_entries)
d9d99f
+    {
d9d99f
+      grub_error (GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+		  "couldn't find space for BLS entry list");
d9d99f
+      return NULL;
d9d99f
+    }
d9d99f
+
d9d99f
+  entries = new_entries;
d9d99f
+
d9d99f
+  entry = grub_malloc (sizeof (*entry));
d9d99f
+  if (!entry)
d9d99f
+    {
d9d99f
+      grub_error (GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+		  "couldn't find space for BLS entry list");
d9d99f
+      return NULL;
d9d99f
+    }
d9d99f
+
d9d99f
+  grub_memset (entry, 0, sizeof (*entry));
d9d99f
+  entries[nentries] = entry;
d9d99f
+
d9d99f
+  nentries = new_n;
d9d99f
+
d9d99f
+  return entry;
d9d99f
+}
d9d99f
+
d9d99f
+static int bls_add_keyval(struct bls_entry *entry, char *key, char *val)
d9d99f
+{
d9d99f
+  char *k, *v;
d9d99f
+  struct keyval **kvs, *kv;
d9d99f
+  int new_n = entry->nkeyvals + 1;
d9d99f
+
d9d99f
+  kvs = grub_realloc (entry->keyvals, new_n * sizeof (struct keyval *));
d9d99f
+  if (!kvs)
d9d99f
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+		       "couldn't find space for BLS entry");
d9d99f
+  entry->keyvals = kvs;
d9d99f
+
d9d99f
+  kv = grub_malloc (sizeof (struct keyval));
d9d99f
+  if (!kv)
d9d99f
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+		       "couldn't find space for BLS entry");
d9d99f
+
d9d99f
+  k = grub_strdup (key);
d9d99f
+  if (!k)
d9d99f
+    {
d9d99f
+      grub_free (kv);
d9d99f
+      return grub_error (GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+			 "couldn't find space for BLS entry");
d9d99f
+    }
d9d99f
+
d9d99f
+  v = grub_strdup (val);
d9d99f
+  if (!v)
d9d99f
+    {
d9d99f
+      grub_free (k);
d9d99f
+      grub_free (kv);
d9d99f
+      return grub_error (GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+			 "couldn't find space for BLS entry");
d9d99f
+    }
d9d99f
+
d9d99f
+  kv->key = k;
d9d99f
+  kv->val = v;
d9d99f
+
d9d99f
+  entry->keyvals[entry->nkeyvals] = kv;
d9d99f
+  grub_dprintf("blscfg", "new keyval at %p:%p:%p\n", entry->keyvals[entry->nkeyvals], k, v);
d9d99f
+  entry->nkeyvals = new_n;
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
+
d9d99f
+static void bls_free_entry(struct bls_entry *entry)
d9d99f
+{
d9d99f
+  int i;
d9d99f
+
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+  for (i = 0; i < entry->nkeyvals; i++)
d9d99f
+    {
d9d99f
+      struct keyval *kv = entry->keyvals[i];
d9d99f
+      grub_free ((void *)kv->key);
d9d99f
+      grub_free (kv->val);
d9d99f
+      grub_free (kv);
d9d99f
+    }
d9d99f
+
d9d99f
+  grub_free (entry->keyvals);
d9d99f
+  grub_memset (entry, 0, sizeof (*entry));
d9d99f
+  grub_free (entry);
d9d99f
+}
d9d99f
+
d9d99f
+static int keyval_cmp (const void *p0, const void *p1,
d9d99f
+		       void *state UNUSED)
d9d99f
+{
d9d99f
+  const struct keyval *kv0 = *(struct keyval * const *)p0;
d9d99f
+  const struct keyval *kv1 = *(struct keyval * const *)p1;
d9d99f
+  int rc;
d9d99f
+
d9d99f
+  rc = grub_strcmp(kv0->key, kv1->key);
d9d99f
+
d9d99f
+  return rc;
d9d99f
+}
d9d99f
+
d9d99f
+/* Find they value of the key named by keyname.  If there are allowed to be
d9d99f
+ * more than one, pass a pointer to an int set to -1 the first time, and pass
d9d99f
+ * the same pointer through each time after, and it'll return them in sorted
d9d99f
+ * order. */
d9d99f
+static char *bls_get_val(struct bls_entry *entry, const char *keyname, int *last)
d9d99f
+{
d9d99f
+  char *foo = (char *)"";
d9d99f
+  struct keyval *kv = NULL, **kvp, key = {keyname, foo}, *keyp = &ke;;
d9d99f
+
d9d99f
+  /* if we've already found an entry that matches, just iterate */
d9d99f
+  if (last && *last >= 0)
d9d99f
+    {
d9d99f
+      int next = ++last[0];
d9d99f
+
d9d99f
+      if (next == entry->nkeyvals)
d9d99f
+	{
d9d99f
+done:
d9d99f
+	  *last = -1;
d9d99f
+	  return NULL;
d9d99f
+	}
d9d99f
+
d9d99f
+      kv = entry->keyvals[next];
d9d99f
+      if (grub_strcmp (keyname, kv->key))
d9d99f
+	goto done;
d9d99f
+
d9d99f
+      return kv->val;
d9d99f
+    }
d9d99f
+
d9d99f
+  kvp = grub_bsearch(&keyp, &entry->keyvals[0], entry->nkeyvals,
d9d99f
+		    sizeof (struct keyval *), keyval_cmp, NULL);
d9d99f
+  if (kvp)
d9d99f
+    kv = *kvp;
d9d99f
+
d9d99f
+  if (kv)
d9d99f
+    {
d9d99f
+      /* if we've got uninitialized but present state, track back until we find
d9d99f
+       * the first match */
d9d99f
+      if (last)
d9d99f
+	{
d9d99f
+	  grub_dprintf("blscfg", "%s trying to find another entry because last was set\n", __func__);
d9d99f
+	  /* figure out the position of this entry in the array */
d9d99f
+	  int idx;
d9d99f
+	  for (idx = 0 ; idx < entry->nkeyvals; idx++)
d9d99f
+	    if (entry->keyvals[idx] == kv)
d9d99f
+	      break;
d9d99f
+	  *last = idx;
d9d99f
+
d9d99f
+	  while (idx > 0)
d9d99f
+	    {
d9d99f
+	      struct keyval *kvtmp = entry->keyvals[idx-1];
d9d99f
+	      if (idx == 0 || grub_strcmp (keyname, kvtmp->key))
d9d99f
+		{
d9d99f
+		  /* if we're at the start, or if the previous entry doesn't
d9d99f
+		   * match, then we're done */
d9d99f
+		  *last = idx;
d9d99f
+		  break;
d9d99f
+		}
d9d99f
+	      else
d9d99f
+		/* but if it does match, keep going backwards */
d9d99f
+		idx--;
d9d99f
+	    }
d9d99f
+	}
d9d99f
+
d9d99f
+      return kv->val;
d9d99f
+    }
d9d99f
+  return NULL;
d9d99f
+}
d9d99f
+
d9d99f
+#define goto_return(x) ({ ret = (x); goto finish; })
d9d99f
+
d9d99f
+/* compare alpha and numeric segments of two versions */
d9d99f
+/* return 1: a is newer than b */
d9d99f
+/*        0: a and b are the same version */
d9d99f
+/*       -1: b is newer than a */
d9d99f
+static int vercmp(const char * a, const char * b)
d9d99f
+{
d9d99f
+    char oldch1, oldch2;
d9d99f
+    char *abuf, *bbuf;
d9d99f
+    char *str1, *str2;
d9d99f
+    char * one, * two;
d9d99f
+    int rc;
d9d99f
+    int isnum;
d9d99f
+    int ret = 0;
d9d99f
+
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+    if (!grub_strcmp(a, b))
d9d99f
+	    return 0;
d9d99f
+
d9d99f
+    abuf = grub_malloc(grub_strlen(a) + 1);
d9d99f
+    bbuf = grub_malloc(grub_strlen(b) + 1);
d9d99f
+    str1 = abuf;
d9d99f
+    str2 = bbuf;
d9d99f
+    grub_strcpy(str1, a);
d9d99f
+    grub_strcpy(str2, b);
d9d99f
+
d9d99f
+    one = str1;
d9d99f
+    two = str2;
d9d99f
+
d9d99f
+    /* loop through each version segment of str1 and str2 and compare them */
d9d99f
+    while (*one || *two) {
d9d99f
+	while (*one && !grub_isalnum(*one) && *one != '~') one++;
d9d99f
+	while (*two && !grub_isalnum(*two) && *two != '~') two++;
d9d99f
+
d9d99f
+	/* handle the tilde separator, it sorts before everything else */
d9d99f
+	if (*one == '~' || *two == '~') {
d9d99f
+	    if (*one != '~') goto_return (1);
d9d99f
+	    if (*two != '~') goto_return (-1);
d9d99f
+	    one++;
d9d99f
+	    two++;
d9d99f
+	    continue;
d9d99f
+	}
d9d99f
+
d9d99f
+	/* If we ran to the end of either, we are finished with the loop */
d9d99f
+	if (!(*one && *two)) break;
d9d99f
+
d9d99f
+	str1 = one;
d9d99f
+	str2 = two;
d9d99f
+
d9d99f
+	/* grab first completely alpha or completely numeric segment */
d9d99f
+	/* leave one and two pointing to the start of the alpha or numeric */
d9d99f
+	/* segment and walk str1 and str2 to end of segment */
d9d99f
+	if (grub_isdigit(*str1)) {
d9d99f
+	    while (*str1 && grub_isdigit(*str1)) str1++;
d9d99f
+	    while (*str2 && grub_isdigit(*str2)) str2++;
d9d99f
+	    isnum = 1;
d9d99f
+	} else {
d9d99f
+	    while (*str1 && grub_isalpha(*str1)) str1++;
d9d99f
+	    while (*str2 && grub_isalpha(*str2)) str2++;
d9d99f
+	    isnum = 0;
d9d99f
+	}
d9d99f
+
d9d99f
+	/* save character at the end of the alpha or numeric segment */
d9d99f
+	/* so that they can be restored after the comparison */
d9d99f
+	oldch1 = *str1;
d9d99f
+	*str1 = '\0';
d9d99f
+	oldch2 = *str2;
d9d99f
+	*str2 = '\0';
d9d99f
+
d9d99f
+	/* this cannot happen, as we previously tested to make sure that */
d9d99f
+	/* the first string has a non-null segment */
d9d99f
+	if (one == str1) goto_return(-1);	/* arbitrary */
d9d99f
+
d9d99f
+	/* take care of the case where the two version segments are */
d9d99f
+	/* different types: one numeric, the other alpha (i.e. empty) */
d9d99f
+	/* numeric segments are always newer than alpha segments */
d9d99f
+	/* XXX See patch #60884 (and details) from bugzilla #50977. */
d9d99f
+	if (two == str2) goto_return (isnum ? 1 : -1);
d9d99f
+
d9d99f
+	if (isnum) {
d9d99f
+	    grub_size_t onelen, twolen;
d9d99f
+	    /* this used to be done by converting the digit segments */
d9d99f
+	    /* to ints using atoi() - it's changed because long  */
d9d99f
+	    /* digit segments can overflow an int - this should fix that. */
d9d99f
+
d9d99f
+	    /* throw away any leading zeros - it's a number, right? */
d9d99f
+	    while (*one == '0') one++;
d9d99f
+	    while (*two == '0') two++;
d9d99f
+
d9d99f
+	    /* whichever number has more digits wins */
d9d99f
+	    onelen = grub_strlen(one);
d9d99f
+	    twolen = grub_strlen(two);
d9d99f
+	    if (onelen > twolen) goto_return (1);
d9d99f
+	    if (twolen > onelen) goto_return (-1);
d9d99f
+	}
d9d99f
+
d9d99f
+	/* grub_strcmp will return which one is greater - even if the two */
d9d99f
+	/* segments are alpha or if they are numeric.  don't return  */
d9d99f
+	/* if they are equal because there might be more segments to */
d9d99f
+	/* compare */
d9d99f
+	rc = grub_strcmp(one, two);
d9d99f
+	if (rc) goto_return (rc < 1 ? -1 : 1);
d9d99f
+
d9d99f
+	/* restore character that was replaced by null above */
d9d99f
+	*str1 = oldch1;
d9d99f
+	one = str1;
d9d99f
+	*str2 = oldch2;
d9d99f
+	two = str2;
d9d99f
+    }
d9d99f
+
d9d99f
+    /* this catches the case where all numeric and alpha segments have */
d9d99f
+    /* compared identically but the segment sepparating characters were */
d9d99f
+    /* different */
d9d99f
+    if ((!*one) && (!*two)) goto_return (0);
d9d99f
+
d9d99f
+    /* whichever version still has characters left over wins */
d9d99f
+    if (!*one) goto_return (-1); else goto_return (1);
d9d99f
+
d9d99f
+finish:
d9d99f
+    grub_free (abuf);
d9d99f
+    grub_free (bbuf);
d9d99f
+    return ret;
d9d99f
+}
d9d99f
+
d9d99f
+typedef int (*void_cmp_t)(void *, void *);
d9d99f
+
d9d99f
+static int nulcmp(char *s0, char *s1, void_cmp_t cmp)
d9d99f
+{
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+  if (s1 && !s0)
d9d99f
+    return 1;
d9d99f
+  if (s0 && !s1)
d9d99f
+    return -1;
d9d99f
+  if (!s0 && !s1)
d9d99f
+    return 0;
d9d99f
+  if (cmp)
d9d99f
+    return cmp(s0, s1);
d9d99f
+  return grub_strcmp(s0, s1);
d9d99f
+}
d9d99f
+
d9d99f
+static int
d9d99f
+bls_keyval_cmp(struct bls_entry *e0, struct bls_entry *e1, const char *keyname)
d9d99f
+{
d9d99f
+  char *val0, *val1;
d9d99f
+
d9d99f
+  val0 = bls_get_val (e0, keyname, NULL);
d9d99f
+  val1 = bls_get_val (e1, keyname, NULL);
d9d99f
+
d9d99f
+  if (val1 && !val0)
d9d99f
+    return 1;
d9d99f
+
d9d99f
+  if (val0 && !val1)
d9d99f
+    return -1;
d9d99f
+
d9d99f
+  if (!val0 && !val1)
d9d99f
+    return 0;
d9d99f
+
d9d99f
+  return nulcmp(val0, val1, (void_cmp_t)vercmp);
d9d99f
+}
d9d99f
+
d9d99f
+static int bls_cmp(const void *p0, const void *p1, void *state UNUSED)
d9d99f
+{
d9d99f
+  struct bls_entry * e0 = *(struct bls_entry **)p0;
d9d99f
+  struct bls_entry * e1 = *(struct bls_entry **)p1;
d9d99f
+  int rc = 0;
d9d99f
+
d9d99f
+  rc = bls_keyval_cmp (e0, e1, "id");
d9d99f
+
d9d99f
+  if (rc == 0)
d9d99f
+    rc = bls_keyval_cmp (e0, e1, "title");
d9d99f
+
d9d99f
+  if (rc == 0)
d9d99f
+    rc = bls_keyval_cmp (e0, e1, "linux");
d9d99f
+
d9d99f
+  return rc;
d9d99f
+}
d9d99f
+
d9d99f
+static int read_entry (
d9d99f
     const char *filename,
d9d99f
-    const struct grub_dirhook_info *info __attribute__ ((unused)),
d9d99f
-    void *data __attribute__ ((unused)))
d9d99f
+    const struct grub_dirhook_info *info UNUSED,
d9d99f
+    void *data)
d9d99f
 {
d9d99f
   grub_size_t n;
d9d99f
   char *p;
d9d99f
   grub_file_t f = NULL;
d9d99f
   grub_off_t sz;
d9d99f
-  char *title = NULL, *options = NULL, *clinux = NULL, *initrd = NULL, *src = NULL;
d9d99f
-  const char *args[2] = { NULL, NULL };
d9d99f
+  struct bls_entry *entry;
d9d99f
+  const char *dirname= (const char *)data;
d9d99f
+  const char *devid = grub_env_get ("boot");
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "filename: \"%s\"\n", filename);
d9d99f
 
d9d99f
   if (filename[0] == '.')
d9d99f
     return 0;
d9d99f
@@ -67,7 +440,7 @@ static int parse_entry (
d9d99f
   if (grub_strcmp (filename + n - 5, ".conf") != 0)
d9d99f
     return 0;
d9d99f
 
d9d99f
-  p = grub_xasprintf (GRUB_BLS_CONFIG_PATH "%s", filename);
d9d99f
+  p = grub_xasprintf ("(%s)%s/%s", devid, dirname, filename);
d9d99f
 
d9d99f
   f = grub_file_open (p);
d9d99f
   if (!f)
d9d99f
@@ -77,54 +450,169 @@ static int parse_entry (
d9d99f
   if (sz == GRUB_FILE_SIZE_UNKNOWN || sz > 1024*1024)
d9d99f
     goto finish;
d9d99f
 
d9d99f
+  entry = bls_new_entry();
d9d99f
+  if (!entry)
d9d99f
+    goto finish;
d9d99f
+
d9d99f
   for (;;)
d9d99f
     {
d9d99f
       char *buf;
d9d99f
+      char *separator;
d9d99f
+      int rc;
d9d99f
 
d9d99f
       buf = grub_file_getline (f);
d9d99f
       if (!buf)
d9d99f
 	break;
d9d99f
 
d9d99f
-      if (grub_strncmp (buf, "title ", 6) == 0)
d9d99f
-	{
d9d99f
-	  grub_free (title);
d9d99f
-	  title = grub_strdup (buf + 6);
d9d99f
-	  if (!title)
d9d99f
-	    goto finish;
d9d99f
-	}
d9d99f
-      else if (grub_strncmp (buf, "options ", 8) == 0)
d9d99f
-	{
d9d99f
-	  grub_free (options);
d9d99f
-	  options = grub_strdup (buf + 8);
d9d99f
-	  if (!options)
d9d99f
-	    goto finish;
d9d99f
-	}
d9d99f
-      else if (grub_strncmp (buf, "linux ", 6) == 0)
d9d99f
-	{
d9d99f
-	  grub_free (clinux);
d9d99f
-	  clinux = grub_strdup (buf + 6);
d9d99f
-	  if (!clinux)
d9d99f
-	    goto finish;
d9d99f
-	}
d9d99f
-      else if (grub_strncmp (buf, "initrd ", 7) == 0)
d9d99f
+      while (buf && buf[0] && (buf[0] == ' ' || buf[0] == '\t'))
d9d99f
+	buf++;
d9d99f
+      if (buf[0] == '#')
d9d99f
+	continue;
d9d99f
+
d9d99f
+      separator = grub_strchr (buf, ' ');
d9d99f
+
d9d99f
+      if (!separator)
d9d99f
+	separator = grub_strchr (buf, '\t');
d9d99f
+
d9d99f
+      if (!separator || separator[1] == '\0')
d9d99f
 	{
d9d99f
-	  grub_free (initrd);
d9d99f
-	  initrd = grub_strdup (buf + 7);
d9d99f
-	  if (!initrd)
d9d99f
-	    goto finish;
d9d99f
+	  grub_free (buf);
d9d99f
+	  break;
d9d99f
 	}
d9d99f
 
d9d99f
-      grub_free(buf);
d9d99f
+      separator[0] = '\0';
d9d99f
+
d9d99f
+      rc = bls_add_keyval (entry, buf, separator+1);
d9d99f
+      grub_free (buf);
d9d99f
+      if (rc < 0)
d9d99f
+	break;
d9d99f
+    }
d9d99f
+
d9d99f
+  grub_qsort(&entry->keyvals[0], entry->nkeyvals, sizeof (struct keyval *),
d9d99f
+	     keyval_cmp, NULL);
d9d99f
+
d9d99f
+finish:
d9d99f
+  grub_free (p);
d9d99f
+
d9d99f
+  if (f)
d9d99f
+    grub_file_close (f);
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
+
d9d99f
+static grub_envblk_t saved_env = NULL;
d9d99f
+
d9d99f
+static int
d9d99f
+save_var (const char *name, const char *value, void *whitelist UNUSED)
d9d99f
+{
d9d99f
+  const char *val = grub_env_get (name);
d9d99f
+  grub_dprintf("blscfg", "saving \"%s\"\n", name);
d9d99f
+
d9d99f
+  if (val)
d9d99f
+    grub_envblk_set (saved_env, name, value);
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
+
d9d99f
+static int
d9d99f
+unset_var (const char *name, const char *value UNUSED, void *whitelist)
d9d99f
+{
d9d99f
+  grub_dprintf("blscfg", "restoring \"%s\"\n", name);
d9d99f
+  if (! whitelist)
d9d99f
+    {
d9d99f
+      grub_env_unset (name);
d9d99f
+      return 0;
d9d99f
     }
d9d99f
 
d9d99f
-  if (!linux)
d9d99f
+  if (test_whitelist_membership (name,
d9d99f
+				 (const grub_env_whitelist_t *) whitelist))
d9d99f
+    grub_env_unset (name);
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
+
d9d99f
+static char **bls_make_list (struct bls_entry *entry, const char *key, int *num)
d9d99f
+{
d9d99f
+  int last = -1;
d9d99f
+  char *val;
d9d99f
+
d9d99f
+  int nlist = 0;
d9d99f
+  char **list = NULL;
d9d99f
+
d9d99f
+  list = grub_malloc (sizeof (char *));
d9d99f
+  if (!list)
d9d99f
+    return NULL;
d9d99f
+  list[0] = NULL;
d9d99f
+
d9d99f
+  while (1)
d9d99f
+    {
d9d99f
+      char **new;
d9d99f
+
d9d99f
+      val = bls_get_val (entry, key, &last);
d9d99f
+      if (!val)
d9d99f
+	break;
d9d99f
+
d9d99f
+      new = grub_realloc (list, (nlist + 2) * sizeof (char *));
d9d99f
+      if (!new)
d9d99f
+	break;
d9d99f
+
d9d99f
+      list = new;
d9d99f
+      list[nlist++] = val;
d9d99f
+      list[nlist] = NULL;
d9d99f
+  }
d9d99f
+
d9d99f
+  if (num)
d9d99f
+    *num = nlist;
d9d99f
+
d9d99f
+  return list;
d9d99f
+}
d9d99f
+
d9d99f
+static void create_entry (struct bls_entry *entry, const char *cfgfile)
d9d99f
+{
d9d99f
+  int argc = 0;
d9d99f
+  const char **argv = NULL;
d9d99f
+
d9d99f
+  char *title = NULL;
d9d99f
+  char *clinux = NULL;
d9d99f
+  char *options = NULL;
d9d99f
+  char *initrd = NULL;
d9d99f
+  char *id = NULL;
d9d99f
+  char *hotkey = NULL;
d9d99f
+
d9d99f
+  char *users = NULL;
d9d99f
+  char **classes = NULL;
d9d99f
+
d9d99f
+  char **args = NULL;
d9d99f
+
d9d99f
+  char *src = NULL;
d9d99f
+  int i;
d9d99f
+
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+  clinux = bls_get_val (entry, "linux", NULL);
d9d99f
+  if (!clinux)
d9d99f
     {
d9d99f
-      grub_printf ("Skipping file %s with no 'linux' key.", p);
d9d99f
+      grub_dprintf ("blscfg", "Skipping file %s with no 'linux' key.\n", cfgfile);
d9d99f
       goto finish;
d9d99f
     }
d9d99f
 
d9d99f
-  args[0] = title ? title : filename;
d9d99f
+  title = bls_get_val (entry, "title", NULL);
d9d99f
+  options = bls_get_val (entry, "options", NULL);
d9d99f
+  initrd = bls_get_val (entry, "initrd", NULL);
d9d99f
+  id = bls_get_val (entry, "id", NULL);
d9d99f
 
d9d99f
+  hotkey = bls_get_val (entry, "grub_hotkey", NULL);
d9d99f
+  users = bls_get_val (entry, "grub_users", NULL);
d9d99f
+  classes = bls_make_list (entry, "grub_class", NULL);
d9d99f
+  args = bls_make_list (entry, "grub_arg", &argc);
d9d99f
+
d9d99f
+  argc += 1;
d9d99f
+  argv = grub_malloc ((argc + 1) * sizeof (char *));
d9d99f
+  argv[0] = title ? title : clinux;
d9d99f
+  for (i = 1; i < argc; i++)
d9d99f
+    argv[i] = args[i-1];
d9d99f
+  argv[argc] = NULL;
d9d99f
+
d9d99f
+  grub_dprintf("blscfg", "adding menu entry for \"%s\"\n", title);
d9d99f
   src = grub_xasprintf ("load_video\n"
d9d99f
 			"set gfx_payload=keep\n"
d9d99f
 			"insmod gzio\n"
d9d99f
@@ -133,40 +621,219 @@ static int parse_entry (
d9d99f
 			GRUB_BOOT_DEVICE, clinux, options ? " " : "", options ? options : "",
d9d99f
 			initrd ? GRUB_INITRD_CMD " " : "", initrd ? GRUB_BOOT_DEVICE : "", initrd ? initrd : "", initrd ? "\n" : "");
d9d99f
 
d9d99f
-  grub_normal_add_menu_entry (1, args, NULL, NULL, "bls", NULL, NULL, src, 0);
d9d99f
+  grub_normal_add_menu_entry (argc, argv, classes, id, users, hotkey, NULL, src, 0);
d9d99f
 
d9d99f
 finish:
d9d99f
-  grub_free (p);
d9d99f
-  grub_free (title);
d9d99f
-  grub_free (options);
d9d99f
-  grub_free (clinux);
d9d99f
-  grub_free (initrd);
d9d99f
-  grub_free (src);
d9d99f
+  if (classes)
d9d99f
+      grub_free (classes);
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+  if (args)
d9d99f
+      grub_free (args);
d9d99f
+
d9d99f
+  if (argv)
d9d99f
+      grub_free (argv);
d9d99f
+
d9d99f
+  if (src)
d9d99f
+      grub_free (src);
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+}
d9d99f
+
d9d99f
+struct find_entry_info {
d9d99f
+	grub_device_t dev;
d9d99f
+	grub_fs_t fs;
d9d99f
+	int efi;
d9d99f
+};
d9d99f
+
d9d99f
+/*
d9d99f
+ * filename: if the directory is /EFI/something/ , filename is "something"
d9d99f
+ * info: unused
d9d99f
+ * data: the filesystem object the file is on.
d9d99f
+ */
d9d99f
+static int find_entry (const char *filename,
d9d99f
+		       const struct grub_dirhook_info *dirhook_info UNUSED,
d9d99f
+		       void *data)
d9d99f
+{
d9d99f
+  struct find_entry_info *info = (struct find_entry_info *)data;
d9d99f
+  grub_file_t f = NULL;
d9d99f
+  char *grubenv_path = NULL;
d9d99f
+  grub_envblk_t env = NULL;
d9d99f
+  char *default_blsdir = NULL;
d9d99f
+  const char *blsdir = NULL;
d9d99f
+  char *saved_env_buf = NULL;
d9d99f
+  int r = 0;
d9d99f
+  const char *devid = grub_env_get ("boot");
d9d99f
+
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+  if (!grub_strcmp (filename, ".") ||
d9d99f
+      !grub_strcmp (filename, ".."))
d9d99f
+    return 0;
d9d99f
+
d9d99f
+  if (info->efi && !grub_strcasecmp (filename, "boot"))
d9d99f
+    return 0;
d9d99f
+
d9d99f
+  saved_env_buf = grub_malloc (512);
d9d99f
+
d9d99f
+  // set a default blsdir
d9d99f
+  if (info->efi)
d9d99f
+    default_blsdir = grub_xasprintf ("/EFI/%s%s", filename,
d9d99f
+				     GRUB_BLS_CONFIG_PATH);
d9d99f
+  else
d9d99f
+    default_blsdir = grub_xasprintf ("%s", GRUB_BLS_CONFIG_PATH);
d9d99f
+
d9d99f
+  grub_env_set ("blsdir", default_blsdir);
d9d99f
+  grub_dprintf ("blscfg", "default_blsdir: \"%s\"\n", default_blsdir);
d9d99f
+
d9d99f
+  /*
d9d99f
+   * try to load a grubenv from /EFI/wherever/grubenv
d9d99f
+   */
d9d99f
+  if (info->efi)
d9d99f
+    grubenv_path = grub_xasprintf ("(%s)/EFI/%s/grubenv", devid, filename);
d9d99f
+  else
d9d99f
+    grubenv_path = grub_xasprintf ("(%s)/grub2/grubenv", devid);
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "looking for \"%s\"\n", grubenv_path);
d9d99f
+  f = grub_file_open (grubenv_path);
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "%s it\n", f ? "found" : "did not find");
d9d99f
+  grub_free (grubenv_path);
d9d99f
+  if (f)
d9d99f
+    {
d9d99f
+      grub_off_t sz;
d9d99f
+
d9d99f
+      grub_dprintf ("blscfg", "getting size\n");
d9d99f
+      sz = grub_file_size (f);
d9d99f
+      if (sz == GRUB_FILE_SIZE_UNKNOWN || sz > 1024*1024)
d9d99f
+	goto finish;
d9d99f
+
d9d99f
+      grub_dprintf ("blscfg", "reading env\n");
d9d99f
+      env = read_envblk_file (f);
d9d99f
+      if (!env)
d9d99f
+	goto finish;
d9d99f
+      grub_dprintf ("blscfg", "read env file\n");
d9d99f
+
d9d99f
+      grub_memset (saved_env_buf, '#', 512);
d9d99f
+      grub_memcpy (saved_env_buf, GRUB_ENVBLK_SIGNATURE,
d9d99f
+		   sizeof (GRUB_ENVBLK_SIGNATURE));
d9d99f
+      grub_dprintf ("blscfg", "saving env\n");
d9d99f
+      saved_env = grub_envblk_open (saved_env_buf, 512);
d9d99f
+      if (!saved_env)
d9d99f
+	goto finish;
d9d99f
+
d9d99f
+      // save everything listed in "env" with values from our existing grub env
d9d99f
+      grub_envblk_iterate (env, NULL, save_var);
d9d99f
+      // set everything from our loaded grubenv into the real grub env
d9d99f
+      grub_envblk_iterate (env, NULL, set_var);
d9d99f
+    }
d9d99f
+  else
d9d99f
+    {
d9d99f
+      grub_err_t e;
d9d99f
+      grub_dprintf ("blscfg", "no such file\n");
d9d99f
+      do
d9d99f
+	{
d9d99f
+	  e = grub_error_pop();
d9d99f
+	} while (e);
d9d99f
+
d9d99f
+    }
d9d99f
+
d9d99f
+  blsdir = grub_env_get ("blsdir");
d9d99f
+  if (!blsdir)
d9d99f
+    goto finish;
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "blsdir: \"%s\"\n", blsdir);
d9d99f
+  if (blsdir[0] != '/' && info->efi)
d9d99f
+    blsdir = grub_xasprintf ("/EFI/%s/%s/", filename, blsdir);
d9d99f
+  else
d9d99f
+    blsdir = grub_strdup (blsdir);
d9d99f
+
d9d99f
+  if (!blsdir)
d9d99f
+    goto finish;
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "blsdir: \"%s\"\n", blsdir);
d9d99f
+  r = info->fs->dir (info->dev, blsdir, read_entry, (char *)blsdir);
d9d99f
+  if (r != 0) {
d9d99f
+      grub_dprintf ("blscfg", "read_entry returned error\n");
d9d99f
+      grub_err_t e;
d9d99f
+      do
d9d99f
+	{
d9d99f
+	  e = grub_error_pop();
d9d99f
+	} while (e);
d9d99f
+  }
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "Sorting %d entries\n", nentries);
d9d99f
+  grub_qsort(&entries[0], nentries, sizeof (struct bls_entry *), bls_cmp, NULL);
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "%s Creating %d entries from bls\n", __func__, nentries);
d9d99f
+  for (r = nentries - 1; r >= 0; r--)
d9d99f
+      create_entry(entries[r], filename);
d9d99f
+
d9d99f
+  for (r = 0; r < nentries; r++)
d9d99f
+      bls_free_entry (entries[r]);
d9d99f
+finish:
d9d99f
+  nentries = 0;
d9d99f
+
d9d99f
+  grub_free (entries);
d9d99f
+  entries = NULL;
d9d99f
+
d9d99f
+  grub_free ((char *)blsdir);
d9d99f
+
d9d99f
+  grub_env_unset ("blsdir");
d9d99f
+
d9d99f
+  if (saved_env)
d9d99f
+    {
d9d99f
+      // remove everything from the real environment that's defined in env
d9d99f
+      grub_envblk_iterate (env, NULL, unset_var);
d9d99f
+
d9d99f
+      // re-set the things from our original environment
d9d99f
+      grub_envblk_iterate (saved_env, NULL, set_var);
d9d99f
+      grub_envblk_close (saved_env);
d9d99f
+      saved_env = NULL;
d9d99f
+    }
d9d99f
+  else if (saved_env_buf)
d9d99f
+    {
d9d99f
+      // if we have a saved environment, grub_envblk_close() freed this.
d9d99f
+      grub_free (saved_env_buf);
d9d99f
+    }
d9d99f
+
d9d99f
+  if (env)
d9d99f
+    grub_envblk_close (env);
d9d99f
 
d9d99f
   if (f)
d9d99f
     grub_file_close (f);
d9d99f
 
d9d99f
+  grub_free (default_blsdir);
d9d99f
+
d9d99f
   return 0;
d9d99f
 }
d9d99f
 
d9d99f
 static grub_err_t
d9d99f
-grub_cmd_bls_import (grub_extcmd_context_t ctxt __attribute__ ((unused)),
d9d99f
-		     int argc __attribute__ ((unused)),
d9d99f
-		     char **args __attribute__ ((unused)))
d9d99f
+grub_cmd_blscfg (grub_extcmd_context_t ctxt UNUSED,
d9d99f
+		     int argc UNUSED,
d9d99f
+		     char **args UNUSED)
d9d99f
 {
d9d99f
   grub_fs_t fs;
d9d99f
   grub_device_t dev;
d9d99f
   static grub_err_t r;
d9d99f
   const char *devid;
d9d99f
+  struct find_entry_info info =
d9d99f
+    {
d9d99f
+      .dev = NULL,
d9d99f
+      .fs = NULL,
d9d99f
+      .efi = 0,
d9d99f
+    };
d9d99f
 
d9d99f
-  devid = grub_env_get ("root");
d9d99f
+
d9d99f
+  grub_dprintf ("blscfg", "finding boot\n");
d9d99f
+  devid = grub_env_get ("boot");
d9d99f
   if (!devid)
d9d99f
-    return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "root");
d9d99f
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND,
d9d99f
+		       N_("variable `%s' isn't set"), "boot");
d9d99f
 
d9d99f
+  grub_dprintf ("blscfg", "opening %s\n", devid);
d9d99f
   dev = grub_device_open (devid);
d9d99f
   if (!dev)
d9d99f
     return grub_errno;
d9d99f
 
d9d99f
+  grub_dprintf ("blscfg", "probing fs\n");
d9d99f
   fs = grub_fs_probe (dev);
d9d99f
   if (!fs)
d9d99f
     {
d9d99f
@@ -174,7 +841,17 @@ grub_cmd_bls_import (grub_extcmd_context_t ctxt __attribute__ ((unused)),
d9d99f
       goto finish;
d9d99f
     }
d9d99f
 
d9d99f
-  r = fs->dir (dev, GRUB_BLS_CONFIG_PATH, parse_entry, NULL);
d9d99f
+  info.dev = dev;
d9d99f
+  info.fs = fs;
d9d99f
+#ifdef GRUB_MACHINE_EFI
d9d99f
+  info.efi = 1;
d9d99f
+  grub_dprintf ("blscfg", "scanning /EFI/\n");
d9d99f
+  r = fs->dir (dev, "/EFI/", find_entry, &info;;
d9d99f
+#else
d9d99f
+  info.efi = 0;
d9d99f
+  grub_dprintf ("blscfg", "scanning %s\n", GRUB_BLS_CONFIG_PATH);
d9d99f
+  r = fs->dir (dev, "/", find_entry, &info;;
d9d99f
+#endif
d9d99f
 
d9d99f
 finish:
d9d99f
   if (dev)
d9d99f
@@ -184,18 +861,27 @@ finish:
d9d99f
 }
d9d99f
 
d9d99f
 static grub_extcmd_t cmd;
d9d99f
+static grub_extcmd_t oldcmd;
d9d99f
 
d9d99f
 GRUB_MOD_INIT(bls)
d9d99f
 {
d9d99f
-  cmd = grub_register_extcmd ("bls_import",
d9d99f
-			      grub_cmd_bls_import,
d9d99f
+  grub_dprintf("blscfg", "%s got here\n", __func__);
d9d99f
+  cmd = grub_register_extcmd ("blscfg",
d9d99f
+			      grub_cmd_blscfg,
d9d99f
 			      0,
d9d99f
 			      NULL,
d9d99f
 			      N_("Import Boot Loader Specification snippets."),
d9d99f
 			      NULL);
d9d99f
+  oldcmd = grub_register_extcmd ("bls_import",
d9d99f
+				 grub_cmd_blscfg,
d9d99f
+				 0,
d9d99f
+				 NULL,
d9d99f
+				 N_("Import Boot Loader Specification snippets."),
d9d99f
+				 NULL);
d9d99f
 }
d9d99f
 
d9d99f
 GRUB_MOD_FINI(bls)
d9d99f
 {
d9d99f
   grub_unregister_extcmd (cmd);
d9d99f
+  grub_unregister_extcmd (oldcmd);
d9d99f
 }
d9d99f
diff --git a/grub-core/commands/loadenv.c b/grub-core/commands/loadenv.c
b71686
index acd93d123..91c994560 100644
d9d99f
--- a/grub-core/commands/loadenv.c
d9d99f
+++ b/grub-core/commands/loadenv.c
d9d99f
@@ -28,6 +28,8 @@
d9d99f
 #include <grub/extcmd.h>
d9d99f
 #include <grub/i18n.h>
d9d99f
 
d9d99f
+#include "loadenv.h"
d9d99f
+
d9d99f
 GRUB_MOD_LICENSE ("GPLv3+");
d9d99f
 
d9d99f
 static const struct grub_arg_option options[] =
d9d99f
@@ -84,81 +86,6 @@ open_envblk_file (char *filename, int untrusted)
d9d99f
   return file;
d9d99f
 }
d9d99f
 
d9d99f
-static grub_envblk_t
d9d99f
-read_envblk_file (grub_file_t file)
d9d99f
-{
d9d99f
-  grub_off_t offset = 0;
d9d99f
-  char *buf;
d9d99f
-  grub_size_t size = grub_file_size (file);
d9d99f
-  grub_envblk_t envblk;
d9d99f
-
d9d99f
-  buf = grub_malloc (size);
d9d99f
-  if (! buf)
d9d99f
-    return 0;
d9d99f
-
d9d99f
-  while (size > 0)
d9d99f
-    {
d9d99f
-      grub_ssize_t ret;
d9d99f
-
d9d99f
-      ret = grub_file_read (file, buf + offset, size);
d9d99f
-      if (ret <= 0)
d9d99f
-        {
d9d99f
-          grub_free (buf);
d9d99f
-          return 0;
d9d99f
-        }
d9d99f
-
d9d99f
-      size -= ret;
d9d99f
-      offset += ret;
d9d99f
-    }
d9d99f
-
d9d99f
-  envblk = grub_envblk_open (buf, offset);
d9d99f
-  if (! envblk)
d9d99f
-    {
d9d99f
-      grub_free (buf);
d9d99f
-      grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid environment block");
d9d99f
-      return 0;
d9d99f
-    }
d9d99f
-
d9d99f
-  return envblk;
d9d99f
-}
d9d99f
-
d9d99f
-struct grub_env_whitelist
d9d99f
-{
d9d99f
-  grub_size_t len;
d9d99f
-  char **list;
d9d99f
-};
d9d99f
-typedef struct grub_env_whitelist grub_env_whitelist_t;
d9d99f
-
d9d99f
-static int
d9d99f
-test_whitelist_membership (const char* name,
d9d99f
-                           const grub_env_whitelist_t* whitelist)
d9d99f
-{
d9d99f
-  grub_size_t i;
d9d99f
-
d9d99f
-  for (i = 0; i < whitelist->len; i++)
d9d99f
-    if (grub_strcmp (name, whitelist->list[i]) == 0)
d9d99f
-      return 1;  /* found it */
d9d99f
-
d9d99f
-  return 0;  /* not found */
d9d99f
-}
d9d99f
-
d9d99f
-/* Helper for grub_cmd_load_env.  */
d9d99f
-static int
d9d99f
-set_var (const char *name, const char *value, void *whitelist)
d9d99f
-{
d9d99f
-  if (! whitelist)
d9d99f
-    {
d9d99f
-      grub_env_set (name, value);
d9d99f
-      return 0;
d9d99f
-    }
d9d99f
-
d9d99f
-  if (test_whitelist_membership (name,
d9d99f
-				 (const grub_env_whitelist_t *) whitelist))
d9d99f
-    grub_env_set (name, value);
d9d99f
-
d9d99f
-  return 0;
d9d99f
-}
d9d99f
-
d9d99f
 static grub_err_t
d9d99f
 grub_cmd_load_env (grub_extcmd_context_t ctxt, int argc, char **args)
d9d99f
 {
d9d99f
diff --git a/grub-core/commands/loadenv.h b/grub-core/commands/loadenv.h
d9d99f
new file mode 100644
b71686
index 000000000..952f46121
d9d99f
--- /dev/null
d9d99f
+++ b/grub-core/commands/loadenv.h
d9d99f
@@ -0,0 +1,93 @@
d9d99f
+/* loadenv.c - command to load/save environment variable.  */
d9d99f
+/*
d9d99f
+ *  GRUB  --  GRand Unified Bootloader
d9d99f
+ *  Copyright (C) 2008,2009,2010  Free Software Foundation, Inc.
d9d99f
+ *
d9d99f
+ *  GRUB is free software: you can redistribute it and/or modify
d9d99f
+ *  it under the terms of the GNU General Public License as published by
d9d99f
+ *  the Free Software Foundation, either version 3 of the License, or
d9d99f
+ *  (at your option) any later version.
d9d99f
+ *
d9d99f
+ *  GRUB is distributed in the hope that it will be useful,
d9d99f
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
d9d99f
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
d9d99f
+ *  GNU General Public License for more details.
d9d99f
+ *
d9d99f
+ *  You should have received a copy of the GNU General Public License
d9d99f
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
d9d99f
+ */
d9d99f
+
d9d99f
+static grub_envblk_t UNUSED
d9d99f
+read_envblk_file (grub_file_t file)
d9d99f
+{
d9d99f
+  grub_off_t offset = 0;
d9d99f
+  char *buf;
d9d99f
+  grub_size_t size = grub_file_size (file);
d9d99f
+  grub_envblk_t envblk;
d9d99f
+
d9d99f
+  buf = grub_malloc (size);
d9d99f
+  if (! buf)
d9d99f
+    return 0;
d9d99f
+
d9d99f
+  while (size > 0)
d9d99f
+    {
d9d99f
+      grub_ssize_t ret;
d9d99f
+
d9d99f
+      ret = grub_file_read (file, buf + offset, size);
d9d99f
+      if (ret <= 0)
d9d99f
+        {
d9d99f
+          grub_free (buf);
d9d99f
+          return 0;
d9d99f
+        }
d9d99f
+
d9d99f
+      size -= ret;
d9d99f
+      offset += ret;
d9d99f
+    }
d9d99f
+
d9d99f
+  envblk = grub_envblk_open (buf, offset);
d9d99f
+  if (! envblk)
d9d99f
+    {
d9d99f
+      grub_free (buf);
d9d99f
+      grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid environment block");
d9d99f
+      return 0;
d9d99f
+    }
d9d99f
+
d9d99f
+  return envblk;
d9d99f
+}
d9d99f
+
d9d99f
+struct grub_env_whitelist
d9d99f
+{
d9d99f
+  grub_size_t len;
d9d99f
+  char **list;
d9d99f
+};
d9d99f
+typedef struct grub_env_whitelist grub_env_whitelist_t;
d9d99f
+
d9d99f
+static int UNUSED
d9d99f
+test_whitelist_membership (const char* name,
d9d99f
+                           const grub_env_whitelist_t* whitelist)
d9d99f
+{
d9d99f
+  grub_size_t i;
d9d99f
+
d9d99f
+  for (i = 0; i < whitelist->len; i++)
d9d99f
+    if (grub_strcmp (name, whitelist->list[i]) == 0)
d9d99f
+      return 1;  /* found it */
d9d99f
+
d9d99f
+  return 0;  /* not found */
d9d99f
+}
d9d99f
+
d9d99f
+/* Helper for grub_cmd_load_env.  */
d9d99f
+static int UNUSED
d9d99f
+set_var (const char *name, const char *value, void *whitelist)
d9d99f
+{
d9d99f
+  if (! whitelist)
d9d99f
+    {
d9d99f
+      grub_env_set (name, value);
d9d99f
+      return 0;
d9d99f
+    }
d9d99f
+
d9d99f
+  if (test_whitelist_membership (name,
d9d99f
+				 (const grub_env_whitelist_t *) whitelist))
d9d99f
+    grub_env_set (name, value);
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
diff --git a/include/grub/compiler.h b/include/grub/compiler.h
b71686
index c9e1d7a73..9859ff4cc 100644
d9d99f
--- a/include/grub/compiler.h
d9d99f
+++ b/include/grub/compiler.h
d9d99f
@@ -48,4 +48,6 @@
d9d99f
 #  define WARN_UNUSED_RESULT
d9d99f
 #endif
d9d99f
 
d9d99f
+#define UNUSED __attribute__((__unused__))
d9d99f
+
d9d99f
 #endif /* ! GRUB_COMPILER_HEADER */