From owner-svn-src-stable-9@FreeBSD.ORG Sun Apr 1 20:53:36 2012 Return-Path: Delivered-To: svn-src-stable-9@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 26427106566B; Sun, 1 Apr 2012 20:53:36 +0000 (UTC) (envelope-from trociny@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 102288FC16; Sun, 1 Apr 2012 20:53:36 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id q31KrZbu065524; Sun, 1 Apr 2012 20:53:35 GMT (envelope-from trociny@svn.freebsd.org) Received: (from trociny@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id q31KrZHE065521; Sun, 1 Apr 2012 20:53:35 GMT (envelope-from trociny@svn.freebsd.org) Message-Id: <201204012053.q31KrZHE065521@svn.freebsd.org> From: Mikolaj Golub Date: Sun, 1 Apr 2012 20:53:35 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-9@freebsd.org X-SVN-Group: stable-9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r233761 - stable/9/usr.sbin/daemon X-BeenThere: svn-src-stable-9@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: SVN commit messages for only the 9-stable src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 01 Apr 2012 20:53:36 -0000 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 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 r231912: If permitted protect the supervisor against pageout kill. Suggested by: Andrey Zonov 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 +#include +#include #include #include -#include #include #include +#include +#include #include #include #include +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) {