Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 1 Apr 2012 20:53:35 +0000 (UTC)
From:      Mikolaj Golub <trociny@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-9@freebsd.org
Subject:   svn commit: r233761 - stable/9/usr.sbin/daemon
Message-ID:  <201204012053.q31KrZHE065521@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: trociny
Date: Sun Apr  1 20:53:35 2012
New Revision: 233761
URL: http://svn.freebsd.org/changeset/base/233761

Log:
  MFC r229667, r230541, r230869, r231909, r231910, r231911, r231912:
  
  r229667, r230541, r230869 (ghelmer):
  
  Change the notes about the pidfile to include Doug's preference
  for pre-creating the pidfile with appropriate owner and permissions.
  
  Requested by dougb
  
  r231909:
  
  The pidfile_open(3) is going to be fixed to set close-on-exec in order
  not to leak the descriptor after exec(3). This raises the issue for
  daemon(3) of the pidfile lock to be lost when the child process
  executes.
  
  To solve this and also to have the pidfile cleaned up when the program
  exits, if a pidfile is specified, spawn a child to exec the command
  and wait in the parent keeping the pidfile locked until the child
  process exits and remove the file.
  
  Reported by:	Andrey Zonov <andrey zonov org>
  Suggested by:	pjd
  Reviewed by:	pjd
  
  r231910:
  
  If the supervising process receives SIGTERM, forward it to the spawned
  process.  Normally it will cause the child to exit followed by the
  termination of the supervisor after removing the pidfile.
  
  This looks like desirable behavior, because termination of a
  supervisor usually supposes termination of its charge. Also it will
  fix the issue with stale pid files after reboot due to init kills a
  supervisor before its child exits.
  
  r231911:
  
  Add -r option to restart the program if it has been terminated.
  
  Suggested by:	Andrey Zonov <andrey zonov org>
  
  r231912:
  
  If permitted protect the supervisor against pageout kill.
  
  Suggested by:	Andrey Zonov <andrey zonov org>

Modified:
  stable/9/usr.sbin/daemon/daemon.8
  stable/9/usr.sbin/daemon/daemon.c
Directory Properties:
  stable/9/usr.sbin/daemon/   (props changed)

Modified: stable/9/usr.sbin/daemon/daemon.8
==============================================================================
--- stable/9/usr.sbin/daemon/daemon.8	Sun Apr  1 18:22:48 2012	(r233760)
+++ stable/9/usr.sbin/daemon/daemon.8	Sun Apr  1 20:53:35 2012	(r233761)
@@ -26,7 +26,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd March 19, 2007
+.Dd February 19, 2012
 .Dt DAEMON 8
 .Os
 .Sh NAME
@@ -59,13 +59,40 @@ Write the ID of the created process into
 using the
 .Xr pidfile 3
 functionality.
-Note, that the file will be created shortly before the process is
-actually executed, and will remain after the process exits (although
-it will be removed if the execution fails).
+The program is executed in a spawned child process while the
+.Nm
+waits until it terminates to keep the
+.Ar file
+locked and removes it after the process exits.
+The
+.Ar file
+owner is the user who runs the
+.Nm
+regardless of whether the
+.Fl u
+option is used or not.
+.It Fl r
+Supervise and restart the program if it has been terminated.
 .It Fl u Ar user
 Login name of the user to execute the program under.
 Requires adequate superuser privileges.
 .El
+.Pp
+If the
+.Fl p
+or
+.Fl r
+option is specified the program is executed in a spawned child process.
+The
+.Nm
+waits until it terminates to keep the pid file locked and removes it
+after the process exits or restarts the program.
+In this case if the monitoring
+.Nm
+receives software termination signal (SIGTERM) it forwards it to the
+spawned process.
+Normally it will cause the child to exit followed by the termination
+of the supervising process after removing the pidfile.
 .Sh EXIT STATUS
 The
 .Nm

Modified: stable/9/usr.sbin/daemon/daemon.c
==============================================================================
--- stable/9/usr.sbin/daemon/daemon.c	Sun Apr  1 18:22:48 2012	(r233760)
+++ stable/9/usr.sbin/daemon/daemon.c	Sun Apr  1 20:53:35 2012	(r233761)
@@ -32,30 +32,37 @@
 __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
 
 #include <err.h>
 #include <errno.h>
-#include <pwd.h>
 #include <libutil.h>
 #include <login_cap.h>
+#include <pwd.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 
+static void dummy_sighandler(int);
 static void restrict_process(const char *);
