chiark / gitweb /
bootctl: add boot loader and firmware interface tool
authorKay Sievers <kay@vrfy.org>
Fri, 8 Feb 2013 16:24:43 +0000 (17:24 +0100)
committerKay Sievers <kay@vrfy.org>
Mon, 11 Feb 2013 18:35:52 +0000 (19:35 +0100)
.gitignore
Makefile.am
src/boot/boot-efi.c [new file with mode: 0644]
src/boot/boot-loader.c [new file with mode: 0644]
src/boot/boot-loader.h [new file with mode: 0644]
src/boot/boot.h [new file with mode: 0644]
src/boot/bootctl.c [new file with mode: 0644]
src/shared/efivars.c
src/shared/efivars.h
src/shared/utf8.c

index 350aaf7..d27cce7 100644 (file)
@@ -2,6 +2,7 @@
 /TAGS
 /accelerometer
 /ata_id
+/bootctl
 /build-aux
 /cdrom_id
 /collect
index 9da3394..2cec04a 100644 (file)
@@ -3069,7 +3069,6 @@ timedatectl_LDADD = \
 
 bin_PROGRAMS += \
        timedatectl
-
 endif
 
 polkitpolicy_in_files += \
@@ -3079,6 +3078,21 @@ EXTRA_DIST += \
        units/systemd-timedated.service.in
 
 # ------------------------------------------------------------------------------
+bootctl_SOURCES = \
+       src/boot/boot.h \
+       src/boot/boot-loader.h \
+       src/boot/bootctl.c \
+       src/boot/boot-loader.c \
+       src/boot/boot-efi.c
+
+bootctl_LDADD = \
+       libsystemd-shared.la \
+       libsystemd-id128.la
+
+bin_PROGRAMS += \
+       bootctl
+
+# ------------------------------------------------------------------------------
 if HAVE_MYHOSTNAME
 libnss_myhostname_la_SOURCES = \
        src/nss-myhostname/nss-myhostname.c \
