Missing From:...

On Sat, Apr 12, 2025 at 03:53:09AM +0000, 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.

Please add links to the specification here.

> 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                 |   29 +
>  grub-core/Makefile.core.def    |   10 +
>  grub-core/commands/blsuki.c    | 1057 ++++++++++++++++++++++++++++++++
>  grub-core/commands/legacycfg.c |    4 +-
>  grub-core/commands/menuentry.c |    8 +-
>  grub-core/lib/vercmp.c         |  317 ++++++++++
>  grub-core/normal/main.c        |    6 +
>  include/grub/lib/vercmp.h      |   35 ++
>  include/grub/menu.h            |   15 +
>  include/grub/normal.h          |    2 +-
>  tests/vercmp_unit_test.c       |   65 ++
>  12 files changed, 1558 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 038253b37..a911f2e0a 100644
> --- a/Makefile.util.def
> +++ b/Makefile.util.def
> @@ -1373,6 +1373,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)';
> +};
> +

Please move tests to separate patch.

>  program = {
>    name = grub-menulst2cfg;
>    mansection = 1;
> diff --git a/docs/grub.texi b/docs/grub.texi
> index d9b26fa36..ea7a13953 100644
> --- a/docs/grub.texi
> +++ b/docs/grub.texi
> @@ -6417,6 +6417,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
> @@ -6603,6 +6604,34 @@ Print a block list (@pxref{Block list syntax}) for 
> @var{file}.
>  @end deffn
>
>
> +@node blscfg
> +@subsection blscfg
> +
> +@deffn Command blscfg [@option{--path} dir] [@option{--enable-fallback}] 
> [@option{--show-default}] [@option{--show-non-default}] [@option{--entry} 
> file]

The long options have short counterparts. Why do not you list them here?

> +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}. If no BLS entries are found, the
> +@option{--enable-fallback} option can be used to check for entries in the 
> default
> +directory.
> +
> +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

I would add links to the specification here and give some examples of
BLS files. Additionally, I think it is worth mentioning how this integrates
with current grub.cfg and how both methods of loading system images are
related.

