commit:     62410eaf4ba92516a58a550717d7f3faf63bb79f
Author:     William Hubbs <w.d.hubbs <AT> gmail <DOT> com>
AuthorDate: Mon Feb  1 18:42:58 2016 +0000
Commit:     William Hubbs <williamh <AT> gentoo <DOT> org>
CommitDate: Wed Apr 27 16:13:50 2016 +0000
URL:        https://gitweb.gentoo.org/proj/openrc.git/commit/?id=62410eaf

add daemon supervisor

The supervise-daemon process is meant to be a lightweight supervisor
which can monitor and restart a daemon if it crashes.

 man/Makefile              |   2 +-
 man/openrc-run.8          |  21 +-
 man/supervise-daemon.8    | 142 +++++++++
 sh/Makefile               |   3 +-
 sh/openrc-run.sh.in       |   4 +
 sh/supervise-daemon.sh    |  49 ++++
 src/rc/.gitignore         |   1 +
 src/rc/Makefile           |   8 +-
 src/rc/supervise-daemon.c | 722 ++++++++++++++++++++++++++++++++++++++++++++++
 supervise-daemon-guide.md |  45 +++
 10 files changed, 990 insertions(+), 7 deletions(-)

diff --git a/man/Makefile b/man/Makefile
index 73db2a8..48c5842 100644
--- a/man/Makefile
+++ b/man/Makefile
@@ -6,7 +6,7 @@ MAN3=           einfo.3 \
                rc_config.3 rc_deptree.3 rc_find_pids.3 rc_plugin_hook.3 \
                rc_runlevel.3 rc_service.3 rc_stringlist.3
 MAN8=          rc-service.8 rc-status.8 rc-update.8 openrc.8 openrc-run.8 \
-               service.8 start-stop-daemon.8
+               service.8 start-stop-daemon.8 supervise-daemon.8
 
 ifeq (${OS},Linux)
 MAN8 += rc-sstat.8

diff --git a/man/openrc-run.8 b/man/openrc-run.8
index b23c5fe..be15d59 100644
--- a/man/openrc-run.8
+++ b/man/openrc-run.8
@@ -95,10 +95,17 @@ String describing the service.
 .It Ar description_$command
 String describing the extra command.
 .It Ar supervisor
-Supervisor to use to monitor this daemon. If this is unset,
-start-stop-daemon will be used. The only alternate supervisor we support
-in this release is S6 from Skarnet software. To use this, set
+Supervisor to use to monitor this daemon. If this is unset or invalid,
+start-stop-daemon will be used.
+Currently, we support s6 from scarnet software, and supervise-daemon
+which is a light-weight supervisor internal to OpenRC.
+To use s6, set
 supervisor=s6.
+or set
+supervisor=supervise-daemon
+to use supervise-daemon.
+Note that supervise-daemon is still in early development, so it is
+considered experimental.
 .It Ar s6_service_path
 The path to the s6 service directory if you are monitoring this service
 with S6. The default is /var/svc.d/${RC_SVCNAME}.
@@ -112,10 +119,16 @@ List of arguments passed to start-stop-daemon when 
starting the daemon.
 .It Ar command
 Daemon to start or stop via
 .Nm start-stop-daemon
+or
+.Nm supervise-daemon
 if no start or stop function is defined by the service.
 .It Ar command_args
 List of arguments to pass to the daemon when starting via
 .Nm start-stop-daemon .
+.It Ar command_args_foreground
+List of arguments to pass to the daemon when starting via
+.Nm supervise-daemon .
+to force the daemon to stay in the foreground
 .It Ar command_background
 Set this to "true", "yes" or "1" (case-insensitive) to force the daemon into
 the background. This implies the "--make-pidfile" and "--pidfile" option of
@@ -123,6 +136,8 @@ the background. This implies the "--make-pidfile" and 
"--pidfile" option of
 so the pidfile variable must be set.
 .It Ar chroot
 .Xr start-stop-daemon 8
+and
+.Xr supervise-daemon 8
 will chroot into this path before writing the pid file or starting the daemon.
 .It Ar pidfile
 Pidfile to use for the above defined command.

diff --git a/man/supervise-daemon.8 b/man/supervise-daemon.8
new file mode 100644
index 0000000..0608767
--- /dev/null
+++ b/man/supervise-daemon.8
@@ -0,0 +1,142 @@
+.\" Copyright (c) 2007-2015 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.
+.\"
+.Dd April 27, 2016
+.Dt supervise-DAEMON 8 SMM
+.Os OpenRC
+.Sh NAME
+.Nm supervise-daemon
+.Nd starts a daemon and restarts it if it crashes
+.Sh SYNOPSIS
+.Nm
+.Fl d , -chdir
+.Ar path
+.Fl e , -env
+.Ar var=value
+.Fl g , -group
+.Ar group
+.Fl I , -ionice
+.Ar arg
+.Fl k , -umask
+.Ar value
+.Fl N , -nicelevel
+.Ar level
+.Fl p , -pidfile
+.Ar pidfile
+.Fl u , -user
+.Ar user
+.Fl r , -chroot
+.Ar chrootpath
+.Fl 1 , -stdout
+.Ar logfile
+.Fl 2 , -stderr
+.Ar logfile
+.Fl S , -start
+.Ar daemon
+.Op Fl -
+.Op Ar arguments
+.Nm
+.Fl K , -stop
+.Ar daemon
+.Fl p , -pidfile
+.Ar pidfile
+.Fl r , -chroot
+.Ar chrootpath
+.Sh DESCRIPTION
+.Nm
+provides a consistent method of starting, stopping and restarting
+daemons. If
+.Fl K , -stop
+is not provided, then we assume we are starting the daemon.
+.Nm
+only works with daemons which do not fork. Also, it uses its own pid
+file, so the daemon should not write a pid file, or the pid file passed
+to 
+.Nm
+should not be the one the daemon writes.
+.Pp
+Here are the options to specify the daemon and how it should start or stop:
+.Bl -tag -width indent
+.It Fl p , -pidfile Ar pidfile
+When starting, we write a
+.Ar pidfile
+so we know which supervisor to stop.  When stopping we only stop the pid(s)
+listed in the
+.Ar pidfile .
+.It Fl u , -user Ar user Ns Op : Ns Ar group
+Start the daemon as the
+.Ar user
+and update $HOME accordingly or stop daemons
+owned by the user. You can optionally append a
+.Ar group
+name here also.
+.It Fl v , -verbose
+Print the action(s) that are taken just before doing them.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d , -chdir Ar path
+chdir to this directory before starting the daemon.
+.It Fl e , -env Ar VAR=VALUE
+Set the environment variable VAR to VALUE.
+.It Fl g , -group Ar group
+Start the daemon as in the group.
+.It Fl I , -ionice Ar class Ns Op : Ns Ar data
+Modifies the IO scheduling priority of the daemon.
+Class can be 0 for none, 1 for real time, 2 for best effort and 3 for idle.
+Data can be from 0 to 7 inclusive.
+.It Fl k , -umask Ar mode
+Set the umask of the daemon.
+.It Fl N , -nicelevel Ar level
+Modifies the scheduling priority of the daemon.
+.It Fl r , -chroot Ar path
+chroot to this directory before starting the daemon. All other paths, such
+as the path to the daemon, chdir and pidfile, should be relative to the chroot.
+.It Fl u , -user Ar user
+Start the daemon as the specified user.
+.It Fl 1 , -stdout Ar logfile
+Redirect the standard output of the process to logfile.
+Must be an absolute pathname, but relative to the path optionally given with
+.Fl r , -chroot .
+The logfile can also be a named pipe.
+.It Fl 2 , -stderr Ar logfile
+The same thing as
+.Fl 1 , -stdout
+but with the standard error output.
+.El
+.Sh ENVIRONMENT
+.Va SSD_NICELEVEL
+can also set the scheduling priority of the daemon, but the command line
+option takes precedence.
+.Sh NOTE
+.Nm
+uses
+.Xr getopt 3
+to parse its options, which allows it to accept the `--' option which will
+cause it to stop processing options at that point. Any subsequent arguments
+are passed as arguments to the daemon to start and used when finding a daemon
+to stop or signal.
+.Sh SEE ALSO
+.Xr chdir 2 ,
+.Xr chroot 2 ,
+.Xr getopt 3 ,
+.Xr nice 2 ,
+.Xr rc_find_pids 3
+.Sh BUGS
+.Nm
+cannot stop an interpreted daemon that no longer exists without a pidfile.
+.Sh HISTORY
+.Nm
+first appeared in Debian.
+.Pp
+This is a complete re-implementation with the process finding code in the
+OpenRC library (librc, -lrc) so other programs can make use of it.
+.Sh AUTHORS
+.An William Hubbs <w.d.hu...@gmail.com>

diff --git a/sh/Makefile b/sh/Makefile
index b9b9fb3..24c2315 100644
--- a/sh/Makefile
+++ b/sh/Makefile
@@ -1,7 +1,8 @@
 DIR=   ${LIBEXECDIR}/sh
 SRCS=  init.sh.in functions.sh.in gendepends.sh.in \
        openrc-run.sh.in rc-functions.sh.in tmpfiles.sh.in ${SRCS-${OS}}
-INC=   rc-mount.sh functions.sh rc-functions.sh s6.sh start-stop-daemon.sh
+INC=   functions.sh rc-mount.sh rc-functions.sh s6.sh start-stop-daemon.sh \
+               supervise-daemon.sh
 BIN=   gendepends.sh init.sh openrc-run.sh tmpfiles.sh ${BIN-${OS}}
 
 INSTALLAFTER=  _installafter

diff --git a/sh/openrc-run.sh.in b/sh/openrc-run.sh.in
index fb6f95b..36bc366 100644
--- a/sh/openrc-run.sh.in
+++ b/sh/openrc-run.sh.in
@@ -154,6 +154,7 @@ start()
        local func=ssd_start
        case "$supervisor" in
                s6) func=s6_start ;;
+               supervise-daemon) func=supervise_start ;;
                ?*)
                        ewarn "Invalid supervisor, \"$supervisor\", using 
start-stop-daemon"
                        ;;
@@ -166,6 +167,7 @@ stop()
        local func=ssd_stop
        case "$supervisor" in
                s6) func=s6_stop ;;
+               supervise-daemon) func=supervise_stop ;;
                ?*)
                        ewarn "Invalid supervisor, \"$supervisor\", using 
start-stop-daemon"
                        ;;
@@ -178,6 +180,7 @@ status()
        local func=ssd_status
        case "$supervisor" in
                s6) func=s6_status ;;
+               supervise-daemon) func=supervise_status ;;
                ?*)
                        ewarn "Invalid supervisor, \"$supervisor\", using 
start-stop-daemon"
                        ;;
@@ -215,6 +218,7 @@ fi
 # load service supervisor functions
 sourcex "@LIBEXECDIR@/sh/s6.sh"
 sourcex "@LIBEXECDIR@/sh/start-stop-daemon.sh"
+sourcex "@LIBEXECDIR@/sh/supervise-daemon.sh"
 
 # Set verbose mode
 if yesno "${rc_verbose:-$RC_VERBOSE}"; then

diff --git a/sh/supervise-daemon.sh b/sh/supervise-daemon.sh
new file mode 100644
index 0000000..34e3ef7
--- /dev/null
+++ b/sh/supervise-daemon.sh
@@ -0,0 +1,49 @@
+# start / stop / status functions for supervise-daemon
+
+# 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.
+
+supervise_start()
+{
+       if [ -z "$command" ]; then
+               ewarn "The command variable is undefined."
+               ewarn "There is nothing for ${name:-$RC_SVCNAME} to start."
+               return 1
+       fi
+
+       ebegin "Starting ${name:-$RC_SVCNAME}"
+       eval supervise-daemon --start \
+               ${pidfile:+--pidfile} $pidfile \
+               ${command_user+--user} $command_user \
+               $supervise_daemon_args \
+               $command \
+               -- $command_args $command_args_foreground
+       rc=$?
+       [ -n "${pidfile}" ] && service_set_value "pidfile" "${pidfile}"
+       eend $rc "failed to start $RC_SVCNAME"
+}
+
+supervise_stop()
+{
+       local startpidfile="$(service_get_value "pidfile")"
+       pidfile="${startpidfile:-$pidfile}"
+       [ -n "$pidfile" ] || return 0
+       ebegin "Stopping ${name:-$RC_SVCNAME}"
+       supervise-daemon --stop \
+               ${pidfile:+--pidfile} $pidfile \
+               ${stopsig:+--signal} $stopsig
+
+       eend $? "Failed to stop $RC_SVCNAME"
+}
+
+supervise_status()
+{
+       _status
+}

diff --git a/src/rc/.gitignore b/src/rc/.gitignore
index bbfede6..c977919 100644
--- a/src/rc/.gitignore
+++ b/src/rc/.gitignore
@@ -5,6 +5,7 @@ rc-update
 runscript
 service
 start-stop-daemon
+supervise-daemon
 einfon
 einfo
 ewarnn

diff --git a/src/rc/Makefile b/src/rc/Makefile
index 71ae503..d4759e7 100644
--- a/src/rc/Makefile
+++ b/src/rc/Makefile
@@ -3,7 +3,7 @@ SRCS=   checkpath.c do_e.c do_mark_service.c do_service.c \
                mountinfo.c openrc-run.c rc-abort.c rc.c \
                rc-depend.c rc-logger.c rc-misc.c rc-plugin.c \
                rc-service.c rc-status.c rc-update.c \
-               shell_var.c start-stop-daemon.c swclock.c _usage.c
+               shell_var.c start-stop-daemon.c supervise-daemon.c swclock.c 
_usage.c
 
 ifeq (${MKSELINUX},yes)
 SRCS+=         rc-selinux.c
@@ -16,7 +16,8 @@ SBINDIR=      ${PREFIX}/sbin
 LINKDIR=       ${LIBEXECDIR}
 
 BINPROGS=      rc-status
-SBINPROGS = openrc openrc-run rc rc-service rc-update runscript service 
start-stop-daemon
+SBINPROGS = openrc openrc-run rc rc-service rc-update runscript service \
+                       start-stop-daemon supervise-daemon
 RC_BINPROGS=   einfon einfo ewarnn ewarn eerrorn eerror ebegin eend ewend \
                                eindent eoutdent esyslog eval_ecolors ewaitfile 
\
                                veinfo vewarn vebegin veend vewend veindent 
veoutdent \
@@ -136,6 +137,9 @@ rc-update: rc-update.o _usage.o rc-misc.o
 start-stop-daemon: start-stop-daemon.o _usage.o rc-misc.o
        ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ 
${LDADD}
 
+supervise-daemon: supervise-daemon.o _usage.o rc-misc.o
+       ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ 
${LDADD}
+
 service_get_value service_set_value get_options save_options: do_value.o 
rc-misc.o
        ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ 
${LDADD}
 

diff --git a/src/rc/supervise-daemon.c b/src/rc/supervise-daemon.c
new file mode 100644
index 0000000..6bb75f3
--- /dev/null
+++ b/src/rc/supervise-daemon.c
@@ -0,0 +1,722 @@
+/*
+ * 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 "_usage.h"
+
+const char *applet = NULL;
+const char *extraopts = NULL;
+const char *getoptstring = "d:e:g:I:Kk:N:p:r:Su:1:2:" \
+       getoptstring_COMMON;
+const struct option longopts[] = {
+       { "chdir",        1, NULL, 'd'},
+       { "env",          1, NULL, 'e'},
+       { "group",        1, NULL, 'g'},
+       { "ionice",       1, NULL, 'I'},
+       { "stop",         0, NULL, 'K'},
+       { "umask",        1, NULL, 'k'},
+       { "nicelevel",    1, NULL, 'N'},
+       { "pidfile",      1, NULL, 'p'},
+       { "user",         1, NULL, 'u'},
+       { "chroot",       1, NULL, 'r'},
+       { "start",        0, NULL, 'S'},
+       { "stdout",       1, NULL, '1'},
+       { "stderr",       1, NULL, '2'},
+       longopts_COMMON
+};
+const char * const longopts_help[] = {
+       "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 a nicelevel when starting",
+       "Match pid found in this file",
+       "Change the process user",
+       "Chroot to this directory",
+       "Start daemon",
+       "Redirect stdout to file",
+       "Redirect stderr to file",
+       longopts_help_COMMON
+};
+const char *usagestring = NULL;
+
+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;
+static bool exiting = false;
+#ifdef TIOCNOTTY
+static int tty_fd = -1;
+#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, int who, int ioprio)
+{
+#ifdef SYS_ioprio_set
+       return syscall(SYS_ioprio_set, which, who, ioprio);
+#else
+       return 0;
+#endif
+}
+#endif
+
+static void cleanup(void)
+{
+       free(changeuser);
+}
+
+static pid_t get_pid(const char *pidfile)
+{
+       FILE *fp;
+       pid_t pid;
+
+       if (! pidfile)
+               return -1;
+
+       if ((fp = fopen(pidfile, "r")) == NULL) {
+               ewarnv("%s: fopen `%s': %s", applet, pidfile, strerror(errno));
+               return -1;
+       }
+
+       if (fscanf(fp, "%d", &pid) != 1) {
+               ewarnv("%s: no pid found in `%s'", applet, pidfile);
+               fclose(fp);
+               return -1;
+       }
+
+       fclose(fp);
+
+       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 **c;
+       char cmdline[PATH_MAX];
+
+#ifdef HAVE_PAM
+       pam_handle_t *pamh = NULL;
+       int pamr;
+       const char *const *pamenv = NULL;
+#endif
+
+       setsid();
+
+       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("start-stop-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();
+
+#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)
+               close(i);
+
+       *cmdline = '\0';
+       c = argv;
+       while (*c) {
+               strcat(cmdline, *c);
+               strcat(cmdline, " ");
+               c++;
+       }
+       syslog(LOG_INFO, "Running command line: %s", 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 handle_signal(int sig)
+{
+       int serrno = errno;
+       char signame[10] = { '\0' };
+
+       switch (sig) {
+       case SIGINT:
+               snprintf(signame, sizeof(signame), "SIGINT");
+               break;
+       case SIGTERM:
+               snprintf(signame, sizeof(signame), "SIGTERM");
+               break;
+       case SIGQUIT:
+               snprintf(signame, sizeof(signame), "SIGQUIT");
+               break;
+       }
+
+       if (*signame != 0) {
+               syslog(LOG_INFO, "%s: caught signal %s, exiting", applet, 
signame);
+               exiting = true;
+       } else
+               syslog(LOG_INFO, "%s: caught unknown signal %d", applet, sig);
+
+       /* Restore errno */
+       errno = serrno;
+}
+
+static char * expand_home(const char *home, const char *path)
+{
+       char *opath, *ppath, *p, *nh;
+       size_t len;
+       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);
+       }
+
+       len = strlen(ppath) + strlen(home) + 1;
+       nh = xmalloc(len);
+       snprintf(nh, len, "%s%s", home, ppath);
+       free(opath);
+       return nh;
+}
+
+int main(int argc, char **argv)
+{
+       int opt;
+       bool start = false;
+       bool stop = false;
+       char *exec = NULL;
+       char *pidfile = NULL;
+       char *home = NULL;
+       int tid = 0;
+       pid_t child_pid, pid;
+       char *svcname = getenv("RC_SVCNAME");
+       char *tmp;
+       char *p;
+       char *token;
+       int i;
+       char exec_file[PATH_MAX];
+       struct passwd *pw;
+       struct group *gr;
+       FILE *fp;
+       mode_t numask = 022;
+
+       applet = basename_c(argv[0]);
+       atexit(cleanup);
+
+       signal_setup(SIGINT, handle_signal);
+       signal_setup(SIGQUIT, handle_signal);
+       signal_setup(SIGTERM, handle_signal);
+       openlog(applet, LOG_PID, LOG_DAEMON);
+
+       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;
+                       }
+               }
+       }
+
+       while ((opt = getopt_long(argc, argv, getoptstring, longopts,
+                   (int *) 0)) != -1)
+               switch (opt) {
+               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 '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 'k':
+                       if (parse_mode(&numask, optarg))
+                               eerrorx("%s: invalid mode `%s'",
+                                   applet, optarg);
+                       break;
+
+               case 'p':  /* --pidfile <pid-file> */
+                       pidfile = 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_RC_COMMON_GETOPT
+               }
+
+       if (!pidfile)
+               eerrorx("%s: --pidfile must be specified", applet);
+
+       endpwent();
+       argc -= optind;
+       argv += optind;
+       exec = *argv;
+
+       if (start) {
+               if (!exec)
+                       eerrorx("%s: nothing to start", applet);
+       }
+
+       /* 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);
+       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)
+                               snprintf(exec_file, sizeof(exec_file),
+                                   "%s/%s", ch_root, exec);
+                       else
+                               snprintf(exec_file, sizeof(exec_file),
+                                   "%s", exec);
+               } else {
+                       /* Something in $PATH */
+                       p = tmp = xstrdup(getenv("PATH"));
+                       *exec_file = '\0';
+                       while ((token = strsep(&p, ":"))) {
+                               if (ch_root)
+                                       snprintf(exec_file, sizeof(exec_file),
+                                           "%s/%s/%s",
+                                           ch_root, token, exec);
+                               else
+                                       snprintf(exec_file, sizeof(exec_file),
+                                           "%s/%s", token, exec);
+                               if (exists(exec_file))
+                                       break;
+                               *exec_file = '\0';
+                       }
+                       free(tmp);
+               }
+       }
+       if (start && !exists(exec_file))
+               eerrorx("%s: %s does not exist", applet,
+                   *exec_file ? exec_file : exec);
+
+       if (stop) {
+               pid = get_pid(pidfile);
+               if (pid == -1)
+                       i = pid;
+               else
+                       i = kill(pid, SIGTERM);
+               if (i != 0)
+                       /* We failed to stop something */
+                       exit(EXIT_FAILURE);
+
+               /* 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);
+               exit(EXIT_SUCCESS);
+       }
+
+       pid = get_pid(pidfile);
+       if (pid != -1)
+               if (kill(pid, 0) == 0)
+                       eerrorx("%s: %s is already running", applet, exec);
+
+       einfov("Detaching to start `%s'", exec);
+       eindentv();
+
+       /* 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);
+
+       child_pid = fork();
+       if (child_pid == -1)
+               eerrorx("%s: fork: %s", applet, strerror(errno));
+
+       /* first parent process, do nothing. */
+       if (child_pid != 0)
+               exit(EXIT_SUCCESS);
+
+       child_pid = fork();
+       if (child_pid == -1)
+               eerrorx("%s: fork: %s", applet, strerror(errno));
+
+       if (child_pid != 0) {
+               /* this is the supervisor */
+               umask(numask);
+
+#ifdef TIOCNOTTY
+               tty_fd = open("/dev/tty", O_RDWR);
+#endif
+
+               devnull_fd = open("/dev/null", O_RDWR);
+
+               fp = fopen(pidfile, "w");
+               if (! fp)
+                       eerrorx("%s: fopen `%s': %s", applet, pidfile, 
strerror(errno));
+               fprintf(fp, "%d\n", getpid());
+               fclose(fp);
+
+               /*
+                * Supervisor main loop
+                */
+               i = 0;
+               while (!exiting) {
+                       wait(&i);
+                       if (exiting) {
+                               syslog(LOG_INFO, "stopping %s, pid %d", exec, 
child_pid);
+                               kill(child_pid, SIGTERM);
+                       } else {
+                               syslog(LOG_INFO, "%s, pid %d, terminated 
unexpectedly",
+                                               exec, child_pid);
+                               child_pid = fork();
+                               if (child_pid == -1)
+                                       eerrorx("%s: fork: %s", applet, 
strerror(errno));
+                               if (child_pid == 0)
+                                       child_process(exec, argv);
+                       }
+               }
+
+               if (svcname)
+                       rc_service_daemon_set(svcname, exec,
+                                                                       (const 
char * const *) argv, pidfile, true);
+
+               exit(EXIT_SUCCESS);
+       } else if (child_pid == 0)
+               child_process(exec, argv);
+}

