[systemd-devel] [PATCH] shutdown: add kexec loading, avoid calling `kexec` binary unnessecarily

Shawn Landden shawn at churchofgit.com
Wed Mar 11 17:22:18 PDT 2015


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



More information about the systemd-devel mailing list