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
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-11-28 22:40:55 +01:00 committed by GitHub
commit 8b4e51a60e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 463 additions and 14 deletions

2
TODO
View File

@ -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()

View File

@ -45,6 +45,8 @@ Most generic unit settings are available for transient units.
✓ StartLimitAction=ACTION
✓ FailureAction=
✓ SuccessAction=
✓ FailureActionExitStatus=
✓ SuccessActionExitStatus=
✓ AddRef=
✓ RebootArgument=STRING
✓ ConditionPathExists=

View File

@ -91,6 +91,17 @@
</listitem>
</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>

View File

@ -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', [], ''],

View File

@ -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>

View File

@ -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>

View File

@ -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',

View File

@ -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);

View File

@ -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;

View File

@ -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_;

View File

@ -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;
}

View File

@ -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)

View File

@ -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) {

View File

@ -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);

View File

@ -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");
}

View File

@ -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 = {

View File

@ -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
* 0255 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",

View File

@ -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, ...) \

View File

@ -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);

View File

@ -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"))