diff --git a/Makefile b/Makefile index e5cfae14..99ec2966 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ ARCHIVEZ = ../$(ARCHIVE).xz EXEC = finit reboot HEADERS = plugin.h svc.h helpers.h queue.h DISTFILES = LICENSE README CHANGELOG finit.conf services -OBJS = finit.o conf.o helpers.o sig.o svc.o plugin.o tty.o inetd.o +OBJS = finit.o conf.o helpers.o sig.o svc.o service.o plugin.o tty.o inetd.o TOPDIR = $(shell pwd) -include config.mk diff --git a/conf.c b/conf.c index 0f5d47c3..3affdba2 100644 --- a/conf.c +++ b/conf.c @@ -26,7 +26,7 @@ #include #include "finit.h" -#include "svc.h" +#include "service.h" #include "tty.h" #include "libite/lite.h" #include "helpers.h" @@ -165,7 +165,7 @@ static void parse_static(char *line) } if (MATCH_CMD(line, "startx ", x)) { - svc_register(SVC_TYPE_SERVICE, strip_line(x), 0, username); + service_register(SVC_TYPE_SERVICE, strip_line(x), 0, username); return; } @@ -216,27 +216,27 @@ static void parse_dynamic(char *line, time_t mtime) * long as the (optional) service callback returns * non-zero */ if (MATCH_CMD(line, "service ", x)) { - svc_register(SVC_TYPE_SERVICE, x, mtime, NULL); + service_register(SVC_TYPE_SERVICE, x, mtime, NULL); return; } /* One-shot task, will not be respawned. Only runs if * the (optional) service callback returns true */ if (MATCH_CMD(line, "task ", x)) { - svc_register(SVC_TYPE_TASK, x, mtime, NULL); + service_register(SVC_TYPE_TASK, x, mtime, NULL); return; } /* Like task but waits for completion, useful w/ [S] */ if (MATCH_CMD(line, "run ", x)) { - svc_register(SVC_TYPE_RUN, x, mtime, NULL); + service_register(SVC_TYPE_RUN, x, mtime, NULL); return; } /* Classic inetd service */ if (MATCH_CMD(line, "inetd ", x)) { #ifndef INETD_DISABLED - svc_register(SVC_TYPE_INETD, x, mtime, NULL); + service_register(SVC_TYPE_INETD, x, mtime, NULL); #else _e("Finit built with inetd support disabled, cannot register service inetd %s!", x); #endif diff --git a/finit.c b/finit.c index ebe67999..e6c14580 100644 --- a/finit.c +++ b/finit.c @@ -32,7 +32,7 @@ #include "helpers.h" #include "private.h" #include "plugin.h" -#include "svc.h" +#include "service.h" #include "sig.h" #include "tty.h" #include "libite/lite.h" @@ -245,7 +245,7 @@ int main(int argc, char* argv[]) /* * Start all bootstrap tasks, no network available! */ - svc_bootstrap(); + service_bootstrap(); /* * Network stuff @@ -267,7 +267,7 @@ int main(int argc, char* argv[]) * tasks/services in the configured runlevel */ parse_finit_d(rcsd); - svc_runlevel(cfglevel); + service_runlevel(cfglevel); _d("Running svc up hooks ..."); plugin_run_hooks(HOOK_SVC_UP); diff --git a/inetd.c b/inetd.c index ac576fbe..fd090510 100644 --- a/inetd.c +++ b/inetd.c @@ -34,7 +34,7 @@ #include "finit.h" #include "helpers.h" #include "private.h" -#include "svc.h" +#include "service.h" #define ENABLE_SOCKOPT(sd, level, opt) \ do { \ @@ -49,13 +49,13 @@ static void socket_cb(uev_ctx_t *UNUSED(ctx), uev_t *w, void *arg, int UNUSED(ev { svc_t *svc = (svc_t *)arg; - if (SVC_START != svc_enabled(svc, -1, NULL)) + if (SVC_START != service_enabled(svc, -1, NULL)) return; if (!svc->inetd.forking) uev_io_stop(w); - svc_start(svc); + service_start(svc); } /* Launch Inet socket for service. @@ -174,12 +174,12 @@ int inetd_stream_peek(int sd, char *ifname) return 0; } -/* Inetd monitor, called by svc_monitor() */ +/* Inetd monitor, called by service_monitor() */ int inetd_respawn(pid_t pid) { svc_t *svc = svc_find_by_pid(pid); - if (svc && SVC_TYPE_INETD == svc->type) { + if (svc_is_inetd(svc)) { inetd_t *inetd = &svc->inetd; svc->pid = 0; @@ -213,7 +213,7 @@ void inetd_stop(inetd_t *inetd) /* Stop any running service, not allowed anymore. */ if (svc->pid) - svc_stop(svc); + service_stop(svc); } static int getent(char *service, char *proto, struct servent **sv, struct protoent **pv) @@ -407,7 +407,7 @@ int inetd_match(inetd_t *inetd, char *service, char *proto, char *port) * before the specific (eth0:222), the new inetd service must find the * (any!) previous rule and add its ifname to their deny list. * - * If equivalent service exists already svc_register() will instead call + * If equivalent service exists already service_register() will instead call * inetd_allow(). */ int inetd_new(inetd_t *inetd, char *service, char *proto, int forking) @@ -477,13 +477,9 @@ int inetd_init(inetd_t *inetd, void *arg, char *ifname, char *port) result = inetd_allow(inetd, ifname); /* For each similar service, on other port, add their ifnames as deny to ours. */ - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + for (svc = svc_inetd_iterator(1); svc; svc = svc_inetd_iterator(0)) { inetd_filter_t *filter; - /* Skip non-inetd services */ - if (svc->type != SVC_TYPE_INETD) - continue; - /* Skip ourselves */ if (&svc->inetd == inetd) continue; diff --git a/plugins/initctl.c b/plugins/initctl.c index f5d5bfd1..e7dd08e9 100644 --- a/plugins/initctl.c +++ b/plugins/initctl.c @@ -33,6 +33,7 @@ #include "../plugin.h" #include "../sig.h" #include "../finit.h" +#include "../service.h" static void parse(void *arg, int fd, int events); @@ -102,7 +103,7 @@ static void parse(void *UNUSED(arg), int fd, int UNUSED(events)) case '1'...'5': case '7'...'9': _d("Setting new runlevel %c", rq.runlevel); - svc_runlevel(rq.runlevel - '0'); + service_runlevel(rq.runlevel - '0'); break; case '6': diff --git a/private.h b/private.h index 57e8b5bc..c8260f99 100644 --- a/private.h +++ b/private.h @@ -30,8 +30,8 @@ uev_ctx_t *ctx; /* Main loop context */ -void svc_bootstrap (void); -void svc_monitor (pid_t lost); +void service_bootstrap(void); +void service_monitor (pid_t lost); void plugin_run_hooks (hook_point_t no); int plugin_load_all (uev_ctx_t *ctx, char *path); diff --git a/service.c b/service.c new file mode 100644 index 00000000..51f83db4 --- /dev/null +++ b/service.c @@ -0,0 +1,821 @@ +/* Finit service monitor, task starter and generic API for managing svc_t + * + * Copyright (c) 2008-2010 Claudio Matsuoka + * Copyright (c) 2008-2015 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "finit.h" +#include "helpers.h" +#include "private.h" +#include "sig.h" +#include "tty.h" +#include "libite/lite.h" +#include "service.h" +#include "inetd.h" + +#define RESPAWN_MAX 10 /* Prevent endless respawn of faulty services. */ + +static int is_norespawn (void); +static void restart_lost_procs (void); +static void svc_dance (svc_t *svc); +static void utmp_save (int pre, int now); +static svc_t *find_inetd_svc (char *path, char *service, char *proto, char *port); + + +/** + * service_bootstrap - Start bootstrap services and tasks + * + * System startup, runlevel S, where only services, tasks and + * run commands absolutely essential to bootstrap are located. + */ +void service_bootstrap(void) +{ + svc_t *svc; + + _d("Bootstrapping all services in runlevel S from %s", FINIT_CONF); + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + svc_cmd_t cmd; + + /* Inetd services cannot be part of bootstrap currently. */ + if (svc_is_inetd(svc)) + continue; + + cmd = service_enabled(svc, 0, NULL); + if (SVC_START == cmd || (SVC_RELOAD == cmd)) + service_start(svc); + } +} + +/** + * service_enabled - Should the service run? + * @svc: Pointer to &svc_t object + * @event: Dynamic event, opaque flag passed to callback + * @arg: Event argument, used only by external service plugins. + * + * This method calls an associated service callback, if registered by a + * plugin, and returns the &svc_cmd_t status. If no plugin is registered + * the service is statically enabled in /etc/finit.conf and the result + * will always be %SVC_START. + * + * Returns: + * Either one of %SVC_START, %SVC_STOP, %SVC_RELOAD. + */ +svc_cmd_t service_enabled(svc_t *svc, int event, void *arg) +{ + if (!svc) { + errno = EINVAL; + return SVC_STOP; + } + + if (!ISSET(svc->runlevels, runlevel)) + return SVC_STOP; + + /* Is there a service plugin registered? */ + if (svc->cb) { + int status; + pid_t pid; + + /* Let callback run in separate process so it doesn't crash PID 1 */ + pid = fork(); + if (-1 == pid) { + _pe("Failed in %s callback", svc->cmd); + return SVC_STOP; + } + + if (!pid) { + status = svc->cb(svc, event, arg); + exit(status); + } + + if (waitpid(pid, &status, 0) == -1) { + _pe("Failed reading status from %s callback", svc->cmd); + return SVC_STOP; + } + + /* Callback normally exits here. */ + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + /* Check for SEGFAULT or other error ... */ + if (WCOREDUMP(status)) + _e("Callback to %s crashed!\n", svc->cmd); + else + _e("Callback to %s did not exit normally!\n", svc->cmd); + + return SVC_STOP; + } + + /* No service plugin, default to start, since listed in finit.conf */ + return SVC_START; +} + +/* Remember: service_enabled() must be called before calling service_start() */ +int service_start(svc_t *svc) +{ + int respawn, sd = 0; + pid_t pid; + sigset_t nmask, omask; + + if (!svc) + return 0; + respawn = svc->pid != 0; + + /* Don't try and start service if it doesn't exist. */ + if (!fexist(svc->cmd) && !svc->inetd.cmd) { + char msg[80]; + + snprintf(msg, sizeof(msg), "Service %s does not exist!", svc->cmd); + print_desc("", msg); + print_result(1); + + return 0; + } + + /* Ignore if finit is SIGSTOP'ed */ + if (is_norespawn()) + return 0; + +#ifndef INETD_DISABLED + if (svc_is_inetd(svc)) { + char ifname[IF_NAMESIZE] = "UNKNOWN"; + + sd = svc->inetd.watcher.fd; + + if (svc->inetd.type == SOCK_STREAM) { + /* Open new client socket from server socket */ + sd = accept(sd, NULL, NULL); + if (sd < 0) { + FLOG_PERROR("Failed accepting inetd service %d/tcp", svc->inetd.port); + return 0; + } + + _d("New client socket %d accepted for inetd service %d/tcp", sd, svc->inetd.port); + + /* Find ifname by means of getsockname() and getifaddrs() */ + inetd_stream_peek(sd, ifname); + } else { /* SOCK_DGRAM */ + /* Find ifname by means of IP_PKTINFO sockopt --> ifindex + if_indextoname() */ + inetd_dgram_peek(sd, ifname); + } + + if (!inetd_is_allowed(&svc->inetd, ifname)) { + FLOG_INFO("Service %s on port %d not allowed from interface %s.", + svc->inetd.name, svc->inetd.port, ifname); + if (svc->inetd.type == SOCK_STREAM) + close(sd); + + return 0; + } + + FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->inetd.name, ifname); + } else +#endif + if (svc_is_daemon(svc)) + print_desc("", svc->desc); + else if (!respawn) + print_desc("Starting ", svc->desc); + + /* Block sigchild while forking. */ + sigemptyset(&nmask); + sigaddset(&nmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &nmask, &omask); + + pid = fork(); + sigprocmask(SIG_SETMASK, &omask, NULL); + if (pid == 0) { + int i = 0; + int status; +#ifdef ENABLE_STATIC + int uid = 0; /* XXX: Fix better warning that dropprivs is disabled. */ +#else + int uid = getuser(svc->username); +#endif + struct sigaction sa; + char *args[MAX_NUM_SVC_ARGS]; + + sigemptyset(&nmask); + sigaddset(&nmask, SIGCHLD); + sigprocmask(SIG_UNBLOCK, &nmask, NULL); + + /* Reset signal handlers that were set by the parent process */ + for (i = 1; i < NSIG; i++) + DFLSIG(sa, i, 0); + + /* Set desired user */ + if (uid >= 0) + setuid(uid); + + /* Serve copy of args to process in case it modifies them. */ + for (i = 0; i < (MAX_NUM_SVC_ARGS - 1) && svc->args[i][0] != 0; i++) + args[i] = svc->args[i]; + args[i] = NULL; + + /* Redirect inetd socket to stdin for service */ + if (svc_is_inetd(svc)) { + /* sd set previously */ + dup2(sd, STDIN_FILENO); + close(sd); + dup2(STDIN_FILENO, STDOUT_FILENO); + dup2(STDIN_FILENO, STDERR_FILENO); + } else if (debug) { + int fd; + char buf[CMD_SIZE] = ""; + + fd = open(CONSOLE, O_WRONLY | O_APPEND); + if (-1 != fd) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + + for (i = 0; i < MAX_NUM_SVC_ARGS && args[i]; i++) { + char arg[MAX_ARG_LEN]; + + snprintf(arg, sizeof(arg), "%s ", args[i]); + if (strlen(arg) < (sizeof(buf) - strlen(buf))) + strcat(buf, arg); + } + _e("%starting %s: %s", respawn ? "Res" : "S", svc->cmd, buf); + } + + if (svc->inetd.cmd) + status = svc->inetd.cmd(svc->inetd.type); + else + status = execv(svc->cmd, args); /* XXX: Maybe use execve() to be able to launch scripts? */ + + if (svc_is_inetd(svc)) { + if (svc->inetd.type == SOCK_STREAM) { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + } + + exit(status); + } + svc->pid = pid; + + if (svc_is_inetd(svc)) { + if (svc->inetd.type == SOCK_STREAM) + close(sd); + } + else if (SVC_TYPE_RUN == svc->type) + print_result(WEXITSTATUS(complete(svc->cmd, pid))); + else if (!respawn) + print_result(svc->pid > 1 ? 0 : 1); + + return 0; +} + +int service_stop(svc_t *svc) +{ + int res = 1; + + if (!svc) { + _e("Failed, no svc pointer."); + return 1; + } + + if (svc->pid <= 1) { + _d("Bad PID %d for %s, SIGTERM", svc->pid, svc->desc); + res = 1; + goto exit; + } + + if (SVC_TYPE_SERVICE != svc->type) + return 0; + + if (runlevel != 1) + print_desc("Stopping ", svc->desc); + + _d("Sending SIGTERM to pid:%d name:%s", svc->pid, pid_get_name(svc->pid, NULL, 0)); + res = kill(svc->pid, SIGTERM); + + if (runlevel != 1) + print_result(res); +exit: + svc->pid = 0; + svc->restart_counter = 0; + + return res; +} + +/** + * service_reload - Send SIGHUP to a service + * @svc: Service to reload + * + * This function does some basic checks of the runtime state of Finit + * and a sanity check of the @svc before sending %SIGHUP. + * + * Returns: + * POSIX OK(0) or non-zero on error. + */ +int service_reload(svc_t *svc) +{ + /* Ignore if finit is SIGSTOP'ed */ + if (is_norespawn()) + return 0; + + if (!svc) { + _e("Failed, no svc pointer."); + return 1; + } + + if (svc->pid <= 1) { + _d("Bad PID %d for %s, SIGHUP", svc->pid, svc->cmd); + svc->pid = 0; + return 1; + } + + _d("Sending SIGHUP to PID %d", svc->pid); + return kill(svc->pid, SIGHUP); +} + +/** + * service_reload_dynamic - Reload modifued dynamic services + * + * This function is called when Finit has recieved SIGHUP to reload + * .conf files in /etc/finit.d. It is responsible for starting, + * stopping and reloading (forwarding SIGHUP) to processes affected. + */ +void service_reload_dynamic(void) +{ + int num = 0; + svc_t *svc; + + _d("Stopping disabled/removed services ..."); + for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0)) { + if (svc->dirty != 0) { + service_stop(svc); + num++; + } + } + + if (!num) + return; + + _d("All disabled/removed services have been stoppped, calling reconf hooks ..."); + plugin_run_hooks(HOOK_SVC_RECONF); /* Reconfigure HW/VLANs/etc here */ + + _d("Starting enabled/added services ..."); + for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0)) { + if (svc->dirty > 0) + svc_dance(svc); + } +} + +/** + * service_runlevel - Change to a new runlevel + * @newlevel: New runlevel to activate + * + * Stops all services not in @newlevel and starts, or lets continue to run, + * those in @newlevel. Also updates @prevlevel and active @runlevel. + */ +void service_runlevel(int newlevel) +{ + svc_t *svc; + + if (runlevel == newlevel) + return; + + if (newlevel < 0 || newlevel > 9) + return; + + prevlevel = runlevel; + runlevel = newlevel; + + _d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel); + utmp_save(prevlevel, newlevel); + + /* Make sure to reload all *.conf in /etc/finit.d/ */ + svc_mark_dynamic(); + parse_finit_d(rcsd); + + _d("Stopping services services not allowed in new runlevel ..."); + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (!ISSET(svc->runlevels, runlevel)) { +#ifndef INETD_DISABLED + if (svc_is_inetd(svc)) + inetd_stop(&svc->inetd); + else +#endif + service_stop(svc); + } + + /* ... or disabled/removed services from /etc/finit.d/ */ + if (svc->mtime && svc->dirty != 0) + service_stop(svc); + } + + /* Prev runlevel services stopped, call hooks before starting new runlevel ... */ + _d("All services have been stoppped, calling runlevel change hooks ..."); + plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */ + + _d("Starting services services new to this runlevel ..."); + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { +#ifndef INETD_DISABLED + /* Inetd services have slightly different semantics */ + if (svc_is_inetd(svc)) { + if (ISSET(svc->runlevels, runlevel)) + inetd_start(&svc->inetd); + + continue; + } +#endif + + /* All other services consult their callback here */ + svc_dance(svc); + } + + /* Cleanup stale services */ + svc_clean_dynamic(service_unregister); + + if (0 == runlevel) { + do_shutdown(SIGUSR2); + return; + } + if (6 == runlevel) { + do_shutdown(SIGUSR1); + return; + } + + if (runlevel == 1) + touch("/etc/nologin"); /* Disable login in single-user mode */ + else + erase("/etc/nologin"); + + if (0 != prevlevel) + tty_runlevel(runlevel); +} + +/** + * service_register - Register service, task or run commands + * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) + * @line: A complete command line with -- separated description text + * @mtime: The modification time if service is loaded from /etc/finit.d + * @username: Optional username to run service as, or %NULL to run as root + * + * This function is used to register commands to be run on different + * system runlevels with optional username. The @type argument details + * if it's service to bo monitored/respawned (daemon), a one-shot task + * or a command that must run in sequence and not in parallell, like + * service and task commands do. + * + * The @line can optionally start with a username, denoted by an @ + * character. Like this: + * + * service @username [!0-6,S] /path/to/daemon arg -- Description + * task @username [!0-6,S] /path/to/task arg -- Description + * run @username [!0-6,S] /path/to/cmd arg -- Description + * inetd tcp/ssh nowait [2345] @root:root /sbin/sshd -i -- Description + * + * If the username is left out the command is started as root. The [] + * brackets denote the allowed runlevels, if left out the default for a + * service is set to [2-5]. Allowed runlevels mimic that of SysV init + * with the addition of the 'S' runlevel, which is only run once at + * startup. It can be seen as the system bootstrap. If a task or run + * command is listed in more than the [S] runlevel they will be called + * when changing runlevel. + * + * For multiple instances of the same command, e.g. multiple DHCP + * clients, the user may enter an optional ID, using the #ID syntax. + * + * service #1 /sbin/udhcpc -i eth1 + * service #2 /sbin/udhcpc -i eth2 + * + * Without the #ID syntax Finit will overwrite the first service line + * with the contents of the second. The #ID must be [1,MAXINT]. + * + * Returns: + * POSIX OK(0) on success, or non-zero errno exit status on failure. + */ +int service_register(int type, char *line, time_t mtime, char *username) +{ + int i = 0; + int id = 1; /* Default to ID:1 */ +#ifndef INETD_DISABLED + int forking = 0; +#endif + char *service = NULL, *proto = NULL, *iface = NULL, *port = NULL; + char *cmd, *desc, *runlevels = NULL; + svc_t *svc; + plugin_t *plugin = NULL; + + if (!line) { + _e("Invalid input argument."); + return errno = EINVAL; + } + + desc = strstr(line, "-- "); + if (desc) + *desc = 0; + + cmd = strtok(line, " "); + if (!cmd) { + incomplete: + _e("Incomplete service, cannot register."); + return errno = ENOENT; + } + + while (cmd) { + if (cmd[0] != '/' && strchr(cmd, '/')) + service = cmd; /* inetd service/proto */ +#ifndef INETD_DISABLED + else if (!strncasecmp(cmd, "nowait", 6)) + forking = 1; + else if (!strncasecmp(cmd, "wait", 4)) + forking = 0; +#endif + else if (cmd[0] == '@') /* @username[:group] */ + username = &cmd[1]; + else if (cmd[0] == '[') /* [runlevels] */ + runlevels = &cmd[0]; + else if (cmd[0] == '#') /* #ID */ + id = atoi(&cmd[1]); + else + break; + + /* Check if valid command follows... */ + cmd = strtok(NULL, " "); + if (!cmd) + goto incomplete; + } + + /* Example: inetd ssh@eth0:222/tcp */ + if (service) { + proto = strchr(service, '/'); + if (!proto) + goto incomplete; + *proto++ = 0; + + port = strchr(service, ':'); + if (port) + *port++ = 0; + + iface = strchr(service, '@'); + if (iface) + *iface++ = 0; + } + +#ifndef INETD_DISABLED + /* Find plugin that provides a callback for this inetd service */ + if (type == SVC_TYPE_INETD && !strncasecmp(cmd, "internal", 8)) { + plugin = plugin_find(service); + if (!plugin || !plugin->inetd.cmd) { + _e("Inetd service %s has no internal plugin, skipping.", service); + return errno = ENOENT; + } + } + + /* Check if known inetd, then add ifname for filtering only. */ + svc = find_inetd_svc(cmd, service, proto, port); + if (svc) + return inetd_allow(&svc->inetd, iface); +#endif + + svc = svc_find(cmd, id); + if (!svc) { + _d("Creating new svc for %s id #%d", cmd, id); + svc = svc_new(id); + if (!svc) { + _e("Out of memory, cannot register service %s", cmd); + return errno = ENOMEM; + } + } + if (svc->mtime != mtime) + svc->dirty = 1; + svc->mtime = mtime; + svc->type = type; + + if (desc) + strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); + + if (username) { + char *ptr = strchr(username, ':'); + + if (ptr) { + *ptr++ = 0; + strlcpy(svc->group, ptr, sizeof(svc->group)); + } + strlcpy(svc->username, username, sizeof(svc->username)); + } + +#ifndef INETD_DISABLED + if (svc_is_inetd(svc)) { + int result; + + result = inetd_new(&svc->inetd, service, proto, forking); + result += inetd_init(&svc->inetd, svc, iface, port); + + if (result) { + _e("Failed registering new inetd service %s.", service); + inetd_del(&svc->inetd); + return svc_del(svc); + } + } +#endif + + if (plugin) { + /* Internal plugin provides this service */ + svc->inetd.cmd = plugin->inetd.cmd; + } else { + /* External program to call */ + strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); + + strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); + while ((cmd = strtok(NULL, " "))) + strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); + svc->args[i][0] = 0; + + plugin = plugin_find(svc->cmd); + if (plugin && plugin->svc.cb) { + svc->cb = plugin->svc.cb; + svc->dynamic = plugin->svc.dynamic; + svc->dynamic_stop = plugin->svc.dynamic_stop; + } + } + + svc->runlevels = parse_runlevels(runlevels); + _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); + + return 0; +} + +void service_unregister(svc_t *svc) +{ + service_stop(svc); + svc_del(svc); +} + +void service_monitor(pid_t lost) +{ + svc_t *svc; + static int was_stopped = 0; + + if (was_stopped && !is_norespawn()) { + was_stopped = 0; + restart_lost_procs(); + return; + } + + if (fexist(SYNC_SHUTDOWN) || lost <= 1) + return; + + /* Power user at the console, don't respawn tasks. */ + if (is_norespawn()) { + was_stopped = 1; + return; + } + + if (tty_respawn(lost)) + return; + +#ifndef INETD_DISABLED + if (inetd_respawn(lost)) + return; +#endif + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (lost != svc->pid) + continue; + + if (SVC_TYPE_SERVICE != svc->type) { + svc->pid = 0; + continue; + } + + _d("Ouch, lost pid %d - %s(%d)", lost, basename(svc->cmd), svc->pid); + + /* No longer running, update books. */ + svc->pid = 0; + + if (sig_stopped()) { + _e("Stopped, not respawning killed processes."); + break; + } + + /* Restarting lost service. */ + if (service_enabled(svc, 0, NULL)) { + if (svc->restart_counter > RESPAWN_MAX) { + _e("Not restarting %s id %d, respawn MAX (%d) reached!", + svc->cmd, svc->id, RESPAWN_MAX); + break; + } + + svc->restart_counter++; + service_start(svc); + } + + break; + } +} + +static int is_norespawn(void) +{ + return sig_stopped() || + fexist("/mnt/norespawn") || + fexist("/tmp/norespawn"); +} + +static void restart_lost_procs(void) +{ + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->pid > 0 && pid_alive(svc->pid)) + continue; + + /* Only restart lost daemons, not task/run/inetd services */ + if (SVC_TYPE_SERVICE != svc->type) { + svc->pid = 0; + continue; + } + + service_start(svc); + } +} + +/* Singing and dancing ... */ +static void svc_dance(svc_t *svc) +{ + svc_cmd_t cmd = service_enabled(svc, 0, NULL); + + if (svc->pid) { + if (SVC_STOP == cmd) + service_stop(svc); + else if (SVC_RELOAD == cmd) + service_reload(svc); + } else { + if (SVC_START == cmd || SVC_RELOAD == cmd) + service_start(svc); + } +} + +static int encode(int lvl) +{ + if (!lvl) return 0; + return lvl + '0'; +} + +static void utmp_save(int pre, int now) +{ + struct utmp utent; + + utent.ut_type = RUN_LVL; + utent.ut_pid = (encode(pre) << 8) | (encode(now) & 0xFF); + strlcpy(utent.ut_user, "runlevel", sizeof(utent.ut_user)); + + setutent(); + pututline(&utent); + endutent(); +} + +#ifndef INETD_DISABLED +static svc_t *find_inetd_svc(char *path, char *service, char *proto, char *port) +{ + svc_t *svc; + + for (svc = svc_inetd_iterator(1); svc; svc = svc_inetd_iterator(0)) { + if (strncmp(path, svc->cmd, strlen(svc->cmd))) + continue; + + if (inetd_match(&svc->inetd, service, proto, port)) { + _d("Found a matching svc for %s", path); + return svc; + } + } + + return NULL; +} +#endif + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/service.h b/service.h new file mode 100644 index 00000000..93a175d2 --- /dev/null +++ b/service.h @@ -0,0 +1,50 @@ +/* Finit service monitor, task starter and generic API for managing svc_t + * + * Copyright (c) 2008-2010 Claudio Matsuoka + * Copyright (c) 2008-2015 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_SERVICE_H_ +#define FINIT_SERVICE_H_ + +#include "svc.h" + +void service_runlevel (int newlevel); +int service_register (int type, char *line, time_t mtime, char *username); +void service_unregister (svc_t *svc); +svc_cmd_t service_enabled (svc_t *svc, int event, void *arg); + +int service_start (svc_t *svc); +int service_stop (svc_t *svc); +int service_reload (svc_t *svc); +void service_reload_dynamic (void); + +void service_cleanup (void); + +#endif /* FINIT_SERVICE_H_ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/sig.c b/sig.c index b401467a..b5fcbc39 100644 --- a/sig.c +++ b/sig.c @@ -46,6 +46,7 @@ #include "plugin.h" #include "private.h" #include "sig.h" +#include "service.h" static int stopped = 0; static uev_t sighup_watcher, sigint_watcher, sigpwr_watcher; @@ -99,10 +100,10 @@ static void sighup_cb(uev_ctx_t *UNUSED(ctx), uev_t *UNUSED(w), void *UNUSED(arg parse_finit_d(rcsd); /* Reload dirty services */ - svc_reload_dynamic(); + service_reload_dynamic(); /* Cleanup stale services */ - svc_cleanup(); + svc_clean_dynamic(service_unregister); } /* @@ -125,7 +126,7 @@ static void sigchld_cb(uev_ctx_t *UNUSED(ctx), uev_t *UNUSED(w), void *UNUSED(ar pid = waitpid(-1, NULL, WNOHANG); if (pid > 0) { _d("Collected child %d", pid); - svc_monitor(pid); + service_monitor(pid); } } while (pid > 0); } diff --git a/svc.c b/svc.c index f4d64039..2cc1ab36 100644 --- a/svc.c +++ b/svc.c @@ -1,4 +1,4 @@ -/* Finit service monitor, task starter and generic API for managing svc_t +/* Low-level service primitives and generic API for managing svc_t structures * * Copyright (c) 2008-2010 Claudio Matsuoka * Copyright (c) 2008-2015 Joachim Nilsson @@ -22,41 +22,24 @@ * THE SOFTWARE. */ -#include -#include -#include -#include +#include -#include "finit.h" -#include "helpers.h" -#include "private.h" -#include "sig.h" -#include "tty.h" -#include "libite/lite.h" #include "svc.h" -#include "inetd.h" - -#define RESPAWN_MAX 10 /* Prevent endless respawn of faulty services. */ - -/* The registered services are kept in shared memory for easy read-only access - * by 3rd party APIs. Mostly because of the ability to, from the outside, query - * liveness state of all daemons that should run. */ -static int was_stopped = 0; -static svc_t *services = NULL; /* List of registered services, in shared memory. */ +#include "helpers.h" -/* Connect to/create shared memory area of registered servies */ -static void __connect_shm(void) +static svc_t *__connect_shm(void) { - if (!services) { - services = finit_svc_connect(); - if (!services) { - /* This should never happen, but if it does we're probably - * knee-deep in more serious problems already... */ - _e("Failed allocating shared memory, error %d: %s", errno, strerror (errno)); - abort(); - } + svc_t *list = finit_svc_connect(); + + if (!list) { + /* This should never happen, but if it does we're probably + * knee-deep in more serious problems already... */ + _e("Failed connecting to shared memory, error %d: %s", errno, strerror(errno)); + abort(); } + + return list; } /** @@ -69,10 +52,10 @@ static void __connect_shm(void) svc_t *svc_new(int id) { int i; + svc_t *list = __connect_shm(); - __connect_shm(); for (i = 0; i < MAX_NUM_SVC; i++) { - svc_t *svc = &services[i]; + svc_t *svc = &list[i]; if (svc->type == SVC_TYPE_FREE) { memset(svc, 0, sizeof(*svc)); @@ -110,13 +93,13 @@ int svc_del(svc_t *svc) svc_t *svc_iterator(int first) { static int i = 0; + svc_t *list = __connect_shm(); - __connect_shm(); if (first) i = 0; while (i < MAX_NUM_SVC) { - svc_t *svc = &services[i++]; + svc_t *svc = &list[i++]; if (svc->type != SVC_TYPE_FREE) return svc; @@ -125,6 +108,81 @@ svc_t *svc_iterator(int first) return NULL; } + +/** + * svc_inetd_iterator - Naive iterator over all registered inetd services. + * @first: Get first &svc_t object, or next until end. + * + * Returns: + * The first inetd &svc_t when @first is set, otherwise the next + * inetd &svc_t until the end when %NULL is returned. + */ +svc_t *svc_inetd_iterator(int first) +{ + svc_t *svc = svc_iterator(first); + + do { + if (svc_is_inetd(svc)) + return svc; + } while ((svc = svc_iterator(0))); + + return NULL; +} + + +/** + * svc_dynamic_iterator - Naive iterator over all registered dynamic services. + * @first: Get first &svc_t object, or next until end. + * + * Returns: + * The first dynamically loaded &svc_t when @first is set, otherwise the + * next dynamically loaded &svc_t until the end when %NULL is returned. + */ +svc_t *svc_dynamic_iterator(int first) +{ + svc_t *svc = svc_iterator(first); + + do { + if (svc->mtime) + return svc; + } while ((svc = svc_iterator(0))); + + return NULL; +} + + +/** + * svc_foreach - Run a callback for each registered service + * @cb: Callback to run for each service + */ +void svc_foreach(void (*cb)(svc_t *)) +{ + svc_t *svc; + + if (!cb) + return; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) + cb(svc); +} + + +/** + * svc_foreach_dynamic - Run a callback for each registered dynamic service + * @cb: Callback to run for each dynamic service + */ +void svc_foreach_dynamic(void (*cb)(svc_t *)) +{ + svc_t *svc; + + if (!cb) + return; + + for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0)) + cb(svc); +} + + /** * svc_find - Find a service object by its full path name * @path: Full path name, e.g., /sbin/syslogd @@ -165,811 +223,47 @@ svc_t *svc_find_by_pid(pid_t pid) return NULL; } -#ifndef INETD_DISABLED -svc_t *svc_find_inetd(char *path, char *service, char *proto, char *port) -{ - svc_t *svc; - - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->type != SVC_TYPE_INETD) - continue; - - if (strncmp(path, svc->cmd, strlen(svc->cmd))) - continue; - - if (inetd_match(&svc->inetd, service, proto, port)) { - _d("Found a matching svc for %s", path); - return svc; - } - } - - return NULL; -} -#endif - /** * svc_mark_dynamic - Mark dynamically loaded services for deletion. * * This function traverses the list of known services, marking all that * have been loaded from /etc/finit.d/ for deletion. If a .conf file - * has been removed svc_cleanup() will stop and delete the service. + * has been removed svc_cleanup_dynamic() will stop and delete the + * service. * * This function is called from sighup_cb(). */ void svc_mark_dynamic(void) { - svc_t *svc; + svc_t *svc = svc_dynamic_iterator(1); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->mtime) - svc->dirty = -1; + while (svc) { + svc->dirty = -1; + svc = svc_dynamic_iterator(0); } } /** - * svc_bootstrap - Start bootstrap services and tasks - * - * System startup, runlevel S, where only services, tasks and - * run commands absolutely essential to bootstrap are located. - */ -void svc_bootstrap(void) -{ - svc_t *svc; - - _d("Bootstrapping all services in runlevel S from %s", FINIT_CONF); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - svc_cmd_t cmd; - - /* Inetd services cannot be part of bootstrap currently. */ - if (svc->type == SVC_TYPE_INETD) - continue; - - cmd = svc_enabled(svc, 0, NULL); - if (SVC_START == cmd || (SVC_RELOAD == cmd)) - svc_start(svc); - } -} - -static int encode(int lvl) -{ - if (!lvl) - return 0; - - return lvl + '0'; -} - -static void utmp_save(int pre, int now) -{ - struct utmp utent; - - utent.ut_type = RUN_LVL; - utent.ut_pid = (encode(pre) << 8) | (encode(now) & 0xFF); - strlcpy(utent.ut_user, "runlevel", sizeof(utent.ut_user)); - - setutent(); - pututline(&utent); - endutent(); -} - -/* Singing and dancing ... */ -static void svc_dance(svc_t *svc) -{ - svc_cmd_t cmd = svc_enabled(svc, 0, NULL); - - if (svc->pid) { - if (SVC_STOP == cmd) - svc_stop(svc); - else if (SVC_RELOAD == cmd) - svc_reload(svc); - } else { - if (SVC_START == cmd || SVC_RELOAD == cmd) - svc_start(svc); - } -} - -/** - * svc_runlevel - Change to a new runlevel - * @newlevel: New runlevel to activate - * - * Stops all services not in @newlevel and starts, or lets continue to run, - * those in @newlevel. Also updates @prevlevel and active @runlevel. - */ -void svc_runlevel(int newlevel) -{ - svc_t *svc; - - if (runlevel == newlevel) - return; - - if (newlevel < 0 || newlevel > 9) - return; - - prevlevel = runlevel; - runlevel = newlevel; - - _d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel); - utmp_save(prevlevel, newlevel); - - /* Make sure to reload all *.conf in /etc/finit.d/ */ - svc_mark_dynamic(); - parse_finit_d(rcsd); - - _d("Stopping services services not allowed in new runlevel ..."); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (!ISSET(svc->runlevels, runlevel)) { -#ifndef INETD_DISABLED - if (svc->type == SVC_TYPE_INETD) - inetd_stop(&svc->inetd); - else -#endif - svc_stop(svc); - } - - /* ... or disabled/removed services from /etc/finit.d/ */ - if (svc->mtime && svc->dirty != 0) - svc_stop(svc); - } - - /* Prev runlevel services stopped, call hooks before starting new runlevel ... */ - _d("All services have been stoppped, calling runlevel change hooks ..."); - plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */ - - _d("Starting services services new to this runlevel ..."); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { -#ifndef INETD_DISABLED - /* Inetd services have slightly different semantics */ - if (svc->type == SVC_TYPE_INETD) { - if (ISSET(svc->runlevels, runlevel)) - inetd_start(&svc->inetd); - - continue; - } -#endif - - /* All other services consult their callback here */ - svc_dance(svc); - } - - /* Cleanup stale services */ - svc_cleanup(); - - if (0 == runlevel) { - do_shutdown(SIGUSR2); - return; - } - if (6 == runlevel) { - do_shutdown(SIGUSR1); - return; - } - - if (runlevel == 1) - touch("/etc/nologin"); /* Disable login in single-user mode */ - else - erase("/etc/nologin"); - - if (0 != prevlevel) - tty_runlevel(runlevel); -} - -/** - * svc_register - Register service, task or run commands - * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) - * @line: A complete command line with -- separated description text - * @mtime: The modification time if service is loaded from /etc/finit.d - * @username: Optional username to run service as, or %NULL to run as root - * - * This function is used to register commands to be run on different - * system runlevels with optional username. The @type argument details - * if it's service to bo monitored/respawned (daemon), a one-shot task - * or a command that must run in sequence and not in parallell, like - * service and task commands do. - * - * The @line can optionally start with a username, denoted by an @ - * character. Like this: - * - * service @username [!0-6,S] /path/to/daemon arg -- Description - * task @username [!0-6,S] /path/to/task arg -- Description - * run @username [!0-6,S] /path/to/cmd arg -- Description - * inetd tcp/ssh nowait [2345] @root:root /sbin/sshd -i -- Description - * - * If the username is left out the command is started as root. The [] - * brackets denote the allowed runlevels, if left out the default for a - * service is set to [2-5]. Allowed runlevels mimic that of SysV init - * with the addition of the 'S' runlevel, which is only run once at - * startup. It can be seen as the system bootstrap. If a task or run - * command is listed in more than the [S] runlevel they will be called - * when changing runlevel. - * - * For multiple instances of the same command, e.g. multiple DHCP - * clients, the user may enter an optional ID, using the #ID syntax. - * - * service #1 /sbin/udhcpc -i eth1 - * service #2 /sbin/udhcpc -i eth2 - * - * Without the #ID syntax Finit will overwrite the first service line - * with the contents of the second. The #ID must be [1,MAXINT]. - * - * Returns: - * POSIX OK(0) on success, or non-zero errno exit status on failure. - */ -int svc_register(int type, char *line, time_t mtime, char *username) -{ - int i = 0; - int id = 1; /* Default to ID:1 */ -#ifndef INETD_DISABLED - int forking = 0; -#endif - char *service = NULL, *proto = NULL, *iface = NULL, *port = NULL; - char *cmd, *desc, *runlevels = NULL; - svc_t *svc; - plugin_t *plugin = NULL; - - if (!line) { - _e("Invalid input argument."); - return errno = EINVAL; - } - - desc = strstr(line, "-- "); - if (desc) - *desc = 0; - - cmd = strtok(line, " "); - if (!cmd) { - incomplete: - _e("Incomplete service, cannot register."); - return errno = ENOENT; - } - - while (cmd) { - if (cmd[0] != '/' && strchr(cmd, '/')) - service = cmd; /* inetd service/proto */ -#ifndef INETD_DISABLED - else if (!strncasecmp(cmd, "nowait", 6)) - forking = 1; - else if (!strncasecmp(cmd, "wait", 4)) - forking = 0; -#endif - else if (cmd[0] == '@') /* @username[:group] */ - username = &cmd[1]; - else if (cmd[0] == '[') /* [runlevels] */ - runlevels = &cmd[0]; - else if (cmd[0] == '#') /* #ID */ - id = atoi(&cmd[1]); - else - break; - - /* Check if valid command follows... */ - cmd = strtok(NULL, " "); - if (!cmd) - goto incomplete; - } - - /* Example: inetd ssh@eth0:222/tcp */ - if (service) { - proto = strchr(service, '/'); - if (!proto) - goto incomplete; - *proto++ = 0; - - port = strchr(service, ':'); - if (port) - *port++ = 0; - - iface = strchr(service, '@'); - if (iface) - *iface++ = 0; - } - -#ifndef INETD_DISABLED - /* Find plugin that provides a callback for this inetd service */ - if (type == SVC_TYPE_INETD && !strncasecmp(cmd, "internal", 8)) { - plugin = plugin_find(service); - if (!plugin || !plugin->inetd.cmd) { - _e("Inetd service %s has no internal plugin, skipping.", service); - return errno = ENOENT; - } - } - - /* Check if known inetd, then add ifname for filtering only. */ - svc = svc_find_inetd(cmd, service, proto, port); - if (svc) - return inetd_allow(&svc->inetd, iface); -#endif - - svc = svc_find(cmd, id); - if (!svc) { - _d("Creating new svc for %s id #%d", cmd, id); - svc = svc_new(id); - if (!svc) { - _e("Out of memory, cannot register service %s", cmd); - return errno = ENOMEM; - } - } - if (svc->mtime != mtime) - svc->dirty = 1; - svc->mtime = mtime; - svc->type = type; - - if (desc) - strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); - - if (username) { - char *ptr = strchr(username, ':'); - - if (ptr) { - *ptr++ = 0; - strlcpy(svc->group, ptr, sizeof(svc->group)); - } - strlcpy(svc->username, username, sizeof(svc->username)); - } - -#ifndef INETD_DISABLED - if (svc->type == SVC_TYPE_INETD) { - int result; - - result = inetd_new(&svc->inetd, service, proto, forking); - result += inetd_init(&svc->inetd, svc, iface, port); - - if (result) { - _e("Failed registering new inetd service %s.", service); - inetd_del(&svc->inetd); - return svc_del(svc); - } - } -#endif - - if (plugin) { - /* Internal plugin provides this service */ - svc->inetd.cmd = plugin->inetd.cmd; - } else { - /* External program to call */ - strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); - - strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); - while ((cmd = strtok(NULL, " "))) - strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); - svc->args[i][0] = 0; - - plugin = plugin_find(svc->cmd); - if (plugin && plugin->svc.cb) { - svc->cb = plugin->svc.cb; - svc->dynamic = plugin->svc.dynamic; - svc->dynamic_stop = plugin->svc.dynamic_stop; - } - } - - svc->runlevels = parse_runlevels(runlevels); - _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); - - return 0; -} - - -/** - * svc_enabled - Should the service run? - * @svc: Pointer to &svc_t object - * @event: Dynamic event, opaque flag passed to callback - * @arg: Event argument, used only by external service plugins. - * - * This method calls an associated service callback, if registered by a - * plugin, and returns the &svc_cmd_t status. If no plugin is registered - * the service is statically enabled in /etc/finit.conf and the result - * will always be %SVC_START. - * - * Returns: - * Either one of %SVC_START, %SVC_STOP, %SVC_RELOAD. - */ -svc_cmd_t svc_enabled(svc_t *svc, int event, void *arg) -{ - if (!svc) { - errno = EINVAL; - return SVC_STOP; - } - - if (!ISSET(svc->runlevels, runlevel)) - return SVC_STOP; - - /* Is there a service plugin registered? */ - if (svc->cb) { - int status; - pid_t pid; - - /* Let callback run in separate process so it doesn't crash PID 1 */ - pid = fork(); - if (-1 == pid) { - _pe("Failed in %s callback", svc->cmd); - return SVC_STOP; - } - - if (!pid) { - status = svc->cb(svc, event, arg); - exit(status); - } - - if (waitpid(pid, &status, 0) == -1) { - _pe("Failed reading status from %s callback", svc->cmd); - return SVC_STOP; - } - - /* Callback normally exits here. */ - if (WIFEXITED(status)) - return WEXITSTATUS(status); - - /* Check for SEGFAULT or other error ... */ - if (WCOREDUMP(status)) - _e("Callback to %s crashed!\n", svc->cmd); - else - _e("Callback to %s did not exit normally!\n", svc->cmd); - - return SVC_STOP; - } - - /* No service plugin, default to start, since listed in finit.conf */ - return SVC_START; -} - -static int is_norespawn(void) -{ - return sig_stopped() || - fexist("/mnt/norespawn") || - fexist("/tmp/norespawn"); -} - -static void restart_any_lost_procs(void) -{ - svc_t *svc; - - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->pid > 0 && pid_alive(svc->pid)) - continue; - - /* Only restart lost daemons, not task/run/inetd services */ - if (SVC_TYPE_SERVICE != svc->type) { - svc->pid = 0; - continue; - } - - svc_start(svc); - } -} - -void svc_monitor(pid_t lost) -{ - svc_t *svc; - - if (was_stopped && !is_norespawn()) { - was_stopped = 0; - restart_any_lost_procs(); - return; - } - - if (fexist(SYNC_SHUTDOWN) || lost <= 1) - return; - - /* Power user at the console, don't respawn tasks. */ - if (is_norespawn()) { - was_stopped = 1; - return; - } - - if (tty_respawn(lost)) - return; - -#ifndef INETD_DISABLED - if (inetd_respawn(lost)) - return; -#endif - - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (lost != svc->pid) - continue; - - if (SVC_TYPE_SERVICE != svc->type) { - svc->pid = 0; - continue; - } - - _d("Ouch, lost pid %d - %s(%d)", lost, basename(svc->cmd), svc->pid); - - /* No longer running, update books. */ - svc->pid = 0; - - if (sig_stopped()) { - _e("Stopped, not respawning killed processes."); - break; - } - - /* Restarting lost service. */ - if (svc_enabled(svc, 0, NULL)) { - if (svc->restart_counter > RESPAWN_MAX) { - _e("Not restarting %s id %d, respawn MAX (%d) reached!", - svc->cmd, svc->id, RESPAWN_MAX); - break; - } - - svc->restart_counter++; - svc_start(svc); - } - - break; - } -} - -/* Remember: svc_enabled() must be called before calling svc_start() */ -int svc_start(svc_t *svc) -{ - int respawn, sd = 0; - pid_t pid; - sigset_t nmask, omask; - - if (!svc) - return 0; - respawn = svc->pid != 0; - - /* Don't try and start service if it doesn't exist. */ - if (!fexist(svc->cmd) && !svc->inetd.cmd) { - char msg[80]; - - snprintf(msg, sizeof(msg), "Service %s does not exist!", svc->cmd); - print_desc("", msg); - print_result(1); - - return 0; - } - - /* Ignore if finit is SIGSTOP'ed */ - if (is_norespawn()) - return 0; - -#ifndef INETD_DISABLED - if (SVC_TYPE_INETD == svc->type) { - char ifname[IF_NAMESIZE] = "UNKNOWN"; - - sd = svc->inetd.watcher.fd; - - if (svc->inetd.type == SOCK_STREAM) { - /* Open new client socket from server socket */ - sd = accept(sd, NULL, NULL); - if (sd < 0) { - FLOG_PERROR("Failed accepting inetd service %d/tcp", svc->inetd.port); - return 0; - } - - _d("New client socket %d accepted for inetd service %d/tcp", sd, svc->inetd.port); - - /* Find ifname by means of getsockname() and getifaddrs() */ - inetd_stream_peek(sd, ifname); - } else { /* SOCK_DGRAM */ - /* Find ifname by means of IP_PKTINFO sockopt --> ifindex + if_indextoname() */ - inetd_dgram_peek(sd, ifname); - } - - if (!inetd_is_allowed(&svc->inetd, ifname)) { - FLOG_INFO("Service %s on port %d not allowed from interface %s.", - svc->inetd.name, svc->inetd.port, ifname); - if (svc->inetd.type == SOCK_STREAM) - close(sd); - - return 0; - } - - FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->inetd.name, ifname); - } else -#endif - if (SVC_TYPE_SERVICE != svc->type) - print_desc("", svc->desc); - else if (!respawn) - print_desc("Starting ", svc->desc); - - /* Block sigchild while forking. */ - sigemptyset(&nmask); - sigaddset(&nmask, SIGCHLD); - sigprocmask(SIG_BLOCK, &nmask, &omask); - - pid = fork(); - sigprocmask(SIG_SETMASK, &omask, NULL); - if (pid == 0) { - int i = 0; - int status; -#ifdef ENABLE_STATIC - int uid = 0; /* XXX: Fix better warning that dropprivs is disabled. */ -#else - int uid = getuser(svc->username); -#endif - struct sigaction sa; - char *args[MAX_NUM_SVC_ARGS]; - - sigemptyset(&nmask); - sigaddset(&nmask, SIGCHLD); - sigprocmask(SIG_UNBLOCK, &nmask, NULL); - - /* Reset signal handlers that were set by the parent process */ - for (i = 1; i < NSIG; i++) - DFLSIG(sa, i, 0); - - /* Set desired user */ - if (uid >= 0) - setuid(uid); - - /* Serve copy of args to process in case it modifies them. */ - for (i = 0; i < (MAX_NUM_SVC_ARGS - 1) && svc->args[i][0] != 0; i++) - args[i] = svc->args[i]; - args[i] = NULL; - - /* Redirect inetd socket to stdin for service */ - if (SVC_TYPE_INETD == svc->type) { - /* sd set previously */ - dup2(sd, STDIN_FILENO); - close(sd); - dup2(STDIN_FILENO, STDOUT_FILENO); - dup2(STDIN_FILENO, STDERR_FILENO); - } else if (debug) { - int fd; - char buf[CMD_SIZE] = ""; - - fd = open(CONSOLE, O_WRONLY | O_APPEND); - if (-1 != fd) { - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - close(fd); - } - - for (i = 0; i < MAX_NUM_SVC_ARGS && args[i]; i++) { - char arg[MAX_ARG_LEN]; - - snprintf(arg, sizeof(arg), "%s ", args[i]); - if (strlen(arg) < (sizeof(buf) - strlen(buf))) - strcat(buf, arg); - } - _e("%starting %s: %s", respawn ? "Res" : "S", svc->cmd, buf); - } - - if (svc->inetd.cmd) - status = svc->inetd.cmd(svc->inetd.type); - else - status = execv(svc->cmd, args); /* XXX: Maybe use execve() to be able to launch scripts? */ - - if (SVC_TYPE_INETD == svc->type) { - if (svc->inetd.type == SOCK_STREAM) { - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - } - } - - exit(status); - } - svc->pid = pid; - - if (SVC_TYPE_INETD == svc->type) { - if (svc->inetd.type == SOCK_STREAM) - close(sd); - } - else if (SVC_TYPE_RUN == svc->type) - print_result(WEXITSTATUS(complete(svc->cmd, pid))); - else if (!respawn) - print_result(svc->pid > 1 ? 0 : 1); - - return 0; -} - -int svc_stop(svc_t *svc) -{ - int res = 1; - - if (!svc) { - _e("Failed, no svc pointer."); - return 1; - } - - if (svc->pid <= 1) { - _d("Bad PID %d for %s, SIGTERM", svc->pid, svc->desc); - svc->pid = 0; - svc->restart_counter = 0; - return 1; - } - - if (SVC_TYPE_SERVICE != svc->type) - return 0; - - if (runlevel != 1) - print_desc("Stopping ", svc->desc); - - _d("Sending SIGTERM to pid:%d name:%s", svc->pid, pid_get_name(svc->pid, NULL, 0)); - res = kill(svc->pid, SIGTERM); - - if (runlevel != 1) - print_result(res); - - svc->pid = 0; - svc->restart_counter = 0; - - return res; -} - -/** - * svc_reload - Send SIGHUP to a service - * @svc: Service to reload - * - * This function does some basic checks of the runtime state of Finit - * and a sanity check of the @svc before sending %SIGHUP. - * - * Returns: - * POSIX OK(0) or non-zero on error. - */ -int svc_reload(svc_t *svc) -{ - /* Ignore if finit is SIGSTOP'ed */ - if (is_norespawn()) - return 0; - - if (!svc) { - _e("Failed, no svc pointer."); - return 1; - } - - if (svc->pid <= 1) { - _d("Bad PID %d for %s, SIGHUP", svc->pid, svc->cmd); - svc->pid = 0; - return 1; - } - - _d("Sending SIGHUP to PID %d", svc->pid); - return kill(svc->pid, SIGHUP); -} - -/** - * svc_reload_dynamic - Reload modifued dynamic services - * - * This function is called when Finit has recieved SIGHUP to reload - * .conf files in /etc/finit.d. It is responsible for starting, - * stopping and reloading (forwarding SIGHUP) to processes affected. - */ -void svc_reload_dynamic(void) -{ - int num = 0; - svc_t *svc; - - _d("Stopping disabled/removed services ..."); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->mtime && svc->dirty != 0) { - svc_stop(svc); - num++; - } - } - - if (!num) - return; - - _d("All disabled/removed services have been stoppped, calling reconf hooks ..."); - plugin_run_hooks(HOOK_SVC_RECONF); /* Reconfigure HW/VLANs/etc here */ - - _d("Starting enabled/added services ..."); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->mtime && svc->dirty > 0) - svc_dance(svc); - } -} - -/** - * svc_cleanup - Stop and cleanup stale services removed from /etc/finit.d + * svc_clean_dynamic - Stop and cleanup stale services removed from /etc/finit.d + * @cb: Callback to run for each stale service * * This function traverses the list of known services to stop and clean * up all non-existing services previously marked by svc_mark_dynamic(). */ -void svc_cleanup(void) +void svc_clean_dynamic(void (*cb)(svc_t *)) { - svc_t *svc; + svc_t *svc = svc_dynamic_iterator(1); + + while (svc) { + if (svc->dirty == -1 && cb) + cb(svc); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->mtime && svc->dirty == -1) { - svc_stop(svc); - svc_del(svc); - } svc->dirty = 0; + svc = svc_dynamic_iterator(0); } } + /** * Local Variables: * version-control: t diff --git a/svc.h b/svc.h index b641fdd7..827ba016 100644 --- a/svc.h +++ b/svc.h @@ -1,4 +1,4 @@ -/* Finit service monitor and generic API for managing svc_t structures +/* Low-level service primitives and generic API for managing svc_t structures * * Copyright (c) 2008-2010 Claudio Matsuoka * Copyright (c) 2008-2015 Joachim Nilsson @@ -33,7 +33,6 @@ #include "inetd.h" #include "queue.h" /* BSD sys/queue.h API */ -#include "libuev/uev.h" typedef enum { SVC_STOP = 0, /* Disabled */ @@ -115,23 +114,24 @@ static inline svc_t *finit_svc_connect(void) return (svc_t *)ptr; } -svc_t *svc_new (int id); -int svc_del (svc_t *svc); -svc_t *svc_find (char *path, int id); -svc_t *svc_find_by_pid (pid_t pid); -svc_t *svc_find_inetd (char *path, char *service, char *proto, char *port); -svc_t *svc_iterator (int first); -void svc_mark_dynamic (void); -void svc_runlevel (int newlevel); +svc_t *svc_new (int id); +int svc_del (svc_t *svc); -int svc_register (int type, char *line, time_t mtime, char *username); -int svc_id_by_name (char *name); -svc_cmd_t svc_enabled (svc_t *svc, int event, void *arg); -int svc_start (svc_t *svc); -int svc_stop (svc_t *svc); -int svc_reload (svc_t *svc); -void svc_reload_dynamic(void); -void svc_cleanup (void); +svc_t *svc_find (char *path, int id); +svc_t *svc_find_by_pid (pid_t pid); + +svc_t *svc_iterator (int first); +svc_t *svc_inetd_iterator (int first); +svc_t *svc_dynamic_iterator (int first); + +void svc_foreach (void (*cb)(svc_t *)); +void svc_foreach_dynamic (void (*cb)(svc_t *)); + +void svc_mark_dynamic (void); +void svc_clean_dynamic (void (*cb)(svc_t *)); + +static inline int svc_is_inetd (svc_t *svc) { return svc && SVC_TYPE_INETD == svc->type; } +static inline int svc_is_daemon(svc_t *svc) { return svc && SVC_TYPE_SERVICE == svc->type; } #endif /* FINIT_SVC_H_ */ diff --git a/tty.c b/tty.c index 54d1209c..1344003f 100644 --- a/tty.c +++ b/tty.c @@ -187,7 +187,7 @@ int tty_enabled(finit_tty_t *tty, int runlevel) return 0; } -/* TTY monitor, called by svc_monitor() */ +/* TTY monitor, called by service_monitor() */ int tty_respawn(pid_t pid) { tty_node_t *entry = tty_find_by_pid(pid);