summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2018-11-28 22:40:55 +0100
committerGitHub <noreply@github.com>2018-11-28 22:40:55 +0100
commit8b4e51a60e1a2217d3bdd0c917f5837cee8703b4 (patch)
tree31d47e2aa68b50eb4f330996e1a8bc560ec7cf62
parent50ae773f85216875033ae47362bb906cf5f5b2d8 (diff)
parent7154bebb3e60e7eb73e2c1066acdbc5277b8d7aa (diff)
downloadsystemd-8b4e51a60e1a2217d3bdd0c917f5837cee8703b4.tar.gz
Merge pull request #10797 from poettering/run-generator
add new "systemd-run-generator" for running arbitrary commands from the kernel command line as system services using the "systemd.run=" kernel command line switch
-rw-r--r--TODO2
-rw-r--r--docs/TRANSIENT-SETTINGS.md2
-rw-r--r--man/kernel-command-line.xml11
-rw-r--r--man/rules/meson.build1
-rw-r--r--man/systemd-run-generator.xml82
-rw-r--r--man/systemd.unit.xml24
-rw-r--r--meson.build8
-rw-r--r--src/core/dbus-unit.c40
-rw-r--r--src/core/emergency-action.c9
-rw-r--r--src/core/emergency-action.h2
-rw-r--r--src/core/job.c2
-rw-r--r--src/core/load-fragment-gperf.gperf.m42
-rw-r--r--src/core/load-fragment.c35
-rw-r--r--src/core/load-fragment.h1
-rw-r--r--src/core/manager.c2
-rw-r--r--src/core/service.c16
-rw-r--r--src/core/unit.c59
-rw-r--r--src/core/unit.h13
-rw-r--r--src/run-generator/run-generator.c147
-rw-r--r--src/shared/bus-unit-util.c19
20 files changed, 463 insertions, 14 deletions
diff --git a/TODO b/TODO
index ae1bedcd29..97681c421f 100644
--- a/TODO
+++ b/TODO
@@ -655,8 +655,6 @@ Features:
* add a pam module that on password changes updates any LUKS slot where the password matches
-* maybe add a generator that looks for "systemd.run=" on the kernel cmdline for container usercases...
-
* test/:
- add unit tests for config_parse_device_allow()
diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md
index 9a1a0a27f1..89a185b527 100644
--- a/docs/TRANSIENT-SETTINGS.md
+++ b/docs/TRANSIENT-SETTINGS.md
@@ -45,6 +45,8 @@ Most generic unit settings are available for transient units.
✓ StartLimitAction=ACTION
✓ FailureAction=
✓ SuccessAction=
+✓ FailureActionExitStatus=
+✓ SuccessActionExitStatus=
✓ AddRef=
✓ RebootArgument=STRING
✓ ConditionPathExists=
diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml
index 43b3a3667e..7e4b51eb9f 100644
--- a/man/kernel-command-line.xml
+++ b/man/kernel-command-line.xml
@@ -92,6 +92,17 @@
</varlistentry>
<varlistentry>
+ <term><varname>systemd.run=</varname></term>
+ <term><varname>systemd.run_success_action=</varname></term>
+ <term><varname>systemd.run_failure_action=</varname></term>
+ <listitem>
+ <para>Additional parameters understood by
+ <citerefentry><refentrytitle>systemd-run-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, to
+ run a command line specified on the kernel command line as system service after booting up.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>systemd.early_core_pattern=</varname></term>
<listitem>
<para>During early boot, the generation of core dump files is disabled until a core dump handler (if any)
diff --git a/man/rules/meson.build b/man/rules/meson.build
index b93ed99713..b091859829 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -737,6 +737,7 @@ manpages = [
'8',
['systemd-rfkill', 'systemd-rfkill.socket'],
'ENABLE_RFKILL'],
+ ['systemd-run-generator', '8', [], ''],
['systemd-run', '1', [], ''],
['systemd-sleep.conf', '5', ['sleep.conf.d'], ''],
['systemd-socket-activate', '1', [], ''],
diff --git a/man/systemd-run-generator.xml b/man/systemd-run-generator.xml
new file mode 100644
index 0000000000..20eb6916eb
--- /dev/null
+++ b/man/systemd-run-generator.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!--
+ SPDX-License-Identifier: LGPL-2.1+
+-->
+<refentry id="systemd-run-generator">
+
+ <refentryinfo>
+ <title>systemd-run-generator</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-run-generator</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-run-generator</refname>
+ <refpurpose>Generator for invoking commands specified on the kernel command line as system service</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/usr/lib/systemd/system-generators/systemd-run-generator</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><filename>systemd-run-generator</filename> is a generator
+ that reads the kernel command line and understands three
+ options:</para>
+
+ <para>If the <option>systemd.run=</option> option is specified and followed by a command line, a unit named
+ <filename>kernel-command-line.service</filename> is generated for it and booted into. The service has
+ <varname>Type=oneshot</varname> set, and has <varname>SuccessAction=exit</varname> and
+ <varname>FailureAction=exit</varname> configured by default, thus ensuring that the system is shut down as soon as
+ the command completes. The exit status of the command line is propagated to the invoking container manager, if
+ this applies (which might propagate this further, to the calling shell — e.g.
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>7</manvolnum></citerefentry> does this). If
+ this option is used multiple times the unit file will contain multiple <varname>ExecStart=</varname> lines, to
+ execute all commands in order. The command is started as regular service, i.e. with
+ <varname>DefaultDependencies=</varname> on. </para>
+
+ <para>Use <option>systemd.run_success_action=</option> and <option>systemd.run_failure_action=</option> to tweak
+ how to react to the process completing. In particular assigning <literal>none</literal> will leave the system
+ running after the command completes. For further details on supported arguments, see
+ <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+ <para><filename>systemd-run-generator</filename> implements
+ <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+
+ <para>Use a command like the following to add a user to the user database inside a container run with
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>7</manvolnum></citerefentry>:</para>
+
+ <programlisting># systemd-nspawn -D mycontainer -b systemd.run='"adduser test"'</programlisting>
+ <para>(Note the requirement for double quoting in the command line above. The first level of quoting ('') is
+ processed and removed by the command shell used to invoke <command>systemd-nspawn</command>. The second level of
+ quoting ("") is propagated to the kernel command line of the container and processed and removed by
+ <command>systemd-run-generator</command>. Both together make sure both words of the specified command line
+ <command>adduser test</command> end up in the generated unit file together and are neither split apart by the
+ command shell nor by the generator.)</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index be77e72290..2c66db854b 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -887,10 +887,26 @@
cause no dirty file systems on reboot (i.e. equivalent to <command>systemctl reboot -f</command>) and
<option>reboot-immediate</option> causes immediate execution of the
<citerefentry><refentrytitle>reboot</refentrytitle><manvolnum>2</manvolnum></citerefentry> system call, which
- might result in data loss. Similarly, <option>poweroff</option>, <option>poweroff-force</option>,
- <option>poweroff-immediate</option> have the effect of powering down the system with similar
- semantics. <option>exit</option> causes the manager to exit following the normal shutdown procedure, and
- <option>exit-force</option> causes it terminate without shutting down services.</para></listitem>
+ might result in data loss (i.e. equivalent to <command>systemctl reboot -ff</command>). Similarly,
+ <option>poweroff</option>, <option>poweroff-force</option>, <option>poweroff-immediate</option> have the effect
+ of powering down the system with similar semantics. <option>exit</option> causes the manager to exit following
+ the normal shutdown procedure, and <option>exit-force</option> causes it terminate without shutting down
+ services. When <option>exit</option> or <option>exit-force</option> is used by default the exit status of the
+ main process of the unit (if this applies) is returned from the service manager. However, this may be overriden
+ with <varname>FailureActionExitStatus=</varname>/<varname>SuccessActionExitStatus=</varname>, see
+ below.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>FailureActionExitStatus=</varname></term>
+ <term><varname>SuccessActionExitStatus=</varname></term>
+
+ <listitem><para>Controls the exit status to propagate back to an invoking container manager (in case of a
+ system service) or service manager (in case of a user manager) when the
+ <varname>FailureAction=</varname>/<varname>SuccessAction=</varname> are set to <option>exit</option> or
+ <option>exit-force</option> and the action is triggered. By default the exit status of the main process of the
+ triggering unit (if this applies) is propagated. Takes a value in the range 0…255 or the empty string to
+ request default behaviour.</para></listitem>
</varlistentry>
<varlistentry>
diff --git a/meson.build b/meson.build
index c2d48f0770..37ae27b4a5 100644
--- a/meson.build
+++ b/meson.build
@@ -1633,6 +1633,14 @@ executable('systemd-debug-generator',
install : true,
install_dir : systemgeneratordir)
+executable('systemd-run-generator',
+ 'src/run-generator/run-generator.c',
+ include_directories : includes,
+ link_with : [libshared],
+ install_rpath : rootlibexecdir,
+ install : true,
+ install_dir : systemgeneratordir)
+
executable('systemd-fstab-generator',
'src/fstab-generator/fstab-generator.c',
'src/core/mount-setup.c',
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index 18e5b8d2df..6d9b559d2c 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -671,7 +671,9 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StartLimitAction", "s", property_get_emergency_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("FailureAction", "s", property_get_emergency_action, offsetof(Unit, failure_action), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("FailureActionExitStatus", "i", bus_property_get_int, offsetof(Unit, failure_action_exit_status), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SuccessAction", "s", property_get_emergency_action, offsetof(Unit, success_action), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("SuccessActionExitStatus", "i", bus_property_get_int, offsetof(Unit, success_action_exit_status), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("InvocationID", "ay", bus_property_get_id128, offsetof(Unit, invocation_id), 0),
SD_BUS_PROPERTY("CollectMode", "s", property_get_collect_mode, offsetof(Unit, collect_mode), 0),
@@ -1374,6 +1376,38 @@ static int bus_set_transient_emergency_action(
return 1;
}
+static int bus_set_transient_exit_status(
+ Unit *u,
+ const char *name,
+ int *p,
+ sd_bus_message *message,
+ UnitWriteFlags flags,
+ sd_bus_error *error) {
+
+ int32_t k;
+ int r;
+
+ assert(p);
+
+ r = sd_bus_message_read(message, "i", &k);
+ if (r < 0)
+ return r;
+
+ if (k > 255)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Exit status must be in range 0…255 or negative.");
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ *p = k < 0 ? -1 : k;
+
+ if (k < 0)
+ unit_write_settingf(u, flags, name, "%s=", name);
+ else
+ unit_write_settingf(u, flags, name, "%s=%i", name, k);
+ }
+
+ return 1;
+}
+
static BUS_DEFINE_SET_TRANSIENT_PARSE(collect_mode, CollectMode, collect_mode_from_string);
static BUS_DEFINE_SET_TRANSIENT_PARSE(job_mode, JobMode, job_mode_from_string);
@@ -1524,6 +1558,12 @@ static int bus_unit_set_transient_property(
if (streq(name, "SuccessAction"))
return bus_set_transient_emergency_action(u, name, &u->success_action, message, flags, error);
+ if (streq(name, "FailureActionExitStatus"))
+ return bus_set_transient_exit_status(u, name, &u->failure_action_exit_status, message, flags, error);
+
+ if (streq(name, "SuccessActionExitStatus"))
+ return bus_set_transient_exit_status(u, name, &u->success_action_exit_status, message, flags, error);
+
if (streq(name, "RebootArgument"))
return bus_set_transient_string(u, name, &u->reboot_arg, message, flags, error);
diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c
index c91d2bf5ff..f98b0de792 100644
--- a/src/core/emergency-action.c
+++ b/src/core/emergency-action.c
@@ -25,6 +25,7 @@ int emergency_action(
EmergencyAction action,
EmergencyActionFlags options,
const char *reboot_arg,
+ int exit_status,
const char *reason) {
assert(m);
@@ -75,6 +76,10 @@ int emergency_action(
break;
case EMERGENCY_ACTION_EXIT:
+
+ if (exit_status >= 0)
+ m->return_value = exit_status;
+
if (MANAGER_IS_USER(m) || detect_container() > 0) {
log_and_status(m, warn, "Exiting", reason);
(void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_EXIT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL);
@@ -90,6 +95,10 @@ int emergency_action(
break;
case EMERGENCY_ACTION_EXIT_FORCE:
+
+ if (exit_status >= 0)
+ m->return_value = exit_status;
+
if (MANAGER_IS_USER(m) || detect_container() > 0) {
log_and_status(m, warn, "Exiting immediately", reason);
m->objective = MANAGER_EXIT;
diff --git a/src/core/emergency-action.h b/src/core/emergency-action.h
index 1fc4a11e30..6e6c69ddfc 100644
--- a/src/core/emergency-action.h
+++ b/src/core/emergency-action.h
@@ -26,7 +26,7 @@ typedef enum EmergencyActionFlags {
int emergency_action(Manager *m,
EmergencyAction action, EmergencyActionFlags options,
- const char *reboot_arg, const char *reason);
+ const char *reboot_arg, int exit_status, const char *reason);
const char* emergency_action_to_string(EmergencyAction i) _const_;
EmergencyAction emergency_action_from_string(const char *s) _pure_;
diff --git a/src/core/job.c b/src/core/job.c
index 1f6b36033d..2a630356bf 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -1084,7 +1084,7 @@ static int job_dispatch_timer(sd_event_source *s, uint64_t monotonic, void *user
emergency_action(u->manager, u->job_timeout_action,
EMERGENCY_ACTION_IS_WATCHDOG|EMERGENCY_ACTION_WARN,
- u->job_timeout_reboot_arg, "job timed out");
+ u->job_timeout_reboot_arg, -1, "job timed out");
return 0;
}
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 7ae38538a4..97a707c144 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -239,6 +239,8 @@ Unit.StartLimitBurst, config_parse_unsigned, 0,
Unit.StartLimitAction, config_parse_emergency_action, 0, offsetof(Unit, start_limit_action)
Unit.FailureAction, config_parse_emergency_action, 0, offsetof(Unit, failure_action)
Unit.SuccessAction, config_parse_emergency_action, 0, offsetof(Unit, success_action)
+Unit.FailureActionExitStatus, config_parse_exit_status, 0, offsetof(Unit, failure_action_exit_status)
+Unit.SuccessActionExitStatus, config_parse_exit_status, 0, offsetof(Unit, success_action_exit_status)
Unit.RebootArgument, config_parse_unit_string_printf, 0, offsetof(Unit, reboot_arg)
Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions)
Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 8b36365dd6..067d36dad5 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -4291,6 +4291,41 @@ int config_parse_pid_file(
return 0;
}
+int config_parse_exit_status(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *exit_status = data, r;
+ uint8_t u;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(exit_status);
+
+ if (isempty(rvalue)) {
+ *exit_status = -1;
+ return 0;
+ }
+
+ r = safe_atou8(rvalue, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse exit status '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+
+ *exit_status = u;
+ return 0;
+}
+
#define FOLLOW_MAX 8
static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index d236f66a39..71d94a7762 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -104,6 +104,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_job_running_timeout_sec);
CONFIG_PARSER_PROTOTYPE(config_parse_log_extra_fields);
CONFIG_PARSER_PROTOTYPE(config_parse_collect_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_pid_file);
+CONFIG_PARSER_PROTOTYPE(config_parse_exit_status);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/core/manager.c b/src/core/manager.c
index 5f11515b3d..871047e628 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -2550,7 +2550,7 @@ static void manager_handle_ctrl_alt_del(Manager *m) {
if (ratelimit_below(&m->ctrl_alt_del_ratelimit) || m->cad_burst_action == EMERGENCY_ACTION_NONE)
manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY);
else
- emergency_action(m, m->cad_burst_action, EMERGENCY_ACTION_WARN, NULL,
+ emergency_action(m, m->cad_burst_action, EMERGENCY_ACTION_WARN, NULL, -1,
"Ctrl-Alt-Del was pressed more than 7 times within 2s");
}
diff --git a/src/core/service.c b/src/core/service.c
index 79347269ec..964a7fd057 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -4006,6 +4006,21 @@ static bool service_needs_console(Unit *u) {
SERVICE_FINAL_SIGKILL);
}
+static int service_exit_status(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(u);
+
+ if (s->main_exec_status.pid <= 0 ||
+ !dual_timestamp_is_set(&s->main_exec_status.exit_timestamp))
+ return -ENODATA;
+
+ if (s->main_exec_status.code != CLD_EXITED)
+ return -EBADE;
+
+ return s->main_exec_status.status;
+}
+
static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
[SERVICE_RESTART_NO] = "no",
[SERVICE_RESTART_ON_SUCCESS] = "on-success",
@@ -4126,6 +4141,7 @@ const UnitVTable service_vtable = {
.get_timeout = service_get_timeout,
.needs_console = service_needs_console,
+ .exit_status = service_exit_status,
.status_message_formats = {
.starting_stopping = {
diff --git a/src/core/unit.c b/src/core/unit.c
index 73a625e7a1..89bb95e2f1 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -96,6 +96,7 @@ Unit *unit_new(Manager *m, size_t size) {
u->ref_gid = GID_INVALID;
u->cpu_usage_last = NSEC_INFINITY;
u->cgroup_invalidated_mask |= CGROUP_MASK_BPF_FIREWALL;
+ u->failure_action_exit_status = u->success_action_exit_status = -1;
u->ip_accounting_ingress_map_fd = -1;
u->ip_accounting_egress_map_fd = -1;
@@ -1225,8 +1226,12 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
if (u->failure_action != EMERGENCY_ACTION_NONE)
fprintf(f, "%s\tFailure Action: %s\n", prefix, emergency_action_to_string(u->failure_action));
+ if (u->failure_action_exit_status >= 0)
+ fprintf(f, "%s\tFailure Action Exit Status: %i\n", prefix, u->failure_action_exit_status);
if (u->success_action != EMERGENCY_ACTION_NONE)
fprintf(f, "%s\tSuccess Action: %s\n", prefix, emergency_action_to_string(u->success_action));
+ if (u->success_action_exit_status >= 0)
+ fprintf(f, "%s\tSuccess Action Exit Status: %i\n", prefix, u->success_action_exit_status);
if (u->job_timeout != USEC_INFINITY)
fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0));
@@ -1673,7 +1678,7 @@ int unit_start_limit_test(Unit *u) {
return emergency_action(u->manager, u->start_limit_action,
EMERGENCY_ACTION_IS_WATCHDOG|EMERGENCY_ACTION_WARN,
- u->reboot_arg, reason);
+ u->reboot_arg, -1, reason);
}
bool unit_shall_confirm_spawn(Unit *u) {
@@ -2483,10 +2488,10 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
if (os != UNIT_FAILED && ns == UNIT_FAILED) {
reason = strjoina("unit ", u->id, " failed");
- (void) emergency_action(m, u->failure_action, 0, u->reboot_arg, reason);
+ (void) emergency_action(m, u->failure_action, 0, u->reboot_arg, unit_failure_action_exit_status(u), reason);
} else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && ns == UNIT_INACTIVE) {
reason = strjoina("unit ", u->id, " succeeded");
- (void) emergency_action(m, u->success_action, 0, u->reboot_arg, reason);
+ (void) emergency_action(m, u->success_action, 0, u->reboot_arg, unit_success_action_exit_status(u), reason);
}
}
@@ -5518,6 +5523,54 @@ void unit_log_process_exit(
LOG_UNIT_INVOCATION_ID(u));
}
+int unit_exit_status(Unit *u) {
+ assert(u);
+
+ /* Returns the exit status to propagate for the most recent cycle of this unit. Returns a value in the range
+ * 0…255 if there's something to propagate. EOPNOTSUPP if the concept does not apply to this unit type, ENODATA
+ * if no data is currently known (for example because the unit hasn't deactivated yet) and EBADE if the main
+ * service process has exited abnormally (signal/coredump). */
+
+ if (!UNIT_VTABLE(u)->exit_status)
+ return -EOPNOTSUPP;
+
+ return UNIT_VTABLE(u)->exit_status(u);
+}
+
+int unit_failure_action_exit_status(Unit *u) {
+ int r;
+
+ assert(u);
+
+ /* Returns the exit status to propagate on failure, or an error if there's nothing to propagate */
+
+ if (u->failure_action_exit_status >= 0)
+ return u->failure_action_exit_status;
+
+ r = unit_exit_status(u);
+ if (r == -EBADE) /* Exited, but not cleanly (i.e. by signal or such) */
+ return 255;
+
+ return r;
+}
+
+int unit_success_action_exit_status(Unit *u) {
+ int r;
+
+ assert(u);
+
+ /* Returns the exit status to propagate on success, or an error if there's nothing to propagate */
+
+ if (u->success_action_exit_status >= 0)
+ return u->success_action_exit_status;
+
+ r = unit_exit_status(u);
+ if (r == -EBADE) /* Exited, but not cleanly (i.e. by signal or such) */
+ return 255;
+
+ return r;
+}
+
static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
[COLLECT_INACTIVE] = "inactive",
[COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
diff --git a/src/core/unit.h b/src/core/unit.h
index 613d7b32e6..e300cc4f2f 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -226,8 +226,9 @@ typedef struct Unit {
RateLimit start_limit;
EmergencyAction start_limit_action;
- EmergencyAction failure_action;
- EmergencyAction success_action;
+ /* What to do on failure or success */
+ EmergencyAction success_action, failure_action;
+ int success_action_exit_status, failure_action_exit_status;
char *reboot_arg;
/* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */
@@ -529,6 +530,10 @@ typedef struct UnitVTable {
/* Returns true if the unit currently needs access to the console */
bool (*needs_console)(Unit *u);
+ /* Returns the exit status to propagate in case of FailureAction=exit/SuccessAction=exit; usually returns the
+ * exit code of the "main" process of the service or similar. */
+ int (*exit_status)(Unit *u);
+
/* Like the enumerate() callback further down, but only enumerates the perpetual units, i.e. all units that
* unconditionally exist and are always active. The main reason to keep both enumeration functions separate is
* philosophical: the state of perpetual units should be put in place by coldplug(), while the state of those
@@ -813,6 +818,10 @@ static inline void unit_log_result(Unit *u, bool success, const char *result) {
void unit_log_process_exit(Unit *u, int level, const char *kind, const char *command, int code, int status);
+int unit_exit_status(Unit *u);
+int unit_success_action_exit_status(Unit *u);
+int unit_failure_action_exit_status(Unit *u);
+
/* Macros which append UNIT= or USER_UNIT= to the message */
#define log_unit_full(unit, level, error, ...) \
diff --git a/src/run-generator/run-generator.c b/src/run-generator/run-generator.c
new file mode 100644
index 0000000000..243a426cda
--- /dev/null
+++ b/src/run-generator/run-generator.c
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "generator.h"
+#include "main-func.h"
+#include "mkdir.h"
+#include "proc-cmdline.h"
+#include "specifier.h"
+#include "strv.h"
+
+static const char *arg_dest = "/tmp";
+static char **arg_commands = NULL;
+static char *arg_success_action = NULL;
+static char *arg_failure_action = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_commands, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_success_action, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_failure_action, freep);
+
+static int parse(const char *key, const char *value, void *data) {
+ int r;
+
+ if (proc_cmdline_key_streq(key, "systemd.run")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ r = strv_extend(&arg_commands, value);
+ if (r < 0)
+ return log_oom();
+
+ } else if (proc_cmdline_key_streq(key, "systemd.run_success_action")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ if (free_and_strdup(&arg_success_action, value) < 0)
+ return log_oom();
+
+ } else if (proc_cmdline_key_streq(key, "systemd.run_failure_action")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ if (free_and_strdup(&arg_failure_action, value) < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+static int generate(void) {
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *p;
+ char **c;
+ int r;
+
+ if (strv_isempty(arg_commands) && !arg_success_action)
+ return 0;
+
+ p = strjoina(arg_dest, "/kernel-command-line.service");
+ f = fopen(p, "wxe");
+ if (!f)
+ return log_error_errno(errno, "Failed to create unit file %s: %m", p);
+
+ fputs("# Automatically generated by systemd-run-generator\n\n"
+ "[Unit]\n"
+ "Description=Command from Kernel Command Line\n"
+ "Documentation=man:systemd-run-generator(8)\n"
+ "SourcePath=/proc/cmdline\n", f);
+
+ if (!streq_ptr(arg_success_action, "none"))
+ fprintf(f, "SuccessAction=%s\n",
+ arg_success_action ?: "exit");
+
+ if (!streq_ptr(arg_failure_action, "none"))
+ fprintf(f, "FailureAction=%s\n",
+ arg_failure_action ?: "exit");
+
+ fputs("\n"
+ "[Service]\n"
+ "Type=oneshot\n"
+ "StandardOutput=journal+console\n", f);
+
+ STRV_FOREACH(c, arg_commands) {
+ _cleanup_free_ char *a = NULL;
+
+ a = specifier_escape(*c);
+ if (!a)
+ return log_oom();
+
+ fprintf(f, "ExecStart=%s\n", a);
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write unit file %s: %m", p);
+
+ /* Let's create a a target we can link "default.target" to */
+ p = strjoina(arg_dest, "/kernel-command-line.target");
+ r = write_string_file(
+ p,
+ "# Automatically generated by systemd-run-generator\n\n"
+ "[Unit]\n"
+ "Description=Command from Kernel Command Line\n"
+ "Documentation=man:systemd-run-generator(8)\n"
+ "SourcePath=/proc/cmdline\n"
+ "Requires=kernel-command-line.service\n"
+ "After=kernel-command-line.service\n",
+ WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_NOFOLLOW);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create unit file %s: %m", p);
+
+ /* And now redirect default.target to our new target */
+ p = strjoina(arg_dest, "/default.target");
+ if (symlink("kernel-command-line.target", p) < 0)
+ return log_error_errno(errno, "Failed to link unit file kernel-command-line.target → %s: %m", p);
+
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_setup_generator();
+
+ if (argc > 1 && argc != 4) {
+ log_error("This program takes three or no arguments.");
+ return -EINVAL;
+ }
+
+ if (argc > 1)
+ arg_dest = argv[1];
+
+ umask(0022);
+
+ r = proc_cmdline_parse(parse, NULL, PROC_CMDLINE_RD_STRICT|PROC_CMDLINE_STRIP_RD_PREFIX);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+
+ return generate();
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 0c65f75251..d11b8c26c7 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -1557,6 +1557,25 @@ static int bus_append_unit_property(sd_bus_message *m, const char *field, const
return bus_append_safe_atou(m, field, eq);
+ if (STR_IN_SET(field, "SuccessActionExitStatus", "FailureActionExitStatus")) {
+
+ if (isempty(eq))
+ r = sd_bus_message_append(m, "(sv)", field, "i", -1);
+ else {
+ uint8_t u;
+
+ r = safe_atou8(eq, &u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s=%s", field, eq);
+
+ r = sd_bus_message_append(m, "(sv)", field, "i", (int) u);
+ }
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+ }
+
if (unit_dependency_from_string(field) >= 0 ||
STR_IN_SET(field, "Documentation", "RequiresMountsFor"))