aboutsummaryrefslogtreecommitdiff
path: root/src/genrc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/genrc.c')
-rw-r--r--src/genrc.c41
1 files changed, 34 insertions, 7 deletions
diff --git a/src/genrc.c b/src/genrc.c
index ae3070d..9052987 100644
--- a/src/genrc.c
+++ b/src/genrc.c
@@ -1,322 +1,343 @@
/* 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 <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),
S (SIGPIPE),
S (SIGALRM),
S (SIGTERM),
#ifdef SIGSTKFLT
S (SIGSTKFLT),
#endif
S (SIGCHLD),
S (SIGCONT),
S (SIGSTOP),
S (SIGTSTP),
S (SIGTTIN),
S (SIGTTOU),
#ifdef SIGURG
S (SIGURG),
#endif
#ifdef SIGXCPU
S (SIGXCPU),
#endif
#ifdef SIGXFSZ
S (SIGXFSZ),
#endif
#ifdef SIGVTALRM
S (SIGVTALRM),
#endif
#ifdef SIGPROF
S (SIGPROF),
#endif
#ifdef SIGWINCH
S (SIGWINCH),
#endif
#ifdef SIGPOLL
S (SIGPOLL),
#endif
#ifdef SIGIO
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",
" file <FILENAME>, written in language <LANG>",
"",
" GREP:<FILE>:s/<RX>/<REPL>/[<FLAGS>][;...]",
" Grep for the first line in <FILE> that matches <RX>. If found, process",
" replace the matched portion according to <REPL> and <FLAGS>. Use",
" the resulting string as PID. More sed expressions can be supplied",
" separated with semicolons.",
"",
" PROC[:[<EXE>][:<FLAGS>]]",
" Look for matching program in /proc/<PID>/*. If <EXE> is not supplied",
" or empty, program name from --program will be used. <FLAGS> are:",
" e exact match",
" g glob pattern match",
" x extended POSIX regexp match (default)",
" p PCRE match",
" i case-insensitive match",
"",
" c match entire command line",
" r match real executable name (instead of argv0)",
" a signal all matching PIDs",
"",
" PS:[:[<EXE>][:<FLAGS>]]",
" Look for matching program in the output of 'ps -ef'. <EXE> and <FLAGS>",
" as described above",
"",
NULL
};
void
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;
ws.ws_col = ws.ws_row = 0;
if ((ioctl(1, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0) {
char *p = getenv("COLUMNS");
if (p)
ws.ws_col = atoi(p);
if (ws.ws_col == 0)
ws.ws_col = 80;
}
return ws.ws_col;
}
void
usage(void)
{
int i, j, pos = 0;
int width = screen_width();
if (width > 4)
width -= 4;
for (i = j = 0; usage_msg[i]; i++) {
int len = strlen(usage_msg[i]);
if (j > 0)
len++;
if (pos + len > width) {
putchar('\n');
@@ -377,133 +398,139 @@ int
main(int argc, char **argv)
{
int c;
char *p;
int no_reload = 0;
GENRC_COMMAND command;
setprogname(argv[0]);
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL))
!= EOF) {
switch (c) {
case 'h':
help();
exit(0);
case OPT_USAGE:
usage();
exit(0);
case OPT_VERSION:
version();
exit(0);
case 'c':
setenv("GENRC_COMMAND", optarg, 1);
break;
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) {
struct wordsplit ws;
ws.ws_error = genrc_error;
if (wordsplit(genrc_command, &ws,
WRDSF_NOCMD|WRDSF_NOVAR|
WRDSF_ENOMEMABRT|WRDSF_SHOWERR|WRDSF_ERROR))
exit(1);
genrc_program = xstrdup(ws.ws_wordv[0]);
wordsplit_free(&ws);
}
genrc_create_pidfile = getenv("GENRC_CREATE_PIDFILE");
if ((p = getenv("GENRC_PID_FROM")) != NULL) {
genrc_pid_closure = get_pid_closure(p);
} else if (genrc_create_pidfile) {
p = xmalloc(6 + strlen(genrc_create_pidfile));
strcat(strcpy(p, "FILE:"), genrc_create_pidfile);
genrc_pid_closure = get_pid_closure(p);
free(p);
} else {
genrc_pid_closure = get_pid_closure(DEFAULT_PID_SOURCE);
}
argc -= optind;

Return to:

Send suggestions and report system problems to the System administrator.