This can be especially helpful, as the Fedora version of the blscfg actually made use of positional arguments, but current implementation switched to parameters. For example what used to be "blscfg (hd0,gpt2)/..." now should be "blscfg --path (hd0,gpt2)/...)". In case of old configs/scripts still supplying positional arguments we will now error out instead of just ignoring them and falling back to defaults silently. Signed-off-by: Radoslav Kolev <radoslav.kolev@suse.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
1504 lines
37 KiB
C
1504 lines
37 KiB
C
/*
|
|
* 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 <filevercmp.h>
|
|
|
|
#ifdef GRUB_MACHINE_EFI
|
|
#include <grub/efi/efi.h>
|
|
#include <grub/efi/disk.h>
|
|
#include <grub/efi/pe32.h>
|
|
#endif
|
|
|
|
#ifdef GRUB_MACHINE_EMU
|
|
#include <grub/emu/misc.h>
|
|
#define GRUB_BOOT_DEVICE "/boot"
|
|
#else
|
|
#define GRUB_BOOT_DEVICE ""
|
|
#endif
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
#define GRUB_BLS_CONFIG_PATH "/loader/entries/"
|
|
#define GRUB_UKI_CONFIG_PATH "/EFI/Linux"
|
|
|
|
#define BLS_EXT_LEN (sizeof (".conf") - 1)
|
|
#define UKI_EXT_LEN (sizeof (".efi") - 1)
|
|
|
|
/*
|
|
* It is highly unlikely to ever receive a large amount of keyval pairs. A
|
|
* limit of 10000 is more than enough.
|
|
*/
|
|
#define BLSUKI_KEYVALS_MAX 10000
|
|
/*
|
|
* The only sections we read are ".cmdline" and ".osrel". The ".cmdline"
|
|
* section has a size limit of 4096 and it would be very unlikely for the size
|
|
* of the ".osrel" section to be 5 times larger than 4096.
|
|
*/
|
|
#define UKI_SECTION_SIZE_MAX (5 * 4096)
|
|
|
|
enum blsuki_cmd_type
|
|
{
|
|
BLSUKI_BLS_CMD,
|
|
BLSUKI_UKI_CMD,
|
|
};
|
|
|
|
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}
|
|
};
|
|
|
|
#ifdef GRUB_MACHINE_EFI
|
|
static const struct grub_arg_option uki_opt[] =
|
|
{
|
|
{"path", 'p', 0, N_("Specify path to find UKI entries."), N_("DIR"), ARG_TYPE_PATHNAME},
|
|
{"enable-fallback", 'f', 0, "Fallback to the default BLS path if --path fails to find UKI entries.", 0, ARG_TYPE_NONE},
|
|
{"show-default", 'd', 0, N_("Allow the default UKI entry to be added to the GRUB menu."), 0, ARG_TYPE_NONE},
|
|
{"show-non-default", 'n', 0, N_("Allow the non-default UKI entries to be added to the GRUB menu."), 0, ARG_TYPE_NONE},
|
|
{"entry", 'e', 0, N_("Allow specific UKI entries to be added to the GRUB menu."), N_("FILE"), ARG_TYPE_FILE},
|
|
{0, 0, 0, 0, 0, 0}
|
|
};
|
|
#endif
|
|
|
|
struct keyval
|
|
{
|
|
const char *key;
|
|
char *val;
|
|
};
|
|
|
|
struct read_entry_info
|
|
{
|
|
const char *devid;
|
|
const char *dirname;
|
|
enum blsuki_cmd_type cmd_type;
|
|
grub_file_t file;
|
|
};
|
|
|
|
struct find_entry_info
|
|
{
|
|
const char *dirname;
|
|
const char *devid;
|
|
grub_device_t dev;
|
|
grub_fs_t fs;
|
|
};
|
|
|
|
static grub_blsuki_entry_t *entries = NULL;
|
|
|
|
#define FOR_BLSUKI_ENTRIES(var) FOR_LIST_ELEMENTS (var, entries)
|
|
|
|
/*
|
|
* BLS appears to make paths relative to the filesystem that snippets are
|
|
* on, not /. Attempt to cope.
|
|
*/
|
|
static char *blsuki_update_boot_device (char *tmp)
|
|
{
|
|
#ifdef GRUB_MACHINE_EMU
|
|
static int separate_boot = -1;
|
|
char *ret;
|
|
|
|
if (separate_boot != -1)
|
|
goto probed;
|
|
|
|
separate_boot = 0;
|
|
|
|
ret = grub_make_system_path_relative_to_its_root (GRUB_BOOT_DEVICE);
|
|
|
|
if (ret != NULL && ret[0] == '\0')
|
|
separate_boot = 1;
|
|
|
|
probed:
|
|
if (separate_boot == 0)
|
|
return tmp;
|
|
#endif
|
|
|
|
return grub_stpcpy (tmp, GRUB_BOOT_DEVICE);
|
|
}
|
|
|
|
/*
|
|
* This function will add a new keyval pair to a list of keyvals stored in the
|
|
* entry parameter.
|
|
*/
|
|
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 (new_n > BLSUKI_KEYVALS_MAX)
|
|
return grub_error (GRUB_ERR_BAD_NUMBER, "too many keyval pairs");
|
|
|
|
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 set to -1 to the last parameter the first
|
|
* time, and pass the same pointer through each time after, and it'll return
|
|
* them in sorted order.
|
|
*/
|
|
static char *
|
|
blsuki_get_val (grub_blsuki_entry_t *entry, const char *keyname, int *last)
|
|
{
|
|
int idx, start = (last != NULL) ? (*last + 1) : 0;
|
|
struct keyval *kv = NULL;
|
|
char *ret = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Add a new grub_blsuki_entry_t struct to the entries list and sort it's
|
|
* position on the list.
|
|
*/
|
|
static grub_err_t
|
|
blsuki_add_entry (grub_blsuki_entry_t *entry)
|
|
{
|
|
grub_blsuki_entry_t *e, *last = NULL;
|
|
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)
|
|
{
|
|
rc = filevercmp (entry->filename, e->filename);
|
|
if (rc == 0)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("duplicate file: `%s'"), entry->filename);
|
|
|
|
if (rc > 0)
|
|
{
|
|
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 if (last != NULL)
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* This function parses each line of a BLS config file to obtain the key value
|
|
* pairs that will be used to setup the GRUB menu entries. The key value pair
|
|
* will be stored in a list in the entry parameter.
|
|
*/
|
|
static grub_err_t
|
|
bls_parse_keyvals (grub_file_t f, grub_blsuki_entry_t *entry)
|
|
{
|
|
grub_err_t err = GRUB_ERR_NONE;
|
|
|
|
for (;;)
|
|
{
|
|
char *line, *key, *val;
|
|
|
|
line = grub_file_getline (f);
|
|
if (line == NULL)
|
|
break;
|
|
|
|
key = grub_strtok_r (line, " \t", &val);
|
|
if (key == NULL)
|
|
{
|
|
grub_free (line);
|
|
break;
|
|
}
|
|
if (*key == '#')
|
|
{
|
|
grub_free (line);
|
|
continue;
|
|
}
|
|
|
|
while (*val == ' ' || *val == '\t')
|
|
val++;
|
|
|
|
if (*val == '\0')
|
|
{
|
|
grub_free (line);
|
|
break;
|
|
}
|
|
|
|
err = blsuki_add_keyval (entry, key, val);
|
|
grub_free (line);
|
|
if (err != GRUB_ERR_NONE)
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef GRUB_MACHINE_EFI
|
|
/*
|
|
* This function searches for the .cmdline, .osrel, and .linux sections of a
|
|
* UKI. We only need to store the data for the .cmdline and .osrel sections,
|
|
* but we also need to verify that the .linux section exists.
|
|
*/
|
|
static grub_err_t
|
|
uki_parse_keyvals (grub_file_t f, grub_blsuki_entry_t *entry)
|
|
{
|
|
struct grub_msdos_image_header *dos = NULL;
|
|
struct grub_pe_image_header *pe = NULL;
|
|
grub_off_t section_offset = 0;
|
|
struct grub_pe32_section_table *section = NULL;
|
|
struct grub_pe32_coff_header *coff_header = NULL;
|
|
char *val = NULL;
|
|
char *key = NULL;
|
|
const char *target[] = {".cmdline", ".osrel", ".linux", NULL};
|
|
bool has_linux = false;
|
|
grub_err_t err = GRUB_ERR_NONE;
|
|
|
|
dos = grub_zalloc (sizeof (*dos));
|
|
if (dos == NULL)
|
|
return grub_errno;
|
|
if (grub_file_read (f, dos, sizeof (*dos)) < (grub_ssize_t) sizeof (*dos))
|
|
{
|
|
err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read UKI image header");
|
|
goto finish;
|
|
}
|
|
if (dos->msdos_magic != GRUB_PE32_MAGIC)
|
|
{
|
|
err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "plain image kernel is not supported");
|
|
goto finish;
|
|
}
|
|
|
|
grub_dprintf ("blsuki", "PE/COFF header @ %08x\n", dos->pe_image_header_offset);
|
|
pe = grub_zalloc (sizeof (*pe));
|
|
if (pe == NULL)
|
|
{
|
|
err = grub_errno;
|
|
goto finish;
|
|
}
|
|
if (grub_file_seek (f, dos->pe_image_header_offset) == (grub_off_t) -1 ||
|
|
grub_file_read (f, pe, sizeof (*pe)) != sizeof (*pe))
|
|
{
|
|
err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read COFF image header");
|
|
goto finish;
|
|
}
|
|
if (pe->optional_header.magic != GRUB_PE32_NATIVE_MAGIC)
|
|
{
|
|
err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "non-native image not supported");
|
|
goto finish;
|
|
}
|
|
|
|
coff_header = &(pe->coff_header);
|
|
section_offset = dos->pe_image_header_offset + sizeof (*pe);
|
|
|
|
for (int i = 0; i < coff_header->num_sections; i++)
|
|
{
|
|
section = grub_zalloc (sizeof (*section));
|
|
if (section == NULL)
|
|
{
|
|
err = grub_errno;
|
|
goto finish;
|
|
}
|
|
|
|
if (grub_file_seek (f, section_offset) == (grub_off_t) -1 ||
|
|
grub_file_read (f, section, sizeof (*section)) != sizeof (*section))
|
|
{
|
|
err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read section header");
|
|
goto finish;
|
|
}
|
|
|
|
key = grub_strndup (section->name, 8);
|
|
if (key == NULL)
|
|
{
|
|
err = grub_errno;
|
|
goto finish;
|
|
}
|
|
|
|
for (int j = 0; target[j] != NULL; j++)
|
|
{
|
|
if (grub_strcmp (key, target[j]) == 0)
|
|
{
|
|
/*
|
|
* We don't need to read the contents of the .linux PE section, but we
|
|
* should verify that the section exists.
|
|
*/
|
|
if (grub_strcmp (key, ".linux") == 0)
|
|
{
|
|
has_linux = true;
|
|
break;
|
|
}
|
|
|
|
if (section->raw_data_size > UKI_SECTION_SIZE_MAX)
|
|
{
|
|
err = grub_error (GRUB_ERR_BAD_NUMBER, "UKI section size is larger than expected");
|
|
goto finish;
|
|
}
|
|
|
|
val = grub_zalloc (section->raw_data_size);
|
|
if (val == NULL)
|
|
{
|
|
err = grub_errno;
|
|
goto finish;
|
|
}
|
|
|
|
if (grub_file_seek (f, section->raw_data_offset) == (grub_off_t) -1 ||
|
|
grub_file_read (f, val, section->raw_data_size) != (grub_ssize_t) section->raw_data_size)
|
|
{
|
|
err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read section");
|
|
goto finish;
|
|
}
|
|
|
|
err = blsuki_add_keyval (entry, key, val);
|
|
if (err != GRUB_ERR_NONE)
|
|
goto finish;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
section_offset += sizeof (*section);
|
|
grub_free (section);
|
|
grub_free (val);
|
|
grub_free (key);
|
|
section = NULL;
|
|
val = NULL;
|
|
key = NULL;
|
|
}
|
|
|
|
if (has_linux == false)
|
|
err = grub_error (GRUB_ERR_NO_KERNEL, "UKI is missing the '.linux' section");
|
|
|
|
finish:
|
|
grub_free (dos);
|
|
grub_free (pe);
|
|
grub_free (section);
|
|
grub_free (val);
|
|
grub_free (key);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* This function obtains the keyval pairs when the .osrel data is input into
|
|
* the osrel_ptr parameter and returns the keyval pair. Since we are using
|
|
* grub_strtok_r(), the osrel_ptr will be updated to the following line of
|
|
* osrel. This function returns NULL when it reaches the end of osrel.
|
|
*/
|
|
static char *
|
|
uki_read_osrel (char **osrel_ptr, char **val_ret)
|
|
{
|
|
char *key, *val;
|
|
grub_size_t val_size;
|
|
|
|
for (;;)
|
|
{
|
|
key = grub_strtok_r (NULL, "\n\r", osrel_ptr);
|
|
if (key == NULL)
|
|
return NULL;
|
|
|
|
/* Remove leading white space */
|
|
while (*key == ' ' || *key == '\t')
|
|
key++;
|
|
|
|
/* Skip commented lines */
|
|
if (*key == '#')
|
|
continue;
|
|
|
|
/* Split key/value */
|
|
key = grub_strtok_r (key, "=", &val);
|
|
if (key == NULL || *val == '\0')
|
|
continue;
|
|
|
|
/* Remove quotes from value */
|
|
val_size = grub_strlen (val);
|
|
if ((*val == '\"' && val[val_size - 1] == '\"') ||
|
|
(*val == '\'' && val[val_size - 1] == '\''))
|
|
{
|
|
val[val_size - 1] = '\0';
|
|
val++;
|
|
}
|
|
|
|
*val_ret = val;
|
|
break;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If a file hasn't already been opened, this function opens a BLS config file
|
|
* or UKI and initializes entry data before parsing keyvals and adding the entry
|
|
* to the list of BLS or UKI entries.
|
|
*/
|
|
static int
|
|
blsuki_read_entry (const char *filename,
|
|
const struct grub_dirhook_info *dirhook_info __attribute__ ((__unused__)),
|
|
void *data)
|
|
{
|
|
grub_size_t path_len = 0, ext_len = 0, filename_len;
|
|
grub_err_t err = GRUB_ERR_NONE;
|
|
char *p = NULL;
|
|
const char *ext = NULL;
|
|
grub_file_t f = NULL;
|
|
enum grub_file_type file_type = 0;
|
|
grub_blsuki_entry_t *entry;
|
|
struct read_entry_info *info = (struct read_entry_info *) data;
|
|
|
|
grub_dprintf ("blsuki", "filename: \"%s\"\n", filename);
|
|
|
|
filename_len = grub_strlen (filename);
|
|
|
|
if (info->cmd_type == BLSUKI_BLS_CMD)
|
|
{
|
|
ext = ".conf";
|
|
ext_len = BLS_EXT_LEN;
|
|
file_type = GRUB_FILE_TYPE_CONFIG;
|
|
}
|
|
#ifdef GRUB_MACHINE_EFI
|
|
else if (info->cmd_type == BLSUKI_UKI_CMD)
|
|
{
|
|
ext = ".efi";
|
|
ext_len = UKI_EXT_LEN;
|
|
file_type = GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE;
|
|
}
|
|
#endif
|
|
|
|
if (info->file != NULL)
|
|
f = info->file;
|
|
else
|
|
{
|
|
if (filename_len < ext_len ||
|
|
grub_strcmp (filename + filename_len - ext_len, ext) != 0)
|
|
return 0;
|
|
|
|
p = grub_xasprintf ("(%s)%s/%s", info->devid, info->dirname, filename);
|
|
f = grub_file_open (p, file_type);
|
|
grub_free (p);
|
|
if (f == NULL)
|
|
goto finish;
|
|
}
|
|
|
|
entry = grub_zalloc (sizeof (*entry));
|
|
if (entry == NULL)
|
|
goto finish;
|
|
|
|
/*
|
|
* If a file is opened before this function, the filename may have a path.
|
|
* Since the filename is used for the ID of the GRUB menu entry, we can
|
|
* remove the path.
|
|
*/
|
|
if (info->file != NULL)
|
|
{
|
|
char *slash;
|
|
|
|
slash = grub_strrchr (filename, '/');
|
|
if (slash != NULL)
|
|
path_len = slash - filename + 1;
|
|
}
|
|
filename_len -= path_len;
|
|
|
|
entry->filename = grub_strndup (filename + path_len, filename_len);
|
|
if (entry->filename == NULL)
|
|
{
|
|
grub_free (entry);
|
|
goto finish;
|
|
}
|
|
|
|
entry->dirname = grub_strdup (info->dirname);
|
|
if (entry->dirname == NULL)
|
|
{
|
|
grub_free (entry);
|
|
goto finish;
|
|
}
|
|
|
|
entry->devid = grub_strdup (info->devid);
|
|
if (entry->devid == NULL)
|
|
{
|
|
grub_free (entry);
|
|
goto finish;
|
|
}
|
|
|
|
if (info->cmd_type == BLSUKI_BLS_CMD)
|
|
err = bls_parse_keyvals (f, entry);
|
|
#ifdef GRUB_MACHINE_EFI
|
|
else if (info->cmd_type == BLSUKI_UKI_CMD)
|
|
err = uki_parse_keyvals (f, entry);
|
|
#endif
|
|
|
|
if (err == GRUB_ERR_NONE)
|
|
blsuki_add_entry (entry);
|
|
else
|
|
grub_free (entry);
|
|
|
|
finish:
|
|
if (f != NULL)
|
|
grub_file_close (f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function returns a list of values that had the same key in the BLS
|
|
* config file or UKI. The number of entries in this list is returned by the len
|
|
* parameter.
|
|
*/
|
|
static char **
|
|
blsuki_make_list (grub_blsuki_entry_t *entry, const char *key, int *len)
|
|
{
|
|
int last = -1;
|
|
char *val;
|
|
int nlist = 0;
|
|
char **list;
|
|
|
|
list = grub_zalloc (sizeof (char *));
|
|
if (list == NULL)
|
|
return NULL;
|
|
|
|
while (1)
|
|
{
|
|
char **new;
|
|
|
|
/*
|
|
* Since the same key might appear more than once, the 'last' variable
|
|
* starts at -1 and increments to indicate the last index in the list
|
|
* we obtained from blsuki_get_val().
|
|
*/
|
|
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 (len != NULL)
|
|
*len = nlist;
|
|
|
|
return list;
|
|
}
|
|
|
|
/*
|
|
* This function appends a field to the end of a buffer. If the field given is
|
|
* an enviornmental variable, it gets the value stored for that variable and
|
|
* appends that to the buffer instead.
|
|
*/
|
|
static char *
|
|
blsuki_field_append (bool is_env_var, char *buffer, const char *start, const char *end)
|
|
{
|
|
char *tmp;
|
|
const char *field;
|
|
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), 1, &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;
|
|
}
|
|
|
|
/*
|
|
* This function takes a value string, checks for environmental variables, and
|
|
* returns the value string with all environmental variables replaced with the
|
|
* value stored in the variable.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* This function returns a string with the command to load a linux kernel with
|
|
* kernel command-line options based on what was specified in the BLS config
|
|
* file.
|
|
*/
|
|
static char *
|
|
bls_get_linux (grub_blsuki_entry_t *entry)
|
|
{
|
|
char *linux_path;
|
|
char *linux_cmd = NULL;
|
|
char *options = NULL;
|
|
char *tmp;
|
|
grub_size_t size;
|
|
|
|
linux_path = blsuki_get_val (entry, "linux", NULL);
|
|
options = blsuki_expand_val (blsuki_get_val (entry, "options", NULL));
|
|
|
|
if (grub_add (sizeof ("linux " GRUB_BOOT_DEVICE), grub_strlen (linux_path), &size))
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected while calculating linux buffer size");
|
|
goto finish;
|
|
}
|
|
|
|
if (options != NULL)
|
|
{
|
|
if (grub_add (size, grub_strlen (options), &size) ||
|
|
grub_add (size, 1, &size))
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected while calculating linux buffer size");
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
linux_cmd = grub_malloc (size);
|
|
if (linux_cmd == NULL)
|
|
goto finish;
|
|
|
|
tmp = linux_cmd;
|
|
tmp = grub_stpcpy (tmp, "linux ");
|
|
tmp = blsuki_update_boot_device (tmp);
|
|
tmp = grub_stpcpy (tmp, linux_path);
|
|
if (options != NULL)
|
|
{
|
|
tmp = grub_stpcpy (tmp, " ");
|
|
tmp = grub_stpcpy (tmp, options);
|
|
}
|
|
|
|
tmp = grub_stpcpy (tmp, "\n");
|
|
|
|
finish:
|
|
grub_free (options);
|
|
return linux_cmd;
|
|
}
|
|
|
|
/*
|
|
* This function returns a string with the command to load all initrds for a
|
|
* linux kernel image based on the list provided by the BLS config file.
|
|
*/
|
|
static char *
|
|
bls_get_initrd (grub_blsuki_entry_t *entry)
|
|
{
|
|
char **initrd_list;
|
|
char *initrd_cmd = NULL;
|
|
char *tmp;
|
|
grub_size_t size;
|
|
int i;
|
|
|
|
initrd_list = blsuki_make_list (entry, "initrd", NULL);
|
|
if (initrd_list != NULL)
|
|
{
|
|
size = sizeof ("initrd");
|
|
|
|
for (i = 0; initrd_list != NULL && initrd_list[i] != NULL; i++)
|
|
{
|
|
if (grub_add (size, sizeof (" " GRUB_BOOT_DEVICE) - 1, &size) ||
|
|
grub_add (size, grub_strlen (initrd_list[i]), &size))
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating initrd buffer size");
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
if (grub_add (size, 1, &size))
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating initrd buffer size");
|
|
goto finish;
|
|
}
|
|
|
|
initrd_cmd = grub_malloc (size);
|
|
if (initrd_cmd == NULL)
|
|
goto finish;
|
|
|
|
tmp = grub_stpcpy (initrd_cmd, "initrd");
|
|
for (i = 0; initrd_list != NULL && initrd_list[i] != NULL; i++)
|
|
{
|
|
grub_dprintf ("blsuki", "adding initrd %s\n", initrd_list[i]);
|
|
tmp = grub_stpcpy (tmp, " ");
|
|
tmp = blsuki_update_boot_device (tmp);
|
|
tmp = grub_stpcpy (tmp, initrd_list[i]);
|
|
}
|
|
tmp = grub_stpcpy (tmp, "\n");
|
|
}
|
|
|
|
finish:
|
|
grub_free (initrd_list);
|
|
return initrd_cmd;
|
|
}
|
|
|
|
/*
|
|
* This function returns a string with the command to load a device tree blob
|
|
* from the BLS config file.
|
|
*/
|
|
static char *
|
|
bls_get_devicetree (grub_blsuki_entry_t *entry)
|
|
{
|
|
char *dt_path;
|
|
char *dt_cmd = NULL;
|
|
char *tmp;
|
|
grub_size_t size;
|
|
|
|
dt_path = blsuki_expand_val (blsuki_get_val (entry, "devicetree", NULL));
|
|
if (dt_path != NULL)
|
|
{
|
|
if (grub_add (sizeof ("devicetree " GRUB_BOOT_DEVICE), grub_strlen (dt_path), &size) ||
|
|
grub_add (size, 1, &size))
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_RANGE, "overflow detected calculating device tree buffer size");
|
|
return NULL;
|
|
}
|
|
|
|
dt_cmd = grub_malloc (size);
|
|
if (dt_cmd == NULL)
|
|
return NULL;
|
|
|
|
tmp = dt_cmd;
|
|
tmp = grub_stpcpy (dt_cmd, "devicetree ");
|
|
tmp = blsuki_update_boot_device (tmp);
|
|
tmp = grub_stpcpy (tmp, dt_path);
|
|
tmp = grub_stpcpy (tmp, "\n");
|
|
}
|
|
|
|
return dt_cmd;
|
|
}
|
|
|
|
/*
|
|
* This function puts together all of the commands generated from the contents
|
|
* of the BLS config file and creates a new entry in the GRUB boot menu.
|
|
*/
|
|
static void
|
|
bls_create_entry (grub_blsuki_entry_t *entry)
|
|
{
|
|
int argc = 0;
|
|
const char **argv = NULL;
|
|
char *title = NULL;
|
|
char *linux_path = NULL;
|
|
char *linux_cmd = NULL;
|
|
char *initrd_cmd = NULL;
|
|
char *dt_cmd = NULL;
|
|
char *id = entry->filename;
|
|
grub_size_t id_len;
|
|
char *hotkey = NULL;
|
|
char *users = NULL;
|
|
char **classes = NULL;
|
|
char **args = NULL;
|
|
char *src = NULL;
|
|
int i;
|
|
grub_size_t size;
|
|
bool blsuki_save_default;
|
|
|
|
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;
|
|
}
|
|
|
|
id_len = grub_strlen (id);
|
|
if (id_len >= BLS_EXT_LEN && grub_strcmp (id + id_len - BLS_EXT_LEN, ".conf") == 0)
|
|
id[id_len - BLS_EXT_LEN] = '\0';
|
|
|
|
title = blsuki_get_val (entry, "title", NULL);
|
|
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++;
|
|
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 != NULL) ? title : linux_path;
|
|
for (i = 1; i < argc; i++)
|
|
argv[i] = args[i - 1];
|
|
argv[argc] = NULL;
|
|
|
|
linux_cmd = bls_get_linux (entry);
|
|
if (linux_cmd == NULL)
|
|
goto finish;
|
|
|
|
initrd_cmd = bls_get_initrd (entry);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
goto finish;
|
|
|
|
dt_cmd = bls_get_devicetree (entry);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
goto finish;
|
|
|
|
blsuki_save_default = grub_env_get_bool ("blsuki_save_default", false);
|
|
src = grub_xasprintf ("%s%s%s%s",
|
|
blsuki_save_default ? "savedefault\n" : "",
|
|
linux_cmd, initrd_cmd ? initrd_cmd : "",
|
|
dt_cmd ? dt_cmd : "");
|
|
|
|
grub_normal_add_menu_entry (argc, argv, classes, id, users, hotkey, NULL, src, 0, entry);
|
|
|
|
finish:
|
|
grub_free (linux_cmd);
|
|
grub_free (dt_cmd);
|
|
grub_free (initrd_cmd);
|
|
grub_free (classes);
|
|
grub_free (args);
|
|
grub_free (argv);
|
|
grub_free (src);
|
|
}
|
|
|
|
#ifdef GRUB_MACHINE_EFI
|
|
/*
|
|
* This function puts together the section data received from the UKI and
|
|
* generates a new entry in the GRUB boot menu.
|
|
*/
|
|
static void
|
|
uki_create_entry (grub_blsuki_entry_t *entry)
|
|
{
|
|
const char **argv = NULL;
|
|
char *id = entry->filename;
|
|
char *title = NULL;
|
|
char *options = NULL;
|
|
char *osrel, *osrel_line;
|
|
char *key = NULL;
|
|
char *value = NULL;
|
|
char *src = NULL;
|
|
bool blsuki_save_default;
|
|
|
|
/*
|
|
* Although .osrel is listed as optional in the UKI specification, the .osrel
|
|
* section is needed to generate the GRUB menu entry title.
|
|
*/
|
|
osrel = blsuki_get_val (entry, ".osrel", NULL);
|
|
if (osrel == NULL)
|
|
{
|
|
grub_dprintf ("blsuki", "Skipping file %s with no '.osrel' key.\n", entry->filename);
|
|
goto finish;
|
|
}
|
|
|
|
osrel_line = osrel;
|
|
while ((key = uki_read_osrel (&osrel_line, &value)) != NULL)
|
|
{
|
|
if (grub_strcmp ("PRETTY_NAME", key) == 0)
|
|
{
|
|
title = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
options = blsuki_get_val (entry, ".cmdline", NULL);
|
|
|
|
argv = grub_zalloc (2 * sizeof (char *));
|
|
if (argv == NULL)
|
|
goto finish;
|
|
argv[0] = title;
|
|
|
|
blsuki_save_default = grub_env_get_bool ("blsuki_save_default", false);
|
|
src = grub_xasprintf ("%schainloader (%s)%s/%s%s%s\n",
|
|
blsuki_save_default ? "savedefault\n" : "",
|
|
entry->devid, entry->dirname,
|
|
entry->filename,
|
|
(options != NULL) ? " " : "",
|
|
(options != NULL) ? options : "");
|
|
|
|
grub_normal_add_menu_entry (1, argv, NULL, id, NULL, NULL, NULL, src, 0, entry);
|
|
|
|
finish:
|
|
grub_free (argv);
|
|
grub_free (src);
|
|
grub_free (options);
|
|
grub_free (osrel);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* This function fills a find_entry_info struct passed in by the info parameter.
|
|
* If the dirname or devid parameters are set to NULL, the dirname and devid
|
|
* fields in the info parameter will be set to default values. If info already
|
|
* has a value in the dev fields, we can compare it to the value passed in by
|
|
* the devid parameter or the default devid to see if we need to open a new
|
|
* device.
|
|
*/
|
|
static grub_err_t
|
|
blsuki_set_find_entry_info (struct find_entry_info *info, const char *dirname, const char *devid, enum blsuki_cmd_type cmd_type)
|
|
{
|
|
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)
|
|
{
|
|
if (cmd_type == BLSUKI_BLS_CMD)
|
|
{
|
|
#ifdef GRUB_MACHINE_EMU
|
|
devid = "host";
|
|
#else
|
|
devid = grub_env_get ("root");
|
|
#endif
|
|
}
|
|
#ifdef GRUB_MACHINE_EFI
|
|
else if (cmd_type == BLSUKI_UKI_CMD)
|
|
{
|
|
grub_efi_loaded_image_t *image = grub_efi_get_loaded_image (grub_efi_image_handle);
|
|
|
|
if (image == NULL)
|
|
return grub_error (GRUB_ERR_BAD_DEVICE, N_("unable to find boot device"));
|
|
devid = grub_efidisk_get_device_name (image->device_handle);
|
|
}
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* This function searches for BLS config files and UKIs based on the data in the
|
|
* info parameter. If the fallback option is enabled, the default location will
|
|
* be checked for BLS config files or UKIs if the first attempt fails.
|
|
*/
|
|
static grub_err_t
|
|
blsuki_find_entry (struct find_entry_info *info, bool enable_fallback, enum blsuki_cmd_type cmd_type)
|
|
{
|
|
struct read_entry_info read_entry_info;
|
|
char *default_dir = NULL;
|
|
const char *cmd_dir = NULL;
|
|
char *tmp;
|
|
grub_size_t default_size;
|
|
grub_fs_t dir_fs = NULL;
|
|
grub_device_t dir_dev = NULL;
|
|
bool fallback = false;
|
|
int r;
|
|
|
|
do
|
|
{
|
|
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;
|
|
read_entry_info.cmd_type = cmd_type;
|
|
|
|
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 of "/loader/entries/" and see if we
|
|
* can find the files there. If we can't find UKI entries, fallback to
|
|
* "/EFI/Linux" on the EFI system partition.
|
|
*/
|
|
if (entries == NULL && fallback == false && enable_fallback == true)
|
|
{
|
|
if (cmd_type == BLSUKI_BLS_CMD)
|
|
cmd_dir = GRUB_BLS_CONFIG_PATH;
|
|
#ifdef GRUB_MACHINE_EFI
|
|
else if (cmd_type == BLSUKI_UKI_CMD)
|
|
cmd_dir = GRUB_UKI_CONFIG_PATH;
|
|
#endif
|
|
|
|
default_size = sizeof (GRUB_BOOT_DEVICE) + grub_strlen (cmd_dir);
|
|
default_dir = grub_malloc (default_size);
|
|
if (default_dir == NULL)
|
|
return grub_errno;
|
|
|
|
tmp = blsuki_update_boot_device (default_dir);
|
|
tmp = grub_stpcpy (tmp, cmd_dir);
|
|
|
|
blsuki_set_find_entry_info (info, default_dir, info->devid, cmd_type);
|
|
grub_dprintf ("blsuki", "Entries weren't found in %s, fallback to %s\n",
|
|
read_entry_info.dirname, info->dirname);
|
|
fallback = true;
|
|
}
|
|
else
|
|
fallback = false;
|
|
}
|
|
while (fallback == true);
|
|
|
|
grub_free (default_dir);
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
blsuki_load_entries (char *path, bool enable_fallback, enum blsuki_cmd_type cmd_type)
|
|
{
|
|
grub_size_t len, ext_len = 0;
|
|
static grub_err_t r;
|
|
const char *devid = NULL;
|
|
char *dir = NULL;
|
|
char *default_dir = NULL;
|
|
char *tmp;
|
|
const char *cmd_dir = NULL;
|
|
grub_size_t dir_size;
|
|
const char *ext = NULL;
|
|
struct find_entry_info info = {
|
|
.dev = NULL,
|
|
.fs = NULL,
|
|
.dirname = NULL,
|
|
};
|
|
struct read_entry_info rei = {
|
|
.devid = NULL,
|
|
.dirname = NULL,
|
|
.cmd_type = cmd_type,
|
|
};
|
|
|
|
if (path != NULL)
|
|
{
|
|
if (cmd_type == BLSUKI_BLS_CMD)
|
|
{
|
|
ext = ".conf";
|
|
ext_len = BLS_EXT_LEN;
|
|
}
|
|
#ifdef GRUB_MACHINE_EFI
|
|
else if (cmd_type == BLSUKI_UKI_CMD)
|
|
{
|
|
ext = ".efi";
|
|
ext_len = UKI_EXT_LEN;
|
|
}
|
|
#endif
|
|
|
|
len = grub_strlen (path);
|
|
if (len >= ext_len && grub_strcmp (path + len - ext_len, ext) == 0)
|
|
{
|
|
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)
|
|
{
|
|
if (cmd_type == BLSUKI_BLS_CMD)
|
|
cmd_dir = GRUB_BLS_CONFIG_PATH;
|
|
#ifdef GRUB_MACHINE_EFI
|
|
else if (cmd_type == BLSUKI_UKI_CMD)
|
|
cmd_dir = GRUB_UKI_CONFIG_PATH;
|
|
#endif
|
|
|
|
dir_size = sizeof (GRUB_BOOT_DEVICE) + grub_strlen (cmd_dir);
|
|
default_dir = grub_malloc (dir_size);
|
|
if (default_dir == NULL)
|
|
return grub_errno;
|
|
|
|
tmp = blsuki_update_boot_device (default_dir);
|
|
tmp = grub_stpcpy (tmp, cmd_dir);
|
|
dir = default_dir;
|
|
}
|
|
|
|
r = blsuki_set_find_entry_info (&info, dir, devid, cmd_type);
|
|
if (r == GRUB_ERR_NONE)
|
|
r = blsuki_find_entry (&info, enable_fallback, cmd_type);
|
|
|
|
if (info.dev != NULL)
|
|
grub_device_close (info.dev);
|
|
|
|
grub_free (default_dir);
|
|
return r;
|
|
}
|
|
|
|
static bool
|
|
blsuki_is_default_entry (const char *def_entry, grub_blsuki_entry_t *entry, int idx)
|
|
{
|
|
const char *title;
|
|
const char *def_entry_end;
|
|
long def_idx;
|
|
|
|
if (def_entry == NULL || *def_entry == '\0')
|
|
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 = grub_strtol (def_entry, &def_entry_end, 0);
|
|
|
|
/* Clear grub_errno so we can plug the leak. */
|
|
grub_errno = GRUB_ERR_NONE;
|
|
|
|
if (*def_entry_end != '\0' || def_idx < 0 || def_idx > GRUB_INT_MAX)
|
|
return false;
|
|
|
|
if ((int) def_idx == idx)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* This function creates a GRUB boot menu entry for each BLS or UKI entry in
|
|
* the entries list.
|
|
*/
|
|
static grub_err_t
|
|
blsuki_create_entries (bool show_default, bool show_non_default, char *entry_id, enum blsuki_cmd_type cmd_type)
|
|
{
|
|
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 == true)
|
|
{
|
|
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))
|
|
{
|
|
if (cmd_type == BLSUKI_BLS_CMD)
|
|
bls_create_entry (entry);
|
|
#ifdef GRUB_MACHINE_EFI
|
|
else if (cmd_type == BLSUKI_UKI_CMD)
|
|
uki_create_entry (entry);
|
|
#endif
|
|
entry->visible = true;
|
|
}
|
|
|
|
idx++;
|
|
}
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
blsuki_cmd (grub_extcmd_context_t ctxt, enum blsuki_cmd_type cmd_type)
|
|
{
|
|
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;
|
|
entries = NULL;
|
|
|
|
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, cmd_type);
|
|
if (err != GRUB_ERR_NONE)
|
|
return err;
|
|
|
|
return blsuki_create_entries (show_default, show_non_default, entry_id, cmd_type);
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_blscfg (grub_extcmd_context_t ctxt, int argc,
|
|
char **args __attribute__ ((unused)))
|
|
{
|
|
if (argc != 0)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unexpected argument(s) found, see --help"));
|
|
return blsuki_cmd (ctxt, BLSUKI_BLS_CMD);
|
|
}
|
|
|
|
static grub_extcmd_t bls_cmd;
|
|
|
|
#ifdef GRUB_MACHINE_EFI
|
|
static grub_err_t
|
|
grub_cmd_uki (grub_extcmd_context_t ctxt, int argc,
|
|
char **args __attribute__ ((unused)))
|
|
{
|
|
if (argc != 0)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unexpected argument(s) found, see --help"));
|
|
return blsuki_cmd (ctxt, BLSUKI_UKI_CMD);
|
|
}
|
|
|
|
static grub_extcmd_t uki_cmd;
|
|
#endif
|
|
|
|
GRUB_MOD_INIT(blsuki)
|
|
{
|
|
bls_cmd = grub_register_extcmd ("blscfg", grub_cmd_blscfg, 0,
|
|
N_("[-p|--path] DIR [-f|--enable-fallback] [-d|--show-default] [-n|--show-non-default] [-e|--entry] FILE"),
|
|
N_("Import Boot Loader Specification snippets."),
|
|
bls_opt);
|
|
#ifdef GRUB_MACHINE_EFI
|
|
uki_cmd = grub_register_extcmd ("uki", grub_cmd_uki, 0,
|
|
N_("[-p|--path] DIR [-f|--enable-fallback] [-d|--show-default] [-n|--show-non-default] [-e|--entry] FILE"),
|
|
N_("Import Unified Kernel Images"), uki_opt);
|
|
#endif
|
|
}
|
|
|
|
GRUB_MOD_FINI(blsuki)
|
|
{
|
|
grub_unregister_extcmd (bls_cmd);
|
|
#ifdef GRUB_MACHINE_EFI
|
|
grub_unregister_extcmd (uki_cmd);
|
|
#endif
|
|
}
|