]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
systemctl: add --kernel-cmdline-reuse option
authorLuca Boccassi <luca.boccassi@gmail.com>
Sat, 20 Jun 2026 00:05:00 +0000 (01:05 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Mon, 22 Jun 2026 13:02:52 +0000 (14:02 +0100)
kexec-tools has a --reuse-cmdline option which is very convenient
when doing a lot of reboots, add the same to systemctl.
Dedup options, letting the last one wins in case of duplicates,
so that 'systemctl kexec --reuse-cmdline' can be chained many times
without continuosly expanding the cmdline with duplicates from
the boot entry.

man/systemctl.xml
shell-completion/bash/systemctl.in
shell-completion/zsh/_systemctl.in
src/systemctl/systemctl-start-special.c
src/systemctl/systemctl.c
src/systemctl/systemctl.h

index ce485aee14fa75f8fd0dc9e8c6ec4ab749fc6679..122d58d66d0409cf03ea16a11920a2a1c197c4c6 100644 (file)
@@ -2795,14 +2795,43 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
           <para>When used with <command>kexec</command>, append the specified string to the kernel command
           line options of the kexec kernel. The kernel command line is taken from the boot loader entry of
           the currently booted kernel (as selected automatically when no kexec kernel is preloaded, see
-          <command>kexec</command> above). This string is appended verbatim, separated from the existing
-          options by a single space. <command>systemctl kexec</command> will fail if this option is specified
-          when a kexec kernel is already loaded.</para>
+          <command>kexec</command> above). This string is appended, separated from the existing
+          options by a single space. This option may be specified more than once, in which case the
+          strings are appended in the order given. <command>systemctl kexec</command> will fail if this
+          option is specified when a kexec kernel is already loaded.</para>
+
+          <para>The kexec kernel command line is assembled in a fixed order, independent of the order in
+          which the options are specified on the command line: first the command line from the boot loader
+          entry, then the command line of the currently running kernel (if
+          <option>--kernel-cmdline-reuse</option> is used), and finally the strings specified with
+          <option>--kernel-cmdline=</option>. Afterwards, arguments that are exactly identical and appear
+          more than once are removed, keeping the last occurrence. Note that only byte-for-byte identical
+          arguments are deduplicated: for example <literal>foo=bar</literal> and
+          <literal>foo=quux</literal> are both kept, as they are not identical (the latter takes effect, as
+          the kernel uses the last assignment of a given parameter).</para>
 
           <xi:include href="version-info.xml" xpointer="v261"/>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--kernel-cmdline-reuse</option></term>
+
+        <listitem>
+          <para>Like <option>--kernel-cmdline=</option>, but instead of taking the string to append as an
+          argument, the command line of the currently running kernel is reused. This is useful to boot the
+          kexec kernel with the same command line options the running system was booted with, including any
+          modifications applied at boot time.
+          As with <option>--kernel-cmdline=</option>, the reused command line is added to the command line
+          taken from the boot loader entry, and <command>systemctl kexec</command> will fail if this option
+          is specified when a kexec kernel is already loaded. This option may be combined with
+          <option>--kernel-cmdline=</option>; see <option>--kernel-cmdline=</option> above for the order in
+          which the various command line sources are combined and deduplicated.</para>
+
+          <xi:include href="version-info.xml" xpointer="v262"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--plain</option></term>
 
index 748126ea14a74f0ae86f41fb48ea442eaefcd463..319ca134ead7d99f7e160d965c954a130bcfc473 100644 (file)
@@ -134,7 +134,7 @@ _systemctl () {
                       --help -h --no-ask-password --no-block --legend=no --no-pager --no-reload --no-wall --now
                       --quiet -q --system --user --version --runtime --recursive -r --firmware-setup
                       --show-types --plain --failed --value --fail --dry-run --wait --no-warn --with-dependencies
-                      --show-transaction -T --mkdir --read-only'
+                      --show-transaction -T --mkdir --read-only --kernel-cmdline-reuse'
         [ARG]='--host -H --kill-whom --property -p -P --signal -s --type -t --state --job-mode --root
                --preset-mode -n --lines -o --output -M --machine --message --timestamp --check-inhibitors --what
                --image --boot-loader-menu --boot-loader-entry --reboot-argument --kernel-cmdline --drop-in --when'
index ca8b218c2cecc4a59dc7eb1f1e00c427f4780dd4..39791c0a39d04eee258b6d9f4ad1780bc7272de0 100644 (file)
@@ -543,4 +543,5 @@ _arguments -s \
     '--failed[Show failed units]' \
     '--timestamp=[Change format of printed timestamps]:style:_systemctl_timestamp' \
     '--when=[Schedule halt/power-off/reboot/kexec action after a certain timestamp]:timestamp:(show cancel auto)' \
+    '--kernel-cmdline-reuse[Reuse the current kernel command line when loading the kexec kernel]' \
     '*::systemctl command:_systemctl_commands'
index a8702ad438edfbbffcb1aef2fcb3a5f125987d14..ab0285289bc29c8d7f78dbc45b465f5aeae74a44 100644 (file)
@@ -9,6 +9,7 @@
 #include "bus-error.h"
 #include "bus-locator.h"
 #include "efivars.h"
+#include "extract-word.h"
 #include "fd-util.h"
 #include "log.h"
 #include "parse-util.h"
 #include "systemctl-trivial-method.h"
 #include "systemctl-util.h"
 
+static int deduplicate_kernel_cmdline(char **cmdline) {
+        int r;
+
+        assert(cmdline);
+
+        /* Split, reverse, deduplicate, reverse again: the last occurrence of each identical argument wins. */
+
+        _cleanup_strv_free_ char **l = NULL;
+        r = strv_split_full(&l, *cmdline, /* separators= */ NULL,
+                            EXTRACT_KEEP_QUOTE | EXTRACT_RETAIN_ESCAPE | EXTRACT_RELAX);
+        if (r < 0)
+                return r;
+
+        strv_reverse(l);
+        strv_uniq(l);
+        strv_reverse(l);
+
+        _cleanup_free_ char *joined = strv_join(l, " ");
+        if (!joined)
+                return -ENOMEM;
+
+        free_and_replace(*cmdline, joined);
+        return 0;
+}
+
 static int load_kexec_kernel(void) {
         int r;
 
         if (kexec_loaded()) {
-                if (arg_kernel_cmdline)
+                if (arg_kernel_cmdline || arg_reuse_kernel_cmdline)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "--kernel-cmdline= specified but kexec kernel already loaded");
+                                               "--kernel-cmdline=/--kernel-cmdline-reuse specified but kexec kernel already loaded");
                 log_debug("Kexec kernel already loaded.");
                 return 0;
         }