diff --git a/supervise-daemon-guide.md b/supervise-daemon-guide.md
new file mode 100644
index 0000000..7dae0e6
--- /dev/null
+++ b/supervise-daemon-guide.md
@@ -0,0 +1,45 @@
+# Using supervise-daemon
+
+Beginning with OpenRC-0.21 we have our own daemon supervisor,
+supervise-daemon., which can start a daemon and restart it if it
+terminates unexpectedly.
+
+## Use Default start, stop and status functions
+
+If you write your own start, stop and status functions in your service
+script, none of this will work. You must allow OpenRC to use the default
+functions.
+
+## Daemons must not fork
+
+Any deamon that you would like to have monitored by supervise-daemon
+must not fork. Instead, it must stay in the foreground. If the daemon
+itself forks, the supervisor will be unable to monitor it.
+
+If the daemon has an option to instruct it not to fork, you should add this
+to the command_args_foreground variable listed below.
+
+## Variable Settings
+
+The most important setting is the supervisor variable. At the top of
+your service script, you should set this variable as follows:
+
+supervisor=supervise-daemon
+
+Several other variables affect the way services behave under
+supervise-daemon. They are documented on the  openrc-run man page, but I
+will list them here for convenience:
+
+pidfile=/pid/of/supervisor.pid
+
+If you are using start-stop-daemon to monitor your scripts, the pidfile
+is the path to the pidfile the daemon creates. If, on the other hand,
+you are using supervise-daemon, this is the path to the pidfile the
+supervisor creates.
+
+command_args_foreground should be used if the daemon you want to monitor
+forks and goes to the background by default. This should be set to the
+command line option that instructs the daemon to stay in the foreground.
+
+This is very early support, so feel free to file bugs if you have
+issues.

Reply via email to