diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 58ba5829112..0fc658f1807 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -1602,6 +1602,43 @@
functions) if their standard output or standard error output is connected to the journal anyway, thus enabling
delivery of structured metadata along with logged messages.
+
+
+ $SERVICE_RESULT
+
+ Only defined for the service unit type, this environment variable is passed to all
+ ExecStop= and ExecStopPost= processes, and encodes the service
+ "result". Currently, the following values are defined: timeout (in case of an operation
+ timeout), exit-code (if a service process exited with a non-zero exit code; see
+ $EXIT_STATUS below for the actual exit status returned), signal (if a
+ service process was terminated abnormally by a signal; see $EXIT_STATUS below for the actual
+ signal used for the termination), core-dump (if a service process terminated abnormally and
+ dumped core), watchdog (if the watchdog keep-alive ping was enabled for the service but it
+ missed the deadline), or resources (a catch-all condition in case a system operation
+ failed).
+
+ This environment variable is useful to monitor failure or successful termination of a service. Even
+ though this variable is available in both ExecStop= and ExecStopPost=, it
+ is usually a better choice to place monitoring tools in the latter, as the former is only invoked for services
+ that managed to start up correctly, and the latter covers both services that failed during their start-up and
+ those which failed during their runtime.
+
+
+
+ $EXIT_CODE
+ $EXIT_STATUS
+
+ Only defined for the service unit type, these environment variables are passed to all
+ ExecStop=, ExecStopPost= processes and contain exit status/code
+ information of the main process of the service. For the precise definition of the exit code and status, see
+ wait2. $EXIT_CODE
+ is one of exited, killed,
+ dumped. $EXIT_STATUS contains the numeric exit code formatted as string
+ if $EXIT_CODE is exited, and the signal name in all other cases. Note
+ that these environment variables are only set if the service manager succeeded to start and identify the main
+ process of the service.
+
+
Additional variables may be configured by the following
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index 875d368fcfb..e82edbe93e4 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -429,7 +429,13 @@
service failed to start up correctly. Commands configured with this setting need to be able to operate even if
the service failed starting up half-way and left incompletely initialized data around. As the service's
processes have been terminated already when the commands specified with this setting are executed they should
- not attempt to communicate with them.
+ not attempt to communicate with them.
+
+ Note that all commands that are configured with this setting are invoked with the result code of the
+ service, as well as the main process' exit code and status, set in the $SERVICE_RESULT,
+ $EXIT_CODE and $EXIT_STATUS environment variables, see
+ systemd.exec5 for
+ details.
diff --git a/src/core/execute.h b/src/core/execute.h
index 8d659ca1783..2b4238ed7e2 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -217,6 +217,7 @@ typedef enum ExecFlags {
/* The following are not used by execute.c, but by consumers internally */
EXEC_PASS_FDS = 1U << 4,
EXEC_IS_CONTROL = 1U << 5,
+ EXEC_SETENV_RESULT = 1U << 6,
} ExecFlags;
struct ExecParameters {
diff --git a/src/core/service.c b/src/core/service.c
index 32b8e7d2c53..0cbea522769 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1216,7 +1216,7 @@ static int service_spawn(
if (r < 0)
return r;
- our_env = new0(char*, 6);
+ our_env = new0(char*, 9);
if (!our_env)
return -ENOMEM;
@@ -1264,6 +1264,24 @@ static int service_spawn(
}
}
+ if (flags & EXEC_SETENV_RESULT) {
+ if (asprintf(our_env + n_env++, "SERVICE_RESULT=%s", service_result_to_string(s->result)) < 0)
+ return -ENOMEM;
+
+ if (s->main_exec_status.pid > 0 &&
+ dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) {
+ if (asprintf(our_env + n_env++, "EXIT_CODE=%s", sigchld_code_to_string(s->main_exec_status.code)) < 0)
+ return -ENOMEM;
+
+ if (s->main_exec_status.code == CLD_EXITED)
+ r = asprintf(our_env + n_env++, "EXIT_STATUS=%i", s->main_exec_status.status);
+ else
+ r = asprintf(our_env + n_env++, "EXIT_STATUS=%s", signal_to_string(s->main_exec_status.status));
+ if (r < 0)
+ return -ENOMEM;
+ }
+ }
+
final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL);
if (!final_env)
return -ENOMEM;
@@ -1467,7 +1485,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) {
r = service_spawn(s,
s->control_command,
s->timeout_stop_usec,
- EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_IS_CONTROL,
+ EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_IS_CONTROL|EXEC_SETENV_RESULT,
&s->control_pid);
if (r < 0)
goto fail;
@@ -1578,7 +1596,7 @@ static void service_enter_stop(Service *s, ServiceResult f) {
r = service_spawn(s,
s->control_command,
s->timeout_stop_usec,
- EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL,
+ EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_SETENV_RESULT,
&s->control_pid);
if (r < 0)
goto fail;
@@ -1898,7 +1916,8 @@ static void service_run_next_control(Service *s) {
s->control_command,
timeout,
EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|
- (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0),
+ (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)|
+ (IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0),
&s->control_pid);
if (r < 0)
goto fail;