mirror of
https://github.com/troglobit/finit.git
synced 2026-10-01 05:22:48 +07:00
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:
@@ -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
|
||||
------------
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user