]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bootctl: discover local UKI PE addons
authorEmanuele Giuseppe Esposito <eesposit@redhat.com>
Thu, 21 Sep 2023 06:21:42 +0000 (02:21 -0400)
committerEmanuele Giuseppe Esposito <eesposit@redhat.com>
Wed, 14 Feb 2024 09:58:20 +0000 (04:58 -0500)
An UKI final command line is not just made of the content of .cmdline,
but also from the addons that are inserted in
/boot/efi/EFI/Linux/<UKI_NAME>.efi.extra.d (local addons) and
/boot/efi/loader/addons (global addons).

Therefore bootclt "status" and "list" should also include these addons
when printing the UKI command line.

Right now, discover addons present in
/boot/efi/EFI/Linux/<UKI_NAME>.efi.extra.d.

Example output (assume UKI_NAME=devel):
$ bootctl
ukiCmdline: console=tty0 console=ttyS0
   localAddon: devel.efi.extra.d/rpm_addon.addon.efi
      cmdline: └─this is a normal addon
 finalCmdline: console=tty0 console=ttyS0 this is a normal addon

src/shared/bootspec.c
src/shared/bootspec.h

index 3e1e9c32b4938d8e73c191f15e35d8b15a2a4e3b..16986c791229f0186efee1a950ec2ad8709ed95d 100644 (file)
@@ -43,6 +43,15 @@ static const char* const boot_entry_type_json_table[_BOOT_ENTRY_TYPE_MAX] = {
 
 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json, BootEntryType);
 
