Module Name:    src
Committed By:   roy
Date:           Sun Apr 10 19:05:51 UTC 2016

Modified Files:
        src/distrib/sets/lists/comp: mi
        src/include: util.h
        src/lib/libutil: Makefile pidfile.3 pidfile.c

Log Message:
Implement pidfile_lock, pidfile_read and pidfile_clean.

Discussed on tech-net@, ok core@.


To generate a diff of this commit:
cvs rdiff -u -r1.2028 -r1.2029 src/distrib/sets/lists/comp/mi
cvs rdiff -u -r1.68 -r1.69 src/include/util.h
cvs rdiff -u -r1.78 -r1.79 src/lib/libutil/Makefile
cvs rdiff -u -r1.13 -r1.14 src/lib/libutil/pidfile.3
cvs rdiff -u -r1.11 -r1.12 src/lib/libutil/pidfile.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/comp/mi
diff -u src/distrib/sets/lists/comp/mi:1.2028 src/distrib/sets/lists/comp/mi:1.2029
--- src/distrib/sets/lists/comp/mi:1.2028	Sat Apr  9 06:21:16 2016
+++ src/distrib/sets/lists/comp/mi	Sun Apr 10 19:05:50 2016
@@ -1,4 +1,4 @@
-#	$NetBSD: mi,v 1.2028 2016/04/09 06:21:16 riastradh Exp $
+#	$NetBSD: mi,v 1.2029 2016/04/10 19:05:50 roy Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 ./etc/mtree/set.comp				comp-sys-root
@@ -8026,6 +8026,9 @@
 ./usr/share/man/cat3/pechochar.0		comp-c-catman		.cat
 ./usr/share/man/cat3/perror.0			comp-c-catman		.cat
 ./usr/share/man/cat3/pidfile.0			comp-c-catman		.cat
+./usr/share/man/cat3/pidfile_clean.0		comp-c-catman		.cat
+./usr/share/man/cat3/pidfile_lock.0		comp-c-catman		.cat
+./usr/share/man/cat3/pidfile_read.0		comp-c-catman		.cat
 ./usr/share/man/cat3/pidlock.0			comp-c-catman		.cat
 ./usr/share/man/cat3/pmap_getmaps.0		comp-c-catman		.cat
 ./usr/share/man/cat3/pmap_getport.0		comp-c-catman		.cat
@@ -15174,6 +15177,9 @@
 ./usr/share/man/html3/pechochar.html		comp-c-htmlman		html
 ./usr/share/man/html3/perror.html		comp-c-htmlman		html
 ./usr/share/man/html3/pidfile.html		comp-c-htmlman		html
+./usr/share/man/html3/pidfile_clean.html	comp-c-htmlman		html
+./usr/share/man/html3/pidfile_lock.html		comp-c-htmlman		html
+./usr/share/man/html3/pidfile_read.html		comp-c-htmlman		html
 ./usr/share/man/html3/pidlock.html		comp-c-htmlman		html
 ./usr/share/man/html3/pmap_getmaps.html		comp-c-htmlman		html
 ./usr/share/man/html3/pmap_getport.html		comp-c-htmlman		html
@@ -22311,6 +22317,9 @@
 ./usr/share/man/man3/pechochar.3		comp-c-man		.man
 ./usr/share/man/man3/perror.3			comp-c-man		.man
 ./usr/share/man/man3/pidfile.3			comp-c-man		.man
+./usr/share/man/man3/pidfile_clean.3		comp-c-man		.man
+./usr/share/man/man3/pidfile_lock.3		comp-c-man		.man
+./usr/share/man/man3/pidfile_read.3		comp-c-man		.man
 ./usr/share/man/man3/pidlock.3			comp-c-man		.man
 ./usr/share/man/man3/pmap_getmaps.3		comp-c-man		.man
 ./usr/share/man/man3/pmap_getport.3		comp-c-man		.man

