From 687f52b0b5f4e971ead756fa216b95181af1f26e Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Thu, 14 May 2015 15:25:27 +0200 Subject: [PATCH] Add support for adding/removing services from /etc/finit.d at runtime. This patch adds support for adding and removing services from Finit at runtime. Configuration file stanzas for service, run and task may now be written to files in /etc/finit.d/*.conf, using the exact same syntax as before in /etc/finit.conf. When a file is added, removed or modified the user may simply SIGHUP Finit (PID 1) to activate the changes. Signed-off-by: Joachim Nilsson --- README.md | 44 ++++++- TODO.md | 8 +- conf.c | 379 +++++++++++++++++++++++++++++++++-------------------- finit.c | 15 ++- finit.conf | 4 +- finit.h | 4 +- inetd.c | 4 +- sig.c | 24 +++- svc.c | 185 ++++++++++++++++++-------- svc.h | 20 ++- 10 files changed, 454 insertions(+), 233 deletions(-) diff --git a/README.md b/README.md index 9c543150..9fef3d49 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Table of Contents * [Features](#features) * [/etc/finit.conf](#etcfinitconf) * [/etc/finit.d](#etcfinitd) +* [Runparts](#runparts) * [Bootstrap](#bootstrap) * [Runlevels](#runlevels) * [Inetd](#inetd) @@ -45,7 +46,7 @@ of processes to provide a very basic bootstrap written entirely in C. Hence, there is no `/etc/init.d/rcS` script, or similar, instead Finit reads its configuration from [/etc/finit.conf](#etcfinitconf). This file details what kernel modules to load, programs to run, daemons to -supervise, and services to launch on demand. +supervise, and inetd services to launch on demand. See [TroglOS][9] for an example of how to boot a small embedded system with Finit. @@ -159,15 +160,15 @@ Syntax: Finit does not output this text on the console when launching inetd services. Instead this text is sent to syslog. -* `runparts ` +* `runparts ` - Call run-parts(8) on a directory other than default ``/etc/finit.d`` + Call run-parts(8) on a directory to run start scripts. All executable + files, or scripts, in the directory are called, in alphabetic order. * `include ` - Include another configuration file, ``/etc/finit.d``, or runparts path - is prepended to file if the file is not found or an absolute path is - not given + Include another configuration file. If the file is not an absolute + path ``/etc/finit.d`` is prepended. * `tty [LVLS] ` @@ -209,8 +210,37 @@ the `/path/to/cmd` should be executed with. Simply prefix the path with /etc/finit.d ------------ +Finit supports changes to the overall system configuration at runtime. +For this purpose the (configurable) directory `/etc/finit.d` is used. +Here you can put configuration file snippets, one per service if you +like, which are all sourced automatically by finit at boot when loading +the static configuration from `/etc/finit.conf`. This is the default +behavior, so no include directives are necessary. + +To add a new service, simply drop a `.conf` file in `/etc/finit.d` and +send `SIGHUP` to finit, or call `init q`. Any service read from this +directory is flagged as a dynamic service, so changes to their .conf +files, or even removal of the files, is detected at `SIGHUP`. + +- If a service's .conf file has been removed, the service is stopped. +- If the file has changed the service is reloaded, stopped and + restarted. +- If it is a new service, it is started -- respecting runlevels and + callbacks. + +The `/etc/finit.d` directory was previously the default Finit `runparts` +directory. Finit no longer has a default runparts, so make sure to +update your setup, or the finit configuration, accordingly. + +**Note:** Configurations read from `/etc/finit.d` are read *after* + initial bootstrap, runlevel S(1). + + +Runparts +-------- + At the end of the boot, when networking and all services are up, finit -calls its built-in [run-parts(8)] on the `/etc/finit.d/` directory, if +calls its built-in [run-parts(8)] on the `runparts ` directory, if it exists. Similar to how the `/ec/rc.local` file works in most other init daemons, only finit runs a directory of scripts. This replaces the earlier support for a `/usr/sbin/services.sh` script in the original diff --git a/TODO.md b/TODO.md index 21cb23d7..75bffa42 100644 --- a/TODO.md +++ b/TODO.md @@ -18,9 +18,7 @@ very low dependency on external packages. General ------- -* Make it possible to at least reload .conf files in `/etc/finit.d/` -- - At first all we need to support is the service directive, All other - directives, read from `/etc/finit.conf`, can remain static. +* Add support for instances, e.g. to monitor several DHCP clients. * Make initctl synchroneous, don't let `init 1` return until finit has successfully entered runlevel 1, all proceses have been stopped. * Add `!` or `[wait ]` to service directive to let finit pause @@ -31,10 +29,6 @@ General and `RELOAD` on `STDOUT` from a script ... call it for instance `.monitor=` in `svc_t` ... the script named as the basename of the service it monitors + `.sh`. -* The `runparts` directive may need to be deprecated, or to have its - semantics changed. We need `/etc/finit.d/` for extending `finit.conf` - -- if not possible, then maybe filter out the service monitor scripts - before running the scripts? * Add compile-time supoprt for running `/bin/sh` instead of `getty` * Add support for `init ` and `SIGHUP` to have finit reload its `finit.conf`, but also diff --git a/conf.c b/conf.c index 75ea48ac..1baede06 100644 --- a/conf.c +++ b/conf.c @@ -1,6 +1,6 @@ /* Parser for finit.conf * - * Copyright (c) 2012 Joachim Nilsson + * Copyright (c) 2012-2015 Joachim Nilsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ */ #include +#include #include #include "finit.h" @@ -33,6 +34,9 @@ #define MATCH_CMD(l, c, x) \ (!strncasecmp(l, c, strlen(c)) && (x = (l) + strlen(c))) +static int parse_conf(char *file); + + static char *strip_line(char *line) { char *ptr; @@ -86,13 +90,197 @@ int parse_runlevels(char *runlevels) return bitmask; } +static void parse_static(char *line) +{ + char *x; + char cmd[CMD_SIZE]; + + /* Do this before mounting / read-write + * XXX: Move to plugin which checks /etc/fstab instead */ + if (MATCH_CMD(line, "check ", x)) { + char *dev = strip_line(x); + + strcpy(cmd, "/sbin/fsck -C -a "); + strlcat(cmd, dev, sizeof(cmd)); + run_interactive(cmd, "Checking file system %s", dev); + + return; + } + + if (MATCH_CMD(line, "user ", x)) { + if (username) free(username); + username = strdup(strip_line(x)); + return; + } + + if (MATCH_CMD(line, "host ", x)) { + if (hostname) free(hostname); + hostname = strdup(strip_line(x)); + return; + } + + if (MATCH_CMD(line, "module ", x)) { + char *mod = strip_line(x); + + strcpy(cmd, "/sbin/modprobe "); + strlcat(cmd, mod, sizeof(cmd)); + run_interactive(cmd, "Loading kernel module %s", mod); + + return; + } + + if (MATCH_CMD(line, "mknod ", x)) { + char *dev = strip_line(x); + + strcpy(cmd, "/bin/mknod "); + strlcat(cmd, dev, sizeof(cmd)); + run_interactive(cmd, "Creating device node %s", dev); + + return; + } + + if (MATCH_CMD(line, "network ", x)) { + if (network) free(network); + network = strdup(strip_line(x)); + return; + } + + /* This is the directory from which executable scripts and + * any finit include files are read, default /etc/finit.d/ */ + if (MATCH_CMD(line, "runparts ", x)) { + if (runparts) free(runparts); + runparts = strdup(strip_line(x)); + return; + } + + /* Parse any include file, use rcsd if absolute path not given */ + if (MATCH_CMD(line, "include ", x)) { + char *file = strip_line(x); + + strlcpy(cmd, file, sizeof(cmd)); + if (!fexist(cmd)) { + /* ... try /etc/finit.d/ as prefix */ + snprintf(cmd, sizeof(cmd), "%s/%s", rcsd, file); + if (!fexist(cmd)) + return; + } + + parse_conf(cmd); + return; + } + + if (MATCH_CMD(line, "startx ", x)) { + svc_register(SVC_TYPE_SERVICE, strip_line(x), 0, username); + return; + } + + if (MATCH_CMD(line, "shutdown ", x)) { + if (sdown) free(sdown); + sdown = strdup(strip_line(x)); + return; + } + + /* The desired runlevel to start when leaving + * bootstrap (S). Finit supports 1-9, but most + * systems only use 1-6, where 6 is reserved for + * reboot */ + if (MATCH_CMD(line, "runlevel ", x)) { + char *token = strip_line(x); + const char *err = NULL; + + cfglevel = strtonum(token, 1, 9, &err); + if (err) + cfglevel = RUNLEVEL; + if (cfglevel < 1 || cfglevel > 9 || cfglevel == 6) + cfglevel = 2; /* Fallback */ + return; + } + + /* TODO: Make console & tty dynamically loadable from /etc/finit.d */ + if (MATCH_CMD(line, "console ", x)) { + if (console) free(console); + console = strdup(strip_line(x)); + return; + } + + /* TODO: Make console & tty dynamically loadable from /etc/finit.d */ + if (MATCH_CMD(line, "tty ", x)) { + tty_register(strip_line(x)); + return; + } +} + +static void parse_dynamic(char *line, time_t mtime) +{ + char *x; + + /* Skip comments. */ + if (MATCH_CMD(line, "#", x)) + return; + + /* Monitored daemon, will be respawned on exit, as + * long as the (optional) service callback returns + * non-zero */ + if (MATCH_CMD(line, "service ", x)) { + svc_register(SVC_TYPE_SERVICE, x, mtime, NULL); + return; + } + + /* One-shot task, will not be respawned. Only runs if + * the (optional) service callback returns true */ + if (MATCH_CMD(line, "task ", x)) { + svc_register(SVC_TYPE_TASK, x, mtime, NULL); + return; + } + + /* Like task but waits for completion, useful w/ [S] */ + if (MATCH_CMD(line, "run ", x)) { + svc_register(SVC_TYPE_RUN, x, mtime, NULL); + return; + } + + /* Classic inetd service */ + if (MATCH_CMD(line, "inetd ", x)) { +#ifndef INETD_DISABLED + svc_register(SVC_TYPE_INETD, x, mtime, NULL); +#else + _e("Finit built with inetd support disabled, cannot register service inetd %s!", x); +#endif + return; + } +} + +static int parse_conf_dynamic(char *file, time_t mtime) +{ + FILE *fp = fopen(file, "r"); + + if (!fp) { + _pe("Failed opening %s", file); + return 1; + } + + while (!feof(fp)) { + char line[LINE_SIZE] = ""; + + if (!fgets(line, sizeof(line), fp)) + continue; + + chomp(line); + _d("dyn conf: %s", line); + + parse_dynamic(line, mtime); + } + + fclose(fp); + + return 0; +} + static int parse_conf(char *file) { FILE *fp; char line[LINE_SIZE] = ""; - char cmd[CMD_SIZE]; char *x; - const char *err = NULL; fp = fopen(file, "r"); if (!fp) @@ -123,146 +311,10 @@ static int parse_conf(char *file) if (!fgets(line, sizeof(line), fp)) continue; chomp(line); + _d("conf: %s", line); - - /* Skip comments. */ - if (MATCH_CMD(line, "#", x)) - continue; - - /* Do this before mounting / read-write - * XXX: Move to plugin which checks /etc/fstab instead */ - if (MATCH_CMD(line, "check ", x)) { - char *dev = strip_line(x); - - strcpy(cmd, "/sbin/fsck -C -a "); - strlcat(cmd, dev, sizeof(cmd)); - run_interactive(cmd, "Checking file system %s", dev); - - continue; - } - - if (MATCH_CMD(line, "user ", x)) { - if (username) free(username); - username = strdup(strip_line(x)); - continue; - } - if (MATCH_CMD(line, "host ", x)) { - if (hostname) free(hostname); - hostname = strdup(strip_line(x)); - continue; - } - - if (MATCH_CMD(line, "module ", x)) { - char *mod = strip_line(x); - - strcpy(cmd, "/sbin/modprobe "); - strlcat(cmd, mod, sizeof(cmd)); - run_interactive(cmd, "Loading kernel module %s", mod); - - continue; - } - if (MATCH_CMD(line, "mknod ", x)) { - char *dev = strip_line(x); - - strcpy(cmd, "/bin/mknod "); - strlcat(cmd, dev, sizeof(cmd)); - run_interactive(cmd, "Creating device node %s", dev); - - continue; - } - - if (MATCH_CMD(line, "network ", x)) { - if (network) free(network); - network = strdup(strip_line(x)); - continue; - } - /* This is the directory from which executable scripts and - * any finit include files are read, default /etc/finit.d/ */ - if (MATCH_CMD(line, "runparts ", x)) { - if (rcsd) free(rcsd); - rcsd = strdup(strip_line(x)); - continue; - } - /* Parse any include file, use rcsd if absolute path not given */ - if (MATCH_CMD(line, "include ", x)) { - char *file = strip_line(x); - - strlcpy(cmd, file, sizeof(cmd)); - if (!fexist(cmd)) { - /* ... try /etc/finit.d/ as prefix */ - snprintf(cmd, sizeof(cmd), "%s/%s", rcsd, file); - if (!fexist(cmd)) - continue; - } - - parse_conf(cmd); - continue; - } - if (MATCH_CMD(line, "startx ", x)) { - svc_register(SVC_CMD_SERVICE, strip_line(x), username); - continue; - } - if (MATCH_CMD(line, "shutdown ", x)) { - if (sdown) free(sdown); - sdown = strdup(strip_line(x)); - continue; - } - - /* The desired runlevel to start when leaving - * bootstrap (S). Finit supports 1-9, but most - * systems only use 1-6, where 6 is reserved for - * reboot */ - if (MATCH_CMD(line, "runlevel ", x)) { - char *token = strip_line(x); - - cfglevel = strtonum(token, 1, 9, &err); - if (err) - cfglevel = RUNLEVEL; - if (cfglevel < 1 || cfglevel > 9 || cfglevel == 6) - cfglevel = 2; /* Fallback */ - continue; - } - - /* Monitored daemon, will be respawned on exit, as - * long as the (optional) service callback returns - * non-zero */ - if (MATCH_CMD(line, "service ", x)) { - svc_register(SVC_CMD_SERVICE, x, NULL); - continue; - } - - /* One-shot task, will not be respawned. Only runs if - * the (optional) service callback returns true */ - if (MATCH_CMD(line, "task ", x)) { - svc_register(SVC_CMD_TASK, x, NULL); - continue; - } - - /* Like task but waits for completion, useful w/ [S] */ - if (MATCH_CMD(line, "run ", x)) { - svc_register(SVC_CMD_RUN, x, NULL); - continue; - } - - /* Classic inetd service */ - if (MATCH_CMD(line, "inetd ", x)) { -#ifndef INETD_DISABLED - svc_register(SVC_CMD_INETD, x, NULL); -#else - _e("Finit built with inetd support disabled, cannot register service inetd %s!", x); -#endif - continue; - } - - if (MATCH_CMD(line, "console ", x)) { - if (console) free(console); - console = strdup(strip_line(x)); - continue; - } - if (MATCH_CMD(line, "tty ", x)) { - tty_register(strip_line(x)); - continue; - } + parse_static(line); + parse_dynamic(line, 0); } fclose(fp); @@ -270,13 +322,54 @@ static int parse_conf(char *file) return 0; } +int parse_finit_d(char *dir) +{ + int i, num; + struct dirent **e; + + num = scandir(dir, &e, NULL, alphasort); + if (num < 0) { + _d("No files found in %s, skipping ...", dir); + return -1; + } + + for (i = 0; i < num; i++) { + char *name = e[i]->d_name; + char path[CMD_SIZE]; + struct stat st; + + snprintf(path, sizeof(path), "%s/%s", dir, name); + + /* Check that it's an actual file ... */ + if (stat(path, &st)) { + _d("Cannot even read .conf file %s, skipping ...", path); + continue; + } + if (S_ISEXEC(st.st_mode) || S_ISDIR(st.st_mode)) + continue; + + /* Check that file ends with '.conf' */ + if (strcmp(&path[strlen(path) - 5], ".conf")) { + _d("File %s is not a .conf, skipping ... ", path); + continue; + } + + parse_conf_dynamic(path, st.st_mtime); + } + + while (num--) + free(e[num]); + free(e); + + return 0; +} + int parse_finit_conf(char *file) { int result; username = strdup(DEFUSER); hostname = strdup(DEFHOST); - rcsd = strdup(FINIT_RCSD); result = parse_conf(file); if (!tty_num()) { diff --git a/finit.c b/finit.c index 58a40927..ebe67999 100644 --- a/finit.c +++ b/finit.c @@ -47,7 +47,8 @@ char *sdown = NULL; char *network = NULL; char *username = NULL; char *hostname = NULL; -char *rcsd = NULL; +char *rcsd = FINIT_RCSD; +char *runparts = NULL; char *console = NULL; uev_ctx_t *ctx = NULL; /* Main loop context */ @@ -262,19 +263,21 @@ int main(int argc, char* argv[]) plugin_run_hooks(HOOK_NETWORK_UP); /* - * Start all tasks/services in the configured runlevel + * Load .conf files from /etc/finit.d and start all + * tasks/services in the configured runlevel */ + parse_finit_d(rcsd); svc_runlevel(cfglevel); _d("Running svc up hooks ..."); plugin_run_hooks(HOOK_SVC_UP); /* - * Run startup scripts in /etc/finit.d/, if any. + * Run startup scripts in the runparts directory, if any. */ - if (rcsd && fisdir(rcsd)) { - _d("Running startup scripts in %s ...", rcsd); - run_parts(rcsd, NULL); + if (runparts && fisdir(runparts)) { + _d("Running startup scripts in %s ...", runparts); + run_parts(runparts, NULL); } /* Hooks that should run at the very end */ diff --git a/finit.conf b/finit.conf index a38c8575..7583834d 100644 --- a/finit.conf +++ b/finit.conf @@ -33,8 +33,8 @@ service [2345] /sbin/klogd -n -- Kernel logging server service [2345] /sbin/syslogd -n -- Syslog server service [3] /usr/sbin/gdm -- GNOME Display Manager -# By default Finit looks for /etc/finit.d -# runparts /etc/finit.d +# Run start scripts from this directory +# runparts /etc/start.d # Inetd services inetd time/udp wait [2345] internal -- UNIX rdate service diff --git a/finit.h b/finit.h index 2dbf6c0a..edf24825 100644 --- a/finit.h +++ b/finit.h @@ -89,17 +89,19 @@ struct init_request { extern int runlevel; extern int cfglevel; extern int prevlevel; +extern char *rcsd; extern char *sdown; extern char *network; extern char *hostname; extern char *username; -extern char *rcsd; +extern char *runparts; extern char *console; extern char *__progname; /* conf.c */ int parse_runlevels (char *runlevels); int parse_finit_conf (char *file); +int parse_finit_d (char *dir); #endif /* FINIT_H_ */ diff --git a/inetd.c b/inetd.c index 4d2f6cab..ac576fbe 100644 --- a/inetd.c +++ b/inetd.c @@ -179,7 +179,7 @@ int inetd_respawn(pid_t pid) { svc_t *svc = svc_find_by_pid(pid); - if (svc && SVC_CMD_INETD == svc->type) { + if (svc && SVC_TYPE_INETD == svc->type) { inetd_t *inetd = &svc->inetd; svc->pid = 0; @@ -481,7 +481,7 @@ int inetd_init(inetd_t *inetd, void *arg, char *ifname, char *port) inetd_filter_t *filter; /* Skip non-inetd services */ - if (svc->type != SVC_CMD_INETD) + if (svc->type != SVC_TYPE_INETD) continue; /* Skip ourselves */ diff --git a/sig.c b/sig.c index 95b74939..b33c55d1 100644 --- a/sig.c +++ b/sig.c @@ -48,7 +48,7 @@ #include "sig.h" static int stopped = 0; -static uev_t sigint_watcher, sigpwr_watcher; +static uev_t sighup_watcher, sigint_watcher, sigpwr_watcher; static uev_t sigchld_watcher, sigsegv_watcher; static uev_t sigstop_watcher, sigtstp_watcher, sigcont_watcher; @@ -87,6 +87,24 @@ void do_shutdown(int sig) reboot(RB_POWER_OFF); } +/* + * Reload .conf files in /etc/finit.d/ + */ +static void sighup_cb(uev_ctx_t *UNUSED(ctx), uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events)) +{ + /* Mark and sweep */ + svc_mark_dynamic(); + + /* Reload all *.conf in /etc/finit.d/ */ + parse_finit_d(rcsd); + + /* Reload dirty services */ + svc_reload_dynamic(); + + /* Cleanup stale services */ + svc_cleanup(); +} + /* * Shut down on INT PWR */ @@ -207,8 +225,8 @@ void sig_setup(uev_ctx_t *ctx) /* Some C APIs may need SIGALRM for implementing timers. */ IGNSIG(sa, SIGALRM, 0); - /* We don't have any /etc/inittab yet, reread finit.conf? */ - IGNSIG(sa, SIGHUP, 0); + /* /etc/inittab not supported yet, instead /etc/finit.d/ is scanned for *.conf */ + uev_signal_init(ctx, &sighup_watcher, sighup_cb, NULL, SIGHUP); /* After initial bootstrap of Finit we call the service monitor to reap children */ uev_signal_init(ctx, &sigchld_watcher, sigchld_cb, NULL, SIGCHLD); diff --git a/svc.c b/svc.c index d9786b81..d5aa839f 100644 --- a/svc.c +++ b/svc.c @@ -40,7 +40,6 @@ * by 3rd party APIs. Mostly because of the ability to, from the outside, query * liveness state of all daemons that should run. */ static int was_stopped = 0; -static int svc_counter = 0; /* Number of registered services to monitor. */ static svc_t *services = NULL; /* List of registered services, in shared memory. */ @@ -66,42 +65,37 @@ static void __connect_shm(void) */ svc_t *svc_new(void) { - svc_t *svc = NULL; + int i; __connect_shm(); - if (svc_counter >= 0 && svc_counter < MAX_NUM_SVC) { - svc = &services[svc_counter++]; - memset(svc, 0, sizeof(*svc)); - } else { - errno = ENOMEM; - return NULL; + for (i = 0; i < MAX_NUM_SVC; i++) { + svc_t *svc = &services[i]; + + if (svc->type == SVC_TYPE_FREE) { + memset(svc, 0, sizeof(*svc)); + return svc; + } } - return svc; + errno = ENOMEM; + return NULL; } /** * svc_del - Delete a service object * @svc: Pointer to an &svc_t object * - * Note, this function is not safe for public use. Should only be used - * by this module until the storage of svc_t's has been refactored! - * * Returns: * Always succeeds, returning POSIX OK(0). */ int svc_del(svc_t *svc) { - if (svc_counter >= 0 && svc_counter < MAX_NUM_SVC) { - svc = &services[--svc_counter]; - memset(svc, 0, sizeof(*svc)); - } - + svc->type = SVC_TYPE_FREE; return 0; } /** - * svc_iterator - Iterates over all registered services. + * svc_iterator - Naive iterator over all registered services. * @first: Get first &svc_t object, or next until end. * * Returns: @@ -116,10 +110,14 @@ svc_t *svc_iterator(int first) if (first) i = 0; - if (i >= svc_counter) - return NULL; + while (i < MAX_NUM_SVC) { + svc_t *svc = &services[i++]; - return &services[i++]; + if (svc->type != SVC_TYPE_FREE) + return svc; + } + + return NULL; } /** @@ -168,7 +166,7 @@ svc_t *svc_find_inetd(char *path, char *service, char *proto, char *port) svc_t *svc; for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (svc->type != SVC_CMD_INETD) + if (svc->type != SVC_TYPE_INETD) continue; if (strncmp(path, svc->cmd, strlen(svc->cmd))) @@ -184,6 +182,25 @@ svc_t *svc_find_inetd(char *path, char *service, char *proto, char *port) } #endif +/** + * svc_mark_dynamic - Mark dynamically loaded services for deletion. + * + * This function traverses the list of known services, marking all that + * have been loaded from /etc/finit.d/ for deletion. If a .conf file + * has been removed svc_cleanup() will stop and delete the service. + * + * This function is called from sighup_cb(). + */ +void svc_mark_dynamic(void) +{ + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->mtime) + svc->dirty = -1; + } +} + /** * svc_bootstrap - Start bootstrap services and tasks * @@ -199,7 +216,7 @@ void svc_bootstrap(void) svc_cmd_t cmd; /* Inetd services cannot be part of bootstrap currently. */ - if (svc->type == SVC_CMD_INETD) + if (svc->type == SVC_TYPE_INETD) continue; cmd = svc_enabled(svc, 0, NULL); @@ -229,6 +246,22 @@ static void utmp_save(int pre, int now) endutent(); } +/* Singing and dancing ... */ +static void svc_dance(svc_t *svc) +{ + svc_cmd_t cmd = svc_enabled(svc, 0, NULL); + + if (svc->pid) { + if (cmd == SVC_STOP) + svc_stop(svc); + else if (cmd == SVC_RELOAD) + svc_reload(svc); + } else { + if (cmd == SVC_START) + svc_start(svc); + } +} + /** * svc_runlevel - Change to a new runlevel * @newlevel: New runlevel to activate @@ -253,11 +286,9 @@ void svc_runlevel(int newlevel) _d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel); for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - svc_cmd_t cmd; - /* Inetd services have slightly different semantics */ #ifndef INETD_DISABLED - if (svc->type == SVC_CMD_INETD) { + if (svc->type == SVC_TYPE_INETD) { if (!ISSET(svc->runlevels, runlevel)) inetd_stop(&svc->inetd); else @@ -268,16 +299,7 @@ void svc_runlevel(int newlevel) #endif /* All other services consult their callback here */ - cmd = svc_enabled(svc, 0, NULL); - if (svc->pid) { - if (cmd == SVC_STOP) - svc_stop(svc); - else if (cmd == SVC_RELOAD) - svc_reload(svc); - } else { - if (cmd == SVC_START) - svc_start(svc); - } + svc_dance(svc); } if (0 == runlevel) { @@ -300,8 +322,9 @@ void svc_runlevel(int newlevel) /** * svc_register - Register service, task or run commands - * @type: %SVC_CMD_SERVICE(0), %SVC_CMD_TASK(1), %SVC_CMD_RUN(2) + * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) * @line: A complete command line with -- separated description text + * @mtime: The modification time if service is loaded from /etc/finit.d * @username: Optional username to run service as, or %NULL to run as root * * This function is used to register commands to be run on different @@ -319,7 +342,7 @@ void svc_runlevel(int newlevel) * "inetd tcp/ssh nowait [2345] @root:root /usr/sbin/sshd -i -- Description" * * If the username is left out the command is started as root. - * Inetd services, launched on demand (SVC_CMD_INETD) + * Inetd services, launched on demand (SVC_TYPE_INETD) * The [] brackets are there to denote the allowed runlevels. Allowed * runlevels mimic that of SysV init with the addition of the 'S' * runlevel, which is only run once at startup. It can be seen as the @@ -329,7 +352,7 @@ void svc_runlevel(int newlevel) * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. */ -int svc_register(int type, char *line, char *username) +int svc_register(int type, char *line, time_t mtime, char *username) { int i = 0; #ifndef INETD_DISABLED @@ -396,7 +419,7 @@ int svc_register(int type, char *line, char *username) #ifndef INETD_DISABLED /* Find plugin that provides a callback for this inetd service */ - if (type == SVC_CMD_INETD && !strncasecmp(cmd, "internal", 8)) { + if (type == SVC_TYPE_INETD && !strncasecmp(cmd, "internal", 8)) { plugin = plugin_find(service); if (!plugin || !plugin->inetd.cmd) { _e("Inetd service %s has no internal plugin, skipping.", service); @@ -410,13 +433,18 @@ int svc_register(int type, char *line, char *username) return inetd_allow(&svc->inetd, iface); #endif - svc = svc_new(); + svc = svc_find(cmd); if (!svc) { - _e("Out of memory, cannot register service %s", cmd); - return errno = ENOMEM; + svc = svc_new(); + if (!svc) { + _e("Out of memory, cannot register service %s", cmd); + return errno = ENOMEM; + } } - - svc->type = type; + if (svc->mtime != mtime) + svc->dirty = 1; + svc->mtime = mtime; + svc->type = type; if (desc) strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); @@ -432,7 +460,7 @@ int svc_register(int type, char *line, char *username) } #ifndef INETD_DISABLED - if (svc->type == SVC_CMD_INETD) { + if (svc->type == SVC_TYPE_INETD) { int result; result = inetd_new(&svc->inetd, service, proto, forking); @@ -552,7 +580,7 @@ static void restart_any_lost_procs(void) continue; /* Only restart lost daemons, not task/run/inetd services */ - if (SVC_CMD_SERVICE != svc->type) { + if (SVC_TYPE_SERVICE != svc->type) { svc->pid = 0; continue; } @@ -592,7 +620,7 @@ void svc_monitor(pid_t lost) if (lost != svc->pid) continue; - if (SVC_CMD_SERVICE != svc->type) { + if (SVC_TYPE_SERVICE != svc->type) { svc->pid = 0; continue; } @@ -648,7 +676,7 @@ int svc_start(svc_t *svc) return 0; #ifndef INETD_DISABLED - if (SVC_CMD_INETD == svc->type) { + if (SVC_TYPE_INETD == svc->type) { char ifname[IF_NAMESIZE] = "UNKNOWN"; sd = svc->inetd.watcher.fd; @@ -682,7 +710,7 @@ int svc_start(svc_t *svc) FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->inetd.name, ifname); } else #endif - if (SVC_CMD_SERVICE != svc->type) + if (SVC_TYPE_SERVICE != svc->type) print_desc("", svc->desc); else if (!respawn) print_desc("Starting ", svc->desc); @@ -723,7 +751,7 @@ int svc_start(svc_t *svc) args[i] = NULL; /* Redirect inetd socket to stdin for service */ - if (SVC_CMD_INETD == svc->type) { + if (SVC_TYPE_INETD == svc->type) { /* sd set previously */ dup2(sd, STDIN_FILENO); close(sd); @@ -755,7 +783,7 @@ int svc_start(svc_t *svc) else status = execv(svc->cmd, args); /* XXX: Maybe use execve() to be able to launch scripts? */ - if (SVC_CMD_INETD == svc->type) { + if (SVC_TYPE_INETD == svc->type) { if (svc->inetd.type == SOCK_STREAM) { close(STDIN_FILENO); close(STDOUT_FILENO); @@ -767,11 +795,11 @@ int svc_start(svc_t *svc) } svc->pid = pid; - if (SVC_CMD_INETD == svc->type) { + if (SVC_TYPE_INETD == svc->type) { if (svc->inetd.type == SOCK_STREAM) close(sd); } - else if (SVC_CMD_RUN == svc->type) + else if (SVC_TYPE_RUN == svc->type) print_result(WEXITSTATUS(complete(svc->cmd, pid))); else if (!respawn) print_result(svc->pid > 1 ? 0 : 1); @@ -805,7 +833,7 @@ int svc_stop(svc_t *svc) return 1; } - if (SVC_CMD_SERVICE != svc->type) + if (SVC_TYPE_SERVICE != svc->type) return 0; if (runlevel != 1) @@ -820,6 +848,16 @@ int svc_stop(svc_t *svc) return res; } +/** + * svc_reload - Send SIGHUP to a service + * @svc: Service to reload + * + * This function does some basic checks of the runtime state of Finit + * and a sanity check of the @svc before sending %SIGHUP. + * + * Returns: + * POSIX OK(0) or non-zero on error. + */ int svc_reload(svc_t *svc) { /* Ignore if finit is SIGSTOP'ed */ @@ -841,6 +879,43 @@ int svc_reload(svc_t *svc) return kill(svc->pid, SIGHUP); } +/** + * svc_reload_dynamic - Reload modifued dynamic services + * + * This function is called when Finit has recieved SIGHUP to reload + * .conf files in /etc/finit.d. It is responsible for starting, + * stopping and reloading (forwarding SIGHUP) to processes affected. + */ +void svc_reload_dynamic(void) +{ + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->mtime && svc->dirty > 0) { + svc_dance(svc); + svc->dirty = 0; + } + } +} + +/** + * svc_cleanup - Stop and cleanup stale services removed from /etc/finit.d + * + * This function traverses the list of known services to stop and clean + * up all non-existing services previously marked by svc_mark_dynamic(). + */ +void svc_cleanup(void) +{ + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->mtime && svc->dirty == -1) { + svc_stop(svc); + svc_del(svc); + } + } +} + /** * Local Variables: * version-control: t diff --git a/svc.h b/svc.h index d6ce8a7a..e8b83401 100644 --- a/svc.h +++ b/svc.h @@ -42,10 +42,11 @@ typedef enum { } svc_cmd_t; typedef enum { - SVC_CMD_SERVICE = 0, /* Monitored, will be respawned */ - SVC_CMD_TASK, /* One-shot, runs in parallell */ - SVC_CMD_RUN, /* Like task, but wait for completion */ - SVC_CMD_INETD /* Classic inetd service */ + SVC_TYPE_FREE = 0, /* Free to allocate */ + SVC_TYPE_SERVICE , /* Monitored, will be respawned */ + SVC_TYPE_TASK, /* One-shot, runs in parallell */ + SVC_TYPE_RUN, /* Like task, but wait for completion */ + SVC_TYPE_INETD /* Classic inetd service */ } svc_type_t; #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ @@ -62,7 +63,9 @@ typedef enum { typedef struct svc { pid_t pid; svc_type_t type; - int reload; + time_t mtime; /* Modification time for .conf from /etc/finit.d/ */ + int dirty; /* Set if old mtime != new mtime => reloaded, + * or -1 when marked for removal */ int runlevels; /* Incremented for each restart by service monitor. */ @@ -80,7 +83,7 @@ typedef struct svc { char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN]; char desc[MAX_STR_LEN]; - /* For external plugins. If @cb is set a plugin is loaded. + /* For external plugins. If @cb is set, a plugin is loaded. * @dynamic: Set by plugins that want dynamic events. * @dynamic_stop: Set by plugins that allow dyn. events to stop it as well. * @private: Can be used freely by plugin, e.g., to store "states". @@ -114,15 +117,18 @@ svc_t *svc_find (char *path); svc_t *svc_find_by_pid (pid_t pid); svc_t *svc_find_inetd (char *path, char *service, char *proto, char *port); svc_t *svc_iterator (int first); +void svc_mark_dynamic (void); void svc_runlevel (int newlevel); -int svc_register (int type, char *line, char *username); +int svc_register (int type, char *line, time_t mtime, char *username); int svc_id_by_name (char *name); svc_cmd_t svc_enabled (svc_t *svc, int event, void *arg); 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_reload_dynamic(void); +void svc_cleanup (void); #endif /* FINIT_SVC_H_ */