diff options
Diffstat (limited to 'src/grp-boot')
34 files changed, 5962 insertions, 0 deletions
diff --git a/src/grp-boot/Makefile b/src/grp-boot/Makefile new file mode 100644 index 0000000000..df1febe823 --- /dev/null +++ b/src/grp-boot/Makefile @@ -0,0 +1,30 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew JÄ™drzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +nested.subdirs += bootctl +nested.subdirs += kernel-install +nested.subdirs += systemd-boot + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/bootctl/Makefile b/src/grp-boot/bootctl/Makefile new file mode 100644 index 0000000000..90bbed9fad --- /dev/null +++ b/src/grp-boot/bootctl/Makefile @@ -0,0 +1,53 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew JÄ™drzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_BLKID),) +bootctl_SOURCES = \ + src/boot/bootctl.c + +bootctl_CPPFLAGS = \ + -DEFI_MACHINE_TYPE_NAME=\"$(EFI_MACHINE_TYPE_NAME)\" \ + -DBOOTLIBDIR=\"$(bootlibdir)\" + +bootctl_CFLAGS = \ + $(BLKID_CFLAGS) + +bootctl_LDADD = \ + libsystemd-shared.la \ + $(BLKID_LIBS) + +bin_PROGRAMS += \ + bootctl + +dist_bashcompletion_data += \ + shell-completion/bash/bootctl + +dist_zshcompletion_data += \ + shell-completion/zsh/_bootctl +endif # HAVE_BLKID +endif # ENABLE_EFI + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/bootctl/bootctl.c b/src/grp-boot/bootctl/bootctl.c new file mode 100644 index 0000000000..19c5d1417f --- /dev/null +++ b/src/grp-boot/bootctl/bootctl.c @@ -0,0 +1,1143 @@ +/*** + This file is part of systemd. + + 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 + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <blkid/blkid.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <ftw.h> +#include <getopt.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <unistd.h> + +#include "basic/alloc-util.h" +#include "basic/blkid-util.h" +#include "basic/dirent-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/locale-util.h" +#include "basic/rm-rf.h" +#include "basic/string-util.h" +#include "basic/util.h" +#include "shared/efivars.h" + +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; + _cleanup_free_ char *t = NULL; + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + int r; + const char *v, *t2; + + if (statfs(p, &sfs) < 0) + return log_error_errno(errno, "Failed to check file system type of \"%s\": %m", p); + + if (sfs.f_type != 0x4d44) { + 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_error_errno(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_error_errno(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; + } + + 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) { + if (errno == 0) + return log_oom(); + + return log_error_errno(errno, "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) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe file system \"%s\": %m", p); + } + + errno = 0; + r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); + if (r != 0) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "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) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "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) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "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) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "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) { + r = errno ? -errno : -EIO; + return log_error_errno(r, "Failed to probe partition number \"%s\": m", p); + } + *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; + return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p); + } + *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; + return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p); + } + *psize = strtoul(v, NULL, 10); + + return 0; +} + +/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ +static int get_file_version(int fd, char **v) { + struct stat st; + char *buf; + const char *s, *e; + char *x = NULL; + int r = 0; + + assert(fd >= 0); + assert(v); + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_size < 27) + return 0; + + buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buf == MAP_FAILED) + return -errno; + + s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17); + if (!s) + goto finish; + s += 17; + + e = memmem(s, st.st_size - (s - buf), " ####", 5); + if (!e || e - s < 3) { + log_error("Malformed version string."); + r = -EINVAL; + goto finish; + } + + x = strndup(s, e - s); + if (!x) { + r = log_oom(); + goto finish; + } + r = 1; + +finish: + munmap(buf, st.st_size); + *v = x; + return r; +} + +static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { + char *p; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0, c = 0; + + p = strjoina(esp_path, "/", path); + d = opendir(p); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to read \"%s\": %m", p); + } + + FOREACH_DIRENT(de, d, break) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *v = NULL; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + if (prefix && !startswith_no_case(de->d_name, prefix)) + continue; + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); + + r = get_file_version(fd, &v); + if (r < 0) + return r; + if (r > 0) + printf(" File: %s/%s/%s (%s)\n", special_glyph(TREE_RIGHT), path, de->d_name, v); + else + printf(" File: %s/%s/%s\n", special_glyph(TREE_RIGHT), path, de->d_name); + c++; + } + + return c; +} + +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) + log_error("systemd-boot not installed in ESP."); + else if (r < 0) + return r; + + r = enumerate_binaries(esp_path, "EFI/BOOT", "boot"); + if (r == 0) + log_error("No default/fallback boot loader installed in ESP."); + else if (r < 0) + return r; + + printf("\n"); + + return 0; +} + +static int print_efi_option(uint16_t id, bool in_order) { + _cleanup_free_ char *title = NULL; + _cleanup_free_ 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) + return r; + + /* print only configured entries with partition information */ + if (!path || sd_id128_is_null(partition)) + 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%s\n", special_glyph(TREE_RIGHT), path); + printf("\n"); + + return 0; +} + +static int status_variables(void) { + int n_options, n_order; + _cleanup_free_ uint16_t *options = NULL, *order = NULL; + int i; + + if (!is_efi_boot()) { + log_notice("Not booted with EFI, not showing EFI variables."); + return 0; + } + + n_options = efi_get_boot_options(&options); + if (n_options == -ENOENT) + return log_error_errno(ENOENT, "Failed to access EFI variables, efivarfs" + " needs to be available at /sys/firmware/efi/efivars/."); + else if (n_options < 0) + return log_error_errno(n_options, "Failed to read EFI boot entries: %m"); + + n_order = efi_get_boot_order(&order); + if (n_order == -ENOENT) + n_order = 0; + else if (n_order < 0) + return log_error_errno(n_order, "Failed to read EFI boot order."); + + /* print entries in BootOrder first */ + printf("Boot Loader Entries in EFI Variables:\n"); + 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; + + for (j = 0; j < n_order; j++) + if (options[i] == order[j]) + goto next; + + print_efi_option(options[i], false); + next: + continue; + } + + return 0; +} + +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(int fd, const char *from, const char *to) { + _cleanup_free_ char *a = NULL, *b = NULL; + _cleanup_close_ int fd2 = -1; + int r; + + assert(fd >= 0); + assert(from); + assert(to); + + r = get_file_version(fd, &a); + if (r < 0) + return r; + if (r == 0) { + log_error("Source file \"%s\" does not carry version information!", from); + return -EINVAL; + } + + fd2 = open(to, O_RDONLY|O_CLOEXEC); + if (fd2 < 0) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to); + } + + r = get_file_version(fd2, &b); + if (r < 0) + return r; + if (r == 0 || compare_product(a, b) != 0) { + log_notice("Skipping \"%s\", since it's owned by another boot loader.", to); + return -EEXIST; + } + + if (compare_version(a, b) < 0) { + log_warning("Skipping \"%s\", since a newer boot loader version exists already.", to); + return -ESTALE; + } + + return 0; +} + +static int copy_file(const char *from, const char *to, bool force) { + _cleanup_fclose_ FILE *f = NULL, *g = NULL; + char *p; + int r; + struct timespec t[2]; + struct stat st; + + assert(from); + assert(to); + + f = fopen(from, "re"); + if (!f) + return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from); + + if (!force) { + /* If this is an update, then let's compare versions first */ + r = version_check(fileno(f), from, to); + if (r < 0) + return r; + } + + p = strjoina(to, "~"); + g = fopen(p, "wxe"); + if (!g) { + /* Directory doesn't exist yet? Then let's skip this... */ + if (!force && errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", to); + } + + rewind(f); + do { + size_t k; + uint8_t buf[32*1024]; + + k = fread(buf, 1, sizeof(buf), f); + if (ferror(f)) { + r = log_error_errno(EIO, "Failed to read \"%s\": %m", from); + goto error; + } + + if (k == 0) + break; + + fwrite(buf, 1, k, g); + if (ferror(g)) { + r = log_error_errno(EIO, "Failed to write \"%s\": %m", to); + goto error; + } + } while (!feof(f)); + + r = fflush_and_check(g); + if (r < 0) { + log_error_errno(r, "Failed to write \"%s\": %m", to); + goto error; + } + + r = fstat(fileno(f), &st); + if (r < 0) { + r = log_error_errno(errno, "Failed to get file timestamps of \"%s\": %m", from); + goto error; + } + + t[0] = st.st_atim; + t[1] = st.st_mtim; + + r = futimens(fileno(g), t); + if (r < 0) { + r = log_error_errno(errno, "Failed to set file timestamps on \"%s\": %m", p); + goto error; + } + + if (rename(p, to) < 0) { + r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", p, to); + goto error; + } + + log_info("Copied \"%s\" to \"%s\".", from, to); + return 0; + +error: + (void) unlink(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; + + p = strjoina(prefix, "/", suffix); + if (mkdir(p, 0700) < 0) { + if (errno != EEXIST) + return log_error_errno(errno, "Failed to create \"%s\": %m", p); + } else + log_info("Created \"%s\".", p); + + return 0; +} + +static const char *efi_subdirs[] = { + "EFI", + "EFI/systemd", + "EFI/BOOT", + "loader", + "loader/entries" +}; + +static int create_dirs(const char *esp_path) { + int r; + unsigned i; + + for (i = 0; i < ELEMENTSOF(efi_subdirs); i++) { + r = mkdir_one(esp_path, efi_subdirs[i]); + if (r < 0) + return r; + } + + return 0; +} + +static int copy_one_file(const char *esp_path, const char *name, bool force) { + char *p, *q; + int r; + + p = strjoina(BOOTLIBDIR "/", name); + q = strjoina(esp_path, "/EFI/systemd/", name); + r = copy_file(p, q, force); + + if (startswith(name, "systemd-boot")) { + int k; + char *v; + + /* Create the EFI default boot loader name (specified for removable devices) */ + v = strjoina(esp_path, "/EFI/BOOT/BOOT", name + strlen("systemd-boot")); + strupper(strrchr(v, '/') + 1); + + k = copy_file(p, v, force); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static int install_binaries(const char *esp_path, bool force) { + struct dirent *de; + _cleanup_closedir_ DIR *d = NULL; + 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(BOOTLIBDIR); + if (!d) + return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); + + FOREACH_DIRENT(de, d, break) { + int k; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + k = copy_one_file(esp_path, de->d_name, force); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static bool same_entry(uint16_t id, const sd_id128_t uuid, const char *path) { + _cleanup_free_ char *opath = NULL; + sd_id128_t ouuid; + int r; + + r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL); + if (r < 0) + return false; + if (!sd_id128_equal(uuid, ouuid)) + return false; + if (!streq_ptr(path, opath)) + return false; + + return true; +} + +static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { + _cleanup_free_ uint16_t *options = NULL; + int n, i; + + n = efi_get_boot_options(&options); + if (n < 0) + return n; + + /* find already existing systemd-boot entry */ + for (i = 0; i < n; i++) + if (same_entry(options[i], uuid, path)) { + *id = options[i]; + return 1; + } + + /* find free slot in the sorted BootXXXX variable list */ + for (i = 0; i < n; i++) + if (i != options[i]) { + *id = i; + return 1; + } + + /* use the next one */ + if (i == 0xffff) + return -ENOSPC; + *id = i; + return 0; +} + +static int insert_into_order(uint16_t slot, bool first) { + _cleanup_free_ uint16_t *order = NULL; + uint16_t *t; + int n, i; + + n = efi_get_boot_order(&order); + if (n <= 0) + /* no entry, add us */ + return efi_set_boot_order(&slot, 1); + + /* are we the first and only one? */ + if (n == 1 && order[0] == slot) + return 0; + + /* are we already in the boot order? */ + for (i = 0; i < n; i++) { + if (order[i] != slot) + continue; + + /* we do not require to be the first one, all is fine */ + if (!first) + return 0; + + /* move us to the first slot */ + memmove(order + 1, order, i * sizeof(uint16_t)); + order[0] = slot; + return efi_set_boot_order(order, n); + } + + /* extend array */ + t = realloc(order, (n + 1) * sizeof(uint16_t)); + if (!t) + return -ENOMEM; + order = t; + + /* add us to the top or end of the list */ + if (first) { + memmove(order + 1, order, n * sizeof(uint16_t)); + order[0] = slot; + } else + order[n] = slot; + + return efi_set_boot_order(order, n + 1); +} + +static int remove_from_order(uint16_t slot) { + _cleanup_free_ uint16_t *order = NULL; + int n, i; + + n = efi_get_boot_order(&order); + if (n <= 0) + return n; + + for (i = 0; i < n; i++) { + if (order[i] != slot) + continue; + + if (i + 1 < n) + memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t)); + return efi_set_boot_order(order, n - 1); + } + + return 0; +} + +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; + uint16_t slot; + int r; + + if (!is_efi_boot()) { + log_warning("Not booted with EFI, skipping EFI variable setup."); + return 0; + } + + p = strjoina(esp_path, path); + if (access(p, F_OK) < 0) { + if (errno == ENOENT) + return 0; + else + return log_error_errno(errno, "Cannot access \"%s\": %m", p); + } + + r = find_slot(uuid, path, &slot); + if (r < 0) + return log_error_errno(r, + r == -ENOENT ? + "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" : + "Failed to determine current boot order: %m"); + + if (first || r == false) { + r = efi_add_boot_option(slot, "Systemd Boot Manager", + part, pstart, psize, + uuid, path); + if (r < 0) + return log_error_errno(r, "Failed to create EFI Boot variable entry: %m"); + + log_info("Created EFI boot entry \"Systemd Boot Manager\"."); + } + + return insert_into_order(slot, first); +} + +static int remove_boot_efi(const char *esp_path) { + char *p; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r, c = 0; + + p = strjoina(esp_path, "/EFI/BOOT"); + d = opendir(p); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open directory \"%s\": %m", p); + } + + FOREACH_DIRENT(de, d, break) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *v = NULL; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + if (!startswith_no_case(de->d_name, "boot")) + continue; + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); + + r = get_file_version(fd, &v); + if (r < 0) + return r; + if (r > 0 && startswith(v, "systemd-boot ")) { + r = unlinkat(dirfd(d), de->d_name, 0); + if (r < 0) + return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name); + + log_info("Removed \"%s/%s\".", p, de->d_name); + } + + c++; + } + + return c; +} + +static int rmdir_one(const char *prefix, const char *suffix) { + char *p; + + p = strjoina(prefix, "/", suffix); + if (rmdir(p) < 0) { + if (!IN_SET(errno, ENOENT, ENOTEMPTY)) + return log_error_errno(errno, "Failed to remove \"%s\": %m", p); + } else + log_info("Removed \"%s\".", p); + + return 0; +} + +static int remove_binaries(const char *esp_path) { + char *p; + int r, q; + unsigned i; + + p = strjoina(esp_path, "/EFI/systemd"); + r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); + + q = remove_boot_efi(esp_path); + if (q < 0 && r == 0) + r = q; + + for (i = ELEMENTSOF(efi_subdirs); i > 0; i--) { + q = rmdir_one(esp_path, efi_subdirs[i-1]); + 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) + return remove_from_order(slot); + else + return 0; +} + +static int install_loader_config(const char *esp_path) { + char *p; + char line[64]; + char *machine = NULL; + _cleanup_fclose_ FILE *f = NULL, *g = NULL; + + f = fopen("/etc/machine-id", "re"); + if (!f) + return errno == ENOENT ? 0 : -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; + } + + if (!machine) + return -ESRCH; + + p = strjoina(esp_path, "/loader/loader.conf"); + g = fopen(p, "wxe"); + if (g) { + fprintf(g, "#timeout 3\n"); + fprintf(g, "default %s-*\n", machine); + if (ferror(g)) + return log_error_errno(EIO, "Failed to write \"%s\": %m", p); + } + + return 0; +} + +static int help(void) { + printf("%s [COMMAND] [OPTIONS...]\n" + "\n" + "Install, update or remove the systemd-boot 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" + "Commands:\n" + " status Show status of installed systemd-boot and EFI variables\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", + program_invocation_short_name); + + return 0; +} + +static const char *arg_path = "/boot"; +static bool arg_touch_variables = true; + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_PATH = 0x100, + ARG_VERSION, + ARG_NO_VARIABLES, + }; + + 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); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_PATH: + arg_path = optarg; + break; + + case ARG_NO_VARIABLES: + arg_touch_variables = false; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option"); + } + + return 1; +} + +static void read_loader_efi_var(const char *name, char **var) { + int r; + + r = efi_get_variable_string(EFI_VENDOR_LOADER, name, var); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read EFI variable %s: %m", name); +} + +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, psize = 0; + int r, q; + + if (argv[optind]) { + unsigned i; + + 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)) { + log_error("Unknown operation \"%s\"", argv[optind]); + return -EINVAL; + } + } + + if (geteuid() != 0) + return log_error_errno(EPERM, "Need to be root."); + + r = verify_esp(arg_path, &part, &pstart, &psize, &uuid); + if (r == -ENODEV && !arg_path) + log_notice("You might want to use --path= to indicate the path to your ESP, in case it is not mounted on /boot."); + if (r < 0) + return r; + + 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 = {}; + + if (is_efi_boot()) { + read_loader_efi_var("LoaderFirmwareType", &fw_type); + read_loader_efi_var("LoaderFirmwareInfo", &fw_info); + read_loader_efi_var("LoaderInfo", &loader); + read_loader_efi_var("LoaderImageIdentifier", &loader_path); + if (loader_path) + efi_tilt_backslashes(loader_path); + r = efi_loader_get_device_part_uuid(&loader_part_uuid); + if (r < 0 && r == -ENOENT) + log_warning_errno(r, "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) + log_warning_errno(r, "Failed to query secure boot status: %m"); + else + printf(" Secure Boot: %s\n", r ? "enabled" : "disabled"); + + r = is_efi_secure_boot_setup_mode(); + if (r < 0) + log_warning_errno(r, "Failed to query secure boot mode: %m"); + else + printf(" Setup Mode: %s\n", r ? "setup" : "user"); + printf("\n"); + + printf("Loader:\n"); + printf(" Product: %s\n", strna(loader)); + if (!sd_id128_is_null(loader_part_uuid)) + 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", special_glyph(TREE_RIGHT), strna(loader_path)); + printf("\n"); + } else + printf("System:\n Not booted with EFI\n"); + + r = status_binaries(arg_path, uuid); + if (r < 0) + return r; + + if (arg_touch_variables) + r = status_variables(); + break; + } + + case ACTION_INSTALL: + case ACTION_UPDATE: + umask(0002); + + r = install_binaries(arg_path, arg_action == ACTION_INSTALL); + if (r < 0) + return r; + + if (arg_action == ACTION_INSTALL) { + r = install_loader_config(arg_path); + if (r < 0) + return r; + } + + if (arg_touch_variables) + r = install_variables(arg_path, + part, pstart, psize, uuid, + "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", + arg_action == ACTION_INSTALL); + break; + + case ACTION_REMOVE: + r = remove_binaries(arg_path); + + if (arg_touch_variables) { + q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + if (q < 0 && r == 0) + r = q; + } + break; + } + + return r; +} + +int main(int argc, char *argv[]) { + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bootctl_main(argc, argv); + + finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-boot/bootctl/bootctl.completion.bash b/src/grp-boot/bootctl/bootctl.completion.bash new file mode 100644 index 0000000000..c86ec7edc9 --- /dev/null +++ b/src/grp-boot/bootctl/bootctl.completion.bash @@ -0,0 +1,60 @@ +# bootctl(1) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2014 Thomas H.P. Andersen +# +# 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 +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +_bootctl() { + local i verb comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local -A OPTS=( + [STANDALONE]='-h --help --version' + ) + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [STANDALONE]='status' + ) + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z $verb ]]; then + comps=${VERBS[*]} + elif __contains_word "$verb" ${VERBS[STANDALONE]}; then + comps='' + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _bootctl bootctl diff --git a/src/grp-boot/bootctl/bootctl.completion.zsh b/src/grp-boot/bootctl/bootctl.completion.zsh new file mode 100644 index 0000000000..0e1b0a5562 --- /dev/null +++ b/src/grp-boot/bootctl/bootctl.completion.zsh @@ -0,0 +1,30 @@ +#compdef bootctl + +(( $+functions[_bootctl_command] )) || _bootctl_command() +{ + local -a _bootctl_cmds + _bootctl_cmds=( + "status:Show status of installed systemd-boot and EFI variables" + "install:Install systemd-boot to the ESP and EFI variables" + "update:Update systemd-boot in the ESP and EFI variables" + "remove:Remove systemd-boot from the ESP and EFI variables" + ) + if (( CURRENT == 1 )); then + _describe -t commands 'bootctl command' _bootctl_cmds || compadd "$@" + else + local curcontext="$curcontext" + cmd="${${_bootctl_cmds[(r)$words[1]:*]%%:*}}" + if (( $+functions[_bootctl_$cmd] )); then + _bootctl_$cmd + else + _message "no more options" + fi + fi +} + +_arguments \ + {-h,--help}'[Prints a short help text and exits.]' \ + '--version[Prints a short version string and exits.]' \ + '--path=[Path to the EFI System Partition (ESP)]:path:_directories' \ + '--no-variables[Do not touch EFI variables]' \ + '*::bootctl command:_bootctl_command' diff --git a/src/grp-boot/bootctl/bootctl.xml b/src/grp-boot/bootctl/bootctl.xml new file mode 100644 index 0000000000..6e835c037f --- /dev/null +++ b/src/grp-boot/bootctl/bootctl.xml @@ -0,0 +1,128 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="bootctl" conditional='ENABLE_EFI' + xmlns:xi="http://www.w3.org/2001/XInclude"> + <refentryinfo> + <title>bootctl</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Kay</firstname> + <surname>Sievers</surname> + <email>kay@vrfy.org</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>bootctl</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname>bootctl</refname> + <refpurpose>Control the firmware and boot manager settings</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>status</command> + </cmdsynopsis> + <cmdsynopsis> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>update</command> + </cmdsynopsis> + <cmdsynopsis> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>install</command> + </cmdsynopsis> + <cmdsynopsis> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>remove</command> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>bootctl</command> checks, updates, + installs or removes the boot loader from the current + system.</para> + + <para><command>bootctl status</command> checks and prints the + currently installed versions of the boot loader binaries and + all current EFI boot variables.</para> + + <para><command>bootctl update</command> updates all installed + versions of systemd-boot, if the current version is newer than the + version installed in the EFI system partition. This also includes + the EFI default/fallback loader at /EFI/BOOT/BOOT*.EFI. A + systemd-boot entry in the EFI boot variables is created if there + is no current entry. The created entry will be added to the end of + the boot order list.</para> + + <para><command>bootctl install</command> installs systemd-boot into + the EFI system partition. A copy of systemd-boot will be stored as + the EFI default/fallback loader at /EFI/BOOT/BOOT*.EFI. A systemd-boot + entry in the EFI boot variables is created and added to the top + of the boot order list.</para> + + <para><command>bootctl remove</command> removes all installed + versions of systemd-boot from the EFI system partition, and removes + systemd-boot from the EFI boot variables.</para> + + <para>If no command is passed, <command>status</command> is + implied.</para> + </refsect1> + + <refsect1> + <title>Options</title> + <para>The following options are understood:</para> + + <variablelist> + <xi:include href="standard-options.xml" xpointer="help" /> + <xi:include href="standard-options.xml" xpointer="version" /> + <varlistentry> + <term><option>--path</option></term> + <listitem><para>Path to the EFI system partition. The default is /boot.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--no-variables</option></term> + <listitem><para>Do not touch the EFI boot variables.</para></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Exit status</title> + <para>On success, 0 is returned, a non-zero failure + code otherwise.</para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <ulink url="http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec">Boot loader specification</ulink> + <ulink url="http://www.freedesktop.org/wiki/Software/systemd/BootLoaderInterface">Systemd boot loader interface</ulink> + </para> + </refsect1> +</refentry> diff --git a/src/grp-boot/kernel-install/50-depmod.install b/src/grp-boot/kernel-install/50-depmod.install new file mode 100644 index 0000000000..68c24bed7a --- /dev/null +++ b/src/grp-boot/kernel-install/50-depmod.install @@ -0,0 +1,8 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +[[ $1 == "add" ]] || exit 0 +[[ $2 ]] || exit 1 + +exec depmod -a "$2" diff --git a/src/grp-boot/kernel-install/90-loaderentry.install b/src/grp-boot/kernel-install/90-loaderentry.install new file mode 100644 index 0000000000..af9f0f9ccd --- /dev/null +++ b/src/grp-boot/kernel-install/90-loaderentry.install @@ -0,0 +1,89 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +COMMAND="$1" +KERNEL_VERSION="$2" +BOOT_DIR_ABS="$3" +KERNEL_IMAGE="$4" + +if [[ -f /etc/machine-id ]]; then + read MACHINE_ID < /etc/machine-id +fi + +if ! [[ $MACHINE_ID ]]; then + exit 1 +fi + +BOOT_DIR="/$MACHINE_ID/$KERNEL_VERSION" +BOOT_ROOT=${BOOT_DIR_ABS%$BOOT_DIR} +LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf" + +if [[ $COMMAND == remove ]]; then + exec rm -f "$LOADER_ENTRY" +fi + +if ! [[ $COMMAND == add ]]; then + exit 1 +fi + +if ! [[ $KERNEL_IMAGE ]]; then + exit 1 +fi + +if [[ -f /etc/os-release ]]; then + . /etc/os-release +elif [[ -f /usr/lib/os-release ]]; then + . /usr/lib/os-release +fi + +if ! [[ $PRETTY_NAME ]]; then + PRETTY_NAME="GNU/Linux $KERNEL_VERSION" +fi + +declare -a BOOT_OPTIONS + +if [[ -f /etc/kernel/cmdline ]]; then + read -r -d '' -a BOOT_OPTIONS < /etc/kernel/cmdline +fi + +if ! [[ ${BOOT_OPTIONS[*]} ]]; then + read -r -d '' -a line < /proc/cmdline + for i in "${line[@]}"; do + [[ "${i#initrd=*}" != "$i" ]] && continue + BOOT_OPTIONS+=("$i") + done +fi + +if ! [[ ${BOOT_OPTIONS[*]} ]]; then + echo "Could not determine the kernel command line parameters." >&2 + echo "Please specify the kernel command line in /etc/kernel/cmdline!" >&2 + exit 1 +fi + +cp "$KERNEL_IMAGE" "$BOOT_DIR_ABS/linux" && + chown root:root "$BOOT_DIR_ABS/linux" && + chmod 0644 "$BOOT_DIR_ABS/linux" || { + echo "Could not copy '$KERNEL_IMAGE to '$BOOT_DIR_ABS/linux'." >&2 + exit 1 +} + +mkdir -p "${LOADER_ENTRY%/*}" || { + echo "Could not create loader entry directory '${LOADER_ENTRY%/*}'." >&2 + exit 1 +} + +{ + echo "title $PRETTY_NAME" + echo "version $KERNEL_VERSION" + echo "machine-id $MACHINE_ID" + echo "options ${BOOT_OPTIONS[*]}" + echo "linux $BOOT_DIR/linux" + [[ -f $BOOT_DIR_ABS/initrd ]] && \ + echo "initrd $BOOT_DIR/initrd" + : +} > "$LOADER_ENTRY" || { + echo "Could not create loader entry '$LOADER_ENTRY'." >&2 + exit 1 +} +exit 0 diff --git a/src/grp-boot/kernel-install/Makefile b/src/grp-boot/kernel-install/Makefile new file mode 100644 index 0000000000..6a937f516c --- /dev/null +++ b/src/grp-boot/kernel-install/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew JÄ™drzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +dist_bin_SCRIPTS = \ + src/kernel-install/kernel-install + +dist_kernelinstall_SCRIPTS = \ + src/kernel-install/50-depmod.install \ + src/kernel-install/90-loaderentry.install + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/kernel-install/kernel-install b/src/grp-boot/kernel-install/kernel-install new file mode 100644 index 0000000000..1159dc384d --- /dev/null +++ b/src/grp-boot/kernel-install/kernel-install @@ -0,0 +1,144 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +# +# This file is part of systemd. +# +# Copyright 2013 Harald Hoyer +# +# 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 +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +usage() +{ + echo "Usage:" + echo " $0 add KERNEL-VERSION KERNEL-IMAGE" + echo " $0 remove KERNEL-VERSION" +} + +dropindirs_sort() +{ + local suffix=$1; shift + local -a files + local f d i + + readarray -t files < <( + for d in "$@"; do + for i in "$d/"*"$suffix"; do + if [[ -e "$i" ]]; then + echo "${i##*/}" + fi + done + done | sort -Vu + ) + + for f in "${files[@]}"; do + for d in "$@"; do + if [[ -e "$d/$f" ]]; then + echo "$d/$f" + continue 2 + fi + done + done +} + +export LC_COLLATE=C + +for i in "$@"; do + if [ "$i" == "--help" -o "$i" == "-h" ]; then + usage + exit 0 + fi +done + +if [[ "${0##*/}" == 'installkernel' ]]; then + COMMAND='add' +else + COMMAND="$1" + shift +fi + +KERNEL_VERSION="$1" +KERNEL_IMAGE="$2" + +if [[ -f /etc/machine-id ]]; then + read MACHINE_ID < /etc/machine-id +fi + +if ! [[ $MACHINE_ID ]]; then + echo "Could not determine your machine ID from /etc/machine-id." >&2 + echo "Please run 'systemd-machine-id-setup' as root. See man:machine-id(5)" >&2 + exit 1 +fi + +if [[ ! $COMMAND ]] || [[ ! $KERNEL_VERSION ]]; then + echo "Not enough arguments" >&2 + exit 1 +fi + +if [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then + BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" +elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]] \ + || mountpoint -q /boot/efi; then + BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION" +else + BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" +fi + +ret=0 + +readarray -t PLUGINS < <( + dropindirs_sort ".install" \ + "/etc/kernel/install.d" \ + "/usr/lib/kernel/install.d" +) + +case $COMMAND in + add) + if [[ ! "$KERNEL_IMAGE" ]]; then + echo "Command 'add' requires an argument" >&2 + exit 1 + fi + + mkdir -p "$BOOT_DIR_ABS" || { + echo "Could not create boot directory '$BOOT_DIR_ABS'." >&2 + exit 1 + } + + for f in "${PLUGINS[@]}"; do + if [[ -x $f ]]; then + "$f" add "$KERNEL_VERSION" "$BOOT_DIR_ABS" "$KERNEL_IMAGE" + ((ret+=$?)) + fi + done + ;; + + remove) + for f in "${PLUGINS[@]}"; do + if [[ -x $f ]]; then + "$f" remove "$KERNEL_VERSION" "$BOOT_DIR_ABS" + ((ret+=$?)) + fi + done + + rm -rf "$BOOT_DIR_ABS" + ((ret+=$?)) + ;; + + *) + echo "Unknown command '$COMMAND'" >&2 + exit 1 + ;; +esac + +exit $ret diff --git a/src/grp-boot/kernel-install/kernel-install.completion.bash b/src/grp-boot/kernel-install/kernel-install.completion.bash new file mode 100644 index 0000000000..7cd2494cf7 --- /dev/null +++ b/src/grp-boot/kernel-install/kernel-install.completion.bash @@ -0,0 +1,50 @@ +# kernel-install(8) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2013 Kay Sievers +# Copyright 2013 Harald Hoyer +# +# 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 +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +_kernel_install() { + local comps + local MACHINE_ID + local cur=${COMP_WORDS[COMP_CWORD]} + + case $COMP_CWORD in + 1) + comps="add remove" + ;; + 2) + comps=$(cd /lib/modules; echo [0-9]*) + if [[ ${COMP_WORDS[1]} == "remove" ]] && [[ -f /etc/machine-id ]]; then + read MACHINE_ID < /etc/machine-id + if [[ $MACHINE_ID ]] && ( [[ -d /boot/$MACHINE_ID ]] || [[ -L /boot/$MACHINE_ID ]] ); then + comps=$(cd "/boot/$MACHINE_ID"; echo [0-9]*) + fi + fi + ;; + 3) + [[ "$cur" ]] || cur=/boot/vmlinuz-${COMP_WORDS[2]} + comps=$(compgen -f -- "$cur") + compopt -o filenames + ;; + esac + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _kernel_install kernel-install diff --git a/src/grp-boot/kernel-install/kernel-install.completion.zsh b/src/grp-boot/kernel-install/kernel-install.completion.zsh new file mode 100644 index 0000000000..4fdd3a4ae7 --- /dev/null +++ b/src/grp-boot/kernel-install/kernel-install.completion.zsh @@ -0,0 +1,26 @@ +#compdef kernel-install + +_images(){ + if [[ "$words[2]" == "remove" ]]; then + _message 'No more options' + else + _path_files -W /boot/ -P /boot/ -g "vmlinuz-*" + fi +} + +_kernels(){ + read _MACHINE_ID < /etc/machine-id + _kernel=( /lib/modules/[0-9]* ) + if [[ "$cmd" == "remove" && -n "$_MACHINE_ID" ]]; then + _kernel=( "/boot/$_MACHINE_ID"/[0-9]* ) + fi + _kernel=( ${_kernel##*/} ) + _describe "installed kernels" _kernel +} + +_arguments \ + '1::add or remove:(add remove)' \ + '2::kernel versions:_kernels' \ + '3::kernel images:_images' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/grp-boot/kernel-install/kernel-install.xml b/src/grp-boot/kernel-install/kernel-install.xml new file mode 100644 index 0000000000..eb519188a6 --- /dev/null +++ b/src/grp-boot/kernel-install/kernel-install.xml @@ -0,0 +1,192 @@ +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" +"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2013 Harald Hoyer + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="kernel-install"> + + <refentryinfo> + <title>kernel-install</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Harald</firstname> + <surname>Hoyer</surname> + <email>harald@redhat.com</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>kernel-install</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>kernel-install</refname> + <refpurpose>Add and remove kernel and initramfs images to and from /boot</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>kernel-install</command> + <arg choice="plain">COMMAND</arg> + <arg choice="plain"><replaceable>KERNEL-VERSION</replaceable></arg> + <arg choice="opt"><replaceable>KERNEL-IMAGE</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + <para> + <command>kernel-install</command> is used to install and remove kernel and + initramfs images to and from <filename>/boot</filename>. + </para> + + <para><command>kernel-install</command> will execute the files + located in the directory <filename>/usr/lib/kernel/install.d/</filename> + and the local administration directory <filename>/etc/kernel/install.d/</filename>. + All files are collectively sorted and executed in lexical order, regardless of the directory in + which they live. However, files with identical filenames replace each other. + Files in <filename>/etc/kernel/install.d/</filename> take precedence over files with the same name + in <filename>/usr/lib/kernel/install.d/</filename>. This can be used to override a system-supplied + executables with a local file if needed; a symbolic link in <filename>/etc/kernel/install.d/</filename> + with the same name as an executable in <filename>/usr/lib/kernel/install.d/</filename>, + pointing to /dev/null, disables the executable entirely. Executables must have the + extension <literal>.install</literal>; other extensions are ignored.</para> + + </refsect1> + + <refsect1> + <title>Commands</title> + <para>The following commands are understood:</para> + <variablelist> + <varlistentry> + <term><command>add <replaceable>KERNEL-VERSION</replaceable> <replaceable>KERNEL-IMAGE</replaceable></command></term> + <listitem> + <para><command>kernel-install</command> creates the directory + <filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename> + and calls every executable + <filename>/usr/lib/kernel/install.d/*.install</filename> and + <filename>/etc/kernel/install.d/*.install</filename> with + the arguments + <programlisting>add <replaceable>KERNEL-VERSION</replaceable> <filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename></programlisting> + </para> + + <para>The kernel-install plugin <filename>50-depmod.install</filename> runs depmod for the <replaceable>KERNEL-VERSION</replaceable>.</para> + + <para>The kernel-install plugin + <filename>90-loaderentry.install</filename> copies + <replaceable>KERNEL-IMAGE</replaceable> to + <filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/linux</filename>. + It also creates a boot loader entry according to the boot + loader specification in + <filename>/boot/loader/entries/<replaceable>MACHINE-ID</replaceable>-<replaceable>KERNEL-VERSION</replaceable>.conf</filename>. + The title of the entry is the + <replaceable>PRETTY_NAME</replaceable> parameter specified + in <filename>/etc/os-release</filename> or + <filename>/usr/lib/os-release</filename> (if the former is + missing), or "GNU/Linux + <replaceable>KERNEL-VERSION</replaceable>", if unset. If + the file <filename>initrd</filename> is found next to the + <filename>linux</filename> file, the initrd will be added to + the configuration.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><command>remove <replaceable>KERNEL-VERSION</replaceable></command></term> + <listitem> + <para>Calls every executable <filename>/usr/lib/kernel/install.d/*.install</filename> + and <filename>/etc/kernel/install.d/*.install</filename> with the arguments + <programlisting>remove <replaceable>KERNEL-VERSION</replaceable> <filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename></programlisting> + </para> + + <para><command>kernel-install</command> removes the entire directory + <filename>/boot/<replaceable>MACHINE-ID</replaceable>/<replaceable>KERNEL-VERSION</replaceable>/</filename> afterwards.</para> + + <para>The kernel-install plugin <filename>90-loaderentry.install</filename> removes the file + <filename>/boot/loader/entries/<replaceable>MACHINE-ID</replaceable>-<replaceable>KERNEL-VERSION</replaceable>.conf</filename>.</para> + </listitem> + </varlistentry> + + </variablelist> + + </refsect1> + + <refsect1> + <title>Exit status</title> + <para>If every executable returns with 0, 0 is returned, a non-zero failure code otherwise.</para> + </refsect1> + + <refsect1> + <title>Files</title> + <variablelist> + <varlistentry> + <term> + <filename>/usr/lib/kernel/install.d/*.install</filename> + <filename>/etc/kernel/install.d/*.install</filename> + </term> + <listitem> + <para>Drop-in files which are executed by kernel-install.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <filename>/etc/kernel/cmdline</filename> + <filename>/proc/cmdline</filename> + </term> + <listitem> + <para>The content of the file <filename>/etc/kernel/cmdline</filename> specifies the kernel command line to use. + If that file does not exist, <filename>/proc/cmdline</filename> is used.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <filename>/etc/machine-id</filename> + </term> + <listitem> + <para>The content of the file specifies the machine identification <replaceable>MACHINE-ID</replaceable>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <filename>/etc/os-release</filename> + <filename>/usr/lib/os-release</filename> + </term> + <listitem> + <para>The content of the file specifies the operating system title <replaceable>PRETTY_NAME</replaceable>.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <ulink url="http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec">Boot loader specification</ulink> + </para> + </refsect1> + +</refentry> diff --git a/src/grp-boot/systemd-boot/.gitignore b/src/grp-boot/systemd-boot/.gitignore new file mode 100644 index 0000000000..e193acbe12 --- /dev/null +++ b/src/grp-boot/systemd-boot/.gitignore @@ -0,0 +1,2 @@ +/systemd_boot.so +/stub.so diff --git a/src/grp-boot/systemd-boot/Makefile b/src/grp-boot/systemd-boot/Makefile new file mode 100644 index 0000000000..3b4d4ddda1 --- /dev/null +++ b/src/grp-boot/systemd-boot/Makefile @@ -0,0 +1,193 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew JÄ™drzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +efi_cppflags = \ + $(EFI_CPPFLAGS) \ + -I$(top_builddir) -include config.h \ + -I$(EFI_INC_DIR)/efi \ + -I$(EFI_INC_DIR)/efi/$(EFI_ARCH) \ + -DEFI_MACHINE_TYPE_NAME=\"$(EFI_MACHINE_TYPE_NAME)\" + +efi_cflags = \ + $(EFI_CFLAGS) \ + -Wall \ + -Wextra \ + -std=gnu90 \ + -nostdinc \ + -ggdb -O0 \ + -fpic \ + -fshort-wchar \ + -nostdinc \ + -ffreestanding \ + -fno-strict-aliasing \ + -fno-stack-protector \ + -Wsign-compare \ + -Wno-missing-field-initializers + +ifneq ($(ARCH_X86_64),) +efi_cflags += \ + -mno-red-zone \ + -mno-sse \ + -mno-mmx \ + -DEFI_FUNCTION_WRAPPER \ + -DGNU_EFI_USE_MS_ABI +endif # ARCH_X86_64 + +ifneq ($(ARCH_IA32),) +efi_cflags += \ + -mno-sse \ + -mno-mmx +endif # ARCH_IA32 + +efi_ldflags = \ + $(EFI_LDFLAGS) \ + -T $(EFI_LDS_DIR)/elf_$(EFI_ARCH)_efi.lds \ + -shared \ + -Bsymbolic \ + -nostdlib \ + -znocombreloc \ + -L $(EFI_LIB_DIR) \ + $(EFI_LDS_DIR)/crt0-efi-$(EFI_ARCH).o + +# Aarch64 and ARM32 don't have an EFI capable objcopy. Use 'binary' instead, +# and add required symbols manually. +ifneq ($(ARCH_AARCH64),) +efi_ldflags += --defsym=EFI_SUBSYSTEM=0xa +EFI_FORMAT = -O binary +else +EFI_FORMAT = --target=efi-app-$(EFI_ARCH) +endif # ARCH_AARCH64 +endif # HAVE_GNUEFI +endif # ENABLE_EFI + +# ------------------------------------------------------------------------------ +systemd_boot_headers = \ + src/boot/efi/util.h \ + src/boot/efi/console.h \ + src/boot/efi/graphics.h \ + src/boot/efi/pefile.h \ + src/boot/efi/measure.h \ + src/boot/efi/disk.h + +systemd_boot_sources = \ + src/boot/efi/util.c \ + src/boot/efi/console.c \ + src/boot/efi/graphics.c \ + src/boot/efi/pefile.c \ + src/boot/efi/disk.c \ + src/boot/efi/measure.c \ + src/boot/efi/boot.c + +EXTRA_DIST += $(systemd_boot_sources) $(systemd_boot_headers) + +systemd_boot_objects = $(addprefix $(top_builddir)/,$(systemd_boot_sources:.c=.o)) +systemd_boot_solib = $(top_builddir)/src/boot/efi/systemd_boot.so +systemd_boot = systemd-boot$(EFI_MACHINE_TYPE_NAME).efi + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +bootlib_DATA = $(systemd_boot) + +$(outdir)/%.o: $(top_srcdir)/src/boot/efi/%.c $(addprefix $(top_srcdir)/,$(systemd_boot_headers)) + @$(MKDIR_P) $(top_builddir)/src/boot/efi/ + $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ + +$(systemd_boot_solib): $(systemd_boot_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(systemd_boot_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : + +$(systemd_boot): $(systemd_boot_solib) + $(AM_V_GEN)$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc $(EFI_FORMAT) $< $@ +endif # HAVE_GNUEFI +endif # ENABLE_EFI + +CLEANFILES += $(systemd_boot_objects) $(systemd_boot_solib) $(systemd_boot) + +# ------------------------------------------------------------------------------ +stub_headers = \ + src/boot/efi/util.h \ + src/boot/efi/pefile.h \ + src/boot/efi/disk.h \ + src/boot/efi/graphics.h \ + src/boot/efi/splash.h \ + src/boot/efi/measure.h \ + src/boot/efi/linux.h + +stub_sources = \ + src/boot/efi/util.c \ + src/boot/efi/pefile.c \ + src/boot/efi/disk.c \ + src/boot/efi/graphics.c \ + src/boot/efi/splash.c \ + src/boot/efi/linux.c \ + src/boot/efi/measure.c \ + src/boot/efi/stub.c + +EXTRA_DIST += \ + $(stub_sources) \ + $(stub_headers) \ + test/splash.bmp + +stub_objects = $(addprefix $(top_builddir)/,$(stub_sources:.c=.o)) +stub_solib = $(top_builddir)/src/boot/efi/stub.so +stub = linux$(EFI_MACHINE_TYPE_NAME).efi.stub + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +bootlib_DATA += $(stub) + +$(outdir)/%.o: $(top_srcdir)/src/boot/efi/%.c $(addprefix $(top_srcdir)/,$(stub_headers)) + @$(MKDIR_P) $(top_builddir)/src/boot/efi/ + $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ + +$(stub_solib): $(stub_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(stub_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : + +$(stub): $(stub_solib) + $(AM_V_GEN)$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc $(EFI_FORMAT) $< $@ +endif # HAVE_GNUEFI +endif # ENABLE_EFI + +CLEANFILES += $(stub_objects) $(stub_solib) $(stub) + +# ------------------------------------------------------------------------------ +CLEANFILES += test-efi-disk.img + +test-efi-disk.img: $(systemd_boot) $(stub) test/test-efi-create-disk.sh + $(AM_V_GEN)test/test-efi-create-disk.sh + +test-efi: test-efi-disk.img + $(QEMU) -machine accel=kvm -m 1024 -bios $(QEMU_BIOS) -snapshot test-efi-disk.img + +EXTRA_DIST += test/test-efi-create-disk.sh + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/systemd-boot/boot.c b/src/grp-boot/systemd-boot/boot.c new file mode 100644 index 0000000000..9dfaed6d46 --- /dev/null +++ b/src/grp-boot/systemd-boot/boot.c @@ -0,0 +1,1857 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2015 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012-2015 Harald Hoyer <harald@redhat.com> + */ + +#include <efi.h> +#include <efilib.h> + +#include "console.h" +#include "disk.h" +#include "graphics.h" +#include "linux.h" +#include "measure.h" +#include "pefile.h" +#include "util.h" + +#ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI +#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL +#endif + +/* magic string to find in the binary image */ +static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-boot " VERSION " ####"; + +static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; + +enum loader_type { + LOADER_UNDEFINED, + LOADER_EFI, + LOADER_LINUX +}; + +typedef struct { + CHAR16 *file; + CHAR16 *title_show; + CHAR16 *title; + CHAR16 *version; + CHAR16 *machine_id; + EFI_HANDLE *device; + enum loader_type type; + CHAR16 *loader; + CHAR16 *options; + CHAR16 key; + EFI_STATUS (*call)(VOID); + BOOLEAN no_autoselect; + BOOLEAN non_unique; +} ConfigEntry; + +typedef struct { + ConfigEntry **entries; + UINTN entry_count; + INTN idx_default; + INTN idx_default_efivar; + UINTN timeout_sec; + UINTN timeout_sec_config; + INTN timeout_sec_efivar; + CHAR16 *entry_default_pattern; + CHAR16 *entry_oneshot; + CHAR16 *options_edit; + BOOLEAN no_editor; +} Config; + +static VOID cursor_left(UINTN *cursor, UINTN *first) { + if ((*cursor) > 0) + (*cursor)--; + else if ((*first) > 0) + (*first)--; +} + +static VOID cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len) { + if ((*cursor)+1 < x_max) + (*cursor)++; + else if ((*first) + (*cursor) < len) + (*first)++; +} + +static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) { + CHAR16 *line; + UINTN size; + UINTN len; + UINTN first; + CHAR16 *print; + UINTN cursor; + UINTN clear; + BOOLEAN exit; + BOOLEAN enter; + + if (!line_in) + line_in = L""; + size = StrLen(line_in) + 1024; + line = AllocatePool(size * sizeof(CHAR16)); + StrCpy(line, line_in); + len = StrLen(line); + print = AllocatePool((x_max+1) * sizeof(CHAR16)); + + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE); + + first = 0; + cursor = 0; + clear = 0; + enter = FALSE; + exit = FALSE; + while (!exit) { + EFI_STATUS err; + UINT64 key; + UINTN i; + + i = len - first; + if (i >= x_max-1) + i = x_max-1; + CopyMem(print, line + first, i * sizeof(CHAR16)); + while (clear > 0 && i < x_max-1) { + clear--; + print[i++] = ' '; + } + print[i] = '\0'; + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + + err = console_key_read(&key, TRUE); + if (EFI_ERROR(err)) + continue; + + switch (key) { + case KEYPRESS(0, SCAN_ESC, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')): + exit = TRUE; + break; + + case KEYPRESS(0, SCAN_HOME, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')): + /* beginning-of-line */ + cursor = 0; + first = 0; + continue; + + case KEYPRESS(0, SCAN_END, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')): + /* end-of-line */ + cursor = len - first; + if (cursor+1 >= x_max) { + cursor = x_max-1; + first = len - (x_max-1); + } + continue; + + case KEYPRESS(0, SCAN_DOWN, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0): + /* forward-word */ + while (line[first + cursor] && line[first + cursor] == ' ') + cursor_right(&cursor, &first, x_max, len); + while (line[first + cursor] && line[first + cursor] != ' ') + cursor_right(&cursor, &first, x_max, len); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(0, SCAN_UP, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0): + /* backward-word */ + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + while ((first + cursor) > 0 && line[first + cursor] == ' ') + cursor_left(&cursor, &first); + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') + cursor_left(&cursor, &first); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(0, SCAN_RIGHT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')): + /* forward-char */ + if (first + cursor == len) + continue; + cursor_right(&cursor, &first, x_max, len); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(0, SCAN_LEFT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): + /* backward-char */ + cursor_left(&cursor, &first); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + continue; + + case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): + /* kill-word */ + clear = 0; + for (i = first + cursor; i < len && line[i] == ' '; i++) + clear++; + for (; i < len && line[i] != ' '; i++) + clear++; + + for (i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')): + case KEYPRESS(EFI_ALT_PRESSED, 0, CHAR_BACKSPACE): + /* backward-kill-word */ + clear = 0; + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + clear++; + while ((first + cursor) > 0 && line[first + cursor] == ' ') { + cursor_left(&cursor, &first); + clear++; + } + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') { + cursor_left(&cursor, &first); + clear++; + } + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos); + + for (i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(0, SCAN_DELETE, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')): + if (len == 0) + continue; + if (first + cursor == len) + continue; + for (i = first + cursor; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')): + /* kill-line */ + line[first + cursor] = '\0'; + clear = len - (first + cursor); + len = first + cursor; + continue; + + case KEYPRESS(0, 0, CHAR_LINEFEED): + case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN): + if (StrCmp(line, line_in) != 0) { + *line_out = line; + line = NULL; + } + enter = TRUE; + exit = TRUE; + break; + + case KEYPRESS(0, 0, CHAR_BACKSPACE): + if (len == 0) + continue; + if (first == 0 && cursor == 0) + continue; + for (i = first + cursor-1; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + if (cursor > 0) + cursor--; + if (cursor > 0 || first == 0) + continue; + /* show full line if it fits */ + if (len < x_max) { + cursor = first; + first = 0; + continue; + } + /* jump left to see what we delete */ + if (first > 10) { + first -= 10; + cursor = 10; + } else { + cursor = first; + first = 0; + } + continue; + + case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'): + case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff): + if (len+1 == size) + continue; + for (i = len; i > first + cursor; i--) + line[i] = line[i-1]; + line[first + cursor] = KEYCHAR(key); + len++; + line[len] = '\0'; + if (cursor+1 < x_max) + cursor++; + else if (first + cursor < len) + first++; + continue; + } + } + + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); + FreePool(print); + FreePool(line); + return enter; +} + +static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) { + UINTN i; + + if (key == 0) + return -1; + + /* select entry by number key */ + if (key >= '1' && key <= '9') { + i = key - '0'; + if (i > config->entry_count) + i = config->entry_count; + return i-1; + } + + /* find matching key in config entries */ + for (i = start; i < config->entry_count; i++) + if (config->entries[i]->key == key) + return i; + + for (i = 0; i < start; i++) + if (config->entries[i]->key == key) + return i; + + return -1; +} + +static VOID print_status(Config *config, CHAR16 *loaded_image_path) { + UINT64 key; + UINTN i; + CHAR16 *s; + CHAR8 *b; + UINTN x; + UINTN y; + UINTN size; + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + Print(L"systemd-boot version: " VERSION "\n"); + Print(L"architecture: " EFI_MACHINE_TYPE_NAME "\n"); + Print(L"loaded image: %s\n", loaded_image_path); + Print(L"UEFI specification: %d.%02d\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); + Print(L"firmware vendor: %s\n", ST->FirmwareVendor); + Print(L"firmware version: %d.%02d\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + + if (uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x, &y) == EFI_SUCCESS) + Print(L"console size: %d x %d\n", x, y); + + if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) { + Print(L"SecureBoot: %s\n", yes_no(*b > 0)); + FreePool(b); + } + + if (efivar_get_raw(&global_guid, L"SetupMode", &b, &size) == EFI_SUCCESS) { + Print(L"SetupMode: %s\n", *b > 0 ? L"setup" : L"user"); + FreePool(b); + } + + if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) { + Print(L"OsIndicationsSupported: %d\n", (UINT64)*b); + FreePool(b); + } + Print(L"\n"); + + Print(L"timeout: %d\n", config->timeout_sec); + if (config->timeout_sec_efivar >= 0) + Print(L"timeout (EFI var): %d\n", config->timeout_sec_efivar); + Print(L"timeout (config): %d\n", config->timeout_sec_config); + if (config->entry_default_pattern) + Print(L"default pattern: '%s'\n", config->entry_default_pattern); + Print(L"editor: %s\n", yes_no(!config->no_editor)); + Print(L"\n"); + + Print(L"config entry count: %d\n", config->entry_count); + Print(L"entry selected idx: %d\n", config->idx_default); + if (config->idx_default_efivar >= 0) + Print(L"entry EFI var idx: %d\n", config->idx_default_efivar); + Print(L"\n"); + + if (efivar_get_int(L"LoaderConfigTimeout", &i) == EFI_SUCCESS) + Print(L"LoaderConfigTimeout: %d\n", i); + if (config->entry_oneshot) + Print(L"LoaderEntryOneShot: %s\n", config->entry_oneshot); + if (efivar_get(L"LoaderDevicePartUUID", &s) == EFI_SUCCESS) { + Print(L"LoaderDevicePartUUID: %s\n", s); + FreePool(s); + } + if (efivar_get(L"LoaderEntryDefault", &s) == EFI_SUCCESS) { + Print(L"LoaderEntryDefault: %s\n", s); + FreePool(s); + } + + Print(L"\n--- press key ---\n\n"); + console_key_read(&key, TRUE); + + for (i = 0; i < config->entry_count; i++) { + ConfigEntry *entry; + + if (key == KEYPRESS(0, SCAN_ESC, 0) || key == KEYPRESS(0, 0, 'q')) + break; + + entry = config->entries[i]; + Print(L"config entry: %d/%d\n", i+1, config->entry_count); + if (entry->file) + Print(L"file '%s'\n", entry->file); + Print(L"title show '%s'\n", entry->title_show); + if (entry->title) + Print(L"title '%s'\n", entry->title); + if (entry->version) + Print(L"version '%s'\n", entry->version); + if (entry->machine_id) + Print(L"machine-id '%s'\n", entry->machine_id); + if (entry->device) { + EFI_DEVICE_PATH *device_path; + CHAR16 *str; + + device_path = DevicePathFromHandle(entry->device); + if (device_path) { + str = DevicePathToStr(device_path); + Print(L"device handle '%s'\n", str); + FreePool(str); + } + } + if (entry->loader) + Print(L"loader '%s'\n", entry->loader); + if (entry->options) + Print(L"options '%s'\n", entry->options); + Print(L"auto-select %s\n", yes_no(!entry->no_autoselect)); + if (entry->call) + Print(L"internal call yes\n"); + + Print(L"\n--- press key ---\n\n"); + console_key_read(&key, TRUE); + } + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); +} + +static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *loaded_image_path) { + EFI_STATUS err; + UINTN visible_max; + UINTN idx_highlight; + UINTN idx_highlight_prev; + UINTN idx_first; + UINTN idx_last; + BOOLEAN refresh; + BOOLEAN highlight; + UINTN i; + UINTN line_width; + CHAR16 **lines; + UINTN x_start; + UINTN y_start; + UINTN x_max; + UINTN y_max; + CHAR16 *status; + CHAR16 *clearline; + INTN timeout_remain; + INT16 idx; + BOOLEAN exit = FALSE; + BOOLEAN run = TRUE; + BOOLEAN wait = FALSE; + + graphics_mode(FALSE); + uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE); + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + + /* draw a single character to make ClearScreen work on some firmware */ + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L" "); + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max); + if (EFI_ERROR(err)) { + x_max = 80; + y_max = 25; + } + + /* we check 10 times per second for a keystroke */ + if (config->timeout_sec > 0) + timeout_remain = config->timeout_sec * 10; + else + timeout_remain = -1; + + idx_highlight = config->idx_default; + idx_highlight_prev = 0; + + visible_max = y_max - 2; + + if ((UINTN)config->idx_default >= visible_max) + idx_first = config->idx_default-1; + else + idx_first = 0; + + idx_last = idx_first + visible_max-1; + + refresh = TRUE; + highlight = FALSE; + + /* length of the longest entry */ + line_width = 5; + for (i = 0; i < config->entry_count; i++) { + UINTN entry_len; + + entry_len = StrLen(config->entries[i]->title_show); + if (line_width < entry_len) + line_width = entry_len; + } + if (line_width > x_max-6) + line_width = x_max-6; + + /* offsets to center the entries on the screen */ + x_start = (x_max - (line_width)) / 2; + if (config->entry_count < visible_max) + y_start = ((visible_max - config->entry_count) / 2) + 1; + else + y_start = 0; + + /* menu entries title lines */ + lines = AllocatePool(sizeof(CHAR16 *) * config->entry_count); + for (i = 0; i < config->entry_count; i++) { + UINTN j, k; + + lines[i] = AllocatePool(((x_max+1) * sizeof(CHAR16))); + for (j = 0; j < x_start; j++) + lines[i][j] = ' '; + + for (k = 0; config->entries[i]->title_show[k] != '\0' && j < x_max; j++, k++) + lines[i][j] = config->entries[i]->title_show[k]; + + for (; j < x_max; j++) + lines[i][j] = ' '; + lines[i][x_max] = '\0'; + } + + status = NULL; + clearline = AllocatePool((x_max+1) * sizeof(CHAR16)); + for (i = 0; i < x_max; i++) + clearline[i] = ' '; + clearline[i] = 0; + + while (!exit) { + UINT64 key; + + if (refresh) { + for (i = 0; i < config->entry_count; i++) { + if (i < idx_first || i > idx_last) + continue; + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + i - idx_first); + if (i == idx_highlight) + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY); + else + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]); + if ((INTN)i == config->idx_default_efivar) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + i - idx_first); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); + } + } + refresh = FALSE; + } else if (highlight) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight_prev - idx_first); + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]); + if ((INTN)idx_highlight_prev == config->idx_default_efivar) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight_prev - idx_first); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); + } + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight - idx_first); + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]); + if ((INTN)idx_highlight == config->idx_default_efivar) { + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight - idx_first); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"=>"); + } + highlight = FALSE; + } + + if (timeout_remain > 0) { + FreePool(status); + status = PoolPrint(L"Boot in %d sec.", (timeout_remain + 5) / 10); + } + + /* print status at last line of screen */ + if (status) { + UINTN len; + UINTN x; + + /* center line */ + len = StrLen(status); + if (len < x_max) + x = (x_max - len) / 2; + else + x = 0; + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline + (x_max - x)); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len); + } + + err = console_key_read(&key, wait); + if (EFI_ERROR(err)) { + /* timeout reached */ + if (timeout_remain == 0) { + exit = TRUE; + break; + } + + /* sleep and update status */ + if (timeout_remain > 0) { + uefi_call_wrapper(BS->Stall, 1, 100 * 1000); + timeout_remain--; + continue; + } + + /* timeout disabled, wait for next key */ + wait = TRUE; + continue; + } + + timeout_remain = -1; + + /* clear status after keystroke */ + if (status) { + FreePool(status); + status = NULL; + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); + } + + idx_highlight_prev = idx_highlight; + + switch (key) { + case KEYPRESS(0, SCAN_UP, 0): + case KEYPRESS(0, 0, 'k'): + if (idx_highlight > 0) + idx_highlight--; + break; + + case KEYPRESS(0, SCAN_DOWN, 0): + case KEYPRESS(0, 0, 'j'): + if (idx_highlight < config->entry_count-1) + idx_highlight++; + break; + + case KEYPRESS(0, SCAN_HOME, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, '<'): + if (idx_highlight > 0) { + refresh = TRUE; + idx_highlight = 0; + } + break; + + case KEYPRESS(0, SCAN_END, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, '>'): + if (idx_highlight < config->entry_count-1) { + refresh = TRUE; + idx_highlight = config->entry_count-1; + } + break; + + case KEYPRESS(0, SCAN_PAGE_UP, 0): + if (idx_highlight > visible_max) + idx_highlight -= visible_max; + else + idx_highlight = 0; + break; + + case KEYPRESS(0, SCAN_PAGE_DOWN, 0): + idx_highlight += visible_max; + if (idx_highlight > config->entry_count-1) + idx_highlight = config->entry_count-1; + break; + + case KEYPRESS(0, 0, CHAR_LINEFEED): + case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN): + exit = TRUE; + break; + + case KEYPRESS(0, SCAN_F1, 0): + case KEYPRESS(0, 0, 'h'): + case KEYPRESS(0, 0, '?'): + status = StrDuplicate(L"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp"); + break; + + case KEYPRESS(0, 0, 'Q'): + exit = TRUE; + run = FALSE; + break; + + case KEYPRESS(0, 0, 'd'): + if (config->idx_default_efivar != (INTN)idx_highlight) { + /* store the selected entry in a persistent EFI variable */ + efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE); + config->idx_default_efivar = idx_highlight; + status = StrDuplicate(L"Default boot entry selected."); + } else { + /* clear the default entry EFI variable */ + efivar_set(L"LoaderEntryDefault", NULL, TRUE); + config->idx_default_efivar = -1; + status = StrDuplicate(L"Default boot entry cleared."); + } + refresh = TRUE; + break; + + case KEYPRESS(0, 0, '-'): + case KEYPRESS(0, 0, 'T'): + if (config->timeout_sec_efivar > 0) { + config->timeout_sec_efivar--; + efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE); + if (config->timeout_sec_efivar > 0) + status = PoolPrint(L"Menu timeout set to %d sec.", config->timeout_sec_efivar); + else + status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); + } else if (config->timeout_sec_efivar <= 0){ + config->timeout_sec_efivar = -1; + efivar_set(L"LoaderConfigTimeout", NULL, TRUE); + if (config->timeout_sec_config > 0) + status = PoolPrint(L"Menu timeout of %d sec is defined by configuration file.", + config->timeout_sec_config); + else + status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); + } + break; + + case KEYPRESS(0, 0, '+'): + case KEYPRESS(0, 0, 't'): + if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0) + config->timeout_sec_efivar++; + config->timeout_sec_efivar++; + efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE); + if (config->timeout_sec_efivar > 0) + status = PoolPrint(L"Menu timeout set to %d sec.", + config->timeout_sec_efivar); + else + status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu."); + break; + + case KEYPRESS(0, 0, 'e'): + /* only the options of configured entries can be edited */ + if (config->no_editor || config->entries[idx_highlight]->type == LOADER_UNDEFINED) + break; + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); + if (line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-1, y_max-1)) + exit = TRUE; + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1); + uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1); + break; + + case KEYPRESS(0, 0, 'v'): + status = PoolPrint(L"systemd-boot " VERSION " (" EFI_MACHINE_TYPE_NAME "), UEFI Specification %d.%02d, Vendor %s %d.%02d", + ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff, + ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + break; + + case KEYPRESS(0, 0, 'P'): + print_status(config, loaded_image_path); + refresh = TRUE; + break; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')): + refresh = TRUE; + break; + + default: + /* jump with a hotkey directly to a matching entry */ + idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key)); + if (idx < 0) + break; + idx_highlight = idx; + refresh = TRUE; + } + + if (idx_highlight > idx_last) { + idx_last = idx_highlight; + idx_first = 1 + idx_highlight - visible_max; + refresh = TRUE; + } else if (idx_highlight < idx_first) { + idx_first = idx_highlight; + idx_last = idx_highlight + visible_max-1; + refresh = TRUE; + } + + if (!refresh && idx_highlight != idx_highlight_prev) + highlight = TRUE; + } + + *chosen_entry = config->entries[idx_highlight]; + + for (i = 0; i < config->entry_count; i++) + FreePool(lines[i]); + FreePool(lines); + FreePool(clearline); + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK); + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + return run; +} + +static VOID config_add_entry(Config *config, ConfigEntry *entry) { + if ((config->entry_count & 15) == 0) { + UINTN i; + + i = config->entry_count + 16; + if (config->entry_count == 0) + config->entries = AllocatePool(sizeof(VOID *) * i); + else + config->entries = ReallocatePool(config->entries, + sizeof(VOID *) * config->entry_count, sizeof(VOID *) * i); + } + config->entries[config->entry_count++] = entry; +} + +static VOID config_entry_free(ConfigEntry *entry) { + FreePool(entry->title_show); + FreePool(entry->title); + FreePool(entry->machine_id); + FreePool(entry->loader); + FreePool(entry->options); +} + +static BOOLEAN is_digit(CHAR16 c) { + return (c >= '0') && (c <= '9'); +} + +static UINTN c_order(CHAR16 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 INTN str_verscmp(CHAR16 *s1, CHAR16 *s2) { + CHAR16 *os1 = s1; + CHAR16 *os2 = s2; + + while (*s1 || *s2) { + INTN first; + + while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) { + INTN 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) + return first; + } + + return StrCmp(os1, os2); +} + +static CHAR8 *line_get_key_value(CHAR8 *content, CHAR8 *sep, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) { + CHAR8 *line; + UINTN linelen; + CHAR8 *value; + +skip: + line = content + *pos; + if (*line == '\0') + return NULL; + + linelen = 0; + while (line[linelen] && !strchra((CHAR8 *)"\n\r", line[linelen])) + linelen++; + + /* move pos to next line */ + *pos += linelen; + if (content[*pos]) + (*pos)++; + + /* empty line */ + if (linelen == 0) + goto skip; + + /* terminate line */ + line[linelen] = '\0'; + + /* remove leading whitespace */ + while (strchra((CHAR8 *)" \t", *line)) { + line++; + linelen--; + } + + /* remove trailing whitespace */ + while (linelen > 0 && strchra(sep, line[linelen-1])) + linelen--; + line[linelen] = '\0'; + + if (*line == '#') + goto skip; + + /* split key/value */ + value = line; + while (*value && !strchra(sep, *value)) + value++; + if (*value == '\0') + goto skip; + *value = '\0'; + value++; + while (*value && strchra(sep, *value)) + value++; + + /* unquote */ + if (value[0] == '\"' && line[linelen-1] == '\"') { + value++; + line[linelen-1] = '\0'; + } + + *key_ret = line; + *value_ret = value; + return line; +} + +static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { + CHAR8 *line; + UINTN pos = 0; + CHAR8 *key, *value; + + line = content; + while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { + if (strcmpa((CHAR8 *)"timeout", key) == 0) { + CHAR16 *s; + + s = stra_to_str(value); + config->timeout_sec_config = Atoi(s); + config->timeout_sec = config->timeout_sec_config; + FreePool(s); + continue; + } + + if (strcmpa((CHAR8 *)"default", key) == 0) { + FreePool(config->entry_default_pattern); + config->entry_default_pattern = stra_to_str(value); + StrLwr(config->entry_default_pattern); + continue; + } + + if (strcmpa((CHAR8 *)"editor", key) == 0) { + BOOLEAN on; + + if (EFI_ERROR(parse_boolean(value, &on))) + continue; + config->no_editor = !on; + } + } +} + +static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) { + ConfigEntry *entry; + CHAR8 *line; + UINTN pos = 0; + CHAR8 *key, *value; + UINTN len; + CHAR16 *initrd = NULL; + + entry = AllocateZeroPool(sizeof(ConfigEntry)); + + line = content; + while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { + if (strcmpa((CHAR8 *)"title", key) == 0) { + FreePool(entry->title); + entry->title = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"version", key) == 0) { + FreePool(entry->version); + entry->version = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"machine-id", key) == 0) { + FreePool(entry->machine_id); + entry->machine_id = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"linux", key) == 0) { + FreePool(entry->loader); + entry->type = LOADER_LINUX; + entry->loader = stra_to_path(value); + entry->key = 'l'; + continue; + } + + if (strcmpa((CHAR8 *)"efi", key) == 0) { + entry->type = LOADER_EFI; + FreePool(entry->loader); + entry->loader = stra_to_path(value); + + /* do not add an entry for ourselves */ + if (StriCmp(entry->loader, loaded_image_path) == 0) { + entry->type = LOADER_UNDEFINED; + break; + } + continue; + } + + if (strcmpa((CHAR8 *)"architecture", key) == 0) { + /* do not add an entry for an EFI image of architecture not matching with that of the image */ + if (strcmpa((CHAR8 *)EFI_MACHINE_TYPE_NAME, value) != 0) { + entry->type = LOADER_UNDEFINED; + break; + } + continue; + } + + if (strcmpa((CHAR8 *)"initrd", key) == 0) { + CHAR16 *new; + + new = stra_to_path(value); + if (initrd) { + CHAR16 *s; + + s = PoolPrint(L"%s initrd=%s", initrd, new); + FreePool(initrd); + initrd = s; + } else + initrd = PoolPrint(L"initrd=%s", new); + FreePool(new); + continue; + } + + if (strcmpa((CHAR8 *)"options", key) == 0) { + CHAR16 *new; + + new = stra_to_str(value); + if (entry->options) { + CHAR16 *s; + + s = PoolPrint(L"%s %s", entry->options, new); + FreePool(entry->options); + entry->options = s; + } else { + entry->options = new; + new = NULL; + } + FreePool(new); + continue; + } + } + + if (entry->type == LOADER_UNDEFINED) { + config_entry_free(entry); + FreePool(initrd); + FreePool(entry); + return; + } + + /* add initrd= to options */ + if (entry->type == LOADER_LINUX && initrd) { + if (entry->options) { + CHAR16 *s; + + s = PoolPrint(L"%s %s", initrd, entry->options); + FreePool(entry->options); + entry->options = s; + } else { + entry->options = initrd; + initrd = NULL; + } + } + FreePool(initrd); + + entry->device = device; + entry->file = StrDuplicate(file); + len = StrLen(entry->file); + /* remove ".conf" */ + if (len > 5) + entry->file[len - 5] = '\0'; + StrLwr(entry->file); + + config_add_entry(config, entry); +} + +static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { + CHAR8 *content = NULL; + UINTN sec; + UINTN len; + EFI_STATUS err; + + len = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content); + if (len > 0) + config_defaults_load_from_file(config, content); + FreePool(content); + + err = efivar_get_int(L"LoaderConfigTimeout", &sec); + if (!EFI_ERROR(err)) { + config->timeout_sec_efivar = sec; + config->timeout_sec = sec; + } else + config->timeout_sec_efivar = -1; +} + +static VOID config_load_entries(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path) { + EFI_FILE_HANDLE entries_dir; + EFI_STATUS err; + + err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0ULL); + if (!EFI_ERROR(err)) { + for (;;) { + CHAR16 buf[256]; + UINTN bufsize; + EFI_FILE_INFO *f; + CHAR8 *content = NULL; + UINTN len; + + bufsize = sizeof(buf); + err = uefi_call_wrapper(entries_dir->Read, 3, entries_dir, &bufsize, buf); + if (bufsize == 0 || EFI_ERROR(err)) + break; + + f = (EFI_FILE_INFO *) buf; + if (f->FileName[0] == '.') + continue; + if (f->Attribute & EFI_FILE_DIRECTORY) + continue; + + len = StrLen(f->FileName); + if (len < 6) + continue; + if (StriCmp(f->FileName + len - 5, L".conf") != 0) + continue; + if (StrnCmp(f->FileName, L"auto-", 5) == 0) + continue; + + len = file_read(entries_dir, f->FileName, 0, 0, &content); + if (len > 0) + config_entry_add_from_file(config, device, f->FileName, content, loaded_image_path); + FreePool(content); + } + uefi_call_wrapper(entries_dir->Close, 1, entries_dir); + } +} + +static VOID config_sort_entries(Config *config) { + UINTN i; + + for (i = 1; i < config->entry_count; i++) { + BOOLEAN more; + UINTN k; + + more = FALSE; + for (k = 0; k < config->entry_count - i; k++) { + ConfigEntry *entry; + + if (str_verscmp(config->entries[k]->file, config->entries[k+1]->file) <= 0) + continue; + entry = config->entries[k]; + config->entries[k] = config->entries[k+1]; + config->entries[k+1] = entry; + more = TRUE; + } + if (!more) + break; + } +} + +static VOID config_default_entry_select(Config *config) { + CHAR16 *var; + EFI_STATUS err; + UINTN i; + + /* + * The EFI variable to specify a boot entry for the next, and only the + * next reboot. The variable is always cleared directly after it is read. + */ + err = efivar_get(L"LoaderEntryOneShot", &var); + if (!EFI_ERROR(err)) { + BOOLEAN found = FALSE; + + for (i = 0; i < config->entry_count; i++) { + if (StrCmp(config->entries[i]->file, var) == 0) { + config->idx_default = i; + found = TRUE; + break; + } + } + + config->entry_oneshot = StrDuplicate(var); + efivar_set(L"LoaderEntryOneShot", NULL, TRUE); + FreePool(var); + if (found) + return; + } + + /* + * The EFI variable to select the default boot entry overrides the + * configured pattern. The variable can be set and cleared by pressing + * the 'd' key in the loader selection menu, the entry is marked with + * an '*'. + */ + err = efivar_get(L"LoaderEntryDefault", &var); + if (!EFI_ERROR(err)) { + BOOLEAN found = FALSE; + + for (i = 0; i < config->entry_count; i++) { + if (StrCmp(config->entries[i]->file, var) == 0) { + config->idx_default = i; + config->idx_default_efivar = i; + found = TRUE; + break; + } + } + FreePool(var); + if (found) + return; + } + config->idx_default_efivar = -1; + + if (config->entry_count == 0) + return; + + /* + * Match the pattern from the end of the list to the start, find last + * entry (largest number) matching the given pattern. + */ + if (config->entry_default_pattern) { + i = config->entry_count; + while (i--) { + if (config->entries[i]->no_autoselect) + continue; + if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) { + config->idx_default = i; + return; + } + } + } + + /* select the last suitable entry */ + i = config->entry_count; + while (i--) { + if (config->entries[i]->no_autoselect) + continue; + config->idx_default = i; + return; + } + + /* no entry found */ + config->idx_default = -1; +} + +/* generate a unique title, avoiding non-distinguishable menu entries */ +static VOID config_title_generate(Config *config) { + UINTN i, k; + BOOLEAN unique; + + /* set title */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *title; + + FreePool(config->entries[i]->title_show); + title = config->entries[i]->title; + if (!title) + title = config->entries[i]->file; + 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) + return; + + /* add version to non-unique titles */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *s; + + if (!config->entries[i]->non_unique) + continue; + if (!config->entries[i]->version) + continue; + + 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) + return; + + /* add machine-id to non-unique titles */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *s; + CHAR16 *m; + + if (!config->entries[i]->non_unique) + continue; + if (!config->entries[i]->machine_id) + continue; + + m = StrDuplicate(config->entries[i]->machine_id); + m[8] = '\0'; + 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) + return; + + /* add file name to non-unique titles */ + for (i = 0; i < config->entry_count; i++) { + CHAR16 *s; + + if (!config->entries[i]->non_unique) + continue; + s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->file); + FreePool(config->entries[i]->title_show); + config->entries[i]->title_show = s; + config->entries[i]->non_unique = FALSE; + } +} + +static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (*call)(VOID)) { + ConfigEntry *entry; + + entry = AllocateZeroPool(sizeof(ConfigEntry)); + entry->title = StrDuplicate(title); + entry->call = call; + entry->no_autoselect = TRUE; + config_add_entry(config, entry); + return TRUE; +} + +static ConfigEntry *config_entry_add_loader(Config *config, EFI_HANDLE *device, + enum loader_type type,CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { + ConfigEntry *entry; + + entry = AllocateZeroPool(sizeof(ConfigEntry)); + entry->type = type; + entry->title = StrDuplicate(title); + entry->device = device; + entry->loader = StrDuplicate(loader); + entry->file = StrDuplicate(file); + StrLwr(entry->file); + entry->key = key; + config_add_entry(config, entry); + + return entry; +} + +static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, + CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { + EFI_FILE_HANDLE handle; + ConfigEntry *entry; + EFI_STATUS err; + + /* do not add an entry for ourselves */ + if (loaded_image_path && StriCmp(loader, loaded_image_path) == 0) + return FALSE; + + /* check existence */ + err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, loader, EFI_FILE_MODE_READ, 0ULL); + if (EFI_ERROR(err)) + return FALSE; + uefi_call_wrapper(handle->Close, 1, handle); + + entry = config_entry_add_loader(config, device, LOADER_UNDEFINED, file, key, title, loader); + if (!entry) + return FALSE; + + /* do not boot right away into auto-detected entries */ + entry->no_autoselect = TRUE; + + return TRUE; +} + +static VOID config_entry_add_osx(Config *config) { + EFI_STATUS err; + UINTN handle_count = 0; + EFI_HANDLE *handles = NULL; + + err = LibLocateHandle(ByProtocol, &FileSystemProtocol, NULL, &handle_count, &handles); + if (!EFI_ERROR(err)) { + UINTN i; + + for (i = 0; i < handle_count; i++) { + EFI_FILE *root; + BOOLEAN found; + + root = LibOpenRoot(handles[i]); + if (!root) + continue; + found = config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X", + L"\\System\\Library\\CoreServices\\boot.efi"); + uefi_call_wrapper(root->Close, 1, root); + if (found) + break; + } + + FreePool(handles); + } +} + +static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_image, EFI_FILE *root_dir) { + EFI_FILE_HANDLE linux_dir; + EFI_STATUS err; + ConfigEntry *entry; + + err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &linux_dir, L"\\EFI\\Linux", EFI_FILE_MODE_READ, 0ULL); + if (!EFI_ERROR(err)) { + for (;;) { + CHAR16 buf[256]; + UINTN bufsize; + EFI_FILE_INFO *f; + CHAR8 *sections[] = { + (UINT8 *)".osrel", + (UINT8 *)".cmdline", + NULL + }; + UINTN offs[ELEMENTSOF(sections)-1] = {}; + UINTN szs[ELEMENTSOF(sections)-1] = {}; + UINTN addrs[ELEMENTSOF(sections)-1] = {}; + CHAR8 *content = NULL; + UINTN len; + CHAR8 *line; + UINTN pos = 0; + CHAR8 *key, *value; + CHAR16 *os_name = NULL; + CHAR16 *os_id = NULL; + CHAR16 *os_version = NULL; + CHAR16 *os_build = NULL; + + bufsize = sizeof(buf); + err = uefi_call_wrapper(linux_dir->Read, 3, linux_dir, &bufsize, buf); + if (bufsize == 0 || EFI_ERROR(err)) + break; + + f = (EFI_FILE_INFO *) buf; + if (f->FileName[0] == '.') + continue; + if (f->Attribute & EFI_FILE_DIRECTORY) + continue; + len = StrLen(f->FileName); + if (len < 5) + continue; + if (StriCmp(f->FileName + len - 4, L".efi") != 0) + continue; + + /* look for .osrel and .cmdline sections in the .efi binary */ + err = pefile_locate_sections(linux_dir, f->FileName, sections, addrs, offs, szs); + if (EFI_ERROR(err)) + continue; + + len = file_read(linux_dir, f->FileName, offs[0], szs[0], &content); + if (len <= 0) + continue; + + /* read properties from the embedded os-release file */ + line = content; + while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) { + if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) { + FreePool(os_name); + os_name = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"ID", key) == 0) { + FreePool(os_id); + os_id = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) { + FreePool(os_version); + os_version = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"BUILD_ID", key) == 0) { + FreePool(os_build); + os_build = stra_to_str(value); + continue; + } + } + + if (os_name && os_id && (os_version || os_build)) { + CHAR16 *conf; + CHAR16 *path; + CHAR16 *cmdline; + + conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build); + path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName); + entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path); + + FreePool(content); + /* read the embedded cmdline file */ + len = file_read(linux_dir, f->FileName, offs[1], szs[1] - 1 , &content); + if (len > 0) { + cmdline = stra_to_str(content); + entry->options = cmdline; + cmdline = NULL; + } + FreePool(cmdline); + FreePool(conf); + FreePool(path); + } + + FreePool(os_name); + FreePool(os_id); + FreePool(os_version); + FreePool(os_build); + FreePool(content); + } + uefi_call_wrapper(linux_dir->Close, 1, linux_dir); + } +} + +static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) { + EFI_HANDLE image; + EFI_DEVICE_PATH *path; + CHAR16 *options; + EFI_STATUS err; + + path = FileDevicePath(entry->device, entry->loader); + if (!path) { + Print(L"Error getting device path."); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return EFI_INVALID_PARAMETER; + } + + err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image); + if (EFI_ERROR(err)) { + Print(L"Error loading %s: %r", entry->loader, err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out; + } + + if (config->options_edit) + options = config->options_edit; + else if (entry->options) + options = entry->options; + else + options = NULL; + if (options) { + EFI_LOADED_IMAGE *loaded_image; + + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, + parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(err)) { + Print(L"Error getting LoadedImageProtocol handle: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out_unload; + } + loaded_image->LoadOptions = options; + loaded_image->LoadOptionsSize = (StrLen(loaded_image->LoadOptions)+1) * sizeof(CHAR16); + +#ifdef SD_BOOT_LOG_TPM + /* Try to log any options to the TPM, escpecially to catch manually edited options */ + err = tpm_log_event(SD_TPM_PCR, + (EFI_PHYSICAL_ADDRESS) loaded_image->LoadOptions, + loaded_image->LoadOptionsSize, loaded_image->LoadOptions); + if (EFI_ERROR(err)) { + Print(L"Unable to add image options measurement: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } +#endif + } + + efivar_set_time_usec(L"LoaderTimeExecUSec", 0); + err = uefi_call_wrapper(BS->StartImage, 3, image, NULL, NULL); +out_unload: + uefi_call_wrapper(BS->UnloadImage, 1, image); +out: + FreePool(path); + return err; +} + +static EFI_STATUS reboot_into_firmware(VOID) { + CHAR8 *b; + UINTN size; + UINT64 osind; + EFI_STATUS err; + + osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + + err = efivar_get_raw(&global_guid, L"OsIndications", &b, &size); + if (!EFI_ERROR(err)) + osind |= (UINT64)*b; + FreePool(b); + + err = efivar_set_raw(&global_guid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE); + if (EFI_ERROR(err)) + return err; + + err = uefi_call_wrapper(RT->ResetSystem, 4, EfiResetCold, EFI_SUCCESS, 0, NULL); + Print(L"Error calling ResetSystem: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; +} + +static VOID config_free(Config *config) { + UINTN i; + + for (i = 0; i < config->entry_count; i++) + config_entry_free(config->entries[i]); + FreePool(config->entries); + FreePool(config->entry_default_pattern); + FreePool(config->options_edit); + FreePool(config->entry_oneshot); +} + +EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { + CHAR16 *s; + CHAR8 *b; + UINTN size; + EFI_LOADED_IMAGE *loaded_image; + EFI_FILE *root_dir; + CHAR16 *loaded_image_path; + EFI_STATUS err; + Config config; + UINT64 init_usec; + BOOLEAN menu = FALSE; + CHAR16 uuid[37]; + + InitializeLib(image, sys_table); + init_usec = time_usec(); + efivar_set_time_usec(L"LoaderTimeInitUSec", init_usec); + efivar_set(L"LoaderInfo", L"systemd-boot " VERSION, FALSE); + s = PoolPrint(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + efivar_set(L"LoaderFirmwareInfo", s, FALSE); + FreePool(s); + s = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); + efivar_set(L"LoaderFirmwareType", s, FALSE); + FreePool(s); + + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, + image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(err)) { + Print(L"Error getting a LoadedImageProtocol handle: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + /* export the device path this image is started from */ + if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) + efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); + + root_dir = LibOpenRoot(loaded_image->DeviceHandle); + if (!root_dir) { + Print(L"Unable to open root directory: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return EFI_LOAD_ERROR; + } + + + /* the filesystem path to this image, to prevent adding ourselves to the menu */ + loaded_image_path = DevicePathToStr(loaded_image->FilePath); + efivar_set(L"LoaderImageIdentifier", loaded_image_path, FALSE); + + ZeroMem(&config, sizeof(Config)); + config_load_defaults(&config, root_dir); + + /* scan /EFI/Linux/ directory */ + config_entry_add_linux(&config, loaded_image, root_dir); + + /* scan /loader/entries/\*.conf files */ + config_load_entries(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path); + + /* sort entries after version number */ + config_sort_entries(&config); + + /* if we find some well-known loaders, add them to the end of the list */ + config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, + L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi"); + config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, + L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" EFI_MACHINE_TYPE_NAME ".efi"); + config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, + L"auto-efi-default", '\0', L"EFI Default Loader", L"\\EFI\\Boot\\boot" EFI_MACHINE_TYPE_NAME ".efi"); + config_entry_add_osx(&config); + + if (efivar_get_raw(&global_guid, L"OsIndicationsSupported", &b, &size) == EFI_SUCCESS) { + UINT64 osind = (UINT64)*b; + + if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) + config_entry_add_call(&config, L"Reboot Into Firmware Interface", reboot_into_firmware); + FreePool(b); + } + + if (config.entry_count == 0) { + Print(L"No loader found. Configuration files in \\loader\\entries\\*.conf are needed."); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out; + } + + config_title_generate(&config); + + /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/ + config_default_entry_select(&config); + + /* if no configured entry to select from was found, enable the menu */ + if (config.idx_default == -1) { + config.idx_default = 0; + if (config.timeout_sec == 0) + config.timeout_sec = 10; + } + + /* select entry or show menu when key is pressed or timeout is set */ + if (config.timeout_sec == 0) { + UINT64 key; + + err = console_key_read(&key, FALSE); + if (!EFI_ERROR(err)) { + INT16 idx; + + /* find matching key in config entries */ + idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key)); + if (idx >= 0) + config.idx_default = idx; + else + menu = TRUE; + } + } else + menu = TRUE; + + for (;;) { + ConfigEntry *entry; + + entry = config.entries[config.idx_default]; + if (menu) { + efivar_set_time_usec(L"LoaderTimeMenuUSec", 0); + uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0x10000, 0, NULL); + if (!menu_run(&config, &entry, loaded_image_path)) + break; + + /* run special entry like "reboot" */ + if (entry->call) { + entry->call(); + continue; + } + } + + /* export the selected boot entry to the system */ + efivar_set(L"LoaderEntrySelected", entry->file, FALSE); + + uefi_call_wrapper(BS->SetWatchdogTimer, 4, 5 * 60, 0x10000, 0, NULL); + err = image_start(image, &config, entry); + if (EFI_ERROR(err)) { + graphics_mode(FALSE); + Print(L"\nFailed to execute %s (%s): %r\n", entry->title, entry->loader, err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + goto out; + } + + menu = TRUE; + config.timeout_sec = 0; + } + err = EFI_SUCCESS; +out: + FreePool(loaded_image_path); + config_free(&config); + uefi_call_wrapper(root_dir->Close, 1, root_dir); + uefi_call_wrapper(BS->CloseProtocol, 4, image, &LoadedImageProtocol, image, NULL); + return err; +} diff --git a/src/grp-boot/systemd-boot/console.c b/src/grp-boot/systemd-boot/console.c new file mode 100644 index 0000000000..2b797c9a5f --- /dev/null +++ b/src/grp-boot/systemd-boot/console.c @@ -0,0 +1,135 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + */ + +#include <efi.h> +#include <efilib.h> + +#include "console.h" +#include "util.h" + +#define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \ + { 0xdd9e7534, 0x7762, 0x4698, { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } } + +struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; + +typedef EFI_STATUS (EFIAPI *EFI_INPUT_RESET_EX)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + BOOLEAN ExtendedVerification +); + +typedef UINT8 EFI_KEY_TOGGLE_STATE; + +typedef struct { + UINT32 KeyShiftState; + EFI_KEY_TOGGLE_STATE KeyToggleState; +} EFI_KEY_STATE; + +typedef struct { + EFI_INPUT_KEY Key; + EFI_KEY_STATE KeyState; +} EFI_KEY_DATA; + +typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + EFI_KEY_DATA *KeyData +); + +typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + EFI_KEY_TOGGLE_STATE *KeyToggleState +); + +typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)( + EFI_KEY_DATA *KeyData +); + +typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + EFI_KEY_DATA KeyData, + EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction, + VOID **NotifyHandle +); + +typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)( + struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, + VOID *NotificationHandle +); + +typedef struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL { + EFI_INPUT_RESET_EX Reset; + EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx; + EFI_EVENT WaitForKeyEx; + EFI_SET_STATE SetState; + EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify; + EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify; +} EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; + +EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) { + EFI_GUID EfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; + static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *TextInputEx; + static BOOLEAN checked; + UINTN index; + EFI_INPUT_KEY k; + EFI_STATUS err; + + if (!checked) { + err = LibLocateProtocol(&EfiSimpleTextInputExProtocolGuid, (VOID **)&TextInputEx); + if (EFI_ERROR(err)) + TextInputEx = NULL; + + checked = TRUE; + } + + /* wait until key is pressed */ + if (wait) + uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index); + + if (TextInputEx) { + EFI_KEY_DATA keydata; + UINT64 keypress; + + err = uefi_call_wrapper(TextInputEx->ReadKeyStrokeEx, 2, TextInputEx, &keydata); + if (!EFI_ERROR(err)) { + UINT32 shift = 0; + + /* do not distinguish between left and right keys */ + if (keydata.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) { + if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED)) + shift |= EFI_CONTROL_PRESSED; + if (keydata.KeyState.KeyShiftState & (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED)) + shift |= EFI_ALT_PRESSED; + }; + + /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */ + keypress = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar); + if (keypress > 0) { + *key = keypress; + return 0; + } + } + } + + /* fallback for firmware which does not support SimpleTextInputExProtocol + * + * This is also called in case ReadKeyStrokeEx did not return a key, because + * some broken firmwares offer SimpleTextInputExProtocol, but never acually + * handle any key. */ + err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &k); + if (EFI_ERROR(err)) + return err; + + *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar); + return 0; +} diff --git a/src/grp-boot/systemd-boot/console.h b/src/grp-boot/systemd-boot/console.h new file mode 100644 index 0000000000..3fe0ce5ec4 --- /dev/null +++ b/src/grp-boot/systemd-boot/console.h @@ -0,0 +1,32 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + */ + +#ifndef __SDBOOT_CONSOLE_H +#define __SDBOOT_CONSOLE_H + +#define EFI_SHIFT_STATE_VALID 0x80000000 +#define EFI_RIGHT_CONTROL_PRESSED 0x00000004 +#define EFI_LEFT_CONTROL_PRESSED 0x00000008 +#define EFI_RIGHT_ALT_PRESSED 0x00000010 +#define EFI_LEFT_ALT_PRESSED 0x00000020 + +#define EFI_CONTROL_PRESSED (EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED) +#define EFI_ALT_PRESSED (EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED) +#define KEYPRESS(keys, scan, uni) ((((UINT64)keys) << 32) | ((scan) << 16) | (uni)) +#define KEYCHAR(k) ((k) & 0xffff) +#define CHAR_CTRL(c) ((c) - 'a' + 1) + +EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait); +#endif diff --git a/src/grp-boot/systemd-boot/disk.c b/src/grp-boot/systemd-boot/disk.c new file mode 100644 index 0000000000..3e3b5b224a --- /dev/null +++ b/src/grp-boot/systemd-boot/disk.c @@ -0,0 +1,49 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers <kay@vrfy.org> + */ + +#include <efi.h> +#include <efilib.h> + +#include "util.h" + +EFI_STATUS disk_get_part_uuid(EFI_HANDLE *handle, CHAR16 uuid[37]) { + EFI_DEVICE_PATH *device_path; + EFI_STATUS r = EFI_NOT_FOUND; + + /* export the device path this image is started from */ + device_path = DevicePathFromHandle(handle); + if (device_path) { + EFI_DEVICE_PATH *path, *paths; + + paths = UnpackDevicePath(device_path); + for (path = paths; !IsDevicePathEnd(path); path = NextDevicePathNode(path)) { + HARDDRIVE_DEVICE_PATH *drive; + + if (DevicePathType(path) != MEDIA_DEVICE_PATH) + continue; + if (DevicePathSubType(path) != MEDIA_HARDDRIVE_DP) + continue; + drive = (HARDDRIVE_DEVICE_PATH *)path; + if (drive->SignatureType != SIGNATURE_TYPE_GUID) + continue; + + GuidToString(uuid, (EFI_GUID *)&drive->Signature); + r = EFI_SUCCESS; + break; + } + FreePool(paths); + } + + return r; +} diff --git a/src/grp-boot/systemd-boot/disk.h b/src/grp-boot/systemd-boot/disk.h new file mode 100644 index 0000000000..af91a9c674 --- /dev/null +++ b/src/grp-boot/systemd-boot/disk.h @@ -0,0 +1,19 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers <kay@vrfy.org> + */ + +#ifndef __SDBOOT_DISK_H +#define __SDBOOT_DISK_H + +EFI_STATUS disk_get_part_uuid(EFI_HANDLE *handle, CHAR16 uuid[37]); +#endif diff --git a/src/grp-boot/systemd-boot/graphics.c b/src/grp-boot/systemd-boot/graphics.c new file mode 100644 index 0000000000..4854baf874 --- /dev/null +++ b/src/grp-boot/systemd-boot/graphics.c @@ -0,0 +1,88 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + * Copyright (C) 2013 Intel Corporation + * Authored by Joonas Lahtinen <joonas.lahtinen@linux.intel.com> + */ + +#include <efi.h> +#include <efilib.h> + +#include "graphics.h" +#include "util.h" + +EFI_STATUS graphics_mode(BOOLEAN on) { + #define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \ + { 0xf42f7782, 0x12e, 0x4c12, { 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21 } }; + + struct _EFI_CONSOLE_CONTROL_PROTOCOL; + + typedef enum { + EfiConsoleControlScreenText, + EfiConsoleControlScreenGraphics, + EfiConsoleControlScreenMaxValue, + } EFI_CONSOLE_CONTROL_SCREEN_MODE; + + typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE)( + struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, + EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode, + BOOLEAN *UgaExists, + BOOLEAN *StdInLocked + ); + + typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE)( + struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, + EFI_CONSOLE_CONTROL_SCREEN_MODE Mode + ); + + typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN)( + struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, + CHAR16 *Password + ); + + typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL { + EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE GetMode; + EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode; + EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN LockStdIn; + } EFI_CONSOLE_CONTROL_PROTOCOL; + + EFI_GUID ConsoleControlProtocolGuid = EFI_CONSOLE_CONTROL_PROTOCOL_GUID; + EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL; + EFI_CONSOLE_CONTROL_SCREEN_MODE new; + EFI_CONSOLE_CONTROL_SCREEN_MODE current; + BOOLEAN uga_exists; + BOOLEAN stdin_locked; + EFI_STATUS err; + + err = LibLocateProtocol(&ConsoleControlProtocolGuid, (VOID **)&ConsoleControl); + if (EFI_ERROR(err)) + /* console control protocol is nonstandard and might not exist. */ + return err == EFI_NOT_FOUND ? EFI_SUCCESS : err; + + /* check current mode */ + err = uefi_call_wrapper(ConsoleControl->GetMode, 4, ConsoleControl, ¤t, &uga_exists, &stdin_locked); + if (EFI_ERROR(err)) + return err; + + /* do not touch the mode */ + new = on ? EfiConsoleControlScreenGraphics : EfiConsoleControlScreenText; + if (new == current) + return EFI_SUCCESS; + + err = uefi_call_wrapper(ConsoleControl->SetMode, 2, ConsoleControl, new); + + /* some firmware enables the cursor when switching modes */ + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); + + return err; +} diff --git a/src/grp-boot/systemd-boot/graphics.h b/src/grp-boot/systemd-boot/graphics.h new file mode 100644 index 0000000000..cf48e647e7 --- /dev/null +++ b/src/grp-boot/systemd-boot/graphics.h @@ -0,0 +1,22 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + * Copyright (C) 2013 Intel Corporation + * Authored by Joonas Lahtinen <joonas.lahtinen@linux.intel.com> + */ + +#ifndef __SDBOOT_GRAPHICS_H +#define __SDBOOT_GRAPHICS_H + +EFI_STATUS graphics_mode(BOOLEAN on); +#endif diff --git a/src/grp-boot/systemd-boot/linux.c b/src/grp-boot/systemd-boot/linux.c new file mode 100644 index 0000000000..0dc99a6c53 --- /dev/null +++ b/src/grp-boot/systemd-boot/linux.c @@ -0,0 +1,128 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers <kay@vrfy.org> + */ + +#include <efi.h> +#include <efilib.h> + +#include "linux.h" +#include "util.h" + +#define SETUP_MAGIC 0x53726448 /* "HdrS" */ +struct SetupHeader { + UINT8 boot_sector[0x01f1]; + UINT8 setup_secs; + UINT16 root_flags; + UINT32 sys_size; + UINT16 ram_size; + UINT16 video_mode; + UINT16 root_dev; + UINT16 signature; + UINT16 jump; + UINT32 header; + UINT16 version; + UINT16 su_switch; + UINT16 setup_seg; + UINT16 start_sys; + UINT16 kernel_ver; + UINT8 loader_id; + UINT8 load_flags; + UINT16 movesize; + UINT32 code32_start; + UINT32 ramdisk_start; + UINT32 ramdisk_len; + UINT32 bootsect_kludge; + UINT16 heap_end; + UINT8 ext_loader_ver; + UINT8 ext_loader_type; + UINT32 cmd_line_ptr; + UINT32 ramdisk_max; + UINT32 kernel_alignment; + UINT8 relocatable_kernel; + UINT8 min_alignment; + UINT16 xloadflags; + UINT32 cmdline_size; + UINT32 hardware_subarch; + UINT64 hardware_subarch_data; + UINT32 payload_offset; + UINT32 payload_length; + UINT64 setup_data; + UINT64 pref_address; + UINT32 init_size; + UINT32 handover_offset; +} __attribute__((packed)); + +#ifdef __x86_64__ +typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup); +static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { + handover_f handover; + + asm volatile ("cli"); + handover = (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset); + handover(image, ST, setup); +} +#else +typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup) __attribute__((regparm(0))); +static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { + handover_f handover; + + handover = (handover_f)((UINTN)setup->code32_start + setup->handover_offset); + handover(image, ST, setup); +} +#endif + +EFI_STATUS linux_exec(EFI_HANDLE *image, + CHAR8 *cmdline, UINTN cmdline_len, + UINTN linux_addr, + UINTN initrd_addr, UINTN initrd_size) { + struct SetupHeader *image_setup; + struct SetupHeader *boot_setup; + EFI_PHYSICAL_ADDRESS addr; + EFI_STATUS err; + + image_setup = (struct SetupHeader *)(linux_addr); + if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC) + return EFI_LOAD_ERROR; + + if (image_setup->version < 0x20b || !image_setup->relocatable_kernel) + return EFI_LOAD_ERROR; + + addr = 0x3fffffff; + err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, + EFI_SIZE_TO_PAGES(0x4000), &addr); + if (EFI_ERROR(err)) + return err; + boot_setup = (struct SetupHeader *)(UINTN)addr; + ZeroMem(boot_setup, 0x4000); + CopyMem(boot_setup, image_setup, sizeof(struct SetupHeader)); + boot_setup->loader_id = 0xff; + + boot_setup->code32_start = (UINT32)linux_addr + (image_setup->setup_secs+1) * 512; + + if (cmdline) { + addr = 0xA0000; + err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, + EFI_SIZE_TO_PAGES(cmdline_len + 1), &addr); + if (EFI_ERROR(err)) + return err; + CopyMem((VOID *)(UINTN)addr, cmdline, cmdline_len); + ((CHAR8 *)addr)[cmdline_len] = 0; + boot_setup->cmd_line_ptr = (UINT32)addr; + } + + boot_setup->ramdisk_start = (UINT32)initrd_addr; + boot_setup->ramdisk_len = (UINT32)initrd_size; + + linux_efi_handover(image, boot_setup); + return EFI_LOAD_ERROR; +} diff --git a/src/grp-boot/systemd-boot/linux.h b/src/grp-boot/systemd-boot/linux.h new file mode 100644 index 0000000000..d9e6ed7955 --- /dev/null +++ b/src/grp-boot/systemd-boot/linux.h @@ -0,0 +1,22 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers <kay@vrfy.org> + */ + +#ifndef __SDBOOT_kernel_H +#define __SDBOOT_kernel_H + +EFI_STATUS linux_exec(EFI_HANDLE *image, + CHAR8 *cmdline, UINTN cmdline_size, + UINTN linux_addr, + UINTN initrd_addr, UINTN initrd_size); +#endif diff --git a/src/grp-boot/systemd-boot/measure.c b/src/grp-boot/systemd-boot/measure.c new file mode 100644 index 0000000000..05adf41778 --- /dev/null +++ b/src/grp-boot/systemd-boot/measure.c @@ -0,0 +1,312 @@ +/* + * This program 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. + * + * This program 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. + * + */ + +#ifdef SD_BOOT_LOG_TPM + +#include <efi.h> +#include <efilib.h> + +#include "measure.h" + +#define EFI_TCG_PROTOCOL_GUID { 0xf541796d, 0xa62e, 0x4954, {0xa7, 0x75, 0x95, 0x84, 0xf6, 0x1b, 0x9c, 0xdd} } + +typedef struct _TCG_VERSION { + UINT8 Major; + UINT8 Minor; + UINT8 RevMajor; + UINT8 RevMinor; +} TCG_VERSION; + +typedef struct _TCG_BOOT_SERVICE_CAPABILITY { + UINT8 Size; + struct _TCG_VERSION StructureVersion; + struct _TCG_VERSION ProtocolSpecVersion; + UINT8 HashAlgorithmBitmap; + BOOLEAN TPMPresentFlag; + BOOLEAN TPMDeactivatedFlag; +} TCG_BOOT_SERVICE_CAPABILITY; + +typedef UINT32 TCG_ALGORITHM_ID; +#define TCG_ALG_SHA 0x00000004 // The SHA1 algorithm + +#define SHA1_DIGEST_SIZE 20 + +typedef struct _TCG_DIGEST { + UINT8 Digest[SHA1_DIGEST_SIZE]; +} TCG_DIGEST; + +#define EV_IPL 13 + +typedef struct _TCG_PCR_EVENT { + UINT32 PCRIndex; + UINT32 EventType; + struct _TCG_DIGEST digest; + UINT32 EventSize; + UINT8 Event[1]; +} TCG_PCR_EVENT; + +INTERFACE_DECL(_EFI_TCG); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_STATUS_CHECK) (IN struct _EFI_TCG * This, + OUT struct _TCG_BOOT_SERVICE_CAPABILITY * ProtocolCapability, + OUT UINT32 * TCGFeatureFlags, + OUT EFI_PHYSICAL_ADDRESS * EventLogLocation, + OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_ALL) (IN struct _EFI_TCG * This, + IN UINT8 * HashData, + IN UINT64 HashDataLen, + IN TCG_ALGORITHM_ID AlgorithmId, + IN OUT UINT64 * HashedDataLen, IN OUT UINT8 ** HashedDataResult); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_LOG_EVENT) (IN struct _EFI_TCG * This, + IN struct _TCG_PCR_EVENT * TCGLogData, + IN OUT UINT32 * EventNumber, IN UINT32 Flags); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_PASS_THROUGH_TO_TPM) (IN struct _EFI_TCG * This, + IN UINT32 TpmInputParameterBlockSize, + IN UINT8 * TpmInputParameterBlock, + IN UINT32 TpmOutputParameterBlockSize, + IN UINT8 * TpmOutputParameterBlock); + +typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_LOG_EXTEND_EVENT) (IN struct _EFI_TCG * This, + IN EFI_PHYSICAL_ADDRESS HashData, + IN UINT64 HashDataLen, + IN TCG_ALGORITHM_ID AlgorithmId, + IN struct _TCG_PCR_EVENT * TCGLogData, + IN OUT UINT32 * EventNumber, + OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry); + +typedef struct _EFI_TCG { + EFI_TCG_STATUS_CHECK StatusCheck; + EFI_TCG_HASH_ALL HashAll; + EFI_TCG_LOG_EVENT LogEvent; + EFI_TCG_PASS_THROUGH_TO_TPM PassThroughToTPM; + EFI_TCG_HASH_LOG_EXTEND_EVENT HashLogExtendEvent; +} EFI_TCG; + +#define EFI_TCG2_PROTOCOL_GUID {0x607f766c, 0x7455, 0x42be, { 0x93, 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f }} + +typedef struct tdEFI_TCG2_PROTOCOL EFI_TCG2_PROTOCOL; + +typedef struct tdEFI_TCG2_VERSION { + UINT8 Major; + UINT8 Minor; +} EFI_TCG2_VERSION; + +typedef UINT32 EFI_TCG2_EVENT_LOG_BITMAP; +typedef UINT32 EFI_TCG2_EVENT_LOG_FORMAT; +typedef UINT32 EFI_TCG2_EVENT_ALGORITHM_BITMAP; + +#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2 0x00000001 +#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_2 0x00000002 + +typedef struct tdEFI_TCG2_BOOT_SERVICE_CAPABILITY { + UINT8 Size; + EFI_TCG2_VERSION StructureVersion; + EFI_TCG2_VERSION ProtocolVersion; + EFI_TCG2_EVENT_ALGORITHM_BITMAP HashAlgorithmBitmap; + EFI_TCG2_EVENT_LOG_BITMAP SupportedEventLogs; + BOOLEAN TPMPresentFlag; + UINT16 MaxCommandSize; + UINT16 MaxResponseSize; + UINT32 ManufacturerID; + UINT32 NumberOfPCRBanks; + EFI_TCG2_EVENT_ALGORITHM_BITMAP ActivePcrBanks; +} EFI_TCG2_BOOT_SERVICE_CAPABILITY; + +#define EFI_TCG2_EVENT_HEADER_VERSION 1 + +typedef struct { + UINT32 HeaderSize; + UINT16 HeaderVersion; + UINT32 PCRIndex; + UINT32 EventType; +} EFI_TCG2_EVENT_HEADER; + +typedef struct tdEFI_TCG2_EVENT { + UINT32 Size; + EFI_TCG2_EVENT_HEADER Header; + UINT8 Event[1]; +} EFI_TCG2_EVENT; + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_CAPABILITY) (IN EFI_TCG2_PROTOCOL * This, + IN OUT EFI_TCG2_BOOT_SERVICE_CAPABILITY * ProtocolCapability); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_EVENT_LOG) (IN EFI_TCG2_PROTOCOL * This, + IN EFI_TCG2_EVENT_LOG_FORMAT EventLogFormat, + OUT EFI_PHYSICAL_ADDRESS * EventLogLocation, + OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry, + OUT BOOLEAN * EventLogTruncated); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_HASH_LOG_EXTEND_EVENT) (IN EFI_TCG2_PROTOCOL * This, + IN UINT64 Flags, + IN EFI_PHYSICAL_ADDRESS DataToHash, + IN UINT64 DataToHashLen, IN EFI_TCG2_EVENT * EfiTcgEvent); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_SUBMIT_COMMAND) (IN EFI_TCG2_PROTOCOL * This, + IN UINT32 InputParameterBlockSize, + IN UINT8 * InputParameterBlock, + IN UINT32 OutputParameterBlockSize, IN UINT8 * OutputParameterBlock); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, OUT UINT32 * ActivePcrBanks); +typedef EFI_STATUS(EFIAPI * EFI_TCG2_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, IN UINT32 ActivePcrBanks); + +typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, + OUT UINT32 * OperationPresent, OUT UINT32 * Response); + +typedef struct tdEFI_TCG2_PROTOCOL { + EFI_TCG2_GET_CAPABILITY GetCapability; + EFI_TCG2_GET_EVENT_LOG GetEventLog; + EFI_TCG2_HASH_LOG_EXTEND_EVENT HashLogExtendEvent; + EFI_TCG2_SUBMIT_COMMAND SubmitCommand; + EFI_TCG2_GET_ACTIVE_PCR_BANKS GetActivePcrBanks; + EFI_TCG2_SET_ACTIVE_PCR_BANKS SetActivePcrBanks; + EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS GetResultOfSetActivePcrBanks; +} EFI_TCG2; + + +static EFI_STATUS tpm1_measure_to_pcr_and_event_log(const EFI_TCG *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, + UINTN buffer_size, const CHAR16 *description) { + EFI_STATUS status; + TCG_PCR_EVENT *tcg_event; + UINT32 event_number; + EFI_PHYSICAL_ADDRESS event_log_last; + UINTN desc_len; + + desc_len = (StrLen(description) + 1) * sizeof(CHAR16); + + tcg_event = AllocateZeroPool(desc_len + sizeof(TCG_PCR_EVENT)); + + if (tcg_event == NULL) + return EFI_OUT_OF_RESOURCES; + + tcg_event->EventSize = desc_len; + CopyMem((VOID *) & tcg_event->Event[0], (VOID *) description, desc_len); + + tcg_event->PCRIndex = pcrindex; + tcg_event->EventType = EV_IPL; + + event_number = 1; + status = uefi_call_wrapper(tcg->HashLogExtendEvent, 7, + tcg, buffer, buffer_size, TCG_ALG_SHA, tcg_event, &event_number, &event_log_last); + + if (EFI_ERROR(status)) + return status; + + uefi_call_wrapper(BS->FreePool, 1, tcg_event); + + return EFI_SUCCESS; +} + + +static EFI_STATUS tpm2_measure_to_pcr_and_event_log(const EFI_TCG2 *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, + UINT64 buffer_size, const CHAR16 *description) { + EFI_STATUS status; + EFI_TCG2_EVENT *tcg_event; + UINTN desc_len; + + desc_len = StrLen(description) * sizeof(CHAR16); + + tcg_event = AllocateZeroPool(sizeof(*tcg_event) - sizeof(tcg_event->Event) + desc_len + 1); + + if (tcg_event == NULL) + return EFI_OUT_OF_RESOURCES; + + tcg_event->Size = sizeof(EFI_TCG2_EVENT) - sizeof(tcg_event->Event) + desc_len + 1; + tcg_event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER); + tcg_event->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION; + tcg_event->Header.PCRIndex = pcrindex; + tcg_event->Header.EventType = EV_IPL; + + CopyMem((VOID *) tcg_event->Event, (VOID *) description, desc_len); + + status = uefi_call_wrapper(tcg->HashLogExtendEvent, 5, tcg, 0, buffer, buffer_size, tcg_event); + + uefi_call_wrapper(BS->FreePool, 1, tcg_event); + + if (EFI_ERROR(status)) + return status; + + return EFI_SUCCESS; +} + +static EFI_TCG * tcg1_interface_check(void) { + EFI_GUID tpm_guid = EFI_TCG_PROTOCOL_GUID; + EFI_STATUS status; + EFI_TCG *tcg; + TCG_BOOT_SERVICE_CAPABILITY capability; + UINT32 features; + EFI_PHYSICAL_ADDRESS event_log_location; + EFI_PHYSICAL_ADDRESS event_log_last_entry; + + status = LibLocateProtocol(&tpm_guid, (void **) &tcg); + + if (EFI_ERROR(status)) + return NULL; + + capability.Size = (UINT8) sizeof(capability); + status = uefi_call_wrapper(tcg->StatusCheck, 5, tcg, &capability, &features, &event_log_location, &event_log_last_entry); + + if (EFI_ERROR(status)) + return NULL; + + if (capability.TPMDeactivatedFlag) + return NULL; + + if (!capability.TPMPresentFlag) + return NULL; + + return tcg; +} + +static EFI_TCG2 * tcg2_interface_check(void) { + EFI_GUID tpm2_guid = EFI_TCG2_PROTOCOL_GUID; + EFI_STATUS status; + EFI_TCG2 *tcg; + EFI_TCG2_BOOT_SERVICE_CAPABILITY capability; + + status = LibLocateProtocol(&tpm2_guid, (void **) &tcg); + + if (EFI_ERROR(status)) + return NULL; + + capability.Size = (UINT8) sizeof(capability); + status = uefi_call_wrapper(tcg->GetCapability, 2, tcg, &capability); + + if (EFI_ERROR(status)) + return NULL; + + if (!capability.TPMPresentFlag) + return NULL; + + return tcg; +} + +EFI_STATUS tpm_log_event(UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const CHAR16 *description) { + EFI_TCG *tpm1; + EFI_TCG2 *tpm2; + + tpm2 = tcg2_interface_check(); + if (tpm2) + return tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + + tpm1 = tcg1_interface_check(); + if (tpm1) + return tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); + + /* No active TPM found, so don't return an error */ + return EFI_SUCCESS; +} + +#endif diff --git a/src/grp-boot/systemd-boot/measure.h b/src/grp-boot/systemd-boot/measure.h new file mode 100644 index 0000000000..a2cfe817d0 --- /dev/null +++ b/src/grp-boot/systemd-boot/measure.h @@ -0,0 +1,21 @@ +/* + * This program 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. + * + * This program 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. + * + */ +#ifndef __SDBOOT_MEASURE_H +#define __SDBOOT_MEASURE_H + +#ifndef SD_TPM_PCR +#define SD_TPM_PCR 8 +#endif + +EFI_STATUS tpm_log_event(UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const CHAR16 *description); +#endif diff --git a/src/grp-boot/systemd-boot/pefile.c b/src/grp-boot/systemd-boot/pefile.c new file mode 100644 index 0000000000..77fff77b69 --- /dev/null +++ b/src/grp-boot/systemd-boot/pefile.c @@ -0,0 +1,170 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers <kay@vrfy.org> + */ + +#include <efi.h> +#include <efilib.h> + +#include "pefile.h" +#include "util.h" + +struct DosFileHeader { + UINT8 Magic[2]; + UINT16 LastSize; + UINT16 nBlocks; + UINT16 nReloc; + UINT16 HdrSize; + UINT16 MinAlloc; + UINT16 MaxAlloc; + UINT16 ss; + UINT16 sp; + UINT16 Checksum; + UINT16 ip; + UINT16 cs; + UINT16 RelocPos; + UINT16 nOverlay; + UINT16 reserved[4]; + UINT16 OEMId; + UINT16 OEMInfo; + UINT16 reserved2[10]; + UINT32 ExeHeader; +} __attribute__((packed)); + +#define PE_HEADER_MACHINE_I386 0x014c +#define PE_HEADER_MACHINE_X64 0x8664 +struct PeFileHeader { + UINT16 Machine; + UINT16 NumberOfSections; + UINT32 TimeDateStamp; + UINT32 PointerToSymbolTable; + UINT32 NumberOfSymbols; + UINT16 SizeOfOptionalHeader; + UINT16 Characteristics; +} __attribute__((packed)); + +struct PeSectionHeader { + UINT8 Name[8]; + UINT32 VirtualSize; + UINT32 VirtualAddress; + UINT32 SizeOfRawData; + UINT32 PointerToRawData; + UINT32 PointerToRelocations; + UINT32 PointerToLinenumbers; + UINT16 NumberOfRelocations; + UINT16 NumberOfLinenumbers; + UINT32 Characteristics; +} __attribute__((packed)); + + +EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes) { + EFI_FILE_HANDLE handle; + struct DosFileHeader dos; + uint8_t magic[4]; + struct PeFileHeader pe; + UINTN len; + UINTN i; + EFI_STATUS err; + + err = uefi_call_wrapper(dir->Open, 5, dir, &handle, path, EFI_FILE_MODE_READ, 0ULL); + if (EFI_ERROR(err)) + return err; + + /* MS-DOS stub */ + len = sizeof(dos); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &dos); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(dos)) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (CompareMem(dos.Magic, "MZ", 2) != 0) { + err = EFI_LOAD_ERROR; + goto out; + } + + err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader); + if (EFI_ERROR(err)) + goto out; + + /* PE header */ + len = sizeof(magic); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &magic); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(magic)) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (CompareMem(magic, "PE\0\0", 2) != 0) { + err = EFI_LOAD_ERROR; + goto out; + } + + len = sizeof(pe); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &pe); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(pe)) { + err = EFI_LOAD_ERROR; + goto out; + } + + /* PE32+ Subsystem type */ + if (pe.Machine != PE_HEADER_MACHINE_X64 && + pe.Machine != PE_HEADER_MACHINE_I386) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (pe.NumberOfSections > 96) { + err = EFI_LOAD_ERROR; + goto out; + } + + /* the sections start directly after the headers */ + err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader + sizeof(magic) + sizeof(pe) + pe.SizeOfOptionalHeader); + if (EFI_ERROR(err)) + goto out; + + for (i = 0; i < pe.NumberOfSections; i++) { + struct PeSectionHeader sect; + UINTN j; + + len = sizeof(sect); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, §); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(sect)) { + err = EFI_LOAD_ERROR; + goto out; + } + for (j = 0; sections[j]; j++) { + if (CompareMem(sect.Name, sections[j], strlena(sections[j])) != 0) + continue; + + if (addrs) + addrs[j] = (UINTN)sect.VirtualAddress; + if (offsets) + offsets[j] = (UINTN)sect.PointerToRawData; + if (sizes) + sizes[j] = (UINTN)sect.VirtualSize; + } + } + +out: + uefi_call_wrapper(handle->Close, 1, handle); + return err; +} diff --git a/src/grp-boot/systemd-boot/pefile.h b/src/grp-boot/systemd-boot/pefile.h new file mode 100644 index 0000000000..2e445ede17 --- /dev/null +++ b/src/grp-boot/systemd-boot/pefile.h @@ -0,0 +1,20 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers <kay@vrfy.org> + */ + +#ifndef __SDBOOT_PEFILE_H +#define __SDBOOT_PEFILE_H + +EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, + CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes); +#endif diff --git a/src/grp-boot/systemd-boot/splash.c b/src/grp-boot/systemd-boot/splash.c new file mode 100644 index 0000000000..c0ef7f64fe --- /dev/null +++ b/src/grp-boot/systemd-boot/splash.c @@ -0,0 +1,321 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + */ + +#include <efi.h> +#include <efilib.h> + +#include "graphics.h" +#include "splash.h" +#include "util.h" + +struct bmp_file { + CHAR8 signature[2]; + UINT32 size; + UINT16 reserved[2]; + UINT32 offset; +} __attribute__((packed)); + +/* we require at least BITMAPINFOHEADER, later versions are + accepted, but their features ignored */ +struct bmp_dib { + UINT32 size; + UINT32 x; + UINT32 y; + UINT16 planes; + UINT16 depth; + UINT32 compression; + UINT32 image_size; + INT32 x_pixel_meter; + INT32 y_pixel_meter; + UINT32 colors_used; + UINT32 colors_important; +} __attribute__((packed)); + +struct bmp_map { + UINT8 blue; + UINT8 green; + UINT8 red; + UINT8 reserved; +} __attribute__((packed)); + +EFI_STATUS bmp_parse_header(UINT8 *bmp, UINTN size, struct bmp_dib **ret_dib, + struct bmp_map **ret_map, UINT8 **pixmap) { + struct bmp_file *file; + struct bmp_dib *dib; + struct bmp_map *map; + UINTN row_size; + + if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib)) + return EFI_INVALID_PARAMETER; + + /* check file header */ + file = (struct bmp_file *)bmp; + if (file->signature[0] != 'B' || file->signature[1] != 'M') + return EFI_INVALID_PARAMETER; + if (file->size != size) + return EFI_INVALID_PARAMETER; + if (file->size < file->offset) + return EFI_INVALID_PARAMETER; + + /* check device-independent bitmap */ + dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file)); + if (dib->size < sizeof(struct bmp_dib)) + return EFI_UNSUPPORTED; + + switch (dib->depth) { + case 1: + case 4: + case 8: + case 24: + if (dib->compression != 0) + return EFI_UNSUPPORTED; + + break; + + case 16: + case 32: + if (dib->compression != 0 && dib->compression != 3) + return EFI_UNSUPPORTED; + + break; + + default: + return EFI_UNSUPPORTED; + } + + row_size = ((UINTN) dib->depth * dib->x + 31) / 32 * 4; + if (file->size - file->offset < dib->y * row_size) + return EFI_INVALID_PARAMETER; + if (row_size * dib->y > 64 * 1024 * 1024) + return EFI_INVALID_PARAMETER; + + /* check color table */ + map = (struct bmp_map *)(bmp + sizeof(struct bmp_file) + dib->size); + if (file->offset < sizeof(struct bmp_file) + dib->size) + return EFI_INVALID_PARAMETER; + + if (file->offset > sizeof(struct bmp_file) + dib->size) { + UINT32 map_count; + UINTN map_size; + + if (dib->colors_used) + map_count = dib->colors_used; + else { + switch (dib->depth) { + case 1: + case 4: + case 8: + map_count = 1 << dib->depth; + break; + + default: + map_count = 0; + break; + } + } + + map_size = file->offset - (sizeof(struct bmp_file) + dib->size); + if (map_size != sizeof(struct bmp_map) * map_count) + return EFI_INVALID_PARAMETER; + } + + *ret_map = map; + *ret_dib = dib; + *pixmap = bmp + file->offset; + + return EFI_SUCCESS; +} + +static VOID pixel_blend(UINT32 *dst, const UINT32 source) { + UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g; + + alpha = (source & 0xff); + + /* convert src from RGBA to XRGB */ + src = source >> 8; + + /* decompose into RB and G components */ + src_rb = (src & 0xff00ff); + src_g = (src & 0x00ff00); + + dst_rb = (*dst & 0xff00ff); + dst_g = (*dst & 0x00ff00); + + /* blend */ + rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff; + g = ((((src_g - dst_g) * alpha + 0x008000) >> 8) + dst_g) & 0x00ff00; + + *dst = (rb | g); +} + +EFI_STATUS bmp_to_blt(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf, + struct bmp_dib *dib, struct bmp_map *map, + UINT8 *pixmap) { + UINT8 *in; + UINTN y; + + /* transform and copy pixels */ + in = pixmap; + for (y = 0; y < dib->y; y++) { + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out; + UINTN row_size; + UINTN x; + + out = &buf[(dib->y - y - 1) * dib->x]; + for (x = 0; x < dib->x; x++, in++, out++) { + switch (dib->depth) { + case 1: { + UINTN i; + + for (i = 0; i < 8 && x < dib->x; i++) { + out->Red = map[((*in) >> (7 - i)) & 1].red; + out->Green = map[((*in) >> (7 - i)) & 1].green; + out->Blue = map[((*in) >> (7 - i)) & 1].blue; + out++; + x++; + } + out--; + x--; + break; + } + + case 4: { + UINTN i; + + i = (*in) >> 4; + out->Red = map[i].red; + out->Green = map[i].green; + out->Blue = map[i].blue; + if (x < (dib->x - 1)) { + out++; + x++; + i = (*in) & 0x0f; + out->Red = map[i].red; + out->Green = map[i].green; + out->Blue = map[i].blue; + } + break; + } + + case 8: + out->Red = map[*in].red; + out->Green = map[*in].green; + out->Blue = map[*in].blue; + break; + + case 16: { + UINT16 i = *(UINT16 *) in; + + out->Red = (i & 0x7c00) >> 7; + out->Green = (i & 0x3e0) >> 2; + out->Blue = (i & 0x1f) << 3; + in += 1; + break; + } + + case 24: + out->Red = in[2]; + out->Green = in[1]; + out->Blue = in[0]; + in += 2; + break; + + case 32: { + UINT32 i = *(UINT32 *) in; + + pixel_blend((UINT32 *)out, i); + + in += 3; + break; + } + } + } + + /* add row padding; new lines always start at 32 bit boundary */ + row_size = in - pixmap; + in += ((row_size + 3) & ~3) - row_size; + } + + return EFI_SUCCESS; +} + +EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) { + EFI_GRAPHICS_OUTPUT_BLT_PIXEL pixel = {}; + EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL; + struct bmp_dib *dib; + struct bmp_map *map; + UINT8 *pixmap; + UINT64 blt_size; + VOID *blt = NULL; + UINTN x_pos = 0; + UINTN y_pos = 0; + EFI_STATUS err; + + if (!background) { + if (StriCmp(L"Apple", ST->FirmwareVendor) == 0) { + pixel.Red = 0xc0; + pixel.Green = 0xc0; + pixel.Blue = 0xc0; + } + background = &pixel; + } + + err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); + if (EFI_ERROR(err)) + return err; + + err = bmp_parse_header(content, len, &dib, &map, &pixmap); + if (EFI_ERROR(err)) + goto err; + + if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution) + x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2; + if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution) + y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2; + + uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, + (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background, + EfiBltVideoFill, 0, 0, 0, 0, + GraphicsOutput->Mode->Info->HorizontalResolution, + GraphicsOutput->Mode->Info->VerticalResolution, 0); + + /* EFI buffer */ + blt_size = dib->x * dib->y * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); + blt = AllocatePool(blt_size); + if (!blt) + return EFI_OUT_OF_RESOURCES; + + err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, + blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0, + dib->x, dib->y, 0); + if (EFI_ERROR(err)) + goto err; + + err = bmp_to_blt(blt, dib, map, pixmap); + if (EFI_ERROR(err)) + goto err; + + err = graphics_mode(TRUE); + if (EFI_ERROR(err)) + goto err; + + err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, + blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos, + dib->x, dib->y, 0); +err: + FreePool(blt); + return err; +} diff --git a/src/grp-boot/systemd-boot/splash.h b/src/grp-boot/systemd-boot/splash.h new file mode 100644 index 0000000000..09b543fb47 --- /dev/null +++ b/src/grp-boot/systemd-boot/splash.h @@ -0,0 +1,20 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + */ + +#ifndef __SDBOOT_SPLASH_H +#define __SDBOOT_SPLASH_H + +EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background); +#endif diff --git a/src/grp-boot/systemd-boot/stub.c b/src/grp-boot/systemd-boot/stub.c new file mode 100644 index 0000000000..9fae0c1372 --- /dev/null +++ b/src/grp-boot/systemd-boot/stub.c @@ -0,0 +1,130 @@ +/* This program 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. + * + * This program 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. + * + * Copyright (C) 2015 Kay Sievers <kay@vrfy.org> + */ + +#include <efi.h> +#include <efilib.h> + +#include "disk.h" +#include "graphics.h" +#include "linux.h" +#include "measure.h" +#include "pefile.h" +#include "splash.h" +#include "util.h" + +/* magic string to find in the binary image */ +static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-stub " VERSION " ####"; + +static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; + +EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { + EFI_LOADED_IMAGE *loaded_image; + EFI_FILE *root_dir; + CHAR16 *loaded_image_path; + CHAR8 *b; + UINTN size; + BOOLEAN secure = FALSE; + CHAR8 *sections[] = { + (UINT8 *)".cmdline", + (UINT8 *)".linux", + (UINT8 *)".initrd", + (UINT8 *)".splash", + NULL + }; + UINTN addrs[ELEMENTSOF(sections)-1] = {}; + UINTN offs[ELEMENTSOF(sections)-1] = {}; + UINTN szs[ELEMENTSOF(sections)-1] = {}; + CHAR8 *cmdline = NULL; + UINTN cmdline_len; + CHAR16 uuid[37]; + EFI_STATUS err; + + InitializeLib(image, sys_table); + + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, + image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(err)) { + Print(L"Error getting a LoadedImageProtocol handle: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + root_dir = LibOpenRoot(loaded_image->DeviceHandle); + if (!root_dir) { + Print(L"Unable to open root directory: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return EFI_LOAD_ERROR; + } + + loaded_image_path = DevicePathToStr(loaded_image->FilePath); + + if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS) { + if (*b > 0) + secure = TRUE; + FreePool(b); + } + + err = pefile_locate_sections(root_dir, loaded_image_path, sections, addrs, offs, szs); + if (EFI_ERROR(err)) { + Print(L"Unable to locate embedded .linux section: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + if (szs[0] > 0) + cmdline = (CHAR8 *)(loaded_image->ImageBase + addrs[0]); + + cmdline_len = szs[0]; + + /* if we are not in secure boot mode, accept a custom command line and replace the built-in one */ + if (!secure && loaded_image->LoadOptionsSize > 0) { + CHAR16 *options; + CHAR8 *line; + UINTN i; + + options = (CHAR16 *)loaded_image->LoadOptions; + cmdline_len = (loaded_image->LoadOptionsSize / sizeof(CHAR16)) * sizeof(CHAR8); + line = AllocatePool(cmdline_len); + for (i = 0; i < cmdline_len; i++) + line[i] = options[i]; + cmdline = line; + +#ifdef SD_BOOT_LOG_TPM + /* Try to log any options to the TPM, escpecially manually edited options */ + err = tpm_log_event(SD_TPM_PCR, + (EFI_PHYSICAL_ADDRESS) loaded_image->LoadOptions, + loaded_image->LoadOptionsSize, loaded_image->LoadOptions); + if (EFI_ERROR(err)) { + Print(L"Unable to add image options measurement: %r", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } +#endif + } + + /* export the device path this image is started from */ + if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) + efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); + + if (szs[3] > 0) + graphics_splash((UINT8 *)((UINTN)loaded_image->ImageBase + addrs[3]), szs[3], NULL); + + err = linux_exec(image, cmdline, cmdline_len, + (UINTN)loaded_image->ImageBase + addrs[1], + (UINTN)loaded_image->ImageBase + addrs[2], szs[2]); + + graphics_mode(FALSE); + Print(L"Execution of embedded linux image failed: %r\n", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; +} diff --git a/src/grp-boot/systemd-boot/test-efi-create-disk.sh b/src/grp-boot/systemd-boot/test-efi-create-disk.sh new file mode 100755 index 0000000000..cd4699dc18 --- /dev/null +++ b/src/grp-boot/systemd-boot/test-efi-create-disk.sh @@ -0,0 +1,42 @@ +#!/bin/bash -e + +# create GPT table with EFI System Partition +rm -f test-efi-disk.img +dd if=/dev/null of=test-efi-disk.img bs=1M seek=512 count=1 +parted --script test-efi-disk.img "mklabel gpt" "mkpart ESP fat32 1MiB 511MiB" "set 1 boot on" + +# create FAT32 file system +LOOP=$(losetup --show -f -P test-efi-disk.img) +mkfs.vfat -F32 ${LOOP}p1 +mkdir -p mnt +mount ${LOOP}p1 mnt + +mkdir -p mnt/EFI/{BOOT,systemd} +cp systemd-bootx64.efi mnt/EFI/BOOT/BOOTX64.efi + +[ -e /boot/shellx64.efi ] && cp /boot/shellx64.efi mnt/ + +mkdir mnt/EFI/Linux +echo -n "foo=yes bar=no root=/dev/fakeroot debug rd.break=initqueue" > mnt/cmdline.txt +objcopy \ + --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \ + --add-section .cmdline=mnt/cmdline.txt --change-section-vma .cmdline=0x30000 \ + --add-section .splash=test/splash.bmp --change-section-vma .splash=0x40000 \ + --add-section .linux=/boot/$(cat /etc/machine-id)/$(uname -r)/linux --change-section-vma .linux=0x2000000 \ + --add-section .initrd=/boot/$(cat /etc/machine-id)/$(uname -r)/initrd --change-section-vma .initrd=0x3000000 \ + linuxx64.efi.stub mnt/EFI/Linux/linux-test.efi + +# install entries +mkdir -p mnt/loader/entries +echo -e "timeout 3\n" > mnt/loader/loader.conf +echo -e "title Test\nefi /test\n" > mnt/loader/entries/test.conf +echo -e "title Test2\nlinux /test2\noptions option=yes word number=1000 more\n" > mnt/loader/entries/test2.conf +echo -e "title Test3\nlinux /test3\n" > mnt/loader/entries/test3.conf +echo -e "title Test4\nlinux /test4\n" > mnt/loader/entries/test4.conf +echo -e "title Test5\nefi /test5\n" > mnt/loader/entries/test5.conf +echo -e "title Test6\nlinux /test6\n" > mnt/loader/entries/test6.conf + +sync +umount mnt +rmdir mnt +losetup -d $LOOP diff --git a/src/grp-boot/systemd-boot/util.c b/src/grp-boot/systemd-boot/util.c new file mode 100644 index 0000000000..98c5be74ce --- /dev/null +++ b/src/grp-boot/systemd-boot/util.c @@ -0,0 +1,345 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + */ + +#include <efi.h> +#include <efilib.h> + +#include "util.h" + +/* + * Allocated random UUID, intended to be shared across tools that implement + * the (ESP)\loader\entries\<vendor>-<revision>.conf convention and the + * associated EFI variables. + */ +static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} }; + +#ifdef __x86_64__ +UINT64 ticks_read(VOID) { + UINT64 a, d; + __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d)); + return (d << 32) | a; +} +#elif defined(__i386__) +UINT64 ticks_read(VOID) { + UINT64 val; + __asm__ volatile ("rdtsc" : "=A" (val)); + return val; +} +#else +UINT64 ticks_read(VOID) { + UINT64 val = 1; + return val; +} +#endif + +/* count TSC ticks during a millisecond delay */ +UINT64 ticks_freq(VOID) { + UINT64 ticks_start, ticks_end; + + ticks_start = ticks_read(); + uefi_call_wrapper(BS->Stall, 1, 1000); + ticks_end = ticks_read(); + + return (ticks_end - ticks_start) * 1000; +} + +UINT64 time_usec(VOID) { + UINT64 ticks; + static UINT64 freq; + + ticks = ticks_read(); + if (ticks == 0) + return 0; + + if (freq == 0) { + freq = ticks_freq(); + if (freq == 0) + return 0; + } + + return 1000 * 1000 * ticks / freq; +} + +EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b) { + if (strcmpa(v, (CHAR8 *)"1") == 0 || + strcmpa(v, (CHAR8 *)"yes") == 0 || + strcmpa(v, (CHAR8 *)"y") == 0 || + strcmpa(v, (CHAR8 *)"true") == 0) { + *b = TRUE; + return EFI_SUCCESS; + } + + if (strcmpa(v, (CHAR8 *)"0") == 0 || + strcmpa(v, (CHAR8 *)"no") == 0 || + strcmpa(v, (CHAR8 *)"n") == 0 || + strcmpa(v, (CHAR8 *)"false") == 0) { + *b = FALSE; + return EFI_SUCCESS; + } + + return EFI_INVALID_PARAMETER; +} + +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) { + UINT32 flags; + + flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; + if (persistent) + flags |= EFI_VARIABLE_NON_VOLATILE; + + return uefi_call_wrapper(RT->SetVariable, 5, name, (EFI_GUID *)vendor, flags, size, buf); +} + +EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) { + return efivar_set_raw(&loader_guid, name, (CHAR8 *)value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, persistent); +} + +EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent) { + CHAR16 str[32]; + + SPrint(str, 32, L"%d", i); + return efivar_set(name, str, persistent); +} + +EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) { + CHAR8 *buf; + CHAR16 *val; + UINTN size; + EFI_STATUS err; + + err = efivar_get_raw(&loader_guid, name, &buf, &size); + if (EFI_ERROR(err)) + return err; + + val = StrDuplicate((CHAR16 *)buf); + if (!val) { + FreePool(buf); + return EFI_OUT_OF_RESOURCES; + } + + *value = val; + return EFI_SUCCESS; +} + +EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) { + CHAR16 *val; + EFI_STATUS err; + + err = efivar_get(name, &val); + if (!EFI_ERROR(err)) { + *i = Atoi(val); + FreePool(val); + } + return err; +} + +EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) { + CHAR8 *buf; + UINTN l; + EFI_STATUS err; + + l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE; + buf = AllocatePool(l); + if (!buf) + return EFI_OUT_OF_RESOURCES; + + err = uefi_call_wrapper(RT->GetVariable, 5, name, (EFI_GUID *)vendor, NULL, &l, buf); + if (!EFI_ERROR(err)) { + *buffer = buf; + if (size) + *size = l; + } else + FreePool(buf); + return err; + +} + +VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec) { + CHAR16 str[32]; + + if (usec == 0) + usec = time_usec(); + if (usec == 0) + return; + + SPrint(str, 32, L"%ld", usec); + efivar_set(name, str, FALSE); +} + +static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) { + CHAR16 unichar; + UINTN len; + UINTN i; + + if (stra[0] < 0x80) + len = 1; + else if ((stra[0] & 0xe0) == 0xc0) + len = 2; + else if ((stra[0] & 0xf0) == 0xe0) + len = 3; + else if ((stra[0] & 0xf8) == 0xf0) + len = 4; + else if ((stra[0] & 0xfc) == 0xf8) + len = 5; + else if ((stra[0] & 0xfe) == 0xfc) + len = 6; + else + return -1; + + switch (len) { + case 1: + unichar = stra[0]; + break; + case 2: + unichar = stra[0] & 0x1f; + break; + case 3: + unichar = stra[0] & 0x0f; + break; + case 4: + unichar = stra[0] & 0x07; + break; + case 5: + unichar = stra[0] & 0x03; + break; + case 6: + unichar = stra[0] & 0x01; + break; + } + + for (i = 1; i < len; i++) { + if ((stra[i] & 0xc0) != 0x80) + return -1; + unichar <<= 6; + unichar |= stra[i] & 0x3f; + } + + *c = unichar; + return len; +} + +CHAR16 *stra_to_str(CHAR8 *stra) { + UINTN strlen; + UINTN len; + UINTN i; + CHAR16 *str; + + len = strlena(stra); + str = AllocatePool((len + 1) * sizeof(CHAR16)); + + strlen = 0; + i = 0; + while (i < len) { + INTN utf8len; + + utf8len = utf8_to_16(stra + i, str + strlen); + if (utf8len <= 0) { + /* invalid utf8 sequence, skip the garbage */ + i++; + continue; + } + + strlen++; + i += utf8len; + } + str[strlen] = '\0'; + return str; +} + +CHAR16 *stra_to_path(CHAR8 *stra) { + CHAR16 *str; + UINTN strlen; + UINTN len; + UINTN i; + + len = strlena(stra); + str = AllocatePool((len + 2) * sizeof(CHAR16)); + + str[0] = '\\'; + strlen = 1; + i = 0; + while (i < len) { + INTN utf8len; + + utf8len = utf8_to_16(stra + i, str + strlen); + if (utf8len <= 0) { + /* invalid utf8 sequence, skip the garbage */ + i++; + continue; + } + + if (str[strlen] == '/') + str[strlen] = '\\'; + if (str[strlen] == '\\' && str[strlen-1] == '\\') { + /* skip double slashes */ + i += utf8len; + continue; + } + + strlen++; + i += utf8len; + } + str[strlen] = '\0'; + return str; +} + +CHAR8 *strchra(CHAR8 *s, CHAR8 c) { + do { + if (*s == c) + return s; + } while (*s++); + return NULL; +} + +INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content) { + EFI_FILE_HANDLE handle; + CHAR8 *buf; + UINTN buflen; + EFI_STATUS err; + UINTN len; + + err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0ULL); + if (EFI_ERROR(err)) + return err; + + if (size == 0) { + EFI_FILE_INFO *info; + + info = LibFileInfo(handle); + buflen = info->FileSize+1; + FreePool(info); + } else + buflen = size; + + if (off > 0) { + err = uefi_call_wrapper(handle->SetPosition, 2, handle, off); + if (EFI_ERROR(err)) + return err; + } + + buf = AllocatePool(buflen); + err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf); + if (!EFI_ERROR(err)) { + buf[buflen] = '\0'; + *content = buf; + len = buflen; + } else { + len = err; + FreePool(buf); + } + + uefi_call_wrapper(handle->Close, 1, handle); + return len; +} diff --git a/src/grp-boot/systemd-boot/util.h b/src/grp-boot/systemd-boot/util.h new file mode 100644 index 0000000000..e673cdf9a0 --- /dev/null +++ b/src/grp-boot/systemd-boot/util.h @@ -0,0 +1,48 @@ +/* + * This program 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. + * + * This program 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. + * + * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> + */ + +#ifndef __SDBOOT_UTIL_H +#define __SDBOOT_UTIL_H + +#include <efi.h> +#include <efilib.h> + +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +static inline const CHAR16 *yes_no(BOOLEAN b) { + return b ? L"yes" : L"no"; +} + +EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b); + +UINT64 ticks_read(void); +UINT64 ticks_freq(void); +UINT64 time_usec(void); + +EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent); +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent); +EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent); +VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec); + +EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value); +EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size); +EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i); + +CHAR8 *strchra(CHAR8 *s, CHAR8 c); +CHAR16 *stra_to_path(CHAR8 *stra); +CHAR16 *stra_to_str(CHAR8 *stra); + +INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content); +#endif |