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)
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;
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)
FreePool(entry->id);
FreePool(entry->title_show);
FreePool(entry->title);
+ FreePool(entry->sort_key);
FreePool(entry->version);
FreePool(entry->machine_id);
FreePool(entry->loader);
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);
assert(a);
assert(b);
- /* Order entries that have no tries left to the beginning of the list */
- 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;
+ /* 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) {
+
+ r = strcmp(a->sort_key, b->sort_key);
+ if (r != 0)
+ return r;
+
+ /* 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;
b->tries_left == UINTN_MAX)
return 0;
- /* If both items have boot counting, and otherwise are identical, put the entry with more tries left last */
- if (a->tries_left > b->tries_left)
- return 1;
+ /* 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 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;
return 0;
if (!needle)
return IDX_INVALID;
- for (INTN i = config->entry_count - 1; i >= 0; i--)
+ for (UINTN i = 0; i < config->entry_count; i++)
if (MetaiMatch(config->entries[i]->id, (CHAR16*) needle))
return i;
return;
}
- /* select the last suitable entry */
- i = config->entry_count;
- while (i--) {
+ /* 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;
config->idx_default = i;
CHAR16 key,
const CHAR16 *title,
const CHAR16 *loader,
+ const CHAR16 *sort_key,
const CHAR16 *version) {
ConfigEntry *entry;
.device = device,
.loader = xstrdup(loader),
.id = xstrdup(id),
+ .sort_key = xstrdup(sort_key),
.key = key,
.tries_done = UINTN_MAX,
.tries_left = UINTN_MAX,
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) {
_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] = {};
}
}
- if (!bootspec_pick_name_version(
+ if (!bootspec_pick_name_version_sort_key(
os_pretty_name,
os_image_id,
os_name,
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);
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");
#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,
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
* 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;
if (ret_version)
*ret_version = good_version;
+ if (ret_sort_key)
+ *ret_sort_key = good_sort_key;
+
return sd_true;
}
#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,
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);
free(entry->root);
free(entry->title);
free(entry->show_title);
+ free(entry->sort_key);
free(entry->version);
free(entry->machine_id);
free(entry->architecture);
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"))
}
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);
}
_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;
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,
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);
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();
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;
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) {
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;