]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: allow to set exit status when using SuccessAction=/FailureAction=exit in units
authorLennart Poettering <lennart@poettering.net>
Fri, 16 Nov 2018 10:41:18 +0000 (11:41 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 27 Nov 2018 08:44:40 +0000 (09:44 +0100)
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.

13 files changed:
docs/TRANSIENT-SETTINGS.md
src/core/dbus-unit.c
src/core/emergency-action.c
src/core/emergency-action.h
src/core/job.c
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/manager.c
src/core/service.c
src/core/unit.c
src/core/unit.h
src/shared/bus-unit-util.c

index 9a1a0a27f19ee628495a896b70c884239ecbcd56..89a185b5271ae4a8487658e38e1d48614a47a324 100644 (file)
@@ -45,6 +45,8 @@ Most generic unit settings are available for transient units.
 ✓ StartLimitAction=ACTION
 ✓ FailureAction=
 ✓ SuccessAction=
+✓ FailureActionExitStatus=
+✓ SuccessActionExitStatus=
 ✓ AddRef=
 ✓ RebootArgument=STRING
 ✓ ConditionPathExists=
index 18e5b8d2dfebe19a0304f4042b95e47d6644e682..6d9b559d2c7d522ff63f561edb41b66f9fc007b6 100644 (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);
 
index c91d2bf5ff5a2e770391ea3cd3727afddfb023a9..f98b0de7925d8155660474ed37e837a49492ad6f 100644 (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;
index 1fc4a11e306818bd4bdc4f3f7e067613837c0bdf..6e6c69ddfcba9ba8da06608eace85b9fa0cae450 100644 (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_;
index 1f6b36033d043758a106a50c5633a721cc5d7796..2a630356bfa5738bf6994b70c5ac693180c88ef8 100644 (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;
 }
index 7ae38538a4f6a1cebf9c6aac0093b43eafba3d3d..97a707c1447e3e486f0e853212d1f13343335fc3 100644 (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)
index ce222e402308d102861a1657d8952accbd156b40..b9fa47a7c2a210dd0b01bbcea237a8746c741554 100644 (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) {
index d236f66a39ad010f5fcaf0ff9850cc1414765b4e..71d94a77629becc2d25373989d303cc1978cbc99 100644 (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);
index e2de1c84b91c1face2dd88c8849522451bd4be69..bd58db72d3885f9020890ee2528d59893fd78370 100644 (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");
 }
 
index 7675663f8b679cc4d1bb66ce149012e3ae1cba1b..aadec67932fbb9a2e3bc443ee344ff1a9b5c32af 100644 (file)
@@ -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 = {
index df340548bb7acd3cfb1251030547c1f9d743384e..40c7879bf0a83755eeb48db2595cf98988264474 100644 (file)
@@ -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",
index 613d7b32e68040c5ece9583055f4d34cca974346..e300cc4f2fef413530cbb7e711cd7e01b336edda 100644 (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, ...)                          \
index 0141c131f683f6376b97cd22b984c03983cbb966..17a064c30bd65454427a2ceccff87f981cecc3ec 100644 (file)
@@ -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"))