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.
Take fopenat() from lsof. Add opendirat() Future: generate BootLoaderSpec files for other kernel install locations v5: fix syscall invocation from draft version v6: usr either /boot/efi or /boot --- Makefile.am | 4 +- TODO | 3 - man/systemctl.xml | 15 ++- src/core/shutdown.c | 30 ++++-- src/shared/fileio.c | 69 +++++++++++++ src/shared/fileio.h | 5 + src/shared/missing.h | 11 +++ src/shared/rpmvercmp.c | 26 +++-- src/shared/strv.c | 9 +- src/systemctl/bootspec.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++ src/systemctl/bootspec.h | 48 +++++++++ src/systemctl/systemctl.c | 57 ++++++++++- 12 files changed, 495 insertions(+), 29 deletions(-) create mode 100644 src/systemctl/bootspec.c create mode 100644 src/systemctl/bootspec.h diff --git a/Makefile.am b/Makefile.am index 353a7de..20a6484 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 6180077..9ba7be0 100644 --- a/TODO +++ b/TODO @@ -86,9 +86,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..616a70a 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> @@ -173,15 +174,21 @@ int main(int argc, char *argv[]) { goto error; } + in_container = !!detect_virtualization(NULL); + if (streq(arg_verb, "reboot")) cmd = RB_AUTOBOOT; else if (streq(arg_verb, "poweroff")) 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 +207,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 +346,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 +370,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..0b12dce 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -815,3 +815,72 @@ int get_status_field(const char *filename, const char *pattern, char **field) { return 0; } + +DIR *opendirat(int dirfd, const char *name) { + int fd; + + fd = openat(dirfd, name, O_DIRECTORY); + if (fd < 0) + return NULL; /*errno is set */ + + return fdopendir(fd); +} + +/* + * 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(const 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..df174e2 100644 --- a/src/shared/fileio.h +++ b/src/shared/fileio.h @@ -22,6 +22,8 @@ ***/ #include <stddef.h> #include <stdio.h> +#include <sys/types.h> +#include <dirent.h> #include "macro.h" @@ -43,3 +45,6 @@ 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); + +DIR *opendirat(int dirfd, const char *name); +FILE *fopenat(int dirfd, const char *path, const char *mode); diff --git a/src/shared/missing.h b/src/shared/missing.h index 802b495..4416a51 100644 --- a/src/shared/missing.h +++ b/src/shared/missing.h @@ -36,6 +36,7 @@ #include <linux/audit.h> #include <linux/capability.h> #include <linux/neighbour.h> +#include <linux/kexec.h> #ifdef HAVE_AUDIT #include <libaudit.h> @@ -789,3 +790,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..1649929 100644 --- a/src/shared/rpmvercmp.c +++ b/src/shared/rpmvercmp.c @@ -13,18 +13,26 @@ /* 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) { + char oldch1, oldch2; + char abuf[strlen(a)+1], bbuf[strlen(b)+1]; + char *str1 = abuf, *str2 = bbuf; + char * one, * two; + int rc; + int isnum; + + 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; - char oldch1, oldch2; - char abuf[strlen(a)+1], bbuf[strlen(b)+1]; - char *str1 = abuf, *str2 = bbuf; - char * one, * two; - int rc; - int isnum; - strcpy(str1, a); strcpy(str2, b); 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..8194eaf --- /dev/null +++ b/src/systemctl/bootspec.c @@ -0,0 +1,247 @@ +/*-*- 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; + + /* reverse sort to put highest version first */ + return rpmvercmp(r->version, l->version); +} + +int bootspec_getkernels(struct BootSpec **ret, size_t *cl, int *bootdir_fd_ret) { + int r = 0; + struct BootSpec *c; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *dent; + size_t i = 1, ii = 2; + _cleanup_close_ int midfd = -1; + int bootdir_fd = -1; + ssize_t ss; + char mid_char[32 + 1]; + sd_id128_t machine_id; + + assert(ret); + assert(cl); + + /* we assume that the EFI partition is mounted + * and at /boot/efi or /boot + */ + errno = 0; + if (access("/boot/efi/loader/entries", R_OK|X_OK) == 0) + bootdir_fd = open("/boot/efi", O_DIRECTORY); + else if (access("/boot/loader/entries", R_OK|X_OK) == 0) + bootdir_fd = open("/boot", O_DIRECTORY); + if (bootdir_fd < 0) + return IN_SET(errno, 0, ENOENT) ? 0 : -errno; + + 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 = opendirat(bootdir_fd, "loader/entries"); + 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 || !sd_id128_equal(bs->machine_id, machine_id)) { + free(bs->conf); + zero(bs); + continue; + } + + i++; + } + + i--; + + qsort(c, i, sizeof(struct BootSpec), bootspec_cmp); + *cl = i; + *ret = c; + if (bootdir_fd_ret) + *bootdir_fd_ret = bootdir_fd; + + 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 *cmdline = NULL; + _cleanup_close_ int vmlinuz_fd = -1, initrd_fd = -1, bootdir = -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, &bootdir); + 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]; + + cmdline = strjoina(c->options, " ", append_cmdline); + + vmlinuz_fd = openat(bootdir, c->linux_loc, O_RDONLY); + if (vmlinuz_fd < 0) + return -errno; + + if (c->initrd) { + initrd_fd = openat(bootdir, c->initrd, O_RDONLY); + if (initrd_fd < 0) + return -errno; + } + + if (initrd_fd < 0) + log_info("kexec: kexec -l %s --command-line=\"%s\"", + c->linux_loc, + cmdline); + else + log_info("kexec: kexec -l %s --initrd=%s --command-line=\"%s\"", + c->linux_loc, + c->initrd, + cmdline); + + r = syscall(__NR_kexec_file_load, vmlinuz_fd, initrd_fd, + (unsigned long) strlen(cmdline) + 1, 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..d8893a0 --- /dev/null +++ b/src/systemctl/bootspec.h @@ -0,0 +1,48 @@ +/*-*- 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" + +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 *bootdir_fd_ret); +int kernel_load(char *version, char *append_cmdline, bool overwrite); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 41f7b9f..1f283c8 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 = 0; + struct BootSpec *b; + + r = bootspec_getkernels(&a, &al, NULL); + 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 && @@ -6038,6 +6074,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" @@ -6045,7 +6082,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" @@ -6966,6 +7003,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 }, @@ -7004,7 +7042,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 }, @@ -7222,6 +7260,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; @@ -7377,10 +7420,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