#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"
#include "exit-status.h"
#include "def.h"
#include "util.h"
+#include "utf8.h"
#ifdef HAVE_SYSV_COMPAT
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;
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;
}
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) {
unit_ref_unset(&s->accept_socket);
+ service_stop_watchdog(s);
+
unit_unwatch_timer(u, &s->timer_watch);
}
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;
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;
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;
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;
}
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 &&
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;
}
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,
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;
goto fail;
}
- service_enter_dead(s, SERVICE_SUCCESS, false);
-
- if ((r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(s), JOB_FAIL, false, &error, NULL)) < 0)
+ /* Any units that are bound to this service must also be
+ * 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_FAIL, false, &error, NULL);
+ if (r < 0)
goto fail;
log_debug("%s scheduled restart job.", UNIT(s)->id);
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);
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;
assert(s);
assert(elapsed == 1);
+ if (w == &s->watchdog_watch) {
+ service_handle_watchdog(s);
+ return;
+ }
+
assert(w == &s->timer_watch);
switch (s->state) {
}
/* 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;
}
[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),