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 <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson
2015-05-31 01:58:20 +02:00
parent 687f52b0b5
commit cb075560ed
8 changed files with 89 additions and 109 deletions
+10
View File
@@ -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
------------
+1 -1
View File
@@ -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;
+11 -1
View File
@@ -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
+3 -65
View File
@@ -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;
}
-1
View File
@@ -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);
+6 -2
View File
@@ -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;
+52 -36
View File
@@ -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;
}
+6 -3
View File
@@ -61,6 +61,10 @@ typedef enum {
* of issuing an initctl call. E.g.
* initctl <stop|start|restart> 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);