diff options
Diffstat (limited to 'src/supervise-daemon/supervise-daemon.c')
-rw-r--r-- | src/supervise-daemon/supervise-daemon.c | 1250 |
1 files changed, 1250 insertions, 0 deletions
diff --git a/src/supervise-daemon/supervise-daemon.c b/src/supervise-daemon/supervise-daemon.c new file mode 100644 index 00000000..194ef9df --- /dev/null +++ b/src/supervise-daemon/supervise-daemon.c @@ -0,0 +1,1250 @@ +/* + * supervise-daemon + * This is a 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/HEAD/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/HEAD/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 */ +#include <sys/prctl.h> /* For prctl */ +#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 + +#ifdef HAVE_CAP +#include <sys/capability.h> +#endif + +#include "einfo.h" +#include "queue.h" +#include "rc.h" +#include "misc.h" +#include "plugin.h" +#include "schedules.h" +#include "_usage.h" +#include "helpers.h" + +/* Use long option value that is out of range for 8 bit getopt values. + * The exact enum value is internal and can freely change, so we keep the + * options sorted. + */ +enum { + /* This has to come first so following values stay in the 0x100+ range. */ + LONGOPT_BASE = 0x100, + + LONGOPT_CAPABILITIES, + LONGOPT_OOM_SCORE_ADJ, + LONGOPT_NO_NEW_PRIVS, + LONGOPT_SECBITS, +}; + +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'}, + { "capabilities", 1, NULL, LONGOPT_CAPABILITIES}, + { "secbits", 1, NULL, LONGOPT_SECBITS}, + { "no-new-privs", 0, NULL, LONGOPT_NO_NEW_PRIVS}, + { "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'}, + { "oom-score-adj",1, NULL, LONGOPT_OOM_SCORE_ADJ}, + { "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 the inheritable, ambient and bounding capabilities", + "Set the security-bits for the program", + "Set the No New Privs flag for the program", + "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", + "Set OOM score adjustment 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 = INT_MIN; +static int ionicec = -1; +static int ioniced = 0; +static int oom_score_adj = INT_MIN; +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; +static bool verbose = false; +#ifdef HAVE_CAP +static cap_iab_t cap_iab = NULL; +static unsigned secbits = 0; +#endif +#ifdef PR_SET_NO_NEW_PRIVS +static bool no_new_privs = false; +#endif + +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; + pid_t pid; + + switch (sig) { + case SIGALRM: + do_healthcheck = 1; + break; + case SIGCHLD: + if (exiting) + while (waitpid((pid_t)(-1), NULL, WNOHANG) > 0) {} + else { + while ((pid = waitpid((pid_t)(-1), NULL, WNOHANG|WNOWAIT)) > 0) { + if (pid == child_pid) + break; + pid = waitpid(pid, NULL, WNOHANG); + } + } + 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 pid_t exec_command(const char *cmd) +{ + char *file; + pid_t pid = -1; + sigset_t full; + sigset_t old; + struct sigaction sa; + + file = rc_service_resolve(svcname); + if (!exists(file)) { + free(file); + return 0; + } + + /* We need to block signals until we have forked */ + memset(&sa, 0, sizeof (sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + + pid = fork(); + if (pid == 0) { + /* Restore default handlers */ + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGWINCH, &sa, NULL); + + /* Unmask signals */ + sigprocmask(SIG_SETMASK, &old, NULL); + + /* Safe to run now */ + execl(file, file, cmd, (char *) NULL); + syslog(LOG_ERR, "unable to exec `%s': %s\n", + file, strerror(errno)); + _exit(EXIT_FAILURE); + } + + if (pid == -1) + syslog(LOG_ERR, "fork: %s\n",strerror (errno)); + + sigprocmask(SIG_SETMASK, &old, NULL); + free(file); + return pid; +} + +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]; + FILE *fp; + +#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 != INT_MIN) { + 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 (oom_score_adj != INT_MIN) { + fp = fopen("/proc/self/oom_score_adj", "w"); + if (!fp) + eerrorx("%s: oom_score_adj %d: %s", applet, + oom_score_adj, strerror(errno)); + fprintf(fp, "%d\n", oom_score_adj); + fclose(fp); + } + + 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); +#ifdef HAVE_CAP + if (uid && cap_setuid(uid)) +#else + if (uid && setuid(uid)) +#endif + eerrorx ("%s: unable to set userid to %d", applet, uid); + + /* Close any fd's to the passwd database */ + endpwent(); + +#ifdef HAVE_CAP + if (cap_iab != NULL) { + i = cap_iab_set_proc(cap_iab); + + if (cap_free(cap_iab) != 0) + eerrorx("Could not releasable memory: %s", strerror(errno)); + + if (i != 0) + eerrorx("Could not set iab: %s", strerror(errno)); + } + + if (secbits != 0) { + if (cap_set_secbits(secbits) < 0) + eerrorx("Could not set securebits to 0x%x: %s", secbits, strerror(errno)); + } +#endif + +#ifdef PR_SET_NO_NEW_PRIVS + if (no_new_privs) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) + eerrorx("Could not set No New Privs flag: %s", strerror(errno)); + } +#endif + + /* 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=", 11) != 0 && + strncmp(env->value, "RC_SVCNAME=", 11) != 0) || + strncmp(env->value, "SSD_NICELEVEL=", 14) == 0 || + strncmp(env->value, "SSD_IONICELEVEL=", 16) == 0 || + strncmp(env->value, "SSD_OOM_SCORE_ADJ=", 18) == 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); + failing = 0; + while (!exiting) { + healthcheck_respawn = 0; + fifo_fd = open(fifopath, O_RDONLY); + if (fifo_fd > 0) { + memset(buf, 0, sizeof(buf)); + count = read(fifo_fd, buf, sizeof(buf) - 1); + close(fifo_fd); + if (count != -1) + buf[count] = 0; + if (count == 0) + continue; + if (verbose) + 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); + } + } + continue; + } + if (do_healthcheck) { + do_healthcheck = 0; + alarm(0); + if (verbose) + syslog(LOG_DEBUG, "running health check for %s", svcname); + health_pid = exec_command("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_command("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; + } + wait_pid = waitpid(child_pid, &i, WNOHANG); + 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); + if (exiting) + continue; + 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 || 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); + if ((tmp = getenv("SSD_IONICELEVEL"))) { + int n = sscanf(tmp, "%d:%d", &ionicec, &ioniced); + if (n != 1 && n != 2) + eerror("%s: invalid ionice level `%s' (SSD_IONICELEVEL)", + applet, tmp); + if (ionicec == 0) + ioniced = 0; + else if (ionicec == 3) + ioniced = 7; + ionicec <<= 13; /* class shift */ + } + if ((tmp = getenv("SSD_OOM_SCORE_ADJ"))) + if (sscanf(tmp, "%d", &oom_score_adj) != 1) + eerror("%s: invalid oom_score_adj `%s' (SSD_OOM_SCORE_ADJ)", + 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 LONGOPT_CAPABILITIES: +#ifdef HAVE_CAP + cap_iab = cap_iab_from_text(optarg); + if (cap_iab == NULL) + eerrorx("Could not parse iab: %s", strerror(errno)); +#else + eerrorx("Capabilities support not enabled"); +#endif + break; + + case LONGOPT_SECBITS: +#ifdef HAVE_CAP + if (*optarg == '\0') + eerrorx("Secbits are empty"); + + tmp = NULL; + secbits = strtoul(optarg, &tmp, 0); + if (*tmp != '\0') + eerrorx("Could not parse secbits: invalid char %c", *tmp); +#else + eerrorx("Capabilities support not enabled"); +#endif + break; + + case LONGOPT_NO_NEW_PRIVS: +#ifdef PR_SET_NO_NEW_PRIVS + no_new_privs = true; +#else + eerrorx("The No New Privs flag is only supported by Linux (since 3.5)"); +#endif + 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 LONGOPT_OOM_SCORE_ADJ: /* --oom-score-adj */ + if (sscanf(optarg, "%d", &oom_score_adj) != 1) + eerrorx("%s: invalid oom-score-adj `%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 < 0) + 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> */ + { + char dummy[2]; + p = optarg; + tmp = strsep(&p, ":"); + changeuser = xstrdup(tmp); + if (sscanf(tmp, "%d%1s", &tid, dummy) != 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%1s", &tid, dummy) != 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 + } + + verbose = rc_yesno(getenv ("EINFO_VERBOSE")); + 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); + dup2(devnull_fd, STDIN_FILENO); + dup2(devnull_fd, STDOUT_FILENO); + dup2(devnull_fd, STDERR_FILENO); + 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); + } +} |