aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am11
-rw-r--r--configure.ac4
-rw-r--r--src/com_start.c10
-rw-r--r--src/genrc.858
-rw-r--r--src/genrc.c41
-rw-r--r--src/genrc.h10
-rw-r--r--src/sentinel.c190
7 files changed, 304 insertions, 20 deletions
diff --git a/Makefile.am b/Makefile.am
index 31e9e5f..dac3cb3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1 +1,12 @@
SUBDIRS = grecs src
+dist: ChangeLog
+.PHONY: ChangeLog
+ChangeLog:
+ $(AM_V_GEN)if test -d .git; then \
+ git log --pretty='format:%ct %an <%ae>%n%n%s%n%n%b%n' | \
+ awk -f $(top_srcdir)/@GRECS_SUBDIR@/build-aux/git2chg.awk \
+ > ChangeLog.tmp; \
+ cmp ChangeLog ChangeLog.tmp > /dev/null 2>&1 || \
+ mv ChangeLog.tmp ChangeLog; \
+ rm -f ChangeLog.tmp; \
+ fi
diff --git a/configure.ac b/configure.ac
index 5d36092..a568649 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,56 +1,56 @@
# This file is part of genrc. -*- Autoconf -*-
# Copyright (C) 2018 Sergey Poznyakoff.
#
# Genrc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# Genrc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with genrc. If not, see <http://www.gnu.org/licenses/>.
AC_PREREQ([2.69])
-AC_INIT([genrc], [1.0], [gray@gnu.org])
+AC_INIT([genrc], [1.0.90], [gray@gnu.org])
AC_CONFIG_SRCDIR([src/genrc.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([1.11 foreign silent-rules])
AC_CONFIG_MACRO_DIR(grecs/am)
# Enable silent rules by default
AM_SILENT_RULES([yes])
# Checks for programs.
AC_PROG_CC
AC_PROG_RANLIB
# Checks for libraries.
AC_CHECK_LIB(pcre, main)
# Checks for header files.
AC_CHECK_HEADERS([getopt.h pcre.h])
# Checks for library functions.
AC_CHECK_FUNCS([getdtablesize])
-GRECS_SETUP(grecs, [all-parsers])
+GRECS_SETUP(grecs, [all-parsers git2chg])
AM_CONDITIONAL([COND_PCRE],
[test "$ac_cv_header_pcre_h" = yes && test "$ac_cv_lib_pcre_main" = yes])
if test -f /proc/$$/cmdline; then
DEFAULT_PID_SOURCE='"PROC"'
else
DEFAULT_PID_SOURCE='"PS"'
fi
AC_DEFINE_UNQUOTED(DEFAULT_PID_SOURCE,$DEFAULT_PID_SOURCE,
[Default PID source])
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
diff --git a/src/com_start.c b/src/com_start.c
index 5744e39..3a9dffc 100644
--- a/src/com_start.c
+++ b/src/com_start.c
@@ -22,79 +22,83 @@ report_exec_error(int rc, char const *program)
#endif
genrc_error("%s terminated on signal %d%s",
program, WTERMSIG(rc), coremsg);
} else if (WIFSTOPPED(rc)) {
genrc_error("%s stopped on signal %d",
program, WSTOPSIG(rc));
} else {
genrc_error("%s terminated with unrecognized status: %d",
program, rc);
}
}
typedef void (*SIGHANDLER)(int);
void
sigchld(int sig)
{
}
int
timedwaitpid(pid_t pid, int *status)
{
struct timeval now, stoptime, ttw;
int rc = -1;
- SIGHANDLER oldsig;
+ struct sigaction act, oldact;
+
+ act.sa_handler = sigchld;
+ act.sa_flags = 0;
+ sigemptyset(&act.sa_mask);
+ sigaction(SIGCHLD, &act, &oldact);
- oldsig = signal(SIGCHLD, sigchld);
gettimeofday(&stoptime, NULL);
stoptime.tv_sec += genrc_timeout;
while (1) {
pid_t p;
p = waitpid(pid, status, WNOHANG);
if (p == pid) {
rc = 0;
break;
}
if (p < 0 && errno != EINTR) {
system_error(errno, "waitpid");
break;
}
gettimeofday(&now, NULL);
if (timercmp(&now, &stoptime, >=))
break;
timersub(&stoptime, &now, &ttw);
if (select(0, NULL, NULL, NULL, &ttw) < 0) {
if (errno != EINTR) {
system_error(errno, "select");
break;
}
}
}
- signal(SIGCHLD, oldsig);
+ sigaction(SIGCHLD, &oldact, NULL);
if (rc) {
kill(pid, SIGKILL);
}
return rc;
}
int
com_start(void)
{
pid_t pid;
int status;
PIDLIST pids;
char *p;
pidlist_init(&pids);
if (get_pid_list(genrc_pid_closure, &pids) == 0) {
int running = pids.pidc > 0;
pidlist_free(&pids);
if (running) {
genrc_error("%s is already running", genrc_program);
return 1;
}
}
diff --git a/src/genrc.8 b/src/genrc.8
index 00522ee..959a00e 100644
--- a/src/genrc.8
+++ b/src/genrc.8
@@ -1,62 +1,64 @@
.\" This file is part of genrc.
.\" Copyright (C) 2018 Sergey Poznyakoff.
.\"
.\" Genrc is free software; you can redistribute it and/or modify
.\" it under the terms of the GNU General Public License as published by
.\" the Free Software Foundation; either version 3, or (at your option)
.\" any later version.
.\"
.\" Genrc is distributed in the hope that it will be useful,
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
.\" GNU General Public License for more details.
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with genrc. If not, see <http://www.gnu.org/licenses/>.
-.TH GENRC 8 "May 17, 2018" "GENRC" "Genrc User Manual"
+.TH GENRC 8 "May 20, 2018" "GENRC" "Genrc User Manual"
.SH NAME
genrc \- generic system initialization script helper
.SH SYNOPSIS
.nh
.na
\fBgenrc\fR\
[\fB\-hv\fR]\
[\fB\-F\fR \fIPIDFILE\fR]\
[\fB\-P\fR \fISOURCE\fR]\
[\fB\-c\fR \fICOMMAND\fR]\
[\fB\-g\fR \fIGROUP\fR[,\fIGROUP\fR...]]\
[\fB\-p\fR \fIPROGRAM\fR]\
[\fB\-t\fR \fISECONDS\fR]\
[\fB\-u\fR \fIUSER\fR]\
[\fB\-\-command=\fICOMMAND\fR]\
[\fB\-\-create\-pidfile=\fIPIDFILE\fR]\
[\fB\-\-group=\fIGROUP\fR[,\fIGROUP\fR...]]\
[\fB\-\-help\fR]\
[\fB\-\-no\-reload\fR]\
[\fB\-\-pid\-from=\fISOURCE\fR]\
[\fB\-\-pidfile=\fIPIDFILE\fR]\
[\fB\-\-program=\fIPROGRAM\fR]\
+ [\fB\-\-restart\-on\-exit=\fR[\fB!\fR]\fISTATUS\fR[\fB,\fISTATUS\fR...]]\
+ [\fB\-\-restart\-on\-signal=\fR[\fB!\fR]\fISIG\fR[\fB,\fISIG\fR...]]\
[\fB\-\-sentinel\fR]\
[\fB\-\-signal\-reload=\fISIG\fR]\
[\fB\-\-signal\-stop=\fISIG\fR]\
[\fB\-\-timeout=\fISECONDS\fR]\
[\fB\-\-user=\fIUSER\fR]\
[\fB\-\-usage\fR]\
[\fB\-\-verbose\fR]\
{\
\fBstart\fR\
|\
\fBstop\fR\
|\
\fBrestart\fR\
|\
\fBreload\fR\
|\
\fBstatus\fR\
}
.ad
.hy
.SH DESCRIPTION
.B genrc
is a generic helper program for writing system initialization
scripts. Depending on the operation mode, it starts, stops,
@@ -80,48 +82,72 @@ If \fB\-\-program\fR is given, but \fB\-\-command\fR is not, its value
will be used as the command to run.
.PP
The program operation modes are:
.SS start
If given \fBstart\fR argument, \fBgenrc\fR runs the supplier
command. Before, it checks if the program is not already running and
refuses to start its second copy if so.
.PP
It is supposed that the program to be run will detach from the
controlling terminal and continue running in the background (i.e. it
is a \fIdaemon\fR, in UNIX sense). If it is not the case, use the
\fB\-\-sentinel\fR option. With this option, \fBgenrc\fR will start
the command and will become daemon itself, controlling the execution
of the program. It will exit when the command terminates. So long as
the command runs, \fBgenrc\fR will pipe its standard output and error
to syslog facility \fBdaemon\fR. The standard output will be logged
with the priority \fBinfo\fR and the error with the priority
\fBerr\fR.
.PP
If the \fB\-\-create\-pidfile=\fIFILENAME\fR option is given together with
\fB\-\-sentinel\fR, the PID of the subsidiary command will be stored
in \fIFILE\fR. The file will be unlinked after the subsidiary command
terminates. Unless the \fB\-\-pid\-from\fR option is given,
\fB\-\-pid\-from=FILE:\fIFILENAME\fR will be assumed.
+.PP
+In sentinel mode, it is possible to restart the program if it
+terminates with a specific exit code or on a specific signal. This is
+controlled by the \fB\-\-restart\-on\-exit\fR and
+\fB\-\-restart\-on\-signal\fR options. Use this feature to ensure the
+service provided by the program won't get terminated because of
+hitting a bug or encountering an unforeseen external condition. For
+example, the following two options will ensure that the program will
+be terminated only if it exits with status 0 or it is terminated by
+SIGTERM or SIGQUIT signal:
+.EX
+--restart-on-exit='!0' --restart-on-signal='!TERM,QUIT'
+.EE
+.PP
+If restarts are requested, \fBgenrc\fR will control how often it has
+to restart the program using the same algorithm as
+.B init (8).
+Namely, if the program is restarted more than 10 times within two
+minutes, \fBgenrc\fR will disable subsequent restarts for the next
+5 minutes. If the \fB\-\-create\-pidfile\fR option was used, the
+PID of the controlling \fBgenrc\fR process will be stored in the
+file during that interval. If the \fBSIGHUP\fR signal is delivered
+during the sleep interval, the sleep will be broken prematurely and
+the program restarted again.
.SS status
In \fBstatus\fR mode \fBgenrc\fR verifies if the \fICOMMAND\fR is
already running and outputs its status on the standard output. To this
effect, it uses an abstraction called \fIPID source\fR, which allows
it to determine the PID of the program by its name of command line.
.PP
The default PID source is the Linux \fB/proc\fR filesystem (or, if it
is not available, the output of \fBps -ef\fR), which is scanned for
the name of the program (given by \fB\-\-program\fR or
\fB\-\-command\fR options).
.PP
The source to use can be supplied with the \fB\-\-pid\-from\fR option
(or the \fB\-\-pidfile option, which is equivalent to
\fB\-\-pid\-from=FILE:\fR). See the section \fBPID SOURCES\fR for a
detailed discussion of available sources.
.SS stop
In the \fBstop\fR mode \fBgenrc\fR stops the command by sending it
\fBSIGTERM\fR (or another signal as supplied with the
\fB\-\-signal\-stop\fR option). If the PID source returns multiple
PIDs, by default only parent PID is selected. However, \fBgenrc\fR can
be instructed to signal all PIDs instead (see the \fBa\fR flag in the
description of \fBPROC\fR or \fBPS\fR PID source).
.PP
After sending the signal, the program will wait for all processes to
@@ -167,51 +193,79 @@ variable is set).
\fB\-F\fR, \fB\-\-pidfile=\fINAME\fR
Name of the PID file (same as \fB\-\-pid\-from=FILE:\fINAME\fR)
.TP
\fB\-h\fR, \fB\-\-help\fR
Display a short help list.
.TP
\fB\-g\fR, \fB\-\-group=\fIGROUP\fR[,\fIGROUP\fR...]
Run program with this \fIGROUP\fR privileges. If the argument is a
list of groups, the first group becomes the principal, and the
rest of them supplementary groups. Each \fIGROUP\fR is either a group
name or a numeric group number prefixed with a plus sign. Whatever
notation is used, it must exist in the system group database.
See also the \fB\-\-user\fR option.
.TP
\fB\-\-no\-reload\fR
Makes \fBreload\fR equivalent to \fBrestart\fR.
.TP
\fB\-p\fR, \fB\-\-program=\fIPROGRAM\fR
Name of the program to run.
.TP
\fB\-P\fR, \fB\-\-pid\-from=\fISOURCE\fR
Where to look for PIDs of the running programs.
.TP
+\fB\-\-restart\-on\-exit=\fR[\fB!\fR]\fISTATUS\fR[\fB,\fISTATUS\fR...]
+This option takes effect when used together with
+\fB\-\-sentinel\fR. If the program terminates with one of status
+codes listed as the argument to this option, it will be immediately
+restarted. The exclamation mark at the start of the list inverts the
+set, e.g. \fB\-\-restart\-on\-exit='!0,1'\fR means restart unless the
+program exit code is 0 or 1. Note the use of quotation to prevent the
+\fB!\fR from being interpreted by the shell.
+.TP
+\fB\-\-restart\-on\-signal=\fR[\fB!\fR]\fISIG\fR[\fB,\fISIG\fR...]
+This option takes effect when used together with
+\fB\-\-sentinel\fR. If the program terminates due to receiving one of
+the signals from this list, it will be immediately restarted. Each
+\fISIG\fR is either a signal number, or a signal name, as listed in
+.BR signal (7).
+The \fBSIG\fR prefix can be omitted from the signal name. Names are
+case-insensitive. Thus, \fB1\fR, \fBHUP\fR, \fBSIGHUP\fR, and
+\fBsighup\fR all stand for the same signal.
+.sp
+The exclamation mark at the start of the list complements the signal
+set, so that e.g. \fB\-\-restart\-on\-signal='!TERM,QUIT,INT'\fR will
+restart the program unless it terminates on one of the listed signals.
+.TP
\fB\-\-sentinel\fR
\fIPROGRAM\fR runs in foreground; disconnect from the controlling
-terminal, run it and act as a sentinel.
+terminal, start it and run in background until it terminates. The
+program's stdout and stderr are sent to the syslog facility
+\fBdaemon\fR, priorities \fBinfo\fR and \fBerr\fR, correspondingly.
+
+See the options \fB\-\-restart\-on\-exit\fR and
+\fB\-\-restart\-on\-signal\fR for details on how to restart the program.
.TP
\fB\-\-signal\-reload=\fISIG\fR
Signal to send on reload (default: \fBSIGHUP\fR). Setting it to 0 is
equivalent to \fB\-\-no\-reload\fR.
.TP
\fB\-\-signal\-stop=\fISIG\fR
Signal to send in order to terminate the program (default:
\fBSIGTERM\fR).
.TP
\fB\-t\fR, \fB\-\-timeout=\fISECONDS\fR
Time to wait for the program to start up or terminate.
.TP
\fB\-\-usage\fR
Display a short usage summary.
.TP
\fB\-u\fR, \fB\-\-user=\fIUSER\fR
Run with this user privileges. The argument is either a login
name or a numeric UID prefixed with the plus sign. Whatever form is
used, it must correspond to a valid user from the system user
database.
Unless \fB\-\-group\fR option is also given, the primary and
supplementary groups of \fIUSER\fR will be used.
.TP
diff --git a/src/genrc.c b/src/genrc.c
index ae3070d..9052987 100644
--- a/src/genrc.c
+++ b/src/genrc.c
@@ -4,68 +4,72 @@ License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
*/
#include "genrc.h"
#include <sys/ioctl.h>
char *genrc_command;
char *genrc_program;
char *genrc_pid_from;
unsigned genrc_timeout = 5;
int genrc_no_reload;
int genrc_signal_stop = SIGTERM;
int genrc_signal_reload = SIGHUP;
GENRC_PID_CLOSURE *genrc_pid_closure;
char *genrc_create_pidfile;
int genrc_verbose;
enum {
OPT_USAGE = 256,
OPT_VERSION,
OPT_SIGNAL_RELOAD,
OPT_NO_RELOAD,
OPT_SIGNAL_STOP,
- OPT_CREATE_PIDFILE
+ OPT_CREATE_PIDFILE,
+ OPT_RESTART_ON_EXIT,
+ OPT_RESTART_ON_SIGNAL,
};
struct option longopts[] = {
{ "help", no_argument, 0, 'h' },
{ "usage", no_argument, 0, OPT_USAGE },
{ "command", required_argument, 0, 'c' },
{ "program", required_argument, 0, 'p' },
{ "pid-from", required_argument, 0, 'P' },
{ "pidfile", required_argument, 0, 'F' },
{ "timeout", required_argument, 0, 't' },
{ "signal-reload", required_argument, 0, OPT_SIGNAL_RELOAD },
{ "no-reload", no_argument, 0, OPT_NO_RELOAD },
{ "signal-stop", required_argument, 0, OPT_SIGNAL_STOP },
{ "sentinel", no_argument, 0, 'S' },
{ "create-pidfile", required_argument, 0, OPT_CREATE_PIDFILE },
{ "version", no_argument, 0, OPT_VERSION },
{ "verbose", no_argument, 0, 'v' },
{ "user", required_argument, 0, 'u' },
{ "group", required_argument, 0, 'g' },
+ { "restart-on-exit", required_argument, 0, OPT_RESTART_ON_EXIT },
+ { "restart-on-signal", required_argument, 0, OPT_RESTART_ON_SIGNAL },
{ NULL }
};
char shortopts[] = "c:hF:g:P:p:St:u:v";
struct sigdefn {
char const *sig_name;
int sig_no;
};
#define S(s) { #s, s }
static struct sigdefn sigdefn[] = {
S (SIGHUP),
S (SIGINT),
S (SIGQUIT),
S (SIGILL),
S (SIGTRAP),
S (SIGABRT),
S (SIGIOT),
S (SIGBUS),
S (SIGFPE),
S (SIGKILL),
S (SIGUSR1),
S (SIGSEGV),
S (SIGUSR2),
@@ -106,115 +110,130 @@ static struct sigdefn sigdefn[] = {
S (SIGIO),
#endif
#ifdef SIGPWR
S (SIGPWR),
#endif
#ifdef SIGSYS
S (SIGSYS),
#endif
{NULL}
};
#undef S
static int
is_numeric_str(char const *s)
{
while (*s) {
if (!isdigit(*s))
return 0;
s++;
}
return 1;
}
int
-sig_name_to_str(char const *s)
+str_to_int(char const *s)
{
- if (is_numeric_str(s)) {
char *end;
unsigned long n;
errno = 0;
n = strtoul(s, &end, 10);
if (errno || *end || n > UINT_MAX)
return -1;
return n;
+}
+
+int
+str_to_sig(char const *s)
+{
+ if (is_numeric_str(s)) {
+ return str_to_int(s);
} else {
struct sigdefn *sd;
for (sd = sigdefn; sd->sig_name; sd++) {
if (s[0] == 's' || s[0] == 'S') {
if (strcasecmp(sd->sig_name, s) == 0)
return sd->sig_no;
} else if (strcasecmp(sd->sig_name + 3, s) == 0)
return sd->sig_no;
}
}
return -1;
}
char const *help_msg[] = {
"Usage: genrc [OPTIONS] COMMAND",
"generic system initialization script helper\n",
"COMMANDs are:\n",
" start start the program",
" stop stop the program",
" restart restart the running program",
" reload send a reload signal to the program",
" status display the program status",
"",
"OPTIONs are:",
"",
"Command to run:",
"",
" -c, --command=COMMAND command line to run",
" -p, --program=PROGRAM name of the program to run",
"",
" At least one of COMMAND or PROGRAM must be given.",
" If PROGRAM is supplied, but COMMAND is not, COMMAND is set to PROGRAM",
" Otherwise, if COMMAND is set, but PROGRAM is not, PROGRAM is set to the",
" first word (in the shell sense) in COMMAND.",
"",
"Runtime privileges:",
"",
" -u, --user=NAME run with this user privileges",
" -g, --group=GROUP[,GROUP...]]",
" run with this group(s) privileges",
"",
"Additional configuration:",
"",
" -t, --timeout=SECONDS time to wait for the program to start up or",
" terminate",
- " --sentinel PROGRAM runs in foreground; disconnect from the",
- " controlling terminal, run it and act as a sentinel",
" -P, --pid-from=SOURCE where to look for PIDs of the running programs",
" -F, --pidfile=NAME name of the PID file",
" (same as --pid-from=FILE:NAME)",
" --signal-reload=SIG signal to send on reload (default: SIGHUP)",
" setting to 0 is equivalent to --no-reload",
" --no-reload makes reload equivalent to restart",
" --signal-stop=SIG signal to send in order to terminate the program",
" (default: SIGTERM)",
"",
+ "Sentinel mode:",
+ "",
+ " --sentinel PROGRAM runs in foreground; disconnect from the",
+ " controlling terminal, run it and act as a sentinel",
+ " --restart-on-exit=[!]CODE[,...]",
+ " restart the program if it exits with one of the",
+ " listed status codes",
+ " --restart-on-signal=[!]SIG[,...]",
+ " restart the program if it terminates on one of the",
+ " listed signals",
+ "",
"Informational options:",
"",
" -h, --help display this help list",
" --usage display short usage information",
" --version display program version and exist",
"",
"Influential environment variables and corresponding options:",
"",
" GENRC_COMMAND=COMMAND --command=COMMAND",
" GENRC_PROGRAM=NAME --program=NAME",
" GENRC_PID_FROM=SOURCE --pid-from=SOURCE",
" GENRC_TIMEOUT=SECONDS --timeout=SECONDS",
" GENRC_SENTINEL=1 --sentinel",
" GENRC_USER=NAME --user=NAME",
" GENRC_GROUP=GROUPS --group=GROUPS",
"",
"",
"PID sources:",
"",
" FILE:<NAME>",
" Read PID from the file <NAME>",
"",
" CONFIG:<LANG>:<FILENAME>:<FQRN>",
" Name of the PID file is stored in relation <FQRN> of the configuration",
@@ -251,48 +270,50 @@ help(void)
{
int i;
for (i = 0; help_msg[i]; i++)
puts(help_msg[i]);
}
char const *usage_msg[] = {
"genrc",
"[-h]",
"[-F PIDFILE]",
"[-P SOURCE]",
"[-c COMMAND]",
"[-g GROUP[,GROUP...]]",
"[-p PROGRAM]",
"[-t SECONDS]",
"[-u USER]",
"[--command=COMMAND]",
"[--group GROUP[,GROUP...]]",
"[--help]",
"[--no-reload]",
"[--pid-from=SOURCE]",
"[--pidfile=PIDFILE]",
"[--program=PROGRAM]",
+ "[--restart-on-exit=[!]CODE[,...]]",
+ "[--restart-on-signal=[!]SIG[,...]]",
"[--sentinel]",
"[--signal-reload=SIG]",
"[--signal-stop=SIG]",
"[--timeout=SECONDS]",
"[--usage]",
"[--user=USER]",
"{",
"start",
"|",
"stop",
"|",
"restart",
"|",
"reload",
"|",
"status",
"}",
NULL
};
static int
screen_width(void)
{
struct winsize ws;
@@ -401,85 +422,91 @@ main(int argc, char **argv)
case 'p':
setenv("GENRC_PROGRAM", optarg, 1);
break;
case 'P':
setenv("GENRC_PID_FROM", optarg, 1);
break;
case 'F':
p = xmalloc(6 + strlen(optarg));
strcat(strcpy(p, "FILE:"), optarg);
setenv("GENRC_PID_FROM", p, 1);
free(p);
break;
case 'g':
setenv("GENRC_GROUP", optarg, 1);
break;
case OPT_CREATE_PIDFILE:
setenv("GENRC_CREATE_PIDFILE", optarg, 1);
break;
case 't':
setenv("GENRC_TIMEOUT", optarg, 1);
break;
case 'S':
setenv("GENRC_SENTINEL", "1", 1);
break;
+ case OPT_RESTART_ON_EXIT:
+ add_restart_condition(RESTART_ON_EXIT, optarg);
+ break;
+ case OPT_RESTART_ON_SIGNAL:
+ add_restart_condition(RESTART_ON_SIGNAL, optarg);
+ break;
case OPT_NO_RELOAD:
no_reload = 1;
break;
case OPT_SIGNAL_RELOAD:
setenv("GENRC_SIGNAL_RELOAD", optarg, 1);
break;
case OPT_SIGNAL_STOP:
setenv("GENRC_SIGNAL_STOP", optarg, 1);
break;
case 'u':
setenv("GENRC_USER", optarg, 1);
break;
case 'v':
genrc_verbose++;
break;
default:
exit(1);
}
}
if ((p = getenv("GENRC_COMMAND")) != NULL)
genrc_command = p;
if ((p = getenv("GENRC_PROGRAM")) != NULL)
genrc_program = p;
if (no_reload)
genrc_no_reload = 1;
else if ((p = getenv("GENRC_SIGNAL_RELOAD")) != NULL) {
- genrc_signal_reload = sig_name_to_str(p);
+ genrc_signal_reload = str_to_sig(p);
if (genrc_signal_reload == -1)
usage_error("%s: invalid signal number", p);
else if (genrc_signal_reload == 0)
genrc_no_reload = 1;
}
if ((p = getenv("GENRC_SIGNAL_STOP")) != NULL) {
- genrc_signal_stop = sig_name_to_str(p);
+ genrc_signal_stop = str_to_sig(p);
if (genrc_signal_stop <= 0)
usage_error("%s: invalid signal number", p);
}
if ((p = getenv("GENRC_TIMEOUT")) != NULL) {
char *end;
unsigned long n;
errno = 0;
n = strtoul(p, &end, 10);
if (errno || *p || n > UINT_MAX)
usage_error("%s: invalid timeout", p);
if (n == 0)
genrc_no_reload = 1;
else
genrc_timeout = n;
}
if (!genrc_command) {
if (genrc_program) {
genrc_command = xstrdup(genrc_program);
} else {
usage_error("either --command (GENRC_COMMAND) or --program (GENRC_PROGRAM) must be given");
}
} else if (!genrc_program) {
diff --git a/src/genrc.h b/src/genrc.h
index 9842016..c6ee57b 100644
--- a/src/genrc.h
+++ b/src/genrc.h
@@ -42,94 +42,104 @@ char *transform_string(TRANSFORM tf, const char *input);
char *transform_string_if_match(TRANSFORM tf, const char *input);
TRANSFORM compile_transform_expr(const char *expr, int cflags);
struct pidlist {
pid_t *pidv;
size_t pidc;
size_t pidn;
};
typedef struct pidlist PIDLIST;
void pidlist_init(PIDLIST *);
void pidlist_free(PIDLIST *);
void pidlist_clear(PIDLIST *plist);
void pidlist_add(PIDLIST *, pid_t);
ssize_t pidlist_index(PIDLIST *plist, pid_t p);
int pidlist_member(PIDLIST *plist, pid_t p);
int pidlist_remove(PIDLIST *plist, size_t i);
void pidlist_kill(PIDLIST *plist, int sig);
pid_t strtopid(char const *str);
int pid_is_running(pid_t pid);
void runas(void);
+int str_to_sig(char const *);
+int str_to_int(char const *);
+
enum {
MATCH_REGEX, /* extended POSIX regexp match (default) */
MATCH_PCRE, /* PCRE match (not implemented) */
MATCH_GLOB, /* glob pattern match */
MATCH_EXACT, /* exact match */
};
#define MATCH_DEFAULT MATCH_REGEX
enum {
PROCF_ICASE = 0x01, /* ignore case */
PROCF_ALL = 0x02, /* use all pids */
PROCF_CMDLINE = 0x04, /* match against entire command line */
PROCF_EXE = 0x08 /* match against executable name */
};
#define PROCF_DEFAULT 0
struct procscanbuf {
int match; /* match type */
int flags; /* match flags */
void *pattern;
};
typedef struct procscanbuf *PROCSCANBUF;
PROCSCANBUF procscan_init(char const *pattern, char const *flagstr);
void procscan_free(PROCSCANBUF buf);
int procscan_match(PROCSCANBUF buf, char const *arg);
void match_exact_init(PROCSCANBUF buf, char const *pattern);
void match_exact_free(PROCSCANBUF buf);
int match_exact(PROCSCANBUF buf, char const *arg);
void match_glob_init(PROCSCANBUF buf, char const *pattern);
void match_glob_free(PROCSCANBUF buf);
int match_glob(PROCSCANBUF buf, char const *arg);
void match_regex_init(PROCSCANBUF buf, char const *pattern);
void match_regex_free(PROCSCANBUF buf);
int match_regex(PROCSCANBUF buf, char const *arg);
void match_pcre_init(PROCSCANBUF buf, char const *pattern);
void match_pcre_free(PROCSCANBUF buf);
int match_pcre(PROCSCANBUF buf, char const *arg);
+enum {
+ RESTART_ON_EXIT,
+ RESTART_ON_SIGNAL
+};
+
+void add_restart_condition(int type, char const *arg);
+
struct genrc_pid_closure {
char const *name;
int (*pid)(struct genrc_pid_closure *, PIDLIST *);
};
typedef struct genrc_pid_closure GENRC_PID_CLOSURE;
GENRC_PID_CLOSURE *get_pid_closure(char const *str);
int get_pid_list(GENRC_PID_CLOSURE *, PIDLIST *);
GENRC_PID_CLOSURE *genrc_pid_file_init(int argc, char **argv);
GENRC_PID_CLOSURE *genrc_pid_config_init(int argc, char **argv);
GENRC_PID_CLOSURE *genrc_pid_grep_init(int argc, char **argv);
GENRC_PID_CLOSURE *genrc_pid_proc_init(int argc, char **argv);
GENRC_PID_CLOSURE *genrc_pid_ps_init(int argc, char **argv);
extern char *genrc_command;
extern char *genrc_program;
extern char *genrc_pid_from;
extern unsigned genrc_timeout;
extern int genrc_no_reload;
extern int genrc_signal_reload;
extern int genrc_signal_stop;
extern GENRC_PID_CLOSURE *genrc_pid_closure;
diff --git a/src/sentinel.c b/src/sentinel.c
index 59b89cc..33d3e06 100644
--- a/src/sentinel.c
+++ b/src/sentinel.c
@@ -1,155 +1,254 @@
/* This file is part of genrc
Copyryght (C) 2018 Sergey Poznyakoff
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
*/
#include "genrc.h"
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
+#include <time.h>
static void
xpipe(int p[2])
{
if (pipe(p)) {
system_error(errno, "pipe");
exit(1);
}
}
#define max(a,b) ((a) > (b) ? (a) : (b))
static void
write_pid_file(pid_t pid)
{
if (genrc_create_pidfile) {
FILE *fp;
fp = fopen(genrc_create_pidfile, "w");
fprintf(fp, "%lu\n", (unsigned long)pid);
fclose(fp);
}
}
+static void
+unlink_pid_file(void)
+{
+ if (genrc_create_pidfile)
+ unlink(genrc_create_pidfile);
+}
+
#define LOGBUFSIZE 1024
struct log_buffer {
char buf[LOGBUFSIZE];
size_t pos;
int prio;
};
static void
log_buffer_init(struct log_buffer *lb, int prio)
{
lb->buf[sizeof(lb->buf) - 1] = 0;
lb->pos = 0;
lb->prio = prio;
}
static void
log_buffer_read(int fd, struct log_buffer *lb)
{
int rc;
rc = read(fd, lb->buf + lb->pos, 1);
if (rc == -1) {
syslog(LOG_ERR, "read: %m");
} else if (rc == 1) {
if (lb->pos == sizeof(lb->buf)-1) {
syslog(lb->prio, "%s", lb->buf);
lb->pos = 0;
} else if (lb->buf[lb->pos] == '\n') {
lb->buf[lb->pos] = 0;
syslog(lb->prio, "%s", lb->buf);
lb->pos = 0;
} else
lb->pos++;
}
}
+struct restart_cond {
+ struct restart_cond *next;
+ int type;
+ int negate;
+ int numc;
+ int numv[1];
+};
+
+struct restart_cond *restart_head, *restart_tail;
+
+static int
+restart_on(int type, int num)
+{
+ struct restart_cond *cond;
+
+ for (cond = restart_head; cond; cond = cond->next) {
+ if (cond->type == type) {
+ int result = cond->negate;
+ int i;
+ for (i = 0; i < cond->numc; i++) {
+ if (cond->numv[i] == num) {
+ result = !result;
+ break;
+ }
+ }
+ if (result)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+typedef int (*RESTART_STON)(char const *);
+
+static RESTART_STON restart_ston[] = { str_to_int, str_to_sig };
+static char const *restart_what[] = { "exit status", "signal" };
+
+void
+add_restart_condition(int type, char const *arg)
+{
+ struct wordsplit ws;
+ size_t i;
+ int negate = 0;
+ struct restart_cond *cond;
+ RESTART_STON ston = restart_ston[type];
+
+ if (arg[0] == '!') {
+ negate = 1;
+ arg++;
+ }
+
+ ws.ws_delim = ",";
+ ws.ws_error = genrc_error;
+ if (wordsplit(arg, &ws,
+ WRDSF_NOCMD
+ | WRDSF_NOVAR
+ | WRDSF_DELIM
+ | WRDSF_ENOMEMABRT
+ | WRDSF_SHOWERR
+ | WRDSF_ERROR))
+ exit(1);
+
+ if (ws.ws_wordc == 0)
+ usage_error("empty restart condition");
+
+ cond = xmalloc(sizeof(*cond)
+ + (ws.ws_wordc - 1) * sizeof(cond->numv[0]));
+ cond->next = NULL;
+ cond->type = type;
+ cond->negate = negate;
+ cond->numc = ws.ws_wordc;
+ for (i = 0; i < ws.ws_wordc; i++) {
+ int n = ston(ws.ws_wordv[i]);
+ if (n == -1)
+ usage_error("bad %s: %s", restart_what[type],
+ ws.ws_wordv[i]);
+ cond->numv[i] = n;
+ }
+
+ if (restart_tail)
+ restart_tail->next = cond;
+ else
+ restart_head = cond;
+ restart_tail = cond;
+}
+
void
wait_loop(pid_t child, int out, int err)
{
fd_set rdset;
int nfd = (out > err ? out : err) + 1;
struct log_buffer obuf, ebuf;
openlog(genrc_program, LOG_PID, LOG_DAEMON);
log_buffer_init(&obuf, LOG_INFO);
log_buffer_init(&ebuf, LOG_ERR);
while (1) {
int rc, status;
if (waitpid(child, &status, WNOHANG) == child) {
- if (genrc_create_pidfile)
- unlink(genrc_create_pidfile);
+ write_pid_file(getpid());
if (WIFEXITED(status)) {
+ int code = WEXITSTATUS(status);
syslog(LOG_INFO, "%s exited with status %d",
- genrc_program, WEXITSTATUS(status));
- _exit(WEXITSTATUS(status));
+ genrc_program, code);
+ if (restart_on(RESTART_ON_EXIT, code))
+ return;
} else if (WIFSIGNALED(status)) {
char const *coremsg = "";
+ int sig = WTERMSIG(status);
#ifdef WCOREDUMP
if (WCOREDUMP(status))
coremsg = " (core dumped)";
#endif
syslog(LOG_INFO, "%s terminated on signal %d%s",
- genrc_program, WTERMSIG(status), coremsg);
+ genrc_program, sig, coremsg);
+ if (restart_on(RESTART_ON_SIGNAL, sig))
+ return;
} else if (WIFSTOPPED(status)) {
syslog(LOG_INFO, "%s stopped on signal %d",
genrc_program, WSTOPSIG(status));
} else {
syslog(LOG_INFO, "%s terminated; status %d",
genrc_program, rc);
}
break;
}
FD_ZERO(&rdset);
FD_SET(out, &rdset);
FD_SET(err, &rdset);
rc = select(nfd, &rdset, NULL, NULL, NULL);
if (rc == -1) {
if (errno == EINTR)
continue;
syslog(LOG_CRIT, "select: %m");
syslog(LOG_CRIT, "killing %lu and terminating",
(unsigned long) child);
kill(child, SIGKILL);
break;
}
if (FD_ISSET(out, &rdset)) {
log_buffer_read(out, &obuf);
}
if (FD_ISSET(err, &rdset)) {
log_buffer_read(err, &ebuf);
}
}
- _exit(1);
+ unlink_pid_file();
+ _exit(0);
}
pid_t
start_command(int p[])
{
int errpipe[2], outpipe[2];
pid_t pid;
xpipe(errpipe);
xpipe(outpipe);
pid = fork();
if (pid == -1) {
system_error(errno, "pipe");
return -1;
}
if (pid == 0) {
char *argv[] = { SHELL, "-c", NULL, NULL };
int i;
runas();