On 2/14/25 8:40 AM, Alec Brown wrote:
The BootLoaderSpec (BLS) defines a scheme where different bootloaders can
share a format for boot items and a configuration directory that accepts
these common configurations as drop-in files.

I have looked over this patch carefully (esp. the parsing routines which are complicated). I did not catch anything obviously wrong though I cannot run every posslbe input through them. Everything seems to follow the spec too. Have you run Coverity on these bits yet? That might help check the parsing logic too.

There are also a couple of minor comments inline below.

Thanks
Ross


Signed-off-by: Peter Jones <pjo...@redhat.com>
Signed-off-by: Javier Martinez Canillas <javi...@redhat.com>
Signed-off-by: Will Thompson <w...@endlessm.com>
Signed-off-by: Alec Brown <alec.r.br...@oracle.com>
---
  Makefile.util.def              |   16 +
  docs/grub.texi                 |   27 +
  grub-core/Makefile.core.def    |   10 +
  grub-core/commands/blsuki.c    | 1030 ++++++++++++++++++++++++++++++++
  grub-core/commands/legacycfg.c |    4 +-
  grub-core/commands/menuentry.c |    8 +-
  grub-core/lib/vercmp.c         |  312 ++++++++++
  grub-core/normal/main.c        |    6 +
  include/grub/lib/vercmp.h      |   28 +
  include/grub/menu.h            |   15 +
  include/grub/normal.h          |    2 +-
  tests/vercmp_unit_test.c       |   65 ++
  12 files changed, 1517 insertions(+), 6 deletions(-)
  create mode 100644 grub-core/commands/blsuki.c
  create mode 100644 grub-core/lib/vercmp.c
  create mode 100644 include/grub/lib/vercmp.h
  create mode 100644 tests/vercmp_unit_test.c

diff --git a/Makefile.util.def b/Makefile.util.def
index 6fe08efd8..7770e7755 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -1327,6 +1327,22 @@ program = {
    ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
  };
