#include <errno.h>
#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
#include "sd-messages.h"
s->timeout_start_usec = u->manager->default_timeout_start_usec;
s->timeout_stop_usec = u->manager->default_timeout_stop_usec;
+ s->timeout_abort_usec = u->manager->default_timeout_abort_usec;
+ s->timeout_abort_set = u->manager->default_timeout_abort_set;
s->restart_usec = u->manager->default_restart_usec;
s->runtime_max_usec = USEC_INFINITY;
s->type = _SERVICE_TYPE_INVALID;
EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT;
s->watchdog_original_usec = USEC_INFINITY;
+
+ s->oom_policy = _OOM_POLICY_INVALID;
}
static void service_unwatch_control_pid(Service *s) {
}
static int service_set_main_pid(Service *s, pid_t pid) {
- pid_t ppid;
-
assert(s);
if (pid <= 1)
s->main_pid = pid;
s->main_pid_known = true;
+ s->main_pid_alien = pid_is_my_child(pid) == 0;
- if (get_process_ppid(pid, &ppid) >= 0 && ppid != getpid_cached()) {
+ if (s->main_pid_alien)
log_unit_warning(UNIT(s), "Supervising process "PID_FMT" which is not our child. We'll most likely not notice when it exits.", pid);
- s->main_pid_alien = true;
- } else
- s->main_pid_alien = false;
return 0;
}
fs->service->n_fd_store--;
}
- if (fs->event_source) {
- sd_event_source_set_enabled(fs->event_source, SD_EVENT_OFF);
- sd_event_source_unref(fs->event_source);
- }
+ sd_event_source_disable_unref(fs->event_source);
free(fs->fdname);
safe_close(fs->fd);
log_unit_warning(UNIT(s), "Service has USBFunctionStrings= setting, but no USBFunctionDescriptors=. Ignoring.");
if (s->runtime_max_usec != USEC_INFINITY && s->type == SERVICE_ONESHOT)
- log_unit_warning(UNIT(s), "MaxRuntimeSec= has no effect in combination with Type=oneshot. Ignoring.");
+ log_unit_warning(UNIT(s), "RuntimeMaxSec= has no effect in combination with Type=oneshot. Ignoring.");
return 0;
}
(s->type == SERVICE_NOTIFY || s->watchdog_usec > 0 || s->n_fd_store_max > 0))
s->notify_access = NOTIFY_MAIN;
+ /* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when
+ * delegation is on, in that case it we assume the payload knows better what to do and can process
+ * things in a more focused way. */
+ if (s->oom_policy < 0)
+ s->oom_policy = s->cgroup_context.delegate ? OOM_CONTINUE : UNIT(s)->manager->default_oom_policy;
+
+ /* Let the kernel do the killing if that's requested. */
+ s->cgroup_context.memory_oom_group = s->oom_policy == OOM_KILL;
+
r = service_add_default_dependencies(s);
if (r < 0)
return r;
static void service_dump(Unit *u, FILE *f, const char *prefix) {
char buf_restart[FORMAT_TIMESPAN_MAX], buf_start[FORMAT_TIMESPAN_MAX], buf_stop[FORMAT_TIMESPAN_MAX];
- char buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX];
+ char buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX], buf_abort[FORMAT_TIMESPAN_MAX];
ServiceExecCommand c;
Service *s = SERVICE(u);
const char *prefix2;
"%sType: %s\n"
"%sRestart: %s\n"
"%sNotifyAccess: %s\n"
- "%sNotifyState: %s\n",
+ "%sNotifyState: %s\n"
+ "%sOOMPolicy: %s\n",
prefix, service_state_to_string(s->state),
prefix, service_result_to_string(s->result),
prefix, service_result_to_string(s->reload_result),
prefix, service_type_to_string(s->type),
prefix, service_restart_to_string(s->restart),
prefix, notify_access_to_string(s->notify_access),
- prefix, notify_state_to_string(s->notify_state));
+ prefix, notify_state_to_string(s->notify_state),
+ prefix, oom_policy_to_string(s->oom_policy));
if (s->control_pid > 0)
fprintf(f,
fprintf(f,
"%sRestartSec: %s\n"
"%sTimeoutStartSec: %s\n"
- "%sTimeoutStopSec: %s\n"
- "%sRuntimeMaxSec: %s\n"
- "%sWatchdogSec: %s\n",
+ "%sTimeoutStopSec: %s\n",
prefix, format_timespan(buf_restart, sizeof(buf_restart), s->restart_usec, USEC_PER_SEC),
prefix, format_timespan(buf_start, sizeof(buf_start), s->timeout_start_usec, USEC_PER_SEC),
- prefix, format_timespan(buf_stop, sizeof(buf_stop), s->timeout_stop_usec, USEC_PER_SEC),
+ prefix, format_timespan(buf_stop, sizeof(buf_stop), s->timeout_stop_usec, USEC_PER_SEC));
+
+ if (s->timeout_abort_set)
+ fprintf(f,
+ "%sTimeoutAbortSec: %s\n",
+ prefix, format_timespan(buf_abort, sizeof(buf_abort), s->timeout_abort_usec, USEC_PER_SEC));
+
+ fprintf(f,
+ "%sRuntimeMaxSec: %s\n"
+ "%sWatchdogSec: %s\n",
prefix, format_timespan(buf_runtime, sizeof(buf_runtime), s->runtime_max_usec, USEC_PER_SEC),
prefix, format_timespan(buf_watchdog, sizeof(buf_watchdog), s->watchdog_usec, USEC_PER_SEC));
if (r < 0)
return r;
- r = unit_watch_pid(UNIT(s), pid);
+ r = unit_watch_pid(UNIT(s), pid, false);
if (r < 0) /* FIXME: we need to do something here */
return log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" for service: %m", pid);
if (service_set_main_pid(s, pid) < 0)
return;
- r = unit_watch_pid(UNIT(s), pid);
+ r = unit_watch_pid(UNIT(s), pid, false);
if (r < 0)
/* FIXME: we need to do something here */
log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" from: %m", pid);
return usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec);
case SERVICE_STOP:
- case SERVICE_STOP_WATCHDOG:
case SERVICE_STOP_SIGTERM:
case SERVICE_STOP_SIGKILL:
case SERVICE_STOP_POST:
case SERVICE_FINAL_SIGKILL:
return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_stop_usec);
+ case SERVICE_STOP_WATCHDOG:
+ return usec_add(UNIT(s)->state_change_timestamp.monotonic, service_timeout_abort_usec(s));
+
case SERVICE_AUTO_RESTART:
return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec);
SERVICE_RUNNING, SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))) {
- r = unit_watch_pid(UNIT(s), s->main_pid);
+ r = unit_watch_pid(UNIT(s), s->main_pid, false);
if (r < 0)
return r;
}
SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) {
- r = unit_watch_pid(UNIT(s), s->control_pid);
+ r = unit_watch_pid(UNIT(s), s->control_pid, false);
if (r < 0)
return r;
}
s->exec_fd_event_source = TAKE_PTR(exec_fd_source);
s->exec_fd_hot = false;
- r = unit_watch_pid(UNIT(s), pid);
- if (r < 0) /* FIXME: we need to do something here */
+ r = unit_watch_pid(UNIT(s), pid, true);
+ if (r < 0)
return r;
*_pid = pid;
* user can still introspect the counter. Do so on the next start. */
s->flush_n_restarts = true;
- /* The new state is in effect, let's decrease the fd store ref counter again. Let's also readd us to the GC
+ /* The new state is in effect, let's decrease the fd store ref counter again. Let's also re-add us to the GC
* queue, so that the fd store is possibly gc'ed again */
s->n_keep_fd_store--;
unit_add_to_gc_queue(UNIT(s));
if (s->pid_file)
(void) unlink(s->pid_file);
+ /* Reset TTY ownership if necessary */
+ exec_context_revert_tty(&s->exec_context);
+
return;
fail:
goto fail;
if (r > 0) {
- r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec));
+ r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC),
+ state == SERVICE_STOP_WATCHDOG ? service_timeout_abort_usec(s) : s->timeout_stop_usec));
if (r < 0)
goto fail;
* aren't as rigoriously written to protect aganst against multiple use. */
if (unit_warn_leftover_processes(UNIT(s)) &&
IN_SET(s->kill_context.kill_mode, KILL_MIXED, KILL_CONTROL_GROUP) &&
- !s->kill_context.send_sigkill) {
- return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(EBUSY), "Will not start SendSIGKILL=no service of type KillMode=control-group or mixed while processes exist");
- }
+ !s->kill_context.send_sigkill)
+ return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(EBUSY),
+ "Will not start SendSIGKILL=no service of type KillMode=control-group or mixed while processes exist");
+
return 0;
}
/* We force a fake state transition here. Otherwise, the unit would go directly from
* SERVICE_DEAD to SERVICE_DEAD without SERVICE_ACTIVATING or SERVICE_ACTIVE
- * inbetween. This way we can later trigger actions that depend on the state
+ * in between. This way we can later trigger actions that depend on the state
* transition, including SuccessAction=. */
service_set_state(s, SERVICE_START);
* restarted. We use JOB_RESTART (instead of the more obvious
* JOB_START) here so that those dependency jobs will be added
* as well. */
- r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_REPLACE, &error, NULL);
+ r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_REPLACE, NULL, &error, NULL);
if (r < 0)
goto fail;
assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED));
/* Make sure we don't enter a busy loop of some kind. */
- r = unit_start_limit_test(u);
+ r = unit_test_start_limit(u);
if (r < 0) {
service_enter_dead(s, SERVICE_FAILURE_START_LIMIT_HIT, false);
return r;
/* Already on it */
if (IN_SET(s->state,
- SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
+ SERVICE_STOP, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))
return 0;
/* If there's already something running we go directly into
* kill mode. */
- if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) {
+ if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_STOP_WATCHDOG)) {
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
return 0;
}
assert(u);
- log_unit_debug(u, "cgroup is empty");
+ log_unit_debug(u, "Control group is empty.");
switch (s->state) {
}
}
+static void service_notify_cgroup_oom_event(Unit *u) {
+ Service *s = SERVICE(u);
+
+ log_unit_debug(u, "Process of control group was killed by the OOM killer.");
+
+ if (s->oom_policy == OOM_CONTINUE)
+ return;
+
+ switch (s->state) {
+
+ case SERVICE_START_PRE:
+ case SERVICE_START:
+ case SERVICE_START_POST:
+ case SERVICE_STOP:
+ if (s->oom_policy == OOM_STOP)
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_OOM_KILL);
+ else if (s->oom_policy == OOM_KILL)
+ service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_OOM_KILL);
+
+ break;
+
+ case SERVICE_EXITED:
+ case SERVICE_RUNNING:
+ if (s->oom_policy == OOM_STOP)
+ service_enter_stop(s, SERVICE_FAILURE_OOM_KILL);
+ else if (s->oom_policy == OOM_KILL)
+ service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_OOM_KILL);
+
+ break;
+
+ case SERVICE_STOP_WATCHDOG:
+ case SERVICE_STOP_SIGTERM:
+ service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_OOM_KILL);
+ break;
+
+ case SERVICE_STOP_SIGKILL:
+ case SERVICE_FINAL_SIGKILL:
+ if (s->result == SERVICE_SUCCESS)
+ s->result = SERVICE_FAILURE_OOM_KILL;
+ break;
+
+ case SERVICE_STOP_POST:
+ case SERVICE_FINAL_SIGTERM:
+ service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_OOM_KILL);
+ break;
+
+ default:
+ ;
+ }
+}
+
static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
bool notify_dbus = true;
Service *s = SERVICE(u);
if (main_pid_good(s) <= 0)
service_enter_stop_post(s, f);
- /* If there is still a service
- * process around, wait until
+ /* If there is still a service process around, wait until
* that one quit, too */
break;
if (notify_dbus)
unit_add_to_dbus_queue(u);
- /* If we get a SIGCHLD event for one of the processes we were interested in, then we look for others to watch,
- * under the assumption that we'll sooner or later get a SIGCHLD for them, as the original process we watched
- * was probably the parent of them, and they are hence now our children. */
- (void) unit_enqueue_rewatch_pids(u);
+ /* We watch the main/control process otherwise we can't retrieve the unit they
+ * belong to with cgroupv1. But if they are not our direct child, we won't get a
+ * SIGCHLD for them. Therefore we need to look for others to watch so we can
+ * detect when the cgroup becomes empty. Note that the control process is always
+ * our child so it's pointless to watch all other processes. */
+ if (!control_pid_good(s))
+ if (!s->main_pid_known || s->main_pid_alien)
+ (void) unit_enqueue_rewatch_pids(u);
}
static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) {
return true;
}
+static void service_force_watchdog(Service *s) {
+ if (!UNIT(s)->manager->service_watchdogs)
+ return;
+
+ log_unit_error(UNIT(s), "Watchdog request (last status: %s)!",
+ s->status_text ? s->status_text : "<unset>");
+
+ service_enter_signal(s, SERVICE_STOP_WATCHDOG, SERVICE_FAILURE_WATCHDOG);
+}
+
static void service_notify_message(
Unit *u,
const struct ucred *ucred,
r = service_is_suitable_main_pid(s, new_main_pid, LOG_WARNING);
if (r == 0) {
- /* The new main PID is a bit suspicous, which is OK if the sender is privileged. */
+ /* The new main PID is a bit suspicious, which is OK if the sender is privileged. */
if (ucred->uid == 0) {
log_unit_debug(u, "New main PID "PID_FMT" does not belong to service, but we'll accept it as the request to change it came from a privileged process.", new_main_pid);
if (r > 0) {
service_set_main_pid(s, new_main_pid);
- r = unit_watch_pid(UNIT(s), new_main_pid);
+ r = unit_watch_pid(UNIT(s), new_main_pid, false);
if (r < 0)
log_unit_warning_errno(UNIT(s), r, "Failed to watch new main PID "PID_FMT" for service: %m", new_main_pid);
}
/* Interpret WATCHDOG= */
- if (strv_find(tags, "WATCHDOG=1"))
- service_reset_watchdog(s);
+ e = strv_find_startswith(tags, "WATCHDOG=");
+ if (e) {
+ if (streq(e, "1"))
+ service_reset_watchdog(s);
+ else if (streq(e, "trigger"))
+ service_force_watchdog(s);
+ else
+ log_unit_warning(u, "Passed WATCHDOG= field is invalid, ignoring.");
+ }
e = strv_find_startswith(tags, "WATCHDOG_USEC=");
if (e) {
log_unit_debug(u, "D-Bus name %s is now owned by process " PID_FMT, name, pid);
service_set_main_pid(s, pid);
- unit_watch_pid(UNIT(s), pid);
+ unit_watch_pid(UNIT(s), pid, false);
}
}
}
DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand);
+static const char* const service_exec_ex_command_table[_SERVICE_EXEC_COMMAND_MAX] = {
+ [SERVICE_EXEC_START_PRE] = "ExecStartPreEx",
+ [SERVICE_EXEC_START] = "ExecStartEx",
+ [SERVICE_EXEC_START_POST] = "ExecStartPostEx",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_exec_ex_command, ServiceExecCommand);
+
static const char* const notify_state_table[_NOTIFY_STATE_MAX] = {
[NOTIFY_UNKNOWN] = "unknown",
[NOTIFY_READY] = "ready",
[SERVICE_FAILURE_CORE_DUMP] = "core-dump",
[SERVICE_FAILURE_WATCHDOG] = "watchdog",
[SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit",
+ [SERVICE_FAILURE_OOM_KILL] = "oom-kill",
};
DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult);
.reset_failed = service_reset_failed,
.notify_cgroup_empty = service_notify_cgroup_empty_event,
+ .notify_cgroup_oom = service_notify_cgroup_oom_event,
.notify_message = service_notify_message,
.main_pid = service_main_pid,