diff --git a/src/boot/boot-efi.c b/src/boot/boot-efi.c
new file mode 100644 (file)
index 0000000..32cb973
--- /dev/null
@@ -0,0 +1,171 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Kay Sievers
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <locale.h>
+#include <string.h>
+#include <fnmatch.h>
+#include <fcntl.h>
+#include <sys/timex.h>
+
+#include "boot.h"
+#include "boot-loader.h"
+#include "build.h"
+#include "util.h"
+#include "strv.h"
+#include "efivars.h"
+#include "conf-files.h"
+
+static int get_boot_entries(struct boot_info *info) {
+        DIR *d = NULL;
+        struct dirent *dent;
+        int err = 0;
+
+        d = opendir("/sys/firmware/efi/efivars");
+        if (!d)
+                return -errno;
+
+        for (dent = readdir(d); dent != NULL; dent = readdir(d)) {
+                unsigned int id;
+                struct boot_info_entry *e;
+
+                if (dent->d_name[0] == '.')
+                        continue;
+                if (sscanf(dent->d_name, "Boot%04X-8be4df61-93ca-11d2-aa0d-00e098032b8c", &id) != 1)
+                        continue;
+
+                e = realloc(info->fw_entries, (info->fw_entries_count+1) * sizeof(struct boot_info_entry));
+                if (!e) {
+                        err = -ENOMEM;
+                        break;
+                }
+                info->fw_entries = e;
+
+                e = &info->fw_entries[info->fw_entries_count];
+                memset(e, 0, sizeof(struct boot_info_entry));
+                e->order = -1;
+
+                err = efi_get_boot_option(id, NULL, &e->title, &e->part_uuid, &e->path, &e->data, &e->data_size);
+                if (err < 0)
+                        break;
+                e->id = id;
+
+                info->fw_entries_count++;
+        }
+        closedir(d);
+
+        return err;
+}
+
+static int find_active_entry(struct boot_info *info) {
+        uint16_t boot_cur;
+        void *buf;
+        size_t l;
+        size_t i;
+        int err = -ENOENT;
+
+        err = efi_get_variable(EFI_VENDOR_GLOBAL, "BootCurrent", NULL, &buf, &l);
+        if (err < 0)
+                return err;
+
+        memcpy(&boot_cur, buf, sizeof(uint16_t));
+        for (i = 0; i < info->fw_entries_count; i++) {
+                if (info->fw_entries[i].id != boot_cur)
+                        continue;
+                info->fw_entry_active = i;
+                err = 0;
+                break;
+        }
+        free(buf);
+        return err;
+}
+
+static int get_boot_order(struct boot_info *info) {
+        size_t i, k;
+        int err;
+
+        err = efi_get_boot_order(&info->fw_entries_order, &info->fw_entries_order_count);
+        if (err < 0)
+                return err;
+
+        for (i = 0; i < info->fw_entries_order_count; i++) {
+                for (k = 0; k < info->fw_entries_count; k++) {
+                        if (info->fw_entries[k].id != info->fw_entries_order[i])
+                                continue;
+                        info->fw_entries[k].order = i;
+                        break;
+                }
+        }
+
+        return 0;
+}
+
+static int entry_cmp(const void *a, const void *b) {
+        const struct boot_info_entry *e1 = a;
+        const struct boot_info_entry *e2 = b;
+
+        /* boot order of active entries */
+        if (e1->order > 0 && e2->order > 0)
+                return e1->order - e2->order;
+
+        /* sort active entries before inactive ones */
+        if (e1->order > 0)
+                return 1;
+        if (e2->order > 0)
+                return -1;
+
+        /* order of inactive entries */
+        return e1->id - e2->id;
+}
+
+int boot_info_query(struct boot_info *info) {
+        char str[64];
+        char buf[64];
+        char *loader_active;
+
+        info->loader = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderInfo");
+
+        get_boot_entries(info);
+        if (info->fw_entries_count > 0) {
+                get_boot_order(info);
+                qsort(info->fw_entries, info->fw_entries_count, sizeof(struct boot_info_entry), entry_cmp);
+                find_active_entry(info);
+        }
+
+        info->fw_type = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareType");
+        info->fw_info = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareInfo");
+        info->loader_image_path = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderImageIdentifier");
+        efi_get_loader_device_part_uuid(&info->loader_part_uuid);
+
+        boot_loader_read_entries(info);
+        loader_active = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntrySelected");
+        if (loader_active) {
+                boot_loader_find_active_entry(info, loader_active);
+                free(loader_active);
+        }
+
+        snprintf(str, sizeof(str), "LoaderEntryOptions-%s", sd_id128_to_string(info->machine_id, buf));
+        info->loader_options_added = efi_get_variable_string(EFI_VENDOR_LOADER, str);
+        return 0;
+}
diff --git a/src/boot/boot-loader.c b/src/boot/boot-loader.c
new file mode 100644 (file)
index 0000000..bd563da
--- /dev/null
@@ -0,0 +1,131 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Kay Sievers
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <locale.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/timex.h>
+
+#include "boot.h"
+#include "boot-loader.h"
+#include "build.h"
+#include "util.h"
+#include "strv.h"
+#include "conf-files.h"
+
+static char *loader_fragment_read_title(const char *fragment) {
+        FILE *f;
+        char line[LINE_MAX];
+        char *title = NULL;
+
+        f = fopen(fragment, "re");
+        if (!f)
+                return NULL;
+
+        while (fgets(line, sizeof(line), f) != NULL) {
+                char *s;
+                size_t l;
+
+                l = strlen(line);
+                if (l < 1)
+                        continue;
+                if (line[l-1] == '\n')
+                        line[l-1] = '\0';
+
+                s = line;
+                while (isspace(s[0]))
+                        s++;
+
+                if (s[0] == '#')
+                        continue;
+
+                if (!startswith(s, "title"))
+                        continue;
+
+                s += strlen("title");
+                if (!isspace(s[0]))
+                        continue;
+                while (isspace(s[0]))
+                        s++;
+
+                title = strdup(s);
+                break;
+        }
+
+        fclose(f);
+        return title;
+}
+
+int boot_loader_read_entries(struct boot_info *info) {
+        _cleanup_strv_free_ char **files = NULL;
+        static const char *loader_dir[] = { "/boot/loader/entries", NULL};
+        unsigned int count;
+        unsigned int i;
+        int err;
+
+        err = conf_files_list_strv(&files, ".conf", NULL, loader_dir);
+        if (err < 0)
+                return err;
+
+        count = strv_length(files);
+        info->loader_entries = new0(struct boot_info_entry, count);
+        if (!info->loader_entries)
+                return -ENOMEM;
+
+        for (i = 0; i < count; i++) {
+                info->loader_entries[i].title = loader_fragment_read_title(files[i]);
+                info->loader_entries[i].path = strdup(files[i]);
+                if (!info->loader_entries[i].title || !info->loader_entries[i].path) {
+                        free(info->loader_entries[i].title);
+                        free(info->loader_entries[i].path);
+                        return -ENOMEM;
+                }
+                info->loader_entries_count++;
+        }
+        return 0;
+}
+
+int boot_loader_find_active_entry(struct boot_info *info, const char *loader_active) {
+        char *fn;
+        unsigned int i;
+
+        if (!loader_active)
+                return -ENOENT;
+        if (info->loader_entries_count == 0)
+                return -ENOENT;
+
+        if (asprintf(&fn, "/boot/loader/entries/%s.conf", loader_active) < 0)
+                return -ENOMEM;
+
+        for (i = 0; i < info->loader_entries_count; i++) {
+                if (strcmp(fn, info->loader_entries[i].path) == 0) {
+                        info->loader_entry_active = i;
+                        break;
+                }
+        }
+
+        free(fn);
+        return 0;
+}
diff --git a/src/boot/boot-loader.h b/src/boot/boot-loader.h
new file mode 100644 (file)
index 0000000..08827c3
--- /dev/null
@@ -0,0 +1,25 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Kay Sievers
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int boot_loader_read_entries(struct boot_info *info);
+int boot_loader_find_active_entry(struct boot_info *info, const char *loader_active);
diff --git a/src/boot/boot.h b/src/boot/boot.h
new file mode 100644 (file)
index 0000000..90e45cc
--- /dev/null
@@ -0,0 +1,64 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Kay Sievers
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-id128.h"
+
+/*
+ * Firmware and boot manager information to be filled in
+ * by the platform.
+ *
+ * This is partly EFI specific, if you add things, keep this
+ * as generic as possible to be able to re-use it on other
+ * platforms.
+ */
+
+struct boot_info_entry {
+        uint16_t id;
+        uint16_t order;
+        char *title;
+        sd_id128_t part_uuid;
+        char *path;
+        char *data;
+        size_t data_size;
+};
+
+struct boot_info {
+        sd_id128_t machine_id;
+        sd_id128_t boot_id;
+        char *fw_type;
+        char *fw_info;
+        struct boot_info_entry *fw_entries;
+        size_t fw_entries_count;
+        uint16_t *fw_entries_order;
+        size_t fw_entries_order_count;
+        ssize_t fw_entry_active;
+        char *loader;
+        char *loader_image_path;
+        sd_id128_t loader_part_uuid;
+        struct boot_info_entry *loader_entries;
+        size_t loader_entries_count;
+        ssize_t loader_entry_active;
+        char *loader_options_added;
+};
+
+int boot_info_query(struct boot_info *info);
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
new file mode 100644 (file)
index 0000000..641546b
--- /dev/null
@@ -0,0 +1,271 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Kay Sievers
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <locale.h>
+#include <string.h>
+#include <sys/timex.h>
+
+#include "boot.h"
+#include "build.h"
+#include "util.h"
+#include "utf8.h"
+
+static int help(void) {
+        printf("%s [OPTIONS...] COMMAND ...\n\n"
+               "Query or change firmware and boot mananger settings.\n\n"
+               "  -h --help              Show this help\n"
+               "     --version           Show package version\n"
+               "Commands:\n"
+               "  status                 Show current time settings\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+        };
+
+        static const struct option options[] = {
+                { "help",                no_argument,       NULL, 'h'                     },
+                { "version",             no_argument,       NULL, ARG_VERSION             },
+                { NULL,                  0,                 NULL, 0                       }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+        while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        return 1;
+}
+
+static int boot_info_new(struct boot_info **info) {
+        struct boot_info *in;
+        int err;
+
+        in = new0(struct boot_info, 1);
+        if (!in)
+                return -ENOMEM;
+
+        err = sd_id128_get_machine(&in->machine_id);
+        if (err < 0)
+                goto err;
+
+        err = sd_id128_get_boot(&in->boot_id);
+        if (err < 0)
+                goto err;
+
+        in->fw_entry_active = -1;
+        in->loader_entry_active = -1;
+
+        *info = in;
+        return 0;
+err:
+        free(in);
+        return err;
+}
+
+static void boot_info_entries_free(struct boot_info_entry *entries, size_t n) {
+        size_t i;
+
+        for (i = 0; i < n; i++) {
+                free(entries[i].title);
+                free(entries[i].path);
+        }
+        free(entries);
+}
+
+static void boot_info_free(struct boot_info *info) {
+        free(info->fw_type);
+        free(info->fw_info);
+        boot_info_entries_free(info->fw_entries, info->fw_entries_count);
+        free(info->fw_entries_order);
+        free(info->loader);
+        free(info->loader_image_path);
+        free(info->loader_options_added);
+        boot_info_entries_free(info->loader_entries, info->loader_entries_count);
+        free(info);
+}
+
+static int show_status(char **args, unsigned n) {
+        char buf[64];
+        struct boot_info *info;
+        int err;
+
+        err = boot_info_new(&info);
+        if (err < 0)
+                return -ENOMEM;
+
+        err = boot_info_query(info);
+
+        printf("    Machine ID: %s\n", sd_id128_to_string(info->machine_id, buf));
+        printf("       Boot ID: %s\n", sd_id128_to_string(info->boot_id, buf));
+        if (info->fw_type)
+                printf("      Firmware: %s (%s)\n", info->fw_type, strna(info->fw_info));
+
+        if (info->fw_entry_active >= 0) {
+                printf("Firmware entry: %s\n", info->fw_entries[info->fw_entry_active].title);
+                if (info->fw_entries[info->fw_entry_active].path)
+                        printf("                %s\n", info->fw_entries[info->fw_entry_active].path);
+                if (!sd_id128_equal(info->fw_entries[info->fw_entry_active].part_uuid, SD_ID128_NULL))
+                        printf("                %s\n", sd_id128_to_string(info->fw_entries[info->fw_entry_active].part_uuid, buf));
+        }
+
+        if (info->loader) {
+                printf("        Loader: %s\n", info->loader);
+                printf("                %s\n", strna(info->loader_image_path));
+                if (!sd_id128_equal(info->loader_part_uuid, SD_ID128_NULL))
+                        printf("                %s\n", sd_id128_to_string(info->loader_part_uuid, buf));
+
+                if (info->loader_entry_active >= 0) {
+                        printf("  Loader entry: %s\n", info->loader_entries[info->loader_entry_active].title);
+                        printf("                %s\n", info->loader_entries[info->loader_entry_active].path);
+                }
+
+                printf("Loader options: %s\n", strna(info->loader_options_added));
+        } else
+                printf("No suitable data is provided by the boot manager. See:\n"
+                       "  http://www.freedesktop.org/wiki/Software/systemd/BootLoaderInterface\n"
+                       "  http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec\n"
+                       "for details.\n");
+        printf("\n");
+
+        boot_info_free(info);
+        return err;
+}
+
+static int bootctl_main(int argc, char *argv[]) {
+        static const struct {
+                const char* verb;
+                const enum {
+                        MORE,
+                        LESS,
+                        EQUAL
+                } argc_cmp;
+                const int argc;
+                int (* const dispatch)(char **args, unsigned n);
+        } verbs[] = {
+                { "status",                LESS,   1, show_status      },
+        };
+
+        int left;
+        unsigned i;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        left = argc - optind;
+
+        if (left <= 0)
+                /* Special rule: no arguments means "status" */
+                i = 0;
+        else {
+                if (streq(argv[optind], "help")) {
+                        help();
+                        return 0;
+                }
+
+                for (i = 0; i < ELEMENTSOF(verbs); i++)
+                        if (streq(argv[optind], verbs[i].verb))
+                                break;
+
+                if (i >= ELEMENTSOF(verbs)) {
+                        log_error("Unknown operation %s", argv[optind]);
+                        return -EINVAL;
+                }
+        }
+
+        switch (verbs[i].argc_cmp) {
+
+        case EQUAL:
+                if (left != verbs[i].argc) {
+                        log_error("Invalid number of arguments.");
+                        return -EINVAL;
+                }
+                break;
+
+        case MORE:
+                if (left < verbs[i].argc) {
+                        log_error("Too few arguments.");
+                        return -EINVAL;
+                }
+                break;
+
+        case LESS:
+                if (left > verbs[i].argc) {
+                        log_error("Too many arguments.");
+                        return -EINVAL;
+                }
+                break;
+
+        default:
+                assert_not_reached("Unknown comparison operator.");
+        }
+
+        return verbs[i].dispatch(argv + optind, left);
+}
+
+int main(int argc, char *argv[]) {
+        int r, retval = EXIT_FAILURE;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r < 0)
+                goto finish;
+        else if (r == 0) {
+                retval = EXIT_SUCCESS;
+                goto finish;
+        }
+
+        r = bootctl_main(argc, argv);
+        retval = r < 0 ? EXIT_FAILURE : r;
+finish:
+        return retval;
+}
index 09af44a..e16c610 100644 (file)
 ***/
 
 #include <unistd.h>
+#include <string.h>
 #include <fcntl.h>
 
 #include "util.h"
 #include "utf8.h"
 #include "efivars.h"
 
-#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f)
-
 bool is_efiboot(void) {
         return access("/sys/firmware/efi", F_OK) >= 0;
 }
@@ -93,6 +92,188 @@ int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, v
         return 0;
 }
 
