X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fservice.c;h=8b5c0b07c4d29efe70b639936389d7b77a2dbdbb;hp=25ebca08909df82f1ac3a4c4378045e7229c665a;hb=189f6d82eef8bccb733f735281bed4588dccbbd9;hpb=f42806df7de0f4669c1296ab66a7f8d8784dd0f2 diff --git a/src/service.c b/src/service.c index 25ebca089..8b5c0b07c 100644 --- a/src/service.c +++ b/src/service.c @@ -23,7 +23,9 @@ #include #include #include +#include +#include "manager.h" #include "unit.h" #include "service.h" #include "load-fragment.h" @@ -37,6 +39,7 @@ #include "exit-status.h" #include "def.h" #include "util.h" +#include "utf8.h" #ifdef HAVE_SYSV_COMPAT @@ -112,6 +115,9 @@ static void service_init(Unit *u) { s->timeout_usec = DEFAULT_TIMEOUT_USEC; s->restart_usec = DEFAULT_RESTART_USEC; + + s->watchdog_watch.type = WATCH_INVALID; + s->timer_watch.type = WATCH_INVALID; #ifdef HAVE_SYSV_COMPAT s->sysv_start_priority = -1; @@ -122,7 +128,7 @@ static void service_init(Unit *u) { exec_context_init(&s->exec_context); - RATELIMIT_INIT(s->ratelimit, 10*USEC_PER_SEC, 5); + RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5); s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; } @@ -208,14 +214,39 @@ static void service_connection_unref(Service *s) { static void service_stop_watchdog(Service *s) { assert(s); + unit_unwatch_timer(UNIT(s), &s->watchdog_watch); s->watchdog_timestamp.realtime = 0; s->watchdog_timestamp.monotonic = 0; } +static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart); + +static void service_handle_watchdog(Service *s) { + usec_t offset; + int r; + + assert(s); + + if (s->watchdog_usec == 0) + return; + + offset = now(CLOCK_MONOTONIC) - s->watchdog_timestamp.monotonic; + if (offset >= s->watchdog_usec) { + log_error("%s watchdog timeout!", UNIT(s)->id); + service_enter_dead(s, SERVICE_FAILURE_WATCHDOG, true); + return; + } + + r = unit_watch_timer(UNIT(s), s->watchdog_usec - offset, &s->watchdog_watch); + if (r < 0) + log_warning("%s failed to install watchdog timer: %s", UNIT(s)->id, strerror(-r)); +} + static void service_reset_watchdog(Service *s) { assert(s); dual_timestamp_get(&s->watchdog_timestamp); + service_handle_watchdog(s); } static void service_done(Unit *u) { @@ -259,6 +290,8 @@ static void service_done(Unit *u) { unit_ref_unset(&s->accept_socket); + service_stop_watchdog(s); + unit_unwatch_timer(u, &s->timer_watch); } @@ -864,6 +897,7 @@ static int service_load_sysv_path(Service *s, const char *path) { s->remain_after_exit = !s->pid_file; s->guess_main_pid = false; s->restart = SERVICE_RESTART_NO; + s->exec_context.ignore_sigpipe = false; if (UNIT(s)->manager->sysv_console) s->exec_context.std_output = EXEC_OUTPUT_JOURNAL_AND_CONSOLE; @@ -1202,6 +1236,9 @@ static int service_load(Unit *u) { if (s->type == SERVICE_NOTIFY && s->notify_access == NOTIFY_NONE) s->notify_access = NOTIFY_MAIN; + if (s->watchdog_usec > 0 && s->notify_access == NOTIFY_NONE) + s->notify_access = NOTIFY_MAIN; + if (s->type == SERVICE_DBUS || s->bus_name) if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true)) < 0) return r; @@ -1400,7 +1437,7 @@ static int service_search_main_pid(Service *s) { return 0; } -static void service_notify_sockets_dead(Service *s) { +static void service_notify_sockets_dead(Service *s, bool failed_permanent) { Iterator i; Unit *u; @@ -1413,7 +1450,7 @@ static void service_notify_sockets_dead(Service *s) { SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) if (u->type == UNIT_SOCKET) - socket_notify_service_dead(SOCKET(u)); + socket_notify_service_dead(SOCKET(u), failed_permanent); return; } @@ -1475,7 +1512,7 @@ static void service_set_state(Service *s, ServiceState state) { state == SERVICE_FINAL_SIGKILL || state == SERVICE_FAILED || state == SERVICE_AUTO_RESTART) - service_notify_sockets_dead(s); + service_notify_sockets_dead(s, false); if (state != SERVICE_START_PRE && state != SERVICE_START && @@ -1568,9 +1605,12 @@ static int service_coldplug(Unit *u) { if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) return r; + if (s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RUNNING) + service_handle_watchdog(s); + service_set_state(s, s->deserialized_state); } - return 0; } @@ -1702,6 +1742,12 @@ static int service_spawn( goto fail; } + if (s->watchdog_usec > 0) + if (asprintf(our_env + n_env++, "WATCHDOG_USEC=%llu", (unsigned long long) s->watchdog_usec) < 0) { + r = -ENOMEM; + goto fail; + } + if (!(final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, @@ -2002,6 +2048,9 @@ static void service_enter_start_post(Service *s) { service_unwatch_control_pid(s); + if (s->watchdog_usec > 0) + service_reset_watchdog(s); + if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) { s->control_command_id = SERVICE_EXEC_START_POST; @@ -2281,8 +2330,56 @@ fail: service_enter_stop(s, SERVICE_FAILURE_RESOURCES); } +static int service_start_limit_test(Service *s) { + assert(s); + + if (ratelimit_test(&s->start_limit)) + return 0; + + switch (s->start_limit_action) { + + case SERVICE_START_LIMIT_NONE: + log_warning("%s start request repeated too quickly, refusing to start.", UNIT(s)->id); + break; + + case SERVICE_START_LIMIT_REBOOT: { + DBusError error; + int r; + + dbus_error_init(&error); + + log_warning("%s start request repeated too quickly, rebooting.", UNIT(s)->id); + + r = manager_add_job_by_name(UNIT(s)->manager, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE, true, &error, NULL); + if (r < 0) { + log_error("Failed to reboot: %s.", bus_error(&error, r)); + dbus_error_free(&error); + } + + break; + } + + case SERVICE_START_LIMIT_REBOOT_FORCE: + log_warning("%s start request repeated too quickly, forcibly rebooting.", UNIT(s)->id); + UNIT(s)->manager->exit_code = MANAGER_REBOOT; + break; + + case SERVICE_START_LIMIT_REBOOT_IMMEDIATE: + log_warning("%s start request repeated too quickly, rebooting immediately.", UNIT(s)->id); + reboot(RB_AUTOBOOT); + break; + + default: + log_error("start limit action=%i", s->start_limit_action); + assert_not_reached("Unknown StartLimitAction."); + } + + return -ECANCELED; +} + static int service_start(Unit *u) { Service *s = SERVICE(u); + int r; assert(s); @@ -2305,9 +2402,10 @@ static int service_start(Unit *u) { assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED || s->state == SERVICE_AUTO_RESTART); /* Make sure we don't enter a busy loop of some kind. */ - if (!ratelimit_test(&s->ratelimit)) { - log_warning("%s start request repeated too quickly, refusing to start.", u->id); - return -ECANCELED; + r = service_start_limit_test(s); + if (r < 0) { + service_notify_sockets_dead(s, true); + return r; } s->result = SERVICE_SUCCESS; @@ -2402,9 +2500,9 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (s->status_text) unit_serialize_item(u, f, "status-text", s->status_text); - /* There's a minor uncleanliness here: if there are multiple - * commands attached here, we will start from the first one - * again */ + /* FIXME: There's a minor uncleanliness here: if there are + * multiple commands attached here, we will start from the + * first one again */ if (s->control_command_id >= 0) unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id)); @@ -2693,7 +2791,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { else if (code == CLD_DUMPED) f = SERVICE_FAILURE_CORE_DUMP; else - f = SERVICE_FAILURE_RESOURCES; + assert_not_reached("Unknown code"); if (s->main_pid == pid) { /* Forking services may occasionally move to a new PID. @@ -2922,6 +3020,11 @@ static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { assert(s); assert(elapsed == 1); + if (w == &s->watchdog_watch) { + service_handle_watchdog(s); + return; + } + assert(w == &s->timer_watch); switch (s->state) { @@ -3098,11 +3201,19 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags) { } /* Interpret STATUS= */ - if ((e = strv_find_prefix(tags, "STATUS="))) { + e = strv_find_prefix(tags, "STATUS="); + if (e) { char *t; if (e[7]) { - if (!(t = strdup(e+7))) { + + if (!utf8_is_valid(e+7)) { + log_warning("Status message in notification is not UTF-8 clean."); + return; + } + + t = strdup(e+7); + if (!t) { log_error("Failed to allocate string."); return; } @@ -3611,11 +3722,20 @@ static const char* const service_result_table[_SERVICE_RESULT_MAX] = { [SERVICE_FAILURE_TIMEOUT] = "timeout", [SERVICE_FAILURE_EXIT_CODE] = "exit-code", [SERVICE_FAILURE_SIGNAL] = "signal", - [SERVICE_FAILURE_CORE_DUMP] = "core-dump" + [SERVICE_FAILURE_CORE_DUMP] = "core-dump", + [SERVICE_FAILURE_WATCHDOG] = "watchdog" }; DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); +static const char* const start_limit_action_table[_SERVICE_START_LIMIT_MAX] = { + [SERVICE_START_LIMIT_NONE] = "none", + [SERVICE_START_LIMIT_REBOOT] = "reboot", + [SERVICE_START_LIMIT_REBOOT_FORCE] = "reboot-force", + [SERVICE_START_LIMIT_REBOOT_IMMEDIATE] = "reboot-immediate" +}; +DEFINE_STRING_TABLE_LOOKUP(start_limit_action, StartLimitAction); + const UnitVTable service_vtable = { .suffix = ".service", .object_size = sizeof(Service),