Files
openrc/src/rc/supervise-daemon.c
William Hubbs af70862a7a supervise-daemon: use a default pid file if one is not specified
Since the pid file is internal to us, start moving toward deprecating it
by not requiring the user to specify it.
In the next release, I plan on working on code to start phasing out the
use of a pid file if this is possible.
2018-12-03 12:41:29 -06:00

1052 lines
26 KiB
C

/*
* supervise-daemon
* This is an experimental supervisor for daemons.
* It will start a deamon and make sure it restarts if it crashes.
*/
/*
* Copyright (c) 2016 The OpenRC Authors.
* See the Authors file at the top-level directory of this distribution and
* https://github.com/OpenRC/openrc/blob/master/AUTHORS
*
* This file is part of OpenRC. It is subject to the license terms in
* the LICENSE file found in the top-level directory of this
* distribution and at https://github.com/OpenRC/openrc/blob/master/LICENSE
* This file may not be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file.
*/
/* nano seconds */
#define POLL_INTERVAL 20000000
#define WAIT_PIDFILE 500000000
#define ONE_SECOND 1000000000
#define ONE_MS 1000000
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/wait.h>
#ifdef __linux__
#include <sys/syscall.h> /* For io priority */
#endif
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_PAM
#include <security/pam_appl.h>
/* We are not supporting authentication conversations */
static struct pam_conv conv = { NULL, NULL};
#endif
#include "einfo.h"
#include "queue.h"
#include "rc.h"
#include "rc-misc.h"
#include "rc-plugin.h"
#include "rc-schedules.h"
#include "_usage.h"
#include "helpers.h"
const char *applet = NULL;
const char *extraopts = NULL;
const char *getoptstring = "A:a:D:d:e:g:H:I:Kk:m:N:p:R:r:s:Su:1:2:3" \
getoptstring_COMMON;
const struct option longopts[] = {
{ "healthcheck-timer", 1, NULL, 'a'},
{ "healthcheck-delay", 1, NULL, 'A'},
{ "respawn-delay", 1, NULL, 'D'},
{ "chdir", 1, NULL, 'd'},
{ "env", 1, NULL, 'e'},
{ "group", 1, NULL, 'g'},
{ "ionice", 1, NULL, 'I'},
{ "stop", 0, NULL, 'K'},
{ "umask", 1, NULL, 'k'},
{ "respawn-max", 1, NULL, 'm'},
{ "nicelevel", 1, NULL, 'N'},
{ "pidfile", 1, NULL, 'p'},
{ "respawn-period", 1, NULL, 'P'},
{ "retry", 1, NULL, 'R'},
{ "chroot", 1, NULL, 'r'},
{ "signal", 1, NULL, 's'},
{ "start", 0, NULL, 'S'},
{ "user", 1, NULL, 'u'},
{ "stdout", 1, NULL, '1'},
{ "stderr", 1, NULL, '2'},
{ "reexec", 0, NULL, '3'},
longopts_COMMON
};
const char * const longopts_help[] = {
"set an initial health check delay",
"set a health check timer",
"Set a respawn delay",
"Change the PWD",
"Set an environment string",
"Change the process group",
"Set an ionice class:data when starting",
"Stop daemon",
"Set the umask for the daemon",
"set maximum number of respawn attempts",
"Set a nicelevel when starting",
"Match pid found in this file",
"Set respawn time period",
"Retry schedule to use when stopping",
"Chroot to this directory",
"Send a signal to the daemon",
"Start daemon",
"Change the process user",
"Redirect stdout to file",
"Redirect stderr to file",
"reexec (used internally)",
longopts_help_COMMON
};
const char *usagestring = NULL;
static int healthcheckdelay = 0;
static int healthchecktimer = 0;
static volatile sig_atomic_t do_healthcheck = 0;
static volatile sig_atomic_t exiting = 0;
static int nicelevel = 0;
static int ionicec = -1;
static int ioniced = 0;
static char *changeuser, *ch_root, *ch_dir;
static uid_t uid = 0;
static gid_t gid = 0;
static int devnull_fd = -1;
static int stdin_fd;
static int stdout_fd;
static int stderr_fd;
static char *redirect_stderr = NULL;
static char *redirect_stdout = NULL;
#ifdef TIOCNOTTY
static int tty_fd = -1;
#endif
static pid_t child_pid;
static int respawn_count = 0;
static int respawn_delay = 0;
static int respawn_max = 10;
static int respawn_period = 0;
static char *fifopath = NULL;
static int fifo_fd = 0;
static char *pidfile = NULL;
static char *svcname = NULL;
extern char **environ;
#if !defined(SYS_ioprio_set) && defined(__NR_ioprio_set)
# define SYS_ioprio_set __NR_ioprio_set
#endif
#if !defined(__DragonFly__)
static inline int ioprio_set(int which _unused, int who _unused,
int ioprio _unused)
{
#ifdef SYS_ioprio_set
return syscall(SYS_ioprio_set, which, who, ioprio);
#else
return 0;
#endif
}
#endif
static void cleanup(void)
{
free(changeuser);
}
static void re_exec_supervisor(void)
{
syslog(LOG_WARNING, "Re-executing for %s", svcname);
execlp("supervise-daemon", "supervise-daemon", svcname, "--reexec",
(char *) NULL);
syslog(LOG_ERR, "Unable to execute supervise-daemon: %s",
strerror(errno));
exit(EXIT_FAILURE);
}
static void handle_signal(int sig)
{
int serrno = errno;
switch(sig) {
case SIGALRM:
do_healthcheck = 1;
break;
case SIGCHLD:
while (waitpid((pid_t)(-1), NULL, WNOHANG) > 0) {}
break;
case SIGTERM:
exiting = 1;
break;
default:
syslog(LOG_WARNING, "caught signal %d", sig);
re_exec_supervisor();
}
/* Restore errno */
errno = serrno;
}
static char * expand_home(const char *home, const char *path)
{
char *opath, *ppath, *p, *nh;
struct passwd *pw;
if (!path || *path != '~')
return xstrdup(path);
opath = ppath = xstrdup(path);
if (ppath[1] != '/' && ppath[1] != '\0') {
p = strchr(ppath + 1, '/');
if (p)
*p = '\0';
pw = getpwnam(ppath + 1);
if (pw) {
home = pw->pw_dir;
ppath = p;
if (ppath)
*ppath = '/';
} else
home = NULL;
} else
ppath++;
if (!home) {
free(opath);
return xstrdup(path);
}
if (!ppath) {
free(opath);
return xstrdup(home);
}
xasprintf(&nh, "%s%s", home, ppath);
free(opath);
return nh;
}
static char *make_cmdline(char **argv)
{
char **c;
char *cmdline = NULL;
size_t len = 0;
for (c = argv; c && *c; c++)
len += (strlen(*c) + 1);
cmdline = xmalloc(len+1);
memset(cmdline, 0, len+1);
for (c = argv; c && *c; c++) {
strcat(cmdline, *c);
strcat(cmdline, " ");
}
return cmdline;
}
static void child_process(char *exec, char **argv)
{
RC_STRINGLIST *env_list;
RC_STRING *env;
int i;
char *p;
char *token;
size_t len;
char *newpath;
char *np;
char *cmdline = NULL;
time_t start_time;
char start_count_string[20];
char start_time_string[20];
#ifdef HAVE_PAM
pam_handle_t *pamh = NULL;
int pamr;
const char *const *pamenv = NULL;
#endif
setsid();
if (svcname) {
start_time = time(NULL);
from_time_t(start_time_string, start_time);
rc_service_value_set(svcname, "start_time", start_time_string);
sprintf(start_count_string, "%i", respawn_count);
rc_service_value_set(svcname, "start_count", start_count_string);
sprintf(start_count_string, "%d", getpid());
rc_service_value_set(svcname, "child_pid", start_count_string);
}
if (nicelevel) {
if (setpriority(PRIO_PROCESS, getpid(), nicelevel) == -1)
eerrorx("%s: setpriority %d: %s", applet, nicelevel,
strerror(errno));
}
if (ionicec != -1 && ioprio_set(1, getpid(), ionicec | ioniced) == -1)
eerrorx("%s: ioprio_set %d %d: %s", applet, ionicec, ioniced,
strerror(errno));
if (ch_root && chroot(ch_root) < 0)
eerrorx("%s: chroot `%s': %s", applet, ch_root, strerror(errno));
if (ch_dir && chdir(ch_dir) < 0)
eerrorx("%s: chdir `%s': %s", applet, ch_dir, strerror(errno));
#ifdef HAVE_PAM
if (changeuser != NULL) {
pamr = pam_start("supervise-daemon",
changeuser, &conv, &pamh);
if (pamr == PAM_SUCCESS)
pamr = pam_acct_mgmt(pamh, PAM_SILENT);
if (pamr == PAM_SUCCESS)
pamr = pam_open_session(pamh, PAM_SILENT);
if (pamr != PAM_SUCCESS)
eerrorx("%s: pam error: %s", applet, pam_strerror(pamh, pamr));
}
#endif
if (gid && setgid(gid))
eerrorx("%s: unable to set groupid to %d", applet, gid);
if (changeuser && initgroups(changeuser, gid))
eerrorx("%s: initgroups (%s, %d)", applet, changeuser, gid);
if (uid && setuid(uid))
eerrorx ("%s: unable to set userid to %d", applet, uid);
/* Close any fd's to the passwd database */
endpwent();
/* remove the controlling tty */
#ifdef TIOCNOTTY
ioctl(tty_fd, TIOCNOTTY, 0);
close(tty_fd);
#endif
/* Clean the environment of any RC_ variables */
env_list = rc_stringlist_new();
i = 0;
while (environ[i])
rc_stringlist_add(env_list, environ[i++]);
#ifdef HAVE_PAM
if (changeuser != NULL) {
pamenv = (const char *const *)pam_getenvlist(pamh);
if (pamenv) {
while (*pamenv) {
/* Don't add strings unless they set a var */
if (strchr(*pamenv, '='))
putenv(xstrdup(*pamenv));
else
unsetenv(*pamenv);
pamenv++;
}
}
}
#endif
TAILQ_FOREACH(env, env_list, entries) {
if ((strncmp(env->value, "RC_", 3) == 0 &&
strncmp(env->value, "RC_SERVICE=", 10) != 0 &&
strncmp(env->value, "RC_SVCNAME=", 10) != 0) ||
strncmp(env->value, "SSD_NICELEVEL=", 14) == 0)
{
p = strchr(env->value, '=');
*p = '\0';
unsetenv(env->value);
continue;
}
}
rc_stringlist_free(env_list);
/* For the path, remove the rcscript bin dir from it */
if ((token = getenv("PATH"))) {
len = strlen(token);
newpath = np = xmalloc(len + 1);
while (token && *token) {
p = strchr(token, ':');
if (p) {
*p++ = '\0';
while (*p == ':')
p++;
}
if (strcmp(token, RC_LIBEXECDIR "/bin") != 0 &&
strcmp(token, RC_LIBEXECDIR "/sbin") != 0)
{
len = strlen(token);
if (np != newpath)
*np++ = ':';
memcpy(np, token, len);
np += len;
}
token = p;
}
*np = '\0';
unsetenv("PATH");
setenv("PATH", newpath, 1);
}
stdin_fd = devnull_fd;
stdout_fd = devnull_fd;
stderr_fd = devnull_fd;
if (redirect_stdout) {
if ((stdout_fd = open(redirect_stdout,
O_WRONLY | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR)) == -1)
eerrorx("%s: unable to open the logfile"
" for stdout `%s': %s",
applet, redirect_stdout, strerror(errno));
}
if (redirect_stderr) {
if ((stderr_fd = open(redirect_stderr,
O_WRONLY | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR)) == -1)
eerrorx("%s: unable to open the logfile"
" for stderr `%s': %s",
applet, redirect_stderr, strerror(errno));
}
dup2(stdin_fd, STDIN_FILENO);
if (redirect_stdout || rc_yesno(getenv("EINFO_QUIET")))
dup2(stdout_fd, STDOUT_FILENO);
if (redirect_stderr || rc_yesno(getenv("EINFO_QUIET")))
dup2(stderr_fd, STDERR_FILENO);
for (i = getdtablesize() - 1; i >= 3; --i)
fcntl(i, F_SETFD, FD_CLOEXEC);
cmdline = make_cmdline(argv);
syslog(LOG_INFO, "Child command line: %s", cmdline);
free(cmdline);
execvp(exec, argv);
#ifdef HAVE_PAM
if (changeuser != NULL && pamr == PAM_SUCCESS)
pam_close_session(pamh, PAM_SILENT);
#endif
eerrorx("%s: failed to exec `%s': %s", applet, exec,strerror(errno));
}
static void supervisor(char *exec, char **argv)
{
FILE *fp;
char buf[2048];
char cmd[2048];
int count;
int failing;
int health_status;
int healthcheck_respawn;
int i;
int nkilled;
int sig_send;
pid_t health_pid;
pid_t wait_pid;
sigset_t old_signals;
sigset_t signals;
struct sigaction sa;
struct timespec ts;
time_t respawn_now= 0;
time_t first_spawn= 0;
/* block all signals we do not handle */
sigfillset(&signals);
sigdelset(&signals, SIGALRM);
sigdelset(&signals, SIGCHLD);
sigdelset(&signals, SIGTERM);
sigprocmask(SIG_SETMASK, &signals, &old_signals);
/* install signal handler */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_signal;
sigaction(SIGALRM, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
fp = fopen(pidfile, "w");
if (! fp)
eerrorx("%s: fopen `%s': %s", applet, pidfile, strerror(errno));
fprintf(fp, "%d\n", getpid());
fclose(fp);
if (svcname)
rc_service_daemon_set(svcname, exec, (const char * const *) argv,
pidfile, true);
/* remove the controlling tty */
#ifdef TIOCNOTTY
ioctl(tty_fd, TIOCNOTTY, 0);
close(tty_fd);
#endif
/*
* Supervisor main loop
*/
if (healthcheckdelay)
alarm(healthcheckdelay);
else if (healthchecktimer)
alarm(healthchecktimer);
fifo_fd = open(fifopath, O_RDONLY |O_NONBLOCK);
failing = 0;
while (!exiting) {
healthcheck_respawn = 0;
wait_pid = waitpid(child_pid, &i, WNOHANG);
memset(buf, 0, sizeof(buf));
if (fifo_fd >= 0) {
count = read(fifo_fd, buf, sizeof(buf) - 1);
if (count != -1)
buf[count] = 0;
}
if (strlen(buf) > 0) {
syslog(LOG_DEBUG, "Received %s from fifo", buf);
if (strncasecmp(buf, "sig", 3) == 0) {
if ((sscanf(buf, "%s %d", cmd, &sig_send) == 2)
&& (sig_send >= 0 && sig_send < NSIG)) {
syslog(LOG_INFO, "Sending signal %d to %d", sig_send,
child_pid);
if (kill(child_pid, sig_send) == -1)
syslog(LOG_ERR, "Unable to send signal %d to %d",
sig_send, child_pid);
}
}
}
if (do_healthcheck) {
do_healthcheck = 0;
alarm(0);
syslog(LOG_DEBUG, "running health check for %s", svcname);
health_pid = exec_service(svcname, "healthcheck");
health_status = rc_waitpid(health_pid);
if (WIFEXITED(health_status) && WEXITSTATUS(health_status) == 0)
alarm(healthchecktimer);
else {
syslog(LOG_WARNING, "health check for %s failed", svcname);
health_pid = exec_service(svcname, "unhealthy");
rc_waitpid(health_pid);
syslog(LOG_INFO, "stopping %s, pid %d", exec, child_pid);
nkilled = run_stop_schedule(applet, NULL, NULL, child_pid, 0,
false, false, true);
if (nkilled < 0)
syslog(LOG_INFO, "Unable to kill %d: %s",
child_pid, strerror(errno));
else
healthcheck_respawn = 1;
}
}
if (exiting ) {
alarm(0);
syslog(LOG_INFO, "stopping %s, pid %d", exec, child_pid);
nkilled = run_stop_schedule(applet, NULL, NULL, child_pid, 0,
false, false, true);
if (nkilled > 0)
syslog(LOG_INFO, "killed %d processes", nkilled);
continue;
}
if (wait_pid == child_pid) {
if (WIFEXITED(i))
syslog(LOG_WARNING, "%s, pid %d, exited with return code %d",
exec, child_pid, WEXITSTATUS(i));
else if (WIFSIGNALED(i))
syslog(LOG_WARNING, "%s, pid %d, terminated by signal %d",
exec, child_pid, WTERMSIG(i));
}
if (wait_pid == child_pid || healthcheck_respawn) {
do_healthcheck = 0;
healthcheck_respawn = 0;
alarm(0);
respawn_now = time(NULL);
if (first_spawn == 0)
first_spawn = respawn_now;
if ((respawn_period > 0)
&& (respawn_now - first_spawn > respawn_period)) {
respawn_count = 0;
first_spawn = 0;
} else
respawn_count++;
if (respawn_max > 0 && respawn_count > respawn_max) {
syslog(LOG_WARNING, "respawned \"%s\" too many times, exiting",
exec);
exiting = 1;
failing = 1;
continue;
}
ts.tv_sec = respawn_delay;
ts.tv_nsec = 0;
nanosleep(&ts, NULL);
child_pid = fork();
if (child_pid == -1) {
syslog(LOG_ERR, "%s: fork: %s", applet, strerror(errno));
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
sigprocmask(SIG_SETMASK, &old_signals, NULL);
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(SIGALRM, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
child_process(exec, argv);
}
if (healthcheckdelay)
alarm(healthcheckdelay);
else if (healthchecktimer)
alarm(healthchecktimer);
}
}
if (svcname) {
rc_service_daemon_set(svcname, exec, (const char *const *)argv,
pidfile, false);
rc_service_value_set(svcname, "child_pid", NULL);
rc_service_mark(svcname, RC_SERVICE_STOPPED);
if (failing)
rc_service_mark(svcname, RC_SERVICE_FAILED);
}
if (pidfile && exists(pidfile))
unlink(pidfile);
if (fifopath && exists(fifopath))
unlink(fifopath);
exit(EXIT_SUCCESS);
}
int main(int argc, char **argv)
{
int opt;
char **c;
int x;
bool start = false;
bool stop = false;
bool reexec = false;
bool sendsig = false;
char *exec = NULL;
char *retry = NULL;
int sig = SIGTERM;
char *home = NULL;
int tid = 0;
pid_t pid;
char *tmp;
char *p;
char *token;
int i;
int n;
char *exec_file = NULL;
char *varbuf = NULL;
struct timespec ts;
struct passwd *pw;
struct group *gr;
FILE *fp;
mode_t numask = 022;
int child_argc = 0;
char **child_argv = NULL;
char *str = NULL;
char *cmdline = NULL;
applet = basename_c(argv[0]);
atexit(cleanup);
svcname = getenv("RC_SVCNAME");
if (!svcname)
eerrorx("%s: The RC_SVCNAME environment variable is not set", applet);
openlog(applet, LOG_PID, LOG_DAEMON);
if (argc >= 1 && svcname && strcmp(argv[1], svcname))
eerrorx("%s: the first argument is %s and must be %s",
applet, argv[1], svcname);
if ((tmp = getenv("SSD_NICELEVEL")))
if (sscanf(tmp, "%d", &nicelevel) != 1)
eerror("%s: invalid nice level `%s' (SSD_NICELEVEL)",
applet, tmp);
/* Get our user name and initial dir */
p = getenv("USER");
home = getenv("HOME");
if (home == NULL || p == NULL) {
pw = getpwuid(getuid());
if (pw != NULL) {
if (p == NULL)
setenv("USER", pw->pw_name, 1);
if (home == NULL) {
setenv("HOME", pw->pw_dir, 1);
home = pw->pw_dir;
}
}
}
cmdline = make_cmdline(argv);
if (svcname) {
argc--;
argv++;
}
while ((opt = getopt_long(argc, argv, getoptstring, longopts,
(int *) 0)) != -1)
switch (opt) {
case 'a': /* --healthcheck-timer <time> */
if (sscanf(optarg, "%d", &healthchecktimer) != 1 || healthchecktimer < 1)
eerrorx("%s: invalid health check timer %s", applet, optarg);
break;
case 'A': /* --healthcheck-delay <time> */
if (sscanf(optarg, "%d", &healthcheckdelay) != 1 || healthcheckdelay < 1)
eerrorx("%s: invalid health check delay %s", applet, optarg);
break;
case 'D': /* --respawn-delay time */
n = sscanf(optarg, "%d", &respawn_delay);
if (n != 1 || respawn_delay < 1)
eerrorx("Invalid respawn-delay value '%s'", optarg);
break;
case 'I': /* --ionice */
if (sscanf(optarg, "%d:%d", &ionicec, &ioniced) == 0)
eerrorx("%s: invalid ionice `%s'",
applet, optarg);
if (ionicec == 0)
ioniced = 0;
else if (ionicec == 3)
ioniced = 7;
ionicec <<= 13; /* class shift */
break;
case 'K': /* --stop */
stop = true;
break;
case 'N': /* --nice */
if (sscanf(optarg, "%d", &nicelevel) != 1)
eerrorx("%s: invalid nice level `%s'",
applet, optarg);
break;
case 'P': /* --respawn-period time */
n = sscanf(optarg, "%d", &respawn_period);
if (n != 1 || respawn_period < 1)
eerrorx("Invalid respawn-period value '%s'", optarg);
break;
case 's': /* --signal */
sig = parse_signal(applet, optarg);
sendsig = true;
break;
case 'S': /* --start */
start = true;
break;
case 'd': /* --chdir /new/dir */
ch_dir = optarg;
break;
case 'e': /* --env */
putenv(optarg);
break;
case 'g': /* --group <group>|<gid> */
if (sscanf(optarg, "%d", &tid) != 1)
gr = getgrnam(optarg);
else
gr = getgrgid((gid_t)tid);
if (gr == NULL)
eerrorx("%s: group `%s' not found",
applet, optarg);
gid = gr->gr_gid;
break;
case 'H': /* --healthcheck-timer <minutes> */
if (sscanf(optarg, "%d", &healthchecktimer) != 1 || healthchecktimer < 1)
eerrorx("%s: invalid health check timer %s", applet, optarg);
break;
case 'k':
if (parse_mode(&numask, optarg))
eerrorx("%s: invalid mode `%s'",
applet, optarg);
break;
case 'm': /* --respawn-max count */
n = sscanf(optarg, "%d", &respawn_max);
if (n != 1 || respawn_max < 1)
eerrorx("Invalid respawn-max value '%s'", optarg);
break;
case 'p': /* --pidfile <pid-file> */
pidfile = optarg;
break;
case 'R': /* --retry <schedule>|timeout */
retry = optarg;
break;
case 'r': /* --chroot /new/root */
ch_root = optarg;
break;
case 'u': /* --user <username>|<uid> */
{
p = optarg;
tmp = strsep(&p, ":");
changeuser = xstrdup(tmp);
if (sscanf(tmp, "%d", &tid) != 1)
pw = getpwnam(tmp);
else
pw = getpwuid((uid_t)tid);
if (pw == NULL)
eerrorx("%s: user `%s' not found",
applet, tmp);
uid = pw->pw_uid;
home = pw->pw_dir;
unsetenv("HOME");
if (pw->pw_dir)
setenv("HOME", pw->pw_dir, 1);
unsetenv("USER");
if (pw->pw_name)
setenv("USER", pw->pw_name, 1);
if (gid == 0)
gid = pw->pw_gid;
if (p) {
tmp = strsep (&p, ":");
if (sscanf(tmp, "%d", &tid) != 1)
gr = getgrnam(tmp);
else
gr = getgrgid((gid_t) tid);
if (gr == NULL)
eerrorx("%s: group `%s'"
" not found",
applet, tmp);
gid = gr->gr_gid;
}
}
break;
case '1': /* --stdout /path/to/stdout.lgfile */
redirect_stdout = optarg;
break;
case '2': /* --stderr /path/to/stderr.logfile */
redirect_stderr = optarg;
break;
case '3': /* --reexec */
reexec = true;
break;
case_RC_COMMON_GETOPT
}
endpwent();
argc -= optind;
argv += optind;
exec = *argv;
/* Expand ~ */
if (ch_dir && *ch_dir == '~')
ch_dir = expand_home(home, ch_dir);
if (ch_root && *ch_root == '~')
ch_root = expand_home(home, ch_root);
umask(numask);
if (!pidfile)
xasprintf(&pidfile, "/var/run/supervise-%s.pid", svcname);
xasprintf(&fifopath, "%s/supervise-%s.ctl", RC_SVCDIR, svcname);
if (mkfifo(fifopath, 0600) == -1 && errno != EEXIST)
eerrorx("%s: unable to create control fifo: %s",
applet, strerror(errno));
if (reexec) {
str = rc_service_value_get(svcname, "argc");
sscanf(str, "%d", &child_argc);
child_argv = xmalloc((child_argc + 1) * sizeof(char *));
memset(child_argv, 0, (child_argc + 1) * sizeof(char *));
for (x = 0; x < child_argc; x++) {
xasprintf(&varbuf, "argv_%d", x);
str = rc_service_value_get(svcname, varbuf);
child_argv[x] = str;
free(varbuf);
varbuf = NULL;
}
free(str);
str = rc_service_value_get(svcname, "child_pid");
sscanf(str, "%d", &child_pid);
free(str);
exec = rc_service_value_get(svcname, "exec");
pidfile = rc_service_value_get(svcname, "pidfile");
retry = rc_service_value_get(svcname, "retry");
if (retry) {
parse_schedule(applet, retry, sig);
rc_service_value_set(svcname, "retry", retry);
} else
parse_schedule(applet, NULL, sig);
str = rc_service_value_get(svcname, "respawn_delay");
sscanf(str, "%d", &respawn_delay);
str = rc_service_value_get(svcname, "respawn_max");
sscanf(str, "%d", &respawn_max);
supervisor(exec, child_argv);
} else if (start) {
if (exec) {
if (*exec == '~')
exec = expand_home(home, exec);
/* Validate that the binary exists if we are starting */
if (*exec == '/' || *exec == '.') {
/* Full or relative path */
if (ch_root)
xasprintf(&exec_file, "%s/%s", ch_root, exec);
else
xasprintf(&exec_file, "%s", exec);
} else {
/* Something in $PATH */
p = tmp = xstrdup(getenv("PATH"));
exec_file = NULL;
while ((token = strsep(&p, ":"))) {
if (ch_root)
xasprintf(&exec_file, "%s/%s/%s", ch_root, token, exec);
else
xasprintf(&exec_file, "%s/%s", token, exec);
if (exec_file && exists(exec_file))
break;
free(exec_file);
exec_file = NULL;
}
free(tmp);
}
if (!exists(exec_file)) {
eerror("%s: %s does not exist", applet,
*exec_file ? exec_file : exec);
free(exec_file);
exit(EXIT_FAILURE);
}
} else
eerrorx("%s: nothing to start", applet);
pid = get_pid(applet, pidfile);
if (pid != -1)
if (do_stop(applet, exec, (const char * const *)argv, pid, uid,
0, false, true) > 0)
eerrorx("%s: %s is already running", applet, exec);
if (respawn_period > 0 && respawn_delay * respawn_max > respawn_period)
ewarn("%s: Please increase the value of --respawn-period to more "
"than %d to avoid infinite respawning", applet,
respawn_delay * respawn_max);
if (retry) {
parse_schedule(applet, retry, sig);
rc_service_value_set(svcname, "retry", retry);
} else
parse_schedule(applet, NULL, sig);
einfov("Detaching to start `%s'", exec);
syslog(LOG_INFO, "Supervisor command line: %s", cmdline);
free(cmdline);
cmdline = NULL;
/* Remove existing pidfile */
if (pidfile)
unlink(pidfile);
/* Make sure we can write a pid file */
fp = fopen(pidfile, "w");
if (! fp)
eerrorx("%s: fopen `%s': %s", applet, pidfile, strerror(errno));
fclose(fp);
rc_service_value_set(svcname, "pidfile", pidfile);
varbuf = NULL;
xasprintf(&varbuf, "%i", respawn_delay);
rc_service_value_set(svcname, "respawn_delay", varbuf);
free(varbuf);
xasprintf(&varbuf, "%i", respawn_max);
rc_service_value_set(svcname, "respawn_max", varbuf);
free(varbuf);
xasprintf(&varbuf, "%i", respawn_period);
rc_service_value_set(svcname, "respawn_period", varbuf);
free(varbuf);
child_pid = fork();
if (child_pid == -1)
eerrorx("%s: fork: %s", applet, strerror(errno));
if (child_pid != 0)
/* first parent process, do nothing. */
exit(EXIT_SUCCESS);
#ifdef TIOCNOTTY
tty_fd = open("/dev/tty", O_RDWR);
#endif
devnull_fd = open("/dev/null", O_RDWR);
child_pid = fork();
if (child_pid == -1)
eerrorx("%s: fork: %s", applet, strerror(errno));
else if (child_pid != 0) {
c = argv;
x = 0;
while (c && *c) {
varbuf = NULL;
xasprintf(&varbuf, "argv_%-d",x);
rc_service_value_set(svcname, varbuf, *c);
free(varbuf);
varbuf = NULL;
x++;
c++;
}
xasprintf(&varbuf, "%d", x);
rc_service_value_set(svcname, "argc", varbuf);
free(varbuf);
rc_service_value_set(svcname, "exec", exec);
supervisor(exec, argv);
} else
child_process(exec, argv);
} else if (stop) {
pid = get_pid(applet, pidfile);
if (pid != -1) {
i = kill(pid, SIGTERM);
if (i != 0)
/* We failed to send the signal */
ewarn("Unable to shut down the supervisor");
else {
/* wait for the supervisor to go down */
while (kill(pid, 0) == 0) {
ts.tv_sec = 0;
ts.tv_nsec = 1;
nanosleep(&ts, NULL);
}
}
}
/* Even if we have not actually killed anything, we should
* remove information about it as it may have unexpectedly
* crashed out. We should also return success as the end
* result would be the same. */
if (pidfile && exists(pidfile))
unlink(pidfile);
if (svcname) {
rc_service_daemon_set(svcname, exec,
(const char *const *)argv,
pidfile, false);
rc_service_mark(svcname, RC_SERVICE_STOPPED);
}
exit(EXIT_SUCCESS);
} else if (sendsig) {
fifo_fd = open(fifopath, O_WRONLY |O_NONBLOCK);
if (fifo_fd < 0)
eerrorx("%s: unable to open control fifo %s", applet, strerror(errno));
xasprintf(&str, "sig %d", sig);
x = write(fifo_fd, str, strlen(str));
if (x == -1) {
free(tmp);
eerrorx("%s: error writing to control fifo: %s", applet,
strerror(errno));
}
free(tmp);
exit(EXIT_SUCCESS);
}
}