bootctl: rework to use common verbs parsing, and add searching of ESP path

This rearranges bootctl a bit, so that it uses the usual verbs parsing
routines, and automatically searches the ESP in /boot, /efi or /boot/efi, thus
increasing compatibility with mainstream distros that insist on /boot/efi.

This also adds minimal support for running bootctl in a container environment:
when run inside a container verification of the ESP via raw block device
access, trusting the container manager to mount the ESP correctly. Moreover,
EFI variables are not accessed when running in the container.
This commit is contained in:
Lennart Poettering 2016-07-19 20:19:20 +02:00
parent c18532e031
commit 2f2c539cd4
2 changed files with 226 additions and 126 deletions

View File

@ -101,8 +101,10 @@
<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>
<term><option>--path=</option></term>
<listitem><para>Path to the EFI System Partition (ESP). If not specified, <filename>/efi</filename>,
<filename>/boot</filename>, and <filename>/boot/efi</filename> are checked in turn. It is recommended to mount
the ESP to <filename>/boot</filename>, if possible.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -42,25 +42,52 @@
#include "fd-util.h"
#include "fileio.h"
#include "locale-util.h"
#include "parse-util.h"
#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
#include "umask-util.h"
#include "util.h"
#include "verbs.h"
#include "virt.h"
static const char *arg_path = "/boot";
static char *arg_path = NULL;
static bool arg_touch_variables = true;
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;
static int verify_esp(
bool searching,
const char *p,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
sd_id128_t *ret_uuid) {
_cleanup_blkid_free_probe_ blkid_probe b = NULL;
_cleanup_free_ char *t = NULL;
uint64_t pstart = 0, psize = 0;
struct stat st, st2;
const char *v, *t2;
struct statfs sfs;
sd_id128_t uuid = SD_ID128_NULL;
uint32_t part = 0;
int r;
assert(p);
if (statfs(p, &sfs) < 0) {
/* If we are searching for the mount point, don't generate a log message if we can't find the path */
if (errno == ENOENT && searching)
return -ENOENT;
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) {
if (searching)
return -EADDRNOTAVAIL;
log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
return -ENODEV;
}
@ -83,6 +110,11 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
return -ENODEV;
}
/* In a container we don't have access to block devices, skip this part of the verification, we trust the
* container manager set everything up correctly on its own. */
if (detect_container() > 0)
goto finish;
r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev));
if (r < 0)
return log_oom();
@ -120,7 +152,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
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;
@ -132,7 +163,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
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;
@ -144,7 +174,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
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;
@ -156,8 +185,7 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
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);
r = sd_id128_from_string(v, &uuid);
if (r < 0) {
log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v);
return -EIO;
@ -169,7 +197,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition number \"%s\": m", p);
}
*part = strtoul(v, NULL, 10);
r = safe_atou32(v, &part);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
@ -177,7 +207,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p);
}
*pstart = strtoul(v, NULL, 10);
r = safe_atou64(v, &pstart);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
@ -185,11 +217,50 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p);
}
*psize = strtoul(v, NULL, 10);
r = safe_atou64(v, &psize);
if (r < 0)
return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
finish:
if (ret_part)
*ret_part = part;
if (ret_pstart)
*ret_pstart = pstart;
if (ret_psize)
*ret_psize = psize;
if (ret_uuid)
*ret_uuid = uuid;
return 0;
}
static int find_esp(uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) {
const char *path;
int r;
if (arg_path)
return verify_esp(false, arg_path, part, pstart, psize, uuid);
FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
r = verify_esp(true, path, part, pstart, psize, uuid);
if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
continue;
if (r < 0)
return r;
arg_path = strdup(path);
if (!arg_path)
return log_oom();
log_info("Using EFI System Parition at %s.", path);
return 0;
}
log_error("Couldn't find EFI system partition. It is recommended to mount it to /boot. Alternatively, use --path= to specify path to mount point.");
return -ENOENT;
}
/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */
static int get_file_version(int fd, char **v) {
struct stat st;
@ -914,7 +985,8 @@ static int install_loader_config(const char *esp_path) {
return 0;
}
static int help(void) {
static int help(int argc, char *argv[], void *userdata) {
printf("%s [COMMAND] [OPTIONS...]\n"
"\n"
"Install, update or remove the systemd-boot EFI boot manager.\n\n"
@ -948,7 +1020,7 @@ static int parse_argv(int argc, char *argv[]) {
{ NULL, 0, NULL, 0 }
};
int c;
int c, r;
assert(argc >= 0);
assert(argv);
@ -957,14 +1029,16 @@ static int parse_argv(int argc, char *argv[]) {
switch (c) {
case 'h':
help();
help(0, NULL, NULL);
return 0;
case ARG_VERSION:
return version();
case ARG_PATH:
arg_path = optarg;
r = free_and_strdup(&arg_path, optarg);
if (r < 0)
return log_oom();
break;
case ARG_NO_VARIABLES:
@ -989,147 +1063,170 @@ static void read_loader_efi_var(const char *name, char **var) {
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 },
};
static int must_be_root(void) {
sd_id128_t uuid = {};
uint32_t part = 0;
uint64_t pstart = 0, psize = 0;
int r, q;
if (geteuid() == 0)
return 0;
if (argv[optind]) {
unsigned i;
log_error("Need to be root.");
return -EPERM;
}
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;
}
}
static int verb_status(int argc, char *argv[], void *userdata) {
if (geteuid() != 0)
return log_error_errno(EPERM, "Need to be root.");
sd_id128_t uuid = SD_ID128_NULL;
int r;
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.");
r = must_be_root();
if (r < 0)
return r;
switch (arg_action) {
r = find_esp(NULL, NULL, NULL, &uuid);
if (r < 0)
return r;
case ACTION_STATUS: {
if (is_efi_boot()) {
_cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL;
sd_id128_t loader_part_uuid = SD_ID128_NULL;
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");
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);
printf("System:\n");
printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info));
if (loader_path)
efi_tilt_backslashes(loader_path);
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 = 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");
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("System:\n");
printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info));
printf("Loader:\n");
printf(" Product: %s\n", strna(loader));
if (!sd_id128_equal(loader_part_uuid, SD_ID128_NULL))
printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
SD_ID128_FORMAT_VAL(loader_part_uuid));
else
printf(" Partition: n/a\n");
printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path));
printf("\n");
} else
printf("System:\n Not booted with EFI\n");
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 = status_binaries(arg_path, uuid);
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_equal(loader_part_uuid, SD_ID128_NULL))
printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
SD_ID128_FORMAT_VAL(loader_part_uuid));
else
printf(" Partition: n/a\n");
printf(" File: %s%s\n", 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();
return r;
}
static int verb_install(int argc, char *argv[], void *userdata) {
sd_id128_t uuid = SD_ID128_NULL;
uint64_t pstart = 0, psize = 0;
uint32_t part = 0;
bool install;
int r;
r = must_be_root();
if (r < 0)
return r;
r = find_esp(&part, &pstart, &psize, &uuid);
if (r < 0)
return r;
install = streq(argv[0], "install");
RUN_WITH_UMASK(0002) {
r = install_binaries(arg_path, install);
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) {
if (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;
if (arg_touch_variables)
r = install_variables(arg_path,
part, pstart, psize, uuid,
"/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi",
install);
case ACTION_REMOVE:
r = remove_binaries(arg_path);
return r;
}
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;
static int verb_remove(int argc, char *argv[], void *userdata) {
sd_id128_t uuid = SD_ID128_NULL;
int r;
r = must_be_root();
if (r < 0)
return r;
r = find_esp(NULL, NULL, NULL, &uuid);
if (r < 0)
return r;
r = remove_binaries(arg_path);
if (arg_touch_variables) {
int q;
q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true);
if (q < 0 && r == 0)
r = q;
}
return r;
}
static int bootctl_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "install", VERB_ANY, 1, 0, verb_install },
{ "update", VERB_ANY, 1, 0, verb_install },
{ "remove", VERB_ANY, 1, 0, verb_remove },
{}
};
return dispatch_verb(argc, argv, verbs, NULL);
}
int main(int argc, char *argv[]) {
int r;
log_parse_environment();
log_open();
/* If we run in a container, automatically turn of EFI file system access */
if (detect_container() > 0)
arg_touch_variables = false;
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
@ -1137,5 +1234,6 @@ int main(int argc, char *argv[]) {
r = bootctl_main(argc, argv);
finish:
free(arg_path);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}