+char *efi_get_variable_string(sd_id128_t vendor, const char *name) {
+        _cleanup_free_ void *s = NULL;
+        size_t ss;
+        int err;
+
+        err = efi_get_variable(vendor, name, NULL, &s, &ss);
+        if (err < 0)
+                return NULL;
+        return utf16_to_utf8(s, ss);
+}
+
+static size_t utf16_size(const uint16_t *s) {
+        size_t l = 0;
+
+        while (s[l] > 0)
+                l++;
+        return (l+1) * sizeof(uint16_t);
+}
+
+static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) {
+        struct uuid {
+                uint32_t u1;
+                uint16_t u2;
+                uint16_t u3;
+                uint8_t u4[8];
+        } _packed_;
+        const struct uuid *uuid = guid;
+
+        id128->bytes[0] = (uuid->u1 >> 24) & 0xff;
+        id128->bytes[1] = (uuid->u1 >> 16) & 0xff;
+        id128->bytes[2] = (uuid->u1 >> 8) & 0xff;
+        id128->bytes[3] = (uuid->u1) & 0xff;
+        id128->bytes[4] = (uuid->u2 >> 8) & 0xff;
+        id128->bytes[5] = (uuid->u2) & 0xff;
+        id128->bytes[6] = (uuid->u3 >> 8) & 0xff;
+        id128->bytes[7] = (uuid->u3) & 0xff;
+        memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4));
+}
+
+int efi_get_boot_option(uint32_t id, uint32_t *attributes, char **title, sd_id128_t *part_uuid, char **path, char **data, size_t *data_size) {
+        struct boot_option {
+                uint32_t attr;
+                uint16_t path_len;
+                uint16_t title[];
+        } _packed_;
+
+        struct drive_path {
+                uint32_t part_nr;
+                uint64_t part_start;
+                uint64_t part_size;
+                char signature[16];
+                uint8_t mbr_type;
+                uint8_t signature_type;
+        } _packed_;
+
+        struct device_path {
+                uint8_t type;
+                uint8_t sub_type;
+                uint16_t length;
+                union {
+                        uint16_t path[0];
+                        struct drive_path drive;
+                };
+        } _packed_;
+
+        char boot_id[32];
+        _cleanup_free_ char *buf = NULL;
+        size_t l;
+        struct boot_option *header;
+        size_t title_size;
+        char *s = NULL;
+        char *p = NULL;
+        sd_id128_t p_uuid = SD_ID128_NULL;
+        char *d = NULL;
+        size_t d_size = 0;
+        int err;
+
+        snprintf(boot_id, sizeof(boot_id), "Boot%04X", id);
+        err = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l);
+        if (err < 0)
+                return err;
+
+        if (l < sizeof(struct boot_option))
+                return -ENOENT;
+
+        header = (struct boot_option *)buf;
+        title_size = utf16_size(header->title);
+        if (title_size > l - offsetof(struct boot_option, title))
+                return -EINVAL;
+
+        s = utf16_to_utf8(header->title, title_size);
+        if (!s) {
+                err = -ENOMEM;
+                goto err;
+        }
+
+        if (header->path_len > 0) {
+                char *dbuf;
+                size_t dnext;
+
+                dbuf = buf + offsetof(struct boot_option, title) + title_size;
+                dnext = 0;
+                while (dnext < header->path_len) {
+                        struct device_path *dpath;
+
+                        dpath = (struct device_path *)(dbuf + dnext);
+                        if (dpath->length < 4)
+                                break;
+
+                        /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */
+                        if (dpath->type == 0x7f && dpath->sub_type == 0xff)
+                                break;
+
+                        dnext += dpath->length;
+
+                        /* Type 0x04 – Media Device Path */
+                        if (dpath->type != 0x04)
+                                continue;
+
+                        /* Sub-Type 1 – Hard Drive */
+                        if (dpath->sub_type == 0x01) {
+                                /* 0x02 – GUID Partition Table */
+                                if (dpath->drive.mbr_type != 0x02)
+                                        continue;
+
+                                /* 0x02 – GUID signature */
+                                if (dpath->drive.signature_type != 0x02)
+                                        continue;
+
+                                efi_guid_to_id128(dpath->drive.signature, &p_uuid);
+                                continue;
+                        }
+
+                        /* Sub-Type 4 – File Path */
+                        if (dpath->sub_type == 0x04) {
+                                p = utf16_to_utf8(dpath->path, dpath->length-4);
+                                continue;
+                        }
+                }
+        }
+
+        *title = s;
+        if (part_uuid)
+                *part_uuid = p_uuid;
+        if (path)
+                *path = p;
+        if (data)
+                *data = d;
+        if (data_size)
+                *data_size = d_size;
+        return 0;
+err:
+        free(s);
+        free(p);
+        free(d);
+        return err;
+}
+
+int efi_get_boot_order(uint16_t **order, size_t *count) {
+        void *buf;
+        size_t l;
+        int err;
+
+        err = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l);
+        if (err < 0)
+                return err;
+
+        if (l == 0) {
+                free(buf);
+                return -ENOENT;
+        }
+
+        if ((l % sizeof(uint16_t) > 0)) {
+                free(buf);
+                return -EINVAL;
+        }
+
+        *order = buf;
+        *count = l / sizeof(uint16_t);
+        return 0;
+}
+
 static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) {
         _cleanup_free_ void *i = NULL;
         _cleanup_free_ char *j = NULL;
index d5cfb4f..d54cc43 100644 (file)
 #include "sd-id128.h"
 #include "time-util.h"
 
+#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f)
+#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c)
+
 bool is_efiboot(void);
 
 int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size);
+char *efi_get_variable_string(sd_id128_t vendor, const char *name);
+
+int efi_get_boot_option(uint32_t nr, uint32_t *attributes, char **title, sd_id128_t *partuuid, char **path, char **data, size_t *data_size);
+int efi_get_boot_order(uint16_t **order, size_t *count);
 
 int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader);
 
index 7ec8cb6..3964e8b 100644 (file)
@@ -317,6 +317,6 @@ char *utf16_to_utf8(const void *s, size_t length) {
         }
 
         *t = 0;
-        return r;
 
+        return r;
 }