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 <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson committed 2015-05-31 01:55:39 +02:00
1 parent 24051ab2fb
commit 687f52b0b5
10 files changed
+454 -233

No files matched your search

+37 -7
View File
@@ -12,6 +12,7 @@ Table of Contents
* [Features](#features) * [Features](#features)
* [/etc/finit.conf](#etcfinitconf) * [/etc/finit.conf](#etcfinitconf)
* [/etc/finit.d](#etcfinitd) * [/etc/finit.d](#etcfinitd)
* [Runparts](#runparts)
* [Bootstrap](#bootstrap) * [Bootstrap](#bootstrap)
* [Runlevels](#runlevels) * [Runlevels](#runlevels)
* [Inetd](#inetd) * [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 Hence, there is no `/etc/init.d/rcS` script, or similar, instead Finit
reads its configuration from [/etc/finit.conf](#etcfinitconf). This reads its configuration from [/etc/finit.conf](#etcfinitconf). This
file details what kernel modules to load, programs to run, daemons to 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 See [TroglOS][9] for an example of how to boot a small embedded system
with Finit. with Finit.
@@ -159,15 +160,15 @@ Syntax:
Finit does not output this text on the console when launching inetd Finit does not output this text on the console when launching inetd
services. Instead this text is sent to syslog. services. Instead this text is sent to syslog.
* `runparts <PATH>` * `runparts <DIR>`
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 <CONF>` * `include <CONF>`
Include another configuration file, ``/etc/finit.d``, or runparts path Include another configuration file. If the file is not an absolute
is prepended to file if the file is not found or an absolute path is path ``/etc/finit.d`` is prepended.
not given
* `tty [LVLS] <DEV | /path/to/cmd [args]>` * `tty [LVLS] <DEV | /path/to/cmd [args]>`
@@ -209,8 +210,37 @@ the `/path/to/cmd` should be executed with. Simply prefix the path with
/etc/finit.d /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 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 <DIR>` directory, if
it exists. Similar to how the `/ec/rc.local` file works in most other 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 init daemons, only finit runs a directory of scripts. This replaces the
earlier support for a `/usr/sbin/services.sh` script in the original earlier support for a `/usr/sbin/services.sh` script in the original
+1 -7
View File
@@ -18,9 +18,7 @@ very low dependency on external packages.
General General
------- -------
* Make it possible to at least reload .conf files in `/etc/finit.d/` -- * Add support for instances, e.g. to monitor several DHCP clients.
At first all we need to support is the service directive, All other
directives, read from `/etc/finit.conf`, can remain static.
* Make initctl synchroneous, don't let `init 1` return until finit * Make initctl synchroneous, don't let `init 1` return until finit
has successfully entered runlevel 1, all proceses have been stopped. has successfully entered runlevel 1, all proceses have been stopped.
* Add `!` or `[wait <pidfile>]` to service directive to let finit pause * Add `!` or `[wait <pidfile>]` to service directive to let finit pause
@@ -31,10 +29,6 @@ General
and `RELOAD` on `STDOUT` from a script ... call it for instance and `RELOAD` on `STDOUT` from a script ... call it for instance
`.monitor=` in `svc_t` ... the script named as the basename of the `.monitor=` in `svc_t` ... the script named as the basename of the
service it monitors + `.sh`. 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 compile-time supoprt for running `/bin/sh` instead of `getty`
* Add support for `init <q | reload-configuration>` and `SIGHUP` to * Add support for `init <q | reload-configuration>` and `SIGHUP` to
have finit reload its `finit.conf`, but also have finit reload its `finit.conf`, but also
+236 -143
View File
@@ -1,6 +1,6 @@
/* Parser for finit.conf /* Parser for finit.conf
* *
* Copyright (c) 2012 Joachim Nilsson <troglobit@gmail.com> * Copyright (c) 2012-2015 Joachim Nilsson <troglobit@gmail.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -22,6 +22,7 @@
*/ */
#include <ctype.h> #include <ctype.h>
#include <dirent.h>
#include <string.h> #include <string.h>
#include "finit.h" #include "finit.h"
@@ -33,6 +34,9 @@
#define MATCH_CMD(l, c, x) \ #define MATCH_CMD(l, c, x) \
(!strncasecmp(l, c, strlen(c)) && (x = (l) + strlen(c))) (!strncasecmp(l, c, strlen(c)) && (x = (l) + strlen(c)))
static int parse_conf(char *file);
static char *strip_line(char *line) static char *strip_line(char *line)
{ {
char *ptr; char *ptr;
@@ -86,13 +90,197 @@ int parse_runlevels(char *runlevels)
return bitmask; 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) static int parse_conf(char *file)
{ {
FILE *fp; FILE *fp;
char line[LINE_SIZE] = ""; char line[LINE_SIZE] = "";
char cmd[CMD_SIZE];
char *x; char *x;
const char *err = NULL;
fp = fopen(file, "r"); fp = fopen(file, "r");
if (!fp) if (!fp)
@@ -123,146 +311,10 @@ static int parse_conf(char *file)
if (!fgets(line, sizeof(line), fp)) if (!fgets(line, sizeof(line), fp))
continue; continue;
chomp(line); chomp(line);
_d("conf: %s", line); _d("conf: %s", line);
parse_static(line);
/* Skip comments. */ parse_dynamic(line, 0);
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;
}
} }
fclose(fp); fclose(fp);
@@ -270,13 +322,54 @@ static int parse_conf(char *file)
return 0; 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 parse_finit_conf(char *file)
{ {
int result; int result;
username = strdup(DEFUSER); username = strdup(DEFUSER);
hostname = strdup(DEFHOST); hostname = strdup(DEFHOST);
rcsd = strdup(FINIT_RCSD);
result = parse_conf(file); result = parse_conf(file);
if (!tty_num()) { if (!tty_num()) {
+9 -6
View File
@@ -47,7 +47,8 @@ char *sdown = NULL;
char *network = NULL; char *network = NULL;
char *username = NULL; char *username = NULL;
char *hostname = NULL; char *hostname = NULL;
char *rcsd = NULL; char *rcsd = FINIT_RCSD;
char *runparts = NULL;
char *console = NULL; char *console = NULL;
uev_ctx_t *ctx = NULL; /* Main loop context */ uev_ctx_t *ctx = NULL; /* Main loop context */
@@ -262,19 +263,21 @@ int main(int argc, char* argv[])
plugin_run_hooks(HOOK_NETWORK_UP); 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); svc_runlevel(cfglevel);
_d("Running svc up hooks ..."); _d("Running svc up hooks ...");
plugin_run_hooks(HOOK_SVC_UP); 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)) { if (runparts && fisdir(runparts)) {
_d("Running startup scripts in %s ...", rcsd); _d("Running startup scripts in %s ...", runparts);
run_parts(rcsd, NULL); run_parts(runparts, NULL);
} }
/* Hooks that should run at the very end */ /* Hooks that should run at the very end */
+2 -2
View File
@@ -33,8 +33,8 @@ service [2345] /sbin/klogd -n -- Kernel logging server
service [2345] /sbin/syslogd -n -- Syslog server service [2345] /sbin/syslogd -n -- Syslog server
service [3] /usr/sbin/gdm -- GNOME Display Manager service [3] /usr/sbin/gdm -- GNOME Display Manager
# By default Finit looks for /etc/finit.d # Run start scripts from this directory
# runparts /etc/finit.d # runparts /etc/start.d
# Inetd services # Inetd services
inetd time/udp wait [2345] internal -- UNIX rdate service inetd time/udp wait [2345] internal -- UNIX rdate service
+3 -1
View File
@@ -89,17 +89,19 @@ struct init_request {
extern int runlevel; extern int runlevel;
extern int cfglevel; extern int cfglevel;
extern int prevlevel; extern int prevlevel;
extern char *rcsd;
extern char *sdown; extern char *sdown;
extern char *network; extern char *network;
extern char *hostname; extern char *hostname;
extern char *username; extern char *username;
extern char *rcsd; extern char *runparts;
extern char *console; extern char *console;
extern char *__progname; extern char *__progname;
/* conf.c */ /* conf.c */
int parse_runlevels (char *runlevels); int parse_runlevels (char *runlevels);
int parse_finit_conf (char *file); int parse_finit_conf (char *file);
int parse_finit_d (char *dir);
#endif /* FINIT_H_ */ #endif /* FINIT_H_ */
+2 -2
View File
@@ -179,7 +179,7 @@ int inetd_respawn(pid_t pid)
{ {
svc_t *svc = svc_find_by_pid(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; inetd_t *inetd = &svc->inetd;
svc->pid = 0; svc->pid = 0;
@@ -481,7 +481,7 @@ int inetd_init(inetd_t *inetd, void *arg, char *ifname, char *port)
inetd_filter_t *filter; inetd_filter_t *filter;
/* Skip non-inetd services */ /* Skip non-inetd services */
if (svc->type != SVC_CMD_INETD) if (svc->type != SVC_TYPE_INETD)
continue; continue;
/* Skip ourselves */ /* Skip ourselves */
+21 -3
View File
@@ -48,7 +48,7 @@
#include "sig.h" #include "sig.h"
static int stopped = 0; 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 sigchld_watcher, sigsegv_watcher;
static uev_t sigstop_watcher, sigtstp_watcher, sigcont_watcher; static uev_t sigstop_watcher, sigtstp_watcher, sigcont_watcher;
@@ -87,6 +87,24 @@ void do_shutdown(int sig)
reboot(RB_POWER_OFF); 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 * 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. */ /* Some C APIs may need SIGALRM for implementing timers. */
IGNSIG(sa, SIGALRM, 0); IGNSIG(sa, SIGALRM, 0);
/* We don't have any /etc/inittab yet, reread finit.conf? */ /* /etc/inittab not supported yet, instead /etc/finit.d/ is scanned for *.conf */
IGNSIG(sa, SIGHUP, 0); uev_signal_init(ctx, &sighup_watcher, sighup_cb, NULL, SIGHUP);
/* After initial bootstrap of Finit we call the service monitor to reap children */ /* After initial bootstrap of Finit we call the service monitor to reap children */
uev_signal_init(ctx, &sigchld_watcher, sigchld_cb, NULL, SIGCHLD); uev_signal_init(ctx, &sigchld_watcher, sigchld_cb, NULL, SIGCHLD);
+130 -55
View File
@@ -40,7 +40,6 @@
* by 3rd party APIs. Mostly because of the ability to, from the outside, query * by 3rd party APIs. Mostly because of the ability to, from the outside, query
* liveness state of all daemons that should run. */ * liveness state of all daemons that should run. */
static int was_stopped = 0; 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. */ 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_new(void)
{ {
svc_t *svc = NULL; int i;
__connect_shm(); __connect_shm();
if (svc_counter >= 0 && svc_counter < MAX_NUM_SVC) { for (i = 0; i < MAX_NUM_SVC; i++) {
svc = &services[svc_counter++]; svc_t *svc = &services[i];
memset(svc, 0, sizeof(*svc));
} else { if (svc->type == SVC_TYPE_FREE) {
errno = ENOMEM; memset(svc, 0, sizeof(*svc));
return NULL; return svc;
}
} }
return svc; errno = ENOMEM;
return NULL;
} }
/** /**
* svc_del - Delete a service object * svc_del - Delete a service object
* @svc: Pointer to an &svc_t 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: * Returns:
* Always succeeds, returning POSIX OK(0). * Always succeeds, returning POSIX OK(0).
*/ */
int svc_del(svc_t *svc) int svc_del(svc_t *svc)
{ {
if (svc_counter >= 0 && svc_counter < MAX_NUM_SVC) { svc->type = SVC_TYPE_FREE;
svc = &services[--svc_counter];
memset(svc, 0, sizeof(*svc));
}
return 0; 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. * @first: Get first &svc_t object, or next until end.
* *
* Returns: * Returns:
@@ -116,10 +110,14 @@ svc_t *svc_iterator(int first)
if (first) if (first)
i = 0; i = 0;
if (i >= svc_counter) while (i < MAX_NUM_SVC) {
return NULL; 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; svc_t *svc;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (svc->type != SVC_CMD_INETD) if (svc->type != SVC_TYPE_INETD)
continue; continue;
if (strncmp(path, svc->cmd, strlen(svc->cmd))) 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 #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 * svc_bootstrap - Start bootstrap services and tasks
* *
@@ -199,7 +216,7 @@ void svc_bootstrap(void)
svc_cmd_t cmd; svc_cmd_t cmd;
/* Inetd services cannot be part of bootstrap currently. */ /* Inetd services cannot be part of bootstrap currently. */
if (svc->type == SVC_CMD_INETD) if (svc->type == SVC_TYPE_INETD)
continue; continue;
cmd = svc_enabled(svc, 0, NULL); cmd = svc_enabled(svc, 0, NULL);
@@ -229,6 +246,22 @@ static void utmp_save(int pre, int now)
endutent(); 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 * svc_runlevel - Change to a new runlevel
* @newlevel: New runlevel to activate * @newlevel: New runlevel to activate
@@ -253,11 +286,9 @@ void svc_runlevel(int newlevel)
_d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel); _d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel);
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
svc_cmd_t cmd;
/* Inetd services have slightly different semantics */ /* Inetd services have slightly different semantics */
#ifndef INETD_DISABLED #ifndef INETD_DISABLED
if (svc->type == SVC_CMD_INETD) { if (svc->type == SVC_TYPE_INETD) {
if (!ISSET(svc->runlevels, runlevel)) if (!ISSET(svc->runlevels, runlevel))
inetd_stop(&svc->inetd); inetd_stop(&svc->inetd);
else else
@@ -268,16 +299,7 @@ void svc_runlevel(int newlevel)
#endif #endif
/* All other services consult their callback here */ /* All other services consult their callback here */
cmd = svc_enabled(svc, 0, NULL); svc_dance(svc);
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);
}
} }
if (0 == runlevel) { if (0 == runlevel) {
@@ -300,8 +322,9 @@ void svc_runlevel(int newlevel)
/** /**
* svc_register - Register service, task or run commands * 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 * @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 * @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 * 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" * "inetd tcp/ssh nowait [2345] @root:root /usr/sbin/sshd -i -- Description"
* *
* If the username is left out the command is started as root. * 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 * The [] brackets are there to denote the allowed runlevels. Allowed
* runlevels mimic that of SysV init with the addition of the 'S' * 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 * runlevel, which is only run once at startup. It can be seen as the
@@ -329,7 +352,7 @@ void svc_runlevel(int newlevel)
* Returns: * Returns:
* POSIX OK(0) on success, or non-zero errno exit status on failure. * 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; int i = 0;
#ifndef INETD_DISABLED #ifndef INETD_DISABLED
@@ -396,7 +419,7 @@ int svc_register(int type, char *line, char *username)
#ifndef INETD_DISABLED #ifndef INETD_DISABLED
/* Find plugin that provides a callback for this inetd service */ /* 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); plugin = plugin_find(service);
if (!plugin || !plugin->inetd.cmd) { if (!plugin || !plugin->inetd.cmd) {
_e("Inetd service %s has no internal plugin, skipping.", service); _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); return inetd_allow(&svc->inetd, iface);
#endif #endif
svc = svc_new(); svc = svc_find(cmd);
if (!svc) { if (!svc) {
_e("Out of memory, cannot register service %s", cmd); svc = svc_new();
return errno = ENOMEM; if (!svc) {
_e("Out of memory, cannot register service %s", cmd);
return errno = ENOMEM;
}
} }
if (svc->mtime != mtime)
svc->type = type; svc->dirty = 1;
svc->mtime = mtime;
svc->type = type;
if (desc) if (desc)
strlcpy(svc->desc, desc + 3, sizeof(svc->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 #ifndef INETD_DISABLED
if (svc->type == SVC_CMD_INETD) { if (svc->type == SVC_TYPE_INETD) {
int result; int result;
result = inetd_new(&svc->inetd, service, proto, forking); result = inetd_new(&svc->inetd, service, proto, forking);
@@ -552,7 +580,7 @@ static void restart_any_lost_procs(void)
continue; continue;
/* Only restart lost daemons, not task/run/inetd services */ /* Only restart lost daemons, not task/run/inetd services */
if (SVC_CMD_SERVICE != svc->type) { if (SVC_TYPE_SERVICE != svc->type) {
svc->pid = 0; svc->pid = 0;
continue; continue;
} }
@@ -592,7 +620,7 @@ void svc_monitor(pid_t lost)
if (lost != svc->pid) if (lost != svc->pid)
continue; continue;
if (SVC_CMD_SERVICE != svc->type) { if (SVC_TYPE_SERVICE != svc->type) {
svc->pid = 0; svc->pid = 0;
continue; continue;
} }
@@ -648,7 +676,7 @@ int svc_start(svc_t *svc)
return 0; return 0;
#ifndef INETD_DISABLED #ifndef INETD_DISABLED
if (SVC_CMD_INETD == svc->type) { if (SVC_TYPE_INETD == svc->type) {
char ifname[IF_NAMESIZE] = "UNKNOWN"; char ifname[IF_NAMESIZE] = "UNKNOWN";
sd = svc->inetd.watcher.fd; 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); FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->inetd.name, ifname);
} else } else
#endif #endif
if (SVC_CMD_SERVICE != svc->type) if (SVC_TYPE_SERVICE != svc->type)
print_desc("", svc->desc); print_desc("", svc->desc);
else if (!respawn) else if (!respawn)
print_desc("Starting ", svc->desc); print_desc("Starting ", svc->desc);
@@ -723,7 +751,7 @@ int svc_start(svc_t *svc)
args[i] = NULL; args[i] = NULL;
/* Redirect inetd socket to stdin for service */ /* Redirect inetd socket to stdin for service */
if (SVC_CMD_INETD == svc->type) { if (SVC_TYPE_INETD == svc->type) {
/* sd set previously */ /* sd set previously */
dup2(sd, STDIN_FILENO); dup2(sd, STDIN_FILENO);
close(sd); close(sd);
@@ -755,7 +783,7 @@ int svc_start(svc_t *svc)
else else
status = execv(svc->cmd, args); /* XXX: Maybe use execve() to be able to launch scripts? */ 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) { if (svc->inetd.type == SOCK_STREAM) {
close(STDIN_FILENO); close(STDIN_FILENO);
close(STDOUT_FILENO); close(STDOUT_FILENO);
@@ -767,11 +795,11 @@ int svc_start(svc_t *svc)
} }
svc->pid = pid; svc->pid = pid;
if (SVC_CMD_INETD == svc->type) { if (SVC_TYPE_INETD == svc->type) {
if (svc->inetd.type == SOCK_STREAM) if (svc->inetd.type == SOCK_STREAM)
close(sd); close(sd);
} }
else if (SVC_CMD_RUN == svc->type) else if (SVC_TYPE_RUN == svc->type)
print_result(WEXITSTATUS(complete(svc->cmd, pid))); print_result(WEXITSTATUS(complete(svc->cmd, pid)));
else if (!respawn) else if (!respawn)
print_result(svc->pid > 1 ? 0 : 1); print_result(svc->pid > 1 ? 0 : 1);
@@ -805,7 +833,7 @@ int svc_stop(svc_t *svc)
return 1; return 1;
} }
if (SVC_CMD_SERVICE != svc->type) if (SVC_TYPE_SERVICE != svc->type)
return 0; return 0;
if (runlevel != 1) if (runlevel != 1)
@@ -820,6 +848,16 @@ int svc_stop(svc_t *svc)
return res; 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) int svc_reload(svc_t *svc)
{ {
/* Ignore if finit is SIGSTOP'ed */ /* Ignore if finit is SIGSTOP'ed */
@@ -841,6 +879,43 @@ int svc_reload(svc_t *svc)
return kill(svc->pid, SIGHUP); 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: * Local Variables:
* version-control: t * version-control: t
+13 -7
View File
@@ -42,10 +42,11 @@ typedef enum {
} svc_cmd_t; } svc_cmd_t;
typedef enum { typedef enum {
SVC_CMD_SERVICE = 0, /* Monitored, will be respawned */ SVC_TYPE_FREE = 0, /* Free to allocate */
SVC_CMD_TASK, /* One-shot, runs in parallell */ SVC_TYPE_SERVICE , /* Monitored, will be respawned */
SVC_CMD_RUN, /* Like task, but wait for completion */ SVC_TYPE_TASK, /* One-shot, runs in parallell */
SVC_CMD_INETD /* Classic inetd service */ SVC_TYPE_RUN, /* Like task, but wait for completion */
SVC_TYPE_INETD /* Classic inetd service */
} svc_type_t; } svc_type_t;
#define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */
@@ -62,7 +63,9 @@ typedef enum {
typedef struct svc { typedef struct svc {
pid_t pid; pid_t pid;
svc_type_t type; 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; int runlevels;
/* Incremented for each restart by service monitor. */ /* Incremented for each restart by service monitor. */
@@ -80,7 +83,7 @@ typedef struct svc {
char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN]; char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN];
char desc[MAX_STR_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: Set by plugins that want dynamic events.
* @dynamic_stop: Set by plugins that allow dyn. events to stop it as well. * @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". * @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_by_pid (pid_t pid);
svc_t *svc_find_inetd (char *path, char *service, char *proto, char *port); svc_t *svc_find_inetd (char *path, char *service, char *proto, char *port);
svc_t *svc_iterator (int first); svc_t *svc_iterator (int first);
void svc_mark_dynamic (void);
void svc_runlevel (int newlevel); 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); int svc_id_by_name (char *name);
svc_cmd_t svc_enabled (svc_t *svc, int event, void *arg); svc_cmd_t svc_enabled (svc_t *svc, int event, void *arg);
int svc_start (svc_t *svc); int svc_start (svc_t *svc);
int svc_start_by_name (char *name); int svc_start_by_name (char *name);
int svc_stop (svc_t *svc); int svc_stop (svc_t *svc);
int svc_reload (svc_t *svc); int svc_reload (svc_t *svc);
void svc_reload_dynamic(void);
void svc_cleanup (void);
#endif /* FINIT_SVC_H_ */ #endif /* FINIT_SVC_H_ */