]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bootctl: add listing of loader entries
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 17 Oct 2017 16:23:16 +0000 (18:23 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 7 Nov 2017 14:14:21 +0000 (15:14 +0100)
man/bootctl.xml
src/boot/bootctl.c
src/shared/bootspec.c [new file with mode: 0644]
src/shared/bootspec.h [new file with mode: 0644]
src/shared/meson.build

index 675e0174e97aeb5744bedf69e20b182d8b1c358c..ae4e62cc299445fbf57c69d091c1c1ae824fb4cc 100644 (file)
@@ -49,6 +49,9 @@
     <cmdsynopsis>
       <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg> status</command>
     </cmdsynopsis>
+    <cmdsynopsis>
+      <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg> list</command>
+    </cmdsynopsis>
     <cmdsynopsis>
       <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg> update</command>
     </cmdsynopsis>
@@ -71,6 +74,9 @@
     currently installed versions of the boot loader binaries and
     all current EFI boot variables.</para>
 
+    <para><command>bootctl list</command> displays all configured boot loader entries.
+    </para>
+
     <para><command>bootctl update</command> updates all installed versions of systemd-boot, if the current version is
     newer than the version installed in the EFI system partition. This also includes the EFI default/fallback loader at
     <filename>/EFI/BOOT/BOOT*.EFI</filename>. A systemd-boot entry in the EFI boot variables is created if there is no
index 85f3b42c482b5f6568f35358b3c2de9a6f0c2d8d..e632d9bc5d06995527c9be10059fef0cb0b3da42 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "alloc-util.h"
 #include "blkid-util.h"
+#include "bootspec.h"
 #include "copy.h"
 #include "dirent-util.h"
 #include "efivars.h"
@@ -49,6 +50,7 @@
 #include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "terminal-util.h"
 #include "umask-util.h"
 #include "util.h"
 #include "verbs.h"
@@ -442,6 +444,54 @@ static int status_variables(void) {
         return 0;
 }
 
+static int status_entries(const char *esp_path, sd_id128_t partition) {
+        int r;
+
+        _cleanup_(boot_config_free) BootConfig config = {};
+
+        printf("Default Boot Entry:\n");
+
+        r = boot_entries_load_config(esp_path, &config);
+        if (r < 0)
+                return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m",
+                                       esp_path);
+
+        if (config.default_entry < 0)
+                printf("%zu entries, no entry suitable as default", config.n_entries);
+        else {
+                const BootEntry *e = &config.entries[config.default_entry];
+
+                printf("        title: %s\n", strna(e->title));
+                if (e->version)
+                        printf("      version: %s\n", e->version);
+                if (e->kernel)
+                        printf("        linux: %s\n", e->kernel);
+                if (!strv_isempty(e->initrd)) {
+                        _cleanup_free_ char *t;
+
+                        t = strv_join(e->initrd, " ");
+                        if (!t)
+                                return log_oom();
+
+                        printf("       initrd: %s\n", t);
+                }
+                if (!strv_isempty(e->options)) {
+                        _cleanup_free_ char *t;
+
+                        t = strv_join(e->options, " ");
+                        if (!t)
+                                return log_oom();
+
+                        printf("      options: %s\n", t);
+                }
+                if (e->device_tree)
+                        printf("   devicetree: %s\n", e->device_tree);
+                puts("");
+        }
+
+        return 0;
+}
+
 static int compare_product(const char *a, const char *b) {
         size_t x, y;
 
@@ -965,6 +1015,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "\n"
                "Commands:\n"
                "     status          Show status of installed systemd-boot and EFI variables\n"
+               "     list            List boot entries\n"
                "     install         Install systemd-boot to the ESP and EFI variables\n"
                "     update          Update systemd-boot in the ESP and EFI variables\n"
                "     remove          Remove systemd-boot from the ESP and EFI variables\n",
@@ -1101,9 +1152,76 @@ static int verb_status(int argc, char *argv[], void *userdata) {
                         r2 = r;
         }
 
+        r = status_entries(arg_path, uuid);
+        if (r < 0)
+                r2 = r;
+
         return r2;
 }
 
