aboutsummaryrefslogtreecommitdiff
path: root/src/supervise-daemon/supervise-daemon.c
diff options
context:
space:
mode:
authorWilliam Hubbs <w.d.hubbs@gmail.com>2022-04-06 10:51:55 -0500
committerWilliam Hubbs <w.d.hubbs@gmail.com>2022-04-06 10:51:55 -0500
commit391d12db48754861b5cecac92ee3321597ee02c1 (patch)
treeb42fad5a31ca342de7b7ecf1fb78784194c1400c /src/supervise-daemon/supervise-daemon.c
parent0efc1b133e4182bd53cde78153bd8b5cc2e99448 (diff)
migrate fully to meson build system
- drop old build system - move shared include and source files to common directory - drop "rc-" prefix from shared include and source files - move executable-specific code to individual directories under src - adjust top-level .gitignore file for new build system This closes #489.
Diffstat (limited to 'src/supervise-daemon/supervise-daemon.c')
-rw-r--r--src/supervise-daemon/supervise-daemon.c1250
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);
+ }
+}