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 @@ -91,6 +91,17 @@ + + systemd.run= + systemd.run_success_action= + systemd.run_failure_action= + + Additional parameters understood by + systemd-run-generator8, to + run a command line specified on the kernel command line as system service after booting up. + + + systemd.early_core_pattern= 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 @@ + + + + + + + + systemd-run-generator + systemd + + + + systemd-run-generator + 8 + + + + systemd-run-generator + Generator for invoking commands specified on the kernel command line as system service + + + + /usr/lib/systemd/system-generators/systemd-run-generator + + + + Description + + systemd-run-generator is a generator + that reads the kernel command line and understands three + options: + + If the option is specified and followed by a command line, a unit named + kernel-command-line.service is generated for it and booted into. The service has + Type=oneshot set, and has SuccessAction=exit and + FailureAction=exit 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. + systemd-nspawn7 does this). If + this option is used multiple times the unit file will contain multiple ExecStart= lines, to + execute all commands in order. The command is started as regular service, i.e. with + DefaultDependencies= on. + + Use and to tweak + how to react to the process completing. In particular assigning none will leave the system + running after the command completes. For further details on supported arguments, see + systemd.unit5. + + systemd-run-generator implements + systemd.generator7. + + + + Example + + Use a command like the following to add a user to the user database inside a container run with + systemd-nspawn7: + + # systemd-nspawn -D mycontainer -b systemd.run='"adduser test"' + (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 systemd-nspawn. The second level of + quoting ("") is propagated to the kernel command line of the container and processed and removed by + systemd-run-generator. Both together make sure both words of the specified command line + adduser test end up in the generated unit file together and are neither split apart by the + command shell nor by the generator.) + + + + See Also + + systemd1, + systemctl1, + kernel-command-line7, + systemd-nspawn7, + systemd.unit5, + systemd.service5 + + + + 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 systemctl reboot -f) and causes immediate execution of the reboot2 system call, which - might result in data loss. Similarly, , , - have the effect of powering down the system with similar - semantics. causes the manager to exit following the normal shutdown procedure, and - causes it terminate without shutting down services. + might result in data loss (i.e. equivalent to systemctl reboot -ff). Similarly, + , , have the effect + of powering down the system with similar semantics. causes the manager to exit following + the normal shutdown procedure, and causes it terminate without shutting down + services. When or 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 FailureActionExitStatus=/SuccessActionExitStatus=, see + below. + + + + FailureActionExitStatus= + SuccessActionExitStatus= + + 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 + FailureAction=/SuccessAction= are set to or + 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. 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"))