#include "bus-errors.h"
#include "exit-status.h"
#include "def.h"
+#include "path-util.h"
#include "util.h"
#include "utf8.h"
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
};
+/* For Type=idle we never want to delay any other jobs, hence we
+ * consider idle jobs active as soon as we start working on them */
+static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = {
+ [SERVICE_DEAD] = UNIT_INACTIVE,
+ [SERVICE_START_PRE] = UNIT_ACTIVE,
+ [SERVICE_START] = UNIT_ACTIVE,
+ [SERVICE_START_POST] = UNIT_ACTIVE,
+ [SERVICE_RUNNING] = UNIT_ACTIVE,
+ [SERVICE_EXITED] = UNIT_ACTIVE,
+ [SERVICE_RELOAD] = UNIT_RELOADING,
+ [SERVICE_STOP] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_POST] = UNIT_DEACTIVATING,
+ [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
+ [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
+ [SERVICE_FAILED] = UNIT_FAILED,
+ [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
+};
+
static void service_init(Unit *u) {
Service *s = SERVICE(u);
+ int i;
assert(u);
assert(u->load_state == UNIT_STUB);
s->timeout_usec = DEFAULT_TIMEOUT_USEC;
s->restart_usec = DEFAULT_RESTART_USEC;
+ s->type = _SERVICE_TYPE_INVALID;
s->watchdog_watch.type = WATCH_INVALID;
s->guess_main_pid = true;
exec_context_init(&s->exec_context);
+ for (i = 0; i < RLIMIT_NLIMITS; i++)
+ if (UNIT(s)->manager->rlimit[i])
+ s->exec_context.rlimit[i] = newdup(struct rlimit, UNIT(s)->manager->rlimit[i], 1);
RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5);
s->pid_file = NULL;
#ifdef HAVE_SYSV_COMPAT
- free(s->sysv_path);
- s->sysv_path = NULL;
-
free(s->sysv_runlevels);
s->sysv_runlevels = NULL;
#endif
ExecCommand *c;
assert(s);
- assert(s->sysv_path);
+ assert(s->is_sysv);
+ assert(UNIT(s)->source_path);
- if (!(c = exec_command_new(s->sysv_path, "start")))
+ c = exec_command_new(UNIT(s)->source_path, "start");
+ if (!c)
return -ENOMEM;
exec_command_append_list(s->exec_command+SERVICE_EXEC_START, c);
- if (!(c = exec_command_new(s->sysv_path, "stop")))
+ c = exec_command_new(UNIT(s)->source_path, "stop");
+ if (!c)
return -ENOMEM;
exec_command_append_list(s->exec_command+SERVICE_EXEC_STOP, c);
- if (!(c = exec_command_new(s->sysv_path, "reload")))
+ c = exec_command_new(UNIT(s)->source_path, "reload");
+ if (!c)
return -ENOMEM;
exec_command_append_list(s->exec_command+SERVICE_EXEC_RELOAD, c);
u = UNIT(s);
- if (!(f = fopen(path, "re"))) {
+ f = fopen(path, "re");
+ if (!f) {
r = errno == ENOENT ? 0 : -errno;
goto finish;
}
goto finish;
}
- free(s->sysv_path);
- if (!(s->sysv_path = strdup(path))) {
+ free(u->source_path);
+ u->source_path = strdup(path);
+ if (!u->source_path) {
r = -ENOMEM;
goto finish;
}
-
- s->sysv_mtime = timespec_load(&st.st_mtim);
+ u->source_mtime = timespec_load(&st.st_mtim);
if (null_or_empty(&st)) {
u->load_state = UNIT_MASKED;
goto finish;
}
+ s->is_sysv = true;
+
while (!feof(f)) {
char l[LINE_MAX], *t;
goto finish;
}
- r = sysv_translate_facility(n, file_name_from_path(path), &m);
+ r = sysv_translate_facility(n, path_get_file_name(path), &m);
free(n);
if (r < 0)
goto finish;
}
- r = sysv_translate_facility(n, file_name_from_path(path), &m);
+ r = sysv_translate_facility(n, path_get_file_name(path), &m);
if (r < 0) {
log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r));
return -EINVAL;
}
+ if (s->bus_name && s->type != SERVICE_DBUS)
+ log_warning("%s has a D-Bus service name specified, but is not of type dbus. Ignoring.", UNIT(s)->id);
+
if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) {
log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id);
return -EINVAL;
/* This is a new unit? Then let's add in some extras */
if (u->load_state == UNIT_LOADED) {
+ if (s->type == _SERVICE_TYPE_INVALID)
+ s->type = s->bus_name ? SERVICE_DBUS : SERVICE_SIMPLE;
+
service_fix_output(s);
if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
}
#ifdef HAVE_SYSV_COMPAT
- if (s->sysv_path)
+ if (s->is_sysv)
fprintf(f,
- "%sSysV Init Script Path: %s\n"
"%sSysV Init Script has LSB Header: %s\n"
"%sSysVEnabled: %s\n",
- prefix, s->sysv_path,
prefix, yes_no(s->sysv_has_lsb),
prefix, yes_no(s->sysv_enabled));
static void service_set_state(Service *s, ServiceState state) {
ServiceState old_state;
+ const UnitActiveState *table;
assert(s);
+ table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table;
+
old_state = s->state;
s->state = state;
if (old_state != state)
log_debug("%s changed %s -> %s", UNIT(s)->id, service_state_to_string(old_state), service_state_to_string(state));
- unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], s->reload_result == SERVICE_SUCCESS);
+ unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS);
s->reload_result = SERVICE_SUCCESS;
}
bool apply_chroot,
bool apply_tty_stdin,
bool set_notify_socket,
+ bool is_control,
pid_t *_pid) {
pid_t pid;
UNIT(s)->manager->confirm_spawn,
UNIT(s)->cgroup_bondings,
UNIT(s)->cgroup_attributes,
+ is_control ? "control" : NULL,
+ s->type == SERVICE_IDLE ? UNIT(s)->manager->idle_pipe : NULL,
&pid);
if (r < 0)
if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) {
s->control_command_id = SERVICE_EXEC_STOP_POST;
- if ((r = service_spawn(s,
- s->control_command,
- true,
- false,
- !s->permissions_start_only,
- !s->root_directory_start_only,
- true,
- false,
- &s->control_pid)) < 0)
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ true,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
goto fail;
if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0)
goto fail;
- if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) {
+ r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, false, pid_set, NULL);
+ if (r < 0) {
if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
log_warning("Failed to kill control group: %s", strerror(-r));
} else if (r > 0)
if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) {
s->control_command_id = SERVICE_EXEC_STOP;
- if ((r = service_spawn(s,
- s->control_command,
- true,
- false,
- !s->permissions_start_only,
- !s->root_directory_start_only,
- false,
- false,
- &s->control_pid)) < 0)
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ false,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
goto fail;
service_set_state(s, SERVICE_STOP);
if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) {
s->control_command_id = SERVICE_EXEC_START_POST;
- if ((r = service_spawn(s,
- s->control_command,
- true,
- false,
- !s->permissions_start_only,
- !s->root_directory_start_only,
- false,
- false,
- &s->control_pid)) < 0)
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ false,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
goto fail;
service_set_state(s, SERVICE_START_POST);
/* We want to ensure that nobody leaks processes from
* START_PRE here, so let's go on a killing spree, People
* should not spawn long running processes from START_PRE. */
- cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL);
+ cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
if (s->type == SERVICE_FORKING) {
s->control_command_id = SERVICE_EXEC_START;
c = s->main_command = s->exec_command[SERVICE_EXEC_START];
}
- if ((r = service_spawn(s,
- c,
- s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY,
- true,
- true,
- true,
- true,
- s->notify_access != NOTIFY_NONE,
- &pid)) < 0)
+ r = service_spawn(s,
+ c,
+ s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY,
+ true,
+ true,
+ true,
+ true,
+ s->notify_access != NOTIFY_NONE,
+ false,
+ &pid);
+ if (r < 0)
goto fail;
- if (s->type == SERVICE_SIMPLE) {
+ if (s->type == SERVICE_SIMPLE || s->type == SERVICE_IDLE) {
/* For simple services we immediately start
* the START_POST binaries. */
/* Before we start anything, let's clear up what might
* be left from previous runs. */
- cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL);
+ cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
s->control_command_id = SERVICE_EXEC_START_PRE;
- if ((r = service_spawn(s,
- s->control_command,
- true,
- false,
- !s->permissions_start_only,
- !s->root_directory_start_only,
- true,
- false,
- &s->control_pid)) < 0)
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ true,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
goto fail;
service_set_state(s, SERVICE_START_PRE);
if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0)
goto fail;
+
+ return;
}
/* Any units that are bound to this service must also be
if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) {
s->control_command_id = SERVICE_EXEC_RELOAD;
- if ((r = service_spawn(s,
- s->control_command,
- true,
- false,
- !s->permissions_start_only,
- !s->root_directory_start_only,
- false,
- false,
- &s->control_pid)) < 0)
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ false,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
goto fail;
service_set_state(s, SERVICE_RELOAD);
s->control_command = s->control_command->command_next;
service_unwatch_control_pid(s);
- if ((r = service_spawn(s,
- s->control_command,
- true,
- false,
- !s->permissions_start_only,
- !s->root_directory_start_only,
- s->control_command_id == SERVICE_EXEC_START_PRE ||
- s->control_command_id == SERVICE_EXEC_STOP_POST,
- false,
- &s->control_pid)) < 0)
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ s->control_command_id == SERVICE_EXEC_START_PRE ||
+ s->control_command_id == SERVICE_EXEC_STOP_POST,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
goto fail;
return;
s->main_command = s->main_command->command_next;
service_unwatch_main_pid(s);
- if ((r = service_spawn(s,
- s->main_command,
- false,
- true,
- true,
- true,
- true,
- s->notify_access != NOTIFY_NONE,
- &pid)) < 0)
+ r = service_spawn(s,
+ s->main_command,
+ false,
+ true,
+ true,
+ true,
+ true,
+ s->notify_access != NOTIFY_NONE,
+ false,
+ &pid);
+ if (r < 0)
goto fail;
service_set_main_pid(s, pid);
s->state == SERVICE_START_POST)
return 0;
- assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED || s->state == SERVICE_AUTO_RESTART);
+ /* A service that will be restarted must be stopped first to
+ * trigger BindTo and/or OnFailure dependencies. If a user
+ * does not want to wait for the holdoff time to elapse, the
+ * service should be manually restarted, not started. */
+ if (s->state == SERVICE_AUTO_RESTART) {
+ log_warning("%s automatic restart is pending, must be stopped before issuing start request.", UNIT(s)->id);
+ return -ECANCELED;
+ }
+
+ assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED);
/* Make sure we don't enter a busy loop of some kind. */
r = service_start_limit_test(s);
assert(s);
- /* This is a user request, so don't do restarts on this
- * shutdown. */
+ /* Don't create restart jobs from here. */
s->forbid_restart = true;
/* Already on it */
s->state == SERVICE_FINAL_SIGKILL)
return 0;
- /* Don't allow a restart */
+ /* A restart will be scheduled or is in progress. */
if (s->state == SERVICE_AUTO_RESTART) {
- service_set_state(s, SERVICE_DEAD);
+ service_enter_dead(s, SERVICE_SUCCESS, false);
return 0;
}
}
static UnitActiveState service_active_state(Unit *u) {
+ const UnitActiveState *table;
+
assert(u);
- return state_translation_table[SERVICE(u)->state];
+ table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table;
+
+ return table[SERVICE(u)->state];
}
static const char *service_sub_state_to_string(Unit *u) {
return true;
#ifdef HAVE_SYSV_COMPAT
- if (s->sysv_path)
+ if (s->is_sysv)
return true;
#endif
if (f != SERVICE_SUCCESS)
s->result = f;
+ /* Immediately get rid of the cgroup, so that the
+ * kernel doesn't delay the cgroup empty messages for
+ * the service cgroup any longer than necessary */
+ cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
+
if (s->control_command &&
s->control_command->command_next &&
f == SERVICE_SUCCESS) {
s->reload_result = SERVICE_SUCCESS;
}
-static bool service_need_daemon_reload(Unit *u) {
- Service *s = SERVICE(u);
-
- assert(s);
-
-#ifdef HAVE_SYSV_COMPAT
- if (s->sysv_path) {
- struct stat st;
-
- zero(st);
- if (stat(s->sysv_path, &st) < 0)
- /* What, cannot access this anymore? */
- return true;
-
- if (s->sysv_mtime > 0 &&
- timespec_load(&st.st_mtim) != s->sysv_mtime)
- return true;
- }
-#endif
-
- return false;
-}
-
static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) {
Service *s = SERVICE(u);
int r = 0;
r = q;
goto finish;
}
-
- if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0)
+ q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, false, pid_set, NULL);
+ if (q < 0)
if (q != -EAGAIN && q != -ESRCH && q != -ENOENT)
r = q;
}
[SERVICE_FORKING] = "forking",
[SERVICE_ONESHOT] = "oneshot",
[SERVICE_DBUS] = "dbus",
- [SERVICE_NOTIFY] = "notify"
+ [SERVICE_NOTIFY] = "notify",
+ [SERVICE_IDLE] = "idle"
};
DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
"Unit\0"
"Service\0"
"Install\0",
- .show_status = true,
.init = service_init,
.done = service_done,
.reset_failed = service_reset_failed,
- .need_daemon_reload = service_need_daemon_reload,
-
.cgroup_notify_empty = service_cgroup_notify_event,
.notify_message = service_notify_message,
.bus_invalidating_properties = bus_service_invalidating_properties,
#ifdef HAVE_SYSV_COMPAT
- .enumerate = service_enumerate
+ .enumerate = service_enumerate,
#endif
+ .status_message_formats = {
+ .starting_stopping = {
+ [0] = "Starting %s...",
+ [1] = "Stopping %s...",
+ },
+ .finished_start_job = {
+ [JOB_DONE] = "Started %s.",
+ [JOB_FAILED] = "Failed to start %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ [JOB_TIMEOUT] = "Timed out starting %s.",
+ },
+ .finished_stop_job = {
+ [JOB_DONE] = "Stopped %s.",
+ [JOB_FAILED] = "Stopped (with error) %s.",
+ [JOB_TIMEOUT] = "Timed out stopping %s.",
+ },
+ },
};