X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fservice.c;h=a297cd911724540c75a1d98483139f1996559e2f;hp=b18c950baaaeef015943a85f3f25ed16b70094c3;hb=9c1b183c709b90e735b60294d7be00b37814645a;hpb=ee95669fb6922d6f45601ab76db5f19b3d8c0eb6 diff --git a/src/service.c b/src/service.c index b18c950ba..a297cd911 100644 --- a/src/service.c +++ b/src/service.c @@ -34,11 +34,14 @@ #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, @@ -62,7 +65,7 @@ static const struct { { "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 @@ -112,10 +115,14 @@ static void service_init(Unit *u) { 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); @@ -178,11 +185,11 @@ static void service_close_socket_fd(Service *s) { 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) { @@ -207,6 +214,7 @@ 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 */ @@ -222,6 +230,8 @@ static void service_done(Unit *u) { service_close_socket_fd(s); service_connection_unref(s); + set_free(s->configured_sockets); + unit_unwatch_timer(u, &s->timer_watch); } @@ -232,16 +242,20 @@ static char *sysv_translate_name(const char *name) { 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 */ @@ -261,30 +275,37 @@ static int sysv_translate_facility(const char *name, const char *filename, char 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, - /* 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; @@ -311,13 +332,16 @@ static int sysv_translate_facility(const char *name, const char *filename, char } /* 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 == '$') { + if (!unit_prefix_is_valid(n)) + return -EINVAL; - if (*name == '$') /* Facilities starting with $ are most likely targets */ r = unit_name_build(n, NULL, ".target"); - else if (filename && streq(name, filename)) - /* Names equalling the file name of the services are redundant */ + } else if (filename && streq(name, filename)) + /* Names equaling the file name of the services are redundant */ return 0; else /* Everything else we assume to be normal service names */ @@ -327,9 +351,7 @@ static int sysv_translate_facility(const char *name, const char *filename, char return -ENOMEM; finish: - - if (_r) - *_r = r; + *_r = r; return 1; } @@ -384,7 +406,7 @@ static int sysv_fix_order(Service *s) { /* 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; } @@ -516,7 +538,7 @@ static int service_load_sysv_path(Service *s, const char *path) { * 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); @@ -634,16 +656,21 @@ static int service_load_sysv_path(Service *s, const char *path) { 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)); @@ -669,10 +696,14 @@ static int service_load_sysv_path(Service *s, const char *path) { } r = sysv_translate_facility(n, file_name_from_path(path), &m); - free(n); - if (r < 0) - goto finish; + if (r < 0) { + log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r)); + free(n); + continue; + } + + free(n); if (r == 0) continue; @@ -788,16 +819,17 @@ static int service_load_sysv_path(Service *s, const char *path) { /* 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. */ @@ -814,7 +846,7 @@ static int service_load_sysv_path(Service *s, const char *path) { if (description) { char *d; - if (!(d = strappend("LSB: ", description))) { + if (!(d = strappend(s->sysv_has_lsb ? "LSB: " : "SYSV: ", description))) { r = -ENOMEM; goto finish; } @@ -822,6 +854,12 @@ static int service_load_sysv_path(Service *s, const char *path) { 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; @@ -843,11 +881,22 @@ static int service_load_sysv_name(Service *s, const char *name) { 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; @@ -861,24 +910,42 @@ static int service_load_sysv_name(Service *s, const char *name) { 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; @@ -923,6 +990,47 @@ static int service_load_sysv(Service *s) { } #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); @@ -940,6 +1048,12 @@ static int service_verify(Service *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; @@ -967,14 +1081,14 @@ static int service_add_default_dependencies(Service *s) { 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) { @@ -1008,7 +1122,7 @@ 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 @@ -1016,6 +1130,9 @@ static int service_load(Unit *u) { 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; @@ -1024,7 +1141,7 @@ static int service_load(Unit *u) { 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) @@ -1052,6 +1169,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { "%sPermissionsStartOnly: %s\n" "%sRootDirectoryStartOnly: %s\n" "%sRemainAfterExit: %s\n" + "%sGuessMainPID: %s\n" "%sType: %s\n" "%sRestart: %s\n" "%sNotifyAccess: %s\n", @@ -1059,6 +1177,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { 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)); @@ -1102,22 +1221,27 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { 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); @@ -1132,11 +1256,6 @@ static int service_load_pid_file(Service *s) { assert(s); - if (s->main_pid_known) - return 0; - - assert(s->main_pid <= 0); - if (!s->pid_file) return -ENOENT; @@ -1165,6 +1284,35 @@ static int service_load_pid_file(Service *s) { 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; @@ -1177,6 +1325,9 @@ static int service_get_sockets(Service *s, Set **_set) { 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. */ @@ -1216,23 +1367,30 @@ fail: 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; } @@ -1263,8 +1421,10 @@ static void service_set_state(Service *s, ServiceState state) { 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 && @@ -1316,7 +1476,8 @@ static void service_set_state(Service *s, ServiceState state) { 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) { @@ -1390,7 +1551,7 @@ static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { int r; int *rfds = NULL; unsigned rn_fds = 0; - Set *set; + Set *set, *free_set = NULL; Socket *sock; assert(s); @@ -1400,8 +1561,14 @@ static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { 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; @@ -1425,8 +1592,8 @@ static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { 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); @@ -1438,7 +1605,7 @@ static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { *fds = rfds; *n_fds = rn_fds; - set_free(set); + set_free(free_set); return 0; @@ -1497,13 +1664,13 @@ static int service_spawn( 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; } @@ -1605,7 +1772,11 @@ static void service_enter_dead(Service *s, bool success, bool allow_restart) { 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; @@ -1634,8 +1805,9 @@ static void service_enter_stop_post(Service *s, bool success) { 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, @@ -1673,19 +1845,14 @@ static void service_enter_signal(Service *s, ServiceState state, bool success) { 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 @@ -1708,13 +1875,14 @@ static void service_enter_signal(Service *s, ServiceState state, bool success) { 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; } } @@ -1753,8 +1921,9 @@ static void service_enter_stop(Service *s, bool success) { 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, @@ -1802,8 +1971,9 @@ static void service_enter_start_post(Service *s) { 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, @@ -1829,6 +1999,7 @@ fail: static void service_enter_start(Service *s) { pid_t pid; int r; + ExecCommand *c; assert(s); @@ -1840,11 +2011,20 @@ static void service_enter_start(Service *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, @@ -1899,8 +2079,9 @@ static void service_enter_start_pre(Service *s) { 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, @@ -1930,6 +2111,13 @@ static void service_enter_restart(Service *s) { 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) @@ -1952,8 +2140,9 @@ static void service_enter_reload(Service *s) { 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, @@ -1973,7 +2162,8 @@ static void service_enter_reload(Service *s) { 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) { @@ -2014,7 +2204,10 @@ fail: 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); } @@ -2023,20 +2216,18 @@ static void service_run_next_main(Service *s, bool success) { 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, @@ -2084,14 +2275,6 @@ static int service_start(Unit *u) { 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; @@ -2198,21 +2381,10 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { 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); } @@ -2313,35 +2485,11 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, 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; @@ -2359,16 +2507,26 @@ static const char *service_sub_state_to_string(Unit *u) { 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); @@ -2392,12 +2550,16 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { 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; } @@ -2405,8 +2567,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { "%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 * @@ -2419,9 +2581,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { /* 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) { @@ -2469,7 +2629,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { 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; @@ -2518,8 +2678,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { * 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 @@ -2540,14 +2700,18 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { 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: @@ -2597,23 +2761,34 @@ static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { 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 */ @@ -2627,8 +2802,14 @@ static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { 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: @@ -2763,67 +2944,9 @@ static int service_enumerate(Manager *m) { 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); - name = NULL; - - 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) @@ -2894,8 +3017,8 @@ static int service_enumerate(Manager *m) { 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; } @@ -2940,7 +3063,7 @@ static int service_enumerate(Manager *m) { /* 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 @@ -2951,7 +3074,7 @@ static int service_enumerate(Manager *m) { 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; } @@ -2961,10 +3084,6 @@ 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]); @@ -3062,7 +3181,7 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock) { s->socket_fd = fd; s->got_socket_fd = true; - s->socket = sock; + s->accept_socket = sock; return 0; } @@ -3078,6 +3197,62 @@ static void service_reset_failed(Unit *u) { 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", @@ -3099,9 +3274,11 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = { 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); @@ -3153,15 +3330,15 @@ const UnitVTable service_vtable = { .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,