Index: src/include/util.h
diff -u src/include/util.h:1.68 src/include/util.h:1.69
--- src/include/util.h:1.68	Thu Sep 24 14:39:37 2015
+++ src/include/util.h	Sun Apr 10 19:05:50 2016
@@ -1,4 +1,4 @@
-/*	$NetBSD: util.h,v 1.68 2015/09/24 14:39:37 christos Exp $	*/
+/*	$NetBSD: util.h,v 1.69 2016/04/10 19:05:50 roy Exp $	*/
 
 /*-
  * Copyright (c) 1995
@@ -103,6 +103,9 @@ time_t		parsedate(const char *, const ti
     __RENAME(__parsedate50);
 #endif
 int		pidfile(const char *);
+pid_t		pidfile_lock(const char *);
+pid_t		pidfile_read(const char *);
+int		pidfile_clean(void);
 int		pidlock(const char *, int, pid_t *, const char *);
 int		pw_abort(void);
 #ifndef __LIBC12_SOURCE__

Index: src/lib/libutil/Makefile
diff -u src/lib/libutil/Makefile:1.78 src/lib/libutil/Makefile:1.79
--- src/lib/libutil/Makefile:1.78	Thu Sep 24 14:39:20 2015
+++ src/lib/libutil/Makefile	Sun Apr 10 19:05:50 2016
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.78 2015/09/24 14:39:20 christos Exp $
+#	$NetBSD: Makefile,v 1.79 2016/04/10 19:05:50 roy Exp $
 #	@(#)Makefile	8.1 (Berkeley) 6/4/93
 
 USE_SHLIBDIR=	yes
@@ -58,6 +58,9 @@ MLINKS+=login_cap.3 setusercontext.3
 MLINKS+=loginx.3 logoutx.3 loginx.3 logwtmpx.3
 MLINKS+=openpty.3 login_tty.3
 MLINKS+=openpty.3 forkpty.3
+MLINKS+=pidfile.3 pidfile_clean.3
+MLINKS+=pidfile.3 pidfile_lock.3
+MLINKS+=pidfile.3 pidfile_read.3
 MLINKS+=pw_getconf.3 pw_getpwconf.3
 MLINKS+=pw_init.3 pw_edit.3
 MLINKS+=pw_init.3 pw_prompt.3

Index: src/lib/libutil/pidfile.3
diff -u src/lib/libutil/pidfile.3:1.13 src/lib/libutil/pidfile.3:1.14
--- src/lib/libutil/pidfile.3:1.13	Tue Mar 29 13:55:37 2011
+++ src/lib/libutil/pidfile.3	Sun Apr 10 19:05:50 2016
@@ -1,10 +1,10 @@
-.\"	$NetBSD: pidfile.3,v 1.13 2011/03/29 13:55:37 jmmv Exp $
+.\"	$NetBSD: pidfile.3,v 1.14 2016/04/10 19:05:50 roy Exp $
 .\"
-.\" Copyright (c) 1999 The NetBSD Foundation, Inc.
+.\" Copyright (c) 1999, 2016 The NetBSD Foundation, Inc.
 .\" All rights reserved.
 .\"
 .\" This code is derived from software contributed to The NetBSD Foundation
-.\" by Jason R. Thorpe.
+.\" by Jason R. Thorpe and Roy Marples.
 .\"
 .\" Redistribution and use in source and binary forms, with or without
 .\" modification, are permitted provided that the following conditions
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd March 23, 2011
+.Dd April 10, 2016
 .Dt PIDFILE 3
 .Os
 .Sh NAME
@@ -39,13 +39,21 @@
 .In util.h
 .Ft int
 .Fn pidfile "const char *path"
+.Ft pid_t
+.Fn pidfile_lock "const char *path"
+.Ft pid_t
+.Fn pidfile_read "const char *path"
+.Ft int
+.Fn pidfile_clean "void"
 .Sh DESCRIPTION
 .Fn pidfile
-creates a file containing the process ID of the caller program.
+and
+.Fn pidfile_lock
+create and lock a file containing the process ID of the calling program.
 The pid file can be used as a quick reference if
 the process needs to be sent a signal.
-When the program exits, the pid file is removed automatically, unless
-the program receives a fatal signal.
+The pid file is truncated and removed automatically when the program exits,
+unless the program receives a fatal signal.
 .Pp
 If
 .Ar path
@@ -72,21 +80,60 @@ is an absolute or relative path (i.e. it
 character),
 the pid file is created in the provided location.
 .Pp
-Note that only the first invocation of
-.Fn pidfile
-causes a pid file to be written; subsequent invocations have no effect
-unless a new
-.Ar path
-is supplied.
 If called with a new
 .Ar path ,
 .Fn pidfile
-will remove the old pid file and write the new one.
+and
+.Fn pidfile_lock
+will remove the old pid file.
+.Pp
+The pid file is truncated, so these functions can be called multiple times and
+allow a child process to take over the lock.
+.Pp
+.Fn pidfile_read
+will read the last pid file created, or specified by
+.Ar path ,
+and return the process ID it contains.
+.Pp
+.Fn pidfile_clean
+will
+.Xr ftruncate 2 ,
+.Xr close 2
+and
+.Xr unlink 2
+the last opening pid file if, and only if, the current process wrote it.
+This function should be called if the program needs to call
+.Xr _exit 2
+(such as from a signal handler) and needs to clean up the pid file.
 .Sh RETURN VALUES
 .Fn pidfile
+and
+.Fn pidfile_clean
 returns 0 on success and -1 on failure.
+.Pp
+.Fn pidfile_lock
+returns 0 on success.
+Otherwise, the process ID who owns the lock is returned and if that
+cannot be derived then -1 is returned.
+.Pp
+.Fn pidfile_read
+returns the process ID if known, otherwise -1.
+.Sh ERRORS
+The
+.Fn pidfile
+and
+.Fn pidfile_lock
+functions will fail if:
+.Bl -tag -width Er
+.It Bq Er EEXIST
+Some process already holds the lock on the given pid file, meaning that a
+daemon is already running.
+.It Bq Er ENAMETOOLONG
+Specified pidfile's name is too long.
+.El
 .Sh SEE ALSO
-.Xr atexit 3
+.Xr atexit 3 ,
+.Xr flock 2
 .Sh HISTORY
 The
 .Fn pidfile
@@ -94,12 +141,30 @@ function call appeared in
 .Nx 1.5 .
 Support for creating pid files in any arbitrary path was added in
 .Nx 6.0 .
-.Sh BUGS
-.Fn pidfile
-uses
+.Pp
+The
+.Fn pidfile_lock ,
+.Fn pidfile_read
+and
+.Fn pidfile_clean
+function calls appeared in
+.Nx 8 .
+.Sh CAVEATS
+.Fn pidfile
+and
+.Fn pidfile_lock
+use
 .Xr atexit 3
-to ensure the pid file is unlinked at program exit.
+to ensure the pid file is cleaned at program exit.
 However, programs that use the
 .Xr _exit 2
 function (for example, in signal handlers)
-will not trigger this behaviour.
+will not trigger this behaviour and should call
+.Xr pidfile_clean.
+Like-wise, if the program creates a pid file before
+.Xr fork 2 Ns ing
+a child to take over, it should use the
+.Xr _exit 2
+function instead of returning or using the
+.Xr exit 2
+function to ensure the pid file is not cleaned.

Index: src/lib/libutil/pidfile.c
diff -u src/lib/libutil/pidfile.c:1.11 src/lib/libutil/pidfile.c:1.12
--- src/lib/libutil/pidfile.c:1.11	Thu Jan 22 19:04:28 2015
+++ src/lib/libutil/pidfile.c	Sun Apr 10 19:05:50 2016
@@ -1,11 +1,11 @@
-/*	$NetBSD: pidfile.c,v 1.11 2015/01/22 19:04:28 christos Exp $	*/
+/*	$NetBSD: pidfile.c,v 1.12 2016/04/10 19:05:50 roy Exp $	*/
 
 /*-
- * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
- * by Jason R. Thorpe, Matthias Scheler and Julio Merino.
+ * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -31,11 +31,14 @@
 
 #include <sys/cdefs.h>
 #if defined(LIBC_SCCS) && !defined(lint)
-__RCSID("$NetBSD: pidfile.c,v 1.11 2015/01/22 19:04:28 christos Exp $");
+__RCSID("$NetBSD: pidfile.c,v 1.12 2016/04/10 19:05:50 roy Exp $");
 #endif
 
 #include <sys/param.h>
 
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
 #include <paths.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -45,131 +48,205 @@ __RCSID("$NetBSD: pidfile.c,v 1.11 2015/
 #include <util.h>
 
 static pid_t pidfile_pid;
-static char *pidfile_path;
+static char pidfile_path[PATH_MAX];
+static int pidfile_fd = -1;
 
-/* Deletes an existent pidfile iff it was created by this process. */
-static void
-pidfile_cleanup(void)
+/* Closes pidfile resources.
+ *
+ * Returns 0 on success, otherwise -1. */
+static int
+pidfile_close(void)
 {
+	int error;
 
-	if ((pidfile_path != NULL) && (pidfile_pid == getpid()))
-		(void) unlink(pidfile_path);
+	pidfile_pid = 0;
+	error = close(pidfile_fd);
+	pidfile_fd = -1;
+	pidfile_path[0] = '\0';
+	return error;
 }
 
