mirror of
https://github.com/troglobit/finit.git
synced 2026-10-08 16:34:45 +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"
|
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
|
/etc/finit.d
|
||||||
------------
|
------------
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ static void parse_dynamic(char *line, time_t mtime)
|
|||||||
{
|
{
|
||||||
char *x;
|
char *x;
|
||||||
|
|
||||||
/* Skip comments. */
|
/* Skip comments, i.e. lines beginning with # */
|
||||||
if (MATCH_CMD(line, "#", x))
|
if (MATCH_CMD(line, "#", x))
|
||||||
return;
|
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
|
user admin
|
||||||
host testbed
|
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 time/tcp nowait [2345] internal -- UNIX rdate service
|
||||||
inetd ssh@eth0:222/tcp nowait [2345] /usr/sbin/sshd -i -- SSH 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
|
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)
|
if (ret)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return name;
|
if (name)
|
||||||
}
|
return name;
|
||||||
|
|
||||||
/**
|
return pname;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ int pid_alive (pid_t pid);
|
|||||||
char *pid_get_name (pid_t pid, char *name, size_t len);
|
char *pid_get_name (pid_t pid, char *name, size_t len);
|
||||||
|
|
||||||
void procname_set (char *name, char *args[]);
|
void procname_set (char *name, char *args[]);
|
||||||
int procname_kill (char *name, int signo);
|
|
||||||
|
|
||||||
void print (int action, const char *fmt, ...);
|
void print (int action, const char *fmt, ...);
|
||||||
void print_desc (char *action, char *desc);
|
void print_desc (char *action, char *desc);
|
||||||
|
|||||||
@@ -87,9 +87,13 @@ int plugin_unregister(plugin_t *plugin)
|
|||||||
TAILQ_REMOVE(&plugins, plugin, link);
|
TAILQ_REMOVE(&plugins, plugin, link);
|
||||||
|
|
||||||
if (plugin->svc.cb) {
|
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->cb = NULL;
|
||||||
svc->dynamic = 0;
|
svc->dynamic = 0;
|
||||||
svc->dynamic_stop = 0;
|
svc->dynamic_stop = 0;
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
#include "svc.h"
|
#include "svc.h"
|
||||||
#include "inetd.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
|
/* 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
|
* 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. */
|
||||||
@@ -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:
|
* 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;
|
int i;
|
||||||
|
|
||||||
@@ -73,6 +76,8 @@ svc_t *svc_new(void)
|
|||||||
|
|
||||||
if (svc->type == SVC_TYPE_FREE) {
|
if (svc->type == SVC_TYPE_FREE) {
|
||||||
memset(svc, 0, sizeof(*svc));
|
memset(svc, 0, sizeof(*svc));
|
||||||
|
svc->id = id;
|
||||||
|
|
||||||
return svc;
|
return svc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,12 +132,12 @@ svc_t *svc_iterator(int first)
|
|||||||
* Returns:
|
* Returns:
|
||||||
* A pointer to an &svc_t object, or %NULL if not found.
|
* 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;
|
svc_t *svc;
|
||||||
|
|
||||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
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);
|
_d("Found a matching svc for %s", path);
|
||||||
return svc;
|
return svc;
|
||||||
}
|
}
|
||||||
@@ -252,12 +257,12 @@ static void svc_dance(svc_t *svc)
|
|||||||
svc_cmd_t cmd = svc_enabled(svc, 0, NULL);
|
svc_cmd_t cmd = svc_enabled(svc, 0, NULL);
|
||||||
|
|
||||||
if (svc->pid) {
|
if (svc->pid) {
|
||||||
if (cmd == SVC_STOP)
|
if (SVC_STOP == cmd)
|
||||||
svc_stop(svc);
|
svc_stop(svc);
|
||||||
else if (cmd == SVC_RELOAD)
|
else if (SVC_RELOAD == cmd)
|
||||||
svc_reload(svc);
|
svc_reload(svc);
|
||||||
} else {
|
} else {
|
||||||
if (cmd == SVC_START)
|
if (SVC_START == cmd || SVC_RELOAD == cmd)
|
||||||
svc_start(svc);
|
svc_start(svc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,18 +341,27 @@ void svc_runlevel(int newlevel)
|
|||||||
* The @line can optionally start with a username, denoted by an @
|
* The @line can optionally start with a username, denoted by an @
|
||||||
* character. Like this:
|
* character. Like this:
|
||||||
*
|
*
|
||||||
* "service @username [!0-6,S] /path/to/daemon arg -- Description text"
|
* service @username [!0-6,S] /path/to/daemon arg -- Description
|
||||||
* "task @username [!0-6,S] /path/to/task arg -- Description text"
|
* task @username [!0-6,S] /path/to/task arg -- Description
|
||||||
* "run @username [!0-6,S] /path/to/cmd arg -- Description text"
|
* run @username [!0-6,S] /path/to/cmd arg -- Description
|
||||||
* "inetd tcp/ssh nowait [2345] @root:root /usr/sbin/sshd -i -- Description"
|
* inetd tcp/ssh nowait [2345] @root:root /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. The []
|
||||||
* Inetd services, launched on demand (SVC_TYPE_INETD)
|
* brackets denote the allowed runlevels, if left out the default for a
|
||||||
* The [] brackets are there to denote the allowed runlevels. Allowed
|
* service is set to [2-5]. Allowed runlevels mimic that of SysV init
|
||||||
* runlevels mimic that of SysV init with the addition of the 'S'
|
* with the addition of the 'S' runlevel, which is only run once at
|
||||||
* runlevel, which is only run once at startup. It can be seen as the
|
* startup. It can be seen as the system bootstrap. If a task or run
|
||||||
* system bootstrap. If a task or run command is listed in more than the
|
* command is listed in more than the [S] runlevel they will be called
|
||||||
* [S] runlevel they will be called when changing runlevel.
|
* 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:
|
* 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.
|
||||||
@@ -355,6 +369,7 @@ void svc_runlevel(int newlevel)
|
|||||||
int svc_register(int type, char *line, time_t mtime, char *username)
|
int svc_register(int type, char *line, time_t mtime, char *username)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
int id = 1; /* Default to ID:1 */
|
||||||
#ifndef INETD_DISABLED
|
#ifndef INETD_DISABLED
|
||||||
int forking = 0;
|
int forking = 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -392,6 +407,8 @@ int svc_register(int type, char *line, time_t mtime, char *username)
|
|||||||
username = &cmd[1];
|
username = &cmd[1];
|
||||||
else if (cmd[0] == '[') /* [runlevels] */
|
else if (cmd[0] == '[') /* [runlevels] */
|
||||||
runlevels = &cmd[0];
|
runlevels = &cmd[0];
|
||||||
|
else if (cmd[0] == '#') /* #ID */
|
||||||
|
id = atoi(&cmd[1]);
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -433,9 +450,10 @@ int svc_register(int type, char *line, time_t mtime, char *username)
|
|||||||
return inetd_allow(&svc->inetd, iface);
|
return inetd_allow(&svc->inetd, iface);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
svc = svc_find(cmd);
|
svc = svc_find(cmd, id);
|
||||||
if (!svc) {
|
if (!svc) {
|
||||||
svc = svc_new();
|
_d("Creating new svc for %s id #%d", cmd, id);
|
||||||
|
svc = svc_new(id);
|
||||||
if (!svc) {
|
if (!svc) {
|
||||||
_e("Out of memory, cannot register service %s", cmd);
|
_e("Out of memory, cannot register service %s", cmd);
|
||||||
return errno = ENOMEM;
|
return errno = ENOMEM;
|
||||||
@@ -635,12 +653,14 @@ void svc_monitor(pid_t lost)
|
|||||||
break;
|
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. */
|
/* Restarting lost service. */
|
||||||
if (svc_enabled(svc, 0, NULL)) {
|
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->restart_counter++;
|
||||||
svc_start(svc);
|
svc_start(svc);
|
||||||
}
|
}
|
||||||
@@ -807,16 +827,6 @@ int svc_start(svc_t *svc)
|
|||||||
return 0;
|
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 svc_stop(svc_t *svc)
|
||||||
{
|
{
|
||||||
int res = 1;
|
int res = 1;
|
||||||
@@ -838,10 +848,13 @@ int svc_stop(svc_t *svc)
|
|||||||
|
|
||||||
if (runlevel != 1)
|
if (runlevel != 1)
|
||||||
print_desc("Stopping ", svc->desc);
|
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);
|
res = kill(svc->pid, SIGTERM);
|
||||||
|
|
||||||
if (runlevel != 1)
|
if (runlevel != 1)
|
||||||
print_result(res);
|
print_result(res);
|
||||||
|
|
||||||
svc->pid = 0;
|
svc->pid = 0;
|
||||||
svc->restart_counter = 0;
|
svc->restart_counter = 0;
|
||||||
|
|
||||||
@@ -890,8 +903,11 @@ void svc_reload_dynamic(void)
|
|||||||
{
|
{
|
||||||
svc_t *svc;
|
svc_t *svc;
|
||||||
|
|
||||||
|
_d("Checking if any dynamically loaded services need to be reloaded ...");
|
||||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
||||||
if (svc->mtime && svc->dirty > 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_dance(svc);
|
||||||
svc->dirty = 0;
|
svc->dirty = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ typedef enum {
|
|||||||
* of issuing an initctl call. E.g.
|
* of issuing an initctl call. E.g.
|
||||||
* initctl <stop|start|restart> service */
|
* initctl <stop|start|restart> service */
|
||||||
typedef struct svc {
|
typedef struct svc {
|
||||||
|
/* Instance specifics */
|
||||||
|
int id;
|
||||||
|
|
||||||
|
/* Service details */
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
svc_type_t type;
|
svc_type_t type;
|
||||||
time_t mtime; /* Modification time for .conf from /etc/finit.d/ */
|
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;
|
return (svc_t *)ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
svc_t *svc_new (void);
|
svc_t *svc_new (int id);
|
||||||
int svc_del (svc_t *svc);
|
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_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);
|
||||||
@@ -124,7 +128,6 @@ 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_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_reload_dynamic(void);
|
||||||
|
|||||||
Reference in New Issue
Block a user