From cb075560eda4df688c8b758f783e3cb22610bc1b Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Sun, 31 May 2015 01:52:14 +0200 Subject: [PATCH] Add support for multiple instances of the same command This patch adds support for starting and supervising multiple services (run, task, or service) using an extra #ID number. service #1 [2345] /sbin/httpd -f -h /http -p 80 -- Web server service #2 [2345] /sbin/httpd -f -h /http -p 8080 -- Old web server Also included in this patch is a fix for endless respawn of faulty services. For instance, a buggy daemon that crashes repeatedly will now be stopped after 10 respawns. Signed-off-by: Joachim Nilsson --- README.md | 10 +++++++ conf.c | 2 +- finit.conf | 12 +++++++- helpers.c | 68 ++--------------------------------------- helpers.h | 1 - plugin.c | 8 +++-- svc.c | 88 ++++++++++++++++++++++++++++++++---------------------- svc.h | 9 ++++-- 8 files changed, 89 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 9fef3d49..15e967d6 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,16 @@ the `/path/to/cmd` should be executed with. Simply prefix the path with run [2345] @joe:users /usr/bin/logger "Hello world" +For multiple instances of the same command, e.g. a DHCP client or +multiple web servers, add `#ID` somewhere between the `run`, `task`, +`service` keyword and the command, like this: + + service #1 [2345] /sbin/httpd -f -h /http -p 80 -- Web server + service #2 [2345] /sbin/httpd -f -h /http -p 8080 -- Old web server + +Without the `#ID` to the service the latter will overwrite the former +and only the old web server would be started and supervised. + /etc/finit.d ------------ diff --git a/conf.c b/conf.c index 1baede06..c6d03445 100644 --- a/conf.c +++ b/conf.c @@ -214,7 +214,7 @@ static void parse_dynamic(char *line, time_t mtime) { char *x; - /* Skip comments. */ + /* Skip comments, i.e. lines beginning with # */ if (MATCH_CMD(line, "#", x)) return; diff --git a/finit.conf b/finit.conf index 7583834d..5c38e613 100644 --- a/finit.conf +++ b/finit.conf @@ -1,4 +1,8 @@ -# Sample Debian finit.conf +# Sample finit.conf to demonstatre the syntax +# +# Comments are lines that start with #, comments are only allowed +# on separate lines. The # sign is used inside service/run/task +# stanzas to denote instance ID, see below. user admin host testbed @@ -41,3 +45,9 @@ inetd time/udp wait [2345] internal -- UNIX rdate servic inetd time/tcp nowait [2345] internal -- UNIX rdate service inetd ssh@eth0:222/tcp nowait [2345] /usr/sbin/sshd -i -- SSH service inetd ssh/tcp nowait [2345] /usr/sbin/sshd -i -- SSH service + +# For multiple instances of the same service, add #ID somewhere between +# the service/run/task keyword and the command. +service #1 [2345] /sbin/httpd -f -h /http -p 80 -- Web server +service #2 [2345] /sbin/httpd -f -h /http -p 8080 -- Old web server + diff --git a/helpers.c b/helpers.c index f8e33cd2..bf6186b3 100644 --- a/helpers.c +++ b/helpers.c @@ -267,72 +267,10 @@ char *pid_get_name(pid_t pid, char *name, size_t len) if (ret) return NULL; - return name; -} + if (name) + return name; -/** - * procname_kill - Send a signal to a process group by name. - * @name: Name of process to send signal to. - * @signo: Signal to send. - * - * Send a signal to a running process (group). This function searches - * for a given process @name and sends a signal, @signo, to each - * process ID matching that @name. - * - * Returns: - * Number of signals sent, i.e. number of processes who have received the signal. - */ -int procname_kill(char *name, int signo) -{ - int result = 0; - char path[32], line[64]; - FILE *fp; - DIR *dir; - struct dirent *entry; - - if (!name) { - errno = EINVAL; - return 0; - } - - dir = opendir("/proc"); - if (!dir) - return 0; - - while ((entry = readdir(dir)) != NULL) { - /* Skip non-process entries in /proc */ - if (!isdigit(*entry->d_name)) - continue; - - snprintf(path, sizeof(path), "/proc/%s/status", entry->d_name); - /* Skip non-readable files (protected?) */ - if ((fp = fopen(path, "r")) == NULL) - continue; - - if (fgets(line, sizeof (line), fp)) { - char *pname = line + 6; /* Skip first part of line --> "Name:\t" */ - - if (strncmp(pname, name, strlen(pname) - 1) == 0) { - int error = errno; - int pid = atonum(entry->d_name); - - if (-1 == pid) { - FLOG_ERROR("Failed converting %s to a PID: %s", entry->d_name, strerror(error)); - } else { - if (kill(pid, signo)) - FLOG_ERROR("Failed signalling(%d) %s: %s!", signo, name, strerror(error)); - else - result++; /* Track number of processes we deliver the signal to. */ - } - } - } - - fclose(fp); - } - - closedir(dir); - - return result; + return pname; } diff --git a/helpers.h b/helpers.h index 97aa8c7d..4d2d8257 100644 --- a/helpers.h +++ b/helpers.h @@ -103,7 +103,6 @@ int pid_alive (pid_t pid); char *pid_get_name (pid_t pid, char *name, size_t len); void procname_set (char *name, char *args[]); -int procname_kill (char *name, int signo); void print (int action, const char *fmt, ...); void print_desc (char *action, char *desc); diff --git a/plugin.c b/plugin.c index 0e1f39d7..8973da24 100644 --- a/plugin.c +++ b/plugin.c @@ -87,9 +87,13 @@ int plugin_unregister(plugin_t *plugin) TAILQ_REMOVE(&plugins, plugin, link); if (plugin->svc.cb) { - svc_t *svc = svc_find(plugin->name); + svc_t *svc; + + /* Unregister plugin callback for all matching services */ + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (strcmp(svc->cmd, plugin->name)) + continue; - if (svc) { svc->cb = NULL; svc->dynamic = 0; svc->dynamic_stop = 0; diff --git a/svc.c b/svc.c index d5aa839f..adab3945 100644 --- a/svc.c +++ b/svc.c @@ -36,6 +36,8 @@ #include "svc.h" #include "inetd.h" +#define RESPAWN_MAX 10 /* Prevent endless respawn of faulty services. */ + /* The registered services are kept in shared memory for easy read-only access * by 3rd party APIs. Mostly because of the ability to, from the outside, query * liveness state of all daemons that should run. */ @@ -58,12 +60,13 @@ static void __connect_shm(void) } /** - * svc_new - Create a new service object + * svc_new - Create a new service + * @id: Instance id * * Returns: - * A pointer to an empty &svc_t object, or %NULL if out of empty slots. + * A pointer to a new &svc_t object, or %NULL if out of empty slots. */ -svc_t *svc_new(void) +svc_t *svc_new(int id) { int i; @@ -73,6 +76,8 @@ svc_t *svc_new(void) if (svc->type == SVC_TYPE_FREE) { memset(svc, 0, sizeof(*svc)); + svc->id = id; + return svc; } } @@ -127,12 +132,12 @@ svc_t *svc_iterator(int first) * Returns: * A pointer to an &svc_t object, or %NULL if not found. */ -svc_t *svc_find(char *path) +svc_t *svc_find(char *path, int id) { svc_t *svc; for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (!strncmp(path, svc->cmd, strlen(svc->cmd))) { + if (svc->id == id && !strncmp(path, svc->cmd, strlen(svc->cmd))) { _d("Found a matching svc for %s", path); return svc; } @@ -252,12 +257,12 @@ static void svc_dance(svc_t *svc) svc_cmd_t cmd = svc_enabled(svc, 0, NULL); if (svc->pid) { - if (cmd == SVC_STOP) + if (SVC_STOP == cmd) svc_stop(svc); - else if (cmd == SVC_RELOAD) + else if (SVC_RELOAD == cmd) svc_reload(svc); } else { - if (cmd == SVC_START) + if (SVC_START == cmd || SVC_RELOAD == cmd) svc_start(svc); } } @@ -336,18 +341,27 @@ void svc_runlevel(int newlevel) * The @line can optionally start with a username, denoted by an @ * character. Like this: * - * "service @username [!0-6,S] /path/to/daemon arg -- Description text" - * "task @username [!0-6,S] /path/to/task arg -- Description text" - * "run @username [!0-6,S] /path/to/cmd arg -- Description text" - * "inetd tcp/ssh nowait [2345] @root:root /usr/sbin/sshd -i -- Description" + * service @username [!0-6,S] /path/to/daemon arg -- Description + * task @username [!0-6,S] /path/to/task arg -- Description + * run @username [!0-6,S] /path/to/cmd arg -- Description + * inetd tcp/ssh nowait [2345] @root:root /sbin/sshd -i -- Description * - * If the username is left out the command is started as root. - * 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 - * system bootstrap. If a task or run command is listed in more than the - * [S] runlevel they will be called when changing runlevel. + * If the username is left out the command is started as root. The [] + * brackets denote the allowed runlevels, if left out the default for a + * service is set to [2-5]. 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 system bootstrap. If a task or run + * command is listed in more than the [S] runlevel they will be called + * when changing runlevel. + * + * For multiple instances of the same command, e.g. multiple DHCP + * clients, the user may enter an optional ID, using the #ID syntax. + * + * service #1 /sbin/udhcpc -i eth1 + * service #2 /sbin/udhcpc -i eth2 + * + * Without the #ID syntax Finit will overwrite the first service line + * with the contents of the second. The #ID must be [1,MAXINT]. * * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. @@ -355,6 +369,7 @@ void svc_runlevel(int newlevel) int svc_register(int type, char *line, time_t mtime, char *username) { int i = 0; + int id = 1; /* Default to ID:1 */ #ifndef INETD_DISABLED int forking = 0; #endif @@ -392,6 +407,8 @@ int svc_register(int type, char *line, time_t mtime, char *username) username = &cmd[1]; else if (cmd[0] == '[') /* [runlevels] */ runlevels = &cmd[0]; + else if (cmd[0] == '#') /* #ID */ + id = atoi(&cmd[1]); else break; @@ -433,9 +450,10 @@ int svc_register(int type, char *line, time_t mtime, char *username) return inetd_allow(&svc->inetd, iface); #endif - svc = svc_find(cmd); + svc = svc_find(cmd, id); if (!svc) { - svc = svc_new(); + _d("Creating new svc for %s id #%d", cmd, id); + svc = svc_new(id); if (!svc) { _e("Out of memory, cannot register service %s", cmd); return errno = ENOMEM; @@ -635,12 +653,14 @@ void svc_monitor(pid_t lost) break; } - /* Cleanup any lingering or semi-restarting tasks before respawning */ - _d("Sending SIGTERM to service group %s", basename(svc->cmd)); - procname_kill(basename(svc->cmd), SIGTERM); - /* Restarting lost service. */ if (svc_enabled(svc, 0, NULL)) { + if (svc->restart_counter > RESPAWN_MAX) { + _e("Not restarting %s id %d, respawn MAX (%d) reached!", + svc->cmd, svc->id, RESPAWN_MAX); + break; + } + svc->restart_counter++; svc_start(svc); } @@ -807,16 +827,6 @@ int svc_start(svc_t *svc) return 0; } -int svc_start_by_name(char *name) -{ - svc_t *svc = svc_find(name); - - if (svc && svc_enabled(svc, 0, NULL)) - return svc_start(svc); - - return 1; -} - int svc_stop(svc_t *svc) { int res = 1; @@ -838,10 +848,13 @@ int svc_stop(svc_t *svc) if (runlevel != 1) print_desc("Stopping ", svc->desc); - _d("Sending SIGTERM to pid:%d name:'%s'", svc->pid, pid_get_name(svc->pid, NULL, 0)); + + _d("Sending SIGTERM to pid:%d name:%s", svc->pid, pid_get_name(svc->pid, NULL, 0)); res = kill(svc->pid, SIGTERM); + if (runlevel != 1) print_result(res); + svc->pid = 0; svc->restart_counter = 0; @@ -890,8 +903,11 @@ void svc_reload_dynamic(void) { svc_t *svc; + _d("Checking if any dynamically loaded services need to be reloaded ..."); for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { if (svc->mtime && svc->dirty > 0) { + _d("Service %s id %d modified, must stop-start for changes to take effect!", svc->cmd, svc->id); + svc_stop(svc); /* This will reset the restart_counter as well! */ svc_dance(svc); svc->dirty = 0; } diff --git a/svc.h b/svc.h index e8b83401..b641fdd7 100644 --- a/svc.h +++ b/svc.h @@ -61,6 +61,10 @@ typedef enum { * of issuing an initctl call. E.g. * initctl service */ typedef struct svc { + /* Instance specifics */ + int id; + + /* Service details */ pid_t pid; svc_type_t type; time_t mtime; /* Modification time for .conf from /etc/finit.d/ */ @@ -111,9 +115,9 @@ static inline svc_t *finit_svc_connect(void) return (svc_t *)ptr; } -svc_t *svc_new (void); +svc_t *svc_new (int id); int svc_del (svc_t *svc); -svc_t *svc_find (char *path); +svc_t *svc_find (char *path, int id); 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); @@ -124,7 +128,6 @@ 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);