From a2fa605a653faff46a39f069e28441e89b3db6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 20 Oct 2017 17:51:54 +0200 Subject: [PATCH 1/9] sd-boot: simplify the implementation of entry uniquification There's a slight change in implementation: we first try to append the version, then look for any non-unique pairs again. Before, we would only mark as possibly unique those entries we changed. But if there are two entries that e.g. have the same title and version, but only one has the machine-id specified, we would treat one of them as still non-unique after appending the machine-id to the other one. So the new algorithm is simpler but more robust (not that it matters). --- src/boot/efi/boot.c | 70 ++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 12176f1fe0..8e4c9d0395 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1305,10 +1305,29 @@ static VOID config_default_entry_select(Config *config) { config->idx_default = -1; } +static BOOLEAN find_nonunique(ConfigEntry **entries, UINTN entry_count) { + BOOLEAN non_unique = FALSE; + UINTN i, k; + + for (i = 0; i < entry_count; i++) + entries[i]->non_unique = FALSE; + + for (i = 0; i < entry_count; i++) + for (k = 0; k < entry_count; k++) { + if (i == k) + continue; + if (StrCmp(entries[i]->title_show, entries[k]->title_show) != 0) + continue; + + non_unique = entries[i]->non_unique = entries[k]->non_unique = TRUE; + } + + return non_unique; +} + /* generate a unique title, avoiding non-distinguishable menu entries */ static VOID config_title_generate(Config *config) { - UINTN i, k; - BOOLEAN unique; + UINTN i; /* set title */ for (i = 0; i < config->entry_count; i++) { @@ -1321,20 +1340,7 @@ static VOID config_title_generate(Config *config) { config->entries[i]->title_show = StrDuplicate(title); } - unique = TRUE; - for (i = 0; i < config->entry_count; i++) { - for (k = 0; k < config->entry_count; k++) { - if (i == k) - continue; - if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) - continue; - - unique = FALSE; - config->entries[i]->non_unique = TRUE; - config->entries[k]->non_unique = TRUE; - } - } - if (unique) + if (!find_nonunique(config->entries, config->entry_count)) return; /* add version to non-unique titles */ @@ -1349,23 +1355,9 @@ static VOID config_title_generate(Config *config) { s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->version); FreePool(config->entries[i]->title_show); config->entries[i]->title_show = s; - config->entries[i]->non_unique = FALSE; } - unique = TRUE; - for (i = 0; i < config->entry_count; i++) { - for (k = 0; k < config->entry_count; k++) { - if (i == k) - continue; - if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) - continue; - - unique = FALSE; - config->entries[i]->non_unique = TRUE; - config->entries[k]->non_unique = TRUE; - } - } - if (unique) + if (!find_nonunique(config->entries, config->entry_count)) return; /* add machine-id to non-unique titles */ @@ -1383,24 +1375,10 @@ static VOID config_title_generate(Config *config) { s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, m); FreePool(config->entries[i]->title_show); config->entries[i]->title_show = s; - config->entries[i]->non_unique = FALSE; FreePool(m); } - unique = TRUE; - for (i = 0; i < config->entry_count; i++) { - for (k = 0; k < config->entry_count; k++) { - if (i == k) - continue; - if (StrCmp(config->entries[i]->title_show, config->entries[k]->title_show) != 0) - continue; - - unique = FALSE; - config->entries[i]->non_unique = TRUE; - config->entries[k]->non_unique = TRUE; - } - } - if (unique) + if (!find_nonunique(config->entries, config->entry_count)) return; /* add file name to non-unique titles */ From 7e87c7d9149cb06a0e4cbe24e808edd906f71fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 17 Oct 2017 18:23:16 +0200 Subject: [PATCH 2/9] bootctl: add listing of loader entries --- man/bootctl.xml | 6 + src/boot/bootctl.c | 119 +++++++++++++++ src/shared/bootspec.c | 335 +++++++++++++++++++++++++++++++++++++++++ src/shared/bootspec.h | 58 +++++++ src/shared/meson.build | 2 + 5 files changed, 520 insertions(+) create mode 100644 src/shared/bootspec.c create mode 100644 src/shared/bootspec.h diff --git a/man/bootctl.xml b/man/bootctl.xml index 675e0174e9..ae4e62cc29 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -49,6 +49,9 @@ bootctl OPTIONS status + + bootctl OPTIONS list + bootctl OPTIONS update @@ -71,6 +74,9 @@ currently installed versions of the boot loader binaries and all current EFI boot variables. + bootctl list displays all configured boot loader entries. + + bootctl update 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 /EFI/BOOT/BOOT*.EFI. A systemd-boot entry in the EFI boot variables is created if there is no diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 85f3b42c48..e632d9bc5d 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -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 index 0000000000..8394f4fe35 --- /dev/null +++ b/src/shared/bootspec.c @@ -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 . +***/ + +#include + +#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 index 0000000000..ff2b90cd32 --- /dev/null +++ b/src/shared/bootspec.h @@ -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 . +***/ + +#pragma once + +#include + +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); diff --git a/src/shared/meson.build b/src/shared/meson.build index 883821352e..dfa94cfbdb 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -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 From 64f05708cf10f4a0f85ae0301e101c67ec87ba7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 20 Oct 2017 17:58:13 +0200 Subject: [PATCH 3/9] bootctl: show unique titles --- src/boot/bootctl.c | 4 +-- src/shared/bootspec.c | 70 +++++++++++++++++++++++++++++++++++++++++++ src/shared/bootspec.h | 5 ++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index e632d9bc5d..6b3bbc28fd 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -461,7 +461,7 @@ static int status_entries(const char *esp_path, sd_id128_t partition) { else { const BootEntry *e = &config.entries[config.default_entry]; - printf(" title: %s\n", strna(e->title)); + printf(" title: %s\n", boot_entry_title(e)); if (e->version) printf(" version: %s\n", e->version); if (e->kernel) @@ -1182,7 +1182,7 @@ static int verb_list(int argc, char *argv[], void *userdata) { printf(" title: %s%s%s%s%s%s\n", ansi_highlight(), - strna(e->title), + boot_entry_title(e), ansi_normal(), ansi_highlight_green(), n == config.default_entry ? " (default)" : "", diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 8394f4fe35..53b8184915 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -33,6 +33,7 @@ void boot_entry_free(BootEntry *entry) { free(entry->filename); free(entry->title); + free(entry->show_title); free(entry->version); free(entry->machine_id); free(entry->architecture); @@ -274,6 +275,71 @@ int boot_entries_find(const char *dir, BootEntry **entries, size_t *n_entries) { return 0; } +static bool find_nonunique(BootEntry *entries, size_t n_entries, bool *arr) { + unsigned i, j; + bool non_unique = false; + + for (i = 0; i < n_entries; i++) + arr[i] = false; + + for (i = 0; i < n_entries; i++) + for (j = 0; j < n_entries; j++) + if (i != j && streq(boot_entry_title(entries + i), + boot_entry_title(entries + j))) + non_unique = arr[i] = arr[j] = true; + + return non_unique; +} + +static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) { + char *s; + unsigned i; + int r; + bool arr[n_entries]; + + /* Find _all_ non-unique titles */ + if (!find_nonunique(entries, n_entries, arr)) + return 0; + + /* Add version to non-unique titles */ + for (i = 0; i < n_entries; i++) + if (arr[i] && entries[i].version) { + r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version); + if (r < 0) + return -ENOMEM; + + free_and_replace(entries[i].show_title, s); + } + + if (!find_nonunique(entries, n_entries, arr)) + return 0; + + /* Add machine-id to non-unique titles */ + for (i = 0; i < n_entries; i++) + if (arr[i] && entries[i].machine_id) { + r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id); + if (r < 0) + return -ENOMEM; + + free_and_replace(entries[i].show_title, s); + } + + if (!find_nonunique(entries, n_entries, arr)) + return 0; + + /* Add file name to non-unique titles */ + for (i = 0; i < n_entries; i++) + if (arr[i]) { + r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].filename); + if (r < 0) + return -ENOMEM; + + free_and_replace(entries[i].show_title, s); + } + + return 0; +} + int boot_entries_select_default(const BootConfig *config) { int i; @@ -322,6 +388,10 @@ int boot_entries_load_config(const char *esp_path, BootConfig *config) { if (r < 0) return log_error_errno(r, "Failed to read boot entries from \"%s\": %m", p); + r = boot_entries_uniquify(config->entries, config->n_entries); + if (r < 0) + return log_error_errno(r, "Failed to uniquify boot entries: %m"); + 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"); diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index ff2b90cd32..b45fdafd70 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -25,6 +25,7 @@ typedef struct BootEntry { char *filename; char *title; + char *show_title; char *version; char *machine_id; char *architecture; @@ -56,3 +57,7 @@ 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); + +static inline const char* boot_entry_title(const BootEntry *entry) { + return entry->show_title ?: entry->title ?: entry->filename; +} From af918182027c249aeae590b3adda82c972af286f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 20 Oct 2017 18:31:34 +0200 Subject: [PATCH 4/9] bootctl: move find_esp() to shared In preparation for use in systemctl. The original function that prints hints is renamed to find_esp_and_warn() to make its purpose clearer. --- src/boot/bootctl.c | 207 +++--------------------------------------- src/shared/bootspec.c | 200 ++++++++++++++++++++++++++++++++++++++++ src/shared/bootspec.h | 3 + 3 files changed, 217 insertions(+), 193 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 6b3bbc28fd..5ac9d0ccdf 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -59,201 +59,22 @@ static char *arg_path = NULL; static bool arg_touch_variables = true; -static int verify_esp( - bool searching, - const char *p, - uint32_t *ret_part, - uint64_t *ret_pstart, - uint64_t *ret_psize, - sd_id128_t *ret_uuid) { - - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - _cleanup_free_ char *t = NULL; - uint64_t pstart = 0, psize = 0; - struct stat st, st2; - const char *v, *t2; - struct statfs sfs; - sd_id128_t uuid = SD_ID128_NULL; - uint32_t part = 0; - bool quiet; +static int find_esp_and_warn(uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { int r; - assert(p); - - /* Non-root user can run only `bootctl status`, then if error occured in the following, it does not cause any issues. - * So, let's silence the error messages. */ - quiet = (geteuid() != 0); - - if (statfs(p, &sfs) < 0) { - - /* If we are searching for the mount point, don't generate a log message if we can't find the path */ - if (errno == ENOENT && searching) - return -ENOENT; - - return log_full_errno(quiet && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, - "Failed to check file system type of \"%s\": %m", p); - } - - if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) { - - if (searching) - return -EADDRNOTAVAIL; - - log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); - return -ENODEV; - } - - if (stat(p, &st) < 0) - return log_full_errno(quiet && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, - "Failed to determine block device node of \"%s\": %m", p); - - if (major(st.st_dev) == 0) { - log_error("Block device node of %p is invalid.", p); - return -ENODEV; - } - - t2 = strjoina(p, "/.."); - r = stat(t2, &st2); - if (r < 0) - return log_full_errno(quiet && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, - "Failed to determine block device node of parent of \"%s\": %m", p); - - if (st.st_dev == st2.st_dev) { - log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p); - return -ENODEV; - } - - /* In a container we don't have access to block devices, skip this part of the verification, we trust the - * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */ - if (detect_container() > 0 || geteuid() != 0) - goto finish; - - r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev)); - if (r < 0) - return log_oom(); - - errno = 0; - b = blkid_new_probe_from_filename(t); - if (!b) - return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p); - - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2) { - log_error("File system \"%s\" is ambiguous.", p); - return -ENODEV; - } else if (r == 1) { - log_error("File system \"%s\" does not contain a label.", p); - return -ENODEV; - } else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe file system \"%s\": %m", p); - - errno = 0; - r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe file system type \"%s\": %m", p); - if (!streq(v, "vfat")) { - log_error("File system \"%s\" is not FAT.", p); - return -ENODEV; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition scheme \"%s\": %m", p); - if (!streq(v, "gpt")) { - log_error("File system \"%s\" is not on a GPT partition table.", p); - return -ENODEV; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p); - if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) { - log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p); - return -ENODEV; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition entry UUID \"%s\": %m", p); - r = sd_id128_from_string(v, &uuid); - if (r < 0) { - log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v); - return -EIO; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition number \"%s\": m", p); - r = safe_atou32(v, &part); - if (r < 0) - return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field."); - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition offset \"%s\": %m", p); - r = safe_atou64(v, &pstart); - if (r < 0) - return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field."); - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition size \"%s\": %m", p); - r = safe_atou64(v, &psize); - if (r < 0) - return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field."); - -finish: - if (ret_part) - *ret_part = part; - if (ret_pstart) - *ret_pstart = pstart; - if (ret_psize) - *ret_psize = psize; - if (ret_uuid) - *ret_uuid = uuid; + r = find_esp(&arg_path, part, pstart, psize, uuid); + if (r == -ENOENT) + return log_error_errno(r, + "Couldn't find EFI system partition. It is recommended to mount it to /boot.\n" + "Alternatively, use --path= to specify path to mount point."); + else if (r < 0) + return log_error_errno(r, + "Couldn't find EFI system partition: %m"); + log_info("Using EFI System Partition at %s.", arg_path); return 0; } -static int find_esp(uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { - const char *path; - int r; - - if (arg_path) - return verify_esp(false, arg_path, part, pstart, psize, uuid); - - FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") { - - r = verify_esp(true, path, part, pstart, psize, uuid); - if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ - continue; - if (r < 0) - return r; - - arg_path = strdup(path); - if (!arg_path) - return log_oom(); - - log_info("Using EFI System Partition at %s.", path); - return 0; - } - - log_error("Couldn't find EFI system partition. It is recommended to mount it to /boot. Alternatively, use --path= to specify path to mount point."); - return -ENOENT; -} - /* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ static int get_file_version(int fd, char **v) { struct stat st; @@ -1096,7 +917,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { sd_id128_t uuid = SD_ID128_NULL; int r, r2; - r2 = find_esp(NULL, NULL, NULL, &uuid); + r2 = find_esp_and_warn(NULL, NULL, NULL, &uuid); if (is_efi_boot()) { _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL; @@ -1166,7 +987,7 @@ static int verb_list(int argc, char *argv[], void *userdata) { _cleanup_(boot_config_free) BootConfig config = {}; - r = find_esp(NULL, NULL, NULL, &uuid); + r = find_esp_and_warn(NULL, NULL, NULL, &uuid); if (r < 0) return r; @@ -1234,7 +1055,7 @@ static int verb_install(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = find_esp(&part, &pstart, &psize, &uuid); + r = find_esp_and_warn(&part, &pstart, &psize, &uuid); if (r < 0) return r; @@ -1269,7 +1090,7 @@ static int verb_remove(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = find_esp(NULL, NULL, NULL, &uuid); + r = find_esp_and_warn(NULL, NULL, NULL, &uuid); if (r < 0) return r; diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 53b8184915..9f80db068d 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -18,16 +18,21 @@ ***/ #include +#include #include "alloc-util.h" +#include "blkid-util.h" #include "bootspec.h" #include "conf-files.h" #include "def.h" #include "efivars.h" #include "fd-util.h" #include "fileio.h" +#include "parse-util.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" +#include "virt.h" void boot_entry_free(BootEntry *entry) { free(entry->filename); @@ -403,3 +408,198 @@ int boot_entries_load_config(const char *esp_path, BootConfig *config) { config->default_entry = boot_entries_select_default(config); return 0; } + +/********************************************************************************/ + +static int verify_esp( + bool searching, + const char *p, + uint32_t *ret_part, + uint64_t *ret_pstart, + uint64_t *ret_psize, + sd_id128_t *ret_uuid) { + + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + _cleanup_free_ char *t = NULL; + uint64_t pstart = 0, psize = 0; + struct stat st, st2; + const char *v, *t2; + struct statfs sfs; + sd_id128_t uuid = SD_ID128_NULL; + uint32_t part = 0; + bool quiet; + int r; + + assert(p); + + /* Non-root user can only check the status, so if an error occured in the following, + * it does not cause any issues. Let's silence the error messages. */ + quiet = geteuid() != 0; + + if (statfs(p, &sfs) < 0) { + /* If we are searching for the mount point, don't generate a log message if we can't find the path */ + if (errno == ENOENT && searching) + return -ENOENT; + + return log_full_errno(quiet && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, + "Failed to check file system type of \"%s\": %m", p); + } + + if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) { + if (searching) + return -EADDRNOTAVAIL; + + log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); + return -ENODEV; + } + + if (stat(p, &st) < 0) + return log_full_errno(quiet && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, + "Failed to determine block device node of \"%s\": %m", p); + + if (major(st.st_dev) == 0) { + log_error("Block device node of %p is invalid.", p); + return -ENODEV; + } + + t2 = strjoina(p, "/.."); + r = stat(t2, &st2); + if (r < 0) + return log_full_errno(quiet && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, + "Failed to determine block device node of parent of \"%s\": %m", p); + + if (st.st_dev == st2.st_dev) { + log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p); + return -ENODEV; + } + + /* In a container we don't have access to block devices, skip this part of the verification, we trust the + * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */ + if (detect_container() > 0 || geteuid() != 0) + goto finish; + + r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev)); + if (r < 0) + return log_oom(); + + errno = 0; + b = blkid_new_probe_from_filename(t); + if (!b) + return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p); + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2) { + log_error("File system \"%s\" is ambiguous.", p); + return -ENODEV; + } else if (r == 1) { + log_error("File system \"%s\" does not contain a label.", p); + return -ENODEV; + } else if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe file system \"%s\": %m", p); + + errno = 0; + r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe file system type \"%s\": %m", p); + if (!streq(v, "vfat")) { + log_error("File system \"%s\" is not FAT.", p); + return -ENODEV; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe partition scheme \"%s\": %m", p); + if (!streq(v, "gpt")) { + log_error("File system \"%s\" is not on a GPT partition table.", p); + return -ENODEV; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p); + if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) { + log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p); + return -ENODEV; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe partition entry UUID \"%s\": %m", p); + r = sd_id128_from_string(v, &uuid); + if (r < 0) { + log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v); + return -EIO; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe partition number \"%s\": m", p); + r = safe_atou32(v, &part); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field."); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe partition offset \"%s\": %m", p); + r = safe_atou64(v, &pstart); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field."); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe partition size \"%s\": %m", p); + r = safe_atou64(v, &psize); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field."); + +finish: + if (ret_part) + *ret_part = part; + if (ret_pstart) + *ret_pstart = pstart; + if (ret_psize) + *ret_psize = psize; + if (ret_uuid) + *ret_uuid = uuid; + + return 0; +} + +int find_esp(char **path, + uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { + + const char *p; + int r; + + if (*path) + return verify_esp(false, *path, part, pstart, psize, uuid); + + FOREACH_STRING(p, "/efi", "/boot", "/boot/efi") { + + r = verify_esp(true, p, part, pstart, psize, uuid); + if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ + continue; + if (r < 0) + return r; + + *path = strdup(p); + if (!*path) + return log_oom(); + + return 0; + } + + return -ENOENT; +} diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index b45fdafd70..391c4a49af 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -61,3 +61,6 @@ int boot_entries_load_config(const char *esp_path, BootConfig *config); static inline const char* boot_entry_title(const BootEntry *entry) { return entry->show_title ?: entry->title ?: entry->filename; } + +int find_esp(char **path, + uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid); From 1ae17672a23a48185ac951c884beeeab874df22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 21 Oct 2017 10:47:52 +0200 Subject: [PATCH 5/9] systemctl: add --dry-run argument --- man/systemctl.xml | 13 ++++++++++ src/systemctl/systemctl.c | 54 +++++++++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index bb3fc1763c..c8adda7af2 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -353,6 +353,19 @@ + + + + + Just print what would be done. Currently supported by verbs + halt, poweroff, reboot, + kexec, suspend, + hibernate, hybrid-sleep, + default, rescue, + emergency, and exit. + + + diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index e4d9ddf4b6..b8c0718686 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -130,7 +130,7 @@ static bool arg_no_reload = false; static bool arg_value = false; static bool arg_show_types = false; static bool arg_ignore_inhibitors = false; -static bool arg_dry = false; +static bool arg_dry_run = false; static bool arg_quiet = false; static bool arg_full = false; static bool arg_recursive = false; @@ -256,6 +256,9 @@ static void ask_password_agent_open_if_enabled(void) { /* Open the password agent as a child process if necessary */ + if (arg_dry_run) + return; + if (!arg_ask_password) return; @@ -2938,7 +2941,11 @@ static int start_unit_one( return log_error_errno(r, "Failed to add match for PropertiesChanged signal: %m"); } - log_debug("Calling manager for %s on %s, %s", method, name, mode); + log_debug("%s manager for %s on %s, %s", + arg_dry_run ? "Would call" : "Calling", + method, name, mode); + if (arg_dry_run) + return 0; r = sd_bus_call_method( bus, @@ -5592,6 +5599,9 @@ static int trivial_method(int argc, char *argv[], void *userdata) { sd_bus *bus; int r; + if (arg_dry_run) + return 0; + r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) return r; @@ -7146,6 +7156,7 @@ static void systemctl_help(void) { " --kill-who=WHO Who to send signal to\n" " -s --signal=SIGNAL Which signal to send\n" " --now Start or stop unit in addition to enabling or disabling it\n" + " --dry-run Only print what would be done\n" " -q --quiet Suppress output\n" " --wait For (re)start, wait until service stopped again\n" " --no-block Do not wait until operation finished\n" @@ -7392,6 +7403,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_REVERSE, ARG_AFTER, ARG_BEFORE, + ARG_DRY_RUN, ARG_SHOW_TYPES, ARG_IRREVERSIBLE, ARG_IGNORE_DEPENDENCIES, @@ -7447,6 +7459,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { "dry-run", no_argument, NULL, ARG_DRY_RUN }, { "quiet", no_argument, NULL, 'q' }, { "root", required_argument, NULL, ARG_ROOT }, { "force", no_argument, NULL, ARG_FORCE }, @@ -7656,6 +7669,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; + case ARG_DRY_RUN: + arg_dry_run = true; + break; + case 'q': arg_quiet = true; break; @@ -7861,7 +7878,7 @@ static int halt_parse_argv(int argc, char *argv[]) { break; case 'w': - arg_dry = true; + arg_dry_run = true; break; case 'd': @@ -8004,7 +8021,7 @@ static int shutdown_parse_argv(int argc, char *argv[]) { break; case 'k': - arg_dry = true; + arg_dry_run = true; break; case ARG_NO_WALL: @@ -8395,24 +8412,28 @@ static int halt_now(enum action a) { /* The kernel will automaticall flush ATA disks and suchlike * on reboot(), but the file systems need to be synce'd * explicitly in advance. */ - if (!arg_no_sync) + if (!arg_no_sync && !arg_dry_run) (void) sync(); - /* Make sure C-A-D is handled by the kernel from this point - * on... */ - (void) reboot(RB_ENABLE_CAD); + /* Make sure C-A-D is handled by the kernel from this point on... */ + if (!arg_dry_run) + (void) reboot(RB_ENABLE_CAD); switch (a) { case ACTION_HALT: if (!arg_quiet) log_info("Halting."); + if (arg_dry_run) + return 0; (void) reboot(RB_HALT_SYSTEM); return -errno; case ACTION_POWEROFF: if (!arg_quiet) log_info("Powering off."); + if (arg_dry_run) + return 0; (void) reboot(RB_POWER_OFF); return -errno; @@ -8427,12 +8448,17 @@ static int halt_now(enum action a) { if (!isempty(param)) { if (!arg_quiet) log_info("Rebooting with argument '%s'.", param); - (void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); - log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); + if (!arg_dry_run) { + (void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, + LINUX_REBOOT_CMD_RESTART2, param); + log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); + } } if (!arg_quiet) log_info("Rebooting."); + if (arg_dry_run) + return 0; (void) reboot(RB_AUTOBOOT); return -errno; } @@ -8474,7 +8500,7 @@ static int logind_schedule_shutdown(void) { break; } - if (arg_dry) + if (arg_dry_run) action = strjoina("dry-", action); (void) logind_set_wall_message(); @@ -8513,7 +8539,7 @@ static int halt_main(void) { return logind_schedule_shutdown(); if (geteuid() != 0) { - if (arg_dry || arg_force > 0) { + if (arg_dry_run || arg_force > 0) { log_error("Must be root."); return -EPERM; } @@ -8534,7 +8560,7 @@ static int halt_main(void) { } } - if (!arg_dry && !arg_force) + if (!arg_dry_run && !arg_force) return start_with_fallback(); assert(geteuid() == 0); @@ -8549,7 +8575,7 @@ static int halt_main(void) { } } - if (arg_dry) + if (arg_dry_run) return 0; r = halt_now(arg_action); From 4bb2e9d466a0f2ed28608e09639b2cedeb08a814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 21 Oct 2017 10:55:32 +0200 Subject: [PATCH 6/9] systemctl: make sure the kernel is loaded before kexec'ing We just load the same kernel that would be loaded by default by sd-boot, with the same options. Changing the kernel or initramfs or options is left for later. Now we will refuse to continue if loading fails. This makes 'systemctl kexec' more predictable: it will not fall back to normal reboot if the kernel is not loaded. --- src/systemctl/systemctl.c | 78 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index b8c0718686..5ec977cb96 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include "sd-login.h" #include "alloc-util.h" +#include "bootspec.h" #include "bus-common-errors.h" #include "bus-error.h" #include "bus-message.h" @@ -143,6 +145,7 @@ static const char *arg_kill_who = NULL; static int arg_signal = SIGTERM; static char *arg_root = NULL; static usec_t arg_when = 0; +static char *arg_esp_path = NULL; static char *argv_cmdline = NULL; static enum action { ACTION_SYSTEMCTL, @@ -3497,6 +3500,75 @@ static int prepare_firmware_setup(void) { return logind_prepare_firmware_setup(); } +static int load_kexec_kernel(void) { + _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL; + const BootEntry *e; + pid_t pid; + int r; + + if (kexec_loaded()) { + log_debug("Kexec kernel already loaded."); + return 0; + } + + r = find_esp(&arg_esp_path, NULL, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Cannot find the ESP partition mount point: %m"); + + r = boot_entries_load_config(arg_esp_path, &config); + if (r < 0) + return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m", + arg_esp_path); + + if (config.default_entry < 0) { + log_error("No entry suitable as default, refusing to guess."); + return -ENOENT; + } + e = &config.entries[config.default_entry]; + + if (strv_length(e->initrd) > 1) { + log_error("Boot entry specifies multiple initrds, which is not supported currently."); + return -EINVAL; + } + + kernel = path_join(NULL, arg_esp_path, e->kernel); + if (!strv_isempty(e->initrd)) + initrd = path_join(NULL, arg_esp_path, *e->initrd); + options = strv_join(e->options, " "); + if (!options) + return log_oom(); + + log_debug("%s kexec kernel %s initrd %s options \"%s\".", + arg_dry_run ? "Would load" : "loading", + kernel, initrd, options); + if (arg_dry_run) + return 0; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + const char* const args[] = { + KEXEC, + "--load", kernel, + "--append", options, + initrd ? "--initrd" : NULL, initrd, + NULL }; + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + execv(args[0], (char * const *) args); + _exit(EXIT_FAILURE); + } else + return wait_for_terminate_and_warn("kexec", pid, true); +} + static int set_exit_code(uint8_t code) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus; @@ -3549,6 +3621,11 @@ static int start_special(int argc, char *argv[], void *userdata) { if (r < 0) return r; + } else if (a == ACTION_KEXEC) { + r = load_kexec_kernel(); + if (r < 0) + return r; + } else if (a == ACTION_EXIT && argc > 1) { uint8_t code; @@ -8723,6 +8800,7 @@ finish: strv_free(arg_wall); free(arg_root); + free(arg_esp_path); /* Note that we return r here, not EXIT_SUCCESS, so that we can implement the LSB-like return codes */ return r < 0 ? EXIT_FAILURE : r; From 46fb255b0d2c5fd304bcf4ba069d843da2828dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Nov 2017 13:50:08 +0100 Subject: [PATCH 7/9] bootctl: rename r2 and r to r and k r2 was assigned first despite the name. This scheme is different than what is used elsewhere in the code. Rename to make it easier to read. --- src/boot/bootctl.c | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 5ac9d0ccdf..de1aaf3221 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -915,9 +915,9 @@ static int must_be_root(void) { static int verb_status(int argc, char *argv[], void *userdata) { sd_id128_t uuid = SD_ID128_NULL; - int r, r2; + int r, k; - r2 = find_esp_and_warn(NULL, NULL, NULL, &uuid); + r = find_esp_and_warn(NULL, NULL, NULL, &uuid); if (is_efi_boot()) { _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL; @@ -931,24 +931,24 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (loader_path) efi_tilt_backslashes(loader_path); - r = efi_loader_get_device_part_uuid(&loader_part_uuid); - if (r < 0 && r != -ENOENT) - r2 = log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m"); + k = efi_loader_get_device_part_uuid(&loader_part_uuid); + if (k < 0 && k != -ENOENT) + r = log_warning_errno(k, "Failed to read EFI variable LoaderDevicePartUUID: %m"); printf("System:\n"); printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info)); - r = is_efi_secure_boot(); - if (r < 0) - r2 = log_warning_errno(r, "Failed to query secure boot status: %m"); + k = is_efi_secure_boot(); + if (k < 0) + r = log_warning_errno(k, "Failed to query secure boot status: %m"); else - printf(" Secure Boot: %sd\n", enable_disable(r)); + printf(" Secure Boot: %sd\n", enable_disable(k)); - r = is_efi_secure_boot_setup_mode(); - if (r < 0) - r2 = log_warning_errno(r, "Failed to query secure boot mode: %m"); + k = is_efi_secure_boot_setup_mode(); + if (k < 0) + r = log_warning_errno(k, "Failed to query secure boot mode: %m"); else - printf(" Setup Mode: %s\n", r ? "setup" : "user"); + printf(" Setup Mode: %s\n", k ? "setup" : "user"); printf("\n"); printf("Current Loader:\n"); @@ -963,21 +963,21 @@ static int verb_status(int argc, char *argv[], void *userdata) { } else printf("System:\n Not booted with EFI\n\n"); - r = status_binaries(arg_path, uuid); - if (r < 0) - r2 = r; + k = status_binaries(arg_path, uuid); + if (k < 0) + r = k; if (is_efi_boot()) { - r = status_variables(); - if (r < 0) - r2 = r; + k = status_variables(); + if (k < 0) + r = k; } - r = status_entries(arg_path, uuid); - if (r < 0) - r2 = r; + k = status_entries(arg_path, uuid); + if (k < 0) + r = k; - return r2; + return r; } static int verb_list(int argc, char *argv[], void *userdata) { From 30b5047762a77884b8badb0b34b1ddba0c9592a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Nov 2017 13:55:05 +0100 Subject: [PATCH 8/9] bootctl: add a convenient way to print the path to EFI --- man/bootctl.xml | 8 ++++++++ src/boot/bootctl.c | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/man/bootctl.xml b/man/bootctl.xml index ae4e62cc29..f16ab60091 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -108,6 +108,14 @@ the ESP to /boot, if possible. + + + + This option modifies the behaviour of status. + Just print the path to the EFI System Partition (ESP) to standard output and + exit. + + Do not touch the EFI boot variables. diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index de1aaf3221..aee6ad8428 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -57,6 +57,7 @@ #include "virt.h" static char *arg_path = NULL; +static bool arg_print_path = false; static bool arg_touch_variables = true; static int find_esp_and_warn(uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { @@ -71,7 +72,7 @@ static int find_esp_and_warn(uint32_t *part, uint64_t *pstart, uint64_t *psize, return log_error_errno(r, "Couldn't find EFI system partition: %m"); - log_info("Using EFI System Partition at %s.", arg_path); + log_debug("Using EFI System Partition at %s.", arg_path); return 0; } @@ -832,6 +833,7 @@ static int help(int argc, char *argv[], void *userdata) { " -h --help Show this help\n" " --version Print version\n" " --path=PATH Path to the EFI System Partition (ESP)\n" + " -p --print-path Print path to the EFI partition\n" " --no-variables Don't touch EFI variables\n" "\n" "Commands:\n" @@ -856,6 +858,7 @@ static int parse_argv(int argc, char *argv[]) { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "path", required_argument, NULL, ARG_PATH }, + { "print-path", no_argument, NULL, 'p' }, { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, { NULL, 0, NULL, 0 } }; @@ -865,7 +868,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hp", options, NULL)) >= 0) switch (c) { case 'h': @@ -881,6 +884,10 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; + case 'p': + arg_print_path = true; + break; + case ARG_NO_VARIABLES: arg_touch_variables = false; break; @@ -919,6 +926,14 @@ static int verb_status(int argc, char *argv[], void *userdata) { r = find_esp_and_warn(NULL, NULL, NULL, &uuid); + if (arg_print_path) { + if (r < 0) + return r; + + puts(arg_path); + return 0; + } + if (is_efi_boot()) { _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL; sd_id128_t loader_part_uuid = SD_ID128_NULL; From 906bbac4747a5adf9a5c3ee7bb1e36d8a35628fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Nov 2017 14:09:11 +0100 Subject: [PATCH 9/9] test: do not hardcode location of EFI partition --- test/test-functions | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/test-functions b/test/test-functions index 745c0a9abe..96118ae3ad 100644 --- a/test/test-functions +++ b/test/test-functions @@ -14,6 +14,7 @@ NSPAWN_TIMEOUT="${NSPAWN_TIMEOUT:-infinity}" TIMED_OUT= # will be 1 after run_* if *_TIMEOUT is set and test timed out [[ "$LOOKS_LIKE_SUSE" ]] && FSTYPE="${FSTYPE:-btrfs}" || FSTYPE="${FSTYPE:-ext3}" UNIFIED_CGROUP_HIERARCHY="${UNIFIED_CGROUP_HIERARCHY:-default}" +EFI_MOUNT="$(bootctl -p)" if ! ROOTLIBDIR=$(pkg-config --variable=systemdutildir systemd); then echo "WARNING! Cannot determine rootlibdir from pkg-config, assuming /usr/lib/systemd" >&2 @@ -61,10 +62,10 @@ function find_qemu_bin() { run_qemu() { if [ -f /etc/machine-id ]; then read MACHINE_ID < /etc/machine-id - [ -z "$INITRD" ] && [ -e "/boot/$MACHINE_ID/$KERNEL_VER/initrd" ] \ - && INITRD="/boot/$MACHINE_ID/$KERNEL_VER/initrd" - [ -z "$KERNEL_BIN" ] && [ -e "/boot/$MACHINE_ID/$KERNEL_VER/linux" ] \ - && KERNEL_BIN="/boot/$MACHINE_ID/$KERNEL_VER/linux" + [ -z "$INITRD" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd" ] \ + && INITRD="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd" + [ -z "$KERNEL_BIN" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux" ] \ + && KERNEL_BIN="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux" fi if [[ ! "$KERNEL_BIN" ]]; then