From 4f60d13820ad95d876f6daacefe4e72ffff57a5f Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Thu, 11 Jul 2019 17:06:31 +0300 Subject: New options to select the shell binary and to run the command directly * .gitmodules: New module: runcap * Makefile.am: Build runcap * configure.ac: Setup runcap * src/Makefile.am: Add runcap dependencies. * src/com_start.c (spawn): New function. (com_start): Use spawn. * src/sentinel.c (start_command): Use spawn. * src/genrc.8: Document new options: --shell, --exec, --verbose. * src/genrc.c (genrc_shell): New global. (longopts): New options: --shell (-s), --exec (-e). (main): Setup shortopts from longopts. * src/genrc.h (genrc_shell): New global. (spawn): New proto. * src/runas.c: Set HOME and USER environment variables. --- .gitmodules | 3 ++ Makefile.am | 3 +- configure.ac | 1 + runcap | 1 + src/Makefile.am | 6 +-- src/com_start.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/genrc.8 | 25 ++++++++-- src/genrc.c | 50 ++++++++++++++++++-- src/genrc.h | 5 +- src/runas.c | 5 +- src/sentinel.c | 42 ++++++----------- 11 files changed, 227 insertions(+), 52 deletions(-) create mode 160000 runcap diff --git a/.gitmodules b/.gitmodules index fea8f96..c9b0a32 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "grecs"] path = grecs url = git://git.gnu.org.ua/grecs.git +[submodule "runcap"] + path = runcap + url = git://git.gnu.org.ua/runcap.git diff --git a/Makefile.am b/Makefile.am index dac3cb3..bb011c5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,5 @@ -SUBDIRS = grecs src +ACLOCAL_AMFLAGS = -I runcap +SUBDIRS = grecs runcap src dist: ChangeLog .PHONY: ChangeLog ChangeLog: diff --git a/configure.ac b/configure.ac index 90107a2..2610762 100644 --- a/configure.ac +++ b/configure.ac @@ -38,6 +38,7 @@ AC_CHECK_HEADERS([getopt.h pcre.h]) AC_CHECK_FUNCS([getdtablesize]) GRECS_SETUP(grecs, [all-parsers git2chg]) +RUNCAP_SETUP AM_CONDITIONAL([COND_PCRE], [test "$ac_cv_header_pcre_h" = yes && test "$ac_cv_lib_pcre_main" = yes]) diff --git a/runcap b/runcap new file mode 160000 index 0000000..191c066 --- /dev/null +++ b/runcap @@ -0,0 +1 @@ +Subproject commit 191c06630813efdda69a58eed2f65e0e07cd9727 diff --git a/src/Makefile.am b/src/Makefile.am index b565303..168ab11 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ # This file is part of genrc. -# Copyright (C) 2018 Sergey Poznyakoff. +# Copyright (C) 2018, 2019 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 @@ -39,8 +39,8 @@ genrc_SOURCES = \ sentinel.c\ runas.c -AM_CPPFLAGS = @GRECS_INCLUDES@ -LDADD = @GRECS_LDADD@ +AM_CPPFLAGS = @GRECS_INCLUDES@ @RUNCAP_INC@ +LDADD = @GRECS_LDADD@ @RUNCAP_LDADD@ if COND_PCRE genrc_SOURCES += match_pcre.c diff --git a/src/com_start.c b/src/com_start.c index 3a9dffc..17581cb 100644 --- a/src/com_start.c +++ b/src/com_start.c @@ -1,10 +1,139 @@ /* This file is part of genrc -Copyryght (C) 2018 Sergey Poznyakoff +Copyryght (C) 2018, 2019 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 +#include +#include + +static int +expand_command(char **ret, const char *str, size_t len, char **argv, + void *closure) +{ + struct runcap rc; + int res; + + rc.rc_argv = argv; + rc.rc_cap[RUNCAP_STDERR].sc_size = 0; /* Disable stderr capturing */ + res = runcap(&rc, 0); + if (res == -1) { + if (errno == ENOMEM) + return WRDSE_NOSPACE; + else { + size_t size = 0; + *ret = NULL; + if (grecs_asprintf(ret, &size, "can't run %s: %s", + argv[0], strerror(errno))) + return WRDSE_NOSPACE; + else + return WRDSE_USERERR; + } + } + + if (rc.rc_cap[RUNCAP_STDOUT].sc_leng) { + char *p = malloc(rc.rc_cap[RUNCAP_STDOUT].sc_leng + 1); + size_t i; + + if (!p) { + runcap_free(&rc); + return WRDSE_NOSPACE; + } + *ret = p; + for (i = 0; i < rc.rc_cap[RUNCAP_STDOUT].sc_leng; i++) { + res = runcap_getc(&rc, RUNCAP_STDOUT, p); + if (res == -1) { + size_t size = 0; + + free(*ret); + *ret = NULL; + runcap_free(&rc); + if (grecs_asprintf(ret, &size, + "error capturing output " + "from %s: %s", + argv[0], strerror(errno))) + return WRDSE_NOSPACE; + else + return WRDSE_USERERR; + } + if (res == 0) + break; + if (*p == '\n') + *p = ' '; + p++; + } + *p = 0; + } + + runcap_free(&rc); + return WRDSE_OK; +} + +extern char **environ; + +/* Spawn genrc_command. If genrc_shell is not NULL, start it via + "genrc_shell -c". Otherwise, do a plain exec(3). + + If P is not null, close all file descriptors, open /dev/null as standard + input, use P[0] as standard output (sic!) and P[1] as standard error. + + The function never returns. +*/ +void +spawn(int *p) +{ + char **argv; + + if (genrc_shell) { + argv = xcalloc(4, sizeof(argv[0])); + argv[0] = genrc_shell; + argv[1] = "-c"; + argv[2] = genrc_command; + argv[3] = NULL; + } else { + struct wordsplit ws; + int wsflags = WRDSF_ENV + | WRDSF_ENOMEMABRT + | WRDSF_SHOWERR + | WRDSF_ERROR + ; + ws.ws_error = genrc_error; + ws.ws_command = expand_command; + ws.ws_env = (const char **)environ; + if (wordsplit(genrc_command, &ws, wsflags)) + exit(127); + argv = ws.ws_wordv; + } + + runas(); + + if (p) { + int i; + + close(0); + close(1); + close(2); + open("/dev/null", O_RDWR); + dup(p[0]); + dup(p[1]); + + i = sysconf(_SC_OPEN_MAX); + while (--i > 2) + close(i); + } + + execvp(argv[0], argv); + + if (p) { + openlog(genrc_program, LOG_PID, LOG_DAEMON); + syslog(LOG_CRIT, "failed to exec: %m"); + } else + system_error(errno, "failed to exec %s", genrc_program); + + exit(127); +} void report_exec_error(int rc, char const *program) @@ -114,12 +243,7 @@ com_start(void) return 1; } if (pid == 0) { - char *argv[] = { SHELL, "-c", NULL, NULL }; - argv[2] = genrc_command; - runas(); - execvp(SHELL, argv); - system_error(errno, "failed to exec %s", genrc_program); - exit(127); + spawn(NULL); } if (timedwaitpid(pid, &status)) { diff --git a/src/genrc.8 b/src/genrc.8 index 959a00e..ed077bf 100644 --- a/src/genrc.8 +++ b/src/genrc.8 @@ -1,5 +1,5 @@ .\" This file is part of genrc. -.\" Copyright (C) 2018 Sergey Poznyakoff. +.\" Copyright (C) 2018, 2019 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 @@ -13,23 +13,25 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with genrc. If not, see . -.TH GENRC 8 "May 20, 2018" "GENRC" "Genrc User Manual" +.TH GENRC 8 "July 11, 2019" "GENRC" "Genrc User Manual" .SH NAME genrc \- generic system initialization script helper .SH SYNOPSIS .nh .na \fBgenrc\fR\ - [\fB\-hv\fR]\ + [\fB\-hev\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\-s\fR \fISHELL\fR]\ [\fB\-t\fR \fISECONDS\fR]\ [\fB\-u\fR \fIUSER\fR]\ [\fB\-\-command=\fICOMMAND\fR]\ [\fB\-\-create\-pidfile=\fIPIDFILE\fR]\ + [\fB\-\-exec\fR]\ [\fB\-\-group=\fIGROUP\fR[,\fIGROUP\fR...]]\ [\fB\-\-help\fR]\ [\fB\-\-no\-reload\fR]\ @@ -39,6 +41,7 @@ genrc \- generic system initialization script helper [\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\-\-shell=\fISHELL\fR]\ [\fB\-\-signal\-reload=\fISIG\fR]\ [\fB\-\-signal\-stop=\fISIG\fR]\ [\fB\-\-timeout=\fISECONDS\fR]\ @@ -188,7 +191,11 @@ When used together with \fB\-\-sentinel\fR, the PID of the command being run will be stored in file \fINAME\fR. The \fB\-\-pid\-from=FILE:\fINAME\fR will be assumed, unless the \fB\-\-pid\-from\fR is given explicitly (or the \fBGENRC_PID_FROM\fR -variable is set). +variable is set). +.TP +\fB\-e\fR, \fB\-\-exec\fR +In sentinel mode, run the command directly via exec(3), instead of +using \fBsh -c\fR. Alias: \fB\-\-shell=none\fR. .TP \fB\-F\fR, \fB\-\-pidfile=\fINAME\fR Name of the PID file (same as \fB\-\-pid\-from=FILE:\fINAME\fR) @@ -244,7 +251,15 @@ 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. +\fB\-\-restart\-on\-signal\fR for details on how to restart the +program. +.TP +\fB\-s\fR, \fB\-\-shell=\fISHELL\fR +In sentinel mode, use \fISHELL\fR to run the command, instead of the +default +.BR /bin/sh . +\fISHELL\fR must support the \fB\-c\fR option. +Use \fB\-\-shell=none\fR or \fB\-\-exec\fR to run the command directly. .TP \fB\-\-signal\-reload=\fISIG\fR Signal to send on reload (default: \fBSIGHUP\fR). Setting it to 0 is diff --git a/src/genrc.c b/src/genrc.c index 9052987..3dccbda 100644 --- a/src/genrc.c +++ b/src/genrc.c @@ -1,5 +1,5 @@ /* This file is part of genrc -Copyryght (C) 2018 Sergey Poznyakoff +Copyryght (C) 2018, 2019 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. @@ -17,6 +17,7 @@ int genrc_signal_reload = SIGHUP; GENRC_PID_CLOSURE *genrc_pid_closure; char *genrc_create_pidfile; int genrc_verbose; +char *genrc_shell = SHELL; enum { @@ -42,6 +43,8 @@ struct option longopts[] = { { "no-reload", no_argument, 0, OPT_NO_RELOAD }, { "signal-stop", required_argument, 0, OPT_SIGNAL_STOP }, { "sentinel", no_argument, 0, 'S' }, + { "shell", no_argument, 0, 's' }, + { "exec", no_argument, 0, 'e' }, { "create-pidfile", required_argument, 0, OPT_CREATE_PIDFILE }, { "version", no_argument, 0, OPT_VERSION }, { "verbose", no_argument, 0, 'v' }, @@ -51,7 +54,27 @@ struct option longopts[] = { { "restart-on-signal", required_argument, 0, OPT_RESTART_ON_SIGNAL }, { NULL } }; -char shortopts[] = "c:hF:g:P:p:St:u:v"; +char shortopts[2*sizeof(longopts)/sizeof(longopts[0])]; + +static void +shortopts_setup(void) +{ + int i; + char *p; + + for (i = 0, p = shortopts; longopts[i].name; i++) { + if (longopts[i].val < 128) { + *p++ = longopts[i].val; + if (longopts[i].has_arg != no_argument) { + *p++ = ':'; + if (longopts[i].has_arg == optional_argument) + *p++ = ':'; + } + } + } + *p = 0; +} + struct sigdefn { char const *sig_name; @@ -206,6 +229,9 @@ char const *help_msg[] = { "", " --sentinel PROGRAM runs in foreground; disconnect from the", " controlling terminal, run it and act as a sentinel", + " -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", @@ -240,7 +266,7 @@ char const *help_msg[] = { " file , written in language ", "", " GREP::s///[][;...]", - " Grep for the first line in that matches . If found, process", + " Grep for the first line in that matches . If found,", " replace the matched portion according to and . Use", " the resulting string as PID. More sed expressions can be supplied", " separated with semicolons.", @@ -276,12 +302,13 @@ help(void) char const *usage_msg[] = { "genrc", - "[-h]", + "[-hev]", "[-F PIDFILE]", "[-P SOURCE]", "[-c COMMAND]", "[-g GROUP[,GROUP...]]", "[-p PROGRAM]", + "[-s SHELL]", "[-t SECONDS]", "[-u USER]", "[--command=COMMAND]", @@ -294,11 +321,13 @@ char const *usage_msg[] = { "[--restart-on-exit=[!]CODE[,...]]", "[--restart-on-signal=[!]SIG[,...]]", "[--sentinel]", + "[--shell=SHELL]", "[--signal-reload=SIG]", "[--signal-stop=SIG]", "[--timeout=SECONDS]", "[--usage]", "[--user=USER]", + "[--verbose]", "{", "start", "|", @@ -403,7 +432,8 @@ main(int argc, char **argv) GENRC_COMMAND command; setprogname(argv[0]); - + + shortopts_setup(); while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF) { switch (c) { @@ -419,6 +449,16 @@ main(int argc, char **argv) case 'c': setenv("GENRC_COMMAND", optarg, 1); break; + case 'e': + genrc_shell = NULL; + break; + case 's': + if (strcmp(optarg, "no") == 0 + || strcmp(optarg, "none") == 0) + genrc_shell = NULL; + else + genrc_shell = optarg; + break; case 'p': setenv("GENRC_PROGRAM", optarg, 1); break; diff --git a/src/genrc.h b/src/genrc.h index c6ee57b..e77c840 100644 --- a/src/genrc.h +++ b/src/genrc.h @@ -1,5 +1,5 @@ /* This file is part of genrc -Copyryght (C) 2018 Sergey Poznyakoff +Copyryght (C) 2018, 2019 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. @@ -145,6 +145,9 @@ extern int genrc_signal_stop; extern GENRC_PID_CLOSURE *genrc_pid_closure; extern char *genrc_create_pidfile; extern int genrc_verbose; +extern char *genrc_shell; + +void spawn(int *p); int sentinel(void); diff --git a/src/runas.c b/src/runas.c index 9d2a6c1..5fa51a9 100644 --- a/src/runas.c +++ b/src/runas.c @@ -1,5 +1,5 @@ /* This file is part of genrc -Copyryght (C) 2018 Sergey Poznyakoff +Copyryght (C) 2018, 2019 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. @@ -188,4 +188,7 @@ runas(void) exit(1); } } + + setenv("HOME", pw->pw_dir, 1); + setenv("USER", pw->pw_name, 1); } diff --git a/src/sentinel.c b/src/sentinel.c index 4f269b8..989acf1 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -1,12 +1,10 @@ /* This file is part of genrc -Copyryght (C) 2018 Sergey Poznyakoff +Copyryght (C) 2018, 2019 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 -#include #include #include @@ -19,7 +17,11 @@ xpipe(int p[2]) } } -#define max(a,b) ((a) > (b) ? (a) : (b)) +static inline int +max(int a, int b) +{ + return a > b ? a : b; +} static void write_pid_file(pid_t pid) @@ -234,8 +236,8 @@ pid_t start_command(int p[]) { int errpipe[2], outpipe[2]; - pid_t pid; - + pid_t pid; + xpipe(errpipe); xpipe(outpipe); pid = fork(); @@ -245,31 +247,13 @@ start_command(int p[]) } if (pid == 0) { - char *argv[] = { SHELL, "-c", NULL, NULL }; - int i; - - runas(); - - close(0); - close(1); - close(2); - open("/dev/null", O_RDWR); - dup(outpipe[1]); - dup(errpipe[1]); - - i = max(max(outpipe[0],outpipe[1]), - max(errpipe[0],errpipe[1])); - while (--i > 2) - close(i); - - argv[2] = genrc_command; - execvp(SHELL, argv); - system_error(errno, "failed to exec %s", genrc_program); - exit(127); + p[0] = outpipe[1]; + p[1] = errpipe[1]; + spawn(p); } write_pid_file(pid); - + close(outpipe[1]); close(errpipe[1]); @@ -349,7 +333,7 @@ sentinel(void) int p[2]; struct ratectl ctl; struct sigaction act; - + /* Detach from the controlling terminal */ pid = fork(); if (pid == -1) { -- cgit v1.2.1