From: Kay Sievers Date: Sun, 8 Feb 2015 16:18:30 +0000 (+0100) Subject: bootctl: add sd-boot support X-Git-Tag: v219.0~666 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=0974a682d155a5874123ba7de9c1e314c6681e0f bootctl: add sd-boot support --- diff --git a/Makefile.am b/Makefile.am index d739445e8..5a17642f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2479,15 +2479,21 @@ systemd_efi_boot_generator_LDADD = \ # ------------------------------------------------------------------------------ bootctl_SOURCES = \ - src/boot/boot.h \ - src/boot/boot-loader.h \ - src/boot/bootctl.c \ - src/boot/boot-loader.c \ - src/boot/boot-efi.c + src/boot/bootctl.c + +bootctl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DEFI_MACHINE_TYPE_NAME=\"$(EFI_MACHINE_TYPE_NAME)\" \ + -DSD_BOOTLIBDIR=\"$(sd_bootlibdir)\" + +bootctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) bootctl_LDADD = \ libsystemd-shared.la \ - libsystemd-internal.la + libsystemd-internal.la \ + $(BLKID_LIBS) bin_PROGRAMS += \ bootctl diff --git a/man/bootctl.xml b/man/bootctl.xml index 00f54c73f..99863bf31 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -21,7 +21,6 @@ - bootctl systemd @@ -48,65 +47,82 @@ - bootctl - OPTIONS - COMMAND + bootctl OPTIONSstatus + + + bootctl OPTIONSupdate + + + bootctl OPTIONSinstall + + + bootctl OPTIONSremove Description - bootctl may be used to query or (in the - future) change the firmware and boot manager settings. - - Firmware information is available only on EFI systems. - - - Currently, only the - gummiboot8 - boot manager implements the required boot loader interface to - provide complete boot manager information. + bootctl checks, updates, + installs or removes the boot loader from the current + system. + + bootctl status checks and prints the + currently installed versions of the boot loader binaries and the + all current EFI boot variables. + + bootctl update updates all installed + versions of sd-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. An + sd-boot entry in the EFI boot variables is created, if there + is no current entry. A created entry will be added to the end of + the boot order list. + + bootctl install installs sd-boot into + the EFI system partition. A copy of sd-boot will be stored as + the EFI default/fallback loader at /EFI/Boot/boot*.efi. An sd-boot + entry in the EFI boot variables is created and added to the top + of the boot order list. + + bootctl remove removes all installed + versions of sd-boot from the EFI system partition, and removes + sd-boot from the EFI boot variables. + + If no command is passed status is + implied. Options - The following options are understood: - - - The following commands are understood: - - - status + + Path to the EFI system partition. The default is /boot. + - Show firmware and boot manager information - about the system, including secure boot mode status and - selected firmware entry (where available). + + + Do not touch the EFI boot variables. - Exit status - - On success, 0 is returned, a non-zero failure code - otherwise. + On success 0 is returned, a non-zero failure + code otherwise. See Also - Boot loader interface, - Boot loader specification, - gummiboot + Boot loader specification + Systemd boot loader interface - diff --git a/man/custom-html.xsl b/man/custom-html.xsl index 32299db71..2cb7b76ae 100644 --- a/man/custom-html.xsl +++ b/man/custom-html.xsl @@ -87,10 +87,6 @@ - - - -

