#include "dbus-service.h"
#include "special.h"
#include "bus-errors.h"
-
-#define COMMENTS "#;\n"
-#define NEWLINES "\n\r"
+#include "exit-status.h"
+#include "def.h"
+#include "util.h"
#ifdef HAVE_SYSV_COMPAT
+
+#define DEFAULT_SYSV_TIMEOUT_USEC (5*USEC_PER_MINUTE)
+
typedef enum RunlevelType {
RUNLEVEL_UP,
RUNLEVEL_DOWN,
{ "boot.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT },
#endif
-#ifdef TARGET_DEBIAN
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_FRUGALWARE)
/* Debian style rcS.d */
{ "rcS.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT },
#endif
s->timer_watch.type = WATCH_INVALID;
#ifdef HAVE_SYSV_COMPAT
s->sysv_start_priority = -1;
+ s->sysv_start_priority_from_rcnd = -1;
#endif
s->socket_fd = -1;
+ s->guess_main_pid = true;
exec_context_init(&s->exec_context);
+ s->exec_context.std_output = u->meta.manager->default_std_output;
+ s->exec_context.std_error = u->meta.manager->default_std_error;
RATELIMIT_INIT(s->ratelimit, 10*USEC_PER_SEC, 5);
static void service_connection_unref(Service *s) {
assert(s);
- if (!s->socket)
+ if (!s->accept_socket)
return;
- socket_connection_unref(s->socket);
- s->socket = NULL;
+ socket_connection_unref(s->accept_socket);
+ s->accept_socket = NULL;
}
static void service_done(Unit *u) {
exec_context_done(&s->exec_context);
exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
s->control_command = NULL;
+ s->main_command = NULL;
/* This will leak a process, but at least no memory or any of
* our resources */
service_close_socket_fd(s);
service_connection_unref(s);
+ set_free(s->configured_sockets);
+
unit_unwatch_timer(u, &s->timer_watch);
}
if (!(r = new(char, strlen(name) + sizeof(".service"))))
return NULL;
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU)
+ if (endswith(name, ".sh"))
+ /* Drop Debian-style .sh suffix */
+ strcpy(stpcpy(r, name) - 3, ".service");
+#endif
+#ifdef TARGET_SUSE
if (startswith(name, "boot."))
/* Drop SuSE-style boot. prefix */
strcpy(stpcpy(r, name + 5), ".service");
- else if (endswith(name, ".sh"))
- /* Drop Debian-style .sh suffix */
- strcpy(stpcpy(r, name) - 3, ".service");
-#ifdef TARGET_ARCH
- else if (startswith(name, "@"))
- /* Drop Arch-style background prefix */
- strcpy(stpcpy(r, name + 1), ".service");
+#endif
+#ifdef TARGET_FRUGALWARE
+ if (startswith(name, "rc."))
+ /* Drop Frugalware-style rc. prefix */
+ strcpy(stpcpy(r, name + 3), ".service");
#endif
else
/* Normal init scripts */
return r;
}
-static int sysv_translate_facility(const char *name, char **_r) {
+static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
/* We silently ignore the $ prefix here. According to the LSB
* spec it simply indicates whether something is a
static const char * const table[] = {
/* LSB defined facilities */
"local_fs", SPECIAL_LOCAL_FS_TARGET,
+#ifndef TARGET_MANDRIVA
+ /* Due to unfortunate name selection in Mandriva,
+ * $network is provided by network-up which is ordered
+ * after network which actually starts interfaces.
+ * To break the loop, just ignore it */
"network", SPECIAL_NETWORK_TARGET,
+#endif
"named", SPECIAL_NSS_LOOKUP_TARGET,
"portmap", SPECIAL_RPCBIND_TARGET,
"remote_fs", SPECIAL_REMOTE_FS_TARGET,
"syslog", SPECIAL_SYSLOG_TARGET,
- "time", SPECIAL_RTC_SET_TARGET,
+ "time", SPECIAL_TIME_SYNC_TARGET,
- /* Debian extensions */
-#ifdef TARGET_DEBIAN
- "mail-transport-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
-#endif
+ /* common extensions */
"mail-transfer-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
"x-display-manager", SPECIAL_DISPLAY_MANAGER_SERVICE,
+ "null", NULL,
+
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU)
+ "mail-transport-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
+#endif
#ifdef TARGET_FEDORA
- /* Fedora extensions */
"MTA", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
"smtpdaemon", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
"httpd", SPECIAL_HTTP_DAEMON_TARGET,
#endif
- /* SuSE extensions */
- "null", NULL
-
+#ifdef TARGET_SUSE
+ "smtp", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
+#endif
};
unsigned i;
char *r;
+ const char *n;
- for (i = 0; i < ELEMENTSOF(table); i += 2)
+ assert(name);
+ assert(_r);
- if (streq(table[i], name) ||
- (*name == '$' && streq(table[i], name+1))) {
+ n = *name == '$' ? name + 1 : name;
- if (!table[i+1])
- return 0;
+ for (i = 0; i < ELEMENTSOF(table); i += 2) {
- if (!(r = strdup(table[i+1])))
- return -ENOMEM;
+ if (!streq(table[i], n))
+ continue;
- goto finish;
- }
+ if (!table[i+1])
+ return 0;
+
+ if (!(r = strdup(table[i+1])))
+ return -ENOMEM;
+
+ goto finish;
+ }
/* If we don't know this name, fallback heuristics to figure
- * out whether something is a target or an service alias. */
+ * out whether something is a target or a service alias. */
- if (*name == '$')
- r = unit_name_build(name+1, NULL, ".target");
+ if (*name == '$') {
+ if (!unit_prefix_is_valid(n))
+ return -EINVAL;
+
+ /* Facilities starting with $ are most likely targets */
+ r = unit_name_build(n, NULL, ".target");
+ } else if (filename && streq(name, filename))
+ /* Names equaling the file name of the services are redundant */
+ return 0;
else
- r = sysv_translate_name(name);
+ /* Everything else we assume to be normal service names */
+ r = sysv_translate_name(n);
if (!r)
return -ENOMEM;
finish:
-
- if (_r)
- *_r = r;
+ *_r = r;
return 1;
}
/* FIXME: Maybe we should compare the name here lexicographically? */
- if (!(r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0)
+ if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0)
return r;
}
* data from the LSB header. */
if (start_priority < 0 || start_priority > 99)
log_warning("[%s:%u] Start priority out of range. Ignoring.", path, line);
- else if (s->sysv_start_priority < 0)
+ else
s->sysv_start_priority = start_priority;
char_array_0(runlevels);
goto finish;
}
- if (streq(n, file_name_from_path(path))) {
- free(n);
- continue;
- }
-
- r = sysv_translate_facility(n, &m);
+ r = sysv_translate_facility(n, file_name_from_path(path), &m);
free(n);
if (r < 0)
if (unit_name_to_type(m) == UNIT_SERVICE)
r = unit_add_name(u, m);
- else {
- r = unit_add_dependency_by_name(u, UNIT_BEFORE, m, NULL, true);
-
- if (s->sysv_enabled) {
- int k;
-
- if ((k = unit_add_dependency_by_name_inverse(u, UNIT_WANTS, m, NULL, true)) < 0)
- r = k;
- }
- }
+ else
+ /* NB: SysV targets
+ * which are provided
+ * by a service are
+ * pulled in by the
+ * services, as an
+ * indication that the
+ * generic service is
+ * now available. This
+ * is strictly
+ * one-way. The
+ * targets do NOT pull
+ * in the SysV
+ * services! */
+ r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_WANTS, m, NULL, true);
if (r < 0)
log_error("[%s:%u] Failed to add LSB Provides name %s, ignoring: %s", path, line, m, strerror(-r));
goto finish;
}
- if (streq(n, file_name_from_path(path))) {
+ r = sysv_translate_facility(n, file_name_from_path(path), &m);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r));
free(n);
continue;
}
- r = sysv_translate_facility(n, &m);
free(n);
- if (r < 0)
- goto finish;
-
if (r == 0)
continue;
/* Don't timeout special services during boot (like fsck) */
s->timeout_usec = 0;
- }
+ } else
+ s->timeout_usec = DEFAULT_SYSV_TIMEOUT_USEC;
/* Special setting for all SysV services */
s->type = SERVICE_FORKING;
s->remain_after_exit = true;
- s->restart = SERVICE_ONCE;
+ s->restart = SERVICE_RESTART_NO;
s->exec_context.std_output =
(s->meta.manager->sysv_console || s->exec_context.std_input == EXEC_INPUT_TTY)
- ? EXEC_OUTPUT_TTY : EXEC_OUTPUT_NULL;
- s->exec_context.kill_mode = KILL_PROCESS_GROUP;
+ ? EXEC_OUTPUT_TTY : s->meta.manager->default_std_output;
+ s->exec_context.kill_mode = KILL_PROCESS;
/* We use the long description only if
* no short description is set. */
if (description) {
char *d;
- if (!(d = strappend("LSB: ", description))) {
+ if (!(d = strappend(s->sysv_has_lsb ? "LSB: " : "SYSV: ", description))) {
r = -ENOMEM;
goto finish;
}
u->meta.description = d;
}
+ /* The priority that has been set in /etc/rcN.d/ hierarchies
+ * takes precedence over what is stored as default in the LSB
+ * header */
+ if (s->sysv_start_priority_from_rcnd >= 0)
+ s->sysv_start_priority = s->sysv_start_priority_from_rcnd;
+
u->meta.load_state = UNIT_LOADED;
r = 0;
assert(s);
assert(name);
- /* For SysV services we strip the boot. or .sh
+ /* For SysV services we strip the boot.*, rc.* and *.sh
* prefixes/suffixes. */
- if (startswith(name, "boot.") ||
- endswith(name, ".sh.service"))
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU)
+ if (endswith(name, ".sh.service"))
+ return -ENOENT;
+#endif
+
+#ifdef TARGET_SUSE
+ if (startswith(name, "boot."))
+ return -ENOENT;
+#endif
+
+#ifdef TARGET_FRUGALWARE
+ if (startswith(name, "rc."))
return -ENOENT;
+#endif
STRV_FOREACH(p, s->meta.manager->lookup_paths.sysvinit_path) {
char *path;
r = service_load_sysv_path(s, path);
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU)
if (r >= 0 && s->meta.load_state == UNIT_STUB) {
- /* Try Debian style xxx.sh source'able init scripts */
+ /* Try Debian style *.sh source'able init scripts */
strcat(path, ".sh");
r = service_load_sysv_path(s, path);
}
-
+#endif
free(path);
+#ifdef TARGET_SUSE
if (r >= 0 && s->meta.load_state == UNIT_STUB) {
- /* Try SUSE style boot.xxx init scripts */
+ /* Try SUSE style boot.* init scripts */
if (asprintf(&path, "%s/boot.%s", *p, name) < 0)
return -ENOMEM;
+ /* Drop .service suffix */
path[strlen(path)-8] = 0;
r = service_load_sysv_path(s, path);
free(path);
}
+#endif
+
+#ifdef TARGET_FRUGALWARE
+ if (r >= 0 && s->meta.load_state == UNIT_STUB) {
+ /* Try Frugalware style rc.* init scripts */
+
+ if (asprintf(&path, "%s/rc.%s", *p, name) < 0)
+ return -ENOMEM;
+
+ /* Drop .service suffix */
+ path[strlen(path)-8] = 0;
+ r = service_load_sysv_path(s, path);
+ free(path);
+ }
+#endif
if (r < 0)
return r;
}
#endif
+static int fsck_fix_order(Service *s) {
+ Meta *other;
+ int r;
+
+ assert(s);
+
+ if (s->fsck_passno <= 0)
+ return 0;
+
+ /* For each pair of services where both have an fsck priority
+ * we order things based on it. */
+
+ LIST_FOREACH(units_per_type, other, s->meta.manager->units_per_type[UNIT_SERVICE]) {
+ Service *t;
+ UnitDependency d;
+
+ t = (Service*) other;
+
+ if (s == t)
+ continue;
+
+ if (t->meta.load_state != UNIT_LOADED)
+ continue;
+
+ if (t->fsck_passno <= 0)
+ continue;
+
+ if (t->fsck_passno < s->fsck_passno)
+ d = UNIT_AFTER;
+ else if (t->fsck_passno > s->fsck_passno)
+ d = UNIT_BEFORE;
+ else
+ continue;
+
+ if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int service_verify(Service *s) {
assert(s);
return -EINVAL;
}
+ if (s->type == SERVICE_ONESHOT &&
+ s->exec_command[SERVICE_EXEC_RELOAD]) {
+ log_error("%s has an ExecReload setting, which is not allowed for Type=oneshot services. Refusing.", s->meta.id);
+ return -EINVAL;
+ }
+
if (s->type == SERVICE_DBUS && !s->bus_name) {
log_error("%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", s->meta.id);
return -EINVAL;
if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
return r;
- } else if (s->meta.manager->running_as == MANAGER_SESSION) {
+ } else if (s->meta.manager->running_as == MANAGER_USER) {
if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0)
return r;
}
/* Second, activate normal shutdown */
- return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTED_BY, SPECIAL_SHUTDOWN_TARGET, NULL, true);
+ return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
}
static int service_load(Unit *u) {
if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
return r;
- if ((r = unit_add_default_cgroup(u)) < 0)
+ if ((r = unit_add_default_cgroups(u)) < 0)
return r;
#ifdef HAVE_SYSV_COMPAT
return r;
#endif
+ if ((r = fsck_fix_order(s)) < 0)
+ return r;
+
if (s->bus_name)
if ((r = unit_watch_bus_name(u, s->bus_name)) < 0)
return r;
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_TARGET, NULL, true)) < 0)
+ if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true)) < 0)
return r;
if (s->meta.default_dependencies)
"%sPermissionsStartOnly: %s\n"
"%sRootDirectoryStartOnly: %s\n"
"%sRemainAfterExit: %s\n"
+ "%sGuessMainPID: %s\n"
"%sType: %s\n"
"%sRestart: %s\n"
"%sNotifyAccess: %s\n",
prefix, yes_no(s->permissions_start_only),
prefix, yes_no(s->root_directory_start_only),
prefix, yes_no(s->remain_after_exit),
+ prefix, yes_no(s->guess_main_pid),
prefix, service_type_to_string(s->type),
prefix, service_restart_to_string(s->restart),
prefix, notify_access_to_string(s->notify_access));
if (s->sysv_path)
fprintf(f,
"%sSysV Init Script Path: %s\n"
- "%sSysV Init Script has LSB Header: %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_has_lsb),
+ prefix, yes_no(s->sysv_enabled));
if (s->sysv_start_priority >= 0)
fprintf(f,
- "%sSysVStartPriority: %i\n"
- "%sSysVEnabled: %s\n",
- prefix, s->sysv_start_priority,
- prefix, yes_no(s->sysv_enabled));
+ "%sSysVStartPriority: %i\n",
+ prefix, s->sysv_start_priority);
if (s->sysv_runlevels)
fprintf(f, "%sSysVRunLevels: %s\n",
prefix, s->sysv_runlevels);
#endif
+ if (s->fsck_passno > 0)
+ fprintf(f,
+ "%sFsckPassNo: %i\n",
+ prefix, s->fsck_passno);
+
if (s->status_text)
fprintf(f, "%sStatus Text: %s\n",
prefix, s->status_text);
assert(s);
- if (s->main_pid_known)
- return 0;
-
- assert(s->main_pid <= 0);
-
if (!s->pid_file)
return -ENOENT;
return 0;
}
+static int service_search_main_pid(Service *s) {
+ pid_t pid;
+ int r;
+
+ assert(s);
+
+ /* If we know it anyway, don't ever fallback to unreliable
+ * heuristics */
+ if (s->main_pid_known)
+ return 0;
+
+ if (!s->guess_main_pid)
+ return 0;
+
+ assert(s->main_pid <= 0);
+
+ if ((pid = cgroup_bonding_search_main_pid_list(s->meta.cgroup_bondings)) <= 0)
+ return -ENOENT;
+
+ if ((r = service_set_main_pid(s, pid)) < 0)
+ return r;
+
+ if ((r = unit_watch_pid(UNIT(s), pid)) < 0)
+ /* FIXME: we need to do something here */
+ return r;
+
+ return 0;
+}
+
static int service_get_sockets(Service *s, Set **_set) {
Set *set;
Iterator i;
if (s->socket_fd >= 0)
return 0;
+ if (!set_isempty(s->configured_sockets))
+ return 0;
+
/* Collects all Socket objects that belong to this
* service. Note that a service might have multiple sockets
* via multiple names. */
static int service_notify_sockets_dead(Service *s) {
Iterator i;
- Set *set;
+ Set *set, *free_set = NULL;
Socket *sock;
int r;
assert(s);
+ /* Notifies all our sockets when we die */
+
if (s->socket_fd >= 0)
return 0;
- /* Notifies all our sockets when we die */
- if ((r = service_get_sockets(s, &set)) < 0)
- return r;
+ if (!set_isempty(s->configured_sockets))
+ set = s->configured_sockets;
+ else {
+ if ((r = service_get_sockets(s, &free_set)) < 0)
+ return r;
+
+ set = free_set;
+ }
SET_FOREACH(sock, set, i)
socket_notify_service_dead(sock);
- set_free(set);
+ set_free(free_set);
return 0;
}
state != SERVICE_RELOAD &&
state != SERVICE_STOP &&
state != SERVICE_STOP_SIGTERM &&
- state != SERVICE_STOP_SIGKILL)
+ state != SERVICE_STOP_SIGKILL) {
service_unwatch_main_pid(s);
+ s->main_command = NULL;
+ }
if (state != SERVICE_START_PRE &&
state != SERVICE_START &&
if (old_state != state)
log_debug("%s changed %s -> %s", s->meta.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]);
+ unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], !s->reload_failure);
+ s->reload_failure = false;
}
static int service_coldplug(Unit *u) {
int r;
int *rfds = NULL;
unsigned rn_fds = 0;
- Set *set;
+ Set *set, *free_set = NULL;
Socket *sock;
assert(s);
if (s->socket_fd >= 0)
return 0;
- if ((r = service_get_sockets(s, &set)) < 0)
- return r;
+ if (!set_isempty(s->configured_sockets))
+ set = s->configured_sockets;
+ else {
+ if ((r = service_get_sockets(s, &free_set)) < 0)
+ return r;
+
+ set = free_set;
+ }
SET_FOREACH(sock, set, i) {
int *cfds;
goto fail;
}
- memcpy(t, rfds, rn_fds);
- memcpy(t+rn_fds, cfds, cn_fds);
+ memcpy(t, rfds, rn_fds * sizeof(int));
+ memcpy(t+rn_fds, cfds, cn_fds * sizeof(int));
free(rfds);
free(cfds);
*fds = rfds;
*n_fds = rn_fds;
- set_free(set);
+ set_free(free_set);
return 0;
goto fail;
}
- if (!(our_env = new0(char*, 3))) {
+ if (!(our_env = new0(char*, 4))) {
r = -ENOMEM;
goto fail;
}
if (set_notify_socket)
- if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=@%s", s->meta.manager->notify_socket) < 0) {
+ if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", s->meta.manager->notify_socket) < 0) {
r = -ENOMEM;
goto fail;
}
if (allow_restart &&
!s->forbid_restart &&
(s->restart == SERVICE_RESTART_ALWAYS ||
- (s->restart == SERVICE_RESTART_ON_SUCCESS && !s->failure))) {
+ (s->restart == SERVICE_RESTART_ON_SUCCESS && !s->failure) ||
+ (s->restart == SERVICE_RESTART_ON_FAILURE && s->failure) ||
+ (s->restart == SERVICE_RESTART_ON_ABORT && s->failure &&
+ (s->main_exec_status.code == CLD_KILLED ||
+ s->main_exec_status.code == CLD_DUMPED)))) {
if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0)
goto fail;
service_unwatch_control_pid(s);
- s->control_command_id = SERVICE_EXEC_STOP_POST;
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,
int sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? s->exec_context.kill_signal : SIGKILL;
if (s->main_pid > 0) {
- if (kill(s->exec_context.kill_mode == KILL_PROCESS_GROUP ?
- -s->main_pid :
- s->main_pid, sig) < 0 && errno != ESRCH)
-
+ if (kill_and_sigcont(s->main_pid, sig) < 0 && errno != ESRCH)
log_warning("Failed to kill main process %li: %m", (long) s->main_pid);
else
wait_for_exit = true;
}
if (s->control_pid > 0) {
- if (kill(s->exec_context.kill_mode == KILL_PROCESS_GROUP ?
- -s->control_pid :
- s->control_pid, sig) < 0 && errno != ESRCH)
+ if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH)
log_warning("Failed to kill control process %li: %m", (long) s->control_pid);
else
if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0)
goto fail;
- if ((r = cgroup_bonding_kill_list(s->meta.cgroup_bondings, sig, pid_set)) < 0) {
+ if ((r = cgroup_bonding_kill_list(s->meta.cgroup_bondings, sig, true, pid_set)) < 0) {
if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
log_warning("Failed to kill control group: %s", strerror(-r));
} else if (r > 0)
wait_for_exit = true;
set_free(pid_set);
+ pid_set = NULL;
}
}
service_unwatch_control_pid(s);
- s->control_command_id = SERVICE_EXEC_STOP;
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,
service_unwatch_control_pid(s);
- s->control_command_id = SERVICE_EXEC_START_POST;
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,
static void service_enter_start(Service *s) {
pid_t pid;
int r;
+ ExecCommand *c;
assert(s);
else
service_unwatch_main_pid(s);
- s->control_command_id = SERVICE_EXEC_START;
- s->control_command = s->exec_command[SERVICE_EXEC_START];
+ if (s->type == SERVICE_FORKING) {
+ s->control_command_id = SERVICE_EXEC_START;
+ c = s->control_command = s->exec_command[SERVICE_EXEC_START];
+
+ s->main_command = NULL;
+ } else {
+ s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+ s->control_command = NULL;
+
+ c = s->main_command = s->exec_command[SERVICE_EXEC_START];
+ }
if ((r = service_spawn(s,
- s->control_command,
+ c,
s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY,
true,
true,
service_unwatch_control_pid(s);
- s->control_command_id = SERVICE_EXEC_START_PRE;
if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) {
+ s->control_command_id = SERVICE_EXEC_START_PRE;
+
if ((r = service_spawn(s,
s->control_command,
true,
assert(s);
dbus_error_init(&error);
+ if (s->meta.job) {
+ log_info("Job pending for unit, delaying automatic restart.");
+
+ if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0)
+ goto fail;
+ }
+
service_enter_dead(s, true, false);
if ((r = manager_add_job(s->meta.manager, JOB_START, UNIT(s), JOB_FAIL, false, &error, NULL)) < 0)
service_unwatch_control_pid(s);
- s->control_command_id = SERVICE_EXEC_RELOAD;
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,
fail:
log_warning("%s failed to run 'reload' task: %s", s->meta.id, strerror(-r));
- service_enter_stop(s, false);
+ s->reload_failure = true;
+ service_enter_running(s, true);
}
static void service_run_next_control(Service *s, bool success) {
service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
else if (s->state == SERVICE_STOP_POST)
service_enter_dead(s, false, true);
- else
+ else if (s->state == SERVICE_RELOAD) {
+ s->reload_failure = true;
+ service_enter_running(s, true);
+ } else
service_enter_stop(s, false);
}
int r;
assert(s);
- assert(s->control_command);
- assert(s->control_command->command_next);
+ assert(s->main_command);
+ assert(s->main_command->command_next);
+ assert(s->type == SERVICE_ONESHOT);
if (!success)
s->failure = true;
- assert(s->control_command_id == SERVICE_EXEC_START);
- assert(s->type == SERVICE_ONESHOT);
-
- s->control_command = s->control_command->command_next;
+ s->main_command = s->main_command->command_next;
service_unwatch_main_pid(s);
if ((r = service_spawn(s,
- s->control_command,
+ s->main_command,
false,
true,
true,
return -ECANCELED;
}
- if ((s->exec_context.std_input == EXEC_INPUT_SOCKET ||
- s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
- s->exec_context.std_error == EXEC_OUTPUT_SOCKET) &&
- s->socket_fd < 0) {
- log_warning("%s can only be started with a per-connection socket.", u->meta.id);
- return -EINVAL;
- }
-
s->failure = false;
s->main_pid_known = false;
s->forbid_restart = false;
if (s->main_exec_status.pid > 0) {
unit_serialize_item_format(u, f, "main-exec-status-pid", "%lu", (unsigned long) s->main_exec_status.pid);
+ dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp);
+ dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp);
- if (s->main_exec_status.start_timestamp.realtime > 0) {
- unit_serialize_item_format(u, f, "main-exec-status-start-realtime",
- "%llu", (unsigned long long) s->main_exec_status.start_timestamp.realtime);
-
- unit_serialize_item_format(u, f, "main-exec-status-start-monotonic",
- "%llu", (unsigned long long) s->main_exec_status.start_timestamp.monotonic);
- }
-
- if (s->main_exec_status.exit_timestamp.realtime > 0) {
- unit_serialize_item_format(u, f, "main-exec-status-exit-realtime",
- "%llu", (unsigned long long) s->main_exec_status.exit_timestamp.realtime);
- unit_serialize_item_format(u, f, "main-exec-status-exit-monotonic",
- "%llu", (unsigned long long) s->main_exec_status.exit_timestamp.monotonic);
-
+ if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) {
unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code);
unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status);
}
log_debug("Failed to parse main-exec-status-status value %s", value);
else
s->main_exec_status.status = i;
- } else if (streq(key, "main-exec-status-start-realtime")) {
- uint64_t k;
-
- if (safe_atou64(value, &k) < 0)
- log_debug("Failed to parse main-exec-status-start-realtime value %s", value);
- else
- s->main_exec_status.start_timestamp.realtime = (usec_t) k;
- } else if (streq(key, "main-exec-status-start-monotonic")) {
- uint64_t k;
-
- if (safe_atou64(value, &k) < 0)
- log_debug("Failed to parse main-exec-status-start-monotonic value %s", value);
- else
- s->main_exec_status.start_timestamp.monotonic = (usec_t) k;
- } else if (streq(key, "main-exec-status-exit-realtime")) {
- uint64_t k;
-
- if (safe_atou64(value, &k) < 0)
- log_debug("Failed to parse main-exec-status-exit-realtime value %s", value);
- else
- s->main_exec_status.exit_timestamp.realtime = (usec_t) k;
- } else if (streq(key, "main-exec-status-exit-monotonic")) {
- uint64_t k;
-
- if (safe_atou64(value, &k) < 0)
- log_debug("Failed to parse main-exec-status-exit-monotonic value %s", value);
- else
- s->main_exec_status.exit_timestamp.monotonic = (usec_t) k;
- } else
+ } else if (streq(key, "main-exec-status-start"))
+ dual_timestamp_deserialize(value, &s->main_exec_status.start_timestamp);
+ else if (streq(key, "main-exec-status-exit"))
+ dual_timestamp_deserialize(value, &s->main_exec_status.exit_timestamp);
+ else
log_debug("Unknown serialization key '%s'", key);
return 0;
return service_state_to_string(SERVICE(u)->state);
}
-#ifdef HAVE_SYSV_COMPAT
static bool service_check_gc(Unit *u) {
Service *s = SERVICE(u);
assert(s);
- return !!s->sysv_path;
-}
+ /* Never clean up services that still have a process around,
+ * even if the service is formally dead. */
+ if (cgroup_good(s) > 0 ||
+ main_pid_good(s) > 0 ||
+ control_pid_good(s) > 0)
+ return true;
+
+#ifdef HAVE_SYSV_COMPAT
+ if (s->sysv_path)
+ return true;
#endif
+ return false;
+}
+
static bool service_check_snapshot(Unit *u) {
Service *s = SERVICE(u);
if (s->main_pid == pid) {
s->main_pid = 0;
- exec_status_exit(&s->main_exec_status, pid, code, status);
+ exec_status_exit(&s->main_exec_status, pid, code, status, s->exec_context.utmp_id);
- if (s->type != SERVICE_FORKING && s->control_command) {
- s->control_command->exec_status = s->main_exec_status;
+ /* If this is not a forking service than the main
+ * process got started and hence we copy the exit
+ * status so that it is recorded both as main and as
+ * control process exit status */
+ if (s->main_command) {
+ s->main_command->exec_status = s->main_exec_status;
- if (s->control_command->ignore)
+ if (s->main_command->ignore)
success = true;
}
"%s: main process exited, code=%s, status=%i", u->meta.id, sigchld_code_to_string(code), status);
s->failure = s->failure || !success;
- if (s->control_command &&
- s->control_command->command_next &&
+ if (s->main_command &&
+ s->main_command->command_next &&
success) {
/* There is another command to *
/* The service exited, so the service is officially
* gone. */
-
- s->control_command = NULL;
- s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+ s->main_command = NULL;
switch (s->state) {
s->control_pid = 0;
if (s->control_command) {
- exec_status_exit(&s->control_command->exec_status, pid, code, status);
+ exec_status_exit(&s->control_command->exec_status, pid, code, status, s->exec_context.utmp_id);
if (s->control_command->ignore)
success = true;
* START_POST script */
if (success) {
- if (s->pid_file)
- service_load_pid_file(s);
+ service_load_pid_file(s);
+ service_search_main_pid(s);
service_enter_start_post(s);
} else
log_warning("%s: failed to load PID file %s: %s", s->meta.id, s->pid_file, strerror(-r));
}
- /* Fall through */
+ s->reload_failure = !success;
+ service_enter_running(s, true);
+ break;
case SERVICE_RELOAD:
- if (success)
- service_enter_running(s, true);
- else
- service_enter_stop(s, false);
+ if (success) {
+ service_load_pid_file(s);
+ service_search_main_pid(s);
+ }
+ s->reload_failure = !success;
+ service_enter_running(s, true);
break;
case SERVICE_STOP:
break;
case SERVICE_START_POST:
- case SERVICE_RELOAD:
log_warning("%s operation timed out. Stopping.", u->meta.id);
service_enter_stop(s, false);
break;
+ case SERVICE_RELOAD:
+ log_warning("%s operation timed out. Stopping.", u->meta.id);
+ s->reload_failure = true;
+ service_enter_running(s, true);
+ break;
+
case SERVICE_STOP:
log_warning("%s stopping timed out. Terminating.", u->meta.id);
service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
break;
case SERVICE_STOP_SIGTERM:
- log_warning("%s stopping timed out. Killing.", u->meta.id);
- service_enter_signal(s, SERVICE_STOP_SIGKILL, false);
+ if (s->exec_context.send_sigkill) {
+ log_warning("%s stopping timed out. Killing.", u->meta.id);
+ service_enter_signal(s, SERVICE_STOP_SIGKILL, false);
+ } else {
+ log_warning("%s stopping timed out. Skipping SIGKILL.", u->meta.id);
+ service_enter_stop_post(s, false);
+ }
+
break;
case SERVICE_STOP_SIGKILL:
- /* Uh, wie sent a SIGKILL and it is still not gone?
+ /* Uh, we sent a SIGKILL and it is still not gone?
* Must be something we cannot kill, so let's just be
* weirded out and continue */
break;
case SERVICE_FINAL_SIGTERM:
- log_warning("%s stopping timed out (2). Killing.", u->meta.id);
- service_enter_signal(s, SERVICE_FINAL_SIGKILL, false);
+ if (s->exec_context.send_sigkill) {
+ log_warning("%s stopping timed out (2). Killing.", u->meta.id);
+ service_enter_signal(s, SERVICE_FINAL_SIGKILL, false);
+ } else {
+ log_warning("%s stopping timed out (2). Skipping SIGKILL. Entering failed mode.", u->meta.id);
+ service_enter_dead(s, false, true);
+ }
+
break;
case SERVICE_FINAL_SIGKILL:
Unit *service;
Iterator j;
int r;
-#ifdef TARGET_ARCH
- Unit *previous = NULL;
- char *arch_daemons = NULL;
- char *arch_daemons_stripped = NULL;
- char **arch_daemons_split = NULL;
-#endif
assert(m);
-#ifdef TARGET_ARCH
- if ((r = parse_env_file("/etc/rc.conf", NEWLINE,
- "DAEMONS", &arch_daemons,
- NULL)) < 0) {
-
- if (r != -ENOENT)
- log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
-
- } else if (arch_daemons) {
- if (!(arch_daemons_stripped = strchr(arch_daemons, '(')))
- arch_daemons_stripped = arch_daemons;
- else
- arch_daemons_stripped++; /* strip start paren */
-
- arch_daemons_stripped[strcspn(arch_daemons_stripped, ")")] = 0; /* strip end paren */
-
- if (!(arch_daemons_split = strv_split_quoted(arch_daemons_stripped))) {
- r = -ENOMEM;
- goto finish;
- }
-
- STRV_FOREACH(p, arch_daemons_split) {
-
- free(name);
-
- if (**p == '!') /* daemons prefixed with ! are disabled, so ignore them */
- continue;
-
- if (!(name = sysv_translate_name(*p))) {
- r = -ENOMEM;
- goto finish;
- }
-
- if ((r = manager_load_unit_prepare(m, name, NULL, NULL, &service)) < 0) {
- log_warning("Failed to prepare unit %s: %s", name, strerror(-r));
- continue;
- }
-
- if ((r = unit_add_two_dependencies_by_name_inverse(service, UNIT_AFTER, UNIT_WANTS, "multi-user.target", NULL, true)) < 0)
- goto finish;
-
- if (previous)
- if ((r = unit_add_dependency(service, UNIT_AFTER, previous, true)) < 0)
- goto finish;
-
- if (**p != '@') /* daemons prefixed with @ can be started in the background */
- previous = service;
- }
- }
-#endif
-
zero(runlevel_services);
STRV_FOREACH(p, m->lookup_paths.sysvrcnd_path)
if (de->d_name[0] == 'S') {
if (rcnd_table[i].type == RUNLEVEL_UP || rcnd_table[i].type == RUNLEVEL_SYSINIT) {
- SERVICE(service)->sysv_start_priority =
- MAX(a*10 + b, SERVICE(service)->sysv_start_priority);
+ SERVICE(service)->sysv_start_priority_from_rcnd =
+ MAX(a*10 + b, SERVICE(service)->sysv_start_priority_from_rcnd);
SERVICE(service)->sysv_enabled = true;
}
/* We honour K links only for halt/reboot. For the normal
* runlevels we assume the stop jobs will be implicitly added
- * by the core logic. Also, we don't really distuingish here
+ * by the core logic. Also, we don't really distinguish here
* between the runlevels 0 and 6 and just add them to the
* special shutdown target. On SUSE the boot.d/ runlevel is
* also used for shutdown, so we add links for that too to the
if (service->meta.fragment_path)
continue;
- if ((r = unit_add_two_dependencies_by_name_inverse(service, UNIT_AFTER, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true)) < 0)
+ if ((r = unit_add_two_dependencies_by_name(service, UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true)) < 0)
goto finish;
}
free(path);
free(fpath);
free(name);
-#ifdef TARGET_ARCH
- free(arch_daemons);
- free(arch_daemons_split);
-#endif
for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
set_free(runlevel_services[i]);
s->socket_fd = fd;
s->got_socket_fd = true;
- s->socket = sock;
+ s->accept_socket = sock;
return 0;
}
s->failure = false;
}
+static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) {
+ Service *s = SERVICE(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(s);
+
+ if (s->main_pid <= 0 && who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill");
+ return -EINVAL;
+ }
+
+ if (s->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ENOENT;
+ }
+
+ if (s->control_pid > 0)
+ if (kill(s->control_pid, signo) < 0)
+ r = -errno;
+
+ if (s->main_pid > 0)
+ if (kill(s->main_pid, signo) < 0)
+ r = -errno;
+
+ if (mode == KILL_CONTROL_GROUP) {
+ int q;
+
+ if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ /* Exclude the control/main pid from being killed via the cgroup */
+ if (s->control_pid > 0)
+ if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) {
+ r = q;
+ goto finish;
+ }
+
+ if (s->main_pid > 0)
+ if ((q = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) {
+ r = q;
+ goto finish;
+ }
+
+ if ((q = cgroup_bonding_kill_list(s->meta.cgroup_bondings, signo, false, pid_set)) < 0)
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_DEAD] = "dead",
[SERVICE_START_PRE] = "start-pre",
DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
- [SERVICE_ONCE] = "once",
- [SERVICE_RESTART_ON_SUCCESS] = "restart-on-success",
- [SERVICE_RESTART_ALWAYS] = "restart-always",
+ [SERVICE_RESTART_NO] = "no",
+ [SERVICE_RESTART_ON_SUCCESS] = "on-success",
+ [SERVICE_RESTART_ON_FAILURE] = "on-failure",
+ [SERVICE_RESTART_ON_ABORT] = "on-abort",
+ [SERVICE_RESTART_ALWAYS] = "always"
};
DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart);
.can_reload = service_can_reload,
+ .kill = service_kill,
+
.serialize = service_serialize,
.deserialize_item = service_deserialize_item,
.active_state = service_active_state,
.sub_state_to_string = service_sub_state_to_string,
-#ifdef HAVE_SYSV_COMPAT
.check_gc = service_check_gc,
-#endif
.check_snapshot = service_check_snapshot,
.sigchld_event = service_sigchld_event,