From 7af67e9a8b1f10c6aaeff56910957907ef852cee Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Nov 2018 11:41:18 +0100 Subject: [PATCH 1/5] core: allow to set exit status when using SuccessAction=/FailureAction=exit in units This adds SuccessActionExitStatus= and FailureActionExitStatus= that may be used to configure the exit status to propagate in when SuccessAction=exit or FailureAction=exit is used. When not specified let's also propagate the exit status of the main process we fork off for the unit. --- docs/TRANSIENT-SETTINGS.md | 2 + src/core/dbus-unit.c | 40 ++++++++++++++++++ src/core/emergency-action.c | 9 ++++ src/core/emergency-action.h | 2 +- src/core/job.c | 2 +- src/core/load-fragment-gperf.gperf.m4 | 2 + src/core/load-fragment.c | 35 ++++++++++++++++ src/core/load-fragment.h | 1 + src/core/manager.c | 2 +- src/core/service.c | 16 ++++++++ src/core/unit.c | 59 +++++++++++++++++++++++++-- src/core/unit.h | 13 +++++- src/shared/bus-unit-util.c | 19 +++++++++ 13 files changed, 194 insertions(+), 8 deletions(-) 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/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 ce222e4023..b9fa47a7c2 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 e2de1c84b9..bd58db72d3 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 7675663f8b..aadec67932 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -4005,6 +4005,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", @@ -4125,6 +4140,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 df340548bb..40c7879bf0 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -95,6 +95,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; @@ -1224,8 +1225,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)); @@ -1667,7 +1672,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) { @@ -2477,10 +2482,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); } } @@ -5512,6 +5517,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/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 0141c131f6..17a064c30b 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1555,6 +1555,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")) From 35a1ff4cfe82810e7f52db07b67793af8c54c9f6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Nov 2018 22:09:29 +0100 Subject: [PATCH 2/5] add new run-generator This is really useful for running commands like this: # systemd-run -i someimage.raw -b systemd.run='"some command line"' This will now run the command line inside a small Type=oneshot service and even propagate the exit code of the command back to the parent. And all that with the full system booted up. By default this causes the system to shutdown right after the command completed, but this can be tweaked with systemd.run_success_action= and systemd.run_failure_action=. Note that when used in VMs the exit status can of course not be propagate, as VMs don't really know a concept for that. --- meson.build | 8 ++ src/run-generator/run-generator.c | 147 ++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/run-generator/run-generator.c diff --git a/meson.build b/meson.build index 8b51d032b8..2c8935c833 100644 --- a/meson.build +++ b/meson.build @@ -1631,6 +1631,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/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); From 6a4e939dd007fef153b0ed5266d761047fb4af06 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Nov 2018 12:40:28 +0100 Subject: [PATCH 3/5] man: document FailureActionExitStatus=/SuccessActionExitStatus= --- man/systemd.unit.xml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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. From 565f3d91a297b289be56e0b9e6ea860911de42b0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Nov 2018 12:15:50 +0100 Subject: [PATCH 4/5] man: document systemd-run-generator --- man/kernel-command-line.xml | 11 +++++ man/rules/meson.build | 1 + man/systemd-run-generator.xml | 82 +++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 man/systemd-run-generator.xml 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 + + + + From 7154bebb3e60e7eb73e2c1066acdbc5277b8d7aa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Nov 2018 12:21:12 +0100 Subject: [PATCH 5/5] update TODO --- TODO | 2 -- 1 file changed, 2 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()