diff --git a/man/systemd-efi-boot-generator.xml b/man/systemd-efi-boot-generator.xml index b2d8d65e3..d05d84abb 100644 --- a/man/systemd-efi-boot-generator.xml +++ b/man/systemd-efi-boot-generator.xml @@ -80,7 +80,6 @@ systemd.mount5, systemd.automount5, systemd-gpt-auto-generator8, - gummiboot8, fstab5 diff --git a/src/boot/boot-efi.c b/src/boot/boot-efi.c deleted file mode 100644 index bd0c59bd5..000000000 --- a/src/boot/boot-efi.c +++ /dev/null @@ -1,190 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Kay Sievers - - 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 -#include -#include -#include -#include -#include -#include -#include - -#include "boot.h" -#include "boot-loader.h" -#include "build.h" -#include "util.h" -#include "strv.h" -#include "efivars.h" -#include "conf-files.h" - -static char *tilt_slashes(char *s) { - char *p; - - if (!s) - return NULL; - - for (p = s; *p; p++) - if (*p == '\\') - *p = '/'; - return s; -} - -static int get_boot_entries(struct boot_info *info) { - uint16_t *list = NULL; - int i, n; - int err = 0; - - n = efi_get_boot_options(&list); - if (n < 0) - return n; - - for (i = 0; i < n; i++) { - struct boot_info_entry *e; - - e = realloc(info->fw_entries, (info->fw_entries_count+1) * sizeof(struct boot_info_entry)); - if (!e) { - err = -ENOMEM; - break; - } - info->fw_entries = e; - - e = &info->fw_entries[info->fw_entries_count]; - memzero(e, sizeof(struct boot_info_entry)); - e->order = -1; - - err = efi_get_boot_option(list[i], &e->title, &e->part_uuid, &e->path); - if (err < 0) - continue; - - if (isempty(e->title)) { - free(e->title); - e->title = NULL; - } - tilt_slashes(e->path); - - e->id = list[i]; - info->fw_entries_count++; - } - - free(list); - return err; -} - -static int find_active_entry(struct boot_info *info) { - uint16_t boot_cur; - void *buf; - size_t l; - size_t i; - int err; - - err = efi_get_variable(EFI_VENDOR_GLOBAL, "BootCurrent", NULL, &buf, &l); - if (err < 0) - return err; - - memcpy(&boot_cur, buf, sizeof(uint16_t)); - for (i = 0; i < info->fw_entries_count; i++) { - if (info->fw_entries[i].id != boot_cur) - continue; - info->fw_entry_active = i; - err = 0; - break; - } - free(buf); - return err; -} - -static int get_boot_order(struct boot_info *info) { - size_t i, k; - int r; - - r = efi_get_boot_order(&info->fw_entries_order); - if (r < 0) - return r; - - info->fw_entries_order_count = r; - - for (i = 0; i < info->fw_entries_order_count; i++) { - for (k = 0; k < info->fw_entries_count; k++) { - if (info->fw_entries[k].id != info->fw_entries_order[i]) - continue; - info->fw_entries[k].order = i; - break; - } - } - - return 0; -} - -static int entry_cmp(const void *a, const void *b) { - const struct boot_info_entry *e1 = a; - const struct boot_info_entry *e2 = b; - - /* boot order of active entries */ - if (e1->order > 0 && e2->order > 0) - return e1->order - e2->order; - - /* sort active entries before inactive ones */ - if (e1->order > 0) - return 1; - if (e2->order > 0) - return -1; - - /* order of inactive entries */ - return e1->id - e2->id; -} - -int boot_info_query(struct boot_info *info) { - char str[64]; - char buf[64]; - char *loader_active = NULL; - - info->fw_secure_boot = is_efi_secure_boot(); - info->fw_secure_boot_setup_mode = is_efi_secure_boot_setup_mode(); - - efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderInfo", &info->loader); - - get_boot_entries(info); - if (info->fw_entries_count > 0) { - get_boot_order(info); - qsort(info->fw_entries, info->fw_entries_count, sizeof(struct boot_info_entry), entry_cmp); - find_active_entry(info); - } - - efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareType", &info->fw_type); - efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareInfo", &info->fw_info); - efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderImageIdentifier", &info->loader_image_path); - tilt_slashes(info->loader_image_path); - efi_loader_get_device_part_uuid(&info->loader_part_uuid); - - boot_loader_read_entries(info); - efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntrySelected", &loader_active); - if (loader_active) { - boot_loader_find_active_entry(info, loader_active); - free(loader_active); - } - - snprintf(str, sizeof(str), "LoaderEntryOptions-%s", sd_id128_to_string(info->machine_id, buf)); - efi_get_variable_string(EFI_VENDOR_LOADER, str, &info->loader_options_added); - - return 0; -} diff --git a/src/boot/boot-loader.c b/src/boot/boot-loader.c deleted file mode 100644 index d44fdb3ae..000000000 --- a/src/boot/boot-loader.c +++ /dev/null @@ -1,132 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Kay Sievers - - 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 -#include -#include -#include -#include -#include -#include - -#include "boot.h" -#include "boot-loader.h" -#include "build.h" -#include "util.h" -#include "strv.h" -#include "conf-files.h" - -static char *loader_fragment_read_title(const char *fragment) { - FILE *f; - char line[LINE_MAX]; - char *title = NULL; - - f = fopen(fragment, "re"); - if (!f) - return NULL; - - while (fgets(line, sizeof(line), f) != NULL) { - char *s; - size_t l; - - l = strlen(line); - if (l < 1) - continue; - if (line[l-1] == '\n') - line[l-1] = '\0'; - - s = line; - while (isspace(s[0])) - s++; - - if (s[0] == '#') - continue; - - if (!startswith(s, "title")) - continue; - - s += strlen("title"); - if (!isspace(s[0])) - continue; - while (isspace(s[0])) - s++; - - title = strdup(s); - break; - } - - fclose(f); - return title; -} - -int boot_loader_read_entries(struct boot_info *info) { - _cleanup_strv_free_ char **files = NULL; - static const char *loader_dir[] = { "/boot/loader/entries", NULL}; - unsigned int count; - unsigned int i; - int err; - - err = conf_files_list_strv(&files, ".conf", NULL, loader_dir); - if (err < 0) - return err; - - count = strv_length(files); - info->loader_entries = new0(struct boot_info_entry, count); - if (!info->loader_entries) - return -ENOMEM; - - for (i = 0; i < count; i++) { - info->loader_entries[i].title = loader_fragment_read_title(files[i]); - info->loader_entries[i].path = strdup(files[i]); - if (!info->loader_entries[i].title || !info->loader_entries[i].path) { - free(info->loader_entries[i].title); - free(info->loader_entries[i].path); - return -ENOMEM; - } - info->loader_entries_count++; - } - - return 0; -} - -int boot_loader_find_active_entry(struct boot_info *info, const char *loader_active) { - char *fn; - unsigned int i; - - if (!loader_active) - return -ENOENT; - if (info->loader_entries_count == 0) - return -ENOENT; - - if (asprintf(&fn, "/boot/loader/entries/%s.conf", loader_active) < 0) - return -ENOMEM; - - for (i = 0; i < info->loader_entries_count; i++) { - if (streq(fn, info->loader_entries[i].path)) { - info->loader_entry_active = i; - break; - } - } - - free(fn); - return 0; -} diff --git a/src/boot/boot-loader.h b/src/boot/boot-loader.h deleted file mode 100644 index b3fcdeeda..000000000 --- a/src/boot/boot-loader.h +++ /dev/null @@ -1,27 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Kay Sievers - - 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 "boot.h" - -int boot_loader_read_entries(struct boot_info *info); -int boot_loader_find_active_entry(struct boot_info *info, const char *loader_active); diff --git a/src/boot/boot.h b/src/boot/boot.h deleted file mode 100644 index bd8dc69d3..000000000 --- a/src/boot/boot.h +++ /dev/null @@ -1,64 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Kay Sievers - - 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 "sd-id128.h" - -/* - * Firmware and boot manager information to be filled in - * by the platform. - * - * This is partly EFI specific, if you add things, keep this - * as generic as possible to be able to re-use it on other - * platforms. - */ - -struct boot_info_entry { - uint16_t id; - uint16_t order; - char *title; - sd_id128_t part_uuid; - char *path; -}; - -struct boot_info { - sd_id128_t machine_id; - sd_id128_t boot_id; - char *fw_type; - char *fw_info; - int fw_secure_boot; - int fw_secure_boot_setup_mode; - struct boot_info_entry *fw_entries; - size_t fw_entries_count; - uint16_t *fw_entries_order; - size_t fw_entries_order_count; - ssize_t fw_entry_active; - char *loader; - char *loader_image_path; - sd_id128_t loader_part_uuid; - struct boot_info_entry *loader_entries; - size_t loader_entries_count; - ssize_t loader_entry_active; - char *loader_options_added; -}; - -int boot_info_query(struct boot_info *info); diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 51b51c425..3b6df4291 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -3,7 +3,8 @@ /*** This file is part of systemd. - Copyright 2013 Kay Sievers + Copyright 2013-2015 Kay Sievers + Copyright 2013 Lennart Poettering 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 @@ -19,249 +20,1374 @@ along with systemd; If not, see . ***/ -#include -#include -#include +#include #include -#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "boot.h" +#include "efivars.h" #include "build.h" #include "util.h" #include "utf8.h" -static void help(void) { - printf("%s [OPTIONS...] COMMAND ...\n\n" - "Query or change firmware and boot manager settings.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "Commands:\n" - " status Show current boot settings\n" - , program_invocation_short_name); +static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { + struct statfs sfs; + struct stat st, st2; + char *t; + blkid_probe b = NULL; + int r; + const char *v; + + if (statfs(p, &sfs) < 0) { + fprintf(stderr, "Failed to check file system type of %s: %m\n", p); + return -errno; + } + + if (sfs.f_type != 0x4d44) { + fprintf(stderr, "File system %s is not a FAT EFI System Partition (ESP) file system.\n", p); + return -ENODEV; + } + + if (stat(p, &st) < 0) { + fprintf(stderr, "Failed to determine block device node of %s: %m\n", p); + return -errno; + } + + if (major(st.st_dev) == 0) { + fprintf(stderr, "Block device node of %p is invalid.\n", p); + return -ENODEV; + } + + r = asprintf(&t, "%s/..", p); + if (r < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + + r = stat(t, &st2); + free(t); + if (r < 0) { + fprintf(stderr, "Failed to determine block device node of parent of %s: %m\n", p); + return -errno; + } + + if (st.st_dev == st2.st_dev) { + fprintf(stderr, "Directory %s is not the root of the EFI System Partition (ESP) file system.\n", p); + return -ENODEV; + } + + r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev)); + if (r < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + + errno = 0; + b = blkid_new_probe_from_filename(t); + free(t); + if (!b) { + if (errno != 0) { + fprintf(stderr, "Failed to open file system %s: %m\n", p); + return -errno; + } + + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + + 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) { + fprintf(stderr, "File system %s is ambigious.\n", p); + r = -ENODEV; + goto fail; + } else if (r == 1) { + fprintf(stderr, "File system %s does not contain a label.\n", p); + r = -ENODEV; + goto fail; + } else if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe file system %s: %s\n", p, strerror(-r)); + goto fail; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe file system type %s: %s\n", p, strerror(-r)); + goto fail; + } + + if (strcmp(v, "vfat") != 0) { + fprintf(stderr, "File system %s is not a FAT EFI System Partition (ESP) file system after all.\n", p); + r = -ENODEV; + goto fail; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe partition scheme %s: %s\n", p, strerror(-r)); + goto fail; + } + + if (strcmp(v, "gpt") != 0) { + fprintf(stderr, "File system %s is not on a GPT partition table.\n", p); + r = -ENODEV; + goto fail; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe partition type UUID %s: %s\n", p, strerror(-r)); + goto fail; + } + + if (strcmp(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b") != 0) { + r = -ENODEV; + fprintf(stderr, "File system %s is not an EFI System Partition (ESP).\n", p); + goto fail; + } + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe partition entry UUID %s: %s\n", p, strerror(-r)); + goto fail; + } + sd_id128_from_string(v, uuid); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe partition number %s: %s\n", p, strerror(-r)); + goto fail; + } + *part = strtoul(v, NULL, 10); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe partition offset %s: %s\n", p, strerror(-r)); + goto fail; + } + *pstart = strtoul(v, NULL, 10); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + fprintf(stderr, "Failed to probe partition size %s: %s\n", p, strerror(-r)); + goto fail; + } + *psize = strtoul(v, NULL, 10); + + blkid_free_probe(b); + return 0; +fail: + if (b) + blkid_free_probe(b); + return r; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; +/* search for "#### LoaderInfo: sd-boot 218 ####" string inside the binary */ +static int get_file_version(FILE *f, char **v) { + struct stat st; + char *buf; + const char *s, *e; + char *x = NULL; + int r = 0; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; + assert(f); + assert(v); - int c; + if (fstat(fileno(f), &st) < 0) + return -errno; - assert(argc >= 0); - assert(argv); + if (st.st_size < 27) + return 0; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fileno(f), 0); + if (buf == MAP_FAILED) + return -errno; - switch (c) { + s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17); + if (!s) + goto finish; + s += 17; - case 'h': - help(); - return 0; + e = memmem(s, st.st_size - (s - buf), " ####", 5); + if (!e || e - s < 3) { + fprintf(stderr, "Malformed version string.\n"); + r = -EINVAL; + goto finish; + } - case ARG_VERSION: - puts(PACKAGE_STRING); - puts(SYSTEMD_FEATURES); - return 0; + x = strndup(s, e - s); + if (!x) { + fprintf(stderr, "Out of memory.\n"); + r = -ENOMEM; + goto finish; + } + r = 1; - case '?': - return -EINVAL; +finish: + munmap(buf, st.st_size); + *v = x; + return r; +} - default: - assert_not_reached("Unhandled option"); +static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { + struct dirent *de; + char *p = NULL, *q = NULL; + DIR *d = NULL; + int r = 0, c = 0; + + if (asprintf(&p, "%s/%s", esp_path, path) < 0) { + fprintf(stderr, "Out of memory.\n"); + r = -ENOMEM; + goto finish; + } + + d = opendir(p); + if (!d) { + if (errno == ENOENT) { + r = 0; + goto finish; } - return 1; + fprintf(stderr, "Failed to read %s: %m\n", p); + r = -errno; + goto finish; + } + + while ((de = readdir(d))) { + char *v; + size_t n; + FILE *f; + + if (de->d_name[0] == '.') + continue; + + n = strlen(de->d_name); + if (n < 4 || strcasecmp(de->d_name + n - 4, ".efi") != 0) + continue; + + if (prefix && strncasecmp(de->d_name, prefix, strlen(prefix)) != 0) + continue; + + free(q); + q = NULL; + if (asprintf(&q, "%s/%s/%s", esp_path, path, de->d_name) < 0) { + fprintf(stderr, "Out of memory.\n"); + r = -ENOMEM; + goto finish; + } + + f = fopen(q, "re"); + if (!f) { + fprintf(stderr, "Failed to open %s for reading: %m\n", q); + r = -errno; + goto finish; + } + + r = get_file_version(f, &v); + fclose(f); + + if (r < 0) + goto finish; + + if (r > 0) + printf(" File: └─/%s/%s (%s)\n", path, de->d_name, v); + else + printf(" File: └─/%s/%s\n", path, de->d_name); + + c++; + free(v); + } + + r = c; + +finish: + if (d) + closedir(d); + + free(p); + free(q); + return r; } -static int boot_info_new(struct boot_info **info) { - struct boot_info *in; - int err; +static int status_binaries(const char *esp_path, sd_id128_t partition) { + int r; + + printf("Boot Loader Binaries:\n"); + + printf(" ESP: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition)); + + r = enumerate_binaries(esp_path, "EFI/systemd", NULL); + if (r == 0) + fprintf(stderr, "sd-boot not installed in ESP.\n"); + else if (r < 0) + return r; + + r = enumerate_binaries(esp_path, "EFI/Boot", "boot"); + if (r == 0) + fprintf(stderr, "No default/fallback boot loader installed in ESP.\n"); + else if (r < 0) + return r; + + printf("\n"); + return 0; +} + +static int print_efi_option(uint16_t id, bool in_order) { + char *title = NULL; + char *path = NULL; + sd_id128_t partition; + bool active; + int r = 0; + + r = efi_get_boot_option(id, &title, &partition, &path, &active); + if (r < 0) + goto finish; - in = new0(struct boot_info, 1); - if (!in) + /* print only configured entries with partition information */ + if (!path || sd_id128_equal(partition, SD_ID128_NULL)) + return 0; + + efi_tilt_backslashes(path); + + printf(" Title: %s\n", strna(title)); + printf(" ID: 0x%04X\n", id); + printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : ""); + printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition)); + printf(" File: └─%s\n", path); + printf("\n"); + +finish: + free(title); + free(path); + return r; +} + +static int status_variables(void) { + int n_options, n_order; + uint16_t *options = NULL, *order = NULL; + int r, i; + + if (!is_efi_boot()) { + fprintf(stderr, "Not booted with EFI, not showing EFI variables.\n"); + return 0; + } + + n_options = efi_get_boot_options(&options); + if (n_options < 0) { + if (n_options == -ENOENT) + fprintf(stderr, "Failed to access EFI variables, " + "efivarfs needs to be available at /sys/firmware/efi/efivars/.\n"); + else + fprintf(stderr, "Failed to read EFI boot entries: %s\n", strerror(-n_options)); + r = n_options; + goto finish; + } + + printf("Boot Loader Entries in EFI Variables:\n"); + n_order = efi_get_boot_order(&order); + if (n_order == -ENOENT) { + n_order = 0; + } else if (n_order < 0) { + fprintf(stderr, "Failed to read EFI boot order.\n"); + r = n_order; + goto finish; + } + + /* print entries in BootOrder first */ + for (i = 0; i < n_order; i++) + print_efi_option(order[i], true); + + /* print remaining entries */ + for (i = 0; i < n_options; i++) { + int j; + bool found = false; + + for (j = 0; j < n_order; j++) + if (options[i] == order[j]) { + found = true; + break; + } + + if (found) + continue; + + print_efi_option(options[i], false); + } + + r = 0; +finish: + free(options); + free(order); + + return r; +} + +static int compare_product(const char *a, const char *b) { + size_t x, y; + + assert(a); + assert(b); + + x = strcspn(a, " "); + y = strcspn(b, " "); + if (x != y) + return x < y ? -1 : x > y ? 1 : 0; + + return strncmp(a, b, x); +} + +static int compare_version(const char *a, const char *b) { + assert(a); + assert(b); + + a += strcspn(a, " "); + a += strspn(a, " "); + b += strcspn(b, " "); + b += strspn(b, " "); + + return strverscmp(a, b); +} + +static int version_check(FILE *f, const char *from, const char *to) { + FILE *g = NULL; + char *a = NULL, *b = NULL; + int r; + + assert(f); + assert(from); + assert(to); + + r = get_file_version(f, &a); + if (r < 0) + goto finish; + if (r == 0) { + r = -EINVAL; + fprintf(stderr, "Source file %s does not carry version information!\n", from); + goto finish; + } + + g = fopen(to, "re"); + if (!g) { + if (errno == ENOENT) { + r = 0; + goto finish; + } + + r = -errno; + fprintf(stderr, "Failed to open %s for reading: %m\n", to); + goto finish; + } + + r = get_file_version(g, &b); + if (r < 0) + goto finish; + if (r == 0 || compare_product(a, b) != 0) { + r = -EEXIST; + fprintf(stderr, "Skipping %s, since it's owned by another boot loader.\n", to); + goto finish; + } + + if (compare_version(a, b) < 0) { + r = -EEXIST; + fprintf(stderr, "Skipping %s, since it's a newer boot loader version already.\n", to); + goto finish; + } + + r = 0; + +finish: + free(a); + free(b); + if (g) + fclose(g); + return r; +} + +static int copy_file(const char *from, const char *to, bool force) { + FILE *f = NULL, *g = NULL; + char *p = NULL; + int r; + struct timespec t[2]; + struct stat st; + + assert(from); + assert(to); + + f = fopen(from, "re"); + if (!f) { + fprintf(stderr, "Failed to open %s for reading: %m\n", from); + return -errno; + } + + if (!force) { + /* If this is an update, then let's compare versions first */ + r = version_check(f, from, to); + if (r < 0) + goto finish; + } + + if (asprintf(&p, "%s~", to) < 0) { + fprintf(stderr, "Out of memory.\n"); + r = -ENOMEM; + goto finish; + } + + g = fopen(p, "wxe"); + if (!g) { + /* Directory doesn't exist yet? Then let's skip this... */ + if (!force && errno == ENOENT) { + r = 0; + goto finish; + } + + fprintf(stderr, "Failed to open %s for writing: %m\n", to); + r = -errno; + goto finish; + } + + rewind(f); + do { + size_t k; + uint8_t buf[32*1024]; + + k = fread(buf, 1, sizeof(buf), f); + if (ferror(f)) { + fprintf(stderr, "Failed to read %s: %m\n", from); + r = -errno; + goto finish; + } + if (k == 0) + break; + + fwrite(buf, 1, k, g); + if (ferror(g)) { + fprintf(stderr, "Failed to write %s: %m\n", to); + r = -errno; + goto finish; + } + } while (!feof(f)); + + fflush(g); + if (ferror(g)) { + fprintf(stderr, "Failed to write %s: %m\n", to); + r = -errno; + goto finish; + } + + r = fstat(fileno(f), &st); + if (r < 0) { + fprintf(stderr, "Failed to get file timestamps of %s: %m", from); + r = -errno; + goto finish; + } + + t[0] = st.st_atim; + t[1] = st.st_mtim; + + r = futimens(fileno(g), t); + if (r < 0) { + fprintf(stderr, "Failed to change file timestamps for %s: %m", p); + r = -errno; + goto finish; + } + + if (rename(p, to) < 0) { + fprintf(stderr, "Failed to rename %s to %s: %m\n", p, to); + r = -errno; + goto finish; + } + + fprintf(stderr, "Copied %s to %s.\n", from, to); + + free(p); + p = NULL; + r = 0; + +finish: + if (f) + fclose(f); + if (g) + fclose(g); + if (p) { + unlink(p); + free(p); + } + return r; +} + +static char* strupper(char *s) { + char *p; + + for (p = s; *p; p++) + *p = toupper(*p); + + return s; +} + +static int mkdir_one(const char *prefix, const char *suffix) { + char *p; + + if (asprintf(&p, "%s/%s", prefix, suffix) < 0) { + fprintf(stderr, "Out of memory.\n"); return -ENOMEM; + } - err = sd_id128_get_machine(&in->machine_id); - if (err < 0) - goto err; + if (mkdir(p, 0700) < 0) { + if (errno != EEXIST) { + fprintf(stderr, "Failed to create %s: %m\n", p); + free(p); + return -errno; + } + } else + fprintf(stderr, "Created %s.\n", p); - err = sd_id128_get_boot(&in->boot_id); - if (err < 0) - goto err; + free(p); + return 0; +} + +static int create_dirs(const char *esp_path) { + int r; + + r = mkdir_one(esp_path, "EFI"); + if (r < 0) + return r; + + r = mkdir_one(esp_path, "EFI/systemd"); + if (r < 0) + return r; + + r = mkdir_one(esp_path, "EFI/Boot"); + if (r < 0) + return r; - in->fw_entry_active = -1; - in->loader_entry_active = -1; + r = mkdir_one(esp_path, "loader"); + if (r < 0) + return r; + + r = mkdir_one(esp_path, "loader/entries"); + if (r < 0) + return r; - *info = in; return 0; -err: - free(in); - return err; } -static void boot_info_entries_free(struct boot_info_entry *entries, size_t n) { - size_t i; +static int copy_one_file(const char *esp_path, const char *name, bool force) { + _cleanup_free_ char *p = NULL; + _cleanup_free_ char *q = NULL; + _cleanup_free_ char *v = NULL; + int r; + + if (asprintf(&p, SD_BOOTLIBDIR "/%s", name) < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } - for (i = 0; i < n; i++) { - free(entries[i].title); - free(entries[i].path); + if (asprintf(&q, "%s/EFI/systemd/%s", esp_path, name) < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; } - free(entries); + + r = copy_file(p, q, force); + + if (startswith(name, "sd-boot")) { + int k; + + /* Create the EFI default boot loader name (specified for removable devices) */ + if (asprintf(&v, "%s/EFI/Boot/BOOT%s", esp_path, name + strlen("sd-boot")) < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + strupper(strrchr(v, '/') + 1); + + k = copy_file(p, v, force); + if (k < 0 && r == 0) + return k; + } + + return r; } -static void boot_info_free(struct boot_info *info) { - free(info->fw_type); - free(info->fw_info); - boot_info_entries_free(info->fw_entries, info->fw_entries_count); - free(info->fw_entries_order); - free(info->loader); - free(info->loader_image_path); - free(info->loader_options_added); - boot_info_entries_free(info->loader_entries, info->loader_entries_count); - free(info); +static int install_binaries(const char *esp_path, bool force) { + struct dirent *de; + DIR *d; + int r = 0; + + if (force) { + /* Don't create any of these directories when we are + * just updating. When we update we'll drop-in our + * files (unless there are newer ones already), but we + * won't create the directories for them in the first + * place. */ + r = create_dirs(esp_path); + if (r < 0) + return r; + } + + d = opendir(SD_BOOTLIBDIR); + if (!d) { + fprintf(stderr, "Failed to open "SD_BOOTLIBDIR": %m\n"); + return -errno; + } + + while ((de = readdir(d))) { + size_t n; + int k; + + if (de->d_name[0] == '.') + continue; + + n = strlen(de->d_name); + if (n < 4 || strcmp(de->d_name + n - 4, ".efi") != 0) + continue; + + k = copy_one_file(esp_path, de->d_name, force); + if (k < 0 && r == 0) + r = k; + } + + closedir(d); + return r; } -static int show_status(char **args, unsigned n) { - char buf[64]; - struct boot_info *info; +static bool same_entry(uint16_t id, const sd_id128_t uuid, const char *path) { + char *opath = NULL; + sd_id128_t ouuid; int err; + bool same = false; - err = boot_info_new(&info); + err = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL); if (err < 0) + return false; + if (!sd_id128_equal(uuid, ouuid)) + goto finish; + + if (!streq_ptr(path, opath)) + goto finish; + + same = true; + +finish: + return same; +} + +static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { + uint16_t *options = NULL; + int n_options; + int i; + uint16_t new_id = 0; + bool existing = false; + + n_options = efi_get_boot_options(&options); + if (n_options < 0) + return n_options; + + /* find already existing sd-boot entry */ + for (i = 0; i < n_options; i++) + if (same_entry(options[i], uuid, path)) { + new_id = options[i]; + existing = true; + goto finish; + } + + /* find free slot in the sorted BootXXXX variable list */ + for (i = 0; i < n_options; i++) + if (i != options[i]) { + new_id = i; + goto finish; + } + + /* use the next one */ + if (i == 0xffff) + return -ENOSPC; + new_id = i; + +finish: + *id = new_id; + free(options); + return existing; +} + +static int insert_into_order(uint16_t slot, bool first) { + uint16_t *order = NULL; + uint16_t *new_order; + int n_order; + int i; + int err = 0; + + n_order = efi_get_boot_order(&order); + if (n_order <= 0) { + /* no entry, add us */ + err = efi_set_boot_order(&slot, 1); + goto finish; + } + + /* are we the first and only one? */ + if (n_order == 1 && order[0] == slot) + goto finish; + + /* are we already in the boot order? */ + for (i = 0; i < n_order; i++) { + if (order[i] != slot) + continue; + + /* we do not require to be the first one, all is fine */ + if (!first) + goto finish; + + /* move us to the first slot */ + memmove(&order[1], order, i * sizeof(uint16_t)); + order[0] = slot; + efi_set_boot_order(order, n_order); + goto finish; + } + + /* extend array */ + new_order = realloc(order, (n_order+1) * sizeof(uint16_t)); + if (!new_order) { + err = -ENOMEM; + goto finish; + } + order = new_order; + + /* add us to the top or end of the list */ + if (first) { + memmove(&order[1], order, n_order * sizeof(uint16_t)); + order[0] = slot; + } else + order[n_order] = slot; + + efi_set_boot_order(order, n_order+1); + +finish: + free(order); + return err; +} + +static int remove_from_order(uint16_t slot) { + uint16_t *order = NULL; + int n_order; + int i; + int err = 0; + + n_order = efi_get_boot_order(&order); + if (n_order < 0) + return n_order; + if (n_order == 0) + return 0; + + for (i = 0; i < n_order; i++) { + if (order[i] != slot) + continue; + + if (i+1 < n_order) + memmove(&order[i], &order[i+1], (n_order - i) * sizeof(uint16_t)); + efi_set_boot_order(order, n_order-1); + break; + } + + free(order); + return err; +} + +static int install_variables(const char *esp_path, + uint32_t part, uint64_t pstart, uint64_t psize, + sd_id128_t uuid, const char *path, + bool first) { + char *p = NULL; + uint16_t *options = NULL; + uint16_t slot; + int r; + + if (!is_efi_boot()) { + fprintf(stderr, "Not booted with EFI, skipping EFI variable setup.\n"); + return 0; + } + + if (asprintf(&p, "%s%s", esp_path, path) < 0) { + fprintf(stderr, "Out of memory.\n"); return -ENOMEM; + } - err = boot_info_query(info); - - printf("System:\n"); - printf(" Machine ID: %s\n", sd_id128_to_string(info->machine_id, buf)); - printf(" Boot ID: %s\n", sd_id128_to_string(info->boot_id, buf)); - if (info->fw_type) - printf(" Firmware: %s (%s)\n", info->fw_type, strna(info->fw_info)); - if (info->fw_secure_boot >= 0) - printf(" Secure Boot: %s\n", info->fw_secure_boot ? "enabled" : "disabled"); - if (info->fw_secure_boot_setup_mode >= 0) - printf(" Setup Mode: %s\n", info->fw_secure_boot_setup_mode ? "setup" : "user"); - printf("\n"); + if (access(p, F_OK) < 0) { + if (errno == ENOENT) + r = 0; + else + r = -errno; + goto finish; + } - if (info->fw_entry_active >= 0) { - printf("Selected Firmware Entry:\n"); - printf(" Title: %s\n", strna(info->fw_entries[info->fw_entry_active].title)); - if (!sd_id128_equal(info->fw_entries[info->fw_entry_active].part_uuid, SD_ID128_NULL)) - printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", - SD_ID128_FORMAT_VAL(info->fw_entries[info->fw_entry_active].part_uuid)); + r = find_slot(uuid, path, &slot); + if (r < 0) { + if (r == -ENOENT) + fprintf(stderr, "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?\n"); else - printf(" Partition: n/a\n"); - if (info->fw_entries[info->fw_entry_active].path) - printf(" File: %s%s\n", draw_special_char(DRAW_TREE_RIGHT), info->fw_entries[info->fw_entry_active].path); + fprintf(stderr, "Failed to determine current boot order: %s\n", strerror(-r)); + goto finish; } - printf("\n"); - if (info->loader) { - printf("Boot Loader:\n"); - printf(" Product: %s\n", info->loader); - if (!sd_id128_equal(info->loader_part_uuid, SD_ID128_NULL)) - printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", - SD_ID128_FORMAT_VAL(info->loader_part_uuid)); - else - printf(" Partition: n/a\n"); - printf(" File: %s%s\n", draw_special_char(DRAW_TREE_RIGHT), strna(info->loader_image_path)); - printf("\n"); + if (first || r == false) { + r = efi_add_boot_option(slot, "Linux Boot Manager", + part, pstart, psize, + uuid, path); + if (r < 0) { + fprintf(stderr, "Failed to create EFI Boot variable entry: %s\n", strerror(-r)); + goto finish; + } + fprintf(stderr, "Created EFI boot entry \"Linux Boot Manager\".\n"); + } + + insert_into_order(slot, first); + +finish: + free(p); + free(options); + return r; +} + +static int remove_boot_efi(const char *esp_path) { + struct dirent *de; + char *p = NULL, *q = NULL; + DIR *d = NULL; + int r = 0, c = 0; + + if (asprintf(&p, "%s/EFI/Boot", esp_path) < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + + d = opendir(p); + if (!d) { + if (errno == ENOENT) { + r = 0; + goto finish; + } + + fprintf(stderr, "Failed to read %s: %m\n", p); + r = -errno; + goto finish; + } + + while ((de = readdir(d))) { + char *v; + size_t n; + FILE *f; + + if (de->d_name[0] == '.') + continue; + + n = strlen(de->d_name); + if (n < 4 || strcasecmp(de->d_name + n - 4, ".EFI") != 0) + continue; + + if (strncasecmp(de->d_name, "Boot", 4) != 0) + continue; + + free(q); + q = NULL; + if (asprintf(&q, "%s/%s", p, de->d_name) < 0) { + fprintf(stderr, "Out of memory.\n"); + r = -ENOMEM; + goto finish; + } + + f = fopen(q, "re"); + if (!f) { + fprintf(stderr, "Failed to open %s for reading: %m\n", q); + r = -errno; + goto finish; + } + + r = get_file_version(f, &v); + fclose(f); + + if (r < 0) + goto finish; + + if (r > 0 && strncmp(v, "sd-boot ", 10) == 0) { + + r = unlink(q); + if (r < 0) { + fprintf(stderr, "Failed to remove %s: %m\n", q); + r = -errno; + free(v); + goto finish; + } else + fprintf(stderr, "Removed %s.\n", q); + } + + c++; + free(v); + } + + r = c; + +finish: + if (d) + closedir(d); + free(p); + free(q); + + return r; +} + +static int rmdir_one(const char *prefix, const char *suffix) { + char *p; - if (info->loader_entry_active >= 0) { - printf("Selected Boot Loader Entry:\n"); - printf(" Title: %s\n", strna(info->loader_entries[info->loader_entry_active].title)); - printf(" File: %s\n", info->loader_entries[info->loader_entry_active].path); - if (info->loader_options_added) - printf(" Options: %s\n", info->loader_options_added); + if (asprintf(&p, "%s/%s", prefix, suffix) < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + + if (rmdir(p) < 0) { + if (errno != ENOENT && errno != ENOTEMPTY) { + fprintf(stderr, "Failed to remove %s: %m\n", p); + free(p); + return -errno; } } else - printf("No suitable data is provided by the boot manager. See:\n" - " http://www.freedesktop.org/wiki/Software/systemd/BootLoaderInterface\n" - " http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec\n" - "for details.\n"); - printf("\n"); + fprintf(stderr, "Removed %s.\n", p); - boot_info_free(info); - return err; + free(p); + return 0; } -static int bootctl_main(int argc, char *argv[]) { - static const struct { - const char* verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (* const dispatch)(char **args, unsigned n); - } verbs[] = { - { "status", LESS, 1, show_status }, + +static int remove_binaries(const char *esp_path) { + char *p; + int r, q; + + if (asprintf(&p, "%s/EFI/sd-boot", esp_path) < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + + r = rm_rf(p, false, false, false); + free(p); + + q = remove_boot_efi(esp_path); + if (q < 0 && r == 0) + r = q; + + q = rmdir_one(esp_path, "loader/entries"); + if (q < 0 && r == 0) + r = q; + + q = rmdir_one(esp_path, "loader"); + if (q < 0 && r == 0) + r = q; + + q = rmdir_one(esp_path, "EFI/Boot"); + if (q < 0 && r == 0) + r = q; + + q = rmdir_one(esp_path, "EFI/sd-boot"); + if (q < 0 && r == 0) + r = q; + + q = rmdir_one(esp_path, "EFI"); + if (q < 0 && r == 0) + r = q; + + return r; +} + +static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { + uint16_t slot; + int r; + + if (!is_efi_boot()) + return 0; + + r = find_slot(uuid, path, &slot); + if (r != 1) + return 0; + + r = efi_remove_boot_option(slot); + if (r < 0) + return r; + + if (in_order) + remove_from_order(slot); + + return 0; +} + +static int install_loader_config(const char *esp_path) { + char *p = NULL; + char line[64]; + char *machine = NULL; + FILE *f; + + f = fopen("/etc/machine-id", "re"); + if (!f) + return -errno; + + if (fgets(line, sizeof(line), f) != NULL) { + char *s; + + s = strchr(line, '\n'); + if (s) + s[0] = '\0'; + if (strlen(line) == 32) + machine = line; + } + + fclose(f); + + if (!machine) + return -ESRCH; + + if (asprintf(&p, "%s/%s", esp_path, "loader/loader.conf") < 0) { + fprintf(stderr, "Out of memory.\n"); + return -ENOMEM; + } + + f = fopen(p, "wxe"); + if (f) { + fprintf(f, "#timeout 3\n"); + fprintf(f, "default %s-*\n", machine); + fclose(f); + } + + free(p); + return 0; +} + +static int help(void) { + printf("%s [COMMAND] [OPTIONS...]\n" + "\n" + "Install, update or remove the sdboot EFI boot manager.\n\n" + " -h --help Show this help\n" + " --version Print version\n" + " --path=PATH Path to the EFI System Partition (ESP)\n" + " --no-variables Don't touch EFI variables\n" + "\n" + "Comands:\n" + " status Show status of installed sd-boot and EFI variables\n" + " install Install sd-boot to the ESP and EFI variables\n" + " update Update sd-boot in the ESP and EFI variables\n" + " remove Remove sd-boot from the ESP and EFI variables\n", + program_invocation_short_name); + + return 0; +} + +static const char *arg_path = NULL; +static bool arg_touch_variables = true; + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_PATH = 0x100, + ARG_VERSION, + ARG_NO_VARIABLES, }; - int left; - unsigned i; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "path", required_argument, NULL, ARG_PATH }, + { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, + { NULL, 0, NULL, 0 } + }; + + int c; assert(argc >= 0); assert(argv); - left = argc - optind; + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { - if (left <= 0) - /* Special rule: no arguments means "status" */ - i = 0; - else { - if (streq(argv[optind], "help")) { + case 'h': help(); return 0; - } - for (i = 0; i < ELEMENTSOF(verbs); i++) - if (streq(argv[optind], verbs[i].verb)) - break; + case ARG_VERSION: + printf(VERSION "\n"); + return 0; - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation %s", argv[optind]); + case ARG_PATH: + arg_path = optarg; + break; + + case ARG_NO_VARIABLES: + arg_touch_variables = false; + break; + + case '?': + return -EINVAL; + + default: + fprintf(stderr, "Unknown option code '%c'.\n", c); return -EINVAL; } } - switch (verbs[i].argc_cmp) { + return 1; +} - case EQUAL: - if (left != verbs[i].argc) { - log_error("Invalid number of arguments."); - return -EINVAL; +static int bootctl_main(int argc, char*argv[]) { + enum action { + ACTION_STATUS, + ACTION_INSTALL, + ACTION_UPDATE, + ACTION_REMOVE + } arg_action = ACTION_STATUS; + static const struct { + const char* verb; + enum action action; + } verbs[] = { + { "status", ACTION_STATUS }, + { "install", ACTION_INSTALL }, + { "update", ACTION_UPDATE }, + { "remove", ACTION_REMOVE }, + }; + + sd_id128_t uuid = {}; + uint32_t part = 0; + uint64_t pstart = 0; + uint64_t psize = 0; + unsigned int i; + int q; + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (argv[optind]) { + for (i = 0; i < ELEMENTSOF(verbs); i++) { + if (!streq(argv[optind], verbs[i].verb)) + continue; + arg_action = verbs[i].action; + break; + } + if (i >= ELEMENTSOF(verbs)) { + fprintf(stderr, "Unknown operation %s\n", argv[optind]); + r = -EINVAL; + goto finish; } + } + + if (!arg_path) + arg_path = "/boot"; + + if (geteuid() != 0) { + fprintf(stderr, "Need to be root.\n"); + r = -EPERM; + goto finish; + } + + r = verify_esp(arg_path, &part, &pstart, &psize, &uuid); + if (r == -ENODEV && !arg_path) + fprintf(stderr, "You might want to use --path= to indicate the path to your ESP, in case it is not mounted to /boot.\n"); + if (r < 0) + goto finish; + + switch (arg_action) { + case ACTION_STATUS: { + _cleanup_free_ char *fw_type = NULL; + _cleanup_free_ char *fw_info = NULL; + _cleanup_free_ char *loader = NULL; + _cleanup_free_ char *loader_path = NULL; + sd_id128_t loader_part_uuid = {}; + + efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareType", &fw_type); + efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareInfo", &fw_info); + efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderInfo", &loader); + efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderImageIdentifier", &loader_path); + efi_tilt_backslashes(loader_path); + efi_loader_get_device_part_uuid(&loader_part_uuid); + + printf("System:\n"); + printf(" Firmware: %s (%s)\n", fw_type, strna(fw_info)); + printf(" Secure Boot: %s\n", is_efi_secure_boot() ? "enabled" : "disabled"); + printf(" Setup Mode: %s\n", is_efi_secure_boot_setup_mode() ? "setup" : "user"); + printf("\n"); + + printf("Loader:\n"); + printf(" Product: %s\n", strna(loader)); + if (!sd_id128_equal(loader_part_uuid, SD_ID128_NULL)) + printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + SD_ID128_FORMAT_VAL(loader_part_uuid)); + else + printf(" Partition: n/a\n"); + printf(" File: %s%s\n", draw_special_char(DRAW_TREE_RIGHT), strna(loader_path)); + printf("\n"); + + r = status_binaries(arg_path, uuid); + if (r < 0) + goto finish; + + if (arg_touch_variables) + r = status_variables(); break; + } - case MORE: - if (left < verbs[i].argc) { - log_error("Too few arguments."); - return -EINVAL; - } + case ACTION_INSTALL: + case ACTION_UPDATE: + umask(0002); + + r = install_binaries(arg_path, arg_action == ACTION_INSTALL); + if (r < 0) + goto finish; + + if (arg_action == ACTION_INSTALL) + install_loader_config(arg_path); + + if (arg_touch_variables) + r = install_variables(arg_path, + part, pstart, psize, uuid, + "/EFI/systemd/sd-boot" EFI_MACHINE_TYPE_NAME ".efi", + arg_action == ACTION_INSTALL); break; - case LESS: - if (left > verbs[i].argc) { - log_error("Too many arguments."); - return -EINVAL; + case ACTION_REMOVE: + r = remove_binaries(arg_path); + + if (arg_touch_variables) { + q = remove_variables(uuid, "/EFI/systemd/sd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + if (q < 0 && r == 0) + r = q; } break; - - default: - assert_not_reached("Unknown comparison operator."); } - return verbs[i].dispatch(argv + optind, left); +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } int main(int argc, char *argv[]) { diff --git a/src/shared/efivars.c b/src/shared/efivars.c index a31957452..5b3c5f09c 100644 --- a/src/shared/efivars.c +++ b/src/shared/efivars.c @@ -31,6 +31,40 @@ #ifdef ENABLE_EFI +#define LOAD_OPTION_ACTIVE 0x00000001 +#define MEDIA_DEVICE_PATH 0x04 +#define MEDIA_HARDDRIVE_DP 0x01 +#define MEDIA_FILEPATH_DP 0x04 +#define SIGNATURE_TYPE_GUID 0x02 +#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 +#define END_DEVICE_PATH_TYPE 0x7f +#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff + +struct boot_option { + uint32_t attr; + uint16_t path_len; + uint16_t title[]; +} __attribute__((packed)); + +struct drive_path { + uint32_t part_nr; + uint64_t part_start; + uint64_t part_size; + char signature[16]; + uint8_t mbr_type; + uint8_t signature_type; +} __attribute__((packed)); + +struct device_path { + uint8_t type; + uint8_t sub_type; + uint16_t length; + union { + uint16_t path[0]; + struct drive_path drive; + }; +} __attribute__((packed)); + bool is_efi_boot(void) { return access("/sys/firmware/efi", F_OK) >= 0; } @@ -128,6 +162,66 @@ int efi_get_variable( return 0; } +int efi_set_variable( + sd_id128_t vendor, + const char *name, + const void *value, + size_t size) { + + struct var { + uint32_t attr; + char buf[]; + } __attribute__((packed)) *buf = NULL; + char *p = NULL; + int fd = -1; + int r; + + assert(name); + + if (asprintf(&p, + "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + name, SD_ID128_FORMAT_VAL(vendor)) < 0) + return -ENOMEM; + + if (size == 0) { + r = unlink(p); + goto finish; + } + + fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644); + if (fd < 0) { + r = -errno; + goto finish; + } + + buf = malloc(sizeof(uint32_t) + size); + if (!buf) { + r = -errno; + goto finish; + } + + buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; + memcpy(buf->buf, value, size); + + r = write(fd, buf, sizeof(uint32_t) + size); + if (r < 0) { + r = -errno; + goto finish; + } + + if ((size_t)r != sizeof(uint32_t) + size) { + r = -EIO; + goto finish; + } + +finish: + if (fd >= 0) + close(fd); + free(buf); + free(p); + return r; +} + int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { _cleanup_free_ void *s = NULL; size_t ss = 0; @@ -179,8 +273,8 @@ int efi_get_boot_option( uint16_t id, char **title, sd_id128_t *part_uuid, - char **path) { - + char **path, + bool *active) { struct boot_option { uint32_t attr; uint16_t path_len; @@ -250,23 +344,23 @@ int efi_get_boot_option( break; /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ - if (dpath->type == 0x7f && dpath->sub_type == 0xff) + if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) break; dnext += dpath->length; /* Type 0x04 – Media Device Path */ - if (dpath->type != 0x04) + if (dpath->type != MEDIA_DEVICE_PATH) continue; /* Sub-Type 1 – Hard Drive */ - if (dpath->sub_type == 0x01) { + if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { /* 0x02 – GUID Partition Table */ - if (dpath->drive.mbr_type != 0x02) + if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) continue; /* 0x02 – GUID signature */ - if (dpath->drive.signature_type != 0x02) + if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) continue; if (part_uuid) @@ -275,8 +369,9 @@ int efi_get_boot_option( } /* Sub-Type 4 – File Path */ - if (dpath->sub_type == 0x04 && !p && path) { + if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && path) { p = utf16_to_utf8(dpath->path, dpath->length-4); + efi_tilt_backslashes(p); continue; } } @@ -288,6 +383,8 @@ int efi_get_boot_option( *part_uuid = p_uuid; if (path) *path = p; + if (active) + *active = !!header->attr & LOAD_OPTION_ACTIVE; return 0; err: @@ -296,6 +393,126 @@ err: return err; } +static void to_utf16(uint16_t *dest, const char *src) { + int i; + + for (i = 0; src[i] != '\0'; i++) + dest[i] = src[i]; + dest[i] = '\0'; +} + +struct guid { + uint32_t u1; + uint16_t u2; + uint16_t u3; + uint8_t u4[8]; +} __attribute__((packed)); + +static void id128_to_efi_guid(sd_id128_t id, void *guid) { + struct guid *uuid = guid; + + uuid->u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3]; + uuid->u2 = id.bytes[4] << 8 | id.bytes[5]; + uuid->u3 = id.bytes[6] << 8 | id.bytes[7]; + memcpy(uuid->u4, id.bytes+8, sizeof(uuid->u4)); +} + +static uint16_t *tilt_slashes(uint16_t *s) { + uint16_t *p; + + for (p = s; *p; p++) + if (*p == '/') + *p = '\\'; + + return s; +} + +char *efi_tilt_backslashes(char *s) { + char *p; + + for (p = s; *p; p++) + if (*p == '\\') + *p = '/'; + + return s; +} + +int efi_add_boot_option(uint16_t id, const char *title, + uint32_t part, uint64_t pstart, uint64_t psize, + sd_id128_t part_uuid, const char *path) { + char boot_id[9]; + char *buf; + size_t size; + size_t title_len; + size_t path_len; + struct boot_option *option; + struct device_path *devicep; + int err; + + title_len = (strlen(title)+1) * 2; + path_len = (strlen(path)+1) * 2; + + buf = calloc(sizeof(struct boot_option) + title_len + + sizeof(struct drive_path) + + sizeof(struct device_path) + path_len, 1); + if (!buf) { + err = -ENOMEM; + goto finish; + } + + /* header */ + option = (struct boot_option *)buf; + option->attr = LOAD_OPTION_ACTIVE; + option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + + offsetof(struct device_path, path) + path_len + + offsetof(struct device_path, path); + to_utf16(option->title, title); + size = offsetof(struct boot_option, title) + title_len; + + /* partition info */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_HARDDRIVE_DP; + devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); + devicep->drive.part_nr = part; + devicep->drive.part_start = pstart; + devicep->drive.part_size = psize; + devicep->drive.signature_type = SIGNATURE_TYPE_GUID; + devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; + id128_to_efi_guid(part_uuid, devicep->drive.signature); + size += devicep->length; + + /* path to loader */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_FILEPATH_DP; + devicep->length = offsetof(struct device_path, path) + path_len; + to_utf16(devicep->path, path); + tilt_slashes(devicep->path); + size += devicep->length; + + /* end of path */ + devicep = (struct device_path *)(buf + size); + devicep->type = END_DEVICE_PATH_TYPE; + devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; + devicep->length = offsetof(struct device_path, path); + size += devicep->length; + + snprintf(boot_id, sizeof(boot_id), "Boot%04X", id); + err = efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, buf, size); + +finish: + free(buf); + return err; +} + +int efi_remove_boot_option(uint16_t id) { + char boot_id[9]; + + snprintf(boot_id, sizeof(boot_id), "Boot%04X", id); + return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, 0); +} + int efi_get_boot_order(uint16_t **order) { void *buf; size_t l; @@ -320,6 +537,10 @@ int efi_get_boot_order(uint16_t **order) { return (int) (l / sizeof(uint16_t)); } +int efi_set_boot_order(uint16_t *order, size_t n) { + return efi_set_variable(EFI_VENDOR_GLOBAL, "BootOrder", order, n * sizeof(uint16_t)); +} + static int boot_id_hex(const char s[4]) { int i; int id = 0; diff --git a/src/shared/efivars.h b/src/shared/efivars.h index 7921bedc9..82364565b 100644 --- a/src/shared/efivars.h +++ b/src/shared/efivars.h @@ -30,17 +30,26 @@ #define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) #define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) +#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 bool is_efi_boot(void); int is_efi_secure_boot(void); int is_efi_secure_boot_setup_mode(void); int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size); +int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size); int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p); -int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *partuuid, char **path); +int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active); +int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path); +int efi_remove_boot_option(uint16_t id); int efi_get_boot_order(uint16_t **order); +int efi_set_boot_order(uint16_t *order, size_t n); int efi_get_boot_options(uint16_t **options); int efi_loader_get_device_part_uuid(sd_id128_t *u); int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); + +char *efi_tilt_backslashes(char *s);