+program = {
+  testcase = native;
+  name = vercmp_unit_test;
+  common = tests/vercmp_unit_test.c;
+  common = tests/lib/unit_test.c;
+  common = grub-core/kern/list.c;
+  common = grub-core/kern/misc.c;
+  common = grub-core/tests/lib/test.c;
+  common = grub-core/lib/vercmp.c;
+  ldadd = libgrubmods.a;
+  ldadd = libgrubgcry.a;
+  ldadd = libgrubkern.a;
+  ldadd = grub-core/lib/gnulib/libgnu.a;
+  ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
+};
+
  program = {
    name = grub-menulst2cfg;
    mansection = 1;
diff --git a/docs/grub.texi b/docs/grub.texi
index 975e521d1..a8465fc0b 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -4333,6 +4333,7 @@ you forget a command, you can run the command 
@command{help}
  * background_image::            Load background image for active terminal
  * badram::                      Filter out bad regions of RAM
  * blocklist::                   Print a block list
+* blscfg::                      Load Boot Loader Specification menu entries
  * boot::                        Start up your operating system
  * cat::                         Show the contents of a file
  * clear::                       Clear the screen
@@ -4515,6 +4516,32 @@ Print a block list (@pxref{Block list syntax}) for 
@var{file}.
  @end deffn
+@node blscfg
+@subsection blscfg
+
+@deffn Command blscfg [@option{--path} dir] [@option{--show-default}] 
[@option{--show-non-default}] [@option{--entry} file]
+Load Boot Loader Specification entries into the GRUB menu.
+
+The @option{--path} option overrides the default path to the directory 
containing
+the BLS entries. If this option isn't used, the default location is
+/loader/entries in @code{$BOOT}.
+
+The @option{--show-default} option allows the default boot entry to be added 
to the
+GRUB menu from the BLS entries.
+
+The @option{--show-non-default} option allows non-default boot entries to be 
added to
+the GRUB menu from the BLS entries.
+
+The @option{--entry} option allows specific boot entries to be added to the 
GRUB menu
+from the BLS entries.
+
+The @option{--entry}, @option{--show-default}, and @option{--show-non-default} 
options
+are used to filter which BLS entries are added to the GRUB menu. If none are
+used, all entries in the default location or the location specified by 
@option{--path}
+will be added to the GRUB menu.
+@end deffn
+
+
  @node boot
  @subsection boot
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index d2cf29584..2cf4e9708 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -841,6 +841,16 @@ module = {
    common = commands/blocklist.c;
  };
+module = {
+  name = blsuki;
+  common = commands/blsuki.c;
+  common = lib/vercmp.c;
+  enable = powerpc_ieee1275;
+  enable = efi;
+  enable = i386_pc;
+  enable = emu;
+};
+
  module = {
    name = boot;
    common = commands/boot.c;
diff --git a/grub-core/commands/blsuki.c b/grub-core/commands/blsuki.c
new file mode 100644
index 000000000..5203e4707
--- /dev/null
+++ b/grub-core/commands/blsuki.c
@@ -0,0 +1,1030 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2025  Free Software Foundation, Inc.
+ *
+ *  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.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/list.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+#include <grub/fs.h>
+#include <grub/env.h>
+#include <grub/file.h>
+#include <grub/normal.h>
+#include <grub/safemath.h>
+#include <grub/lib/envblk.h>
+#include <grub/lib/vercmp.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define GRUB_BLS_CONFIG_PATH "/loader/entries/"
+#define GRUB_BOOT_DEVICE "/"
+
+static const struct grub_arg_option opt[] =
+  {
+    {"path", 'p', 0, N_("Specify path to find BLS entries."), N_("DIR"), 
ARG_TYPE_PATHNAME},
+    {"show-default", 'd', 0, N_("Allow the default BLS entry to be added to the 
GRUB menu."), 0, ARG_TYPE_NONE},
+    {"show-non-default", 'n', 0, N_("Allow the non-default BLS entries to be added 
to the GRUB menu."), 0, ARG_TYPE_NONE},
+    {"entry", 'e', 0, N_("Allow specific BLS entries to be added to the GRUB menu."), 
N_("FILE"), ARG_TYPE_FILE},
+    {0, 0, 0, 0, 0, 0}
+  };
+
+struct keyval
+{
+  const char *key;
+  char *val;
+};
+
+static grub_blsuki_entry_t *entries = NULL;
+
+#define FOR_BLSUKI_ENTRIES(var) FOR_LIST_ELEMENTS (var, entries)
+
+static int
+blsuki_add_keyval (grub_blsuki_entry_t *entry, char *key, char *val)
+{
+  char *k, *v;
+  struct keyval **kvs, *kv;
+  grub_size_t size;
+  int new_n = entry->nkeyvals + 1;
+
+  if (entry->keyvals_size == 0)
+    {
+      size = sizeof (struct keyval *);
+      kvs = grub_malloc (size);
+      if (kvs == NULL)
+       return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't allocate space for BLS 
key values");
+
+      entry->keyvals = kvs;
+      entry->keyvals_size = size;
+    }
+  else if (entry->keyvals_size < new_n * sizeof (struct keyval *))
+    {
+      size = entry->keyvals_size * 2;
+      kvs = grub_realloc (entry->keyvals, size);
+      if (kvs == NULL)
+       return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't reallocate space for 
BLS key values");
+
+      entry->keyvals = kvs;
+      entry->keyvals_size = size;
+    }
+
+  kv = grub_malloc (sizeof (struct keyval));
+  if (kv == NULL)
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't find space for new BLS key 
value");
+
+  k = grub_strdup (key);
+  if (k == NULL)
+    {
+      grub_free (kv);
+      return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't find space for new BLS 
key value");
+    }
+
+  v = grub_strdup (val);
+  if (v == NULL)
+    {
+      grub_free (k);
+      grub_free (kv);
+      return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't find space for new BLS 
key value");
+    }
+
+  kv->key = k;
+  kv->val = v;
+
+  entry->keyvals[entry->nkeyvals] = kv;
+  entry->nkeyvals = new_n;
+
+  return 0;
+}
+
+/*
+ * Find the value of the key named by keyname.  If there are allowed to be
+ * more than one, pass a pointer to an int set to -1 the first time, and pass
+ * the same pointer through each time after, and it'll return them in sorted
+ * order as defined in the BLS fragment file.
+ */
+static char *
+blsuki_get_val (grub_blsuki_entry_t *entry, const char *keyname, int *last)
+{
+  int idx, start = 0;
+  struct keyval *kv = NULL;
+
+  if (last != NULL)
+    start = *last + 1;
+
+  for (idx = start; idx < entry->nkeyvals; idx++)
+    {
+      kv = entry->keyvals[idx];
+
+      if (grub_strcmp (keyname, kv->key) == 0)
+       break;
+    }
+
+  if (idx == entry->nkeyvals)
+    {
+      if (last != NULL)
+       *last = -1;
+      return NULL;
+    }
+
+  if (last != NULL)
+    *last = idx;

With a little bit of reorg, the previous could be rewritten to just check last != NULL once.

+
+  return kv->val;
+}
+
+static grub_err_t
+blsuki_add_entry (grub_blsuki_entry_t *entry)
+{
+  grub_blsuki_entry_t *e, *last = NULL;
+  grub_err_t err;
+  int rc;
+
+  if (entries == NULL)
+    {
+      grub_dprintf ("blsuki", "Add entry with id \"%s\"\n", entry->filename);
+      entries = entry;
+      return GRUB_ERR_NONE;
+    }
+
+  FOR_BLSUKI_ENTRIES (e)
+    {
+      err = grub_split_vercmp (entry->filename, e->filename, true, &rc);
+      if (err != GRUB_ERR_NONE)
+       return err;
+
+      if (rc == 0)
+       return grub_error (GRUB_ERR_BAD_ARGUMENT, "duplicate file");
+
+      if (rc == 1)
+       {
+         grub_dprintf ("blsuki", "Add entry with id \"%s\"\n", 
entry->filename);
+         grub_list_push (GRUB_AS_LIST_P (&e), GRUB_AS_LIST (entry));
+         if (entry->next == entries)
+           {
+             entries = entry;
+             entry->prev = NULL;
+           }
+         else
+           last->next = entry;
+         return GRUB_ERR_NONE;
+       }
+      last = e;
+    }
+
+  if (last != NULL)
+    {
+      grub_dprintf ("blsuki", "Add entry with id \"%s\"\n", entry->filename);
+      last->next = entry;
+      entry->prev = &last;
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+static int
+bls_read_entry (grub_file_t f, grub_blsuki_entry_t *entry)
+{
+  int rc = 0;
+
+  for (;;)
+    {
+      char *buf;
+      char *separator;
+
+      buf = grub_file_getline (f);
+      if (buf == NULL)
+       break;
+
+      while (buf != NULL && buf[0] != '\0' && (buf[0] == ' ' || buf[0] == 
'\t'))
+       buf++;
+      if (buf[0] == '#')
+       {
+         grub_free (buf);
+         continue;
+       }
+
+      separator = grub_strchr (buf, ' ');
+
+      if (separator == NULL)
+       separator = grub_strchr (buf, '\t');
+
+      if (separator == NULL || separator[1] == '\0')
+       {
+         grub_free (buf);
+         break;
+       }
+
+       separator[0] = '\0';
+
+      do
+       {
+         separator++;
+       }
+      while (*separator == ' ' || *separator == '\t');
+
+      rc = blsuki_add_keyval (entry, buf, separator);
+      grub_free (buf);
+      if (rc < 0)
+       break;
+    }
+
+  return rc;
+}
+
+struct read_entry_info
+{
+  const char *devid;
+  const char *dirname;
+  grub_file_t file;
+};
+
+static int
+read_entry (const char *filename,
+           const struct grub_dirhook_info *dirhook_info __attribute__ 
((__unused__)),
+           void *data)

It would make things more readable if all the internal blsuki functions had some comment prefix like blsuki_. That way it is obvious these are all functions in this module.

+{
+  grub_size_t m = 0, n, ext_len = 5;
+  int rc = 0;
+  char *p = NULL;
+  grub_file_t f = NULL;
+  grub_blsuki_entry_t *entry;
+  struct read_entry_info *info = (struct read_entry_info *) data;
+
+  grub_dprintf ("blsuki", "filename: \"%s\"\n", filename);
+
+  n = grub_strlen (filename);
+
+  if (info->file != NULL)
+    {
+      f = info->file;
+    }
+  else
+    {
+      if (filename[0] == '.')
+       return 0;
+
+      if (n <= 5)
+       return 0;
+
+      if (grub_strcmp (filename + n - ext_len, ".conf") != 0)
+       return 0;
+
+      p = grub_xasprintf ("(%s)%s/%s", info->devid, info->dirname, filename);
+
+      f = grub_file_open (p, GRUB_FILE_TYPE_CONFIG);
+      grub_free (p);
+      if (f == NULL)
+       goto finish;
+    }
+
+  entry = grub_zalloc (sizeof (*entry));
+  if (entry == NULL)
+    goto finish;
+
+  if (info->file != NULL)
+    {
+      char *slash;
+
+      slash = grub_strrchr (filename, '/');
+      if (slash == NULL)
+       slash = grub_strrchr (filename, '\\');
+
+      if (slash != NULL)
+       {
+         while (*slash == '/' || *slash == '\\')
+           slash++;
+
+         m = slash - filename;
+       }
+      else
+       m = 0;
+    }
+  n -= m;
+
+  entry->filename = grub_strndup (filename + m, n);
+  if (entry->filename == NULL)
+    {
+      grub_free (entry);
+      goto finish;
+    }
+
+  rc = bls_read_entry (f, entry);
+
+  if (rc == 0)
+    blsuki_add_entry (entry);
+  else
+    grub_free (entry);
+
+ finish:
+  if (f != NULL)
+    grub_file_close (f);
+
+  return 0;
+}
+
+static char **
+blsuki_make_list (grub_blsuki_entry_t *entry, const char *key, int *num)
+{
+  int last = -1;
+  char *val;
+  int nlist = 0;
+  char **list;
+
+  list = grub_malloc (sizeof (char *));
+  if (list == NULL)
+    {
+      grub_error (GRUB_ERR_OUT_OF_MEMORY, "failed to allocate BLS list");
+      return NULL;
+    }
+  list[0] = NULL;
+
+  while (1)
+    {
+      char **new;
+
+      val = blsuki_get_val (entry, key, &last);
+      if (val == NULL)
+       break;
+
+      new = grub_realloc (list, (nlist + 2) * sizeof (char *));
+      if (new == NULL)
+       break;
+
+      list = new;
+      list[nlist++] = val;
+      list[nlist] = NULL;
+  }
+
+  if (nlist == 0)
+    {
+      grub_free (list);
+      return NULL;
+    }
+
+  if (num != NULL)
+    *num = nlist;
+
+  return list;
+}
+
+static char *
+field_append (bool is_env_var, char *buffer, const char *start, const char 
*end)
+{
+  char *tmp;
+  const char *field;
+  int term = (is_env_var == true) ? 2 : 1;

For bools, you don't really need the "== true" part here and elsewhere.

+  grub_size_t size = 0;
+
+  tmp = grub_strndup (start, end - start + 1);
+  if (tmp == NULL)
+    return NULL;
+
+  field = tmp;
+
+  if (is_env_var == true)
+    {
+      field = grub_env_get (tmp);
+      if (field == NULL)
+       return buffer;
+    }
+
+  if (grub_add (grub_strlen (field), term, &size))
+    return NULL;
+
+  if (buffer == NULL)
+    buffer = grub_zalloc (size);
+  else
+    {
+      if (grub_add (size, grub_strlen (buffer), &size))
+       return NULL;
+      buffer = grub_realloc (buffer, size);
+    }
+
+  if (buffer == NULL)
+    return NULL;
+
+  tmp = buffer + grub_strlen (buffer);
+  tmp = grub_stpcpy (tmp, field);
+
+  if (is_env_var == true)
+      tmp = grub_stpcpy (tmp, " ");
+
+  return buffer;
+}
+
+static char *
+expand_val (const char *value)
+{
+  char *buffer = NULL;
+  const char *start = value;
+  const char *end = value;
+  bool is_env_var = false;
+
+  if (value == NULL)
+    return NULL;
+
+  while (*value != '\0')
+    {
+      if (*value == '$')
+       {
+         if (start != end)
+           {
+             buffer = field_append (is_env_var, buffer, start, end);
+             if (buffer == NULL)
+               return NULL;
+           }
+
+         is_env_var = true;
+         start = value + 1;
+       }
+      else if (is_env_var == true)
+       {
+         if (grub_isalnum (*value) == 0 && *value != '_')
+           {
+             buffer = field_append (is_env_var, buffer, start, end);
+             is_env_var = false;
+             start = value;
+             if (*start == ' ')
+               start++;
+           }
+       }
+
+      end = value;
+      value++;
+    }
+
+  if (start != end)
+    {
+      buffer = field_append (is_env_var, buffer, start, end);
+      if (buffer == NULL)
+       return NULL;
+    }
+
+  return buffer;
+}
+
+static char **
+early_initrd_list (const char *initrd)
+{
+  int nlist = 0;
+  char **list = NULL;
+  char *separator;
+  char *tmp;
+
+  while ((separator = grub_strchr (initrd, ' ')))
+    {
+      list = grub_realloc (list, (nlist + 2) * sizeof (char *));
+      if (list == NULL)
+       return NULL;
+
+      tmp = grub_strndup (initrd, separator - initrd);
+      if (tmp == NULL)
+       {
+         grub_free (list);
+         return NULL;
+       }
+      list[nlist++] = tmp;
+      list[nlist] = NULL;
+      initrd = separator + 1;
+    }
+
+  list = grub_realloc (list, (nlist + 2) * sizeof (char *));
+  if (list == NULL)
+    return NULL;
+
+  tmp = grub_strndup (initrd, grub_strlen (initrd));
+  if (tmp == NULL)
+    {
+      grub_free (list);
+      return NULL;
+    }
+  list[nlist++] = tmp;
+  list[nlist] = NULL;
+
+  return list;
+}
+
+static void
+create_entry (grub_blsuki_entry_t *entry)
+{
+  int argc = 0;
+  const char **argv = NULL;
+  char *title = NULL;
+  char *clinux = NULL;
+  char *options = NULL;
+  char **initrds = NULL;
+  char *initrd = NULL;
+  int initrd_size;
+  const char *early_initrd = NULL;
+  char **early_initrds = NULL;
+  char *initrd_prefix = NULL;
+  char *prefix = NULL;
+  char *devicetree = NULL;
+  char *dt = NULL;
+  int dt_size;
+  char *id = entry->filename;
+  char *dotconf = id;
+  char *hotkey = NULL;
+  char *users = NULL;
+  char **classes = NULL;
+  char **args = NULL;
+  char *src = NULL;
+  char *tmp;
+  const char *sdval = NULL;
+  int i;
+  grub_size_t size = 0;
+  bool add_dt_prefix = false;
+  bool savedefault;
+
+  clinux = blsuki_get_val (entry, "linux", NULL);
+  if (clinux == NULL)
+    {
+      grub_dprintf ("blsuki", "Skipping file %s with no 'linux' key.\n", 
entry->filename);
+      goto finish;
+    }
+
+  /* Strip the ".conf" off the end before we make it our "id" field. */
+  do
+    {
+      dotconf = grub_strstr (dotconf, ".conf");
+    }
+  while (dotconf != NULL && dotconf[5] != '\0');
+  if (dotconf != NULL)
+    dotconf[0] = '\0';
+
+  title = blsuki_get_val (entry, "title", NULL);
+  options = expand_val (blsuki_get_val (entry, "options", NULL));
+
+  if (options == NULL)
+    options = expand_val (grub_env_get ("default_kernelopts"));
+
+  initrds = blsuki_make_list (entry, "initrd", NULL);
+
+  devicetree = expand_val (blsuki_get_val (entry, "devicetree", NULL));
+
+  if (devicetree == NULL)
+    {
+      devicetree = expand_val (grub_env_get ("devicetree"));
+      add_dt_prefix = true;
+    }
+
+  hotkey = blsuki_get_val (entry, "grub_hotkey", NULL);
+  users = expand_val (blsuki_get_val (entry, "grub_users", NULL));
+  classes = blsuki_make_list (entry, "grub_class", NULL);
+  args = blsuki_make_list (entry, "grub_arg", &argc);
+
+  argc += 1;
+  if (grub_mul (argc + 1, sizeof (char *), &size))
+    {
+      grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow detected creating argv 
list"));
+      goto finish;
+    }
+  argv = grub_malloc (size);
+  if (argv == NULL)
+    {
+      grub_error (GRUB_ERR_OUT_OF_MEMORY, "failed to allocate argv list");
+      goto finish;
+    }
+  argv[0] = title ? title : clinux;
+  for (i = 1; i < argc; i++)
+    argv[i] = args[i-1];
+  argv[argc] = NULL;
+
+  early_initrd = grub_env_get ("early_initrd");
+
+  grub_dprintf ("blsuki", "adding menu entry for \"%s\" with id \"%s\"\n",
+               title, id);
+  if (early_initrd != NULL)
+    {
+      early_initrds = early_initrd_list (early_initrd);
+      if (early_initrds == NULL)
+       {
+         grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("failed to create early initrd 
list"));
+         goto finish;
+       }
+
+      if (initrds != NULL && initrds[0] != NULL)
+       {
+         initrd_prefix = grub_strrchr (initrds[0], '/');
+         initrd_prefix = grub_strndup (initrds[0], initrd_prefix - initrds[0] 
+ 1);
+       }
+      else
+       {
+         initrd_prefix = grub_strrchr (clinux, '/');
+         initrd_prefix = grub_strndup (clinux, initrd_prefix - clinux + 1);
+       }
+
+      if (initrd_prefix == NULL)
+       {
+         grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("failed to allocate initrd prefix 
buffer"));
+         goto finish;
+       }
+    }
+
+  if (early_initrds != NULL || initrds != NULL)
+    {
+      initrd_size = sizeof ("initrd");
+
+      for (i = 0; early_initrds != NULL && early_initrds[i] != NULL; i++)
+       {
+         if (grub_add (initrd_size, sizeof (" " GRUB_BOOT_DEVICE), 
&initrd_size) ||
+             grub_add (initrd_size, grub_strlen (initrd_prefix), &initrd_size) 
||
+             grub_add (initrd_size, grub_strlen (early_initrds[i]), 
&initrd_size) ||
+             grub_add (initrd_size, 1, &initrd_size))
+           {
+             grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating 
initrd buffer size");
+             goto finish;
+           }
+       }
+
+      for (i = 0; initrds != NULL && initrds[i] != NULL; i++)
+       {
+         if (grub_add (initrd_size, sizeof (" " GRUB_BOOT_DEVICE), 
&initrd_size) ||
+             grub_add (initrd_size, grub_strlen (initrds[i]), &initrd_size) ||
+             grub_add (initrd_size, 1, &initrd_size))
+           {
+             grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating 
initrd buffer size");
+             goto finish;
+           }
+       }
+
+      if (grub_add (initrd_size, 1, &initrd_size))
+       {
+         grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating initrd 
buffer size");
+         goto finish;
+       }
+
+      initrd = grub_malloc (initrd_size);
+      if (initrd == NULL)
+       {
+         grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("failed to allocate initrd 
buffer"));
+         goto finish;
+       }
+
+      tmp = grub_stpcpy (initrd, "initrd");
+      for (i = 0; early_initrds != NULL && early_initrds[i] != NULL; i++)
+       {
+         grub_dprintf ("blsuki", "adding early initrd %s\n", early_initrds[i]);
+         tmp = grub_stpcpy (tmp, " " GRUB_BOOT_DEVICE);
+         tmp = grub_stpcpy (tmp, initrd_prefix);
+         tmp = grub_stpcpy (tmp, early_initrds[i]);
+         grub_free (early_initrds[i]);
+       }
+
+      for (i = 0; initrds != NULL && initrds[i] != NULL; i++)
+       {
+         grub_dprintf ("blsuki", "adding initrd %s\n", initrds[i]);
+         tmp = grub_stpcpy (tmp, " " GRUB_BOOT_DEVICE);
+         tmp = grub_stpcpy (tmp, initrds[i]);
+       }
+      tmp = grub_stpcpy (tmp, "\n");
+    }
+
+  if (devicetree != NULL)
+    {
+      if (add_dt_prefix == true)
+       {
+         prefix = grub_strrchr (clinux, '/');
+         prefix = grub_strndup (clinux, prefix - clinux + 1);
+         if (prefix == NULL)
+           {
+             grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("failed to allocate prefix 
buffer"));
+             goto finish;
+           }
+       }
+
+      if (grub_add (sizeof ("devicetree " GRUB_BOOT_DEVICE), grub_strlen 
(devicetree), &dt_size) ||
+         grub_add (dt_size, 1, &dt_size))
+       {
+         grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating device 
tree buffer size");
+         goto finish;
+       }
+
+      if (add_dt_prefix == true)
+       {
+         if (grub_add (dt_size, grub_strlen (prefix), &dt_size))
+           {
+             grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating 
device tree buffer size");
+             goto finish;
+           }
+       }
+
+      dt = grub_malloc (dt_size);
+      if (dt == NULL)
+        {
+         grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("failed to allocate device tree 
buffer"));
+         goto finish;
+        }
+      tmp = dt;
+      tmp = grub_stpcpy (dt, "devicetree");
+      tmp = grub_stpcpy (tmp, " " GRUB_BOOT_DEVICE);
+      if (add_dt_prefix == true)
+        tmp = grub_stpcpy (tmp, prefix);
+      tmp = grub_stpcpy (tmp, devicetree);
+      tmp = grub_stpcpy (tmp, "\n");
+
+      grub_free (prefix);
+    }
+
+  grub_dprintf ("blsuki", "devicetree %s for id:\"%s\"\n", dt, id);
+
+  sdval = grub_env_get ("save_default");
+  savedefault = ((NULL != sdval) && (grub_strcmp (sdval, "true") == 0));
+  src = grub_xasprintf ("%sload_video\n"
+                       "set gfxpayload=keep\n"
+                       "insmod gzio\n"
+                       "linux %s%s%s%s\n"
+                       "%s%s",
+                       savedefault ? "savedefault\n" : "",
+                       GRUB_BOOT_DEVICE, clinux, options ? " " : "", options ? options : 
"",
+                       initrd ? initrd : "", dt ? dt : "");
+
+  grub_normal_add_menu_entry (argc, argv, classes, id, users, hotkey, NULL, 
src, 0, entry);
+
+ finish:
+  grub_free (dt);
+  grub_free (initrd);
+  grub_free (initrd_prefix);
+  grub_free (early_initrds);
+  grub_free (devicetree);
+  grub_free (initrds);
+  grub_free (options);
+  grub_free (classes);
+  grub_free (args);
+  grub_free (argv);
+  grub_free (src);
+}
+
+struct find_entry_info
+{
+  const char *dirname;
+  const char *devid;
+  grub_device_t dev;
+  grub_fs_t fs;
+};
+
+/* info: the filesystem object the file is on. */
+static void
+find_entry (struct find_entry_info *info)
+{
+  struct read_entry_info read_entry_info;
+  grub_fs_t dir_fs = NULL;
+  grub_device_t dir_dev = NULL;
+  const char *dir = info->dirname;
+  int fallback = 0;
+  int r = 0;
+
+  if (dir == NULL)
+    dir = GRUB_BLS_CONFIG_PATH;
+
+  read_entry_info.file = NULL;
+  read_entry_info.dirname = dir;
+
+  grub_dprintf ("blsuki", "scanning dir: %s\n", dir);
+
+  dir_dev = info->dev;
+  dir_fs = info->fs;
+  read_entry_info.devid = info->devid;
+
+ read_fallback:
+  r = dir_fs->fs_dir (dir_dev, read_entry_info.dirname, read_entry,
+                     &read_entry_info);
+  if (r != 0)
+    {
+      grub_dprintf ("blsuki", "read_entry returned error\n");
+      grub_errno = GRUB_ERR_NONE;
+    }
+
+  /*
+   * If we aren't able to find BLS entries in the directory given by 
info->dirname,
+   * we can fallback to the default location "/boot/loader/entries/" and see 
if we
+   * can find the files there.
+   */
+  if (r != 0 && info->dirname == NULL && fallback == 0)
+    {
+      read_entry_info.dirname = "/boot" GRUB_BLS_CONFIG_PATH;
+      grub_dprintf ("blsuki", "Entries weren't found in %s, fallback to %s\n",
+                   dir, read_entry_info.dirname);
+      fallback = 1;
+      goto read_fallback;
+    }
+}
+
+static grub_err_t
+blsuki_load_entries (char *path)
+{
+  grub_size_t len;
+  grub_fs_t fs;
+  grub_device_t dev;
+  static grub_err_t r;
+  const char *devid = NULL;
+  char *dir = NULL;
+  struct find_entry_info info = {
+      .dev = NULL,
+      .fs = NULL,
+      .dirname = NULL,
+  };
+  struct read_entry_info rei = {
+      .devid = NULL,
+      .dirname = NULL,
+  };
+
+  if (path != NULL)
+    {
+      len = grub_strlen (path);
+      if (grub_strcmp (path + len - 5, ".conf") == 0)
+       {
+         rei.file = grub_file_open (path, GRUB_FILE_TYPE_CONFIG);
+         if (rei.file == NULL)
+           return grub_errno;
+
+         /* read_entry() closes the file. */
+         return read_entry (path, NULL, &rei);
+       }
+      else if (path[0] == '(')
+       {
+         devid = path + 1;
+
+         dir = grub_strchr (path, ')');
+         if (dir == NULL)
+           return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid file name 
`%s'"), path);
+
+         *dir = '\0';
+
+         /* Check if there is more than the devid in the path. */
+         if (dir + 1 < path + len)
+           dir = dir + 1;
+       }
+      else if (path[0] == '/')
+       dir = path;
+    }
+
+  if (devid == NULL)
+    {
+#ifdef GRUB_MACHINE_EMU
+      devid = "host";
+#else
+      devid = grub_env_get ("root");
+#endif
+      if (devid == NULL)
+       return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), 
"root");
+    }
+
+  grub_dprintf ("blsuki", "opening %s\n", devid);
+  dev = grub_device_open (devid);
+  if (dev == NULL)
+    return grub_errno;
+
+  grub_dprintf ("blsuki", "probing fs\n");
+  fs = grub_fs_probe (dev);
+  if (fs == NULL)
+    {
+      r = grub_errno;
+      goto finish;
+    }
+
+  info.dirname = dir;
+  info.devid = devid;
+  info.dev = dev;
+  info.fs = fs;
+  find_entry (&info);
+
+ finish:
+  if (dev != NULL)
+    grub_device_close (dev);
+
+  return r;
+}
+
+static bool
+is_default_entry (const char *def_entry, grub_blsuki_entry_t *entry, int idx)
+{
+  const char *title;
+  int def_idx;
+
+  if (def_entry == NULL)
+    return false;
+
+  if (grub_strcmp (def_entry, entry->filename) == 0)
+    return true;
+
+  title = blsuki_get_val (entry, "title", NULL);
+
+  if (title != NULL && grub_strcmp (def_entry, title) == 0)
+    return true;
+
+  def_idx = (int) grub_strtol (def_entry, NULL, 0);
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_errno = GRUB_ERR_NONE;
+      return false;
+    }
+
+  if (def_idx == idx)
+    return true;
+
+  return false;
+}
+
+static grub_err_t
+blsuki_create_entries (bool show_default, bool show_non_default, char 
*entry_id)
+{
+  const char *def_entry = NULL;
+  grub_blsuki_entry_t *entry = NULL;
+  int idx = 0;
+
+  def_entry = grub_env_get ("default");
+
+  FOR_BLSUKI_ENTRIES(entry)
+    {
+      if (entry->visible == 1)
+       {
+         idx++;
+         continue;
+       }
+      if ((show_default == true && is_default_entry (def_entry, entry, idx) == 
true) ||
+         (show_non_default == true && is_default_entry (def_entry, entry, idx) 
== false) ||
+         (entry_id != NULL && grub_strcmp (entry_id, entry->filename) == 0))
+       {
+         create_entry (entry);
+         entry->visible = 1;
+       }
+      idx++;
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_blscfg (grub_extcmd_context_t ctxt, int argc __attribute__ ((unused)),
+                char **args __attribute__ ((unused)))
+{
+  grub_err_t err;
+  struct grub_arg_list *state = ctxt->state;
+  char *path = NULL;
+  char *entry_id = NULL;
+  bool show_default = false;
+  bool show_non_default = false;
+  bool all = true;
+
+  if (state[0].set)
+    path = state[0].arg;
+  if (state[1].set)
+    {
+      show_default = true;
+      all = false;
+    }
+  if (state[2].set)
+    {
+      show_non_default = true;
+      all = false;
+    }
+  if (state[3].set)
+    {
+      entry_id = state[3].arg;
+      all = false;
+    }
+  if (all == true)
+    {
+      show_default = true;
+      show_non_default = true;
+    }
+
+  err = blsuki_load_entries (path);
+  if (err != GRUB_ERR_NONE)
+    return err;
+
+  return blsuki_create_entries (show_default, show_non_default, entry_id);
+}
+
+static grub_extcmd_t bls_cmd;
+
+GRUB_MOD_INIT(blsuki)
+{
+  bls_cmd = grub_register_extcmd ("blscfg", grub_cmd_blscfg, 0,
+                                 N_("[-p|--path] DIR [-d|--show-default] 
[-n|--show-non-default] [-e|--entry] FILE"),
+                                 N_("Import Boot Loader Specification 
snippets."),
+                                 opt);
+}
+
+GRUB_MOD_FINI(blsuki)
+{
+  grub_unregister_extcmd (bls_cmd);
+}
diff --git a/grub-core/commands/legacycfg.c b/grub-core/commands/legacycfg.c
index e9e9d94ef..e67f8990c 100644
--- a/grub-core/commands/legacycfg.c
+++ b/grub-core/commands/legacycfg.c
@@ -143,7 +143,7 @@ legacy_file (const char *filename)
            args[0] = oldname;
            grub_normal_add_menu_entry (1, args, NULL, NULL, "legacy",
                                        NULL, NULL,
-                                       entrysrc, 0);
+                                       entrysrc, 0, NULL);
            grub_free (args);
            entrysrc[0] = 0;
            grub_free (oldname);
@@ -205,7 +205,7 @@ legacy_file (const char *filename)
        }
        args[0] = entryname;
        grub_normal_add_menu_entry (1, args, NULL, NULL, NULL,
-                                 NULL, NULL, entrysrc, 0);
+                                 NULL, NULL, entrysrc, 0, NULL);
        grub_free (args);
      }
diff --git a/grub-core/commands/menuentry.c b/grub-core/commands/menuentry.c
index 720e6d8ea..09749c415 100644
--- a/grub-core/commands/menuentry.c
+++ b/grub-core/commands/menuentry.c
@@ -78,7 +78,7 @@ grub_normal_add_menu_entry (int argc, const char **args,
                            char **classes, const char *id,
                            const char *users, const char *hotkey,
                            const char *prefix, const char *sourcecode,
-                           int submenu)
+                           int submenu, grub_blsuki_entry_t *blsuki)
  {
    int menu_hotkey = 0;
    char **menu_args = NULL;
@@ -188,6 +188,7 @@ grub_normal_add_menu_entry (int argc, const char **args,
    (*last)->args = menu_args;
    (*last)->sourcecode = menu_sourcecode;
    (*last)->submenu = submenu;
+  (*last)->blsuki = blsuki;
menu->size++;
    return GRUB_ERR_NONE;
@@ -286,7 +287,8 @@ grub_cmd_menuentry (grub_extcmd_context_t ctxt, int argc, 
char **args)
                                       users,
                                       ctxt->state[2].arg, 0,
                                       ctxt->state[3].arg,
-                                      ctxt->extcmd->cmd->name[0] == 's');
+                                      ctxt->extcmd->cmd->name[0] == 's',
+                                      NULL);
src = args[argc - 1];
    args[argc - 1] = NULL;
@@ -303,7 +305,7 @@ grub_cmd_menuentry (grub_extcmd_context_t ctxt, int argc, 
char **args)
                                  ctxt->state[0].args, ctxt->state[4].arg,
                                  users,
                                  ctxt->state[2].arg, prefix, src + 1,
-                                 ctxt->extcmd->cmd->name[0] == 's');
+                                 ctxt->extcmd->cmd->name[0] == 's', NULL);
src[len - 1] = ch;
    args[argc - 1] = src;
diff --git a/grub-core/lib/vercmp.c b/grub-core/lib/vercmp.c
new file mode 100644
index 000000000..1ee70868a
--- /dev/null
+++ b/grub-core/lib/vercmp.c
@@ -0,0 +1,312 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2025  Free Software Foundation, Inc.
+ *
+ *  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.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/lib/vercmp.h>
+
+#define GOTO_RETURN(x) ({ *ret = (x); goto finish; })
+
+/*
+ * compare alpha and numeric segments of two versions
+ * return 1: a is newer than b
+ *        0: a and b are the same version
+ *       -1: b is newer than a

These values are not really the return value but rather the out parameter ret values. Maybe use an enum as the "ret" param instead of just 1,0,-1 directly? It would make it clearer what the values mean.

+ */
+grub_err_t
+grub_vercmp (const char *a, const char *b, int *ret)
+{
+  char oldch1, oldch2;
+  char *abuf, *bbuf;
+  char *str1, *str2;
+  char *one, *two;
+  int rc;
+  bool isnum;
+  *ret = 0;

Perhaps check for a NULL ret passed in and fail with an error if it is since this function requires it to be there.

+
+  grub_dprintf ("blscfg", "%s comparing %s and %s\n", __func__, a, b);
+  if (grub_strcmp (a, b) == 0)
+    return GRUB_ERR_NONE;
+
+  abuf = grub_strdup (a);
+  if (abuf == NULL)
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't duplicate string to 
compare versions");
+
+  bbuf = grub_strdup (b);
+  if (bbuf == NULL)
+    {
+      grub_free (abuf);
+      return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't duplicate string to 
compare versions");
+    }
+
+  str1 = abuf;
+  str2 = bbuf;
+
+  one = str1;
+  two = str2;
+
+  /* Loop through each version segment of str1 and str2 and compare them. */
+  while (*one != '\0' || *two != '\0')
+    {
+      while (*one != '\0' && grub_isalnum (*one) == 0 && *one != '~' && *one 
!= '+')
+        one++;
+      while (*two != '\0' && grub_isalnum (*two) == 0 && *two != '~' && *two 
!= '+')
+        two++;
+
+      /* Handle the tilde separator, it sorts before everything else. */
+      if (*one == '~' || *two == '~')
+        {
+          if (*one != '~')
+            GOTO_RETURN (1);
+          if (*two != '~')
+            GOTO_RETURN (-1);
+          one++;
+          two++;
+          continue;
+        }
+
+      /*
+       * Handle the plus separator. Concept is the same as tilde, except that 
if
+       * one of the strings ends (base version), the other is considered as the
+       * higher version.
+       */
+      if (*one == '+' || *two == '+')
+        {
+          if (*one == '\0')
+            GOTO_RETURN (-1);
+          if (*two == '\0')
+            GOTO_RETURN (1);
+          if (*one != '+')
+            GOTO_RETURN (1);
+          if (*two != '+')
+            GOTO_RETURN (-1);
+          one++;
+          two++;
+          continue;
+        }
+
+      /* If we ran to the end of either, we are finished with the loop. */
+ if (*one == '\0' || *two == '\0')
+        break;
+
+      str1 = one;
+      str2 = two;
+
+      /*
+       * Grab the first completely alpha or completely numeric segment.
+       * Leave one and two pointing to the start of the alpha or numeric
+       * segment and walk str1 and str2 to end of segment.
+       */
+      if (grub_isdigit (*str1) == 1)
+        {
+          while (*str1 != '\0' && grub_isdigit (*str1) == 1)
+            str1++;
+          while (*str2 != '\0' && grub_isdigit (*str2) == 1)
+            str2++;
+          isnum = true;
+        }
+      else
+        {
+          while (*str1 != '\0' && grub_isalpha (*str1) == 1)
+            str1++;
+          while (*str2 != '\0' && grub_isalpha (*str2) == 1)
+            str2++;
+          isnum = false;
+        }
+
+      /*
+       * Save the character at the end of the alpha or numeric segment so that
+       * they can be restored after the comparison.
+       */
+      oldch1 = *str1;
+      *str1 = '\0';
+      oldch2 = *str2;
+      *str2 = '\0';
+
+      /*
+       * This cannot happen, as we previously tested to make sure that
+       * the first string has a non-null segment.
+       */
+      if (one == str1)
+        GOTO_RETURN (-1); /* arbitrary */
+
+      /*
+       * Take care of the case where the two version segments are different
+       * types: one numeric, the other alpha (i.e. empty). Numeric segments are
+       * always newer than alpha segments.
+       */
+      if (two == str2)
+        GOTO_RETURN (isnum ? 1 : -1);
+
+      if (isnum == true)
+        {
+          grub_size_t onelen, twolen;
+          /*
+           * This used to be done by converting the digit segments to ints 
using
+           * atoi() - it's changed because long digit segments can overflow an 
int -
+           * this should fix that.
+           */
+
+          /* Throw away any leading zeros - it's a number, right? */
+          while (*one == '0')
+            one++;
+          while (*two == '0')
+            two++;
+
+          /* Whichever number that has more digits wins. */
+          onelen = grub_strlen (one);
+          twolen = grub_strlen (two);
+          if (onelen > twolen)
+            GOTO_RETURN (1);
+          if (twolen > onelen)
+            GOTO_RETURN (-1);
+        }
+
+      /*
+       * grub_strcmp will return which one is greater - even if the two 
segments
+       * are alpha or if they are numeric. Don't return if they are equal
+       * because there might be more segments to compare.
+       */
+      rc = grub_strcmp (one, two);
+      if (rc != 0)
+        GOTO_RETURN (rc < 1 ? -1 : 1);
+
+      /* Restore the character that was replaced by null above. */
+      *str1 = oldch1;
+      one = str1;
+      *str2 = oldch2;
+      two = str2;
+    }
+
+  /*
+   * This catches the case where all alpha and numeric segments have compared
+   * identically but the segment separating characters were different.
+   */
+  if (*one == '\0' && *two == '\0')
+    GOTO_RETURN (0);
+
+  /* Whichever version that still has characters left over wins. */
+  if (*one == '\0')
+    GOTO_RETURN (-1);
+  else
+    GOTO_RETURN (1);
+
+ finish:
+  grub_free (abuf);
+  grub_free (bbuf);
+  return GRUB_ERR_NONE;
+}
+
+/*
+ * returns name/version/release
+ * NULL string pointer returned if nothing is found
+ */
+void
+grub_split_package_string (char *package_string, char **name,
+                          char **version, char **release)
+{
+  char *package_version, *package_release;
+
+  /* Release */
+  package_release = grub_strrchr (package_string, '-');
+
+  if (package_release != NULL)
+      *package_release++ = '\0';
+
+  *release = package_release;
+
+  if (name == NULL)
+    *version = package_string;
+  else
+    {
+      /* Version */
+      package_version = grub_strrchr (package_string, '-');
+
+      if (package_version != NULL)
+        *package_version++ = '\0';
+
+      *version = package_version;
+      /* Name */
+      *name = package_string;
+    }
+
+  /* Bubble up non-null values from release to name */
+  if (name != NULL && *name == NULL)
+    {
+      *name = (*version == NULL ? *release : *version);
+      *version = *release;
+      *release = NULL;
+    }
+  if (*version == NULL)
+    {
+      *version = *release;
+      *release = NULL;
+    }
+}
+
+/*
+ * return 1: nvr0 is newer than nvr1
+ *        0: nvr0 and nvr1 are the same version
+ *       -1: nvr1 is newer than nvr0
+ */
+grub_err_t
+grub_split_vercmp (const char *nvr0, const char *nvr1, bool has_name, int *ret)
+{
+  grub_err_t err = GRUB_ERR_NONE;
+  char *str0, *name0, *version0, *release0;
+  char *str1, *name1, *version1, *release1;
+  *ret = 0;
+
+  str0 = grub_strdup (nvr0);
+  if (str0 == NULL)
+    return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't duplicate BLS filename to 
compare");
+  str1 = grub_strdup (nvr1);
+  if (str1 == NULL)
+    {
+      grub_free (str0);
+      return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't duplicate BLS filename 
to compare");
+    }
+
+  grub_split_package_string (str0, has_name ? &name0 : NULL, &version0, 
&release0);
+  grub_split_package_string (str1, has_name ? &name1 : NULL, &version1, 
&release1);
+
+  if (has_name == true)
+    {
+      err = grub_vercmp (name0 == NULL ? "" : name0, name1 == NULL ? "" : 
name1, ret);
+      if (*ret != 0 || err != GRUB_ERR_NONE)
+       {
+         grub_free (str0);
+         grub_free (str1);
+         return err;
+       }
+    }
+
+  err = grub_vercmp (version0 == NULL ? "" : version0, version1 == NULL ? "" : 
version1, ret);
+  if (*ret != 0 || err != GRUB_ERR_NONE)
+    {
+      grub_free (str0);
+      grub_free (str1);
+      return err;
+    }
+
+  err = grub_vercmp (release0 == NULL ? "" : release0, release1 == NULL ? "" : 
release1, ret);
+
+  grub_free (str0);
+  grub_free (str1);
+  return err;
+}
diff --git a/grub-core/normal/main.c b/grub-core/normal/main.c
index bd4431000..2e9d9a1e8 100644
--- a/grub-core/normal/main.c
+++ b/grub-core/normal/main.c
@@ -21,6 +21,7 @@
  #include <grub/net.h>
  #include <grub/normal.h>
  #include <grub/dl.h>
+#include <grub/menu.h>
  #include <grub/misc.h>
  #include <grub/file.h>
  #include <grub/mm.h>
@@ -67,6 +68,11 @@ grub_normal_free_menu (grub_menu_t menu)
          grub_free (entry->args);
        }
+ if (entry->blsuki)
+       {
+         entry->blsuki->visible = 0;
+       }
+
        grub_free ((void *) entry->id);
        grub_free ((void *) entry->users);
        grub_free ((void *) entry->title);
diff --git a/include/grub/lib/vercmp.h b/include/grub/lib/vercmp.h
new file mode 100644
index 000000000..32f307e4e
--- /dev/null
+++ b/include/grub/lib/vercmp.h
@@ -0,0 +1,28 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2025  Free Software Foundation, Inc.
+ *
+ *  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.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef GRUB_VERCMP_H
+#define GRUB_VERCMP_H  1
+
+grub_err_t grub_vercmp (const char *a, const char *b, int *ret);
+
+void grub_split_package_string (char *package_string, char **name,
+                               char **version, char **release);
+
+grub_err_t grub_split_vercmp (const char *nvr0, const char *nvr1, bool 
has_name, int *ret);
+
+#endif /* ! GRUB_VERCMP_H */
diff --git a/include/grub/menu.h b/include/grub/menu.h
index ee2b5e910..c25a0d16d 100644
--- a/include/grub/menu.h
+++ b/include/grub/menu.h
@@ -20,6 +20,18 @@
  #ifndef GRUB_MENU_HEADER
  #define GRUB_MENU_HEADER 1
+struct grub_blsuki_entry
+{
+  struct grub_blsuki_entry *next;
+  struct grub_blsuki_entry **prev;
+  struct keyval **keyvals;
+  grub_size_t keyvals_size;
+  int nkeyvals;
+  char *filename;
+  int visible;
+};
+typedef struct grub_blsuki_entry grub_blsuki_entry_t;
+
  struct grub_menu_entry_class
  {
    char *name;
@@ -60,6 +72,9 @@ struct grub_menu_entry
/* The next element. */
    struct grub_menu_entry *next;
+
+  /* BLS used to populate the entry */
+  grub_blsuki_entry_t *blsuki;
  };
  typedef struct grub_menu_entry *grub_menu_entry_t;
diff --git a/include/grub/normal.h b/include/grub/normal.h
index 218cbabcc..d0150e3c2 100644
--- a/include/grub/normal.h
+++ b/include/grub/normal.h
@@ -145,7 +145,7 @@ grub_normal_add_menu_entry (int argc, const char **args, 
char **classes,
                            const char *id,
                            const char *users, const char *hotkey,
                            const char *prefix, const char *sourcecode,
-                           int submenu);
+                           int submenu, grub_blsuki_entry_t *blsuki);
grub_err_t
  grub_normal_set_password (const char *user, const char *password);
diff --git a/tests/vercmp_unit_test.c b/tests/vercmp_unit_test.c
new file mode 100644
index 000000000..765f6b2b6
--- /dev/null
+++ b/tests/vercmp_unit_test.c
@@ -0,0 +1,65 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2025 Free Software Foundation, Inc.
+ *
+ *  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.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <grub/test.h>
+#include <grub/lib/vercmp.h>
+
+#define MSG "vercmp test failed"
+
+static void
+vercmp_test (void)
+{
+  const char *s1 = "name0-1.0.0-1.0.0.el8uek.x86_64";
+  const char *s2 = "name0-1.0.0-1.1.0.el8uek.x86_64";
+  const char *s3 = "name0-1.1.0-1.0.0.el8uek.x86_64";
+  const char *s4 = "name1-1.0.0-1.0.0.el8uek.x86_64";
+  const char *s5 = "version0";
+  const char *s6 = "version1";
+  char *nvr, *name, *version, *release;
+  int ret;
+
+  nvr = strdup (s1);
+
+  grub_split_package_string (nvr, &name, &version, &release);
+  grub_test_assert (strcmp (name, "name0") == 0, MSG);
+  grub_test_assert (strcmp (version, "1.0.0") == 0, MSG);
+  grub_test_assert (strcmp (release, "1.0.0.el8uek.x86_64") == 0, MSG);
+  free (nvr);
+
+  grub_split_vercmp (s1, s1, true, &ret);
+  grub_test_assert (ret == 0, MSG);
+  grub_split_vercmp (s1, s2, true, &ret);
+  grub_test_assert (ret == -1, MSG);
+  grub_split_vercmp (s1, s3, true, &ret);
+  grub_test_assert (ret == -1, MSG);
+  grub_split_vercmp (s1, s4, true, &ret);
+  grub_test_assert (ret == -1, MSG);
+  grub_split_vercmp (s2, s1, true, &ret);
+  grub_test_assert (ret == 1, MSG);
+
+  grub_vercmp (s5, s5, &ret);
+  grub_test_assert (ret == 0, MSG);
+  grub_vercmp (s5, s6, &ret);
+  grub_test_assert (ret == -1, MSG);
+  grub_vercmp (s6, s5, &ret);
+  grub_test_assert (ret == 1, MSG);
+}
+
+GRUB_UNIT_TEST ("vercmp_unit_test", vercmp_test);


_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to