-/* Registers an atexit(3) handler to delete the pidfile we have generated.
- * We only register the handler when we create a pidfile, so we can assume
- * that the pidfile exists.
+/* Truncate, close and unlink an existent pidfile,
+ * if and only if it was created by this process.
+ * The pidfile is truncated because we may have dropped permissions
+ * or entered a chroot and thus unable to unlink it.
  *
- * Returns 0 on success or -1 if the handler could not be registered. */
-static int
-register_atexit_handler(void)
+ * Returns 0 on truncation success, otherwise -1. */
+int
+pidfile_clean(void)
 {
-	static bool done = false;
+	int error;
 
-	if (!done) {
-		if (atexit(pidfile_cleanup) < 0)
-			return -1;
-		done = true;
+	if (pidfile_fd == -1) {
+		errno = EBADF;
+		return -1;
 	}
 
+	if (pidfile_pid != getpid())
+		error = EPERM;
+	else if (ftruncate(pidfile_fd, 0) == -1)
+		error = errno;
+	else {
+		(void) unlink(pidfile_path);
+		error = 0;
+	}
+
+	(void) pidfile_close();
+
+	if (error != 0) {
+		errno = error;
+		return -1;
+	}
 	return 0;
 }
 
-/* Given a new pidfile name in 'path', deletes any previously-created pidfile
- * if the previous file differs to the new one.
- *
- * If a previous file is deleted, returns 1, which means that a new pidfile
- * must be created.  Otherwise, this returns 0, which means that the existing
- * file does not need to be touched. */
-static int
-cleanup_old_pidfile(const char* path)
+/* atexit shim for pidfile_clean */
+static void
+pidfile_cleanup(void)
 {
-	if (pidfile_path != NULL) {
-		if (strcmp(pidfile_path, path) != 0) {
-			pidfile_cleanup();
-
-			free(pidfile_path);
-			pidfile_path = NULL;
 
-			return 1;
-		} else
-			return 0;
-	} else
-		return 1;
+	pidfile_clean();
 }
 
-/* Constructs a name for a pidfile in the default location (/var/run).  If
- * 'bname' is NULL, uses the name of the current program for the name of
+/* Constructs a name for a pidfile in the default location (/var/run).
+ * If 'bname' is NULL, uses the name of the current program for the name of
  * the pidfile.
  *
- * Returns a pointer to a dynamically-allocatd string containing the absolute
- * path to the pidfile; NULL on failure. */
-static char *
-generate_varrun_path(const char *bname)
+ * Returns 0 on success, otherwise -1. */
+static int
+pidfile_varrun_path(char *path, size_t len, const char *bname)
 {
-	char *path;
 
 	if (bname == NULL)
 		bname = getprogname();
 
 	/* _PATH_VARRUN includes trailing / */
-	if (asprintf(&path, "%s%s.pid", _PATH_VARRUN, bname) == -1)
-		return NULL;
-	return path;
+	if ((size_t)snprintf(path, len, "%s%s.pid", _PATH_VARRUN, bname) >= len)
+	{
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+	return 0;
+}
+
+/* Returns the process ID inside path on success, otherwise -1.
+ * If no path is given, use the last pidfile path, othewise the default one. */
+pid_t
+pidfile_read(const char *path)
+{
+	char dpath[PATH_MAX], buf[16], *eptr;
+	int fd, error;
+	ssize_t n;
+	pid_t pid;
+
+	if (path == NULL) {
+		if (pidfile_path[0] != '\0')
+			path = pidfile_path;
+		else if (pidfile_varrun_path(dpath, sizeof(dpath), NULL) == -1)
+			return -1;
+		else
+			path = dpath;
+	}
+
+	if ((fd = open(path, O_RDONLY | O_CLOEXEC | O_NONBLOCK)) == -1)
+		return  -1;
+	n = read(fd, buf, sizeof(buf) - 1);
+	error = errno;
+	(void) close(fd);
+	if (n == -1) {
+		errno = error;
+		return -1;
+	}
+	buf[n] = '\0';
+	pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error);
+	if (error && !(error == ENOTSUP && *eptr == '\n')) {
+		errno = error;
+		return -1;
+	}
+	return pid;
 }
 
-/* Creates a pidfile with the provided name.  The new pidfile is "registered"
- * in the global variables pidfile_path and pidfile_pid so that any further
- * call to pidfile(3) can check if we are recreating the same file or a new
- * one.
+/* Locks the pidfile specified by path and writes the process pid to it.
+ * The new pidfile is "registered" in the global variables pidfile_fd,
+ * pidfile_path and pidfile_pid so that any further call to pidfile_lock(3)
+ * can check if we are recreating the same file or a new one.
  *
- * Returns 0 on success or -1 if there is any error. */
-static int
-create_pidfile(const char* path)
+ * Returns 0 on success, otherwise the pid of the process who owns the
+ * lock if it can be read, otherwise -1. */
+pid_t
+pidfile_lock(const char *path)
 {
-	FILE *f;
+	char dpath[PATH_MAX];
+	static bool registered_atexit = false;
 
-	if (register_atexit_handler() == -1)
-		return -1;
+	/* Register for cleanup with atexit. */
+	if (!registered_atexit) {
+		if (atexit(pidfile_cleanup) == -1)
+			return -1;
+		registered_atexit = true;
+	}
 
-	if (cleanup_old_pidfile(path) == 0)
-		return 0;
+	if (path == NULL || strchr(path, '/') == NULL) {
+		if (pidfile_varrun_path(dpath, sizeof(dpath), NULL) == -1)
+			return -1;
+		path = dpath;
+	}
 
-	pidfile_path = strdup(path);
-	if (pidfile_path == NULL)
-		return -1;
+	/* If path has changed (no good reason), clean up the old pidfile. */
+	if (strcmp(pidfile_path, path) != 0)
+		pidfile_cleanup();
+
+	if (pidfile_fd == -1) {
+		pidfile_fd = open(path,
+		    O_WRONLY | O_CREAT | O_CLOEXEC | O_NONBLOCK | O_EXLOCK,
+		    0644);
+		if (pidfile_fd == -1) {
+			pid_t pid;
+
+			if (errno == EAGAIN) {
+				/* The pidfile is locked, return the process ID
+				 * it contains.
+				 * If sucessful, set errno to EEXIST. */
+				if ((pid = pidfile_read(path)) != -1)
+					errno = EEXIST;
+			} else
+				pid = -1;
 
-	if ((f = fopen(path, "w")) == NULL) {
-		free(pidfile_path);
-		pidfile_path = NULL;
-		return -1;
+			return pid;
+		}
+		strlcpy(pidfile_path, path, sizeof(pidfile_path));
 	}
 
 	pidfile_pid = getpid();
 
-	(void) fprintf(f, "%d\n", pidfile_pid);
-	(void) fclose(f);
+	/* Truncate the file, as we could be re-writing it.
+	 * Then write the process ID. */
+	if (ftruncate(pidfile_fd, 0) == -1 ||
+	    lseek(pidfile_fd, 0, SEEK_SET) == -1 ||
+	    dprintf(pidfile_fd, "%d\n", pidfile_pid) == -1)
+	{
+		int error = errno;
 
+		pidfile_cleanup();
+		errno = error;
+		return -1;
+	}
+
+	/* Hold the fd open to persist the lock. */
 	return 0;
 }
 
+/* The old function.
+ * Historical behaviour is that pidfile is not re-written
+ * if path has not changed.
+ *
+ * Returns 0 on success, otherwise -1.
+ * As such we have no way of knowing the process ID who owns the lock. */
 int
 pidfile(const char *path)
 {
+	pid_t pid;
 
-	if (path == NULL || strchr(path, '/') == NULL) {
-		char *default_path;
-
-		if ((default_path = generate_varrun_path(path)) == NULL)
-			return -1;
-
-		if (create_pidfile(default_path) == -1) {
-			free(default_path);
-			return -1;
-		}
-
-		free(default_path);
-		return 0;
-	} else
-		return create_pidfile(path);
+	pid = pidfile_lock(path);
+	return pid == 0 ? 0 : -1;
 }

Reply via email to