>  @node boot
>  @subsection boot
>
> diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
> index f70e02e69..f3b776c0a 100644
> --- a/grub-core/Makefile.core.def
> +++ b/grub-core/Makefile.core.def
> @@ -845,6 +845,16 @@ module = {
>    common = commands/blocklist.c;
>  };
>
> +module = {
> +  name = blsuki;
> +  common = commands/blsuki.c;
> +  common = lib/vercmp.c;

Probably this should be a part of the kernel.

> +  enable = powerpc_ieee1275;

??? Really? PowerPC? IEEE 1275? I think something is off here...

> +  enable = efi;
> +  enable = i386_pc;

PC?

> +  enable = emu;

Again, emu?

I think we should have EFI here only.

> +};
> +
>  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..0fb4f870a
> --- /dev/null
> +++ b/grub-core/commands/blsuki.c
> @@ -0,0 +1,1057 @@
> +/*
> + *  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/"
> +
> +static const struct grub_arg_option bls_opt[] =
> +  {
> +    {"path", 'p', 0, "Specify path to find BLS entries.", N_("DIR"), 
> ARG_TYPE_PATHNAME},
> +    {"enable-fallback", 'f', 0, "Fallback to the default BLS path if --path 
> fails to find BLS entries.", 0, ARG_TYPE_NONE},
> +    {"show-default", 'd', 0, "Allow the default BLS entry to be added to the 
> GRUB menu.", 0, ARG_TYPE_NONE},
> +    {"show-non-default", 'n', 0, "Allow the non-default BLS entries to be 
> added to the GRUB menu.", 0, ARG_TYPE_NONE},
> +    {"entry", 'e', 0, "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 grub_err_t
> +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 GRUB_ERR_NONE;
> +}
> +
> +/*
> + * 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;
> +  char *ret = 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)
> +     {
> +       ret = kv->val;
> +       break;
> +     }
> +    }
> +
> +  if (last != NULL)
> +    {
> +      if (idx == entry->nkeyvals)
> +     *last = -1;
> +      else
> +     *last = idx;
> +    }
> +
> +  return ret;
> +}
> +
> +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 == GRUB_VERCMP_SAME)
> +     return grub_error (GRUB_ERR_BAD_ARGUMENT, "duplicate file");
> +
> +      if (rc == GRUB_VERCMP_NEWER)
> +     {
> +       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 grub_err_t
> +bls_read_entry (grub_file_t f, grub_blsuki_entry_t *entry)
> +{
> +  grub_err_t err = GRUB_ERR_NONE;
> +
> +  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');
> +
> +      err = blsuki_add_keyval (entry, buf, separator);
> +      grub_free (buf);
> +      if (err != GRUB_ERR_NONE)
> +     break;
> +    }
> +
> +  return err;
> +}
> +
> +struct read_entry_info
> +{
> +  const char *devid;
> +  const char *dirname;
> +  grub_file_t file;
> +};
> +

What is a difference between bls_read_entry() and blsuki_read_entry()?
I think every function, except really basic ones, should have shorter or
longer descriptions what they do.

> +static int
> +blsuki_read_entry (const char *filename,
> +                const struct grub_dirhook_info *dirhook_info __attribute__ 
> ((__unused__)),
> +                void *data)
> +{
> +  grub_size_t m = 0, n, ext_len = 5;

What is n? What is m?

> +  grub_err_t err;
> +  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] == '.')

It seems to me you are skipping all files starting with '.' in name.
Is it intentional?

> +     return 0;
> +
> +      if (n <= 5)

Why 5? I think it begs for comment.

> +     return 0;
> +
> +      if (grub_strcmp (filename + n - ext_len, ".conf") != 0)

Are you sure it does not underflow? What will happen if the file name is
shorter than ".conf"?

> +     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;

When I see this I think the functions require more comments in the code...

> +  entry->filename = grub_strndup (filename + m, n);
> +  if (entry->filename == NULL)
> +    {
> +      grub_free (entry);
> +      goto finish;
> +    }
> +
> +  err = bls_read_entry (f, entry);
> +
> +  if (err == GRUB_ERR_NONE)
> +    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;

Should not you define -1 as a constant?

> +  char *val;
> +  int nlist = 0;
> +  char **list;
> +
> +  list = grub_malloc (sizeof (char *));
> +  if (list == NULL)
> +    return NULL;
> +  list[0] = NULL;

If you use grub_zalloc() you can drop this NULL assignment.

> +  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;

Ditto.

> +  }
> +
> +  if (nlist == 0)
> +    {
> +      grub_free (list);
> +      return NULL;
> +    }
> +
> +  if (num != NULL)
> +    *num = nlist;
> +
> +  return list;
> +}
> +
> +static char *
> +blsuki_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;
> +  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, " ");

Wrong indention.

> +  return buffer;
> +}
> +
> +static char *
> +blsuki_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 = blsuki_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 = blsuki_field_append (is_env_var, buffer, start, end);
> +           is_env_var = false;
> +           start = value;
> +           if (*start == ' ')
> +             start++;
> +         }
> +     }
> +
> +      end = value;
> +      value++;
> +    }
> +
> +  if (start != end)
> +    {
> +      buffer = blsuki_field_append (is_env_var, buffer, start, end);
> +      if (buffer == NULL)
> +     return NULL;
> +    }
> +
> +  return buffer;
> +}
> +
> +static char **
> +blsuki_early_initrd_list (const char *initrd)
> +{
> +  int nlist = 0;
> +  char **list = NULL;
> +  char *separator;
> +  char *tmp;
> +
> +  while ((separator = grub_strchr (initrd, ' ')))

What about TABs as separators? Are they allowed? I think they are...

> +    {
> +      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
> +bls_create_entry (grub_blsuki_entry_t *entry)
> +{
> +  int argc = 0;
> +  const char **argv = NULL;
> +  char *title = NULL;
> +  char *clinux = NULL;
> +  char *linux_path = NULL;
> +  grub_size_t linux_size;
> +  char *options = NULL;
> +  char **initrds = NULL;
> +  char *initrd = NULL;
> +  grub_size_t initrd_size;
> +  const char *early_initrd = NULL;
> +  char **early_initrds = NULL;
> +  char *initrd_prefix = NULL;
> +  char *prefix = NULL;
> +  char *devicetree = NULL;
> +  char *dt = NULL;
> +  grub_size_t 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;

Do we need all these variables?

> +  int i;
> +  grub_size_t size = 0;
> +  bool add_dt_prefix = false;
> +  bool savedefault;
> +
> +  linux_path = blsuki_get_val (entry, "linux", NULL);
> +  if (linux_path == NULL)
> +    {
> +      grub_dprintf ("blsuki", "Skipping file %s with no 'linux' key.\n", 
> entry->filename);
> +      goto finish;
> +    }
> +
> +  if (grub_add (sizeof ("linux "), grub_strlen (linux_path), &linux_size))
> +    {
> +      grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating 
> linux buffer size");
> +      goto finish;
> +    }
> +  clinux = grub_malloc (linux_size);
> +  if (clinux == NULL)
> +    goto finish;
> +  tmp = clinux;
> +  tmp = grub_stpcpy (tmp, "linux");
> +  tmp = grub_stpcpy (tmp, " ");
> +  tmp = grub_stpcpy (tmp, linux_path);
> +
> +  /* 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';

This code looks strange. Why do not you compare 5 last characters of the
string with ".conf" and cut it when it matches?

> +  title = blsuki_get_val (entry, "title", NULL);
> +  options = blsuki_expand_val (blsuki_get_val (entry, "options", NULL));
> +
> +  if (options == NULL)
> +    options = blsuki_expand_val (grub_env_get ("default_kernelopts"));
> +
> +  initrds = blsuki_make_list (entry, "initrd", NULL);
> +
> +  devicetree = blsuki_expand_val (blsuki_get_val (entry, "devicetree", 
> NULL));
> +
> +  if (devicetree == NULL)
> +    {
> +      devicetree = blsuki_expand_val (grub_env_get ("devicetree"));
> +      add_dt_prefix = true;
> +    }
> +
> +  hotkey = blsuki_get_val (entry, "grub_hotkey", NULL);
> +  users = blsuki_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)
> +    goto finish;
> +  argv[0] = title ? title : linux_path;
> +  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 = blsuki_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], '/');

grub_strrchr() may return NULL...

> +       initrd_prefix = grub_strndup (initrds[0], initrd_prefix - initrds[0] 
> + 1);
> +     }
> +      else
> +     {
> +       initrd_prefix = grub_strrchr (linux_path, '/');

Ditto...

> +       initrd_prefix = grub_strndup (linux_path, initrd_prefix - linux_path 
> + 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 (" "), &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 (" "), &initrd_size) ||

You know you add 2 here...

> +           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)
> +     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, " ");
> +       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, " ");
> +       tmp = grub_stpcpy (tmp, initrds[i]);
> +     }
> +      tmp = grub_stpcpy (tmp, "\n");
> +    }
> +
> +  if (devicetree != NULL)
> +    {
> +      if (add_dt_prefix == true)
> +     {
> +       prefix = grub_strrchr (linux_path, '/');
> +       prefix = grub_strndup (linux_path, prefix - linux_path + 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_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)
> +     goto finish;
> +      tmp = dt;
> +      tmp = grub_stpcpy (dt, "devicetree");
> +      tmp = grub_stpcpy (tmp, " ");
> +      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 ("%s%s%s%s\n"
> +                     "%s%s",
> +                     savedefault ? "savedefault\n" : "",
> +                     clinux, options ? " " : "", options ? options : "",
> +                     initrd ? initrd : "", dt ? dt : "");
> +
> +  grub_normal_add_menu_entry (argc, argv, classes, id, users, hotkey, NULL, 
> src, 0, entry);

I think I would split this function to smaller parts for readability...

> + finish:
> +  grub_free (clinux);
> +  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;
> +};
> +
> +static grub_err_t
> +blsuki_set_find_entry_info (struct find_entry_info *info, const char 
> *dirname, const char *devid)
> +{
> +  grub_device_t dev;
> +  grub_fs_t fs;
> +
> +  if (info == NULL)
> +    return grub_error (GRUB_ERR_BAD_ARGUMENT, "info parameter is not set");
> +
> +  if (devid == NULL)
> +    {
> +#ifdef GRUB_MACHINE_EMU
> +      devid = "host";
> +#else
> +      devid = grub_env_get ("root");
> +#endif

Is the EMU implementation fully functional? If yes I would move it to
separate patch to not confuse a reader. If not I would drop it...

> +      if (devid == NULL)
> +     return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't 
> set"), "root");
> +    }
> +
> +  /* Check that we aren't closing and opening the same device. */
> +  if (info->dev != NULL && grub_strcmp (info->devid, devid) != 0)
> +    {
> +      grub_device_close (info->dev);
> +      info->dev = NULL;
> +    }
> +  /* If we are using the same device, then we can skip this step and only 
> set the directory. */
> +  if (info->dev == NULL)
> +    {
> +      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)
> +     {
> +       grub_device_close (dev);
> +       return grub_errno;
> +     }
> +
> +      info->devid = devid;
> +      info->dev = dev;
> +      info->fs = fs;
> +    }
> +
> +  info->dirname = dirname;
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +/* info: the filesystem object the file is on. */
> +static void
> +blsuki_find_entry (struct find_entry_info *info, bool enable_fallback)
> +{
> +  struct read_entry_info read_entry_info;
> +  grub_fs_t dir_fs = NULL;
> +  grub_device_t dir_dev = NULL;
> +  int fallback = 0;

s/int/bool/

> +  int r = 0;

You can drop this assignment.

> + read_fallback:

No, please use for/while for the loop here...

> +  read_entry_info.file = NULL;
> +  read_entry_info.dirname = info->dirname;
> +
> +  grub_dprintf ("blsuki", "scanning dir: %s\n", info->dirname);
> +  dir_dev = info->dev;
> +  dir_fs = info->fs;
> +  read_entry_info.devid = info->devid;
> +
> +  r = dir_fs->fs_dir (dir_dev, read_entry_info.dirname, blsuki_read_entry,
> +                   &read_entry_info);
> +  if (r != 0)
> +    {
> +      grub_dprintf ("blsuki", "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 (entries == NULL && fallback == 0 && enable_fallback == true)
> +    {
> +      blsuki_set_find_entry_info (info, GRUB_BLS_CONFIG_PATH, NULL);
> +      grub_dprintf ("blsuki", "Entries weren't found in %s, fallback to 
> %s\n",
> +                 read_entry_info.dirname, info->dirname);
> +      fallback = 1;
> +      goto read_fallback;
> +    }
> +}
> +
> +static grub_err_t
> +blsuki_load_entries (char *path, bool enable_fallback)
> +{
> +  grub_size_t len;
> +  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)

What if len < 5?

> +     {
> +       rei.file = grub_file_open (path, GRUB_FILE_TYPE_CONFIG);
> +       if (rei.file == NULL)
> +         return grub_errno;
> +
> +       /* blsuki_read_entry() closes the file. */
> +       return blsuki_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 (dir == NULL)
> +    dir = (char *) GRUB_BLS_CONFIG_PATH;
> +
> +  r = blsuki_set_find_entry_info (&info, dir, devid);
> +  if (r == GRUB_ERR_NONE)
> +    blsuki_find_entry (&info, enable_fallback);
> +
> +  if (info.dev != NULL)
> +    grub_device_close (info.dev);
> +
> +  return r;
> +}
> +
> +static bool
> +blsuki_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)

This check is unreliable. Please take a look at commit ac8a37dda (net/http:
Allow use of non-standard TCP/IP ports) how it should be done properly.

> +    {
> +      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 && blsuki_is_default_entry (def_entry, 
> entry, idx) == true) ||
> +       (show_non_default == true && blsuki_is_default_entry (def_entry, 
> entry, idx) == false) ||
> +       (entry_id != NULL && grub_strcmp (entry_id, entry->filename) == 0))
> +     {
> +       bls_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 enable_fallback = false;
> +  bool show_default = false;
> +  bool show_non_default = false;
> +  bool all = true;
> +
> +  if (state[0].set)
> +    path = state[0].arg;
> +  if (state[1].set)
> +    enable_fallback = true;
> +  if (state[2].set)
> +    {
> +      show_default = true;
> +      all = false;
> +    }
> +  if (state[3].set)
> +    {
> +      show_non_default = true;
> +      all = false;
> +    }
> +  if (state[4].set)
> +    {
> +      entry_id = state[4].arg;
> +      all = false;
> +    }
> +  if (all == true)
> +    {
> +      show_default = true;
> +      show_non_default = true;
> +    }
> +
> +  err = blsuki_load_entries (path, enable_fallback);
> +  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] [-f|--enable-fallback] DIR 
> [-d|--show-default] [-n|--show-non-default] [-e|--entry] FILE"),
> +                               N_("Import Boot Loader Specification 
> snippets."),
> +                               bls_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 3bf9fe2e4..f3c86dc7f 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);
> @@ -204,7 +204,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..726e2321b
> --- /dev/null
> +++ b/grub-core/lib/vercmp.c

I would move this code to separate patch. Even if it will be unused initially.

> @@ -0,0 +1,317 @@
> +/*
> + *  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: a is older than b
> + */
> +grub_err_t
> +grub_vercmp (const char *a, const char *b, int *ret)

Where is this algorithm come from? I suppose you copied it from
somewhere. If yes please mention source here. If not I think you
should copy the code from somewhere to not invent the wheel.

> +{
> +  char oldch1, oldch2;
> +  char *abuf, *bbuf;
> +  char *str1, *str2;
> +  char *one, *two;
> +  int rc;
> +  bool isnum;
> +
> +  if (ret == NULL)
> +    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("return parameter is not 
> set"));
> +  *ret = 0;
> +
> +  grub_dprintf ("blscfg", "%s comparing %s and %s\n", __func__, a, b);

This is generic thing not BLS one... And it seems to me this message is
not very helpful...

Daniel

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

Reply via email to