bin/181341: [patch] daemon(8): ability to write pidfile for itself

Mark Felder feld at FreeBSD.org
Fri Aug 16 14:50:01 UTC 2013


>Number:         181341
>Category:       bin
>Synopsis:       [patch] daemon(8): ability to write pidfile for itself
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Fri Aug 16 14:50:00 UTC 2013
>Closed-Date:
>Last-Modified:
>Originator:     Mark Felder
>Release:        
>Organization:
>Environment:
>Description:
I was updating an rc script for a port (net/nss-pam-ldapd) to use daemon(8) and after discussing with zi@ we decided it would be best if this rc script used daemon's ability to supervise and restart the child process (-r option). Unfortunately when the -r functionality was added to daemon(8) the ability to write a pidfile for the daemon(8) supervisor itself was overlooked. This is crucial for this option to be useful in an rc script because the default pidfile written is of the child which the rc framework would directly signal to terminate, but the daemon supervisor will just restart it again! We wanted the child process to be able to restart on its own if it crashed or was accidentally killed, but still be able to cleanly shut it down. The only workaround is to write your own stop_cmd and status_cmd routines which are ugly, inconsistent throughout the ports tree, and sadly unable to leverage the robust built-in features of the rc framework.
>How-To-Repeat:
updating security/nss-pam-ldapd rc script with desired functionality BEFORE this patch will have to look something like this:

command="/usr/sbin/daemon -f -r"
command_args="%%PREFIX%%/bin/nslcd -d"
status_cmd=nslcd_status
stop_cmd=nslcd_stop

nslcd_status()
{
        mypid=$(pgrep -f "daemon: ${command}")
        if [ ! ${mypid} = '' ]; then
                echo "${name} is running with PID ${mypid}";
        else
                echo "${name} not running";
        fi
}

nslcd_stop()
{
        mypid=$(pgrep -f "daemon: ${command}")
        if [ ! ${mypid} = '' ]; then
                echo "Stopping ${name}";
                kill -TERM ${mypid};
                wait_for_pids ${mypid};
        else
                echo "${name} not running";
        fi
}

run_rc_command "$1"
>Fix:
Apply attached patch. This rc script could then be as simple as this:

pidfile="/var/run/nslcd_daemon.pid"
command="/usr/sbin/daemon -f -r -P ${pidfile}"
command_args="%%PREFIX%%/bin/nslcd -d"

run_rc_command "$1"


Of course changes like this throughout the ports tree will have to wait for all supported releases to include this functionality, but it should be perfectly safe to MFC this to a 9.x release.

daemon.c portion of the patch was written by a friend per my request who is interested in doing some FreeBSD hacking, but wishes to remain anonymous.

Patch attached with submission follows:

Index: daemon.8
===================================================================
--- daemon.8	(revision 254331)
+++ daemon.8	(working copy)
@@ -35,7 +35,8 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl cfr
-.Op Fl p Ar pidfile
+.Op Fl p Ar child pidfile
+.Op Fl P Ar supervisor pidfile
 .Op Fl u Ar user
 .Ar command arguments ...
 .Sh DESCRIPTION
@@ -71,6 +72,26 @@
 regardless of whether the
 .Fl u
 option is used or not.
+.It Fl P Ar file
+Write the ID of the 
+.Nm
+process into the
+.Ar file
+using the
+.Xr pidfile 3
+functionality.
+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
@@ -80,6 +101,7 @@
 .Pp
 If the
 .Fl p
+.Fl P
 or
 .Fl r
 option is specified the program is executed in a spawned child process.
@@ -93,6 +115,25 @@
 spawned process.
 Normally it will cause the child to exit followed by the termination
 of the supervising process after removing the pidfile.
+.Pp
+The
+.Fl P
+option is useful combined with the
+.Fl r
+option as the pidfile contains the ID of the supervisor
+not the child. This is especially important if you use
+.Fl r
+in an rc script as the
+.Fl p
+option will give you the child's ID to signal when you attempt to
+stop the service, causing 
+.Nm
+to restart the child. If the ID written by the
+.Fl P
+option is signalled,
+.Nm
+will cause the child to exit followed by the termination of the
+supervising process after removing the pidfile.
 .Sh EXIT STATUS
 The
 .Nm
Index: daemon.c
===================================================================
--- daemon.c	(revision 254331)
+++ daemon.c	(working copy)
@@ -53,16 +53,17 @@
 int
 main(int argc, char *argv[])
 {
-	struct pidfh *pfh = NULL;
+	struct pidfh  *ppfh, *pfh;
 	sigset_t mask, oldmask;
 	int ch, nochdir, noclose, restart;
-	const char *pidfile, *user;
+	const char *pidfile, *ppidfile,  *user;
 	pid_t otherpid, pid;
 
 	nochdir = noclose = 1;
 	restart = 0;
-	pidfile = user = NULL;
-	while ((ch = getopt(argc, argv, "cfp:ru:")) != -1) {
+	ppfh = pfh = NULL;
+	ppidfile = pidfile = user = NULL;
+	while ((ch = getopt(argc, argv, "-cfp:P:ru:")) != -1) {
 		switch (ch) {
 		case 'c':
 			nochdir = 0;
@@ -73,6 +74,9 @@
 		case 'p':
 			pidfile = optarg;
 			break;
+		case 'P':
+			ppidfile = optarg;
+			break;
 		case 'r':
 			restart = 1;
 			break;
@@ -89,7 +93,7 @@
 	if (argc == 0)
 		usage();
 
-	pfh = NULL;
+	ppfh = pfh = NULL;
 	/*
 	 * Try to open the pidfile before calling daemon(3),
 	 * to be able to report the error intelligently
@@ -104,6 +108,18 @@
 			err(2, "pidfile ``%s''", pidfile);
 		}
 	}
+	
+	/* do same for actual daemon process */
+	if (ppidfile != NULL) {
+		ppfh = pidfile_open(ppidfile, 0600, &otherpid);
+		if (ppfh == NULL) {
+			if (errno == EEXIST) {
+				errx(3, "process already running, pid: %d",
+				     otherpid);
+			}
+			err(2, "ppidfile ``%s''", ppidfile);
+		}
+	}
 
 	if (daemon(nochdir, noclose) == -1)
 		err(1, NULL);
@@ -176,6 +192,11 @@
 		 */
 		err(1, "%s", argv[0]);
 	}
+	/* write out parent pidfile if needed */
+	if (ppidfile != NULL) {
+		pidfile_write(ppfh);
+	}
+
 	setproctitle("%s[%d]", argv[0], pid);
 	if (wait_child(pid, &mask) == 0 && restart) {
 		sleep(1);
@@ -182,6 +203,7 @@
 		goto restart;
 	}
 	pidfile_remove(pfh);
+	pidfile_remove(ppfh);
 	exit(0); /* Exit status does not matter. */
 }
 
@@ -240,7 +262,7 @@
 usage(void)
 {
 	(void)fprintf(stderr,
-	    "usage: daemon [-cfr] [-p pidfile] [-u user] command "
+	    "usage: daemon [-cfr] [-p child pidfile] [-P supervisor pidfile] [-u user] command "
 		"arguments ...\n");
 	exit(1);
 }


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list