From 0e02429e75b90412d2dfa270c06cab86384d27aa Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Tue, 25 Sep 2012 14:23:21 +0200 Subject: [PATCH] Convert finit to use libev and fix so plugin ctors runs on dlopen(). Lots of cleanup and simplification of the main code in this commit. Most notably libev has been added to take care of the main loop and monitor services. Still TODO is migrating to use the libev signal handling. Convert initctl.c:listen_initctl() to a plugin instead. This also meant introducing a new form of I/O plugin to finit, also handled by libev. Load plugins *after* we've setup the base filesystem (BASEFS) in case plugins reside on a filesystem not reachable before "mount -a" has run. This also has the side effect of lining up nicely with the first level plugin hooks. If a plugin wants to hook up with a loded service we connect them by their respective numeric IDs in plugin_register(). I.e., the service monitor checks if a service has a registered callback, which in turn decides if a service should be started, stopped or reloaded. Rip out old EeePC distribution defines from finit.h, none of it really applies anymore. If you need distribution specific settings this is the place to put them. Make hookpoint names (#defines) clearer: after BASEFS, after networking, after all setup, etc. Use #inlude_next in signal.h to include system header file! Simplify service monitor: move essential code to svc.c and rip out the TIPC dependency. Write your own service.so plugin, or wait for a more complete example of how to extend service monitoring using the primitives in svc.c Signed-off-by: Joachim Nilsson --- .gitignore | 4 + Makefile | 16 +- common.mk | 1 - finit.c | 38 +-- finit.h | 40 +-- helpers.c | 1 + ipc.c | 275 -------------------- plugin.c | 77 +++++- plugin.h | 81 ++++-- plugins/Makefile | 22 +- plugins/alsa-utils.c | 8 +- plugins/bootmisc.c | 14 +- plugins/dbus.c | 9 +- plugins/hwclock.c | 9 +- initctl.c => plugins/initctl.c | 99 +++---- plugins/resolvconf.c | 9 +- plugins/urandom.c | 9 +- plugins/x11-common.c | 12 +- ipc.h => private.h | 37 +-- service.c | 460 --------------------------------- service.h | 92 ------- signal.c | 19 +- signal.h | 2 + svc.c | 274 +++++++++++++++++++- svc.h | 85 +++++- 25 files changed, 620 insertions(+), 1073 deletions(-) delete mode 100644 ipc.c rename initctl.c => plugins/initctl.c (56%) rename ipc.h => private.h (57%) delete mode 100644 service.c delete mode 100644 service.h diff --git a/.gitignore b/.gitignore index 4ff8cfe8..c9dce24f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ *.o +.*.d +*.so *.gz +*.map +finit diff --git a/Makefile b/Makefile index f196547a..744a79c4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # finit - The Improved fast init # -# Copyright (c) 2008 Claudio Matsuoka +# Copyright (c) 2008-2010 Claudio Matsuoka +# Copyright (c) 2008-2012 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 @@ -30,19 +31,18 @@ PLUGIN_DIR ?= /lib/finit/plugins VERSION = 1.0-pre PKG = finit-$(VERSION) EXEC = finit -OBJS = finit.o helpers.o initctl.o signal.o svc.o ipc.o service.o \ - strlcpy.o plugin.o +OBJS = finit.o helpers.o signal.o svc.o plugin.o +OBJS += strlcpy.o SRCS = $(OBJS:.o=.c) DEPS = $(addprefix .,$(SRCS:.c=.d)) CFLAGS += -W -Wall -Werror -Os # Disable annoying gcc warning for "warn_unused_result", see GIT 37af997 CPPFLAGS += -U_FORTIFY_SOURCE -CPPFLAGS += -I$(ROOTDIR)/$(CONFIG_LINUXDIR)/include/ CPPFLAGS += -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_GNU_SOURCE CPPFLAGS += -DVERSION=\"$(VERSION)\" -DWHOAMI=\"`whoami`@`hostname`\" CPPFLAGS += -DPLUGIN_PATH=\"$(PLUGIN_DIR)\" LDFLAGS += -rdynamic -LDLIBS += -ldl +LDLIBS += -ldl -lev prefix ?= /usr/local sysconfdir ?= /etc @@ -50,7 +50,7 @@ datadir = $(prefix)/share/doc/finit mandir = $(prefix)/share/man/man8 include common.mk -export PLUGIN_DIR ROOTDIR +export PLUGIN_DIR ROOTDIR CPPFLAGS all: Makefile $(EXEC) $(MAKE) -C plugins $@ @@ -69,12 +69,14 @@ install: all printf " INSTALL $(DESTDIR)$(prefix)/sbin/$$file\n"; \ install -m 0755 $$file $(DESTDIR)$(prefix)/sbin/$$file; \ done + $(MAKE) -C plugins $@ uninstall: -@for file in $(EXEC); do \ printf " REMOVE $(DESTDIR)$(prefix)/sbin/$$file\n"; \ - $(RM) $(DESTDIR)$(prefix)/sbin/$$file; \ + rm $(DESTDIR)$(prefix)/sbin/$$file 2>/dev/null; \ done + $(MAKE) -C plugins $@ clean: -@$(RM) $(OBJS) $(DEPS) $(EXEC) diff --git a/common.mk b/common.mk index 3701a49c..d9b4a3d7 100644 --- a/common.mk +++ b/common.mk @@ -1,6 +1,5 @@ # Some junk files we always want to be removed when doing a make clean. JUNK = *~ *.bak *.aux *.dvi *.idx *.ind *.log *.ps *.map .*.d DEADJOE semantic.cache *.gdb *.elf core core.* -RM := -@$(RM) MAKE := @$(MAKE) MAKEFLAGS = --no-print-directory diff --git a/finit.c b/finit.c index 5057b92a..10b9f886 100644 --- a/finit.c +++ b/finit.c @@ -30,10 +30,15 @@ #include "finit.h" #include "helpers.h" +#include "private.h" #include "plugin.h" -#include "service.h" +#include "svc.h" #include "signal.h" +/* Match one command. */ +#define MATCH_CMD(l, c, x) \ + (!strncmp(l, c, strlen(c)) && (x = (l) + strlen(c))) + int debug = 0; int verbose = 1; char *sdown = NULL; @@ -157,7 +162,7 @@ static void parse_finit_conf(char *file) continue; } if (MATCH_CMD(line, "service ", x)) { - if (service_register (x)) + if (svc_register(x)) _e("Failed, too many services to monitor.\n"); continue; } @@ -168,6 +173,8 @@ static void parse_finit_conf(char *file) int main(int UNUSED(args), char *argv[]) { + struct ev_loop *loop = ev_default_loop(0); + /* * Initial setup of signals, ignore all until we're up. */ @@ -204,9 +211,6 @@ int main(int UNUSED(args), char *argv[]) cls(); echo("finit " VERSION " (built " __DATE__ " " __TIME__ " by " WHOAMI ")"); - _d("Loading plugins ..."); - load_plugins(PLUGIN_PATH); - /* * Parse configuration file */ @@ -237,9 +241,13 @@ int main(int UNUSED(args), char *argv[]) sig_setup(); /* - * Most user-level hooks run here, unless they are service hooks + * Load plugins and run first level hooks. */ - run_hooks(HOOK_POST_SIGSETUP); + _d("Loading plugins ..."); + load_plugins(loop, PLUGIN_PATH); + + _d("Running first level hooks ..."); + run_hooks(HOOK_BASEFS_UP); /* * Network stuff @@ -259,21 +267,17 @@ int main(int UNUSED(args), char *argv[]) /* * Hooks that rely on loopback, or basic networking being up. */ - run_hooks(HOOK_POST_NETWORK); + run_hooks(HOOK_NETWORK_UP); /* * Start service monitor framework */ - _d("Starting services from %s", FINIT_CONF); - service_startup(); + _d("Starting all static services from %s", FINIT_CONF); + svc_start_all(); /* Run startup scripts in /etc/finit.d/, if any. */ run_parts(FINIT_RCSD); -#ifdef LISTEN_INITCTL - listen_initctl(); -#endif - if (!fork()) { /* child process */ int i; @@ -341,12 +345,12 @@ int main(int UNUSED(args), char *argv[]) /* * Hooks that should run at the very end */ - run_hooks(HOOK_PRE_RUNLOOP); + run_hooks(HOOK_SYSTEM_UP); /* - * Enter main service monitor loop -- restarts services that dies. + * Enter main loop to monior /dev/initctl and services */ - service_monitor(); + ev_run(loop, 0); return 0; } diff --git a/finit.h b/finit.h index 537ef316..e7e87520 100644 --- a/finit.h +++ b/finit.h @@ -30,9 +30,6 @@ #include #include #include -#include -#include -#include #include #define DO_LOG(level, fmt, args...) \ @@ -56,35 +53,7 @@ # define MDEV "/sbin/mdev" # define GETTY "/sbin/getty -L 115200 ttyS0 vt100" # define LISTEN_INITCTL -#elif defined DIST_MDV /* Mandriva */ -# define RANDOMSEED "/var/lib/random-seed" -# define SYSROOT "/sysroot" -# define CONSOLE "/dev/console" -# define GETTY "/usr/bin/openvt /sbin/mingetty tty2" -# define RUNPARTS "/usr/bin/run-parts" -# define REMOUNT_ROOTFS_RW -# define PAM_CONSOLE -# define LISTEN_INITCTL -# define RUNLEVEL 5 -# define USE_MESSAGE_BUS -#elif defined DIST_EEEXUBUNTU /* eeeXubuntu */ -# define RANDOMSEED "/var/lib/urandom/random-seed" -# define SYSROOT "/sysroot" -# define CONSOLE "/dev/console" -# define GETTY "/usr/bin/openvt /sbin/getty 38400 tty2" -# define RUNPARTS "/bin/run-parts" -# define REMOUNT_ROOTFS_RW -# define TOUCH_ETC_NETWORK_RUN_IFSTATE -# define LISTEN_INITCTL -#elif defined DIST_ORIGEEE /* original EeePC distribution */ -# define RANDOMSEED "/var/lib/urandom/random-seed" -# define SYSROOT "/mnt" -# define CONSOLE "/dev/console" -# define GETTY "/usr/bin/openvt /sbin/getty 38400 tty2" -# define RUNPARTS "/bin/run-parts" -# define TOUCH_ETC_NETWORK_RUN_IFSTATE -# define USE_ETC_RESOLVCONF_RUN -#else /* Ubuntu */ +#else /* Debian/Ubuntu based distributions */ # define RANDOMSEED "/var/lib/urandom/random-seed" # define CONSOLE "/dev/tty1" # define GETTY "/sbin/getty -8 38400 tty1" @@ -125,10 +94,6 @@ #define UNUSED(x) UNUSED_ ## x __attribute__ ((unused)) #endif -/* Match one command. */ -#define MATCH_CMD(l, c, x) \ - (!strncmp(l, c, strlen(c)) && (x = (l) + strlen(c))) - #define echo(fmt, args...) do { if (1) { fprintf(stderr, fmt "\n", ##args); } } while (0) #define _d(fmt, args...) do { if (debug) { fprintf(stderr, "finit:%s() - " fmt "\n", __func__, ##args); } } while (0) #define _e(fmt, args...) do { fprintf(stderr, "finit:%s() - " fmt "\n", __func__, ##args); } while (0) @@ -142,9 +107,6 @@ extern char *startx; extern char *hostname; extern char *username; -/* initctl.c */ -void listen_initctl (void); - /* strlcpy.c */ size_t strlcpy(char *dst, const char *src, size_t siz); diff --git a/helpers.c b/helpers.c index 846e5151..7047a155 100644 --- a/helpers.c +++ b/helpers.c @@ -46,6 +46,7 @@ #include "finit.h" #include "helpers.h" +#include "private.h" #include "signal.h" #define NUM_ARGS 16 diff --git a/ipc.c b/ipc.c deleted file mode 100644 index 13c58f55..00000000 --- a/ipc.c +++ /dev/null @@ -1,275 +0,0 @@ -/* Generic IPC class based on TIPC as message bus - * - * Copyright (c) 2008-2012 Joachim Nilsson - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include -#include -#include -//#include -#include -#include -#include -#include - -#include "finit.h" -#include "ipc.h" - -#define IPC_MAP_SOCKADDR(a,t,p) \ - { \ - (a).family = AF_TIPC; \ - (a).addrtype = TIPC_ADDR_MCAST; \ - (a).scope = TIPC_CLUSTER_SCOPE; \ - (a).addr.nameseq.type = (t); \ - (a).addr.nameseq.lower = (p); \ - (a).addr.nameseq.upper = (p); \ - } - -/** - * ipc_init - Create a TIPC socket for receiving or sending events. - * @type: The msg bus. - * @port: The msg port. - * @conn_type: SERVER or CLIENT connection. - * - * Returns: - * The resulting socket on success, -1 on error. - */ -int ipc_init(int type, int port, int conn_type) -{ - int sd = -1; - int restart_count = 0; - - restart: - while (-1 == sd) { - struct sockaddr_tipc tipc_addr; - - if (restart_count++ > 5) { - _e("Failed too many times, aborting."); - restart_count = 0; - - return -1; - } - - sd = socket(AF_TIPC, SOCK_RDM, 0); - if (-1 == sd) { - _pe("Failed creating TIPC socket"); - sleep(1); - goto restart; - } - - /* In case the user wants a server socket. */ - IPC_MAP_SOCKADDR(tipc_addr, type, port); - /* bind to the socket if it will work as a server part */ - if (conn_type) { - if (bind - (sd, (struct sockaddr *)&tipc_addr, - sizeof(tipc_addr))) { - _pe("TIPC Port {%u,%u,%u} could not be created", - tipc_addr.addr.nameseq.type, - tipc_addr.addr.nameseq.lower, - tipc_addr.addr.nameseq.upper); - close(sd); - return -1; - } - } - - /* Clear counter */ - restart_count = 0; - } - - /* OK, give caller bound socket */ - return sd; -} - -/** - * ipc_send_event - Send a TIPC event. - * @sd: TIPC descriptor - * @event: Data buffer to send. - * @sz: Length of @event data. - * @bus: ID of tipc bus - * - * Returns: - * Posix OK(0) on OK or 1 on error. - */ -int ipc_send_event(int sd, void *event, size_t sz, int bus) -{ - int retries = 0; - struct sockaddr_tipc tipc_addr; - - if (!event) { - errno = EINVAL; - return 1; - } - - memset(&tipc_addr, 0, sizeof(tipc_addr)); - tipc_addr.family = AF_TIPC; - tipc_addr.addrtype = TIPC_ADDR_MCAST; - tipc_addr.addr.nameseq.type = SINGLE_TIPC_NODE; - tipc_addr.addr.nameseq.lower = bus; - tipc_addr.addr.nameseq.upper = bus; - - while (-1 == sendto(sd, (char *)event, sz, 0, (struct sockaddr *)&tipc_addr, (socklen_t) sizeof(tipc_addr))) { - switch (errno) { - case EINTR: - /* Signal received, resume syscall. */ - continue; - - case ENOTSOCK: - case EBADF: - if (retries++ > 1) { - _e("Too many errors while trying to send."); - return 1; - } - continue; - - default: - return 1; - } - } - - return 0; -} - -/** - * ipc_receive_event - Poll or wait for a TIPC event. - * @sd: TIPC descriptor. - * @event: Return buffer for the returned link event. - * @sz: Size of return buffer. - * @timeout: Set to 0 to block, or >0 to have a timeout in milliseconds. - * - * Returns: - * Zero (0) on OK or 1 on error. - */ -int ipc_receive_event(int sd, void *event, size_t sz, int timeout) -{ - struct sockaddr_tipc tipc_addr; - socklen_t alen = sizeof(struct sockaddr_tipc); - - if (!event) { - errno = EINVAL; - return 1; - } - - /* The poll() syscall handles timeout==0 differently */ - while (timeout > 0) { - int ret = 0; - struct pollfd fd = { sd, POLLIN | POLLPRI, 0 }; - - ret = poll(&fd, 1, timeout); - if (0 == ret) { - /* Timeout */ - errno = ETIMEDOUT; - - return 1; - } - if (-1 == ret) { - if (EINTR == errno) { - /* Unlike the below call to recvfrom() we return here, - * because a signal received here could be intended. This - * only affects those tasks calling us with a timeout > 0 - * and they usually know about this, and exploit it. - * E.g. by sending kill() to the pthread in charge of link - * propagation within a process. One such user is - * RSTP. --Jocke 2007-10-15 */ - _d("poll() received signal..."); - errno = EINTR; - return 1; - } - - _pe("poll() failed."); - return 1; - } - - /* Check returned events on our TIPC socket. */ - if (fd.revents & (POLLIN | POLLPRI)) { - /* OK reply, activity on sd */ - break; - } - - /* No input, only error. */ - return 1; - } - - while (-1 == recvfrom(sd, event, sz, 0, (struct sockaddr *)&tipc_addr, &alen)) { - switch (errno) { - case EINTR: - /* Signal received, resume syscall. */ - continue; - - default: - _pe("Failed reading TIPC event"); - return 1; - } - } - - return 0; -} - -/** - * ipc_wait_event - "Robust" IPC receive, waits with timeout and signal resistance before giving up. - * @sd: TIPC descriptor. - * @event: Event type to wait for. - * @timeout: Set to 0 to block, or >0 to have a timeout in seconds. - * - * Returns: - * Zero (0) on OK or 1 on timeout. - */ -int ipc_wait_event(int sd, int event, int timeout) -{ - generic_event_t ev; - struct timeval start, now, diff; - - /* Poll for activation acknowledgement */ - gettimeofday(&start, NULL); - do { - if (ipc_receive_event(sd, &ev, sizeof(ev), timeout * 1000)) { - /* All syscalls may be interrupted by signal, in which case - * errno is set to EINTR */ - if (errno == EINTR) { - _d("GOT SIGNAL"); - continue; - } - if (errno == ETIMEDOUT) { - _d("timeout"); - return 1; - } - - _d("Failed with error %d", errno); - } - - _d("Received event 0x%lx, expected 0x%x", ev.mtype, event); - - gettimeofday(&now, NULL); - timersub(&now, &start, &diff); - if (diff.tv_sec >= timeout) - return 1; - } while (ev.mtype != event); - - return 0; -} - -/** - * Local Variables: - * version-control: t - * indent-tabs-mode: t - * c-file-style: "linux" - * End: - */ diff --git a/plugin.c b/plugin.c index d1760ee9..9e130799 100644 --- a/plugin.c +++ b/plugin.c @@ -24,11 +24,14 @@ #include #include /* dlopen() et al */ #include /* readdir() et al */ -#include #include #include /* BSD sys/queue.h API */ +#include "finit.h" +#include "private.h" +#include "helpers.h" #include "plugin.h" +#include "svc.h" static LIST_HEAD(, plugin) plugins = LIST_HEAD_INITIALIZER(); @@ -45,8 +48,20 @@ int plugin_register(plugin_t *plugin) if (!dladdr(plugin, &info) || !info.dli_fname) plugin->name = "unknown"; else - plugin->name = info.dli_fname; + plugin->name = (char *)info.dli_fname; } + + if (plugin->svc.cb) { + svc_t *svc = svc_find_by_name(plugin->name); + + if (!svc) { + _e("No service \"%s\" loaded, skipping plugin.", basename(plugin->name)); + return 1; + } + plugin->svc.id = svc_id(svc); + svc->plugin = &plugin->svc; + } + LIST_INSERT_HEAD(&plugins, plugin, link); return 0; @@ -59,13 +74,14 @@ int plugin_unregister(plugin_t *plugin) return 0; } +/* Private daemon API *******************************************************/ void run_hooks(hook_point_t no) { plugin_t *p; PLUGIN_ITERATOR(p) { if (p->hook[no].cb) { - printf("Calling %s hook n:o %d from runloop...\n", p->name, no); + _d("Calling %s hook n:o %d from runloop...", basename(p->name), no); p->hook[no].cb(p->hook[no].arg); } } @@ -77,20 +93,57 @@ void run_services(void) PLUGIN_ITERATOR(p) { if (p->svc.cb) { - printf("Calling svc %s from runloop...\n", p->name); - p->svc.cb(p->svc.arg); + _d("Calling svc %s from runloop...", basename(p->name)); + p->svc.cb(p->svc.arg, 0); } } } -int load_plugins(char *path) +/* Generic libev I/O callback, looks up correct plugin and calls its callback */ +static void generic_io_cb(struct ev_loop *loop, ev_io *w, int revents) +{ + plugin_t *p; + + ev_io_stop(loop, w); + + /* Find matching plugin, pick first matching fd */ + PLUGIN_ITERATOR(p) { + if (p->io.cb && p->io.fd == w->fd) { + p->io.cb(p->io.arg, w->fd, revents); + break; + } + } + + ev_io_start(loop, w); +} + +/* Setup any I/O callbacks for plugins that use them */ +static void init_plugins(struct ev_loop *loop) +{ + plugin_t *p; + + PLUGIN_ITERATOR(p) { + if (p->io.cb && p->io.fd >= 0) { + ev_io *w = calloc(1, sizeof(ev_io)); + + _d("Initializing plugin %s for I/O", basename(p->name)); + if (!w) { + _e("Failed setting up plugin %s: Out of memory", basename(p->name)); + continue; + } + ev_io_init(w, generic_io_cb, p->io.fd, p->io.flags); + ev_io_start(loop, w); + } + } +} + +int load_plugins(struct ev_loop *loop, char *path) { DIR *dp = opendir(path); struct dirent *entry; if (!dp) { - fprintf(stderr, "Failed, cannot open plugin directory %s: %s\n", - path, strerror(errno)); + _e("Failed, cannot open plugin directory %s: %s", path, strerror(errno)); return 1; } @@ -102,17 +155,19 @@ int load_plugins(char *path) char filename[1024]; snprintf(filename, sizeof(filename), "%s/%s", path, entry->d_name); - printf("Loading plugin %s ...\n", filename); + print_descr(" Loading plugin ", basename(filename)); handle = dlopen(filename, RTLD_LAZY); if (!handle) { - fprintf(stderr, - "Failed loading plugin %s: %s\n", filename, dlerror()); + _d("Failed loading plugin %s: %s", filename, dlerror()); + print_result(1); return 1; } + print_result(0); } } closedir(dp); + init_plugins(loop); return 0; } diff --git a/plugin.h b/plugin.h index 411ae2f3..7cb0534c 100644 --- a/plugin.h +++ b/plugin.h @@ -21,13 +21,17 @@ * THE SOFTWARE. */ -#ifndef PLUGIN_H_ -#define PLUGIN_H_ +#ifndef FINIT_PLUGIN_H_ +#define FINIT_PLUGIN_H_ +#include #include /* BSD sys/queue.h API */ -#define PLUGIN_INIT(x) void x(void) __attribute__ ((constructor)); -#define PLUGIN_EXIT(x) void x(void) __attribute__ ((destructor)); +#define PLUGIN_IO_READ EV_READ +#define PLUGIN_IO_WRITE EV_READ + +#define PLUGIN_INIT(x) static void __attribute__ ((constructor)) x(void) +#define PLUGIN_EXIT(x) static void __attribute__ ((destructor)) x(void) #define PLUGIN_ITERATOR(x) LIST_FOREACH(x, &plugins, link) @@ -35,45 +39,78 @@ * Predefined hook points for easier plugin debugging */ typedef enum { - HOOK_POST_LOAD = 0, - HOOK_PRE_SETUP, - HOOK_POST_SIGSETUP, - HOOK_POST_NETWORK, - HOOK_PRE_RUNLOOP, + HOOK_BASEFS_UP = 0, + HOOK_NETWORK_UP, + HOOK_SYSTEM_UP, HOOK_SHUTDOWN, HOOK_MAX_NUM } hook_point_t; +/** + * plugin_t - Finit &plugin_t object + * @link: BSD sys/queue.h linked list node + * @name: Plugin name, or identifier to match against a &svc_t object + * @svc: Service callback for a loaded &svc_t object + * @hook: Hook callback definitions + * @io: I/O hook callback + * + * To setup an &svc_t object callback for a service monitor the @name + * must match the @svc_t @cmd exactly for them to "pair". + * + * A &plugin_t object may contain a service callback, several hooks + * and/or an I/O callback as well. This way all critical extensions + * to finit can fit in one single plugin, if needed. + * + * The "dynamic events" discussed in the svc callback is for external + * service plugins to implement. However, it can be anything that + * can cause a service to need to SIGHUP at runtime. E.g., acquiring + * a DHCP lease, or an interface going UP/DOWN. + * + * It is up to the external service plugin to track these events and + * relay them to each @dynamic service plugins' callback. I.e., to + * all those with the dynamic flag set. + */ typedef struct plugin { /* BSD sys/queue.h linked list node. */ LIST_ENTRY(plugin) link; - /* Optional plugin name, defaults to plugin path if unset. */ - const char *name; + /* Plugin name, defaults to plugin path if unset. + * NOTE: Must be same as @cmd for service plugins! */ + char *name; /* Service callback to be called once per lap of runloop. */ - struct { - void *arg; /* Optional argument to callback func. */ - void (*cb) (void *); + struct plugin_svc { + /* Private */ + int id; /* Service ID# to match this service plugin against, set on installation. */ + /* Public */ + void *arg; /* Optional argument to callback func, set by plugin. */ + int dynamic; /* Reload (SIGHUP) on dynamic event? Set by plugin. */ + int private; /* For callbacks to use freely, possibly to store "states", set by plugin. */ + int (*cb)(void *arg, int event); } svc; /* List of hook callbacks. */ struct { - void *arg; /* Optional argument to callback func. */ - void (*cb) (void *); + void *arg; /* Optional argument to callback func. */ + void (*cb)(void *arg); } hook[HOOK_MAX_NUM]; + + /* I/O Plugin */ + struct { + int fd, flags; /* 1:READ, 2:WRITE */ + void *arg; + void (*cb)(void *arg, int fd, int events); + } io; } plugin_t; +/* Used by svc.h */ +typedef struct plugin_svc plugin_svc_t; + /* Public plugin API */ int plugin_register (plugin_t *plugin); int plugin_unregister (plugin_t *plugin); -/* Private daemon API */ -void run_hooks (hook_point_t no); -void run_services (void); -int load_plugins (char *path); - -#endif /* PLUGIN_H_ */ +#endif /* FINIT_PLUGIN_H_ */ /** * Local Variables: diff --git a/plugins/Makefile b/plugins/Makefile index 627867ad..cf03d2ef 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -1,9 +1,9 @@ # Plugin based services architecture for finit -*-Makefile-*- -CFLAGS = -fPIC -g -Os -I.. -CPPFLAGS = -W -Wall +CFLAGS = -fPIC -g -Os +CPPFLAGS += -I.. LDFLAGS = -shared -PLUGINS = alsa-utils.so bootmisc.so dbus.so hwclock.so resolvconf.so \ - urandom.so x11-common.so +PLUGINS = initctl.so alsa-utils.so bootmisc.so dbus.so hwclock.so \ + resolvconf.so urandom.so x11-common.so DEPS = $(addprefix .,$(PLUGINS:.so=.d)) include ../common.mk @@ -11,22 +11,24 @@ include ../common.mk all: $(PLUGINS) clean: - $(RM) $(PLUGINS) *.o + -@$(RM) $(PLUGINS) *.o distclean: clean - $(RM) $(JUNK) $(DEPS) + -@$(RM) $(JUNK) $(DEPS) install: all + @install -d $(DESTDIR)$(prefix)$(PLUGIN_DIR) @for plugin in $(PLUGINS); do \ - echo " INSTALL $(PLUGIN_DIR)/$$plugin"; \ - cp $$plugin $(PLUGIN_DIR); \ + echo " INSTALL $(DESTDIR)$(prefix)$(PLUGIN_DIR)/$$plugin"; \ + cp $$plugin $(DESTDIR)$(prefix)$(PLUGIN_DIR); \ done uninstall: - @for plugin in $(PLUGINS); do \ + -@for plugin in $(PLUGINS); do \ echo " RM $(PLUGIN_DIR)/$$plugin"; \ - rm $(PLUGIN_DIR)/$$plugin; \ + rm $(DESTDIR)$(prefix)$(PLUGIN_DIR)/$$plugin 2>/dev/null; \ done + -@rmdir $(DESTDIR)$(prefix)$(PLUGIN_DIR) 2>/dev/null ifneq ($(MAKECMDGOALS),clean) -include $(DEPS) diff --git a/plugins/alsa-utils.c b/plugins/alsa-utils.c index ff6dc2e0..cf43e263 100644 --- a/plugins/alsa-utils.c +++ b/plugins/alsa-utils.c @@ -38,7 +38,7 @@ static void restore(void *UNUSED(arg)) } static plugin_t plugin = { - .hook[HOOK_POST_SIGSETUP] = { + .hook[HOOK_BASEFS_UP] = { .cb = restore }, .hook[HOOK_SHUTDOWN] = { @@ -46,18 +46,16 @@ static plugin_t plugin = { } }; -static void init_plugin(void) +PLUGIN_INIT(plugin_init) { plugin_register(&plugin); } -static void exit_plugin(void) +PLUGIN_EXIT(plugin_exit) { plugin_unregister(&plugin); } -PLUGIN_INIT(init_plugin) -PLUGIN_EXIT(exit_plugin) /** * Local Variables: diff --git a/plugins/bootmisc.c b/plugins/bootmisc.c index e066d306..3d21f9c9 100644 --- a/plugins/bootmisc.c +++ b/plugins/bootmisc.c @@ -53,27 +53,29 @@ static void setup(void *UNUSED(arg)) #else remove("/etc/network/run/ifstate"); #endif + + _d("Setting up misc files ..."); + mkdir("/var/run/sshd", 01755); /* OpenSSH */ + mkfifo("/dev/xconsole", 0640); /* sysklogd */ + chown("/dev/xconsole", 0, getgroup("tty")); } static plugin_t plugin = { - .hook[HOOK_POST_SIGSETUP] = { + .hook[HOOK_BASEFS_UP] = { .cb = setup }, }; -static void init_plugin(void) +PLUGIN_INIT(plugin_init) { plugin_register(&plugin); } -static void exit_plugin(void) +PLUGIN_EXIT(plugin_exit) { plugin_unregister(&plugin); } -PLUGIN_INIT(init_plugin) -PLUGIN_EXIT(exit_plugin) - /** * Local Variables: * version-control: t diff --git a/plugins/dbus.c b/plugins/dbus.c index 5ef7b9f3..bc4f9b9a 100644 --- a/plugins/dbus.c +++ b/plugins/dbus.c @@ -38,24 +38,21 @@ static void setup(void *UNUSED(arg)) } static plugin_t plugin = { - .hook[HOOK_POST_NETWORK] = { + .hook[HOOK_NETWORK_UP] = { .cb = setup }, }; -static void init_plugin(void) +PLUGIN_INIT(plugin_init) { plugin_register(&plugin); } -static void exit_plugin(void) +PLUGIN_EXIT(plugin_exit) { plugin_unregister(&plugin); } -PLUGIN_INIT(init_plugin) -PLUGIN_EXIT(exit_plugin) - /** * Local Variables: * version-control: t diff --git a/plugins/hwclock.c b/plugins/hwclock.c index 35c9d002..b5ff0315 100644 --- a/plugins/hwclock.c +++ b/plugins/hwclock.c @@ -38,7 +38,7 @@ static void restore(void *UNUSED(arg)) } static plugin_t plugin = { - .hook[HOOK_POST_SIGSETUP] = { + .hook[HOOK_BASEFS_UP] = { .cb = restore }, .hook[HOOK_SHUTDOWN] = { @@ -46,19 +46,16 @@ static plugin_t plugin = { } }; -static void init_plugin(void) +PLUGIN_INIT(plugin_init) { plugin_register(&plugin); } -static void exit_plugin(void) +PLUGIN_EXIT(plugin_exit) { plugin_unregister(&plugin); } -PLUGIN_INIT(init_plugin) -PLUGIN_EXIT(exit_plugin) - /** * Local Variables: * version-control: t diff --git a/initctl.c b/plugins/initctl.c similarity index 56% rename from initctl.c rename to plugins/initctl.c index 608281da..62db6189 100644 --- a/initctl.c +++ b/plugins/initctl.c @@ -22,17 +22,12 @@ * THE SOFTWARE. */ -#include -#include -#include -#include /* According to POSIX.1-2001 */ -#include -#include -#include -#include +#include +#include #include "finit.h" -#include "signal.h" /* do_shutdown() */ +#include "plugin.h" +#include "signal.h" #define INIT_MAGIC 0x03091969 #define INIT_CMD_RUNLVL 1 @@ -49,53 +44,69 @@ struct init_request { /* Standard reboot/shutdown utilities talk to init using /dev/initctl. * We should check if the fifo was recreated and reopen it. */ -void listen_initctl(void) +static void read_initctl(void *UNUSED(arg), int fd, int UNUSED(events)) { - _d("Forking %s watcher...", FINIT_FIFO); - if (!fork()) { - int ctl; - fd_set fds; - struct init_request request; + struct init_request request; - _d("Setting up %s", FINIT_FIFO); - mkfifo(FINIT_FIFO, 0600); - ctl = open(FINIT_FIFO, O_RDONLY); + _d("Receiving request on %s...", FINIT_FIFO); + while (1) { + ssize_t len = read(fd, &request, sizeof(request)); - while (1) { - FD_ZERO(&fds); - FD_SET(ctl, &fds); - _d("Pending %s activity...", FINIT_FIFO); - if (select(ctl + 1, &fds, NULL, NULL, NULL) <= 0) - continue; + if (-1 == len) { + _d("Error %d", errno); + break; /* Probably EAGAIN */ + } - _d("Receiving request on %s...", FINIT_FIFO); - read(ctl, &request, sizeof(request)); + if (request.magic != INIT_MAGIC) + continue; - if (request.magic != INIT_MAGIC) - continue; + _d("Magic OK..."); + if (request.cmd == INIT_CMD_RUNLVL) { + switch (request.runlevel) { + case '0': + _d("Halting system (SIGUSR2)"); + do_shutdown(SIGUSR2); + break; - _d("Magic OK..."); - if (request.cmd == INIT_CMD_RUNLVL) { - switch (request.runlevel) { - case '0': - _d("Halting system (SIGUSR2)"); - do_shutdown(SIGUSR2); - break; + case '6': + _d("Rebooting system (SIGUSR1)"); + do_shutdown(SIGUSR1); + break; - case '6': - _d("Rebooting system (SIGUSR1)"); - do_shutdown(SIGUSR1); - break; - - default: - _d("Unsupported runlevel: %d", request.runlevel); - break; - } + default: + _d("Unsupported runlevel: %d", request.runlevel); + break; } } } } +static plugin_t plugin = { + .io = { + .cb = read_initctl, + .flags = PLUGIN_IO_READ, + }, +}; + +PLUGIN_INIT(plugin_init) +{ + _d("Setting up %s", FINIT_FIFO); + mkfifo(FINIT_FIFO, 0600); + + plugin.io.fd = open(FINIT_FIFO, O_RDONLY | O_NONBLOCK); + if (-1 == plugin.io.fd) { + _e("Failed opening %s FIFO, error %d: %s", FINIT_FIFO, errno, strerror(errno)); + return; + } + + plugin_register(&plugin); +} + +PLUGIN_EXIT(plugin_exit) +{ + plugin_unregister(&plugin); +} + #endif /* LISTEN_INITCTL */ /** diff --git a/plugins/resolvconf.c b/plugins/resolvconf.c index 9f89cdfe..a079410e 100644 --- a/plugins/resolvconf.c +++ b/plugins/resolvconf.c @@ -65,24 +65,21 @@ static void setup(void *UNUSED(arg)) } static plugin_t plugin = { - .hook[HOOK_POST_SIGSETUP] = { + .hook[HOOK_BASEFS_UP] = { .cb = setup }, }; -static void init_plugin(void) +PLUGIN_INIT(plugin_init) { plugin_register(&plugin); } -static void exit_plugin(void) +PLUGIN_EXIT(plugin_exit) { plugin_unregister(&plugin); } -PLUGIN_INIT(init_plugin) -PLUGIN_EXIT(exit_plugin) - /** * Local Variables: * version-control: t diff --git a/plugins/urandom.c b/plugins/urandom.c index e237e0b9..c4dacf69 100644 --- a/plugins/urandom.c +++ b/plugins/urandom.c @@ -41,24 +41,21 @@ static void setup(void *UNUSED(arg)) } static plugin_t plugin = { - .hook[HOOK_POST_SIGSETUP] = { + .hook[HOOK_BASEFS_UP] = { .cb = setup }, }; -static void init_plugin(void) +PLUGIN_INIT(plugin_init) { plugin_register(&plugin); } -static void exit_plugin(void) +PLUGIN_EXIT(plugin_exit) { plugin_unregister(&plugin); } -PLUGIN_INIT(init_plugin) -PLUGIN_EXIT(exit_plugin) - /** * Local Variables: * version-control: t diff --git a/plugins/x11-common.c b/plugins/x11-common.c index 0ae0b116..b775887b 100644 --- a/plugins/x11-common.c +++ b/plugins/x11-common.c @@ -50,30 +50,24 @@ static void setup(void *UNUSED(arg)) mkdir("/tmp/.X11-unix", 01777); mkdir("/tmp/.ICE-unix", 01777); - mkdir("/var/run/sshd", 01755); /* OpenSSH */ - mkfifo("/dev/xconsole", 0640); /* sysklogd */ - chown("/dev/xconsole", 0, getgroup("tty")); } static plugin_t plugin = { - .hook[HOOK_POST_NETWORK] = { + .hook[HOOK_NETWORK_UP] = { .cb = setup }, }; -static void init_plugin(void) +PLUGIN_INIT(plugin_init) { plugin_register(&plugin); } -static void exit_plugin(void) +PLUGIN_EXIT(plugin_exit) { plugin_unregister(&plugin); } -PLUGIN_INIT(init_plugin) -PLUGIN_EXIT(exit_plugin) - /** * Local Variables: * version-control: t diff --git a/ipc.h b/private.h similarity index 57% rename from ipc.h rename to private.h index a4e192d6..740a3034 100644 --- a/ipc.h +++ b/private.h @@ -1,5 +1,6 @@ -/* Generic IPC class based on TIPC as message bus +/* Private header file for main finit daemon, not for plugins * + * Copyright (c) 2008-2010 Claudio Matsuoka * Copyright (c) 2008-2012 Joachim Nilsson * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,35 +22,17 @@ * THE SOFTWARE. */ -#ifndef FINIT_IPC_H_ -#define FINIT_IPC_H_ +#ifndef FINIT_PRIVATE_H_ +#define FINIT_PRIVATE_H_ -#define SINGLE_TIPC_NODE 64 -#define PRIMARY_MESSAGE_BUS 100 -#define HWSETUP_MESSAGE_BUS 101 -#define FINIT_MESSAGE_BUS 102 +#include "plugin.h" -#define SERVER_CONNECTION 1 -#define CLIENT_CONNECTION 0 +/* plugin.c */ +void run_hooks (hook_point_t no); +void run_services (void); +int load_plugins (struct ev_loop *loop, char *path); -/** - * generic_event_t - Generic message type. - * @mtype: Message type. - * - * This is the base message type that is used on the system primary message bus. All - * messages must start with a long @mtype that describes the type, any extended - * messages must add its extra fields after @mtype. - */ -typedef struct { - long mtype; - long arg; /* Optional argument, free use. */ -} generic_event_t; - -int ipc_init (int type, int port, int conn_type); -int ipc_receive_event (int sd, void *event, size_t sz, int timeout); -int ipc_wait_event (int sd, int event, int timeout); - -#endif /* FINIT_IPC_H_ */ +#endif /* FINIT_PRIVATE_H_ */ /** * Local Variables: diff --git a/service.c b/service.c deleted file mode 100644 index c9d15e4d..00000000 --- a/service.c +++ /dev/null @@ -1,460 +0,0 @@ -/* Service starter and supervisor, inspired by daemontools - * - * Copyright (c) 2008-2010 Claudio Matsuoka - * Copyright (c) 2008-2012 Joachim Nilsson - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include /* mknod() */ -#include /* wait() */ - -#include "finit.h" -#include "helpers.h" -#include "ipc.h" -#include "svc.h" -#include "service.h" -#include "signal.h" - -static int dynamic_event_arg = 0; /* XXX: Fragile construct, refactor! --Jocke */ -static int sd = -1; - -/* Returns the event type unless it's a reconfiguration event. */ -static int is_dynamic_event(generic_event_t *event) -{ - if (!event) { - _e("Invalid event pointer received."); - return 0; - } - - /* XXX: Hook point for plugins. */ - - /* XXX: Fragile construct, refactor! --Jocke */ - dynamic_event_arg = (int)event->arg; - - return (int)event->mtype; -} - -/* Return value: - * 0 - Disabled - * 1 - Enabled - * 2 - Enabled, needs restart - */ -int service_enabled(svc_t *svc, int dynamic) -{ - int run; - - if (!svc->reg.cb) { - /* Unknown service, default to enabled. */ - return 1; - } - - run = svc->reg.cb(svc, dynamic); - /* Sanity check callback return value */ - if (run < 0 || run > 2) { - _d("Callback for %s returned invalid value %d, disabling svc.", svc->reg.path, run); - run = 0; - } - - return run; -} - - -/* This is a map of all known services that we care for. All others, found in - * the system finit.conf file, use the default handler. */ -static svc_map_t known_services[] = { - { NULL, NULL, 0, 0, 0}, -}; - -/* Used to cleanup any lingering or semi-restarting tasks before respawning. */ -static int kill_service(svc_t *svc, int signo) -{ - char *name = basename(svc->cmd); - - _d("Sending signal %d to all processes named %s", signo, name); - kill_procname(name, signo); - - /* Don't restart immediately, some throttling necessary. - * Also it's good form to let the application terminate properly. */ - sleep(1); - - return 0; -} - -/** - * service_register - Register a non-backgrounding service to be monitored - * @line: A complete command line with -- separated description text - * - * Returns: - * POSIX OK(0) on success, or non-zero errno exit status on failure. - */ -int service_register(char *line) -{ - int i = 0; - char *cmd, *descr; - svc_t *svc = svc_new_service(); - - if (!svc || !line) { - return -EINVAL; - } - - descr = strstr(line, "-- "); - if (descr) { - *descr = 0; - strlcpy(svc->descr, descr + 3, sizeof(svc->descr)); - } - - cmd = strtok(line, " "); - strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); - strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); - - while ((cmd = strtok(NULL, " "))) - strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); - - svc->args[i][0] = 0; - - /* If this is a known service, then register callbacks and other data. */ - for (i = 0; known_services[i].path; i++) { - svc_map_t *curr = &known_services[i]; - - if (!strncmp(curr->path, svc->cmd, strlen(svc->cmd))) { - memcpy(&svc->reg, curr, sizeof(svc->reg)); - } - } - - return 0; -} - -#if 0 /* May be usefull someday. */ -static int poll_for_pidfile(char *cmd, const char *path) -{ - int pid = 0; - int tries = 0; - - /* Timeout = 100 * 50ms = 5s */ - while (-1 == access(path, F_OK) && tries++ < 100) { - /* Wait 50ms between retries */ - usleep(50000); - } - - if (-1 == access(path, F_OK)) { - _e("Timeout! No PID found for %s, pidfile %s does not exist?", cmd, path); - pid = 0; - } else { - pid = pidfile_read_pid(path); - } - - return pid; -} -#endif -/* See the README in this package for details on how services are started, stopped - * and how the whole process ties together with the rest of the system. - * --Jocke 2009-03-18 */ -/* Remember: service_enabled() must be called before calling service_start() */ -int service_start(svc_t *svc) -{ - char *args[MAX_NUM_SVC_ARGS]; - int i = 0, restart = 0; - - /* Ignore if finit is SIGSTOP'ed */ - if (sig_stopped()) - return 0; - - restart = svc->pid != 0; - if (!restart) - print_descr("Starting ", svc->descr); - while (svc->args[i][0] != 0 && i < MAX_NUM_SVC_ARGS) { - args[i] = svc->args[i]; - i++; - } - args[i] = NULL; - - if (debug) { - _e("(re)starting %s ", svc->cmd); - for (i = 0; args[i] && i < MAX_NUM_SVC_ARGS; i++) - _e("%s ", args[i]); - _e(""); - } - svc->pid = start_process(svc->cmd, args, debug); - - if (!restart) - print_result(0); - - return 0; -} - -/* Tries to find the matching service and start it. */ -int service_start_by_name(char *name) -{ - svc_t *svc = svc_find_by_name (name); - - if (svc && service_enabled(svc, 0)) { - return service_start(svc); - } - - return 1; -} - -/* See the README in this package for details on how services are started, reloaded - * (config), stopped and how the whole process ties together with the rest of the - * system. --Jocke 2009-03-18 */ -static int stop_service(svc_t *svc) -{ - int res = 1; - - if (!svc) { - _e("Failed, no svc pointer."); - return 1; - } - - if (svc->pid <= 1) { - _d("bad pid:%d for %s, SIGTERM", svc->pid, svc->descr); - svc->pid = 0; - return 1; - } - - print_descr("Stopping ", svc->descr); - _d("Sending SIGTERM to pid:%d name:'%s'", svc->pid, get_pidname(svc->pid, NULL, 0)); - res = kill(svc->pid, SIGTERM); - print_result(res); - svc->pid = 0; - - return res; -} - -/* See the README in this package for details on how services are started, reloaded - * (config), stopped and how the whole process ties together with the rest of the - * system. --Jocke 2009-03-18 */ -static int reload_service(svc_t *svc) -{ - int res; - - if (!svc) { - _e("Failed, no svc pointer."); - return 1; - } - - if (svc->pid <= 1) { - _d("Bad PID %d for %s", svc->pid, svc->cmd); - svc->pid = 0; - return 1; - } - - _d("Sending SIGHUP to PID %d", svc->pid); - res = kill(svc->pid, SIGHUP); - - return res; -} - -static int wait_for_event(generic_event_t *event, int timeout) -{ - if (-1 == sd) { - sd = ipc_init(SINGLE_TIPC_NODE, FINIT_MESSAGE_BUS, SERVER_CONNECTION); - if (-1 == sd) { - return 1; - } - } - - /* ipc_receive_event() might be interrupted by signal, errno is then set to EINTR */ - return ipc_receive_event(sd, event, sizeof(*event), timeout); -} - -/* Returns 1 if pid is alive (/proc/pid exists) else 0 */ -static int pid_alive(unsigned int pid) -{ - char name[24]; /* Enough for max pid_t */ - - snprintf(name, sizeof(name), "/proc/%d", pid); - return !access(name, F_OK); -} - -void service_startup(void) -{ - int ret; - svc_t *svc; - - /* Start finit listen socket before start any services, this to - * be sure we don't miss any messages from for example linkd */ - if (-1 == sd) { - sd = ipc_init(SINGLE_TIPC_NODE, FINIT_MESSAGE_BUS, SERVER_CONNECTION); - if (-1 == sd) { - return; - } - } - - - /* Start 'em */ - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - /* Start, when pid==0 we start regardless of ret. */ - ret = service_enabled(svc, 0); - if (ret == 1 || (ret && svc->pid == 0)) - service_start(svc); - else if (ret == 2) - reload_service(svc); - } - - /* XXX: Hook point for plugins. */ -} - -static int is_norespawn(void) -{ - return sig_stopped() || - fexist("/mnt/norespawn") || - fexist("/tmp/norespawn"); -} - -/* See the README in this package for details on how services are started, stopped - * and how the whole process ties together with the rest of the system. - * --Jocke 2009-03-18 */ -void reiterate(generic_event_t *event) -{ - int ret; - int any_stopped = 0; - svc_t *svc; - - _d("event 0x%x", (unsigned int)event->mtype); - /* Reset state variables before each new run. */ - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - svc->reload = 0; - svc->private = 0; - } - - /* Stop services */ - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - /* Only do configuration changes here, e.g. stopping disabled services, etc. */ - if (is_dynamic_event(event) && !svc->reg.dyn_workaround) // XXX: Ugly workaround - continue; - - svc->reload = 0; - if (svc->pid) { - ret = service_enabled(svc, svc->reg.dyn_workaround ? is_dynamic_event(event) : 0); - if (2 == ret) { - _d("Scheduling %s[%d] for delayed reload.", svc->cmd, svc->pid); - svc->reload = 1; - } else if (0 == ret) { - _d("Attempting to stop %s[%d]", svc->cmd, svc->pid); - if (svc->pid && !stop_service(svc)) { - any_stopped++; - } - } - } - } - - /* Wait for any stopped processes before notifying hwsetup. */ - if (any_stopped) - sleep(1); - - /* XXX: Hook point for plugins. */ - - /* (Re)Start services */ - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - /* Filter out services not interested in dynamic changes. */ - if (is_dynamic_event(event) && !(svc->reg.dyn_reload || svc->reg.vlans_changed_reload || svc->reg.dyn_workaround)) - continue; - - /* Check for stale PID! Happens if, e.g., SIGCHLD signal is lost */ - if (svc->pid && !pid_alive(svc->pid)) { - _d("PID:%d (%s) does not seem to be running, scheduling restart.", svc->pid, svc->cmd); - svc->pid = 0; - svc->reload = 0; - goto restart; - } - - if (svc->reload) { - svc->reload = 0; - reload_service(svc); - } else { - int run; - restart: - run = service_enabled(svc, is_dynamic_event(event)); - if (run) { - if (!svc->pid && !is_norespawn()) { - service_start(svc); - } else if (2 == run) { - reload_service(svc); - } - } - } - } - - /* Notify configuration frontends that we're done. */ - if (!is_dynamic_event(event)) { - /* XXX: Hook point for plugins. */ - } -} - -void service_monitor(void) -{ - int status; - pid_t lost; - svc_t *svc; - generic_event_t event; - - while (1) { - /* Poll, timeout 1000 ms, for message from the system, or SIGCHLD - * when children dies. */ - if (!wait_for_event(&event, 1000)) { - _d("Received event 0x%lx", event.mtype); - /* XXX: Hook point for plugins. */ - } - - lost = waitpid(-1, &status, WNOHANG); - if (fexist(SYNC_SHUTDOWN) || lost <= 1) - continue; - - /* Power user at the console, don't respawn tasks. */ - if (is_norespawn()) - return; - - /* Monitor 'em */ - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (lost != svc->pid) - continue; - - if (sig_stopped()) { - _e("Stopped, not respawning killed processes."); - break; - } - - _d("Ouch, lost pid %d - %s(%d)", lost, svc->cmd, svc->pid); - kill_service(svc, SIGTERM); - - /* Restarting lost service. */ - if (service_enabled(svc, 0)) - service_start(svc); - } - } -} - -/** - * Local Variables: - * version-control: t - * indent-tabs-mode: t - * c-file-style: "linux" - * End: - */ diff --git a/service.h b/service.h deleted file mode 100644 index 76a9c980..00000000 --- a/service.h +++ /dev/null @@ -1,92 +0,0 @@ -/* Service starter and supervisor, inspired by daemontools - * - * Copyright (c) 2008-2010 Claudio Matsuoka - * Copyright (c) 2008-2012 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_SERVICES_H_ -#define FINIT_SERVICES_H_ - -#include -#include /* shmat() */ -#include /* pid_t */ - -#include "finit.h" - -#define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ -#define MAX_NUM_SVC 64 /* Enough? */ -#define MAX_NUM_SVC_ARGS 16 - -/* Default enable for all services, can be stopped by means - * of issuing an initctl call. E.g. - * initctl service */ -typedef struct svc { - char cmd[64]; - char args[MAX_NUM_SVC_ARGS][64]; - char descr[64]; - pid_t pid; - int reload; - int private; /* For svc callbacks to use freely, possibly to store "states". */ - /* Set for known services, otherwise "empty". */ - struct svc_map { - char *path; /* E.g., "/sbin/snmpd" */ - int (*cb)(struct svc *, int); /* Service callback, e.g. is_snmpd_enabled() */ - int dyn_reload; /* Reload on new dynamic event. */ - int dyn_workaround; /* Ugly/Temporary workaround for dyn-stop/start of serialoverip. */ - int vlans_changed_reload; /* Reload when VLANs change. */ - } reg; -} svc_t; - -typedef struct svc_map svc_map_t; - -/* Put services array in shm */ -static inline svc_t *finit_connect_shm(void) -{ - const long ID = FINIT_SHM_ID; - static void *ptr = (void *)-1; - - if ((void *)-1 == ptr) { - ptr = shmat (shmget (ID, sizeof(svc_t) * MAX_NUM_SVC, 0600 | IPC_CREAT), NULL, 0); - if ((void *)-1 == ptr) { - _d("Failed allocating shared memory, error %d: %s", errno, strerror (errno)); - return NULL; - } - } - - return (svc_t *)ptr; -} - -int service_register (char *line); -int service_start (svc_t *svc); -int service_start_by_name (char *name); -int service_enabled (svc_t *svc, int dynamic); -void service_startup (void); -void service_monitor (void); - -#endif /* FINIT_SERVICES_H_ */ - -/** - * Local Variables: - * version-control: t - * indent-tabs-mode: t - * c-file-style: "linux" - * End: - */ diff --git a/signal.c b/signal.c index 6881db28..12eeb781 100644 --- a/signal.c +++ b/signal.c @@ -40,10 +40,12 @@ #include #include +#include #include "finit.h" #include "helpers.h" #include "plugin.h" +#include "private.h" #include "signal.h" static int stopped = 0; @@ -85,8 +87,8 @@ void do_shutdown (int sig) */ static void shutdown_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) { - _d("Rebooting on signal %d from %s (PID %d) with code %d", - sig, get_pidname(info->si_pid, NULL, 0), info->si_pid, info->si_code); + _d("Rebooting on signal %d from %s (PID %d)", + sig, get_pidname(info->si_pid, NULL, 0), info->si_pid); do_shutdown(sig); } @@ -94,12 +96,11 @@ static void shutdown_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) /* * SIGCHLD: one of our children has died */ -static void chld_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) +static void chld_handler(int UNUSED(sig), siginfo_t *UNUSED(info), void *UNUSED(ctx)) { int status; - _d("Child %s died, harvesting. Signal %d from pid %d code %d", - get_pidname(info->si_pid, NULL, 0), sig, info->si_pid, info->si_code); +// _d("Child %d died due to signal %d, harvesting.", info->si_pid, sig); while (waitpid(-1, &status, WNOHANG) != 0) { if (errno == ECHILD) break; @@ -111,15 +112,15 @@ static void chld_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) */ static void sigstop_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) { - _d("Received SIGSTOP(%d) from %s (PID %d) code %d", - sig, get_pidname(info->si_pid, NULL, 0), info->si_pid, info->si_code); + _d("Received SIGSTOP(%d) from %s (PID %d)", + sig, get_pidname(info->si_pid, NULL, 0), info->si_pid); touch(SYNC_STOPPED); stopped ++; } static void sigcont_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) { - _d("Received SIGCONT(%d) from %s (PID %d) code %d", - sig, get_pidname(info->si_pid, NULL, 0), info->si_pid, info->si_code); + _d("Received SIGCONT(%d) from %s (PID %d)", + sig, get_pidname(info->si_pid, NULL, 0), info->si_pid); stopped = 0; remove(SYNC_STOPPED); } diff --git a/signal.h b/signal.h index a9803174..8325ef65 100644 --- a/signal.h +++ b/signal.h @@ -25,6 +25,8 @@ #ifndef FINIT_SIGNAL_H_ #define FINIT_SIGNAL_H_ +#include_next + #define SYNC_SHUTDOWN "/var/lock/finit.shutdown" #define SYNC_STOPPED "/var/lock/finit.stopped" diff --git a/svc.c b/svc.c index 85ed183e..924345a8 100644 --- a/svc.c +++ b/svc.c @@ -25,6 +25,9 @@ #include #include "finit.h" +#include "helpers.h" +#include "private.h" +#include "signal.h" #include "svc.h" /* The registered services are kept in shared memory for easy read-only access @@ -32,12 +35,13 @@ * liveness state of all daemons that should run. */ static int svc_counter = 0; /* Number of registered services to monitor. */ static svc_t *services = NULL; /* List of registered services, in shared memory. */ +static ev_child watchers[MAX_NUM_SVC]; /* Connect to/create shared memory area of registered servies */ static void __connect_shm(void) { if (!services) { - services = finit_connect_shm(); + services = finit_svc_connect(); if (!services) { /* This should never happen, but if it does we're probably * knee-deep in more serious problems already... */ @@ -47,20 +51,37 @@ static void __connect_shm(void) } } -svc_t *svc_new_service (void) +/** + * svc_new - Create a new service object + * + * Returns: + * A pointer to an empty &svc_t object, or %NULL if out of empty slots. + */ +svc_t *svc_new(void) { svc_t *svc = NULL; __connect_shm(); if (svc_counter < MAX_NUM_SVC) { - svc = &services[svc_counter++]; + svc = &services[svc_counter]; memset(svc, 0, sizeof(*svc)); + svc->id = svc_counter++; + } else { + errno = ENOMEM; + return NULL; } return svc; } -svc_t *svc_find_by_id (int id) +/** + * svc_find_by_id - Find a service object by its logical ID# + * @id: Logical ID# + * + * Returns: + * A pointer to an &svc_t object, or %NULL if not found. + */ +svc_t *svc_find_by_id(int id) { __connect_shm(); if (id >= svc_counter) @@ -69,7 +90,14 @@ svc_t *svc_find_by_id (int id) return &services[id]; } -svc_t *svc_find_by_name (char *name) +/** + * svc_find_by_name - Find a service object by its full path name + * @name: Full path name, e.g., /sbin/syslogd + * + * Returns: + * A pointer to an &svc_t object, or %NULL if not found. + */ +svc_t *svc_find_by_name(char *name) { int i; @@ -86,8 +114,15 @@ svc_t *svc_find_by_name (char *name) return NULL; } -/* Iterates over all registered services. Get first svc_t by setting @restart */ -svc_t *svc_iterator (int restart) +/** + * svc_iterator - Iterates over all registered services. + * @restart: Get first &svc_t object by setting @restart + * + * Returns: + * The first &svc_t when @restart is set, otherwise the next &svc_t until + * the end when %NULL is returned. + */ +svc_t *svc_iterator(int restart) { static int id = 0; @@ -101,6 +136,231 @@ svc_t *svc_iterator (int restart) return &services[id++]; } +/** + * svc_register - Register a non-backgrounding service to be monitored + * @line: A complete command line with -- separated description text + * + * Returns: + * POSIX OK(0) on success, or non-zero errno exit status on failure. + */ +int svc_register(char *line) +{ + int i = 0; + char *cmd, *descr; + svc_t *svc = svc_new(); + + if (!svc || !line) { + return -EINVAL; + } + + descr = strstr(line, "-- "); + if (descr) { + *descr = 0; + strlcpy(svc->descr, descr + 3, sizeof(svc->descr)); + } + + cmd = strtok(line, " "); + strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); + strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); + + while ((cmd = strtok(NULL, " "))) + strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); + svc->args[i][0] = 0; + + return 0; +} + + +/** + * svc_enabled - Should the service run? + * @svc: Pointer to &svc_t object + * @dynamic: Dynamic event, opaque flag passed to callback + * + * This method calls an associated service callback, if registered by a + * plugin, and returns the &svc_cmd_t status. If no plugin is registered + * the service is statically enabled in /etc/finit.conf and the result + * will always be %SVC_START. + * + * Returns: + * Either one of %SVC_START, %SVC_STOP, %SVC_RELOAD. + */ +svc_cmd_t svc_enabled(svc_t *svc, int dynamic) +{ + if (!svc) { + errno = EINVAL; + return SVC_STOP; + } + + if (!svc->plugin) { + /* Unknown service, default to enabled. */ + return SVC_START; + } + + return svc->plugin->cb(svc, dynamic); +} + +static int is_norespawn(void) +{ + return sig_stopped() || + fexist("/mnt/norespawn") || + fexist("/tmp/norespawn"); +} + +/* Used to cleanup any lingering or semi-restarting tasks before respawning. */ +static int kill_service(svc_t *svc, int signo) +{ + char *name = basename(svc->cmd); + + _d("Sending signal %d to all processes named %s", signo, name); + kill_procname(name, signo); + + /* Don't restart immediately, some throttling necessary. + * Also it's good form to let the application terminate properly. */ + sleep(1); + + return 0; +} + +static void svc_monitor(EV_P_ ev_child *w, int UNUSED(revents)) +{ + pid_t lost = w->rpid; + svc_t *svc; + + ev_child_stop(EV_A_ w); + + if (fexist(SYNC_SHUTDOWN) || lost <= 1) + return; + + /* Power user at the console, don't respawn tasks. */ + if (is_norespawn()) + return; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (lost != svc->pid) + continue; + + if (sig_stopped()) { + _e("Stopped, not respawning killed processes."); + break; + } + + _d("Ouch, lost pid %d - %s(%d)", lost, svc->cmd, svc->pid); + kill_service(svc, SIGTERM); + + /* Restarting lost service. */ + if (svc_enabled(svc, 0)) + svc_start(svc); + + break; + } +} + +int svc_start(svc_t *svc) +{ + char *args[MAX_NUM_SVC_ARGS]; + int i = 0, restart = 0; + ev_child *cw = &watchers[svc->id]; + + /* Ignore if finit is SIGSTOP'ed */ + if (is_norespawn()) + return 0; + + restart = svc->pid != 0; + if (!restart) print_descr("Starting ", svc->descr); + + /* Serve copy of args to process in case it modifies them. */ + while (svc->args[i][0] != 0 && i < MAX_NUM_SVC_ARGS) { + args[i] = svc->args[i]; + i++; + } + args[i] = NULL; + + if (debug) { + _e("(re)starting %s ", svc->cmd); + for (i = 0; args[i] && i < MAX_NUM_SVC_ARGS; i++) + _e("%s ", args[i]); + _e(""); + } + + svc->pid = start_process(svc->cmd, args, debug); + ev_child_init (cw, svc_monitor, svc->pid, 0); + ev_child_start (EV_DEFAULT_ cw); + + if (!restart) print_result(0); + + return 0; +} + +int svc_start_by_name(char *name) +{ + svc_t *svc = svc_find_by_name(name); + + if (svc && svc_enabled(svc, 0)) + return svc_start(svc); + + return 1; +} + +int svc_stop(svc_t *svc) +{ + int res = 1; + ev_child *cw = &watchers[svc->id]; + + if (!svc) { + _e("Failed, no svc pointer."); + return 1; + } + + if (svc->pid <= 1) { + _d("Bad PID %d for %s, SIGTERM", svc->pid, svc->descr); + svc->pid = 0; + return 1; + } + + print_descr("Stopping ", svc->descr); + _d("Sending SIGTERM to pid:%d name:'%s'", svc->pid, get_pidname(svc->pid, NULL, 0)); + ev_child_stop(ev_default_loop(0), cw); + res = kill(svc->pid, SIGTERM); + print_result(res); + svc->pid = 0; + + return res; +} + +int svc_reload(svc_t *svc) +{ + if (!svc) { + _e("Failed, no svc pointer."); + return 1; + } + + if (svc->pid <= 1) { + _d("Bad PID %d for %s, SIGHUP", svc->pid, svc->cmd); + svc->pid = 0; + return 1; + } + + _d("Sending SIGHUP to PID %d", svc->pid); + return kill(svc->pid, SIGHUP); +} + +void svc_start_all(void) +{ + svc_t *svc; + svc_cmd_t cmd; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + cmd = svc_enabled(svc, 0); + if (cmd == SVC_START || (cmd == SVC_RELOAD && svc->pid == 0)) + svc_start(svc); + else if (cmd == SVC_RELOAD) + svc_reload(svc); + } + + /* XXX: Hook point for plugins. */ +} + + /** * Local Variables: * compile-command: "gcc -g -DUNITTEST -o unittest svc.c" diff --git a/svc.h b/svc.h index ff1599d3..e6b1c983 100644 --- a/svc.h +++ b/svc.h @@ -22,17 +22,86 @@ * THE SOFTWARE. */ -#ifndef FINIT_SVC_H__ -#define FINIT_SVC_H__ +#ifndef FINIT_SVC_H_ +#define FINIT_SVC_H_ -#include "service.h" +#include +#include /* shmat() */ +#include /* pid_t */ -svc_t *svc_new_service (void); -svc_t *svc_find_by_id (int id); -svc_t *svc_find_by_name (char *name); -svc_t *svc_iterator (int restart); +#include "finit.h" -#endif /* FINIT_SVC_H__ */ +typedef enum { + SVC_STOP = 0, /* Disabled */ + SVC_START, /* Enabled */ + SVC_RELOAD /* Enabled, needs restart */ +} svc_cmd_t; + +#define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ +#define MAX_ARG_LEN 64 +#define MAX_STR_LEN 64 +#define MAX_NUM_SVC 64 /* Enough? */ +#define MAX_NUM_SVC_ARGS 16 + +/* Default enable for all services, can be stopped by means + * of issuing an initctl call. E.g. + * initctl service */ +typedef struct svc { + /* Private */ + int id; /* Service ID#, set by svc_new() */ + pid_t pid; + char cmd[MAX_ARG_LEN]; + char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN]; + char descr[MAX_STR_LEN]; + /* Public */ + int delayed_reload; /* For external service plugin. */ + plugin_svc_t *plugin; +} svc_t; + +typedef struct svc_map svc_map_t; + +/* Put services array in shm */ +static inline svc_t *finit_svc_connect(void) +{ + const long ID = FINIT_SHM_ID; + static void *ptr = (void *)-1; + + if ((void *)-1 == ptr) { + ptr = shmat (shmget (ID, sizeof(svc_t) * MAX_NUM_SVC, 0600 | IPC_CREAT), NULL, 0); + if ((void *)-1 == ptr) { + _d("Failed allocating shared memory, error %d: %s", errno, strerror (errno)); + return NULL; + } + } + + return (svc_t *)ptr; +} + +static inline int svc_id(svc_t *svc) +{ + if (!svc) { + errno = EINVAL; + return -1; + } + + return svc->id; +} + +svc_t *svc_new (void); +svc_t *svc_find_by_id (int id); +svc_t *svc_find_by_name (char *name); +svc_t *svc_iterator (int restart); + +int svc_register (char *line); +int svc_id_by_name (char *name); +svc_cmd_t svc_enabled (svc_t *svc, int dynamic); +int svc_start (svc_t *svc); +int svc_start_by_name (char *name); +int svc_stop (svc_t *svc); +int svc_reload (svc_t *svc); +void svc_start_all (void); + +#endif /* FINIT_SVC_H_ */ /** * Local Variables: