chiark / gitweb /
journal: Don't hold pointers to journal while remapping
[elogind.git] / src / service.c
index b6bbfab3efad1c2e195b92daa3a6b2b453a31aab..8b5c0b07c4d29efe70b639936389d7b77a2dbdbb 100644 (file)
@@ -23,7 +23,9 @@
 #include <signal.h>
 #include <dirent.h>
 #include <unistd.h>
+#include <sys/reboot.h>
 
+#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;
@@ -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),