Still use helper when Xen Dom0, to avoid duplicating some hairy code. --- Makefile.am | 4 +- TODO | 3 - src/power/shutdown.c | 28 ++++--- src/shared/missing.h | 11 +++ src/shared/strv.c | 9 ++- src/systemctl/bootspec.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++ src/systemctl/bootspec.h | 54 +++++++++++++ src/systemctl/systemctl.c | 33 +++++++- 8 files changed, 321 insertions(+), 16 deletions(-) create mode 100644 src/systemctl/bootspec.c create mode 100644 src/systemctl/bootspec.h
diff --git a/Makefile.am b/Makefile.am index 1310a20..0c5df47 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2736,7 +2736,9 @@ systemd_escape_LDADD = \ # ----------------------------------------------------------------------------- systemctl_SOURCES = \ - src/systemctl/systemctl.c + src/systemctl/systemctl.c \ + src/systemctl/bootspec.c \ + src/systemctl/bootspec.h systemctl_LDADD = \ libsystemd-units.la \ diff --git a/TODO b/TODO index bf66ba1..b2b6cb3 100644 --- a/TODO +++ b/TODO @@ -69,9 +69,6 @@ Features: * maybe introduce WantsMountsFor=? Usecase: http://lists.freedesktop.org/archives/systemd-devel/2015-January/027729.html -* rework kexec logic to use new kexec_file_load() syscall, so that we - don't have to call kexec tool anymore. - * The udev blkid built-in should expose a property that reflects whether media was sensed in USB CF/SD card readers. This should then be used to control SYSTEMD_READY=1/0 so that USB card readers aren't diff --git a/src/power/shutdown.c b/src/power/shutdown.c index ffd12b8..3044fbc 100644 --- a/src/power/shutdown.c +++ b/src/power/shutdown.c @@ -19,6 +19,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <ctype.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/reboot.h> @@ -187,9 +188,13 @@ int main(int argc, char *argv[]) { cmd = RB_POWER_OFF; else if (streq(arg_verb, "halt")) cmd = RB_HALT_SYSTEM; - else if (streq(arg_verb, "kexec")) - cmd = LINUX_REBOOT_CMD_KEXEC; - else { + else if (streq(arg_verb, "kexec")) { + if (in_container) { + log_warning("Can't kexec from container. Rebooting…"); + cmd = RB_AUTOBOOT; + } else + cmd = LINUX_REBOOT_CMD_KEXEC; + } else { r = -EINVAL; log_error("Unknown action '%s'.", arg_verb); goto error; @@ -208,8 +213,6 @@ int main(int argc, char *argv[]) { log_info("Sending SIGKILL to remaining processes..."); broadcast_signal(SIGKILL, true, false); - in_container = detect_container(NULL) > 0; - need_umount = !in_container; need_swapoff = !in_container; need_loop_detach = !in_container; @@ -349,11 +352,14 @@ int main(int argc, char *argv[]) { case LINUX_REBOOT_CMD_KEXEC: - if (!in_container) { - /* We cheat and exec kexec to avoid doing all its work */ - pid_t pid; + log_info("Rebooting with kexec."); - log_info("Rebooting with kexec."); + /* kexec-tools has a bunch of special code to make Xen Dom0 work, + * otherwise it is only doing stuff we have already done. + * This is true for Dom0 and DomU but we only get Dom0 + * because of the !in_container check */ + if (access("/proc/xen", F_OK) == 0) { + pid_t pid; pid = fork(); if (pid < 0) @@ -370,7 +376,9 @@ int main(int argc, char *argv[]) { _exit(EXIT_FAILURE); } else wait_for_terminate_and_warn("kexec", pid, true); - } + + } else + reboot(cmd); cmd = RB_AUTOBOOT; /* Fall through */ diff --git a/src/shared/missing.h b/src/shared/missing.h index b33a70c..d80b2a7 100644 --- a/src/shared/missing.h +++ b/src/shared/missing.h @@ -35,6 +35,7 @@ #include <linux/loop.h> #include <linux/audit.h> #include <linux/capability.h> +#include <linux/kexec.h> #ifdef HAVE_AUDIT #include <libaudit.h> @@ -763,3 +764,13 @@ static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, uns #ifndef KCMP_FILE #define KCMP_FILE 0 #endif + +/* v3.17 */ +#ifndef __NR_kexec_file_load +#ifdef __x86_64__ +#define __NR_kexec_file_load 320 +#endif +#endif +#ifndef KEXEC_FILE_NO_INITRAMFS +#define KEXEC_FILE_NO_INITRAMFS 0x00000004 +#endif diff --git a/src/shared/strv.c b/src/shared/strv.c index e27ac68..d983665 100644 --- a/src/shared/strv.c +++ b/src/shared/strv.c @@ -82,7 +82,14 @@ void strv_clear(char **l) { } void strv_free(char **l) { - strv_clear(l); + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + free(l); } diff --git a/src/systemctl/bootspec.c b/src/systemctl/bootspec.c new file mode 100644 index 0000000..8673957 --- /dev/null +++ b/src/systemctl/bootspec.c @@ -0,0 +1,195 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Shawn Landden + + 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/>. +***/ + +/* + * Implements http://freedesktop.org/wiki/Specifications/BootLoaderSpec/ + * for use with kexec + */ + +#include <ctype.h> +#include <dirent.h> +#include <sys/utsname.h> +#include <linux/kexec.h> + +#include "bootspec.h" +#include "strv.h" +#include "fileio.h" +#include "rpmvercmp.h" + +void bootspec_free(struct BootSpec *s) { + if (!s) + return; + + free(s->conf); + free(s); +} + +static int bootspec_cmp(const struct rbtree_node *left, const struct rbtree_node *right) { + struct BootSpec *l = rbtree_container_of( left, struct BootSpec, node), + *r = rbtree_container_of(right, struct BootSpec, node); + + return rpmvercmp(l->version, r->version); +} + +int kernel_bootloaderspec_readconf(struct rbtree *tree) { + int r = 0; + _cleanup_closedir_ DIR *entries; + struct dirent *dir; + + rbtree_init(tree, bootspec_cmp, 0); + + entries = opendir(BOOTENTRIESDIR); + if (!entries) { + if (r == -ENOENT) + return 0; + else + return -errno; + } + + for (size_t i=0;(dir = readdir(entries));i++) { + struct BootSpec *entry; + /* compiler wont allow 256 + strlen(BOOTENTRIESDIR) here */ + char fn[512] = {BOOTENTRIESDIR}, *d_name = &fn[strlen(BOOTENTRIESDIR)], + *m, *l, *k; + + if (!endswith(dir->d_name, ".conf")) { + i--; + continue; + } + + entry = new0(struct BootSpec, 1); + if (!entry) + return -ENOMEM; + + (void)strcpy(d_name, dir->d_name); + + r = read_full_file(fn, &entry->conf, NULL); + if (r < 0) + return -errno; + + for (m = entry->conf; ; m = k + 1) { + if (m[0] == '#') + continue; + + k = strchr(m, '\n'); + + if (k) + *k = '\0'; + else + break; + + if ((l = startswith(m, "title "))) + entry->title = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "version "))) + entry->version = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "machine-id "))) + (void)sd_id128_from_string(l + strspn(l, WHITESPACE), &entry->machine_id); + else if ((l = startswith(m, "options "))) + entry->options = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "linux "))) + entry->linux_loc = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "initrd "))) + entry->initrd = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "efi "))) + entry->efi = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "devicetree "))) + entry->devicetree = l + strspn(l, WHITESPACE); + else + continue; + } + + /* not interested in EFI programs */ + if (!entry->linux_loc) { + i--; + continue; + } + + if (&entry->node != rbtree_insert(&entry->node, tree)) { + /* already an entry with the same version string */ + } + } + + return r; +} + +int kernel_load(bool overwrite) { + int r = -ENOTSUP; + +/* only x86_64 and x32 in 3.18 */ +#ifdef __NR_kexec_file_load + if (!overwrite && !kexec_loaded()) { + struct utsname u; + char vmlinuz[PATH_MAX+1], initrd[PATH_MAX+1], *cmdline = NULL; + _cleanup_close_ int vmlinuz_fd = -1, initrd_fd = -1; + struct rbtree tree; + struct BootSpec *c; + struct rbtree_node *n; + int flags = 0; + + r = uname(&u); + if (r < 0) + return -errno; + + r = kernel_bootloaderspec_readconf(&tree); + if (r < 0) + return r; + + n = rbtree_first(&tree); + if (!n) + return -ENOENT; + c = rbtree_container_of(n, struct BootSpec, node); + + strcpy(vmlinuz, BOOTSPECDIR); + strncat(vmlinuz, c->linux_loc, PATH_MAX - strlen(vmlinuz)); + cmdline = c->options; + + vmlinuz_fd = open(vmlinuz, O_RDONLY); + if (vmlinuz_fd < 0) + return -errno; + + if (c->initrd) { + strcpy(initrd, BOOTSPECDIR); + strncat(initrd, c->initrd, PATH_MAX - strlen(initrd)); + initrd_fd = open(initrd, O_RDONLY); + if (initrd_fd < 0) + return -errno; + } else { + flags |= KEXEC_FILE_NO_INITRAMFS; + initrd_fd = -1; + } + + log_info("kexec: kexec -l %s --initrd=%s --command-line=%s", vmlinuz, initrd, cmdline); + + r = syscall(__NR_kexec_file_load, vmlinuz_fd, initrd_fd, cmdline, strlen(cmdline), flags); + if (r < 0) + return -errno; + + /* free tree */ + c = NULL; + for (n = rbtree_first(&tree); n; n = rbtree_next(&c->node)) { + bootspec_free(c); + c = rbtree_container_of(n, struct BootSpec, node); + } + } else + r = 0; +#endif + return r; +} diff --git a/src/systemctl/bootspec.h b/src/systemctl/bootspec.h new file mode 100644 index 0000000..19e2901 --- /dev/null +++ b/src/systemctl/bootspec.h @@ -0,0 +1,54 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Shawn Landden + + 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 <stdbool.h> +#include <errno.h> + +#include "sd-id128.h" +#include "rbtree.h" + +#define BOOTSPECDIR "/boot/loader/" +#define BOOTENTRIESDIR BOOTSPECDIR"entries/" + +struct BootSpec { + /* The others are just pointers into malloc()ed conf */ + char *conf; + + struct rbtree_node node; + + char *title; + char *version; + sd_id128_t machine_id; + char *options; + char *linux_loc; /* linux is a reserved keyword with gcc! (and clang!) + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65128 */ + char *initrd; + char *efi; + char *devicetree; +}; + +void bootspec_free(struct BootSpec *l); + +/* returns a populated rbtree of BootSpec, which has been sorted via treesort */ +int kernel_bootloaderspec_readconf(struct rbtree *tree); +int kernel_load(bool overwrite); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 9b08313..e1f7af9 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -74,6 +74,7 @@ #include "bus-common-errors.h" #include "mkdir.h" #include "dropin.h" +#include "bootspec.h" static char **arg_types = NULL; static char **arg_states = NULL; @@ -228,6 +229,29 @@ static int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) return EXIT_FAILURE; } +static int list_kernels(sd_bus *bus, char **args) { + int r; + struct rbtree tree; + struct BootSpec *b = NULL; + struct rbtree_node *n; + + r = kernel_bootloaderspec_readconf(&tree); + if (r < 0) { + log_error_errno(r, "Failed to enumerate boot entries: %m"); + return r; + } + + for (n = rbtree_first(&tree); n; n = rbtree_next(&b->node)) { + /* free prev (or NULL) */ + bootspec_free(b); + + b = rbtree_container_of(n, struct BootSpec, node); + printf("%s - %s\n", b->version, b->title); + } + + return 0; +} + static void warn_wall(enum action a) { static const char *table[_ACTION_MAX] = { [ACTION_HALT] = "The system is going down for system halt NOW!", @@ -6960,6 +6984,7 @@ static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { } bus; } verbs[] = { { "list-units", MORE, 0, list_units }, + { "list-kernels", EQUAL, 1, list_kernels }, { "list-unit-files", MORE, 1, list_unit_files, NOBUS }, { "list-sockets", MORE, 1, list_sockets }, { "list-timers", MORE, 1, list_timers }, @@ -7371,10 +7396,16 @@ int main(int argc, char*argv[]) { r = systemctl_main(bus, argc, argv, r); break; + case ACTION_KEXEC: + r = kernel_load(false); + if (r < 0) { + log_error_errno(r, "Failed to load kernel: %m"); + goto finish; + } + /* fall through */ case ACTION_HALT: case ACTION_POWEROFF: case ACTION_REBOOT: - case ACTION_KEXEC: r = halt_main(bus); break; -- 2.2.1.209.g41e5f3a _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel