From af7b6c17cb82bfe7a00b9713be8b69861ba2614c Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 2 Dec 2015 12:22:39 +0100 Subject: [PATCH 01/14] Correctly parse service configuration stanzas containing slashes Parser would erroneously interpret an event specification containing slashes as an inet service/proto specification. E.g. "" was interpreted as port "". --- service.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/service.c b/service.c index 16cadef8..f598fa52 100644 --- a/service.c +++ b/service.c @@ -687,15 +687,7 @@ int service_register(int type, char *line, time_t mtime, char *username) } 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] */ + if (cmd[0] == '@') /* @username[:group] */ username = &cmd[1]; else if (cmd[0] == '[') /* [runlevels] */ runlevels = &cmd[0]; @@ -703,6 +695,14 @@ int service_register(int type, char *line, time_t mtime, char *username) events = &cmd[1]; else if (cmd[0] == ':') /* :ID */ id = atoi(&cmd[1]); +#ifndef INETD_DISABLED + else if (!strncasecmp(cmd, "nowait", 6)) + forking = 1; + else if (!strncasecmp(cmd, "wait", 4)) + forking = 0; +#endif + else if (cmd[0] != '/' && strchr(cmd, '/')) + service = cmd; /* inetd service/proto */ else break; From 7a3408c17329339df570c42da4c30786373fbe4f Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 5 Jan 2016 12:48:31 +0100 Subject: [PATCH 02/14] Optionally skip stripping binaries during install-exec One may wish to build and install with debug symbols intact, so allow overriding the arguments sent to install. --- common.mk | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 9f2cb0b4..05cc6b56 100644 --- a/common.mk +++ b/common.mk @@ -1,13 +1,16 @@ # Top directory for building complete system, fall back to this directory ROOTDIR ?= $(TOPDIR) +# Strip binaries unless user explicitly disables it +STRIPARGS ?= -s --strip-program=$(CROSS)strip -m 0755 + # Some junk files we always want to be removed when doing a make clean. JUNK = *~ *.bak *.map .*.d *.d DEADJOE semantic.cache *.gdb *.elf core core.* MAKE := @$(MAKE) MAKEFLAGS = --no-print-directory --silent CHECK := cppcheck $(CPPFLAGS) --quiet --enable=all INSTALL := install --backup=off -STRIPINST := $(INSTALL) -s --strip-program=$(CROSS)strip -m 0755 +STRIPINST := $(INSTALL) $(STRIPARGS) ARFLAGS := crus export libdir plugindir incdir ROOTDIR CPPFLAGS LDFLAGS LDLIBS STATIC From 2232aea0f7ced1b45dddf37e33000ea002ac5c7b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 5 Jan 2016 13:39:27 +0100 Subject: [PATCH 03/14] Dynamically manage service states based on user defined conditions Old event system has been replaced with a more generic condition concept. The idea is that finit plugins may provide arbitrary conditions that services may specify as dependencies that they require to run. In order to accomodate this, the service management has been redesigned to use a state machine. --- Makefile | 6 +- api.c | 46 +-- cond-w.c | 86 ++++++ cond.c | 118 ++++++++ cond.h | 36 +++ conf.c | 12 +- conf.h | 2 +- event.c | 241 ---------------- event.h | 40 --- initctl.c | 78 ++++- plugins/Makefile | 2 +- plugins/netlink.c | 33 ++- plugins/pidfile.c | 121 ++++++++ service.c | 704 +++++++++++++++++++++++----------------------- service.h | 11 +- svc.c | 42 ++- svc.h | 37 ++- 17 files changed, 879 insertions(+), 736 deletions(-) create mode 100644 cond-w.c create mode 100644 cond.c create mode 100644 cond.h delete mode 100644 event.c delete mode 100644 event.h create mode 100644 plugins/pidfile.c diff --git a/Makefile b/Makefile index 45d7cf02..5282ec03 100644 --- a/Makefile +++ b/Makefile @@ -34,10 +34,10 @@ ARCHTOOL = `which git-archive-all` ARCHIVE = $(PKG).tar ARCHIVEZ = ../$(ARCHIVE).xz EXEC = finit initctl reboot -HEADERS = finit.h plugin.h svc.h inetd.h helpers.h queue.h +HEADERS = finit.h plugin.h svc.h inetd.h helpers.h queue.h cond.h DISTFILES = LICENSE README ChangeLog finit.conf services OBJS = finit.o api.o client.o conf.o exec.o helpers.o pid.o sig.o \ - svc.o service.o plugin.o tty.o inetd.o event.o + svc.o service.o plugin.o tty.o inetd.o cond.o cond-w.o DEPLIBS = TOPDIR = $(shell pwd) -include config.mk @@ -70,7 +70,7 @@ config.h: configure finit: $(OBJS) $(DEPLIBS) -initctl: initctl.o svc.o helpers.o $(DEPLIBS) +initctl: initctl.o svc.o helpers.o cond.o $(DEPLIBS) reboot: reboot.o $(DEPLIBS) diff --git a/api.c b/api.c index 4465f402..1569a598 100644 --- a/api.c +++ b/api.c @@ -32,6 +32,7 @@ #include "config.h" #include "finit.h" +#include "cond.h" #include "conf.h" #include "helpers.h" #include "plugin.h" @@ -46,7 +47,7 @@ uev_t api_watcher; /* Allowed characters in job/id/name */ static int isallowed(int ch) { - return isalnum(ch) || isspace(ch) || ch == ':'; + return isprint(ch); } /* Sanitize user input, make sure to NUL terminate. */ @@ -113,14 +114,29 @@ static int call(int (*action)(svc_t *), char *buf, size_t len) return result; } -static int service_pause(svc_t *svc) +static int service_block(svc_t *svc) { - return service_stop(svc, SVC_PAUSED_STATE); + svc->block = SVC_BLOCK_USER; + service_step(svc); + return 0; } -static int do_start (char *buf, size_t len) { return call(service_start, buf, len); } -static int do_pause (char *buf, size_t len) { return call(service_pause, buf, len); } -static int do_reload (char *buf, size_t len) { return call(service_reload, buf, len); } +static int service_unblock(svc_t *svc) +{ + svc->block = SVC_BLOCK_NONE; + service_step(svc); + return 0; +} + +static int service_restart(svc_t *svc) +{ + svc->dirty = 1; + service_step(svc); + return 0; +} + +static int do_start (char *buf, size_t len) { return call(service_unblock, buf, len); } +static int do_pause (char *buf, size_t len) { return call(service_block, buf, len); } static int do_restart(char *buf, size_t len) { return call(service_restart, buf, len); } #ifndef INETD_DISABLED @@ -155,9 +171,7 @@ typedef struct { } ev_t; ev_t ev_list[] = { - { "RELOAD", conf_reload_dynamic }, - { "STOP", service_stop_dynamic }, - { "START", service_start_dynamic }, + { "RELOAD", service_reload_dynamic }, { NULL, NULL } }; @@ -175,9 +189,13 @@ static int do_handle_event(char *event) } } - /* XXX: iterate over all services' events before failing. */ - - return -1; + if (event[0] == '-') + cond_clear(&event[1]); + else if (event[0] == '+') + cond_set(&event[1]); + else + cond_set(event); + return 0; } static int do_handle_emit(char *buf, size_t len) @@ -284,10 +302,6 @@ static void cb(uev_t *w, void *UNUSED(arg), int UNUSED(events)) result = do_pause(rq.data, sizeof(rq.data)); break; - case INIT_CMD_RELOAD_SVC: - result = do_reload(rq.data, sizeof(rq.data)); - break; - case INIT_CMD_RESTART_SVC: result = do_restart(rq.data, sizeof(rq.data)); break; diff --git a/cond-w.c b/cond-w.c new file mode 100644 index 00000000..e5ff6b42 --- /dev/null +++ b/cond-w.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include "finit.h" +#include "cond.h" +#include "service.h" + +int cond_set_path(const char *path, enum cond_state new) +{ + static char dir[MAX_ARG_LEN]; + + enum cond_state old; + + old = cond_get_path(path); + + switch (new) { + case COND_ON: + strlcpy(dir, path, sizeof(dir)); + makepath(dirname(dir)); + touch(path); + utime(path, NULL); + break; + case COND_OFF: + unlink(path); + break; + default: + _e("Invalid condition state"); + return 0; + } + + return new != old; +} + +static void cond_update(const char *name) +{ + svc_t *svc; + + _d("%s", name); + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->type != SVC_TYPE_SERVICE || + !svc->cond[0] || + (name && !cond_affects(name, svc->cond))) { + continue; + } + + _d("%s: match <%s> %s", name, svc->cond, svc->cmd); + service_step(svc); + } +} + +void cond_set(const char *name) +{ + _d("%s", name); + if (!cond_set_path(cond_path(name), COND_ON)) + return; + + cond_update(name); +} + +void cond_clear(const char *name) +{ + _d("%s", name); + if (!cond_set_path(cond_path(name), COND_OFF)) + return; + + cond_update(name); +} + +void cond_reload(void) +{ + _d(""); + cond_set_path(COND_RECONF, COND_ON); + + cond_update(NULL); +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/cond.c b/cond.c new file mode 100644 index 00000000..2ca711b3 --- /dev/null +++ b/cond.c @@ -0,0 +1,118 @@ +/* Event aggregator, also serves as event cache, remembering GW and IFUP states + * + * Copyright (c) 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 "finit.h" +#include "cond.h" +#include "service.h" + +static inline int timespec_newer(const struct timespec *a, + const struct timespec *b) +{ + if (a->tv_sec != b->tv_sec) + return a->tv_sec > b->tv_sec; + + return a->tv_nsec > b->tv_nsec; +} + +const char *condstr(enum cond_state s) +{ + static const char *strs[] = { + [COND_OFF] = "off", + [COND_FLUX] = "flux", + [COND_ON] = "on", + }; + + return strs[s]; +} + +const char *cond_path(const char *name) +{ + static char file[MAX_ARG_LEN]; + + snprintf(file, sizeof(file), COND_PATH "/%s", name); + return file; +} + +enum cond_state cond_get_path(const char *path) +{ + struct stat st, st_reconf; + + if (stat(path, &st)) + return COND_OFF; + + if (stat(COND_RECONF, &st_reconf) || + timespec_newer(&st.st_mtim, &st_reconf.st_mtim)) + return COND_ON; + + return COND_FLUX; +} + +enum cond_state cond_get(const char *name) +{ + return cond_get_path(cond_path(name)); +} + +enum cond_state cond_get_agg(const char *names) +{ + static char conds[MAX_ARG_LEN]; + + enum cond_state s = COND_ON; + char *cond; + + if (!names) + return COND_ON; + + strlcpy(conds, names, sizeof(conds)); + for (cond = strtok(conds, ","); s && cond; cond = strtok(NULL, ",")) + s = min(s, cond_get(cond)); + + return s; +} + +int cond_affects(const char *name, const char *names) +{ + static char conds[MAX_ARG_LEN]; + + char *cond; + + if (!name || !names) + return 0; + + strlcpy(conds, names, sizeof(conds)); + for (cond = strtok(conds, ","); cond; cond = strtok(NULL, ",")) + if (!strcmp(cond, name)) + return 1; + + return 0; +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/cond.h b/cond.h new file mode 100644 index 00000000..7a574aaa --- /dev/null +++ b/cond.h @@ -0,0 +1,36 @@ +#ifndef FINIT_COND_H_ +#define FINIT_COND_H_ + +#include + +#define COND_PATH _PATH_VARRUN "finit/cond" +#define COND_SVC_PATH COND_PATH "/svc/" +#define COND_RECONF COND_PATH "/reconf" + +typedef enum cond_state { + COND_OFF = 0, + COND_FLUX, + COND_ON +} cond_state_t; + +const char *condstr (enum cond_state s); +const char *cond_path (const char *name); +enum cond_state cond_get_path(const char *path); +enum cond_state cond_get (const char *name); +enum cond_state cond_get_agg (const char *names); +int cond_affects (const char *name, const char *names); + +int cond_set_path(const char *path, enum cond_state new); +void cond_set (const char *name); +void cond_clear (const char *name); +void cond_reload (void); + +#endif /* FINIT_COND_H_ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/conf.c b/conf.c index 547a672f..66831c5b 100644 --- a/conf.c +++ b/conf.c @@ -28,6 +28,7 @@ #include #include "finit.h" +#include "cond.h" #include "service.h" #include "tty.h" #include "libite/lite.h" @@ -113,7 +114,7 @@ int conf_parse_runlevels(char *runlevels) return bitmask; } -void conf_parse_events(svc_t *svc, char *events) +void conf_parse_cond(svc_t *svc, char *cond) { size_t i = 0; char *ptr; @@ -126,11 +127,11 @@ void conf_parse_events(svc_t *svc, char *events) /* By default we assume UNIX daemons support SIGHUP */ svc->sighup = 1; - if (!events) + if (!cond) return; /* First character must be '!' if SIGHUP is not supported. */ - ptr = events; + ptr = cond; if (ptr[i] == '!') { svc->sighup = 0; ptr++; @@ -140,13 +141,12 @@ void conf_parse_events(svc_t *svc, char *events) i++; ptr[i] = 0; - if (i >= sizeof(svc->events)) { + if (i >= sizeof(svc->cond)) { FLOG_WARN("Too long event list in declaration of %s: %s", svc->cmd, ptr); return; } - svc->state = SVC_CONDHALT_STATE; - strlcpy(svc->events, ptr, sizeof(svc->events)); + strlcpy(svc->cond, ptr, sizeof(svc->cond)); } static void parse_static(char *line) diff --git a/conf.h b/conf.h index d96ae133..01077490 100644 --- a/conf.h +++ b/conf.h @@ -28,7 +28,7 @@ void conf_parse_cmdline (void); int conf_parse_runlevels (char *runlevels); -void conf_parse_events (svc_t *svc, char *events); +void conf_parse_cond (svc_t *svc, char *cond); int conf_parse_config (void); void conf_reload_dynamic (void); diff --git a/event.c b/event.c deleted file mode 100644 index 1fd89f0d..00000000 --- a/event.c +++ /dev/null @@ -1,241 +0,0 @@ -/* Event aggregator, also serves as event cache, remembering GW and IFUP states - * - * Copyright (c) 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 "config.h" /* Generated by configure script */ -#include "libite/lite.h" - -#include "finit.h" -#include "service.h" - -struct ifc { - LIST_ENTRY(ifc) link; - - int updown; - char ifname[IFNAMSIZ]; -}; - -static int gwup = 0; -static LIST_HEAD(, ifc) ifcache = LIST_HEAD_INITIALIZER(); - - -static int cache_gw(char *updown) -{ - int oldgw = gwup; - - if (!strncasecmp(updown, "UP", 2)) - gwup = 1; - else - gwup = 0; - - return (oldgw == gwup) ? 0 : gwup == 0 ? -1 : 1; -} - -static struct ifc *find_if(char *ifname) -{ - struct ifc *entry; - - LIST_FOREACH(entry, &ifcache, link) { - if (!strncmp(entry->ifname, ifname, sizeof(entry->ifname))) - return entry; - } - - return NULL; -} - -static int alloc_if(char *ifname, int updown) -{ - struct ifc *entry = calloc(1, sizeof(struct ifc)); - - if (!entry) { - _pe("Failed recording interface event"); - return 0; - } - - _d("Creating new node for %s", ifname); - entry->updown = updown; - strlcpy(entry->ifname, ifname, sizeof(entry->ifname)); - LIST_INSERT_HEAD(&ifcache, entry, link); - - return 1; -} - -static int free_if(char *ifname) -{ - struct ifc *entry = find_if(ifname); - - if (!entry) - return 0; - - LIST_REMOVE(entry, link); - return -1; -} - -static int cache_if(char *ifname, int updown) -{ - struct ifc *entry = find_if(ifname); - - if (entry) { - int old = entry->updown; - - entry->updown = updown; - return old == updown ? 0 : updown ? 1 : -1; - } - - return alloc_if(ifname, updown); -} - -/* - * System events like GW/IF are cached, this function caters to that - * Returns: 0 if no change, -1 on condition low, +1 on condition high - */ -static int event_cache(char *msg) -{ - if (!strncmp(msg, "GW:", 3)) - return cache_gw(&msg[3]); - - if (!strncmp(msg, "IFUP:", 5)) - return cache_if(&msg[5], 1); - - if (!strncmp(msg, "IFDN:", 5)) - return cache_if(&msg[5], 0); - - if (!strncmp(msg, "IFDEL:", 6)) - return free_if(&msg[6]); - - return 0; /* No chnage, unknown event. */ -} - -int event_cache_gw(void) -{ - return gwup; -} - -int event_cache_if(char *ifname) -{ - struct ifc *entry = find_if(ifname); - - if (!entry) - return 0; - - return entry->updown; -} - -static int has_events(char *events) -{ - return events && events[0]; -} - -static int matches_event(char *events, char *event) -{ - return strcasestr(events, event) ? 1 : 0; -} - -int event_service_cond(char *events) -{ - int cond = 1; - char *msg; - char temp[MAX_ARG_LEN]; - - /* No required events, condition satisfied */ - if (!has_events(events)) - return cond; - - strlcpy(temp, events, sizeof(temp)); - msg = strtok(temp, ","); - while (msg) { - _d("Looking for %s (in %s), cond %d", msg, events, cond); - - if (!strncmp(msg, "GW", 2)) - cond &= event_cache_gw(); - else if (!strncmp(msg, "IFUP:", 5)) - cond &= event_cache_if(&msg[5]); - else if (!strncmp(msg, "IFDN:", 5)) - cond &= event_cache_if(&msg[5]); - else - _e("Unknown event %s, discarding.", msg); - - _d("=> cond %d", cond); - msg = strtok(NULL, ","); - } - - return cond; -} - -/* - * Dispatch an event - * - * Called by initctl (via api.c) or plugins/netlink.so - * May also cache for future reference. - */ -void event_dispatch(char *msg) -{ - int change; - svc_t *svc; - - if (!msg) { - _e("Invalid message received."); - return; - } - - _d("%s", msg); - change = event_cache(msg); - if (!change) { - _d("Nothing to do"); - return; - } - - /* Iterate over svc_t and call service_restart() for event matches */ - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->type != SVC_TYPE_SERVICE || - !service_enabled(svc, 1, NULL) || - !has_events(svc->events) || - !matches_event(svc->events, msg)) { - _d("No match for %s in service %s", msg, svc->cmd); - continue; - } - - if (change == 1) { - _d("%s matches <%s> %s (re)starting ...", msg, svc->events, svc->cmd); - if (!svc->pid) { - service_start(svc); - continue; - } - - if (svc->sighup) - service_reload(svc); - else - service_restart(svc); - } else { /* change == -1 */ - if (svc->pid && !event_service_cond(svc->events)) - service_stop(svc, SVC_CONDHALT_STATE); - } - } -} - -/** - * Local Variables: - * version-control: t - * indent-tabs-mode: t - * c-file-style: "linux" - * End: - */ diff --git a/event.h b/event.h deleted file mode 100644 index 129a22be..00000000 --- a/event.h +++ /dev/null @@ -1,40 +0,0 @@ -/* Event aggregator, also serves as event cache, remembering GW and IFUP states - * - * Copyright (c) 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_EVENT_H_ -#define FINIT_EVENT_H_ - -int event_cache_gw (void); -int event_cache_if (char *ifname); -int event_service_cond (char *events); -void event_dispatch (char *msg); - -#endif /* FINIT_EVENT_H_ */ - -/** - * Local Variables: - * version-control: t - * indent-tabs-mode: t - * c-file-style: "linux" - * End: - */ diff --git a/initctl.c b/initctl.c index 4f40d20c..f03e020d 100644 --- a/initctl.c +++ b/initctl.c @@ -28,6 +28,7 @@ #include #include "finit.h" +#include "cond.h" #include "helpers.h" #include "service.h" @@ -107,26 +108,78 @@ static int do_svc(int cmd, char *arg) .cmd = cmd, }; - if (!arg || !arg[0]) { - if (cmd == INIT_CMD_RELOAD_SVC) { - rq.cmd = INIT_CMD_RELOAD; - goto exit; - } - - return 1; - } strlcpy(rq.data, arg, sizeof(rq.data)); -exit: return do_send(&rq, sizeof(rq)); } static int do_emit (char *arg) { return do_svc(INIT_CMD_EMIT, arg); } +static int do_reload (char *arg) { return do_svc(INIT_CMD_RELOAD, arg); } static int do_start (char *arg) { return do_svc(INIT_CMD_START_SVC, arg); } static int do_stop (char *arg) { return do_svc(INIT_CMD_STOP_SVC, arg); } -static int do_reload (char *arg) { return do_svc(INIT_CMD_RELOAD_SVC, arg); } static int do_restart(char *arg) { return do_svc(INIT_CMD_RESTART_SVC, arg); } +static void show_cond_one(const char *_conds) +{ + static char conds[MAX_ARG_LEN]; + char *cond; + + strlcpy(conds, _conds, sizeof(conds)); + + putchar('<'); + + for (cond = strtok(conds, ","); cond; cond = strtok(NULL, ",")) { + if (cond != conds) + putchar(','); + + switch (cond_get(cond)) { + case COND_ON: + printf("+%s", cond); + break; + case COND_FLUX: + printf("\e[1m~%s\e[0m", cond); + break; + case COND_OFF: + printf("\e[1m-%s\e[0m", cond); + break; + } + } + + putchar('>'); +} + +static void show_cond(void) +{ + enum cond_state cond; + svc_t *svc; + + printf("PID Service Status Condition (+ on, ~ flux, - off)\n"); + printf("====================================================================================\n"); + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (!svc->cond[0]) + continue; + + cond = cond_get_agg(svc->cond); + + printf("%-6d %-20.20s ", svc->pid, svc->cmd); + + if (cond == COND_ON) + printf("%-6.6s ", condstr(cond)); + else + printf("\e[1m%-6.6s\e[0m ", condstr(cond)); + + show_cond_one(svc->cond); + putchar('\n'); + } +} + +static int do_cond(char *arg) +{ + show_cond(); + return 0; +} + static int show_version(char *UNUSED(arg)) { puts("v" VERSION); @@ -250,12 +303,10 @@ static int usage(int rc) " reload Reload *.conf in /etc/finit.d/ and activate changes\n" " runlevel [0-9] Show or set runlevel: 0 halt, 6 reboot\n" " status | show Show status of services\n" + " cond show Show condition status\n" " start [:ID] Start service by job# or name, with optional ID\n" " stop [:ID] Stop/Pause a running service by job# or name\n" " restart [:ID] Restart (stop/start) service by job# or name\n" - " reload [:ID] Reload (SIGHUP) service by job# or name\n" - " status [:ID] Show status of a service by job# or name\n" - " show [:ID] Alias to 'status [:ID]'\n" " version Show Finit version\n\n", __progname); return rc; @@ -271,6 +322,7 @@ int main(int argc, char *argv[]) { "runlevel", do_runlevel }, { "status", show_status }, { "show", show_status }, /* Convenience alias */ + { "cond", do_cond }, { "start", do_start }, { "stop", do_stop }, { "restart", do_restart }, diff --git a/plugins/Makefile b/plugins/Makefile index f94dabf3..112e6e06 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -8,7 +8,7 @@ LDFLAGS += -shared ifneq ($(STATIC), 1) PLUGINS ?= initctl.so alsa-utils.so bootmisc.so dbus.so hwclock.so \ resolvconf.so urandom.so x11-common.so tty.so time.so \ - netlink.so + netlink.so pidfile.so DEPS := $(PLUGINS:.so=.d) endif diff --git a/plugins/netlink.c b/plugins/netlink.c index 5b6a7bed..ca8fdbac 100644 --- a/plugins/netlink.c +++ b/plugins/netlink.c @@ -16,6 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include /* IFNAMSIZ */ #include @@ -25,7 +26,7 @@ #include #include "../finit.h" -#include "../event.h" +#include "../cond.h" #include "../helpers.h" #include "../plugin.h" @@ -86,13 +87,10 @@ static void nl_route(struct nlmsghdr *nlmsg) } if ((!dst && !mask) && (gw || idx)) { - char msg[MAX_ARG_LEN]; - if (nlmsg->nlmsg_type == RTM_DELROUTE) - snprintf(msg, sizeof(msg), "GW:DN"); + cond_clear("net/gw"); else - snprintf(msg, sizeof(msg), "GW:UP"); - event_dispatch(msg); + cond_set("net/gw"); } } @@ -123,19 +121,24 @@ static void nl_link(struct nlmsghdr *nlmsg) * New interface has appearad, or interface flags has changed. * Check ifi_flags here to see if the interface is UP/DOWN */ - if (i->ifi_change & IFF_UP) - snprintf(msg, sizeof(msg), "IF%s:%s", - (i->ifi_flags & IFF_UP) ? "UP" : "DN", - ifname); - else - snprintf(msg, sizeof(msg), "IFADD:%s", ifname); - event_dispatch(msg); + if (i->ifi_change & IFF_UP) { + snprintf(msg, sizeof(msg), "net/%s/up", ifname); + + if (i->ifi_flags & IFF_UP) + cond_set(msg); + else + cond_clear(msg); + + } else { + snprintf(msg, sizeof(msg), "net/%s/exist", ifname); + cond_set(msg); + } break; case RTM_DELLINK: /* NOTE: Interface has dissapeared, not link down ... */ - snprintf(msg, sizeof(msg), "IFDEL:%s", ifname); - event_dispatch(msg); + snprintf(msg, sizeof(msg), "net/%s/exist", ifname); + cond_clear(msg); break; case RTM_NEWADDR: diff --git a/plugins/pidfile.c b/plugins/pidfile.c new file mode 100644 index 00000000..d25a76f0 --- /dev/null +++ b/plugins/pidfile.c @@ -0,0 +1,121 @@ +#include +#include + +#include + +#include "../finit.h" +#include "../cond.h" +#include "../helpers.h" +#include "../plugin.h" + +struct context { + int fd; + int wd; +}; + +static void pidfile_callback(void *UNUSED(arg), int fd, int UNUSED(events)) +{ + static char ev_buf[8 *(sizeof(struct inotify_event) + NAME_MAX + 1)]; + static char cond[MAX_ARG_LEN]; + + struct inotify_event *ev; + ssize_t sz, len; + char *basename; + svc_t *svc; + + sz = read(fd, ev_buf, sizeof(ev_buf)); + if (sz <= 0) { + _pe("invalid inotify event\n"); + return; + } + + for (ev = (void *)ev_buf; sz > sizeof(*ev); + len = sizeof(*ev) + ev->len, ev = (void *)ev + len, sz -= len) { + /* ev = (void *)(ev + 1) + ev->len, sz -= sizeof(*ev) + ev->len) { */ + if (!ev->mask || !strstr(ev->name, ".pid")) + continue; + + basename = strtok(ev->name, "."); + svc = svc_find_by_nameid(basename, 1); + if (!svc) + continue; + + /* TODO FIXME XXX WKZ check that pid is controlled by finit */ + + _d("%s: match %s", basename, svc->cmd); + snprintf(cond, sizeof(cond), "svc%s", svc->cmd); + if (ev->mask & (IN_CREATE | IN_ATTRIB)) + cond_set(cond); + else if (ev->mask & IN_DELETE) + cond_clear(cond); + } +} + +static void pidfile_reconf(void *_null) +{ + static char name[MAX_ARG_LEN]; + + svc_t *svc; + (void)(_null); + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->state == SVC_RUNNING_STATE && !svc_is_changed(svc)) { + snprintf(name, MAX_ARG_LEN, "svc%s", svc->cmd); + cond_set_path(cond_path(name), COND_ON); + } + } +} + +static void pidfile_init (void *arg) +{ + struct context *ctx = arg; + + ctx->wd = inotify_add_watch(ctx->fd, _PATH_VARRUN, + IN_CREATE | IN_ATTRIB | IN_DELETE); + if (ctx->wd < 0) { + _pe("inotify_add_watch()"); + close(ctx->fd); + return; + } + + _d("pidfile monitor active"); +} + +static struct context pidfile_ctx; + +static plugin_t plugin = { + .hook[HOOK_BASEFS_UP] = { .arg = &pidfile_ctx, .cb = pidfile_init }, + .hook[HOOK_SVC_RECONF] = { .cb = pidfile_reconf }, + .io = { + .cb = pidfile_callback, + .flags = PLUGIN_IO_READ, + }, +}; + +PLUGIN_INIT(plugin_init) +{ + pidfile_ctx.fd = inotify_init(); + if (pidfile_ctx.fd < 0) { + _pe("inotify_init()"); + return; + } + + plugin.io.fd = pidfile_ctx.fd; + plugin_register(&plugin); +} + +PLUGIN_EXIT(plugin_exit) +{ + inotify_rm_watch(pidfile_ctx.fd, pidfile_ctx.wd); + close(pidfile_ctx.fd); + + plugin_unregister(&plugin); +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/service.c b/service.c index f598fa52..6d9b38c9 100644 --- a/service.c +++ b/service.c @@ -31,7 +31,7 @@ #include "finit.h" #include "conf.h" -#include "event.h" +#include "cond.h" #include "helpers.h" #include "private.h" #include "sig.h" @@ -41,15 +41,12 @@ #define RESPAWN_MAX 10 /* Prevent endless respawn of faulty services. */ -static int dyn_stop_cnt = 0; +static int in_teardown = 0, in_dyn_teardown = 0; -static int is_norespawn (void); -static void restart_lost_procs (void); -static void svc_dance (svc_t *svc); #ifndef INETD_DISABLED static svc_t *find_inetd_svc (char *path, char *service, char *proto); #endif - + /** * service_bootstrap - Start bootstrap services and tasks * @@ -58,114 +55,69 @@ static svc_t *find_inetd_svc (char *path, char *service, char *proto); */ 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_step_all(SVC_TYPE_RUN | SVC_TYPE_TASK | SVC_TYPE_SERVICE); } /** * 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. + * @svc: Pointer to &svc_t object * * Returns: - * Either one of %SVC_START, %SVC_STOP, %SVC_RELOAD. + * 1, if the service is allowed to run in the current runlevel and the + * user has not manually requested that this service should not run. 0 + * otherwise. */ -svc_cmd_t service_enabled(svc_t *svc, int event, void *arg) +int service_enabled(svc_t *svc) { - svc_cmd_t cmd = SVC_START; /* Default to start, since listed in finit.conf */ + if (!svc || + !svc_in_runlevel(svc, runlevel) || + svc_is_removed(svc) || + svc->block != SVC_BLOCK_NONE) + return 0; - if (!svc) { - errno = EINVAL; - return SVC_STOP; - } - - if (!svc_in_runlevel(svc, runlevel)) - return SVC_STOP; - - /* - * Event conditions for services are ignored during bootstrap. - */ - _d("Checking %s runlevel %d and events %s", svc->cmd, runlevel, svc->events); - if (runlevel && !event_service_cond(svc->events)) - return SVC_STOP; - - if (svc->state == SVC_RELOAD_STATE) - cmd = SVC_RELOAD; - if (svc->state == SVC_PAUSED_STATE) - cmd = 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) - _exit(svc->cb(svc, event, arg)); - - 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)) { - svc_cmd_t tmp = WEXITSTATUS(status); - return tmp == SVC_START ? cmd : tmp; - } - - /* Check for SEGFAULT or other error ... */ - if (WIFSIGNALED(status) && 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; - } - - _d("%s => %s", svc->cmd, (cmd == SVC_START - ? "SVC_START" - : (cmd == SVC_RELOAD - ? "SVC_RELOAD" - : "SVC_STOP"))); - - return cmd; + return 1; } -/* Remember: service_enabled() must be called before calling service_start() */ -int service_start(svc_t *svc) +/** + * service_stop_is_done - Have all stopped services been collected? + * + * Returns: + * 1, if all stopped services have been collected. 0 otherwise. + */ +static int service_stop_is_done(void) { - int respawn, sd = 0; + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) + if (svc->state == SVC_STOPPING_STATE) + return 0; + + return 1; +} + +static int is_norespawn(void) +{ + return sig_stopped() || + fexist("/mnt/norespawn") || + fexist("/tmp/norespawn"); +} + +/** + * service_start - Start service + * @svc: Service to start + * + * Returns: + * 0 if the service was successfully started. Non-zero otherwise. + */ +static int service_start(svc_t *svc) +{ + int sd = 0; pid_t pid; sigset_t nmask, omask; if (!svc) return 1; - respawn = svc->pid != 0; /* Don't try and start service if it doesn't exist. */ if (!fexist(svc->cmd) && !svc->inetd.cmd) { @@ -177,12 +129,13 @@ int service_start(svc_t *svc) print_result(1); } + svc->block = SVC_BLOCK_MISSING; return 1; } /* Ignore if finit is SIGSTOP'ed */ if (is_norespawn()) - return 0; + return 1; #ifndef INETD_DISABLED if (svc_is_inetd(svc)) { @@ -221,9 +174,9 @@ int service_start(svc_t *svc) #endif if (verbose) { if (svc_is_daemon(svc)) - print_desc("", svc->desc); - else if (!respawn) print_desc("Starting ", svc->desc); + else + print_desc("", svc->desc); } /* Block sigchild while forking. */ @@ -291,7 +244,7 @@ int service_start(svc_t *svc) if (strlen(arg) < (sizeof(buf) - strlen(buf))) strcat(buf, arg); } - _e("%starting %s: %s", respawn ? "Res" : "S", svc->cmd, buf); + _e("Starting %s: %s", svc->cmd, buf); } sig_unblock(); @@ -312,20 +265,15 @@ int service_start(svc_t *svc) exit(status); } svc->pid = pid; - svc->state = SVC_RUNNING_STATE; if (svc_is_inetd(svc)) { if (svc->inetd.type == SOCK_STREAM) close(sd); } else { - int result; + int result = 0; if (SVC_TYPE_RUN == svc->type) result = WEXITSTATUS(complete(svc->cmd, pid)); - else if (!respawn) - result = svc->pid > 1 ? 0 : 1; - else - result = 0; if (verbose) print_result(result); @@ -334,7 +282,14 @@ int service_start(svc_t *svc) return 0; } -int service_stop(svc_t *svc, int state) +/** + * service_stop - Stop service + * @svc: Service to stop + * + * Returns: + * 0 if the service was successfully stopped. Non-zero otherwise. + */ +static int service_stop(svc_t *svc) { int res = 0; @@ -343,17 +298,12 @@ int service_stop(svc_t *svc, int state) if (svc->pid <= 1) { _d("Bad PID %d for %s, SIGTERM", svc->pid, svc->desc); - res = 1; - goto exit; + return 1; } if (SVC_TYPE_SERVICE != svc->type) return 0; - _d("Service %s state %d, new state %d, sighup %d", svc->cmd, svc->state, state, svc->sighup); - if (state == SVC_RELOAD_STATE && svc->sighup) - goto exit; - if (runlevel != 1 && verbose) print_desc("Stopping ", svc->desc); @@ -362,114 +312,12 @@ int service_stop(svc_t *svc, int state) if (runlevel != 1 && verbose) print_result(res); -exit: - if (!res) - svc->state = state; - svc->restart_counter = 0; return res; } /** - * service_start_dynamic - Start new or reload modified dynamic services - */ -void service_start_dynamic(void) -{ - svc_t *svc; - - _d("Starting enabled/added services ..."); - for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0)) { - if (svc_is_updated(svc)) - svc_dance(svc); - } - - /* Cleanup stale services */ - svc_clean_dynamic(service_unregister); -} - -/* - * Unless there are services we must collect first (wait for - * them to stop), we can call HOOK_SVC_RECONF here. - */ -static int service_stop_done(svc_t *svc) -{ - if (svc && !svc_is_changed(svc)) - return 0; - - if (svc && dyn_stop_cnt) - dyn_stop_cnt--; - - _d("dyn_stop_cnt %d", dyn_stop_cnt); - if (!dyn_stop_cnt) { - _d("All disabled/removed services have been stoppped, calling reconf hooks ..."); - plugin_run_hooks(HOOK_SVC_RECONF); /* Reconfigure HW/VLANs/etc here */ - - /* Finish off by starting/reload modified/new services */ - service_start_dynamic(); - } - - return 1; -} - -/** - * service_stop_dynamic - Stop disabled/removed dynamic services - * - * We call it "stop", but in reality it could be "skip" as well, if the - * service supports SIGHUP. This function is just one step on the road - * to reload all modified services. - */ -void service_stop_dynamic(void) -{ - svc_t *svc; - - _d("Stopping disabled/removed services ..."); - for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0)) { - if (svc_is_changed(svc) && svc->pid) { - svc_state_t new_state = SVC_RELOAD_STATE; - - if (svc_is_removed(svc)) - new_state = SVC_HALTED_STATE; - - if (!svc_has_sighup(svc)) - dyn_stop_cnt++; - - _d("Marking service %s as state %d", svc->cmd, new_state); - service_stop(svc, new_state); - } - } - - /* Check if we need to collect any services before calling user HOOK */ - service_stop_done(NULL); -} - -/** - * service_restart - Restart or SIGHUP a process - * - * Unfortunately we cannot call service_start() here because we must - * wait for the svc to actually be collected by the service_monitor() - * first. Currently this means that an initctl user will not get - * correct feedback when restarting a service. (Prompt may return - * before we've actually started upp the service again.) - * - * XXX: This should be refactored to let initctl check the sighup - * flag and then to stop+start or reload. - */ -int service_restart(svc_t *svc) -{ - if (!svc) - return 1; - - /* Ignore if finit is SIGSTOP'ed */ - if (is_norespawn()) - return 0; - - svc->restart_counter = 0; - - return service_stop(svc, SVC_RESTART_STATE); -} - -/** - * service_reload - Send SIGHUP to a service + * service_restart - Restart a service by sending %SIGHUP * @svc: Service to reload * * This function does some basic checks of the runtime state of Finit @@ -478,13 +326,15 @@ int service_restart(svc_t *svc) * Returns: * POSIX OK(0) or non-zero on error. */ -int service_reload(svc_t *svc) +static int service_restart(svc_t *svc) { + int err; + /* Ignore if finit is SIGSTOP'ed */ if (is_norespawn()) - return 0; + return 1; - if (!svc) + if (!svc || !svc->sighup) return 1; if (svc->pid <= 1) { @@ -492,10 +342,40 @@ int service_reload(svc_t *svc) svc->pid = 0; return 1; } - svc->state = SVC_RUNNING_STATE; + + if (verbose) + print_desc("Restarting ", svc->desc); _d("Sending SIGHUP to PID %d", svc->pid); - return kill(svc->pid, SIGHUP); + err = kill(svc->pid, SIGHUP); + + if (verbose) + print_result(err); + return err; +} + +/** + * service_reload_dynamic_finish - Finish dynamic service reload + * + * Second stage of dynamic reload. Called either directly from first + * stage if no services had to be stopped, or later from + * service_monitor once all stopped services have been collected. + */ +static void service_reload_dynamic_finish(void) +{ + in_dyn_teardown = 0; + + /* Cleanup stale services */ + svc_clean_dynamic(service_unregister); + + _d("Starting services after reconf ..."); + service_step_all(SVC_TYPE_SERVICE); + + _d("Calling reconf hooks ..."); + plugin_run_hooks(HOOK_SVC_RECONF); + + service_step_all(SVC_TYPE_SERVICE); + _d("Reconfiguration done"); } /** @@ -510,78 +390,38 @@ void service_reload_dynamic(void) /* First reload all *.conf in /etc/finit.d/ */ conf_reload_dynamic(); - /* Then stop any disabled/removed services and non-reloadable */ - service_stop_dynamic(); + /* Then, mark all affected service conditions as in-flux and + * let all affected services move to WAITING/HALTED */ + _d("Stopping services services not allowed after reconf ..."); + in_dyn_teardown = 1; + cond_reload(); + service_step_all(SVC_TYPE_SERVICE); - /* - * Finish off by starting/reloading modified/new services. - * Postponed to service_stop_done() to make sure all services - * are guaranteed to have been stopped before being started - * again. - */ + /* Need to wait for any services to stop? If so, exit early + * and perform second stage from service_monitor later. */ + if (!service_stop_is_done()) + return; + + /* Otherwise, kick all svcs again right away */ + service_reload_dynamic_finish(); } /** - * service_runlevel - Change to a new runlevel - * @newlevel: New runlevel to activate + * service_runlevel_finish - Finish runlevel change * - * Stops all services not in @newlevel and starts, or lets continue to run, - * those in @newlevel. Also updates @prevlevel and active @runlevel. + * Second stage of runlevel change. Called either directly from first + * stage if no services had to be stopped, or later from + * service_monitor once all stopped services have been collected. */ -void service_runlevel(int newlevel) +static void service_runlevel_finish(void) { - 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); - runlevel_set(prevlevel, newlevel); - - /* Make sure to (re)load all *.conf in /etc/finit.d/ */ - conf_reload_dynamic(); - - _d("Stopping services services not allowed in new runlevel ..."); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (!svc_in_runlevel(svc, runlevel)) { -#ifndef INETD_DISABLED - if (svc_is_inetd(svc)) - inetd_stop(&svc->inetd); - else -#endif - service_stop(svc, SVC_HALTED_STATE); - } - - /* ... or disabled/removed services from /etc/finit.d/ */ - if (svc_is_dynamic(svc) && svc_is_changed(svc)) - service_stop(svc, SVC_HALTED_STATE); - } - /* 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 (svc_in_runlevel(svc, runlevel)) - inetd_start(&svc->inetd); - - continue; - } -#endif - - /* All other services consult their callback here */ - svc_dance(svc); - } + in_teardown = 0; + service_step_all(SVC_TYPE_ANY); /* Cleanup stale services */ svc_clean_dynamic(service_unregister); @@ -605,6 +445,42 @@ void service_runlevel(int newlevel) tty_runlevel(runlevel); } +/** + * 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) +{ + if (runlevel == newlevel) + return; + + if (newlevel < 0 || newlevel > 9) + return; + + prevlevel = runlevel; + runlevel = newlevel; + + _d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel); + runlevel_set(prevlevel, newlevel); + + /* Make sure to (re)load all *.conf in /etc/finit.d/ */ + conf_reload_dynamic(); + + _d("Stopping services services not allowed in new runlevel ..."); + in_teardown = 1; + service_step_all(SVC_TYPE_ANY); + + /* Need to wait for any services to stop? If so, exit early + * and perform second stage from service_monitor later. */ + if (!service_stop_is_done()) + return; + + service_runlevel_finish(); +} + /** * service_register - Register service, task or run commands * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) @@ -666,7 +542,7 @@ int service_register(int type, char *line, time_t mtime, char *username) int forking = 0; #endif char *service = NULL, *proto = NULL, *ifaces = NULL; - char *cmd, *desc, *runlevels = NULL, *events = NULL; + char *cmd, *desc, *runlevels = NULL, *cond = NULL; svc_t *svc; plugin_t *plugin = NULL; @@ -691,8 +567,8 @@ int service_register(int type, char *line, time_t mtime, char *username) username = &cmd[1]; else if (cmd[0] == '[') /* [runlevels] */ runlevels = &cmd[0]; - else if (cmd[0] == '<') /* [!ev] */ - events = &cmd[1]; + else if (cmd[0] == '<') /* <[!][ev][,ev..]> */ + cond = &cmd[1]; else if (cmd[0] == ':') /* :ID */ id = atoi(&cmd[1]); #ifndef INETD_DISABLED @@ -800,7 +676,7 @@ int service_register(int type, char *line, time_t mtime, char *username) _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); if (type == SVC_TYPE_SERVICE) - conf_parse_events(svc, events); + conf_parse_cond(svc, cond); #ifndef INETD_DISABLED if (svc_is_inetd(svc)) { @@ -841,26 +717,36 @@ void service_unregister(svc_t *svc) svc_del(svc); } +/** + * service_teardown_finish - Complete runlevel change or dynamic reload + * + * If any runlevel change or dynamic service reload is in progress and + * all services that had to be stopped have been collected, run the + * corresponding second stage. + */ +static void service_teardown_finish(void) +{ + if (!(in_teardown || in_dyn_teardown)) + return; + + if (!service_stop_is_done()) + return; + + if (in_teardown) + service_runlevel_finish(); + + if (in_dyn_teardown) + service_reload_dynamic_finish(); +} + + 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; @@ -869,86 +755,192 @@ void service_monitor(pid_t lost) return; #endif - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (lost != svc->pid) - continue; + svc = svc_find_by_pid(lost); + if (!svc) { + _d("collected unknown PID %d", lost); + FLOG_WARN("collected unknown PID %d", lost); + return; + } - if (!prevlevel && svc_clean_bootstrap(svc)) - continue; + if (!prevlevel && svc_clean_bootstrap(svc)) + return; - if (SVC_TYPE_SERVICE != svc->type) { - svc->pid = 0; - continue; - } + _d("collected %s(%d)", svc->cmd, lost); - _d("Ouch, lost pid %d - %s(%d)", lost, basename(svc->cmd), svc->pid); + /* No longer running, update books. */ + svc->pid = 0; + service_step(svc); - /* No longer running, update books. */ - svc->pid = 0; + /* Check if we're still collecting stopped dynamic services */ + service_teardown_finish(); +} - /* Check if we're still collecting stopped dynamic services */ - if (service_stop_done(svc)) - break; +void service_step(svc_t *svc) +{ + /* These fields are marked as const in svc_t, only this + * function is allowed to modify them */ + svc_state_t *state = (svc_state_t *)&svc->state; + int *restart_counter = (int *)&svc->restart_counter; - if (sig_stopped()) { - _e("Stopped, not respawning killed processes."); - break; - } + svc_cmd_t enabled; + svc_state_t old_state; + cond_state_t cond; + char *old_status = NULL; + int err; - /* 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); +restart: + old_state = *state; + enabled = service_enabled(svc); + + if (debug) + old_status = strdup(svc_status(svc)); + + switch(*state) { + case SVC_HALTED_STATE: + *restart_counter = 0; + if (enabled) + *state = SVC_READY_STATE; + break; + + case SVC_DONE_STATE: + if (svc_is_changed(svc)) + *state = SVC_HALTED_STATE; + break; + + case SVC_STOPPING_STATE: + if (!svc->pid) + *state = SVC_HALTED_STATE; + break; + + case SVC_READY_STATE: + if (!enabled) { + *state = SVC_HALTED_STATE; + } else if (cond_get_agg(svc->cond) == COND_ON) { + if (*restart_counter >= RESPAWN_MAX) { + _e("%s keeps crashing, not restarting", + svc->desc ? : svc->cmd); + svc->block = SVC_BLOCK_CRASHING; + *state = SVC_HALTED_STATE; break; } - svc->restart_counter++; - service_start(svc); + err = service_start(svc); + if (err || !svc->pid) { + (*restart_counter)++; + break; + } + + svc->dirty = 0; + + switch (svc->type) { + case SVC_TYPE_SERVICE: + *state = SVC_RUNNING_STATE; + break; + case SVC_TYPE_INETD: + case SVC_TYPE_TASK: + *state = SVC_STOPPING_STATE; + break; + case SVC_TYPE_RUN: + *state = SVC_DONE_STATE; + break; + default: + _e("unknown service type %d", svc->type); + } + } + break; + + case SVC_RUNNING_STATE: + if (!enabled) { + service_stop(svc); + *state = SVC_STOPPING_STATE; + break; + } + + if (!svc->pid) { + (*restart_counter)++; + *state = SVC_READY_STATE; + break; + } + + cond = cond_get_agg(svc->cond); + + if (cond == COND_OFF || + (!svc->sighup && (cond < COND_ON || svc_is_changed(svc)))) { + service_stop(svc); + *state = SVC_READY_STATE; + break; + } + + if (cond == COND_FLUX) { + kill(svc->pid, SIGSTOP); + *state = SVC_WAITING_STATE; + break; + } + + if (svc_is_changed(svc)) { + if (svc->sighup) { + service_restart(svc); + } else { + service_stop(svc); + *state = SVC_READY_STATE; + } + svc->dirty = 0; } break; + + case SVC_WAITING_STATE: + if (!enabled) { + kill(svc->pid, SIGCONT); + service_stop(svc); + *state = SVC_HALTED_STATE; + break; + } + + if (!svc->pid) { + (*restart_counter)++; + *state = SVC_READY_STATE; + break; + } + + cond = cond_get_agg(svc->cond); + switch (cond) { + case COND_ON: + kill(svc->pid, SIGCONT); + *state = SVC_RUNNING_STATE; + break; + + case COND_OFF: + kill(svc->pid, SIGCONT); + service_stop(svc); + *state = SVC_READY_STATE; + break; + + case COND_FLUX: + break; + } + break; + } + + if (*state != old_state) { + if (debug) { + _d("%-20.20s %s -> %s", svc->cmd, + old_status, svc_status(svc)); + free(old_status); + } + goto restart; } } -static int is_norespawn(void) -{ - return sig_stopped() || - fexist("/mnt/norespawn") || - fexist("/tmp/norespawn"); -} - -static void restart_lost_procs(void) +void service_step_all(int types) { svc_t *svc; for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->pid > 0 && pid_alive(svc->pid)) + if (!(svc->type & types)) 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, SVC_HALTED_STATE); - else if (SVC_RELOAD == cmd) - service_reload(svc); - } else { - if (SVC_START == cmd || SVC_RELOAD == cmd) - service_start(svc); + service_step(svc); } } diff --git a/service.h b/service.h index cf684ce3..7e68f225 100644 --- a/service.h +++ b/service.h @@ -30,16 +30,13 @@ 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_enabled (svc_t *svc); -int service_start (svc_t *svc); -int service_stop (svc_t *svc, int state); -void service_start_dynamic (void); -void service_stop_dynamic (void); -int service_restart (svc_t *svc); -int service_reload (svc_t *svc); void service_reload_dynamic (void); +void service_step(svc_t *svc); +void service_step_all(int types); + #endif /* FINIT_SERVICE_H_ */ /** diff --git a/svc.c b/svc.c index 83bcc942..02a1ad0c 100644 --- a/svc.c +++ b/svc.c @@ -406,36 +406,32 @@ int svc_clean_bootstrap(svc_t *svc) char *svc_status(svc_t *svc) { - if (!svc_in_runlevel(svc, runlevel)) - return "halted"; - switch (svc->state) { + case SVC_HALTED_STATE: + switch (svc->block) { + case SVC_BLOCK_NONE: + return "halted"; + case SVC_BLOCK_MISSING: + return "missing"; + case SVC_BLOCK_CRASHING: + return "crashing"; + case SVC_BLOCK_USER: + return "blocked"; + } + case SVC_DONE_STATE: + return "done"; + case SVC_STOPPING_STATE: + return "stopping"; case SVC_WAITING_STATE: return "waiting"; - - case SVC_PAUSED_STATE: - return "stopped"; - - case SVC_CONDHALT_STATE: - return "nocond"; - - case SVC_RESTART_STATE: - return "restart"; - - case SVC_RELOAD_STATE: - return "reload"; - + case SVC_READY_STATE: + return "ready"; case SVC_RUNNING_STATE: - if (svc->pid) - return "running"; - /* Fall through */ + return "running"; - case SVC_HALTED_STATE: default: - break; + return "UNKNOWN"; } - - return "halted"; } /* Same base service, return unique ID */ diff --git a/svc.h b/svc.h index b15266f5..96c19358 100644 --- a/svc.h +++ b/svc.h @@ -39,23 +39,31 @@ typedef enum { } svc_cmd_t; typedef enum { - SVC_TYPE_FREE = 0, /* Free to allocate */ - SVC_TYPE_SERVICE, /* Monitored, will be respawned */ - SVC_TYPE_TASK, /* One-shot, runs in parallell */ - SVC_TYPE_RUN, /* Like task, but wait for completion */ - SVC_TYPE_INETD /* Classic inetd service */ + SVC_TYPE_FREE = 0, /* Free to allocate */ + SVC_TYPE_SERVICE = 1, /* Monitored, will be respawned */ + SVC_TYPE_TASK = 2, /* One-shot, runs in parallell */ + SVC_TYPE_RUN = 4, /* Like task, but wait for completion */ + SVC_TYPE_INETD = 8 /* Classic inetd service */ } svc_type_t; +#define SVC_TYPE_ANY (-1) + typedef enum { SVC_HALTED_STATE = 0, /* Not allowed in runlevel, or not enabled. */ - SVC_WAITING_STATE, /* Waiting for connection (inetd service) */ - SVC_PAUSED_STATE, /* Stopped/Paused by user started on reload */ - SVC_CONDHALT_STATE, /* Not allowed to run atm. event/state lost */ - SVC_RESTART_STATE, /* Restarting service waiting to be stopped */ - SVC_RELOAD_STATE, /* Reloading services, after .conf changed */ - SVC_RUNNING_STATE, /* Currently running service, see svc->pid */ + SVC_DONE_STATE, /* Task/Run job has been run */ + SVC_STOPPING_STATE, /* Waiting to collect the child process */ + SVC_WAITING_STATE, /* Condition is in flux, process SIGSTOPed */ + SVC_READY_STATE, /* Enabled but condition not satisfied */ + SVC_RUNNING_STATE, /* Process running */ } svc_state_t; +typedef enum { + SVC_BLOCK_NONE = 0, + SVC_BLOCK_MISSING, + SVC_BLOCK_CRASHING, + SVC_BLOCK_USER, +} svc_block_t; + #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ #define MAX_ARG_LEN 64 #define MAX_STR_LEN 64 @@ -73,17 +81,18 @@ typedef struct svc { /* Service details */ pid_t pid; - svc_state_t state; /* Paused, Reloading, Restart, Running, ... */ + const svc_state_t state; /* Paused, Reloading, Restart, Running, ... */ svc_type_t type; time_t mtime; /* Modification time for .conf from /etc/finit.d/ */ int dirty; /* Set if old mtime != new mtime => reloaded, * or -1 when marked for removal */ int runlevels; int sighup; /* This service supports SIGHUP :) */ - char events[MAX_ARG_LEN]; + svc_block_t block; /* Reason that this service is currently blocked */ + char cond[MAX_ARG_LEN]; /* Incremented for each restart by service monitor. */ - unsigned int restart_counter; + const unsigned int restart_counter; /* For inetd services */ inetd_t inetd; From aa1da0e9f28de7c388ac416f3c48f108c04f9742 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 5 Jan 2016 17:24:31 +0100 Subject: [PATCH 04/14] doc: Add documentation for conditions --- doc/conditions.md | 99 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 doc/conditions.md diff --git a/doc/conditions.md b/doc/conditions.md new file mode 100644 index 00000000..2f3d4e35 --- /dev/null +++ b/doc/conditions.md @@ -0,0 +1,99 @@ +# Finit Conditions + +In addition to runlevels, Finit supports user-defined conditions that +services can declare a dependency on. The conditions are specified in +angle brackets on the service stanza. Multiple conditions may be +specified, separated by commas. Conditions are AND-ed together during +evaluation, i.e. all conditions must be satisfied in order for the +service to be able to run. + +##### Example: + +``` +service [2345] /sbin/netd -- Network monitor +``` + +In this example the `netd` daemon will not be started until both +`svc/sbin/setupd` and `svc/sbin/zebra` are satisfied. + + +## Triggering + +Conditions are triggered by using the `emit` sub-command of the +`initctl` command. + + * To set a condition, use: `initctl emit +your/cond/here`. + * To clear a condition, use: `initctl emit -your/cond/here`. + +A condition will retain its current state until the next +reconfiguration or runlevel change. At that point, all set conditions +will transition into the `flux` state, meaning that the condition's +state is unknown. The rationale for this can be found in the +[Internals](#internals) section. Thus, after a reconfiguration, it is +up to the "owner" of the condition to convey the new (or possibly +unchanged) state of it. + + +## Built-in Conditions + +Finit is distributed with the `pidfile`-plugin. If enabled, it will +watch `/var/run/` for pidfiles created by services that is controls +and set a corresponding condition in the `svc/` namespace. + +Thus, if Finit starts the `/sbin/netd` daemon and it creates +`/var/run/netd.pid`, the condition `svc/sbin/netd` will be set. If the +file is removed, the condition will be cleared. + + +## Debugging + +If a service is not being started when it ought to be, the problem +might be that one of its conditions are not in the expected +state. This is indicated when running `initctl status` by the service +being in the `ready` state. + +In that situation, running `initctl cond show` will reveal which of +the conditions that are not in the `on` state. + +##### Example: + +``` +~ # initctl cond show +PID Service Status Condition (+ on, ~ flux, - off) +==================================================================================== +1419 /sbin/netd on <+svc/sbin/setupd,+svc/sbin/zebra> +0 /sbin/udhcpc off <-net/vlan1/exist> +``` + +Here we can see that `netd` is allowed to run since both of its +conditions are in the `on` state, as indicated by the +`+`-prefix. `udhcpc` is not allowed to run since `net/vlan1/exist` is +in the `off` state, indicated by the `-`-prefix. + + +## Internals + +A condition is always in one of three states: + + * `on`: The condition is asserted. + * `off`: The condition is deasserted. + * `flux`: The conditions state is unknown. + +All conditions that have not explicitly been set are interpreted as +being in the `off` state. + +When a reconfiguration is requested, Finit will transition all +conditions to the `flux` state. As a result, any service that depends +on the condition will be sent a SIGSTOP. Once the new state of the +condition becomes known the service will receive a SIGCONT. Then if +the condition is no longer satisfied the service will be stopped, +otherwise no action needs to be taken. + +This minimizes the number of services that have to be restarted +needlessly, just because the depending service was sent a SIGHUP for +example. + +Therefore, any plugin that supplies Finit with conditions must ensure +that their state is updated after each reconfiguration. This can be +done by binding to the `HOOK_SVC_RECONF`-hook. Look at +`plugins/pidfile.c` too see an example of this. From 5b28fed32180467da7d27f80c787f09062578176 Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Tue, 5 Jan 2016 21:11:27 +0100 Subject: [PATCH 05/14] Audit. Signed-off-by: Joachim Nilsson --- doc/conditions.md | 152 +++++++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 63 deletions(-) diff --git a/doc/conditions.md b/doc/conditions.md index 2f3d4e35..ec062cc0 100644 --- a/doc/conditions.md +++ b/doc/conditions.md @@ -1,99 +1,125 @@ -# Finit Conditions +Finit Conditions +================ -In addition to runlevels, Finit supports user-defined conditions that -services can declare a dependency on. The conditions are specified in -angle brackets on the service stanza. Multiple conditions may be -specified, separated by commas. Conditions are AND-ed together during -evaluation, i.e. all conditions must be satisfied in order for the -service to be able to run. +![Condition state machine](../images/cond-statem.jpg "Condition state machine") -##### Example: -``` -service [2345] /sbin/netd -- Network monitor +Table of Contents +----------------- + +* [Introduction](#introduction) +* [Triggering](#triggering) +* [Built-in Conditions](#built-in--conditions) +* [Debugging](#debugging) +* [Internals](#internals) + + +Introduction +------------ + +In addition to runlevels, services can declare user-defined conditions +as dependencies. Conditions are specified within angle brackets (<>) in +the service stanza. Multiple conditions may be specified separated by +comma. Conditions are AND'ed during evaluation, i.e. all conditions +must be satisfied in order for a service to run. + + +**Example:** + +```shell + service [2345] /sbin/netd -- Network monitor ``` -In this example the `netd` daemon will not be started until both -`svc/sbin/setupd` and `svc/sbin/zebra` are satisfied. +In this example the Network monitor daemon `netd` is not started until +both the `svc/sbin/setupd` *and* `svc/sbin/zebra` conditions are +satisfied. An `svc` condition is satisfied by the corresponding +service's pidfile being created. -## Triggering +Triggering +---------- -Conditions are triggered by using the `emit` sub-command of the -`initctl` command. +Conditions are triggered by using the `emit` command of the `initctl` +control tool. - * To set a condition, use: `initctl emit +your/cond/here`. - * To clear a condition, use: `initctl emit -your/cond/here`. +* `initctl emit +your/cond/here` -A condition will retain its current state until the next -reconfiguration or runlevel change. At that point, all set conditions -will transition into the `flux` state, meaning that the condition's -state is unknown. The rationale for this can be found in the -[Internals](#internals) section. Thus, after a reconfiguration, it is -up to the "owner" of the condition to convey the new (or possibly -unchanged) state of it. + To set a condition + +* `initctl emit -your/cond/here` + + To clear a condition + +Conditions retain their current state until the next reconfiguration or +runlevel change. At that point all set conditions transition into the +`flux` state, meaning the condition's state is unknown. (See +[Internals](#internals) for the rationale behind this.) Thus, after a +reconfiguration it is up to the "owner" of the condition to convey the +new (or possibly unchanged) state of it. -## Built-in Conditions +Built-in Conditions +------------------- Finit is distributed with the `pidfile`-plugin. If enabled, it will -watch `/var/run/` for pidfiles created by services that is controls +watch `/var/run/` for pidfiles created by services that it controls and set a corresponding condition in the `svc/` namespace. -Thus, if Finit starts the `/sbin/netd` daemon and it creates -`/var/run/netd.pid`, the condition `svc/sbin/netd` will be set. If the -file is removed, the condition will be cleared. +For example, if Finit starts the `/sbin/netd` daemon and it creates the +file `/var/run/netd.pid`, the condition `svc/sbin/netd` is satisfied. +If the file is removed, the condition is cleared. -## Debugging +Debugging +--------- -If a service is not being started when it ought to be, the problem -might be that one of its conditions are not in the expected -state. This is indicated when running `initctl status` by the service -being in the `ready` state. +If a service is not being started as it should, the problem might be +that one of its conditions are not in the expected state. Use the +command `initctl status` to inspect service status. Services in the +`ready` state are pending a condition. -In that situation, running `initctl cond show` will reveal which of -the conditions that are not in the `on` state. +In that situation, running `initctl cond show` reveals which of the +conditions that are not satisfied. Listed as `off` below. -##### Example: +**Example:** -``` -~ # initctl cond show -PID Service Status Condition (+ on, ~ flux, - off) -==================================================================================== -1419 /sbin/netd on <+svc/sbin/setupd,+svc/sbin/zebra> -0 /sbin/udhcpc off <-net/vlan1/exist> +```shell + ~ # initctl cond show + PID Service Status Condition (+ on, ~ flux, - off) + =============================================================================== + 1419 /sbin/netd on <+svc/sbin/setupd,+svc/sbin/zebra> + 0 /sbin/udhcpc off <-net/vlan1/exist> ``` -Here we can see that `netd` is allowed to run since both of its -conditions are in the `on` state, as indicated by the -`+`-prefix. `udhcpc` is not allowed to run since `net/vlan1/exist` is -in the `off` state, indicated by the `-`-prefix. +Here we can see that `netd` is allowed to run since both its conditions +are in the `on` state, as indicated by the `+`-prefix. `udhcpc` however +is not allowed to run since `net/vlan1/exist` condition is not satsifed. +As indicated by the `-`-prefix. -## Internals +Internals +--------- A condition is always in one of three states: - * `on`: The condition is asserted. - * `off`: The condition is deasserted. - * `flux`: The conditions state is unknown. +* `on`: The condition is asserted. +* `off`: The condition is deasserted. +* `flux`: The conditions state is unknown. All conditions that have not explicitly been set are interpreted as being in the `off` state. -When a reconfiguration is requested, Finit will transition all -conditions to the `flux` state. As a result, any service that depends -on the condition will be sent a SIGSTOP. Once the new state of the -condition becomes known the service will receive a SIGCONT. Then if -the condition is no longer satisfied the service will be stopped, -otherwise no action needs to be taken. +When a reconfiguration is requested, Finit transitions all conditions to +the `flux` state. As a result, services that depend on a condition are +sent `SIGSTOP`. Once the new state of the condition is asserted, the +service receives `SIGCONT`. If the condition is no longer satisfied the +service will then be stopped, otherwise no further action is taken. -This minimizes the number of services that have to be restarted -needlessly, just because the depending service was sent a SIGHUP for -example. +This STOP/CONT handling minimizes the number of unnecessary service +restarts that would otherwise occur because a depending service was sent +`SIGHUP` for example. Therefore, any plugin that supplies Finit with conditions must ensure -that their state is updated after each reconfiguration. This can be -done by binding to the `HOOK_SVC_RECONF`-hook. Look at -`plugins/pidfile.c` too see an example of this. +that their state is updated after each reconfiguration. This can be +done by binding to the `HOOK_SVC_RECONF` hook. For an example of how +to do this, see `plugins/pidfile.c`. From 7fe4c3355f6ee1fa7fd936b090895880559f16fc Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 7 Jan 2016 10:51:21 +0100 Subject: [PATCH 06/14] doc: Add service state machine documentation --- doc/service.md | 26 ++++++++++++++++++++++++++ doc/svc-machine.png | Bin 0 -> 16936 bytes 2 files changed, 26 insertions(+) create mode 100644 doc/service.md create mode 100644 doc/svc-machine.png diff --git a/doc/service.md b/doc/service.md new file mode 100644 index 00000000..ef84cdb3 --- /dev/null +++ b/doc/service.md @@ -0,0 +1,26 @@ +# Services + +## State Machine + +![A service's state machine](svc-machine.png "A service's state machine") + +A service is bound to a state machine that is in one of six (6) +states. For `run`s and `task`s there is an additional end state called +`DONE`, not shown in the diagram for brevity. All services start in +the `HALTED` state. + +The current state depends on the two following conditions: + +* `E`: Service enabled. In order for `E` to be satisfied, the service + must be allowed to run in the current runlevel and must not be + blocked. A service may be blocked for several reasons: + + - The user has manually stopped the service using `initctl stop + JOB`. + + - The binary repeatedly dies after being started. I.e. keeps + crashing. + + - The binary is missing in the filesystem. + +* `C`: Service conditions are satisfied. diff --git a/doc/svc-machine.png b/doc/svc-machine.png new file mode 100644 index 0000000000000000000000000000000000000000..a0903455f9ac9aabd58522013890a6b09fd0cc5a GIT binary patch literal 16936 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

+2`88$M`s*+%B#s`g7z9xr%NY1kEFCD<2GPYHuT ziPdzsLXcir2umS9aNpaLUiqDOT~hm=GLf2^S^$^%@FPx6{6)VzVXLX~8@|KqUxUxi zw=3H8z1a!*+dLjF+mXy!{Mdh|JIQ^Gsi=1v1P|GH`Ya&1eP&2HaK? zMG$At)1B}_<;;&Kl>orfeH)5$3WHMicz;5U%%i>erqGT+l%)8W$jC@-Y6%(&96q7B zqyd1qf&HI=74udxjrRo&1p)$sZiAzgZC@e=W8XXyj#@lPc>Ml~6cqK|C-B}aFB^Fu zkIA6_{F*6hVB7Z!wQmRs=RH0tFltxs@2CWq?MddK@dhXJ8p=9F(6IZv`bYo@VhH~Y z*8z^ZGd#iIt*MHN+Lb5&6~g_Flrkz2k&%Z3Pb-*2NVH1}GL>GTn%@e|EjFYB=|gz# zFQEtO75c(GdP;_d+1fG1{|(^%4(W&mfHdgP)%Eo!Gt|PKL4t03%@Z7Jo%}z!QKWSE zfVA^D4dbt}-n3rr2?m2-m1?Tm>BLeAFRiZTRk;&%rU;TtrA=}HV@tUH8B4^bTZF4r zp$~K&BV%Jj6&V6kQc~oXRZ;sKh$47PO#whj%=lNq?GOPM`NLlS*9Ja>j7kuux)$|9 zC3KpXFNI7yQ^G|j%gpGNGLD*`pcUrjts0Cl(qi4)8eCddMyO^v-^c?Py?C0TZBTVM zvO5=dc!%`d*aZ{7!Vrq5g!h2Z^+sEFK24i)JaPi=_q$3NLTGe!bipdAeD*P`t0q0J zYfX@kVKOujt?`4t0a*Zq4yCDQgENHO-*Q>Mq8S?|0%A+{dF5prKV$;)o(Rfxm1*R1 zF^|WB-<=PO<|nwly}b{&^|MFIaTttV>l$;B5k+y{1&bu*`hy8GoerLw3M928QO$(% zK1qUx-ae!zA=HKn7Ft*7Jzb-!6{OxiNl7Qs2O-MUewS_JUEf{MFG*f~E`v8~3WY9w zEB?f*VpkC@JtXjEom+J7WA?V=vnLNcVyXwxWY|*k;BVepm`8T|`kY5*Y2SO~7+$T- z54Alf{*TgZ87mxPe<5NCAk&?IE^)|{nqcVhC=t622dW3u^u{-Big0L)yVt9?_frN% zmPOmjokB6#J26pE8u}iN01+vx-5wt~g|y2v=k?FU4*z;QPC_5WuLnom``>sy0uT