+static int  wait_child(pid_t pid, sigset_t *mask);
 static void usage(void);
 
 int
 main(int argc, char *argv[])
 {
 	struct pidfh *pfh = NULL;
-	int ch, nochdir, noclose, errcode;
+	sigset_t mask, oldmask;
+	int ch, nochdir, noclose, restart;
 	const char *pidfile, *user;
-	pid_t otherpid;
+	pid_t otherpid, pid;
 
 	nochdir = noclose = 1;
+	restart = 0;
 	pidfile = user = NULL;
-	while ((ch = getopt(argc, argv, "-cfp:u:")) != -1) {
+	while ((ch = getopt(argc, argv, "-cfp:ru:")) != -1) {
 		switch (ch) {
 		case 'c':
 			nochdir = 0;
@@ -66,6 +73,9 @@ main(int argc, char *argv[])
 		case 'p':
 			pidfile = optarg;
 			break;
+		case 'r':
+			restart = 1;
+			break;
 		case 'u':
 			user = optarg;
 			break;
@@ -79,14 +89,12 @@ main(int argc, char *argv[])
 	if (argc == 0)
 		usage();
 
-	if (user != NULL)
-		restrict_process(user);
-
+	pfh = NULL;
 	/*
 	 * Try to open the pidfile before calling daemon(3),
 	 * to be able to report the error intelligently
 	 */
-	if (pidfile) {
+	if (pidfile != NULL) {
 		pfh = pidfile_open(pidfile, 0600, &otherpid);
 		if (pfh == NULL) {
 			if (errno == EEXIST) {
@@ -100,22 +108,87 @@ main(int argc, char *argv[])
 	if (daemon(nochdir, noclose) == -1)
 		err(1, NULL);
 
-	/* Now that we are the child, write out the pid */
-	if (pidfile)
+	/*
+	 * If the pidfile or restart option is specified the daemon
+	 * executes the command in a forked process and wait on child
+	 * exit to remove the pidfile or restart the command. Normally
+	 * we don't want the monitoring daemon to be terminated
+	 * leaving the running process and the stale pidfile, so we
+	 * catch SIGTERM and forward it to the children expecting to
+	 * get SIGCHLD eventually.
+	 */
+	pid = -1;
+	if (pidfile != NULL || restart) {
+		/*
+		 * Restore default action for SIGTERM in case the
+		 * parent process decided to ignore it.
+		 */
+		if (signal(SIGTERM, SIG_DFL) == SIG_ERR)
+			err(1, "signal");
+		/*
+		 * Because SIGCHLD is ignored by default, setup dummy handler
+		 * for it, so we can mask it.
+		 */
+		if (signal(SIGCHLD, dummy_sighandler) == SIG_ERR)
+			err(1, "signal");
+		/*
+		 * Block interesting signals.
+		 */
+		sigemptyset(&mask);
+		sigaddset(&mask, SIGTERM);
+		sigaddset(&mask, SIGCHLD);
+		if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1)
+			err(1, "sigprocmask");
+		/*
+		 * Try to protect against pageout kill. Ignore the
+		 * error, madvise(2) will fail only if a process does
+		 * not have superuser privileges.
+		 */
+		(void)madvise(NULL, 0, MADV_PROTECT);
+restart:
+		/*
+		 * Spawn a child to exec the command, so in the parent
+		 * we could wait for it to exit and remove pidfile.
+		 */
+		pid = fork();
+		if (pid == -1) {
+			pidfile_remove(pfh);
+			err(1, "fork");
+		}
+	}
+	if (pid <= 0) {
+		if (pid == 0) {
+			/* Restore old sigmask in the child. */
+			if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
+				err(1, "sigprocmask");
+		}
+		/* Now that we are the child, write out the pid. */
 		pidfile_write(pfh);
 
-	execvp(argv[0], argv);
+		if (user != NULL)
+			restrict_process(user);
 
-	/*
-	 * execvp() failed -- unlink pidfile if any, and
-	 * report the error
-	 */
-	errcode = errno; /* Preserve errcode -- unlink may reset it */
-	if (pidfile)
-		pidfile_remove(pfh);
+		execvp(argv[0], argv);
+
+		/*
+		 * execvp() failed -- report the error. The child is
+		 * now running, so the exit status doesn't matter.
+		 */
+		err(1, "%s", argv[0]);
+	}
+	setproctitle("%s[%d]", argv[0], pid);
+	if (wait_child(pid, &mask) == 0 && restart) {
+		sleep(1);
+		goto restart;
+	}
+	pidfile_remove(pfh);
+	exit(0); /* Exit status does not matter. */
+}
 
-	/* The child is now running, so the exit status doesn't matter. */
-	errc(1, errcode, "%s", argv[0]);
+static void
+dummy_sighandler(int sig __unused)
+{
+	/* Nothing to do. */
 }
 
 static void
@@ -131,6 +204,34 @@ restrict_process(const char *user)
 		errx(1, "failed to set user environment");
 }
 
+static int
+wait_child(pid_t pid, sigset_t *mask)
+{
+	int terminate, signo;
+
+	terminate = 0;
+	for (;;) {
+		if (sigwait(mask, &signo) == -1) {
+			warn("sigwaitinfo");
+			return (-1);
+		}
+		switch (signo) {
+		case SIGCHLD:
+			return (terminate);
+		case SIGTERM:
+			terminate = 1;
+			if (kill(pid, signo) == -1) {
+				warn("kill");
+				return (-1);
+			}
+			continue;
+		default:
+			warnx("sigwaitinfo: invalid signal: %d", signo);
+			return (-1);
+		}
+	}
+}
+
 static void
 usage(void)
 {



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201204012053.q31KrZHE065521>