X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fsystemd.git;a=blobdiff_plain;f=src%2Fcore%2Fservice.c;h=4b50d8d02975f45e80a61befd621db6732cf762c;hp=89029b6d115eaa72c0c43ae04fbf19264b49800c;hb=4b381a9ef65d68dc79760b093436a9c81f43fa5d;hpb=e947830460645ceab5574a55c1f251300dbcdc70 diff --git a/src/core/service.c b/src/core/service.c index 89029b6d115..4b50d8d0297 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include "sd-messages.h" @@ -97,6 +99,8 @@ static void service_init(Unit *u) { 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; @@ -110,6 +114,8 @@ static void service_init(Unit *u) { 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) { @@ -314,10 +320,7 @@ static void service_fd_store_unlink(ServiceFDStore *fs) { 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); @@ -600,7 +603,7 @@ static int service_verify(Service *s) { 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; } @@ -729,6 +732,15 @@ static int service_add_extras(Service *s) { (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; @@ -776,7 +788,7 @@ static int service_load(Unit *u) { 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; @@ -797,7 +809,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { "%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), @@ -808,7 +821,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { 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, @@ -844,12 +858,19 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { 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)); @@ -1117,7 +1138,6 @@ static usec_t service_coldplug_timeout(Service *s) { 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: @@ -1125,6 +1145,9 @@ static usec_t service_coldplug_timeout(Service *s) { 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); @@ -1726,7 +1749,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) * 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)); @@ -1842,7 +1865,8 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f 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; @@ -2023,9 +2047,10 @@ static int service_adverse_to_leftover_processes(Service *s) { * 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; } @@ -2068,7 +2093,7 @@ static void service_enter_start(Service *s) { /* 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); @@ -2181,7 +2206,7 @@ static void service_enter_restart(Service *s) { * 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; @@ -2413,7 +2438,7 @@ static int service_stop(Unit *u) { /* 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; @@ -2425,7 +2450,7 @@ static int service_stop(Unit *u) { /* 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; } @@ -3146,7 +3171,7 @@ static void service_notify_cgroup_empty_event(Unit *u) { assert(u); - log_unit_debug(u, "cgroup is empty"); + log_unit_debug(u, "Control group is empty."); switch (s->state) { @@ -3209,6 +3234,57 @@ static void service_notify_cgroup_empty_event(Unit *u) { } } +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); @@ -3664,6 +3740,16 @@ static bool service_notify_message_authorized(Service *s, pid_t pid, char **tags 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 : ""); + + service_enter_signal(s, SERVICE_STOP_WATCHDOG, SERVICE_FAILURE_WATCHDOG); +} + static void service_notify_message( Unit *u, const struct ucred *ucred, @@ -3700,7 +3786,7 @@ static void service_notify_message( 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); @@ -3810,8 +3896,15 @@ static void service_notify_message( } /* 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) { @@ -4095,6 +4188,14 @@ static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = 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", @@ -4114,6 +4215,7 @@ static const char* const service_result_table[_SERVICE_RESULT_MAX] = { [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); @@ -4167,6 +4269,7 @@ const UnitVTable service_vtable = { .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,