mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Merge pull request #22519 from poettering/boot-order-title-revert
sd-boot: rework boot entry sorting
This commit is contained in:
4
TODO
4
TODO
@@ -1129,6 +1129,10 @@ Features:
|
||||
- teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation
|
||||
- teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host
|
||||
- make it operate on loopback files, dissecting enough to find ESP to operate on
|
||||
- bootspec: properly support boot attempt counters when parsing entry file names
|
||||
|
||||
* kernel-install:
|
||||
- optionally, support generating type #2 entries instead of type #1, including signing them
|
||||
|
||||
* logind:
|
||||
- logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around
|
||||
|
||||
@@ -232,6 +232,16 @@ spaces from its value. The following keys are known:
|
||||
other installed operating systems. This ID shall be formatted as 32 lower
|
||||
case hexadecimal characters (i.e. without any UUID formatting). This key is
|
||||
optional. Example: `4098b3f648d74c13b1f04ccfba7798e8`.
|
||||
* `sort-key` shall contain a short string used for sorting entries on
|
||||
display. This can be defined freely though should typically be initialized
|
||||
from `IMAGE_ID=` or `ID=` from `/etc/os-release` of the relevant entry,
|
||||
possibly suffixed. This field is optional. If set, it is used as primary
|
||||
sorting key for the entries on display (lexicographically increasing). It
|
||||
does not have to be unique (and usually is not). If non-unique the the
|
||||
`machine-id` (lexicographically increasing) and `version` (lexicographically
|
||||
decreasing, i.e. newest version first) fields described above are used as
|
||||
secondary/ternary sorting keys. If this field is not set entries are
|
||||
typically sorted by the `.conf` file name of the entry.
|
||||
* `linux` refers to the Linux kernel to spawn and shall be a path relative to
|
||||
`$BOOT`. It is recommended that every distribution creates a machine id and
|
||||
version specific subdirectory below `$BOOT` and places its kernels and
|
||||
@@ -269,8 +279,9 @@ key and is otherwise not valid. Here's an example for a complete drop-in file:
|
||||
|
||||
# /boot/loader/entries/6a9857a393724b7a981ebb5b8495b9ea-3.8.0-2.fc19.x86_64.conf
|
||||
title Fedora 19 (Rawhide)
|
||||
version 3.8.0-2.fc19.x86_64
|
||||
sort-key fedora
|
||||
machine-id 6a9857a393724b7a981ebb5b8495b9ea
|
||||
version 3.8.0-2.fc19.x86_64
|
||||
options root=UUID=6d3376e4-fc93-4509-95ec-a21d68011da2
|
||||
architecture x64
|
||||
linux /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/linux
|
||||
@@ -358,10 +369,10 @@ simply reads all files `$BOOT/loader/entries/*.conf`, and populates its boot
|
||||
menu with this. On EFI, it then extends this with any unified kernel images
|
||||
found in `$BOOT/EFI/Linux/*.efi`. It may also add additional entries, for
|
||||
example a "Reboot into firmware" option. Optionally it may sort the menu based
|
||||
on the `machine-id` and `version` fields, and possibly others. It uses the file
|
||||
name to identify specific items, for example in case it supports storing away
|
||||
default entry information somewhere. A boot loader should generally not modify
|
||||
these files.
|
||||
on the `sort-key`, `machine-id` and `version` fields, and possibly others. It
|
||||
uses the file name to identify specific items, for example in case it supports
|
||||
storing away default entry information somewhere. A boot loader should
|
||||
generally not modify these files.
|
||||
|
||||
For "Boot Loader Specification Entries" (Type #1), the _kernel package
|
||||
installer_ installs the kernel and initrd images to `$BOOT` (it is recommended
|
||||
|
||||
@@ -501,10 +501,10 @@
|
||||
considered 'good' from then on.</para>
|
||||
|
||||
<para>The boot menu takes the 'tries left' counter into account when sorting the menu entries: entries in 'bad'
|
||||
state are ordered towards the end of the list, and entries in 'good' or 'indeterminate' towards the beginning.
|
||||
The user can freely choose to boot any entry of the menu, including those already marked 'bad'. If the menu entry
|
||||
to boot is automatically determined, this means that 'good' or 'indeterminate' entries are generally preferred as
|
||||
boot entries are tried in sort order, and 'bad' entries will only be considered if there are no 'good' or
|
||||
state are ordered at the beginning of the list, and entries in 'good' or 'indeterminate' at the end. The user can
|
||||
freely choose to boot any entry of the menu, including those already marked 'bad'. If the menu entry to boot is
|
||||
automatically determined, this means that 'good' or 'indeterminate' entries are generally preferred (as the bottom
|
||||
item of the menu is the one booted by default), and 'bad' entries will only be considered if there are no 'good' or
|
||||
'indeterminate' entries left.</para>
|
||||
|
||||
<para>The <citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry> kernel
|
||||
|
||||
@@ -591,6 +591,8 @@ static int boot_entry_show(
|
||||
|
||||
printf(" source: %s\n", link ?: e->path);
|
||||
}
|
||||
if (e->sort_key)
|
||||
printf(" sort-key: %s\n", e->sort_key);
|
||||
if (e->version)
|
||||
printf(" version: %s\n", e->version);
|
||||
if (e->machine_id)
|
||||
|
||||
@@ -53,6 +53,7 @@ typedef struct {
|
||||
CHAR16 *id; /* The unique identifier for this entry (typically the filename of the file defining the entry) */
|
||||
CHAR16 *title_show; /* The string to actually display (this is made unique before showing) */
|
||||
CHAR16 *title; /* The raw (human readable) title string of the entry (not necessarily unique) */
|
||||
CHAR16 *sort_key; /* The string to use as primary sory key, usually ID= from os-release, possibly suffixed */
|
||||
CHAR16 *version; /* The raw (human readable) version string of the entry */
|
||||
CHAR16 *machine_id;
|
||||
EFI_HANDLE *device;
|
||||
@@ -540,6 +541,7 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
|
||||
ps_string(L" id: %s\n", entry->id);
|
||||
ps_string(L" title: %s\n", entry->title);
|
||||
ps_string(L" title show: %s\n", streq_ptr(entry->title, entry->title_show) ? NULL : entry->title_show);
|
||||
ps_string(L" sort key: %s\n", entry->sort_key);
|
||||
ps_string(L" version: %s\n", entry->version);
|
||||
ps_string(L" machine-id: %s\n", entry->machine_id);
|
||||
if (entry->device)
|
||||
@@ -1026,6 +1028,7 @@ static void config_entry_free(ConfigEntry *entry) {
|
||||
FreePool(entry->id);
|
||||
FreePool(entry->title_show);
|
||||
FreePool(entry->title);
|
||||
FreePool(entry->sort_key);
|
||||
FreePool(entry->version);
|
||||
FreePool(entry->machine_id);
|
||||
FreePool(entry->loader);
|
||||
@@ -1427,6 +1430,12 @@ static void config_entry_add_from_file(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmpa((CHAR8 *)"sort-key", key) == 0) {
|
||||
FreePool(entry->sort_key);
|
||||
entry->sort_key = xstra_to_str(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmpa((CHAR8 *)"version", key) == 0) {
|
||||
FreePool(entry->version);
|
||||
entry->version = xstra_to_str(value);
|
||||
@@ -1531,6 +1540,7 @@ static void config_entry_add_from_file(
|
||||
|
||||
entry->device = device;
|
||||
entry->id = xstrdup(file);
|
||||
StrLwr(entry->id);
|
||||
|
||||
config_add_entry(config, entry);
|
||||
|
||||
@@ -1609,6 +1619,8 @@ static void config_load_entries(
|
||||
assert(device);
|
||||
assert(root_dir);
|
||||
|
||||
/* Adds Boot Loader Type #1 entries (i.e. /loader/entries/….conf) */
|
||||
|
||||
err = open_directory(root_dir, L"\\loader\\entries", &entries_dir);
|
||||
if (EFI_ERROR(err))
|
||||
return;
|
||||
@@ -1642,24 +1654,40 @@ static INTN config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
|
||||
assert(a);
|
||||
assert(b);
|
||||
|
||||
/* Order entries that have no tries left towards the end of the list. They have
|
||||
* proven to be bad and should not be selected automatically. */
|
||||
if (a->tries_left != 0 && b->tries_left == 0)
|
||||
return -1;
|
||||
/* Order entries that have no tries left to the end of the list */
|
||||
if (a->tries_left == 0 && b->tries_left != 0)
|
||||
return 1;
|
||||
if (a->tries_left != 0 && b->tries_left == 0)
|
||||
return -1;
|
||||
|
||||
r = strcasecmp_ptr(a->title ?: a->id, b->title ?: b->id);
|
||||
if (r != 0)
|
||||
/* If there's a sort key defined for *both* entries, then we do new-style ordering, i.e. by
|
||||
* sort-key/machine-id/version, with a final fallback to id. If there's no sort key for either, we do
|
||||
* old-style ordering, i.e. by id only. If one has sort key and the other does not, we put new-style
|
||||
* before old-style. */
|
||||
r = CMP(!a->sort_key, !b->sort_key);
|
||||
if (r != 0) /* one is old-style, one new-style */
|
||||
return r;
|
||||
if (a->sort_key && b->sort_key) {
|
||||
|
||||
/* Sort by machine id now so that different installations don't interleave their versions. */
|
||||
r = strcasecmp_ptr(a->machine_id, b->machine_id);
|
||||
if (r != 0)
|
||||
return r;
|
||||
r = strcmp(a->sort_key, b->sort_key);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* Reverse version comparison order so that higher versions are preferred. */
|
||||
r = strverscmp_improved(b->version, a->version);
|
||||
/* If multiple installations of the same OS are around, group by machine ID */
|
||||
r = strcmp_ptr(a->machine_id, b->machine_id);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* If the sort key was defined, then order by version now (downwards, putting the newest first) */
|
||||
r = -strverscmp_improved(a->version, b->version);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Now order by ID (the version is likely part of the ID, thus note that this might put the oldest
|
||||
* version last, not first, i.e. specifying a sort key explicitly is thus generally preferable, to
|
||||
* take benefit of the explicit sorting above.) */
|
||||
r = strverscmp_improved(a->id, b->id);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
@@ -1668,19 +1696,18 @@ static INTN config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
|
||||
return 0;
|
||||
|
||||
/* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */
|
||||
if (a->tries_left > b->tries_left)
|
||||
return -1;
|
||||
if (a->tries_left < b->tries_left)
|
||||
return 1;
|
||||
if (a->tries_left > b->tries_left)
|
||||
return -1;
|
||||
|
||||
/* If they have the same number of tries left, then let the one win which was tried fewer times so far */
|
||||
if (a->tries_done < b->tries_done)
|
||||
return -1;
|
||||
if (a->tries_done > b->tries_done)
|
||||
return 1;
|
||||
if (a->tries_done < b->tries_done)
|
||||
return -1;
|
||||
|
||||
/* As a last resort, use the id (file name). */
|
||||
return strverscmp_improved(a->id, b->id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static UINTN config_entry_find(Config *config, const CHAR16 *needle) {
|
||||
@@ -1724,7 +1751,7 @@ static void config_default_entry_select(Config *config) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Select the first suitable entry. */
|
||||
/* select the first suitable entry */
|
||||
for (i = 0; i < config->entry_count; i++) {
|
||||
if (config->entries[i]->type == LOADER_AUTO || config->entries[i]->call)
|
||||
continue;
|
||||
@@ -1853,6 +1880,7 @@ static ConfigEntry *config_entry_add_loader(
|
||||
CHAR16 key,
|
||||
const CHAR16 *title,
|
||||
const CHAR16 *loader,
|
||||
const CHAR16 *sort_key,
|
||||
const CHAR16 *version) {
|
||||
|
||||
ConfigEntry *entry;
|
||||
@@ -1871,11 +1899,14 @@ static ConfigEntry *config_entry_add_loader(
|
||||
.device = device,
|
||||
.loader = xstrdup(loader),
|
||||
.id = xstrdup(id),
|
||||
.sort_key = xstrdup(sort_key),
|
||||
.key = key,
|
||||
.tries_done = UINTN_MAX,
|
||||
.tries_left = UINTN_MAX,
|
||||
};
|
||||
|
||||
StrLwr(entry->id);
|
||||
|
||||
config_add_entry(config, entry);
|
||||
return entry;
|
||||
}
|
||||
@@ -1943,7 +1974,7 @@ static ConfigEntry *config_entry_add_loader_auto(
|
||||
if (EFI_ERROR(err))
|
||||
return NULL;
|
||||
|
||||
return config_entry_add_loader(config, device, LOADER_AUTO, id, key, title, loader, NULL);
|
||||
return config_entry_add_loader(config, device, LOADER_AUTO, id, key, title, loader, NULL, NULL);
|
||||
}
|
||||
|
||||
static void config_entry_add_osx(Config *config) {
|
||||
@@ -2097,6 +2128,8 @@ static void config_entry_add_linux(
|
||||
UINTN f_size = 0;
|
||||
EFI_STATUS err;
|
||||
|
||||
/* Adds Boot Loader Type #2 entries (i.e. /EFI/Linux/….efi) */
|
||||
|
||||
assert(config);
|
||||
assert(device);
|
||||
assert(root_dir);
|
||||
@@ -2121,7 +2154,7 @@ static void config_entry_add_linux(
|
||||
_cleanup_freepool_ CHAR16 *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
|
||||
*os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL,
|
||||
*path = NULL;
|
||||
const CHAR16 *good_name, *good_version;
|
||||
const CHAR16 *good_name, *good_version, *good_sort_key;
|
||||
_cleanup_freepool_ CHAR8 *content = NULL;
|
||||
UINTN offs[_SECTION_MAX] = {};
|
||||
UINTN szs[_SECTION_MAX] = {};
|
||||
@@ -2202,7 +2235,7 @@ static void config_entry_add_linux(
|
||||
}
|
||||
}
|
||||
|
||||
if (!bootspec_pick_name_version(
|
||||
if (!bootspec_pick_name_version_sort_key(
|
||||
os_pretty_name,
|
||||
os_image_id,
|
||||
os_name,
|
||||
@@ -2212,7 +2245,8 @@ static void config_entry_add_linux(
|
||||
os_version_id,
|
||||
os_build_id,
|
||||
&good_name,
|
||||
&good_version))
|
||||
&good_version,
|
||||
&good_sort_key))
|
||||
continue;
|
||||
|
||||
path = xpool_print(L"\\EFI\\Linux\\%s", f->FileName);
|
||||
@@ -2220,10 +2254,11 @@ static void config_entry_add_linux(
|
||||
config,
|
||||
device,
|
||||
LOADER_UNIFIED_LINUX,
|
||||
f->FileName,
|
||||
/* id= */ f->FileName,
|
||||
/* key= */ 'l',
|
||||
good_name,
|
||||
path,
|
||||
/* title= */ good_name,
|
||||
/* loader= */ path,
|
||||
/* sort_key= */ good_sort_key,
|
||||
good_version);
|
||||
|
||||
config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi");
|
||||
@@ -2449,14 +2484,12 @@ static void config_load_all_entries(
|
||||
/* Similar, but on any XBOOTLDR partition */
|
||||
config_load_xbootldr(config, loaded_image->DeviceHandle);
|
||||
|
||||
/* Add these now, so they get sorted with the rest. */
|
||||
config_entry_add_osx(config);
|
||||
config_entry_add_windows(config, loaded_image->DeviceHandle, root_dir);
|
||||
|
||||
/* sort entries after version number */
|
||||
sort_pointer_array((void **) config->entries, config->entry_count, (compare_pointer_func_t) config_entry_compare);
|
||||
|
||||
/* if we find some well-known loaders, add them to the end of the list */
|
||||
config_entry_add_osx(config);
|
||||
config_entry_add_windows(config, loaded_image->DeviceHandle, root_dir);
|
||||
config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, NULL,
|
||||
L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" EFI_MACHINE_TYPE_NAME ".efi");
|
||||
config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "bootspec-fundamental.h"
|
||||
|
||||
sd_bool bootspec_pick_name_version(
|
||||
sd_bool bootspec_pick_name_version_sort_key(
|
||||
const sd_char *os_pretty_name,
|
||||
const sd_char *os_image_id,
|
||||
const sd_char *os_name,
|
||||
@@ -12,12 +12,14 @@ sd_bool bootspec_pick_name_version(
|
||||
const sd_char *os_version_id,
|
||||
const sd_char *os_build_id,
|
||||
const sd_char **ret_name,
|
||||
const sd_char **ret_version) {
|
||||
const sd_char **ret_version,
|
||||
const sd_char **ret_sort_key) {
|
||||
|
||||
const sd_char *good_name, *good_version;
|
||||
const sd_char *good_name, *good_version, *good_sort_key;
|
||||
|
||||
/* Find the best human readable title and version string for a boot entry (using the os-release(5)
|
||||
* fields). Precise is preferred over vague, and human readable over machine readable. Thus:
|
||||
/* Find the best human readable title, version string and sort key for a boot entry (using the
|
||||
* os-release(5) fields). Precise is preferred over vague, and human readable over machine
|
||||
* readable. Thus:
|
||||
*
|
||||
* 1. First priority gets the PRETTY_NAME field, which is the primary string intended for display,
|
||||
* and should already contain both a nice description and a version indication (if that concept
|
||||
@@ -37,11 +39,12 @@ sd_bool bootspec_pick_name_version(
|
||||
* which case the version is shown too.
|
||||
*
|
||||
* Note that name/version determined here are used only for display purposes. Boot entry preference
|
||||
* sorting (i.e. algorithmic ordering of boot entries) is done based on the order of the entry "id"
|
||||
* string (i.e. not on os-release(5) data). */
|
||||
* sorting (i.e. algorithmic ordering of boot entries) is done based on the order of the sort key (if
|
||||
* defined) or entry "id" string (i.e. entry file name) otherwise. */
|
||||
|
||||
good_name = os_pretty_name ?: (os_image_id ?: (os_name ?: os_id));
|
||||
good_version = os_image_version ?: (os_version ?: (os_version_id ? : os_build_id));
|
||||
good_sort_key = os_image_id ?: os_id;
|
||||
|
||||
if (!good_name || !good_version)
|
||||
return sd_false;
|
||||
@@ -52,5 +55,8 @@ sd_bool bootspec_pick_name_version(
|
||||
if (ret_version)
|
||||
*ret_version = good_version;
|
||||
|
||||
if (ret_sort_key)
|
||||
*ret_sort_key = good_sort_key;
|
||||
|
||||
return sd_true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "types-fundamental.h"
|
||||
|
||||
sd_bool bootspec_pick_name_version(
|
||||
sd_bool bootspec_pick_name_version_sort_key(
|
||||
const sd_char *os_pretty_name,
|
||||
const sd_char *os_image_id,
|
||||
const sd_char *os_name,
|
||||
@@ -13,4 +13,5 @@ sd_bool bootspec_pick_name_version(
|
||||
const sd_char *os_version_id,
|
||||
const sd_char *os_build_id,
|
||||
const sd_char **ret_name,
|
||||
const sd_char **ret_version);
|
||||
const sd_char **ret_version,
|
||||
const sd_char **ret_sort_key);
|
||||
|
||||
@@ -62,6 +62,9 @@ fi
|
||||
|
||||
[ -n "$PRETTY_NAME" ] || PRETTY_NAME="Linux $KERNEL_VERSION"
|
||||
|
||||
SORT_KEY="$IMAGE_ID"
|
||||
[ -z "$SORT_KEY" ] && SORT_KEY="$ID"
|
||||
|
||||
if [ -r /etc/kernel/cmdline ]; then
|
||||
BOOT_OPTIONS="$(tr -s "$IFS" ' ' </etc/kernel/cmdline)"
|
||||
elif [ -r /usr/lib/kernel/cmdline ]; then
|
||||
@@ -130,6 +133,7 @@ mkdir -p "${LOADER_ENTRY%/*}" || {
|
||||
# See similar logic above for the systemd.machine_id= kernel command line option
|
||||
echo "machine-id $MACHINE_ID"
|
||||
fi
|
||||
[ -n "$SORT_KEY" ] && echo "sort-key $SORT_KEY"
|
||||
echo "options $BOOT_OPTIONS"
|
||||
echo "linux $ENTRY_DIR/linux"
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ static void boot_entry_free(BootEntry *entry) {
|
||||
free(entry->root);
|
||||
free(entry->title);
|
||||
free(entry->show_title);
|
||||
free(entry->sort_key);
|
||||
free(entry->version);
|
||||
free(entry->machine_id);
|
||||
free(entry->architecture);
|
||||
@@ -113,6 +114,8 @@ static int boot_entry_load(
|
||||
|
||||
if (streq(field, "title"))
|
||||
r = free_and_strdup(&tmp.title, p);
|
||||
else if (streq(field, "sort-key"))
|
||||
r = free_and_strdup(&tmp.sort_key, p);
|
||||
else if (streq(field, "version"))
|
||||
r = free_and_strdup(&tmp.version, p);
|
||||
else if (streq(field, "machine-id"))
|
||||
@@ -245,6 +248,28 @@ static int boot_loader_read_conf(const char *path, BootConfig *config) {
|
||||
}
|
||||
|
||||
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
|
||||
int r;
|
||||
|
||||
assert(a);
|
||||
assert(b);
|
||||
|
||||
r = CMP(!a->sort_key, !b->sort_key);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (a->sort_key && b->sort_key) {
|
||||
r = strcmp(a->sort_key, b->sort_key);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = strcmp_ptr(a->machine_id, b->machine_id);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = -strverscmp_improved(a->version, b->version);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return strverscmp_improved(a->id, b->id);
|
||||
}
|
||||
|
||||
@@ -293,7 +318,7 @@ static int boot_entry_load_unified(
|
||||
_cleanup_(boot_entry_free) BootEntry tmp = {
|
||||
.type = BOOT_ENTRY_UNIFIED,
|
||||
};
|
||||
const char *k, *good_name, *good_version;
|
||||
const char *k, *good_name, *good_version, *good_sort_key;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
int r;
|
||||
|
||||
@@ -321,7 +346,7 @@ static int boot_entry_load_unified(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
|
||||
|
||||
if (!bootspec_pick_name_version(
|
||||
if (!bootspec_pick_name_version_sort_key(
|
||||
os_pretty_name,
|
||||
os_image_id,
|
||||
os_name,
|
||||
@@ -331,7 +356,8 @@ static int boot_entry_load_unified(
|
||||
os_version_id,
|
||||
os_build_id,
|
||||
&good_name,
|
||||
&good_version))
|
||||
&good_version,
|
||||
&good_sort_key))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
|
||||
|
||||
r = path_extract_filename(path, &tmp.id);
|
||||
@@ -369,6 +395,10 @@ static int boot_entry_load_unified(
|
||||
if (!tmp.title)
|
||||
return log_oom();
|
||||
|
||||
tmp.sort_key = strdup(good_sort_key);
|
||||
if (!tmp.sort_key)
|
||||
return log_oom();
|
||||
|
||||
tmp.version = strdup(good_version);
|
||||
if (!tmp.version)
|
||||
return log_oom();
|
||||
@@ -616,6 +646,19 @@ static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boot_config_find(const BootConfig *config, const char *id) {
|
||||
assert(config);
|
||||
|
||||
if (!id)
|
||||
return -1;
|
||||
|
||||
for (size_t i = 0; i < config->n_entries; i++)
|
||||
if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int boot_entries_select_default(const BootConfig *config) {
|
||||
int i;
|
||||
|
||||
@@ -627,47 +670,45 @@ static int boot_entries_select_default(const BootConfig *config) {
|
||||
return -1; /* -1 means "no default" */
|
||||
}
|
||||
|
||||
if (config->entry_oneshot)
|
||||
for (i = config->n_entries - 1; i >= 0; i--)
|
||||
if (streq(config->entry_oneshot, config->entries[i].id)) {
|
||||
log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
|
||||
config->entries[i].id);
|
||||
return i;
|
||||
}
|
||||
if (config->entry_oneshot) {
|
||||
i = boot_config_find(config, config->entry_oneshot);
|
||||
if (i >= 0) {
|
||||
log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
|
||||
config->entries[i].id);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
if (config->entry_default)
|
||||
for (i = config->n_entries - 1; i >= 0; i--)
|
||||
if (streq(config->entry_default, config->entries[i].id)) {
|
||||
log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
|
||||
config->entries[i].id);
|
||||
return i;
|
||||
}
|
||||
if (config->entry_default) {
|
||||
i = boot_config_find(config, config->entry_default);
|
||||
if (i >= 0) {
|
||||
log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
|
||||
config->entries[i].id);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
if (config->default_pattern)
|
||||
for (i = config->n_entries - 1; i >= 0; i--)
|
||||
if (fnmatch(config->default_pattern, config->entries[i].id, FNM_CASEFOLD) == 0) {
|
||||
log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
|
||||
config->entries[i].id, config->default_pattern);
|
||||
return i;
|
||||
}
|
||||
if (config->default_pattern) {
|
||||
i = boot_config_find(config, config->default_pattern);
|
||||
if (i >= 0) {
|
||||
log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
|
||||
config->entries[i].id, config->default_pattern);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
|
||||
return config->n_entries - 1;
|
||||
log_debug("Found default: first entry \"%s\"", config->entries[0].id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boot_entries_select_selected(const BootConfig *config) {
|
||||
|
||||
assert(config);
|
||||
assert(config->entries || config->n_entries == 0);
|
||||
|
||||
if (!config->entry_selected || config->n_entries == 0)
|
||||
return -1;
|
||||
|
||||
for (int i = config->n_entries - 1; i >= 0; i--)
|
||||
if (streq(config->entry_selected, config->entries[i].id))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
return boot_config_find(config, config->entry_selected);
|
||||
}
|
||||
|
||||
static int boot_load_efi_entry_pointers(BootConfig *config) {
|
||||
|
||||
@@ -27,6 +27,7 @@ typedef struct BootEntry {
|
||||
char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */
|
||||
char *title;
|
||||
char *show_title;
|
||||
char *sort_key;
|
||||
char *version;
|
||||
char *machine_id;
|
||||
char *architecture;
|
||||
|
||||
@@ -513,6 +513,8 @@ tests += [
|
||||
|
||||
[files('test-strbuf.c')],
|
||||
|
||||
[files('test-bootspec.c')],
|
||||
|
||||
[files('test-strv.c')],
|
||||
|
||||
[files('test-path-util.c')],
|
||||
|
||||
96
src/test/test-bootspec.c
Normal file
96
src/test/test-bootspec.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "bootspec.h"
|
||||
#include "fileio.h"
|
||||
#include "path-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "tests.h"
|
||||
#include "tmpfile-util.h"
|
||||
|
||||
TEST_RET(bootspec_sort) {
|
||||
|
||||
static const struct {
|
||||
const char *fname;
|
||||
const char *contents;
|
||||
} entries[] = {
|
||||
{
|
||||
.fname = "a-10.conf",
|
||||
.contents =
|
||||
"title A\n"
|
||||
"version 10\n"
|
||||
"machine-id dd235d00696545768f6f693bfd23b15f\n",
|
||||
},
|
||||
{
|
||||
.fname = "a-5.conf",
|
||||
.contents =
|
||||
"title A\n"
|
||||
"version 5\n"
|
||||
"machine-id dd235d00696545768f6f693bfd23b15f\n",
|
||||
},
|
||||
{
|
||||
.fname = "b.conf",
|
||||
.contents =
|
||||
"title B\n"
|
||||
"version 3\n"
|
||||
"machine-id b75451ad92f94feeab50b0b442768dbd\n",
|
||||
},
|
||||
{
|
||||
.fname = "c.conf",
|
||||
.contents =
|
||||
"title C\n"
|
||||
"sort-key xxxx\n"
|
||||
"version 5\n"
|
||||
"machine-id 309de666fd5044268a9a26541ac93176\n",
|
||||
},
|
||||
{
|
||||
.fname = "cx.conf",
|
||||
.contents =
|
||||
"title C\n"
|
||||
"sort-key xxxx\n"
|
||||
"version 10\n"
|
||||
"machine-id 309de666fd5044268a9a26541ac93176\n",
|
||||
},
|
||||
{
|
||||
.fname = "d.conf",
|
||||
.contents =
|
||||
"title D\n"
|
||||
"sort-key kkkk\n"
|
||||
"version 100\n"
|
||||
"machine-id 81c6e3147cf544c19006af023e22b292\n",
|
||||
},
|
||||
};
|
||||
|
||||
_cleanup_(rm_rf_physical_and_freep) char *d = NULL;
|
||||
_cleanup_(boot_config_free) BootConfig config = {};
|
||||
|
||||
assert_se(mkdtemp_malloc("/tmp/bootspec-testXXXXXX", &d) >= 0);
|
||||
|
||||
for (size_t i = 0; i < ELEMENTSOF(entries); i++) {
|
||||
_cleanup_free_ char *j = NULL;
|
||||
|
||||
j = path_join(d, "/loader/entries/", entries[i].fname);
|
||||
assert_se(j);
|
||||
|
||||
assert_se(write_string_file(j, entries[i].contents, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0);
|
||||
}
|
||||
|
||||
assert_se(boot_entries_load_config(d, NULL, &config) >= 0);
|
||||
|
||||
assert_se(config.n_entries == 6);
|
||||
|
||||
/* First, because has sort key, and its the lowest one */
|
||||
assert_se(streq(config.entries[0].id, "d.conf"));
|
||||
|
||||
/* These two have a sort key, and newest must be first */
|
||||
assert_se(streq(config.entries[1].id, "cx.conf"));
|
||||
assert_se(streq(config.entries[2].id, "c.conf"));
|
||||
|
||||
/* The following ones have no sort key, hence order by version compared ids, lowest first */
|
||||
assert_se(streq(config.entries[3].id, "a-5.conf"));
|
||||
assert_se(streq(config.entries[4].id, "a-10.conf"));
|
||||
assert_se(streq(config.entries[5].id, "b.conf"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
Reference in New Issue
Block a user