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 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 +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include /* For io priority */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_PAM +#include + +/* 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 | */ + 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 */ + pidfile = optarg; + break; + + case 'r': /* --chroot /new/root */ + ch_root = optarg; + break; + + case 'u': /* --user | */ + { + 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.