From 2232aea0f7ced1b45dddf37e33000ea002ac5c7b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 5 Jan 2016 13:39:27 +0100 Subject: [PATCH] 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;