diff options
Diffstat (limited to 'src/genrc.c')
-rw-r--r-- | src/genrc.c | 218 |
1 files changed, 210 insertions, 8 deletions
diff --git a/src/genrc.c b/src/genrc.c index 3dccbda..c6ebcaf 100644 --- a/src/genrc.c +++ b/src/genrc.c @@ -1,37 +1,43 @@ /* This file is part of genrc -Copyryght (C) 2018, 2019 Sergey Poznyakoff +Copyryght (C) 2018-2022 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 <syslog.h> #include <sys/ioctl.h> +#include <sys/resource.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; +enum genrc_kill_mode genrc_kill_mode; GENRC_PID_CLOSURE *genrc_pid_closure; char *genrc_create_pidfile; int genrc_verbose; char *genrc_shell = SHELL; - +int genrc_log_facility = LOG_DAEMON; +char *genrc_log_tag = NULL; 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, + OPT_LOG_FACILITY, + OPT_LOG_TAG }; struct option longopts[] = { { "help", no_argument, 0, 'h' }, { "usage", no_argument, 0, OPT_USAGE }, { "command", required_argument, 0, 'c' }, @@ -49,12 +55,16 @@ struct option longopts[] = { { "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 }, + { "log-facility", required_argument, 0, OPT_LOG_FACILITY }, + { "log-tag", required_argument, 0, OPT_LOG_TAG }, + { "limit", required_argument, 0, 'l' }, + { "kill-mode", required_argument, 0, 'k' }, { NULL } }; char shortopts[2*sizeof(longopts)/sizeof(longopts[0])]; static void shortopts_setup(void) @@ -203,44 +213,67 @@ char const *help_msg[] = { "", " 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:", + "Runtime privileges and limits:", "", " -u, --user=NAME run with this user privileges", " -g, --group=GROUP[,GROUP...]]", " run with this group(s) privileges", + " -l, --limit=LIM set resource limit; LIM is a resource letter", + " followed by the resource value; resource letters are:", + " c core file size (KB)", + " d data size (KB)", + " f file size (KB)", + " l locked-in-memory address space (KB)", + " m resident set size (KB)", + " n number of open files", + " p process priority -20..20", + " s stack size (KB)", + " t CPU time (seconds)", + " u number of subprocesses", + " v virtual memory (KB)", "", "Additional configuration:", "", + " -k, --kill-mode=MODE set program termination mode: group, program,", + " or mixed", " -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)", + " -v. --verbose enable verbose diagnostics", "", "Sentinel mode:", "", - " --sentinel PROGRAM runs in foreground; disconnect from the", + " -S, --sentinel PROGRAM runs in foreground; disconnect from the", " controlling terminal, run it and act as a sentinel", + " --create-pidfile=FILE", + " store PID of the PROGRAM in FILE; implies", + " --pid-from=FILE:FILENAME, unless --pid-from is also", + " given", " -s, --shell=SHELL use SHELL instead of /bin/sh;", " --shell=none to exec PROGRAM directly", " -e, --exec same as --shell=none", " --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", + " --log-facility=FACILITY", + " log diagnostic messages to this syslog facility", + " --log-tag=TAG use this syslog tag instead of the program name", "", "Informational options:", "", " -h, --help display this help list", " --usage display short usage information", " --version display program version and exist", @@ -251,12 +284,15 @@ char const *help_msg[] = { " 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", + " GENRC_LOG_FACILITY=F --log-facility=F", + " GENRC_LOG_TAG=STR --log-tag=STR", + " GENRC_KILL_MODE=MODE --kill-mode=MODE", "", "", "PID sources:", "", " FILE:<NAME>", " Read PID from the file <NAME>", @@ -281,14 +317,14 @@ char const *help_msg[] = { " 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>", + " PS[:[<EXE>][:<FLAGS>]]", + " Look for matching program in the output of 'ps -efw'. <EXE> and <FLAGS>", " as described above", "", NULL }; void @@ -304,19 +340,25 @@ char const *usage_msg[] = { "genrc", "[-hev]", "[-F PIDFILE]", "[-P SOURCE]", "[-c COMMAND]", "[-g GROUP[,GROUP...]]", + "[-k MODE]", + "[-l LIM]", "[-p PROGRAM]", "[-s SHELL]", "[-t SECONDS]", "[-u USER]", "[--command=COMMAND]", "[--group GROUP[,GROUP...]]", "[--help]", + "[--kill-mode=MODE]", + "[--limit=LIM]", + "[--log-facility=FACILITY]", + "[--log-tag=TAG]", "[--no-reload]", "[--pid-from=SOURCE]", "[--pidfile=PIDFILE]", "[--program=PROGRAM]", "[--restart-on-exit=[!]CODE[,...]]", "[--restart-on-signal=[!]SIG[,...]]", @@ -354,12 +396,90 @@ screen_width(void) if (ws.ws_col == 0) ws.ws_col = 80; } return ws.ws_col; } +static struct { + int letter; + int resource; + int is_set; + long val; +} resource_tab[] = { + { 'c', RLIMIT_CORE }, + { 'd', RLIMIT_DATA }, + { 'f', RLIMIT_FSIZE }, + { 'l', RLIMIT_MEMLOCK }, + { 'm', RLIMIT_RSS }, + { 'n', RLIMIT_NOFILE }, + { 's', RLIMIT_STACK }, + { 't', RLIMIT_CPU }, + { 'u', RLIMIT_NPROC }, + { 'v', RLIMIT_AS }, + { 'p', 0 } +}; + +static int +find_resource(int letter) +{ + int i; + for (i = 0; i < sizeof(resource_tab)/sizeof(resource_tab[0]); i++) + if (letter == resource_tab[i].letter) + return resource_tab[i].resource; + return -1; +} + +void +set_limit(char *arg) +{ + int r; + unsigned long v; + char *p; + struct rlimit rlim; + + if ((r = find_resource(arg[0])) == -1) { + usage_error("%s: unrecognized resource code", arg); + } + if ((v = strtoul(arg + 1, &p, 10)) == ULONG_MAX && errno == ERANGE) { + usage_error("%s: resource value out of range", arg); + } + if (*p) { + usage_error("%s: value is not a number (stopped at %s)", + arg, p); + } + + v *= 1024; + rlim.rlim_cur = v; + rlim.rlim_max = v; + if (setrlimit(r, &rlim)) + genrc_error("%s: failed to set limit: %s", arg, + strerror(errno)); +} + +void +set_nice(char *arg) +{ + long v; + char *p; + + if ((((v = strtol(arg + 1, &p, 10)) == LONG_MAX || v == LONG_MIN) + && errno == ERANGE) || + v < -20 || + v > 20) { + usage_error("%s: resource value out of range", arg); + } + if (*p) { + usage_error("%s: value is not a number (stopped at %s)", + arg, p); + } + if (setpriority (PRIO_PROCESS, 0, v)) { + usage_error("failed to set process priority: %s", + strerror(errno)); + } +} + void usage(void) { int i, j, pos = 0; int width = screen_width(); if (width > 4) @@ -389,16 +509,54 @@ static const char gplv3[] = "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("Copyryght (C) 2018-2021 Sergey Poznyakoff\n"); printf("%s", gplv3); } +static struct facility_def { + char const *name; + int facility; +} facility_tab[] = { + { "auth", LOG_AUTH }, + { "authpriv",LOG_AUTHPRIV }, + { "cron", LOG_CRON }, + { "daemon", LOG_DAEMON }, + { "ftp", LOG_FTP }, + { "lpr", LOG_LPR }, + { "mail", LOG_MAIL }, + { "user", LOG_USER }, + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 }, + { NULL } +}; + +static int +str_to_facility(char const *str) +{ + int i; + for (i = 0; facility_tab[i].name; i++) + if (strcasecmp(str, facility_tab[i].name) == 0) + return facility_tab[i].facility; + return -1; +} + +void +genrc_openlog(void) +{ + openlog(genrc_log_tag, LOG_PID, genrc_log_facility); +} typedef int (*GENRC_COMMAND)(void); struct comtab { char const *com_name; GENRC_COMMAND com_fun; @@ -420,21 +578,24 @@ find_command(char const *s) for (c = comtab; c->com_name; c++) if (strcmp(c->com_name, s) == 0) break; return c->com_fun; } +extern char **environ; + int main(int argc, char **argv) { int c; char *p; int no_reload = 0; GENRC_COMMAND command; setprogname(argv[0]); + mf_proctitle_init (argc, argv, environ); shortopts_setup(); while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF) { switch (c) { case 'h': @@ -449,12 +610,21 @@ main(int argc, char **argv) case 'c': setenv("GENRC_COMMAND", optarg, 1); break; case 'e': genrc_shell = NULL; break; + case 'k': + setenv("GENRC_KILL_MODE", optarg, 1); + break; + case 'l': + if (optarg[0] == 'p') + set_nice(optarg+1); + else + set_limit(optarg); + break; case 's': if (strcmp(optarg, "no") == 0 || strcmp(optarg, "none") == 0) genrc_shell = NULL; else genrc_shell = optarg; @@ -474,12 +644,18 @@ main(int argc, char **argv) case 'g': setenv("GENRC_GROUP", optarg, 1); break; case OPT_CREATE_PIDFILE: setenv("GENRC_CREATE_PIDFILE", optarg, 1); break; + case OPT_LOG_FACILITY: + setenv("GENRC_LOG_FACILITY", optarg, 1); + break; + case OPT_LOG_TAG: + setenv("GENRC_LOG_TAG", optarg, 1); + break; case 't': setenv("GENRC_TIMEOUT", optarg, 1); break; case 'S': setenv("GENRC_SENTINEL", "1", 1); break; @@ -527,18 +703,34 @@ main(int argc, char **argv) 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_KILL_MODE")) != NULL) { + if (strcmp(p, "group") == 0) + genrc_kill_mode = genrc_kill_group; + else if (strcmp(p, "process") == 0) + genrc_kill_mode = genrc_kill_process; + else if (strcmp(p, "mixed") == 0) + genrc_kill_mode = genrc_kill_mixed; + else + usage_error("%s: invalid kill mode", p); + } else { + if ((p = getenv("GENRC_SENTINEL")) && *p == '1') + genrc_kill_mode = genrc_kill_group; + else + genrc_kill_mode = genrc_kill_process; + } + if ((p = getenv("GENRC_TIMEOUT")) != NULL) { char *end; unsigned long n; errno = 0; n = strtoul(p, &end, 10); - if (errno || *p || n > UINT_MAX) + if (errno || *end || n > UINT_MAX) usage_error("%s: invalid timeout", p); if (n == 0) genrc_no_reload = 1; else genrc_timeout = n; } @@ -570,12 +762,22 @@ main(int argc, char **argv) genrc_pid_closure = get_pid_closure(p); free(p); } else { genrc_pid_closure = get_pid_closure(DEFAULT_PID_SOURCE); } + if ((p = getenv("GENRC_LOG_FACILITY")) != NULL) { + if ((c = str_to_facility(p)) == -1) + usage_error("%s: unknown facility", p); + genrc_log_facility = c; + } + if ((p = getenv("GENRC_LOG_TAG")) != NULL) + genrc_log_tag = p; + else + genrc_log_tag = genrc_program; + argc -= optind; if (argc == 0) usage_error("missing command verb"); if (argc > 1) usage_error("too many arguments"); argv += optind; |