/* This file is part of genrc Copyryght (C) 2018 Sergey Poznyakoff License GPLv3+: GNU GPL version 3 or later 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 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_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 str_to_int(char const *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", " -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:", " Read PID from the file ", "", " CONFIG:::", " Name of the PID file is stored in relation of the configuration", " file , written in language ", "", " GREP::s///[][;...]", " Grep for the first line in that matches . If found, process", " replace the matched portion according to and . Use", " the resulting string as PID. More sed expressions can be supplied", " separated with semicolons.", "", " PROC[:[][:]]", " Look for matching program in /proc//*. If is not supplied", " or empty, program name from --program will be used. 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:[:[][:]]", " Look for matching program in the output of 'ps -ef'. and ", " 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'); pos = strlen(usage_msg[0]) + 1; printf("%*s", pos, ""); j = 0; } else if (j > 0) putchar(' '); j++; printf("%s", usage_msg[i]); pos += len; } putchar('\n'); } static const char gplv3[] = "License GPLv3+: GNU GPL version 3 or later " "\n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n\n"; void version(void) { printf("%s\n", PACKAGE_STRING); printf("Copyryght (C) 2018 Sergey Poznyakoff\n"); printf("%s", gplv3); } typedef int (*GENRC_COMMAND)(void); struct comtab { char const *com_name; GENRC_COMMAND com_fun; }; static struct comtab comtab[] = { { "start", com_start }, { "stop", com_stop }, { "restart", com_restart }, { "reload", com_reload }, { "status", com_status }, { NULL, NULL } }; GENRC_COMMAND find_command(char const *s) { struct comtab *c; for (c = comtab; c->com_name; c++) if (strcmp(c->com_name, s) == 0) break; return c->com_fun; } 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 = 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 = 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; if (argc == 0) usage_error("missing command verb"); if (argc > 1) usage_error("too many arguments"); argv += optind; command = find_command(argv[0]); if (!command) usage_error("unrecognized command: %s", argv[0]); exit(command()); }