diff --git a/Makefile b/Makefile index 95d38d0e..828724cc 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ EXEC = finit initctl reboot HEADERS = finit.h plugin.h svc.h inetd.h helpers.h queue.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 + svc.o service.o plugin.o tty.o inetd.o event.o TOPDIR = $(shell pwd) -include config.mk diff --git a/TODO.md b/TODO.md index ef8157d0..70ca76cd 100644 --- a/TODO.md +++ b/TODO.md @@ -18,16 +18,10 @@ very low dependency on external packages. General ------- +* Add compile-time support for running `/bin/sh` instead of `getty` * Add `!` or `[wait ]` to service directive to let finit pause and wait for a pidfile before starting processes -- both at boot and when starting processes in a runlevel. -* Add `` events to service stanza so services - can be reloaded/restarted/stopped -- e.g, new GW --> SIGHUP process, - or loss of `IF:eth1` --> stop service, start it when eth1 comes up - again. -* Add support for listening to netlink messages for GW, ADDR, IF UP/DOWN -* Add `` to let Finit know that a service does not support reload - (`SIGHUP`), but must be restarted (stop+start) * Allow custom reload for processes like libreSwan's pluto service [2345] /sbin/pluto @@ -38,7 +32,6 @@ General `.monitor=` in `svc_t` ... the script named as the basename of the service it monitors + `.sh`. * Add support for `/etc/sysctl.d` -* Add compile-time supoprt for running `/bin/sh` instead of `getty` * Implement `initctl stop|start|restart|reload|status ` and `service stop|start|restart|reload|status` on top * Add PRE and POST hooks for when switching between runlevels diff --git a/conf.c b/conf.c index 3468dc70..5084c2b9 100644 --- a/conf.c +++ b/conf.c @@ -113,6 +113,38 @@ int conf_parse_runlevels(char *runlevels) return bitmask; } +void conf_parse_events(svc_t *svc, char *events) +{ + size_t i = 0; + char *ptr = events; + + if (!events) + return; + + if (!svc) { + _e("Invalid service pointer"); + return; + } + + /* First character must be '!' if SIGHUP is not supported. */ + svc->sighup = 1; + if (ptr[i] == '!') { + svc->sighup = 0; + ptr++; + } + + while (ptr[i] != '>' && ptr[i] != 0) + i++; + ptr[i] = 0; + + if (i >= sizeof(svc->events)) { + FLOG_WARN("Too long event list in declaration of %s: %s", svc->cmd, ptr); + return; + } + + strlcpy(svc->events, ptr, sizeof(svc->events)); +} + static void parse_static(char *line) { char *x; diff --git a/conf.h b/conf.h index d958a68d..d96ae133 100644 --- a/conf.h +++ b/conf.h @@ -24,8 +24,11 @@ #ifndef FINIT_CONF_H_ #define FINIT_CONF_H_ +#include "svc.h" + void conf_parse_cmdline (void); int conf_parse_runlevels (char *runlevels); +void conf_parse_events (svc_t *svc, char *events); int conf_parse_config (void); void conf_reload_dynamic (void); diff --git a/event.c b/event.c new file mode 100644 index 00000000..6b2c526f --- /dev/null +++ b/event.c @@ -0,0 +1,235 @@ +/* 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 validate_events(char *events) +{ + return !events || !events[0]; +} + +int event_service_cond(char *events) +{ + int cond = 1; + char *msg; + char temp[MAX_ARG_LEN]; + + /* No required events, condition satisfied */ + if (validate_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 || validate_events(svc->events)) + continue; + + if (change == 1) { + if (!service_enabled(svc, 1, NULL)) + continue; + + _d("Got event %s, (re)starting %s ...", 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); + } + } +} + + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/event.h b/event.h new file mode 100644 index 00000000..129a22be --- /dev/null +++ b/event.h @@ -0,0 +1,40 @@ +/* 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/plugins/netlink.c b/plugins/netlink.c index 60d8e035..14f5c9ba 100644 --- a/plugins/netlink.c +++ b/plugins/netlink.c @@ -18,7 +18,6 @@ #include #include /* IFNAMSIZ */ -//#include #include #include #include @@ -26,6 +25,7 @@ #include #include "../finit.h" +#include "../event.h" #include "../helpers.h" #include "../plugin.h" @@ -63,18 +63,18 @@ static void nl_route(struct nlmsghdr *nlmsg) switch (a->rta_type) { case RTA_GATEWAY: gw = *((int *)data); - _d("GW: 0x%04x", gw); + //_d("GW: 0x%04x", gw); break; case RTA_DST: dst = *((int *)data); mask = r->rtm_dst_len; - _d("MASK: 0x%04x", mask); + //_d("MASK: 0x%04x", mask); break; case RTA_OIF: idx = *((int *)data); - _d("IDX: 0x%04x", idx); + //_d("IDX: 0x%04x", idx); break; } @@ -82,10 +82,13 @@ static void nl_route(struct nlmsghdr *nlmsg) } if ((!dst && !mask) && (gw || idx)) { + char msg[MAX_ARG_LEN]; + if (nlmsg->nlmsg_type == RTM_DELROUTE) - _d("Default gateway 0x%4x removed", gw); + snprintf(msg, sizeof(msg), "GW:DN"); else - _d("New default gateway: 0x%4x", gw); + snprintf(msg, sizeof(msg), "GW:UP"); + event_dispatch(msg); } } @@ -107,23 +110,28 @@ static void nl_link(struct nlmsghdr *nlmsg) while (RTA_OK(a, la)) { if (a->rta_type == IFLA_IFNAME) { + char msg[MAX_ARG_LEN]; + strncpy(ifname, RTA_DATA(a), sizeof(ifname)); switch (nlmsg->nlmsg_type) { case RTM_NEWLINK: - /* New interface has appearad, or interface flags has changed. + /* + * New interface has appearad, or interface flags has changed. * Check ifi_flags here to see if the interface is UP/DOWN */ - _d("New link (%s), UP: %d", ifname, i->ifi_flags & IFF_UP); - if (i->ifi_change & IFF_UP) { - int msg = (i->ifi_flags & IFF_UP) ? 1 : 0; - - _d("%s: %s", ifname, msg ? "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); break; case RTM_DELLINK: /* NOTE: Interface has dissapeared, not link down ... */ - _d("%s: DOWN", ifname); + snprintf(msg, sizeof(msg), "IFDEL:%s", ifname); + event_dispatch(msg); break; case RTM_NEWADDR: diff --git a/service.c b/service.c index 89c389f1..9e06c808 100644 --- a/service.c +++ b/service.c @@ -31,6 +31,7 @@ #include "finit.h" #include "conf.h" +#include "event.h" #include "helpers.h" #include "private.h" #include "sig.h" @@ -94,6 +95,12 @@ svc_cmd_t service_enabled(svc_t *svc, int event, void *arg) if (!svc_in_runlevel(svc, runlevel)) return SVC_STOP; + /* + * Event conditions for services are ignored during bootstrap. + */ + if (runlevel && !event_service_cond(svc->events)) + return SVC_STOP; + /* Is there a service plugin registered? */ if (svc->cb) { int status; @@ -373,7 +380,11 @@ void service_stop_dynamic(void) plugin_run_hooks(HOOK_SVC_RECONF); /* Reconfigure HW/VLANs/etc here */ } -/* stop + start */ +/* stop + start + * + * XXX: This should be refactored into a background job AND marked such + * so that the service monitor doesn't attempt to restart it! + */ int service_restart(svc_t *svc) { int result = 0; @@ -537,7 +548,7 @@ void service_runlevel(int newlevel) * The @line can optionally start with a username, denoted by an @ * character. Like this: * - * service @username [!0-6,S] /path/to/daemon arg -- Description + * service @username [!0-6,S] /path/to/daemon arg -- Description * task @username [!0-6,S] /path/to/task arg -- Description * run @username [!0-6,S] /path/to/cmd arg -- Description * inetd tcp/ssh nowait [2345] @root:root /sbin/sshd -i -- Description @@ -550,6 +561,18 @@ void service_runlevel(int newlevel) * command is listed in more than the [S] runlevel they will be called * when changing runlevel. * + * Services (daemons, not inetd services) also support an optional + * argument. This is for services that, e.g., require a system gateway + * or interface to be up before they are started. Or restarted, or even + * SIGHUP'ed, when the gateway changes or interfaces come and go. The + * special case when a service is declared with means it does not + * support SIGHUP but must be STOP/START'ed at system reconfiguration. + * + * Supported service events are: GW, IFUP[:ifname], IFDN[:ifname], where + * the interface name (:ifname) is optional. Actully, the check with a + * service event declaration is string based, so 'IFUP:ppp' will match + * any of "IFUP:ppp0" or "IFUP:pppoe1" sent by the netlink.so plugin. + * * For multiple instances of the same command, e.g. multiple DHCP * clients, the user must enter an ID, using the :ID syntax. * @@ -570,7 +593,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; + char *cmd, *desc, *runlevels = NULL, *events = NULL; svc_t *svc; plugin_t *plugin = NULL; @@ -603,6 +626,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] == ':') /* :ID */ id = atoi(&cmd[1]); else @@ -701,6 +726,9 @@ int service_register(int type, char *line, time_t mtime, char *username) svc->runlevels = conf_parse_runlevels(runlevels); _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); + if (type == SVC_TYPE_SERVICE) + conf_parse_events(svc, events); + #ifndef INETD_DISABLED if (svc_is_inetd(svc)) { char *iface, *name = service; diff --git a/svc.h b/svc.h index d3198c35..ba9433e7 100644 --- a/svc.h +++ b/svc.h @@ -68,6 +68,8 @@ typedef struct svc { 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]; /* Incremented for each restart by service monitor. */ unsigned int restart_counter;