+BootEntryAddon* boot_entry_addon_free(BootEntryAddon *addon) {
+        if (!addon)
+                return NULL;
+
+        free(addon->location);
+        free(addon->cmdline);
+        return mfree(addon);
+}
+
 static void boot_entry_free(BootEntry *entry) {
         assert(entry);
 
@@ -57,6 +66,7 @@ static void boot_entry_free(BootEntry *entry) {
         free(entry->machine_id);
         free(entry->architecture);
         strv_free(entry->options);
+        free(entry->local_addons.items);
         free(entry->kernel);
         free(entry->efi);
         strv_free(entry->initrd);
@@ -851,6 +861,121 @@ static int find_uki_sections(
         return 0;
 }
 
+static int find_addon_sections(
+                int fd,
+                const char *path,
+                char **ret_cmdline) {
+
+        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
+        _cleanup_free_ PeHeader *pe_header = NULL;
+        int r;
+
+        r = find_sections(fd, path, &sections, &pe_header);
+        if (r < 0)
+                return r;
+
+        return find_cmdline_section(fd, path, sections, pe_header, ret_cmdline);
+}
+
+static int insert_boot_entry_addon(
+                BootEntryAddons *addons,
+                char *location,
+                char *cmdline) {
+
+        if (!GREEDY_REALLOC(addons->items, addons->count + 1))
+                return log_oom();
+
+        addons->items[addons->count] = (BootEntryAddon) {
+                .location = location,
+                .cmdline = cmdline,
+        };
+        addons->count++;
+
+        return 0;
+}
+
+static int boot_entries_find_unified_addons(
+                BootConfig *config,
+                int d_fd,
+                const char *addon_dir,
+                const char *root,
+                BootEntryAddons *addons) {
+
+        _cleanup_closedir_ DIR *d = NULL;
+        _cleanup_free_ char *full = NULL;
+        int r;
+
+        assert(addons);
+        assert(config);
+
+        r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to open '%s/%s': %m", root, addon_dir);
+
+        *addons = (BootEntryAddons) {};
+
+        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
+                _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL;
+                _cleanup_close_ int fd = -EBADF;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                if (!endswith_no_case(de->d_name, ".addon.efi"))
+                        continue;
+
+                fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
+                if (fd < 0) {
+                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
+                        continue;
+                }
+
+                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
+                if (r < 0)
+                        return r;
+                if (r == 0) /* inode already seen or otherwise not relevant */
+                        continue;
+
+                j = path_join(full, de->d_name);
+                if (!j)
+                        return log_oom();
+
+                if (find_addon_sections(fd, j, &cmdline) < 0)
+                        continue;
+
+                location = strdup(j);
+                if (!location)
+                        return log_oom();
+
+                r = insert_boot_entry_addon(addons, TAKE_PTR(location), TAKE_PTR(cmdline));
+                if (r < 0) {
+                        free(addons);
+                        return r;
+                }
+        }
+        return 0;
+}
+
+static int boot_entries_find_unified_local_addons(
+                BootConfig *config,
+                int d_fd,
+                const char *d_name,
+                const char *root,
+                BootEntry *ret) {
+
+        _cleanup_free_ char *addon_dir = NULL;
+
+        assert(ret);
+
+        addon_dir = strjoin(d_name, ".extra.d");
+        if (!addon_dir)
+                return log_oom();
+
+        return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
+}
+
 static int boot_entries_find_unified(
                 BootConfig *config,
                 const char *root,
@@ -905,6 +1030,11 @@ static int boot_entries_find_unified(
                 if (r < 0)
                         continue;
 
+                /* look for .efi.extra.d */
+                r = boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries);
+                if (r < 0)
+                        continue;
+
                 config->n_entries++;
         }
 
@@ -1297,13 +1427,108 @@ static void boot_entry_file_list(
                 *ret_status = status;
 }
 
+static void print_addon(
+                BootEntryAddon *addon,
+                const char *addon_str) {
+
+        printf("  %s: %s\n", addon_str, addon->location);
+        printf("      cmdline: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), addon->cmdline);
+}
+
+static int print_cmdline(const BootEntry *e) {
+        _cleanup_free_ char *final_cmdline = NULL;
+
+        assert(e);
+
+        if (!strv_isempty(e->options)) {
+                _cleanup_free_ char *t = NULL, *t2 = NULL;
+                _cleanup_strv_free_ char **ts = NULL;
+
+                t = strv_join(e->options, " ");
+                if (!t)
+                        return log_oom();
+
+                ts = strv_split_newlines(t);
+                if (!ts)
+                        return log_oom();
+
+                t2 = strv_join(ts, "\n              ");
+                if (!t2)
+                        return log_oom();
+
+                printf("  ukiCmdline: %s\n", t2);
+                final_cmdline = TAKE_PTR(t2);
+        }
+
+        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.count) {
+                /* Add space at the beginning of addon_str to align it correctly */
+                print_addon(addon, " localAddon");
+                if (!strextend(&final_cmdline, " ", addon->cmdline))
+                        return log_oom();
+        }
+
+        if (final_cmdline)
+                printf(" finalCmdline: %s\n", final_cmdline);
+
+        return 0;
+}
+
+static int json_addon(
+                BootEntryAddon *addon,
+                const char *addon_str,
+                JsonVariant **array) {
+
+        int r;
+
+        r = json_variant_append_arrayb(array,
+                        JSON_BUILD_OBJECT(
+                                JSON_BUILD_PAIR(addon_str, JSON_BUILD_STRING(addon->location)),
+                                JSON_BUILD_PAIR("cmdline", JSON_BUILD_STRING(addon->cmdline))));
+        if (r < 0)
+                return log_oom();
+
+        return 0;
+}
+
+static int json_cmdline(const BootEntry *e, JsonVariant **v) {
+        _cleanup_free_ char *final_cmdline = NULL, *def_cmdline = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *addons_array = NULL;
+        int r;
+
+        assert(e);
+
+        if (!strv_isempty(e->options)) {
+                def_cmdline = strv_join(e->options, " ");
+                if (!def_cmdline)
+                        return log_oom();
+                final_cmdline = TAKE_PTR(def_cmdline);
+        }
+
+        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.count) {
+                r = json_addon(addon, "localAddon", &addons_array);
+                if (r < 0)
+                        return r;
+                if (!strextend(&final_cmdline, " ", addon->cmdline))
+                        return log_oom();
+        }
+
+        r = json_variant_merge_objectb(
+                v, JSON_BUILD_OBJECT(
+                                JSON_BUILD_PAIR_CONDITION(def_cmdline, "ukiCmdline", JSON_BUILD_STRING(def_cmdline)),
+                                JSON_BUILD_PAIR("addons", JSON_BUILD_VARIANT(addons_array)),
+                                JSON_BUILD_PAIR_CONDITION(final_cmdline, "finalCmdline", JSON_BUILD_STRING(final_cmdline))));
+        if (r < 0)
+                return log_oom();
+        return 0;
+}
+
 int show_boot_entry(
                 const BootEntry *e,
                 bool show_as_default,
                 bool show_as_selected,
                 bool show_reported) {
 
-        int status = 0;
+        int status = 0, r = 0;
 
         /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
            boot entry itself. */
@@ -1382,24 +1607,9 @@ int show_boot_entry(
                                      *s,
                                      &status);
 
-        if (!strv_isempty(e->options)) {
-                _cleanup_free_ char *t = NULL, *t2 = NULL;
-                _cleanup_strv_free_ char **ts = NULL;
-
-                t = strv_join(e->options, " ");
-                if (!t)
-                        return log_oom();
-
-                ts = strv_split_newlines(t);
-                if (!ts)
-                        return log_oom();
-
-                t2 = strv_join(ts, "\n              ");
-                if (!t2)
-                        return log_oom();
-
-                printf("      options: %s\n", t2);
-        }
+        r = print_cmdline(e);
+        if (r < 0)
+                return r;
 
         if (e->device_tree)
                 boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
@@ -1422,16 +1632,9 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
                 _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
 
                 for (size_t i = 0; i < config->n_entries; i++) {
-                        _cleanup_free_ char *opts = NULL;
                         const BootEntry *e = config->entries + i;
                         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
 
-                        if (!strv_isempty(e->options)) {
-                                opts = strv_join(e->options, " ");
-                                if (!opts)
-                                        return log_oom();
-                        }
-
                         r = json_variant_merge_objectb(
                                         &v, JSON_BUILD_OBJECT(
                                                        JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
@@ -1444,7 +1647,6 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
                                                        JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)),
                                                        JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)),
                                                        JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)),
