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;