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:
commit
8b4e51a60e
2
TODO
2
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()
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ Most generic unit settings are available for transient units.
|
|||
✓ StartLimitAction=ACTION
|
||||
✓ FailureAction=
|
||||
✓ SuccessAction=
|
||||
✓ FailureActionExitStatus=
|
||||
✓ SuccessActionExitStatus=
|
||||
✓ AddRef=
|
||||
✓ RebootArgument=STRING
|
||||
✓ ConditionPathExists=
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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', [], ''],
|
||||
|
|
82
man/systemd-run-generator.xml
Normal file
82
man/systemd-run-generator.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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, ...) \
|
||||
|
|
147
src/run-generator/run-generator.c
Normal file
147
src/run-generator/run-generator.c
Normal 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);
|
|
@ -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"))
|
||||
|
||||
|
|
Loading…
Reference in a new issue