]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-boot: make use of new "sort-key" boot loader spec field
authorLennart Poettering <lennart@poettering.net>
Tue, 15 Feb 2022 13:24:53 +0000 (14:24 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 18 Mar 2022 10:59:30 +0000 (11:59 +0100)
src/boot/bootctl.c
src/boot/efi/boot.c
src/fundamental/bootspec-fundamental.c
src/fundamental/bootspec-fundamental.h
src/shared/bootspec.c
src/shared/bootspec.h

index e2900291bf01fff9864d6b3e51114cb350ad1a8c..557b66b1e5d6c1b01d8a5b7c13dfaad4402276f6 100644 (file)
@@ -590,6 +590,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)
index c2b799f0075ae969f3f7d5f086a2111ea70610f3..1f254198eef7a9221fb6ee976cbaa5e4028e11aa 100644 (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);
@@ -1643,12 +1652,39 @@ static INTN config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
         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;
@@ -1657,16 +1693,16 @@ static INTN config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
             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;
@@ -1678,7 +1714,7 @@ static UINTN config_entry_find(Config *config, const CHAR16 *needle) {
         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;
 
@@ -1713,9 +1749,8 @@ static void config_default_entry_select(Config *config) {
                 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;
@@ -1843,6 +1878,7 @@ static ConfigEntry *config_entry_add_loader(
                 CHAR16 key,
                 const CHAR16 *title,
                 const CHAR16 *loader,
+                const CHAR16 *sort_key,
                 const CHAR16 *version) {
 
         ConfigEntry *entry;
@@ -1861,6 +1897,7 @@ 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,
@@ -1935,7 +1972,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) {
@@ -2113,7 +2150,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] = {};
@@ -2194,7 +2231,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,
@@ -2204,7 +2241,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);
@@ -2212,10 +2250,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");
index 9c4aee9744e7d9c1355290dd4410d4f93442a3d9..89e29f598285e81dac8233dc850c06ec96a14813 100644 (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;
 }
index 2cb6d7daa10d9eaac566b62f63d7796b06dd429c..ff88f8bc3f4c537d6653c874fbf30e282d1f9e88 100644 (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);
index 524f0ccfcdccd84ec29a3d2906b92fcca7038f6e..99abeac34bf9522bf7ca19ad22727908f1dd1d4d 100644 (file)
@@ -45,6 +45,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);
@@ -131,6 +132,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"))
@@ -263,6 +266,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);
 }
 
@@ -311,7 +336,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;
 
@@ -339,7 +364,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,
@@ -349,7 +374,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);
@@ -387,6 +413,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();
@@ -634,6 +664,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;
 
@@ -645,47 +688,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) {
index 16353746aed64460ee60eac7c04406c9f3a14fe7..64052af593845bb8e6b9186d19c445d88d91e3bb 100644 (file)
@@ -28,6 +28,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;