wtsjsArH?#h~>yZ}jo1%ri%1IL`WJ4Ban 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 literal 0 HcmV?d00001 From 02eb68f46c1165c2d1d9042864af40796c1b950d Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 7 Jan 2016 10:51:40 +0100 Subject: [PATCH 07/14] Ensure that a service has stopped before taking any further action During documentation of the state machine, some theoretical problems where discovered that could have lead finit to spawn a new instance of a daemon before the previous one had been collected. Now a service will always go through the STOPPING state when leaving RUNNING. This ensures that the PID has been collected before any calls to service_start. ...documenting your work is, apparently, not a bad idea. :) --- service.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/service.c b/service.c index 6d9b38c9..af013b3a 100644 --- a/service.c +++ b/service.c @@ -272,8 +272,10 @@ static int service_start(svc_t *svc) } else { int result = 0; - if (SVC_TYPE_RUN == svc->type) + if (SVC_TYPE_RUN == svc->type) { result = WEXITSTATUS(complete(svc->cmd, pid)); + svc->pid = 0; + } if (verbose) print_result(result); @@ -808,8 +810,20 @@ restart: break; case SVC_STOPPING_STATE: - if (!svc->pid) - *state = SVC_HALTED_STATE; + if (!svc->pid) { + switch (svc->type) { + case SVC_TYPE_SERVICE: + case SVC_TYPE_INETD: + *state = SVC_HALTED_STATE; + break; + case SVC_TYPE_TASK: + case SVC_TYPE_RUN: + *state = SVC_DONE_STATE; + break; + default: + _e("unknown service type %d", svc->type); + } + } break; case SVC_READY_STATE: @@ -825,7 +839,7 @@ restart: } err = service_start(svc); - if (err || !svc->pid) { + if (err) { (*restart_counter)++; break; } @@ -838,10 +852,8 @@ restart: break; case SVC_TYPE_INETD: case SVC_TYPE_TASK: - *state = SVC_STOPPING_STATE; - break; case SVC_TYPE_RUN: - *state = SVC_DONE_STATE; + *state = SVC_STOPPING_STATE; break; default: _e("unknown service type %d", svc->type); @@ -858,6 +870,8 @@ restart: if (!svc->pid) { (*restart_counter)++; + /* TODO: There should be an async wait here + * before moving back to READY */ *state = SVC_READY_STATE; break; } @@ -867,7 +881,7 @@ restart: if (cond == COND_OFF || (!svc->sighup && (cond < COND_ON || svc_is_changed(svc)))) { service_stop(svc); - *state = SVC_READY_STATE; + *state = SVC_STOPPING_STATE; break; } @@ -882,7 +896,7 @@ restart: service_restart(svc); } else { service_stop(svc); - *state = SVC_READY_STATE; + *state = SVC_STOPPING_STATE; } svc->dirty = 0; } @@ -893,7 +907,7 @@ restart: if (!enabled) { kill(svc->pid, SIGCONT); service_stop(svc); - *state = SVC_HALTED_STATE; + *state = SVC_STOPPING_STATE; break; } @@ -913,7 +927,7 @@ restart: case COND_OFF: kill(svc->pid, SIGCONT); service_stop(svc); - *state = SVC_READY_STATE; + *state = SVC_STOPPING_STATE; break; case COND_FLUX: From 171d611b141d21134357ce8b017930a250c4d6a8 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 13 Jan 2016 15:09:04 +0100 Subject: [PATCH 08/14] Improve service state machine debug messages In addition to logging state transitions, also log the parameters that might trigger a transition. --- service.c | 12 ++++-------- svc.c | 12 ++++++++++++ svc.h | 1 + 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/service.c b/service.c index af013b3a..7c4dba90 100644 --- a/service.c +++ b/service.c @@ -787,15 +787,15 @@ void service_step(svc_t *svc) svc_cmd_t enabled; svc_state_t old_state; cond_state_t cond; - char *old_status = NULL; int err; restart: old_state = *state; enabled = service_enabled(svc); - if (debug) - old_status = strdup(svc_status(svc)); + _d("%20s(%4d): %8s %3sabled/%-7s cond:%-4s", svc->cmd, svc->pid, + svc_status(svc), enabled? "en" : "dis", svc_dirtystr(svc), + condstr(cond_get_agg(svc->cond))); switch(*state) { case SVC_HALTED_STATE: @@ -937,11 +937,7 @@ restart: } if (*state != old_state) { - if (debug) { - _d("%-20.20s %s -> %s", svc->cmd, - old_status, svc_status(svc)); - free(old_status); - } + _d("%20s(%4d): -> %8s", svc->cmd, svc->pid, svc_status(svc)); goto restart; } } diff --git a/svc.c b/svc.c index 02a1ad0c..bf33393d 100644 --- a/svc.c +++ b/svc.c @@ -434,6 +434,18 @@ char *svc_status(svc_t *svc) } } +const char *svc_dirtystr(svc_t *svc) +{ + if (svc_is_removed(svc)) + return "removed"; + else if (svc_is_updated(svc)) + return "updated"; + else if (svc_is_changed(svc)) + return "UNKNOWN"; + else + return "clean"; +} + /* Same base service, return unique ID */ int svc_next_id(char *cmd) { diff --git a/svc.h b/svc.h index 96c19358..be45c255 100644 --- a/svc.h +++ b/svc.h @@ -167,6 +167,7 @@ static inline int svc_is_dynamic(svc_t *svc) { return svc && 0 != svc->mtime; } static inline int svc_is_removed(svc_t *svc) { return svc && -1 == svc->dirty; } static inline int svc_is_changed(svc_t *svc) { return svc && 0 != svc->dirty; } static inline int svc_is_updated(svc_t *svc) { return svc && 1 == svc->dirty; } +const char *svc_dirtystr (svc_t *svc); 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; } From c641a1374eb960e2a0118c9782f98aed4a8ce564 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 13 Jan 2016 15:14:27 +0100 Subject: [PATCH 09/14] Do not ignore updated services when cleaning up removed ones When cleaning up removed services, the assumption was made that if a service was not 'removed', it was 'clean'. In reality, it could also be 'updated', which was then overwritten, causing services not being restarted. Don't do that. As a future precaution, make the dirty field const, only allowing updates from accessor methods. --- api.c | 2 +- service.c | 4 ++-- svc.c | 17 +++++++++++++---- svc.h | 4 +++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/api.c b/api.c index 1569a598..03ceec34 100644 --- a/api.c +++ b/api.c @@ -130,7 +130,7 @@ static int service_unblock(svc_t *svc) static int service_restart(svc_t *svc) { - svc->dirty = 1; + svc_mark_dirty(svc); service_step(svc); return 0; } diff --git a/service.c b/service.c index 7c4dba90..ebac78a9 100644 --- a/service.c +++ b/service.c @@ -844,7 +844,7 @@ restart: break; } - svc->dirty = 0; + svc_mark_clean(svc); switch (svc->type) { case SVC_TYPE_SERVICE: @@ -898,7 +898,7 @@ restart: service_stop(svc); *state = SVC_STOPPING_STATE; } - svc->dirty = 0; + svc_mark_clean(svc); } break; diff --git a/svc.c b/svc.c index bf33393d..42589985 100644 --- a/svc.c +++ b/svc.c @@ -352,17 +352,27 @@ void svc_mark_dynamic(void) svc_t *svc = svc_dynamic_iterator(1); while (svc) { - svc->dirty = -1; + *((int *)&svc->dirty) = -1; svc = svc_dynamic_iterator(0); } } +void svc_mark_dirty(svc_t *svc) +{ + *((int *)&svc->dirty) = 1; +} + +void svc_mark_clean(svc_t *svc) +{ + *((int *)&svc->dirty) = 0; +} + void svc_check_dirty(svc_t *svc, time_t mtime) { if (svc->mtime != mtime) - svc->dirty = 1; + svc_mark_dirty(svc); else - svc->dirty = 0; + svc_mark_clean(svc); svc->mtime = mtime; } @@ -381,7 +391,6 @@ void svc_clean_dynamic(void (*cb)(svc_t *)) if (svc->dirty == -1 && cb) cb(svc); - svc->dirty = 0; svc = svc_dynamic_iterator(0); } } diff --git a/svc.h b/svc.h index be45c255..b951abc2 100644 --- a/svc.h +++ b/svc.h @@ -84,7 +84,7 @@ typedef struct svc { const svc_state_t state; /* Paused, Reloading, Restart, Running, ... */ svc_type_t type; time_t mtime; /* Modification time for .conf from /etc/finit.d/ */ - int dirty; /* Set if old mtime != new mtime => reloaded, + const int dirty; /* Set if old mtime != new mtime => reloaded, * or -1 when marked for removal */ int runlevels; int sighup; /* This service supports SIGHUP :) */ @@ -153,6 +153,8 @@ void svc_foreach_dynamic (void (*cb)(svc_t *)); void svc_mark_dynamic (void); void svc_check_dirty (svc_t *svc, time_t mtime); +void svc_mark_dirty (svc_t *svc); +void svc_mark_clean (svc_t *svc); void svc_clean_dynamic (void (*cb)(svc_t *)); int svc_clean_bootstrap (svc_t *svc); From 0beb566e4574dfc7b9330cffa45a6d2b3ad3d46e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 13 Jan 2016 16:01:13 +0100 Subject: [PATCH 10/14] git: Ignore Emacs backup files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5688e29c..caae438e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*~ *.o *.d *.so From 6ece4b8fa391b40ee63e2a3d4f70526c60ef3c37 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 15 Jan 2016 10:07:15 +0100 Subject: [PATCH 11/14] inetd: Re-integrate the inetd subsystem into the new service model This patch introduces a new service type called SVC_TYPE_INETD_CONN, which represents a running inetd connection. These are handled in much the same way as tasks. I.e. they are considered one-time jobs, with the difference that they are removed upon completion. --- inetd.c | 221 ++++++++++++++++++++++++++++++++---------------------- inetd.h | 14 ++-- initctl.c | 7 +- service.c | 124 ++++++++++++++---------------- svc.c | 11 ++- svc.h | 18 +++-- 6 files changed, 219 insertions(+), 176 deletions(-) diff --git a/inetd.c b/inetd.c index a191710c..a48272da 100644 --- a/inetd.c +++ b/inetd.c @@ -43,75 +43,8 @@ #opt, inetd->name); \ } while (0); -/* Socket callback, looks up correct svc and starts it as an inetd service */ -static void socket_cb(uev_t *w, void *arg, int UNUSED(events)) -{ - svc_t *svc = (svc_t *)arg; - - if (SVC_START != service_enabled(svc, -1, NULL)) - return; - - if (!svc->inetd.forking) - uev_io_stop(w); - - service_start(svc); -} - -/* Launch Inet socket for service. - * TODO: Add filtering ALLOW/DENY per interface. - */ -static void spawn_socket(inetd_t *inetd) -{ - int sd; - socklen_t len = sizeof(struct sockaddr); - struct sockaddr_in s; - - if (!inetd->type) { - FLOG_ERROR("Skipping invalid inetd service %s", inetd->name); - return; - } - - _d("Spawning server socket for inetd %s ...", inetd->name); - sd = socket(AF_INET, inetd->type | SOCK_NONBLOCK | SOCK_CLOEXEC, inetd->proto); - if (-1 == sd) { - FLOG_PERROR("Failed opening inetd socket type %d proto %d", inetd->type, inetd->proto); - return; - } - - ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEADDR); -#ifdef SO_REUSEPORT - ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEPORT); -#endif - - memset(&s, 0, sizeof(s)); - s.sin_family = AF_INET; - s.sin_addr.s_addr = INADDR_ANY; - s.sin_port = htons(inetd->port); - if (bind(sd, (struct sockaddr *)&s, len) < 0) { - FLOG_PERROR("Failed binding to port %d, maybe another %s server is already running", - inetd->port, inetd->name); - close(sd); - return; - } - - if (inetd->port) { - if (inetd->type == SOCK_STREAM) { - if (-1 == listen(sd, 20)) { - FLOG_PERROR("Failed listening to inetd service %s", inetd->name); - close(sd); - return; - } - } else { /* SOCK_DGRAM */ - /* Set extra sockopt to get ifindex from inbound packets */ - ENABLE_SOCKOPT(sd, SOL_IP, IP_PKTINFO); - } - } - - uev_io_init(ctx, &inetd->watcher, socket_cb, inetd->arg, sd, UEV_READ); -} - /* Peek into SOCK_DGRAM socket to figure out where an inbound packet comes from. */ -int inetd_dgram_peek(int sd, char *ifname) +static int inetd_dgram_peek(int sd, char *ifname) { char cmbuf[0x100]; struct msghdr msgh; @@ -139,7 +72,7 @@ int inetd_dgram_peek(int sd, char *ifname) } /* Peek into SOCK_STREAM on accepted client socket to figure out inbound interface */ -int inetd_stream_peek(int sd, char *ifname) +static int inetd_stream_peek(int sd, char *ifname) { struct ifaddrs *ifaddr, *ifa; struct sockaddr_in sin; @@ -173,46 +106,146 @@ int inetd_stream_peek(int sd, char *ifname) return 0; } -/* Inetd monitor, called by service_monitor() */ -int inetd_respawn(pid_t pid) +static int get_stdin(svc_t *svc) { - svc_t *svc = svc_find_by_pid(pid); + char ifname[IF_NAMESIZE] = "UNKNOWN"; + int stdin = svc->inetd.watcher.fd; - if (svc_is_inetd(svc)) { - inetd_t *inetd = &svc->inetd; + if (svc->inetd.type == SOCK_STREAM) { + /* Open new client socket from server socket */ + stdin = accept(stdin, NULL, NULL); + if (stdin < 0) { + FLOG_PERROR("Failed accepting inetd service %d/tcp", svc->inetd.port); + return -1; + } - svc->pid = 0; + _d("New client socket %d accepted for inetd service %d/tcp", stdin, svc->inetd.port); - if (svc_in_runlevel(svc, runlevel) && !inetd->forking) - uev_io_set(&inetd->watcher, inetd->watcher.fd, UEV_READ); - - return 1; /* It was us! */ + inetd_stream_peek(stdin, ifname); + } else { /* SOCK_DGRAM */ + inetd_dgram_peek(stdin, ifname); } - return 0; /* Not an inetd service */ + 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(stdin); + + return -1; + } + + return stdin; } +/* Socket callback, looks up correct svc and starts it as an inetd service */ +static void socket_cb(uev_t *w, void *arg, int UNUSED(events)) +{ + svc_t *svc = (svc_t *)arg, *task; + int stdin; -void inetd_start(inetd_t *inetd) + stdin = get_stdin(svc); + if (stdin < 0) { + FLOG_ERROR("%s: Unable to accept incoming connection", + svc->cmd); + return; + } + + task = svc_new(svc->cmd, svc->inetd.next_id++, SVC_TYPE_INETD_CONN); + if (!task) { + FLOG_ERROR("%s: Unable to allocate service for inetd client", + svc->cmd); + return; + } + + /* Copy inherited attributes from inetd */ + task->runlevels = svc->runlevels; + task->inetd = svc->inetd; + memcpy(task->cond, svc->cond, sizeof(task->cond)); + memcpy(task->username, svc->username, sizeof(task->username)); + memcpy(task->group, svc->group, sizeof(task->group)); + memcpy(task->args, svc->args, sizeof(task->args)); + snprintf(task->desc, sizeof(task->desc), "%s Connection", svc->desc); + + task->stdin = stdin; + service_step(task); + + if (!svc->inetd.forking) { + svc->block = SVC_BLOCK_INETD_BUSY; + service_step(svc); + } +} + +/* Launch Inet socket for service. + * TODO: Add filtering ALLOW/DENY per interface. + */ +static int spawn_socket(inetd_t *inetd) +{ + int sd; + socklen_t len = sizeof(struct sockaddr); + struct sockaddr_in s; + + if (!inetd->type) { + FLOG_ERROR("Skipping invalid inetd service %s", inetd->name); + return -EINVAL; + } + + _d("Spawning server socket for inetd %s ...", inetd->name); + sd = socket(AF_INET, inetd->type | SOCK_NONBLOCK | SOCK_CLOEXEC, inetd->proto); + if (-1 == sd) { + FLOG_PERROR("Failed opening inetd socket type %d proto %d", inetd->type, inetd->proto); + return -errno; + } + + ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEADDR); +#ifdef SO_REUSEPORT + ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEPORT); +#endif + + memset(&s, 0, sizeof(s)); + s.sin_family = AF_INET; + s.sin_addr.s_addr = INADDR_ANY; + s.sin_port = htons(inetd->port); + if (bind(sd, (struct sockaddr *)&s, len) < 0) { + FLOG_PERROR("Failed binding to port %d, maybe another %s server is already running", + inetd->port, inetd->name); + close(sd); + return -errno; + } + + if (inetd->port) { + if (inetd->type == SOCK_STREAM) { + if (-1 == listen(sd, 20)) { + FLOG_PERROR("Failed listening to inetd service %s", inetd->name); + close(sd); + return -errno; + } + } else { /* SOCK_DGRAM */ + /* Set extra sockopt to get ifindex from inbound packets */ + ENABLE_SOCKOPT(sd, SOL_IP, IP_PKTINFO); + } + } + + uev_io_init(ctx, &inetd->watcher, socket_cb, inetd->svc, sd, UEV_READ); + return 0; +} + +int inetd_start(inetd_t *inetd) { if (inetd->watcher.fd == -1) - spawn_socket(inetd); + return spawn_socket(inetd); + + return -EEXIST; } void inetd_stop(inetd_t *inetd) { - svc_t *svc = (svc_t *)inetd->arg; - if (inetd->watcher.fd != -1) { uev_io_stop(&inetd->watcher); shutdown(inetd->watcher.fd, SHUT_RDWR); close(inetd->watcher.fd); inetd->watcher.fd = -1; } - - /* Stop any running service, not allowed anymore. */ - if (svc->pid) - service_stop(svc, SVC_WAITING_STATE); } static int getent(char *service, char *proto, struct servent **sv, struct protoent **pv) @@ -497,7 +530,7 @@ int inetd_filter_str(inetd_t *inetd, char *str, size_t len) * If equivalent service exists already service_register() will instead call * inetd_allow(). */ -int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forking, void *arg) +int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forking, svc_t *svc) { int result; struct servent *sv = NULL; @@ -515,6 +548,7 @@ int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forkin inetd->port = ntohs(sv->s_port); inetd->proto = pv->p_proto; inetd->forking = !!forking; + inetd->next_id = 2; if (!name) name = service; strlcpy(inetd->name, name, sizeof(inetd->name)); @@ -526,11 +560,18 @@ int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forkin else inetd->type = SOCK_DGRAM; + if (inetd->type == SOCK_DGRAM && inetd->forking) { + FLOG_WARN("%s: 'nowait' is not applicable on UDP services, ignoring", + svc->cmd); + + inetd->forking = 0; + } + /* Reset descriptor, used internally */ inetd->watcher.fd = -1; /* Setup socket callback argument */ - inetd->arg = arg; + inetd->svc = svc; _d("New service %s (default port %d proto %s:%d)", name, inetd->port, sv->s_proto, pv->p_proto); diff --git a/inetd.h b/inetd.h index 9cddd523..83073cfd 100644 --- a/inetd.h +++ b/inetd.h @@ -30,6 +30,7 @@ #include "queue.h" #include "libuev/uev.h" +typedef struct svc svc_t; typedef struct inetd_filter { TAILQ_ENTRY(inetd_filter) link; @@ -39,28 +40,25 @@ typedef struct inetd_filter { typedef struct { uev_t watcher; - void *arg; /* svc_t pointer for the socket callback */ + svc_t *svc; /* svc_t pointer for the socket callback */ int type; /* Socket type: SOCK_STREAM/SOCK_DGRAM */ int std; /* Standard proto/port from /etc/services */ int proto; int port; int forking; + int next_id; /* Next child job's id */ char name[10]; int (*cmd)(int type); /* internal inetd service, like 'time' */ TAILQ_HEAD(, inetd_filter) filters; } inetd_t; -int inetd_dgram_peek (int sd, char *ifname); -int inetd_stream_peek (int sd, char *ifname); - -void inetd_start (inetd_t *inetd); +int inetd_start (inetd_t *inetd); void inetd_stop (inetd_t *inetd); -int inetd_respawn (pid_t pid); - -int inetd_new (inetd_t *inetd, char *name, char *service, char *proto, int forking, void *arg); +int inetd_new (inetd_t *inetd, char *name, char *service, char *proto, + int forking, svc_t *svc); int inetd_del (inetd_t *inetd); int inetd_match (inetd_t *inetd, char *service, char *proto); diff --git a/initctl.c b/initctl.c index f03e020d..4e34734c 100644 --- a/initctl.c +++ b/initctl.c @@ -234,7 +234,12 @@ static int show_status(char *arg) else snprintf(jobid, sizeof(jobid), "%d:%d", svc->job, svc->id); - printf("%-5s %7s %-6d ", jobid, svc_status(svc), svc->pid); + printf("%-5s %7s ", jobid, svc_status(svc)); + if (svc_is_inetd(svc)) + printf("inetd "); + else + printf("%-6d ", svc->pid); + lvls = runlevel_string(svc->runlevels); if (strchr(lvls, '\e')) printf("%-18.18s ", lvls); diff --git a/service.c b/service.c index ebac78a9..bd4d808d 100644 --- a/service.c +++ b/service.c @@ -112,7 +112,7 @@ static int is_norespawn(void) */ static int service_start(svc_t *svc) { - int sd = 0; + int result = 0; pid_t pid; sigset_t nmask, omask; @@ -137,48 +137,20 @@ static int service_start(svc_t *svc) if (is_norespawn()) return 1; -#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 1; - } - - _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 1; - } - - FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->inetd.name, ifname); - } else -#endif if (verbose) { - if (svc_is_daemon(svc)) + if (svc_is_daemon(svc) || svc_is_inetd(svc)) print_desc("Starting ", svc->desc); else print_desc("", svc->desc); } + if (svc_is_inetd(svc)) { + result = inetd_start(&svc->inetd); + if (verbose) + print_result(result); + return result; + } + /* Block sigchild while forking. */ sigemptyset(&nmask); sigaddset(&nmask, SIGCHLD); @@ -219,11 +191,10 @@ static int service_start(svc_t *svc) 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); + /* Redirect inetd socket to stdin for connection */ + if (svc_is_inetd_conn(svc)) { + dup2(svc->stdin, STDIN_FILENO); + close(svc->stdin); dup2(STDIN_FILENO, STDOUT_FILENO); dup2(STDIN_FILENO, STDERR_FILENO); } else if (debug) { @@ -254,7 +225,7 @@ static int service_start(svc_t *svc) else status = execv(svc->cmd, args); /* XXX: Maybe use execve() to be able to launch scripts? */ - if (svc_is_inetd(svc)) { + if (svc_is_inetd_conn(svc)) { if (svc->inetd.type == SOCK_STREAM) { close(STDIN_FILENO); close(STDOUT_FILENO); @@ -266,21 +237,17 @@ static int service_start(svc_t *svc) } svc->pid = pid; - if (svc_is_inetd(svc)) { - if (svc->inetd.type == SOCK_STREAM) - close(sd); - } else { - int result = 0; + if (svc_is_inetd_conn(svc) && svc->inetd.type == SOCK_STREAM) + close(svc->stdin); - if (SVC_TYPE_RUN == svc->type) { - result = WEXITSTATUS(complete(svc->cmd, pid)); - svc->pid = 0; - } - - if (verbose) - print_result(result); + if (SVC_TYPE_RUN == svc->type) { + result = WEXITSTATUS(complete(svc->cmd, pid)); + svc->pid = 0; } + if (verbose) + print_result(result); + return 0; } @@ -298,6 +265,20 @@ static int service_stop(svc_t *svc) if (!svc) return 1; + if (svc_is_inetd(svc)) { + int do_print = runlevel != 1 && verbose && + svc->block != SVC_BLOCK_INETD_BUSY; + + if (do_print) + print_desc("Stopping ", svc->desc); + + inetd_stop(&svc->inetd); + + if (do_print) + print_result(0); + return 0; + } + if (svc->pid <= 1) { _d("Bad PID %d for %s, SIGTERM", svc->pid, svc->desc); return 1; @@ -371,12 +352,12 @@ static void service_reload_dynamic_finish(void) svc_clean_dynamic(service_unregister); _d("Starting services after reconf ..."); - service_step_all(SVC_TYPE_SERVICE); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); _d("Calling reconf hooks ..."); plugin_run_hooks(HOOK_SVC_RECONF); - service_step_all(SVC_TYPE_SERVICE); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); _d("Reconfiguration done"); } @@ -397,7 +378,7 @@ void service_reload_dynamic(void) _d("Stopping services services not allowed after reconf ..."); in_dyn_teardown = 1; cond_reload(); - service_step_all(SVC_TYPE_SERVICE); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); /* Need to wait for any services to stop? If so, exit early * and perform second stage from service_monitor later. */ @@ -684,7 +665,6 @@ int service_register(int type, char *line, time_t mtime, char *username) if (svc_is_inetd(svc)) { char *iface, *name = service; - svc->state = SVC_WAITING_STATE; if (svc->inetd.cmd && plugin) name = plugin->name; @@ -714,8 +694,6 @@ int service_register(int type, char *line, time_t mtime, char *username) void service_unregister(svc_t *svc) { - if (svc->state != SVC_HALTED_STATE) - _e("Failed stopping %s, removing anyway from list of monitored services.", svc->cmd); svc_del(svc); } @@ -752,11 +730,6 @@ void service_monitor(pid_t lost) if (tty_respawn(lost)) return; -#ifndef INETD_DISABLED - if (inetd_respawn(lost)) - return; -#endif - svc = svc_find_by_pid(lost); if (!svc) { _d("collected unknown PID %d", lost); @@ -805,6 +778,15 @@ restart: break; case SVC_DONE_STATE: + if (svc_is_inetd_conn(svc)) { + if (svc->inetd.svc->block == SVC_BLOCK_INETD_BUSY) { + svc->inetd.svc->block = 0; + service_step(svc->inetd.svc); + } + service_unregister(svc); + return; + } + if (svc_is_changed(svc)) *state = SVC_HALTED_STATE; break; @@ -816,6 +798,7 @@ restart: case SVC_TYPE_INETD: *state = SVC_HALTED_STATE; break; + case SVC_TYPE_INETD_CONN: case SVC_TYPE_TASK: case SVC_TYPE_RUN: *state = SVC_DONE_STATE; @@ -841,16 +824,19 @@ restart: err = service_start(svc); if (err) { (*restart_counter)++; - break; + + if (!svc_is_inetd_conn(svc)) + break; } svc_mark_clean(svc); switch (svc->type) { + case SVC_TYPE_INETD: case SVC_TYPE_SERVICE: *state = SVC_RUNNING_STATE; break; - case SVC_TYPE_INETD: + case SVC_TYPE_INETD_CONN: case SVC_TYPE_TASK: case SVC_TYPE_RUN: *state = SVC_STOPPING_STATE; @@ -868,7 +854,7 @@ restart: break; } - if (!svc->pid) { + if (!svc->pid && !svc_is_inetd(svc)) { (*restart_counter)++; /* TODO: There should be an async wait here * before moving back to READY */ diff --git a/svc.c b/svc.c index 42589985..3b7ed7d7 100644 --- a/svc.c +++ b/svc.c @@ -426,11 +426,20 @@ char *svc_status(svc_t *svc) return "crashing"; case SVC_BLOCK_USER: return "blocked"; + case SVC_BLOCK_INETD_BUSY: + return "busy"; } case SVC_DONE_STATE: return "done"; case SVC_STOPPING_STATE: - return "stopping"; + switch (svc->type) { + case SVC_TYPE_INETD_CONN: + case SVC_TYPE_RUN: + case SVC_TYPE_TASK: + return "active"; + default: + return "stopping"; + } case SVC_WAITING_STATE: return "waiting"; case SVC_READY_STATE: diff --git a/svc.h b/svc.h index b951abc2..678f60fd 100644 --- a/svc.h +++ b/svc.h @@ -39,11 +39,12 @@ typedef enum { } svc_cmd_t; typedef enum { - SVC_TYPE_FREE = 0, /* Free to allocate */ - SVC_TYPE_SERVICE = 1, /* Monitored, will be respawned */ - SVC_TYPE_TASK = 2, /* One-shot, runs in parallell */ - SVC_TYPE_RUN = 4, /* Like task, but wait for completion */ - SVC_TYPE_INETD = 8 /* Classic inetd service */ + SVC_TYPE_FREE = 0, /* Free to allocate */ + SVC_TYPE_SERVICE = 1, /* Monitored, will be respawned */ + SVC_TYPE_TASK = 2, /* One-shot, runs in parallell */ + SVC_TYPE_RUN = 4, /* Like task, but wait for completion */ + SVC_TYPE_INETD = 8, /* Classic inetd service */ + SVC_TYPE_INETD_CONN = 16, /* Single inetd connection */ } svc_type_t; #define SVC_TYPE_ANY (-1) @@ -62,6 +63,7 @@ typedef enum { SVC_BLOCK_MISSING, SVC_BLOCK_CRASHING, SVC_BLOCK_USER, + SVC_BLOCK_INETD_BUSY, } svc_block_t; #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ @@ -96,6 +98,7 @@ typedef struct svc { /* For inetd services */ inetd_t inetd; + int stdin; /* Identity */ char username[MAX_USER_LEN]; @@ -171,8 +174,9 @@ static inline int svc_is_changed(svc_t *svc) { return svc && 0 != svc->dirty; } static inline int svc_is_updated(svc_t *svc) { return svc && 1 == svc->dirty; } const char *svc_dirtystr (svc_t *svc); -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; } +static inline int svc_is_inetd (svc_t *svc) { return svc && SVC_TYPE_INETD == svc->type; } +static inline int svc_is_inetd_conn(svc_t *svc) { return svc && SVC_TYPE_INETD_CONN == svc->type; } +static inline int svc_is_daemon (svc_t *svc) { return svc && SVC_TYPE_SERVICE == svc->type; } #endif /* FINIT_SVC_H_ */ From 80273555c5be111cc687c34f8e266d4d589c566e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 28 Jan 2016 13:44:27 +0100 Subject: [PATCH 12/14] Do all service transitions in a helper function This eases development, since it makes it easy to set a breakpoint on a specific state transition. --- service.c | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/service.c b/service.c index bd4d808d..4165db1f 100644 --- a/service.c +++ b/service.c @@ -750,11 +750,17 @@ void service_monitor(pid_t lost) service_teardown_finish(); } +static void svc_set_state(svc_t *svc, svc_state_t new) +{ + svc_state_t *state = (svc_state_t *)&svc->state; + + *state = new; +} + void service_step(svc_t *svc) { /* These fields are marked as const in svc_t, only this * function is allowed to modify them */ - svc_state_t *state = (svc_state_t *)&svc->state; int *restart_counter = (int *)&svc->restart_counter; svc_cmd_t enabled; @@ -763,18 +769,18 @@ void service_step(svc_t *svc) int err; restart: - old_state = *state; + old_state = svc->state; enabled = service_enabled(svc); _d("%20s(%4d): %8s %3sabled/%-7s cond:%-4s", svc->cmd, svc->pid, svc_status(svc), enabled? "en" : "dis", svc_dirtystr(svc), condstr(cond_get_agg(svc->cond))); - switch(*state) { + switch(svc->state) { case SVC_HALTED_STATE: *restart_counter = 0; if (enabled) - *state = SVC_READY_STATE; + svc_set_state(svc, SVC_READY_STATE); break; case SVC_DONE_STATE: @@ -788,7 +794,7 @@ restart: } if (svc_is_changed(svc)) - *state = SVC_HALTED_STATE; + svc_set_state(svc, SVC_HALTED_STATE); break; case SVC_STOPPING_STATE: @@ -796,12 +802,12 @@ restart: switch (svc->type) { case SVC_TYPE_SERVICE: case SVC_TYPE_INETD: - *state = SVC_HALTED_STATE; + svc_set_state(svc, SVC_HALTED_STATE); break; case SVC_TYPE_INETD_CONN: case SVC_TYPE_TASK: case SVC_TYPE_RUN: - *state = SVC_DONE_STATE; + svc_set_state(svc, SVC_DONE_STATE); break; default: _e("unknown service type %d", svc->type); @@ -811,13 +817,13 @@ restart: case SVC_READY_STATE: if (!enabled) { - *state = SVC_HALTED_STATE; + svc_set_state(svc, SVC_HALTED_STATE); } else if (cond_get_agg(svc->cond) == COND_ON) { if (*restart_counter >= RESPAWN_MAX) { _e("%s keeps crashing, not restarting", svc->desc ? : svc->cmd); svc->block = SVC_BLOCK_CRASHING; - *state = SVC_HALTED_STATE; + svc_set_state(svc, SVC_HALTED_STATE); break; } @@ -834,12 +840,12 @@ restart: switch (svc->type) { case SVC_TYPE_INETD: case SVC_TYPE_SERVICE: - *state = SVC_RUNNING_STATE; + svc_set_state(svc, SVC_RUNNING_STATE); break; case SVC_TYPE_INETD_CONN: case SVC_TYPE_TASK: case SVC_TYPE_RUN: - *state = SVC_STOPPING_STATE; + svc_set_state(svc, SVC_STOPPING_STATE); break; default: _e("unknown service type %d", svc->type); @@ -850,7 +856,7 @@ restart: case SVC_RUNNING_STATE: if (!enabled) { service_stop(svc); - *state = SVC_STOPPING_STATE; + svc_set_state(svc, SVC_STOPPING_STATE); break; } @@ -858,7 +864,7 @@ restart: (*restart_counter)++; /* TODO: There should be an async wait here * before moving back to READY */ - *state = SVC_READY_STATE; + svc_set_state(svc, SVC_READY_STATE); break; } @@ -867,13 +873,13 @@ restart: if (cond == COND_OFF || (!svc->sighup && (cond < COND_ON || svc_is_changed(svc)))) { service_stop(svc); - *state = SVC_STOPPING_STATE; + svc_set_state(svc, SVC_STOPPING_STATE); break; } if (cond == COND_FLUX) { kill(svc->pid, SIGSTOP); - *state = SVC_WAITING_STATE; + svc_set_state(svc, SVC_WAITING_STATE); break; } @@ -882,7 +888,7 @@ restart: service_restart(svc); } else { service_stop(svc); - *state = SVC_STOPPING_STATE; + svc_set_state(svc, SVC_STOPPING_STATE); } svc_mark_clean(svc); } @@ -893,13 +899,13 @@ restart: if (!enabled) { kill(svc->pid, SIGCONT); service_stop(svc); - *state = SVC_STOPPING_STATE; + svc_set_state(svc, SVC_STOPPING_STATE); break; } if (!svc->pid) { (*restart_counter)++; - *state = SVC_READY_STATE; + svc_set_state(svc, SVC_READY_STATE); break; } @@ -907,13 +913,13 @@ restart: switch (cond) { case COND_ON: kill(svc->pid, SIGCONT); - *state = SVC_RUNNING_STATE; + svc_set_state(svc, SVC_RUNNING_STATE); break; case COND_OFF: kill(svc->pid, SIGCONT); service_stop(svc); - *state = SVC_STOPPING_STATE; + svc_set_state(svc, SVC_STOPPING_STATE); break; case COND_FLUX: @@ -922,7 +928,7 @@ restart: break; } - if (*state != old_state) { + if (svc->state != old_state) { _d("%20s(%4d): -> %8s", svc->cmd, svc->pid, svc_status(svc)); goto restart; } From e8d699c194ebc35086f39334706a583814712686 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 28 Jan 2016 14:52:49 +0100 Subject: [PATCH 13/14] Mark inetd services as clean on dynamic reload When reloading dynamic services, inetd services deleted marker was not being cleaned. This caused finit to stop and start all inetd services at every other reload. --- service.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/service.c b/service.c index 4165db1f..7ef4ddba 100644 --- a/service.c +++ b/service.c @@ -622,9 +622,6 @@ int service_register(int type, char *line, time_t mtime, char *username) } } - /* New, recently modified or unchanged ... used on reload. */ - svc_check_dirty(svc, mtime); - if (desc) strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); @@ -658,8 +655,7 @@ int service_register(int type, char *line, time_t mtime, char *username) svc->runlevels = conf_parse_runlevels(runlevels); _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); - if (type == SVC_TYPE_SERVICE) - conf_parse_cond(svc, cond); + conf_parse_cond(svc, cond); #ifndef INETD_DISABLED if (svc_is_inetd(svc)) { @@ -677,18 +673,20 @@ int service_register(int type, char *line, time_t mtime, char *username) inetd_setup: if (!ifaces) { _d("No specific iface listed for %s, allowing ANY.", service); - return inetd_allow(&svc->inetd, NULL); - } - - for (iface = strtok(ifaces, ","); iface; iface = strtok(NULL, ",")) { - if (iface[0] == '!') - inetd_deny(&svc->inetd, &iface[1]); - else - inetd_allow(&svc->inetd, iface); + inetd_allow(&svc->inetd, NULL); + } else { + for (iface = strtok(ifaces, ","); iface; iface = strtok(NULL, ",")) { + if (iface[0] == '!') + inetd_deny(&svc->inetd, &iface[1]); + else + inetd_allow(&svc->inetd, iface); + } } } #endif + /* New, recently modified or unchanged ... used on reload. */ + svc_check_dirty(svc, mtime); return 0; } From c6055031b87f9052af5bfff8a8defec6c26f0ebf Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 5 Feb 2016 13:45:06 +0100 Subject: [PATCH 14/14] Avoid unnecessary restarts of services that do not support SIGHUP When a service's condition transitions to `flux`, put it in the waiting state, even if it does not support SIGHUP. That way, if the condition returns to `on` we can simply SIGCONT it. If it goes to `off` it will still be stop/started as before. --- service.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/service.c b/service.c index 7ef4ddba..4454cfc3 100644 --- a/service.c +++ b/service.c @@ -867,30 +867,29 @@ restart: } cond = cond_get_agg(svc->cond); - - if (cond == COND_OFF || - (!svc->sighup && (cond < COND_ON || svc_is_changed(svc)))) { + switch (cond) { + case COND_OFF: service_stop(svc); svc_set_state(svc, SVC_STOPPING_STATE); break; - } - if (cond == COND_FLUX) { + case COND_FLUX: kill(svc->pid, SIGSTOP); svc_set_state(svc, SVC_WAITING_STATE); break; - } - if (svc_is_changed(svc)) { - if (svc->sighup) { - service_restart(svc); - } else { - service_stop(svc); - svc_set_state(svc, SVC_STOPPING_STATE); + case COND_ON: + if (svc_is_changed(svc)) { + if (svc->sighup) { + service_restart(svc); + } else { + service_stop(svc); + svc_set_state(svc, SVC_STOPPING_STATE); + } + svc_mark_clean(svc); } - svc_mark_clean(svc); + break; } - break; case SVC_WAITING_STATE: