add daemon supervisor

The supervise-daemon process is meant to be a lightweight supervisor
which can monitor and restart a daemon if it crashes.
This commit is contained in:
William Hubbs 2016-02-01 12:42:58 -06:00
parent fd80b6fc67
commit 62410eaf4b
10 changed files with 990 additions and 7 deletions

View File

@ -6,7 +6,7 @@ MAN3= einfo.3 \
rc_config.3 rc_deptree.3 rc_find_pids.3 rc_plugin_hook.3 \ rc_config.3 rc_deptree.3 rc_find_pids.3 rc_plugin_hook.3 \
rc_runlevel.3 rc_service.3 rc_stringlist.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 \ 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) ifeq (${OS},Linux)
MAN8 += rc-sstat.8 MAN8 += rc-sstat.8

View File

@ -95,10 +95,17 @@ String describing the service.
.It Ar description_$command .It Ar description_$command
String describing the extra command. String describing the extra command.
.It Ar supervisor .It Ar supervisor
Supervisor to use to monitor this daemon. If this is unset, Supervisor to use to monitor this daemon. If this is unset or invalid,
start-stop-daemon will be used. The only alternate supervisor we support start-stop-daemon will be used.
in this release is S6 from Skarnet software. To use this, set 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. 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 .It Ar s6_service_path
The path to the s6 service directory if you are monitoring this service The path to the s6 service directory if you are monitoring this service
with S6. The default is /var/svc.d/${RC_SVCNAME}. 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 .It Ar command
Daemon to start or stop via Daemon to start or stop via
.Nm start-stop-daemon .Nm start-stop-daemon
or
.Nm supervise-daemon
if no start or stop function is defined by the service. if no start or stop function is defined by the service.
.It Ar command_args .It Ar command_args
List of arguments to pass to the daemon when starting via List of arguments to pass to the daemon when starting via
.Nm start-stop-daemon . .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 .It Ar command_background
Set this to "true", "yes" or "1" (case-insensitive) to force the daemon into 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 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. so the pidfile variable must be set.
.It Ar chroot .It Ar chroot
.Xr start-stop-daemon 8 .Xr start-stop-daemon 8
and
.Xr supervise-daemon 8
will chroot into this path before writing the pid file or starting the daemon. will chroot into this path before writing the pid file or starting the daemon.
.It Ar pidfile .It Ar pidfile
Pidfile to use for the above defined command. Pidfile to use for the above defined command.

142
man/supervise-daemon.8 Normal file
View File

@ -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>

View File

@ -1,7 +1,8 @@
DIR= ${LIBEXECDIR}/sh DIR= ${LIBEXECDIR}/sh
SRCS= init.sh.in functions.sh.in gendepends.sh.in \ SRCS= init.sh.in functions.sh.in gendepends.sh.in \
openrc-run.sh.in rc-functions.sh.in tmpfiles.sh.in ${SRCS-${OS}} 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}} BIN= gendepends.sh init.sh openrc-run.sh tmpfiles.sh ${BIN-${OS}}
INSTALLAFTER= _installafter INSTALLAFTER= _installafter

View File

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

49
sh/supervise-daemon.sh Normal file
View File

@ -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
}

1
src/rc/.gitignore vendored
View File

@ -5,6 +5,7 @@ rc-update
runscript runscript
service service
start-stop-daemon start-stop-daemon
supervise-daemon
einfon einfon
einfo einfo
ewarnn ewarnn

View File

@ -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 \ mountinfo.c openrc-run.c rc-abort.c rc.c \
rc-depend.c rc-logger.c rc-misc.c rc-plugin.c \ rc-depend.c rc-logger.c rc-misc.c rc-plugin.c \
rc-service.c rc-status.c rc-update.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) ifeq (${MKSELINUX},yes)
SRCS+= rc-selinux.c SRCS+= rc-selinux.c
@ -16,7 +16,8 @@ SBINDIR= ${PREFIX}/sbin
LINKDIR= ${LIBEXECDIR} LINKDIR= ${LIBEXECDIR}
BINPROGS= rc-status 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 \ RC_BINPROGS= einfon einfo ewarnn ewarn eerrorn eerror ebegin eend ewend \
eindent eoutdent esyslog eval_ecolors ewaitfile \ eindent eoutdent esyslog eval_ecolors ewaitfile \
veinfo vewarn vebegin veend vewend veindent veoutdent \ 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 start-stop-daemon: start-stop-daemon.o _usage.o rc-misc.o
${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LDADD} ${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 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} ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LDADD}

722
src/rc/supervise-daemon.c Normal file
View File

@ -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);
}

45
supervise-daemon-guide.md Normal file
View File

@ -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.