Still use helper when Xen Dom0, to avoid duplicating some hairy code. I think the rbtree version was far more understandable as greedy_realloc0() is very messy to do correctly.
Future: generate BootLoaderSpec files for other kernel install locations v2: support specifying the kernel version support appending to the kernel cmdline some docs support double force with kexec allow rpmvercmp() to compare against NULL v3: fix append kernel cmdline where no kernel version given v4: remove rbtree respond to review Take fopenat() from lsof --- Makefile.am | 4 +- TODO | 3 - man/systemctl.xml | 15 ++- src/core/shutdown.c | 28 ++++-- src/shared/fileio.c | 59 ++++++++++++ src/shared/fileio.h | 2 + src/shared/missing.h | 11 +++ src/shared/rpmvercmp.c | 12 ++- src/shared/strv.c | 9 +- src/systemctl/bootspec.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++ src/systemctl/bootspec.h | 51 ++++++++++ src/systemctl/systemctl.c | 57 +++++++++++- 12 files changed, 459 insertions(+), 22 deletions(-) create mode 100644 src/systemctl/bootspec.c create mode 100644 src/systemctl/bootspec.h diff --git a/Makefile.am b/Makefile.am index bba5353..4285146 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2742,7 +2742,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 1f5bfaa..15b0c82 100644 --- a/TODO +++ b/TODO @@ -76,9 +76,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/man/systemctl.xml b/man/systemctl.xml index 338c1d3..c550339 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -607,6 +607,15 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service </varlistentry> <varlistentry> + <term><command>list-kernels</command></term> + + <listitem> + <para>List kernels ordered by version. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><command>start <replaceable>PATTERN</replaceable>...</command></term> <listitem> @@ -1550,7 +1559,7 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service </varlistentry> <varlistentry> - <term><command>kexec</command></term> + <term><command>kexec <optional><replaceable>VERSION</replaceable></optional><optional><replaceable>KERNEL-ARG</replaceable>...</optional></command></term> <listitem> <para>Shut down and reboot the system via kexec. This is @@ -1560,6 +1569,10 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service services is skipped, however all processes are killed and all file systems are unmounted or mounted read-only, immediately followed by the reboot.</para> + + <para>Also allows specifying the version and optionally + extra kernel parameters to append. If no version is specified + then the most recent kernel is booted.</para> </listitem> </varlistentry> diff --git a/src/core/shutdown.c b/src/core/shutdown.c index 70a461e..a3d5393 100644 --- a/src/core/shutdown.c +++ b/src/core/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/reboot.h> #include <linux/reboot.h> @@ -179,9 +180,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; @@ -200,8 +205,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; @@ -341,11 +344,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) @@ -362,7 +368,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/fileio.c b/src/shared/fileio.c index ff6b1a7..b2ba33d 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -815,3 +815,62 @@ int get_status_field(const char *filename, const char *pattern, char **field) { return 0; } + +/* + * Copyright 1997 Purdue Research Foundation, West Lafayette, Indiana + * 47907. All rights reserved. + * + * Written by Victor A. Abell + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * + * 1. Neither the authors nor Purdue University are responsible for any + * consequences of the use of this software. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Credit to the authors and Purdue + * University must appear in documentation and sources. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 4. This notice may not be removed or altered. + */ + +static int fopen_to_open(char *m) { + int flags; + + if (strchr(m, '+')) + flags = O_RDWR; + else if (m[0] == 'r') + flags = O_RDONLY; + else if (m[0] == 'w' || m[0] == 'a') + flags = O_WRONLY; + else + return(0); + + if (m[0] == 'a') + flags |= O_APPEND|O_CREAT; + if (m[0] == 'w') + flags |= O_TRUNC|O_CREAT; + + flags |= O_NONBLOCK; + + return flags; +} + +FILE *fopenat(int dirfd, const char *path, const char *mode) { + int fd; + int flags = fopen_to_open(mode); + + fd = openat(dirfd, path, flags); + if (fd < 0) + return NULL; + + return fdopen(fd, mode); +} diff --git a/src/shared/fileio.h b/src/shared/fileio.h index 5ae51c1..597903c 100644 --- a/src/shared/fileio.h +++ b/src/shared/fileio.h @@ -43,3 +43,5 @@ int write_env_file(const char *fname, char **l); int executable_is_script(const char *path, char **interpreter); int get_status_field(const char *filename, const char *pattern, char **field); + +FILE *fopenat(int dirfd, const char *path, const char *mode); diff --git a/src/shared/missing.h b/src/shared/missing.h index e72631e..0753be3 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> @@ -773,3 +774,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/rpmvercmp.c b/src/shared/rpmvercmp.c index c69c2e3..09cfd97 100644 --- a/src/shared/rpmvercmp.c +++ b/src/shared/rpmvercmp.c @@ -13,8 +13,16 @@ /* return 1: a is newer than b */ /* 0: a and b are the same version */ /* -1: b is newer than a */ -int rpmvercmp(const char * a, const char * b) -{ +int rpmvercmp(const char * a, const char * b) { + if (!a) { + if (b) + return -1; + else + return 0; + } + if (!b) + return 1; + /* easy comparison to see if versions are identical */ if (streq_ptr(a, b)) return 0; diff --git a/src/shared/strv.c b/src/shared/strv.c index ee45ad1..ed28b95 100644 --- a/src/shared/strv.c +++ b/src/shared/strv.c @@ -81,7 +81,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..f8d1bc5 --- /dev/null +++ b/src/systemctl/bootspec.c @@ -0,0 +1,230 @@ +/*-*- 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 void *left, const void *right) { + const struct BootSpec *l = left, + *r = right; + + return rpmvercmp(l->version, r->version); +} + +int bootspec_getkernels(struct BootSpec **ret, size_t *cl) { + int r = 0; + struct BootSpec *c; + _cleanup_closedir_ DIR *d; + struct dirent *dent; + size_t i = 1, ii = 2; + _cleanup_close_ int midfd = -1; + ssize_t ss; + char mid_char[32 + 1]; + sd_id128_t machine_id; + + midfd = open("/etc/machine-id", O_RDONLY); + if (midfd < 0) + return -errno; + ss = read(midfd, &mid_char, sizeof(mid_char)); + if (ss < 0) + return -errno; + else if (ss != 33) + return -EBADF; + if (mid_char[32] == '\n') + mid_char[32] = '\0'; + else + return -EBADF; + r = sd_id128_from_string(mid_char, &machine_id); + if (r < 0) + return r; + + c = new0(struct BootSpec, i + 1); + if (!c) + return -ENOMEM; + + d = opendir(BOOTENTRIESDIR); + if (!d) { + if (errno == ENOENT) + return 0; + else + return -errno; + } + + for (dent = readdir(d); dent != NULL; dent = readdir(d)) { + struct BootSpec *bs; + char *m, *l, *k; + _cleanup_fclose_ FILE *f = NULL; + + if (!endswith(dent->d_name, ".conf")) + continue; + + c = greedy_realloc0((void **)&c, &ii, i + 2, sizeof(struct BootSpec)); + if (!c) + return -ENOMEM; + bs = &c[i - 1]; + + f = fopenat(dirfd(d), dent->d_name, "r"); + if (!f) + return -errno; + + r = read_full_stream(f, &bs->conf, NULL); + if (r < 0) + return r; + + for (m = bs->conf; ; m = k + 1) { + if (m[0] == '#') + continue; + + k = strchr(m, '\n'); + + if (k) + *k = '\0'; + else + break; + + if ((l = startswith(m, "title "))) + bs->title = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "version "))) + bs->version = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "machine-id "))) + (void)sd_id128_from_string(l + strspn(l, WHITESPACE), &bs->machine_id); + else if ((l = startswith(m, "options "))) + bs->options = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "linux "))) + bs->linux_loc = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "initrd "))) + bs->initrd = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "efi "))) + bs->efi = l + strspn(l, WHITESPACE); + else if ((l = startswith(m, "devicetree "))) + bs->devicetree = l + strspn(l, WHITESPACE); + else + continue; + } + + /* not interested in EFI programs or kernels for other roots */ + if (!bs->linux_loc || (memcmp(&bs->machine_id, &machine_id, 16) != 0)) { + free(bs->conf); + zero(bs); + continue; + } + + i++; + } + + i--; + + qsort(c, i, sizeof(struct BootSpec), bootspec_cmp); + *cl = i; + *ret = c; + return r; +} + +int kernel_load(char *version, char *append_cmdline, 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 = NULL, *initrd = NULL, *cmdline = NULL; + _cleanup_close_ int vmlinuz_fd = -1, initrd_fd = -1; + struct BootSpec *a = NULL; + size_t al = 0; + struct BootSpec *c; + + r = uname(&u); + if (r < 0) + return -errno; + + r = bootspec_getkernels(&a, &al); + if (r < 0) + return r; + + if (version) { + struct BootSpec q; + + q.version = version; + c = bsearch(&q, a, al, sizeof(struct BootSpec), bootspec_cmp); + if (!c) + return -ENOENT; + } else + c = &a[0]; + + vmlinuz = strjoina(BOOTSPECDIR, c->linux_loc); + cmdline = strjoina(c->options, " ", append_cmdline); + + vmlinuz_fd = open(vmlinuz, O_RDONLY); + if (vmlinuz_fd < 0) + return -errno; + + if (c->initrd) { + initrd = strjoina(BOOTSPECDIR, c->initrd); + initrd_fd = open(initrd, O_RDONLY); + if (initrd_fd < 0) + return -errno; + } + + if (initrd_fd < 0) + log_info("kexec: kexec -l %s --command-line=%s", + vmlinuz, + cmdline); + else + 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), initrd_fd < 0 ? KEXEC_FILE_NO_INITRAMFS : 0); + if (r < 0) + return -errno; + + /* free array */ + for (unsigned i=0;i<al;i++) + free((a[i]).conf); + + free(a); + } else + r = 0; +#endif + return r; +} diff --git a/src/systemctl/bootspec.h b/src/systemctl/bootspec.h new file mode 100644 index 0000000..53a16b9 --- /dev/null +++ b/src/systemctl/bootspec.h @@ -0,0 +1,51 @@ +/*-*- 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" + +#define BOOTSPECDIR "/boot/loader/" +#define BOOTENTRIESDIR BOOTSPECDIR"entries/" + +struct BootSpec { + /* The others are just pointers into malloc()ed conf */ + char *conf; + + 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 an array of struct BootSpec */ +int bootspec_getkernels(struct BootSpec **a, size_t *l); +int kernel_load(char *version, char *append_cmdline, bool overwrite); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 10213af..62556b4 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -68,6 +68,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; @@ -222,6 +223,27 @@ 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; + _cleanup_free_ struct BootSpec *a = NULL; + size_t al; + struct BootSpec *b; + + r = bootspec_getkernels(&a, &al); + if (r < 0) { + log_error_errno(r, "Failed to enumerate boot entries: %m"); + return r; + } + + for (unsigned i=0;i<al;i++) { + b = &a[i]; + printf("%s - %s\n", b->version, b->title); + free(b->conf); + } + + 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!", @@ -2934,10 +2956,24 @@ static int start_special(sd_bus *bus, char **args) { return r; } + if (a == ACTION_KEXEC) { + char *cmd_append = NULL; + + if (args[1]) + cmd_append = strv_join(&args[2], " "); + + r = kernel_load(args[1], cmd_append, false); + if (r < 0) { + log_error_errno(r, "Failed to load kernel: %m"); + return r; + } + } + if (arg_force >= 2 && (a == ACTION_HALT || a == ACTION_POWEROFF || - a == ACTION_REBOOT)) + a == ACTION_REBOOT || + a == ACTION_KEXEC)) return halt_now(a); if (arg_force >= 1 && @@ -6029,6 +6065,7 @@ static void systemctl_help(void) { " daemon-reload Reload systemd manager configuration\n" " daemon-reexec Reexecute systemd manager\n\n" "System Commands:\n" + " list-kernels List BootLoaderSpec kernels highest version first\n" " is-system-running Check whether system is fully running\n" " default Enter system default mode\n" " rescue Enter system rescue mode\n" @@ -6036,7 +6073,7 @@ static void systemctl_help(void) { " halt Shut down and halt the system\n" " poweroff Shut down and power-off the system\n" " reboot [ARG] Shut down and reboot the system\n" - " kexec Shut down and reboot the system with kexec\n" + " kexec [VERSION] [CMDLINE...] Shut down and reboot the system with kexec\n" " exit Request user instance exit\n" " switch-root ROOT [INIT] Change to a different root file system\n" " suspend Suspend the system\n" @@ -6957,6 +6994,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 }, @@ -6995,7 +7033,7 @@ static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { { "halt", EQUAL, 1, start_special, FORCE }, { "poweroff", EQUAL, 1, start_special, FORCE }, { "reboot", MORE, 1, start_special, FORCE }, - { "kexec", EQUAL, 1, start_special }, + { "kexec", MORE, 1, start_special }, { "suspend", EQUAL, 1, start_special }, { "hibernate", EQUAL, 1, start_special }, { "hybrid-sleep", EQUAL, 1, start_special }, @@ -7213,6 +7251,11 @@ static int halt_now(enum action a) { reboot(RB_POWER_OFF); return -errno; + case ACTION_KEXEC: + log_info("Rebooting via kexec."); + reboot(LINUX_REBOOT_CMD_KEXEC); + /* fall through */ + case ACTION_REBOOT: { _cleanup_free_ char *param = NULL; @@ -7368,10 +7411,16 @@ int main(int argc, char*argv[]) { r = systemctl_main(bus, argc, argv, r); break; + case ACTION_KEXEC: + r = kernel_load(NULL, NULL, 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