aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Hubbs <w.d.hubbs@gmail.com>2016-02-01 12:42:58 -0600
committerWilliam Hubbs <w.d.hubbs@gmail.com>2016-04-27 11:13:50 -0500
commit62410eaf4ba92516a58a550717d7f3faf63bb79f (patch)
treea521b050c262f76ea188eaba4ce1a5caec600c52
parentfd80b6fc67ec6a0fe4853167fb67ee40bb51b742 (diff)
add daemon supervisor
The supervise-daemon process is meant to be a lightweight supervisor which can monitor and restart a daemon if it crashes.
-rw-r--r--man/Makefile2
-rw-r--r--man/openrc-run.821
-rw-r--r--man/supervise-daemon.8142
-rw-r--r--sh/Makefile3
-rw-r--r--sh/openrc-run.sh.in4
-rw-r--r--sh/supervise-daemon.sh49
-rw-r--r--src/rc/.gitignore1
-rw-r--r--src/rc/Makefile8
-rw-r--r--src/rc/supervise-daemon.c722
-rw-r--r--supervise-daemon-guide.md45
10 files changed, 990 insertions, 7 deletions
diff --git a/man/Makefile b/man/Makefile
index 73db2a8e..48c58429 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 b23c5fe0..be15d595 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 00000000..06087675
--- /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.hubbs@gmail.com>
diff --git a/sh/Makefile b/sh/Makefile
index b9b9fb33..24c23159 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 fb6f95be..36bc3663 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 00000000..34e3ef71
--- /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 bbfede6a..c9779194 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 71ae5036..d4759e76 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 00000000..6bb75f3d
--- /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 00000000..7dae0e65
--- /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.