+static int verb_list(int argc, char *argv[], void *userdata) {
+        sd_id128_t uuid = SD_ID128_NULL;
+        int r;
+        unsigned n;
+
+        _cleanup_(boot_config_free) BootConfig config = {};
+
+        r = find_esp(NULL, NULL, NULL, &uuid);
+        if (r < 0)
+                return r;
+
+        r = boot_entries_load_config(arg_path, &config);
+        if (r < 0)
+                return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m",
+                                       arg_path);
+
+        printf("Available boot entries:\n");
+
+        for (n = 0; n < config.n_entries; n++) {
+                const BootEntry *e = &config.entries[n];
+
+                printf("        title: %s%s%s%s%s%s\n",
+                       ansi_highlight(),
+                       strna(e->title),
+                       ansi_normal(),
+                       ansi_highlight_green(),
+                       n == config.default_entry ? " (default)" : "",
+                       ansi_normal());
+                if (e->version)
+                        printf("      version: %s\n", e->version);
+                if (e->machine_id)
+                        printf("   machine-id: %s\n", e->machine_id);
+                if (e->architecture)
+                        printf(" architecture: %s\n", e->architecture);
+                if (e->kernel)
+                        printf("        linux: %s\n", e->kernel);
+                if (!strv_isempty(e->initrd)) {
+                        _cleanup_free_ char *t;
+
+                        t = strv_join(e->initrd, " ");
+                        if (!t)
+                                return log_oom();
+
+                        printf("       initrd: %s\n", t);
+                }
+                if (!strv_isempty(e->options)) {
+                        _cleanup_free_ char *t;
+
+                        t = strv_join(e->options, " ");
+                        if (!t)
+                                return log_oom();
+
+                        printf("      options: %s\n", t);
+                }
+                if (e->device_tree)
+                        printf("   devicetree: %s\n", e->device_tree);
+
+                puts("");
+        }
+
+        return 0;
+}
+
 static int verb_install(int argc, char *argv[], void *userdata) {
 
         sd_id128_t uuid = SD_ID128_NULL;
@@ -1173,6 +1291,7 @@ static int bootctl_main(int argc, char *argv[]) {
         static const Verb verbs[] = {
                 { "help",            VERB_ANY, VERB_ANY, 0,            help         },
                 { "status",          VERB_ANY, 1,        VERB_DEFAULT, verb_status  },
+                { "list",            VERB_ANY, 1,        0,            verb_list    },
                 { "install",         VERB_ANY, 1,        0,            verb_install },
                 { "update",          VERB_ANY, 1,        0,            verb_install },
                 { "remove",          VERB_ANY, 1,        0,            verb_remove  },
diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c
new file mode 100644 (file)
index 0000000..8394f4f
--- /dev/null
@@ -0,0 +1,335 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2017 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "bootspec.h"
+#include "conf-files.h"
+#include "def.h"
+#include "efivars.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "string-util.h"
+#include "strv.h"
+
+void boot_entry_free(BootEntry *entry) {
+        free(entry->filename);
+
+        free(entry->title);
+        free(entry->version);
+        free(entry->machine_id);
+        free(entry->architecture);
+        strv_free(entry->options);
+        free(entry->kernel);
+        free(entry->efi);
+        strv_free(entry->initrd);
+        free(entry->device_tree);
+}
+
+int boot_entry_load(const char *path, BootEntry *entry) {
+        _cleanup_fclose_ FILE *f = NULL;
+        unsigned line = 1;
+        _cleanup_(boot_entry_free) BootEntry tmp = {};
+        int r;
+
+        f = fopen(path, "re");
+        if (!f)
+                return log_error_errno(errno, "Failed to open \"%s\": %m", path);
+
+        tmp.filename = strdup(basename(path));
+        if (!tmp.filename)
+                return log_oom();
+
+        for (;;) {
+                _cleanup_free_ char *buf = NULL;
+                char *p;
+
+                r = read_line(f, LONG_LINE_MAX, &buf);
+                if (r == 0)
+                        break;
+                if (r == -ENOBUFS)
+                        return log_error_errno(r, "%s:%u: Line too long", path, line);
+                if (r < 0)
+                        return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
+
+                line++;
+
+                if (IN_SET(*strstrip(buf), '#', '\0'))
+                        continue;
+
+                p = strchr(buf, ' ');
+                if (!p) {
+                        log_warning("%s:%u: Bad syntax", path, line);
+                        continue;
+                }
+                *p = '\0';
+                p = strstrip(p + 1);
+
+                if (streq(buf, "title"))
+                        r = free_and_strdup(&tmp.title, p);
+                else if (streq(buf, "version"))
+                        r = free_and_strdup(&tmp.version, p);
+                else if (streq(buf, "machine-id"))
+                        r = free_and_strdup(&tmp.machine_id, p);
+                else if (streq(buf, "architecture"))
+                        r = free_and_strdup(&tmp.architecture, p);
+                else if (streq(buf, "options"))
+                        r = strv_extend(&tmp.options, p);
+                else if (streq(buf, "linux"))
+                        r = free_and_strdup(&tmp.kernel, p);
+                else if (streq(buf, "efi"))
+                        r = free_and_strdup(&tmp.efi, p);
+                else if (streq(buf, "initrd"))
+                        r = strv_extend(&tmp.initrd, p);
+                else if (streq(buf, "devicetree"))
+                        r = free_and_strdup(&tmp.device_tree, p);
+                else {
+                        log_notice("%s:%u: Unknown line \"%s\"", path, line, buf);
+                        continue;
+                }
+                if (r < 0)
+                        return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
+        }
+
+        *entry = tmp;
+        tmp = (BootEntry) {};
+        return 0;
+}
+
+void boot_config_free(BootConfig *config) {
+        unsigned i;
+
+        free(config->default_pattern);
+        free(config->timeout);
+        free(config->editor);
+
+        free(config->entry_oneshot);
+        free(config->entry_default);
+
+        for (i = 0; i < config->n_entries; i++)
+                boot_entry_free(config->entries + i);
+        free(config->entries);
+}
+
+int boot_loader_read_conf(const char *path, BootConfig *config) {
+        _cleanup_fclose_ FILE *f = NULL;
+        unsigned line = 1;
+        int r;
+
+        f = fopen(path, "re");
+        if (!f)
+                return log_error_errno(errno, "Failed to open \"%s\": %m", path);
+
+        for (;;) {
+                _cleanup_free_ char *buf = NULL;
+                char *p;
+
+                r = read_line(f, LONG_LINE_MAX, &buf);
+                if (r == 0)
+                        break;
+                if (r == -ENOBUFS)
+                        return log_error_errno(r, "%s:%u: Line too long", path, line);
+                if (r < 0)
+                        return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
+
+                line++;
+
+                if (IN_SET(*strstrip(buf), '#', '\0'))
+                        continue;
+
+                p = strchr(buf, ' ');
+                if (!p) {
+                        log_warning("%s:%u: Bad syntax", path, line);
+                        continue;
+                }
+                *p = '\0';
+                p = strstrip(p + 1);
+
+                if (streq(buf, "default"))
+                        r = free_and_strdup(&config->default_pattern, p);
+                else if (streq(buf, "timeout"))
+                        r = free_and_strdup(&config->timeout, p);
+                else if (streq(buf, "editor"))
+                        r = free_and_strdup(&config->editor, p);
+                else {
+                        log_notice("%s:%u: Unknown line \"%s\"", path, line, buf);
+                        continue;
+                }
+                if (r < 0)
+                        return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
+        }
+
+        return 0;
+}
+
+/* This is a direct translation of str_verscmp from boot.c */
+static bool is_digit(int c) {
+        return c >= '0' && c <= '9';
+}
+
+static int c_order(int c) {
+        if (c == '\0')
+                return 0;
+        if (is_digit(c))
+                return 0;
+        else if ((c >= 'a') && (c <= 'z'))
+                return c;
+        else
+                return c + 0x10000;
+}
+
+static int str_verscmp(const char *s1, const char *s2) {
+        const char *os1 = s1;
+        const char *os2 = s2;
+
+        while (*s1 || *s2) {
+                int first;
+
+                while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
+                        int order;
+
+                        order = c_order(*s1) - c_order(*s2);
+                        if (order)
+                                return order;
+                        s1++;
+                        s2++;
+                }
+
+                while (*s1 == '0')
+                        s1++;
+                while (*s2 == '0')
+                        s2++;
+
+                first = 0;
+                while (is_digit(*s1) && is_digit(*s2)) {
+                        if (first == 0)
+                                first = *s1 - *s2;
+                        s1++;
+                        s2++;
+                }
+
+                if (is_digit(*s1))
+                        return 1;
+                if (is_digit(*s2))
+                        return -1;
+
+                if (first != 0)
+                        return first;
+        }
+
+        return strcmp(os1, os2);
+}
+
+static int boot_entry_compare(const void *a, const void *b) {
+        const BootEntry *aa = a;
+        const BootEntry *bb = b;
+
+        return str_verscmp(aa->filename, bb->filename);
+}
+
+int boot_entries_find(const char *dir, BootEntry **entries, size_t *n_entries) {
+        _cleanup_strv_free_ char **files = NULL;
+        char **f;
+        int r;
+
+        BootEntry *array = NULL;
+        size_t n_allocated = 0, n = 0;
+
+        r = conf_files_list(&files, ".conf", NULL, 0, dir, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
+
+        STRV_FOREACH(f, files) {
+                if (!GREEDY_REALLOC0(array, n_allocated, n + 1))
+                        return log_oom();
+
+                r = boot_entry_load(*f, array + n);
+                if (r < 0)
+                        continue;
+
+                n++;
+        }
+
+        qsort_safe(array, n, sizeof(BootEntry), boot_entry_compare);
+
+        *entries = array;
+        *n_entries = n;
+        return 0;
+}
+
+int boot_entries_select_default(const BootConfig *config) {
+        int i;
+
+        if (config->entry_oneshot)
+                for (i = config->n_entries - 1; i >= 0; i--)
+                        if (streq(config->entry_oneshot, config->entries[i].filename)) {
+                                log_debug("Found default: filename \"%s\" is matched by LoaderEntryOneShot",
+                                          config->entries[i].filename);
+                                return i;
+                        }
+
+        if (config->entry_default)
+                for (i = config->n_entries - 1; i >= 0; i--)
+                        if (streq(config->entry_default, config->entries[i].filename)) {
+                                log_debug("Found default: filename \"%s\" is matched by LoaderEntryDefault",
+                                          config->entries[i].filename);
+                                return i;
+                        }
+
+        if (config->default_pattern)
+                for (i = config->n_entries - 1; i >= 0; i--)
+                        if (fnmatch(config->default_pattern, config->entries[i].filename, FNM_CASEFOLD) == 0) {
+                                log_debug("Found default: filename \"%s\" is matched by pattern \"%s\"",
+                                          config->entries[i].filename, config->default_pattern);
+                                return i;
+                        }
+
+        if (config->n_entries > 0)
+                log_debug("Found default: last entry \"%s\"", config->entries[i].filename);
+        else
+                log_debug("Found no default boot entry :(");
+        return config->n_entries - 1; /* -1 means "no default" */
+}
+
+int boot_entries_load_config(const char *esp_path, BootConfig *config) {
+        const char *p;
+        int r;
+
+        p = strjoina(esp_path, "/loader/loader.conf");
+        r = boot_loader_read_conf(p, config);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read boot config from \"%s\": %m", p);
+
+        p = strjoina(esp_path, "/loader/entries");
+        r = boot_entries_find(p, &config->entries, &config->n_entries);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read boot entries from \"%s\": %m", p);
+
+        r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
+        if (r < 0 && r != -ENOENT)
+                return log_error_errno(r, "Failed to read EFI var \"LoaderEntryOneShot\": %m");
+
+        r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
+        if (r < 0 && r != -ENOENT)
+                return log_error_errno(r, "Failed to read EFI var \"LoaderEntryDefault\": %m");
+
+        config->default_entry = boot_entries_select_default(config);
+        return 0;
+}
diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h
new file mode 100644 (file)
index 0000000..ff2b90c
--- /dev/null
@@ -0,0 +1,58 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2017 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include <stdlib.h>
+
+typedef struct BootEntry {
+        char *filename;
+
+        char *title;
+        char *version;
+        char *machine_id;
+        char *architecture;
+        char **options;
+        char *kernel;        /* linux is #defined to 1, yikes! */
+        char *efi;
+        char **initrd;
+        char *device_tree;
+} BootEntry;
+
+typedef struct BootConfig {
+        char *default_pattern;
+        char *timeout;
+        char *editor;
+
+        char *entry_oneshot;
+        char *entry_default;
+
+        BootEntry *entries;
+        size_t n_entries;
+        ssize_t default_entry;
+} BootConfig;
+
+void boot_entry_free(BootEntry *entry);
+int boot_entry_load(const char *path, BootEntry *entry);
+int boot_entries_find(const char *dir, BootEntry **entries, size_t *n_entries);
+int boot_entries_select_default(const BootConfig *config);
+
+int boot_loader_read_conf(const char *path, BootConfig *config);
+void boot_config_free(BootConfig *config);
+int boot_entries_load_config(const char *esp_path, BootConfig *config);
index 883821352e67fdcf4c7e6d21a3c0ae3540f0d4eb..dfa94cfbdb78d992915a4ed60aea146503e350e9 100644 (file)
@@ -10,6 +10,8 @@ shared_sources = '''
         base-filesystem.h
         boot-timestamps.c
         boot-timestamps.h
+        bootspec.c
+        bootspec.h
         bus-unit-util.c
         bus-unit-util.h
         bus-util.c