-                                                       JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)),
                                                        JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)),
                                                        JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)),
                                                        JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)),
@@ -1453,6 +1655,10 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
                         if (r < 0)
                                 return log_oom();
 
+                        r = json_cmdline(e, &v);
+                        if (r < 0)
+                                return log_oom();
+
                         /* Sanitizers (only memory sanitizer?) do not like function call with too many
                          * arguments and trigger false positive warnings. Let's not add too many json objects
                          * at once. */
index ddd149eadbbf84a15f8c4656baae81a8f7cb84b6..141388143c966254825bd49d450102787992dd1c 100644 (file)
@@ -20,6 +20,18 @@ typedef enum BootEntryType {
         _BOOT_ENTRY_TYPE_INVALID = -EINVAL,
 } BootEntryType;
 
+typedef struct BootEntryAddon {
+        char *location;
+        char *cmdline;
+} BootEntryAddon;
+
+typedef struct BootEntryAddons {
+        BootEntryAddon *items;
+        size_t count;
+} BootEntryAddons;
+
+BootEntryAddon* boot_entry_addon_free(BootEntryAddon *t);
+
 typedef struct BootEntry {
         BootEntryType type;
         bool reported_by_loader;
@@ -34,6 +46,7 @@ typedef struct BootEntry {
         char *machine_id;
         char *architecture;
         char **options;
+        BootEntryAddons local_addons;
         char *kernel;        /* linux is #defined to 1, yikes! */
         char *efi;
         char **initrd;