Merge pull request #22519 from poettering/boot-order-title-revert

sd-boot: rework boot entry sorting
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2022-03-18 19:04:48 +01:00
committed by GitHub
12 changed files with 281 additions and 80 deletions

4
TODO
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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"

View File

@@ -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) {

View File

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

View File

@@ -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
View 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);