@@ -81,9 +107,19 @@ static int load_kexec_kernel(void) {
         if (!options)
                 return log_oom();
 
+        /* Assemble in a fixed order: UKI -> current cmdline -> appended cmdline, and dedup at the end */
+        if (!isempty(arg_reuse_kernel_cmdline) && !strextend_with_separator(&options, " ", arg_reuse_kernel_cmdline))
+                return log_oom();
+
         if (!isempty(arg_kernel_cmdline) && !strextend_with_separator(&options, " ", arg_kernel_cmdline))
                 return log_oom();
 
+        if (!isempty(arg_reuse_kernel_cmdline) || !isempty(arg_kernel_cmdline)) {
+                r = deduplicate_kernel_cmdline(&options);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to deduplicate kernel command line: %m");
+        }
+
         log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
                  "%s %s kernel=\"%s\" cmdline=\"%s\"%s%s%s",
                  arg_dry_run ? "Would call" : "Calling",
index 99244de7674c2eb5a5403aef33ca467aaa174cfb..ef90e62a0008ce60b96e7db27df6ddfbf24e3761 100644 (file)
@@ -19,6 +19,7 @@
 #include "parse-argument.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "proc-cmdline.h"
 #include "static-destruct.h"
 #include "string-table.h"
 #include "string-util.h"
@@ -71,6 +72,7 @@ usec_t arg_when = 0;
 bool arg_stdin = false;
 const char *arg_reboot_argument = NULL;
 char *arg_kernel_cmdline = NULL;
+char *arg_reuse_kernel_cmdline = NULL;
 enum action arg_action = ACTION_SYSTEMCTL;
 BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
 const char *arg_host = NULL;
@@ -102,6 +104,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_reboot_argument, unsetp);
 STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_reuse_kernel_cmdline, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_host, unsetp);
 STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp);
 STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep);
@@ -764,10 +767,31 @@ static int systemctl_parse_argv(int argc, char *argv[], int log_level_shift, cha
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                        "--kernel-cmdline= argument contains invalid characters: %s", opts.arg);
 
-                        r = free_and_strdup_warn(&arg_kernel_cmdline, opts.arg);
+                        if (!strextend_with_separator(&arg_kernel_cmdline, " ", opts.arg))
+                                return log_oom();
+                        break;
+
+                OPTION_LONG("kernel-cmdline-reuse", NULL,
+                            "Like --kernel-cmdline=, but reuse the current kernel command line"): {
+                        _cleanup_free_ char *cmdline = NULL;
+
+                        r = proc_cmdline(&cmdline);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to read current kernel command line: %m");
+
+                        const char *stripped = empty_to_null(strstrip(cmdline));
+                        if (!stripped)
+                                break;
+
+                        if (!string_is_safe(stripped, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Current kernel command line contains invalid characters: %s", stripped);
+
+                        r = free_and_strdup_warn(&arg_reuse_kernel_cmdline, stripped);
                         if (r < 0)
                                 return r;
                         break;
+                }
 
                 OPTION_LONG("plain", NULL, "Print unit dependencies as a list instead of a tree"):
                         arg_plain = true;
index e157ab12f067fd8795f321f269d9a472f8cae8e4..8f05bdb46e5f41ebdf7b2b5b9fbe4b302548700f 100644 (file)
@@ -77,6 +77,7 @@ extern usec_t arg_when;
 extern bool arg_stdin;
 extern const char *arg_reboot_argument;
 extern char *arg_kernel_cmdline;
+extern char *arg_reuse_kernel_cmdline;
 extern enum action arg_action;
 extern BusTransport arg_transport;
 extern const char *arg_host;