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: