#include "alloc-util.h"
#include "blkid-util.h"
+#include "bootspec.h"
#include "copy.h"
#include "dirent-util.h"
#include "efivars.h"
#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"
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;
"\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",
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;
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 },
--- /dev/null
+/***
+ 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;
+}