tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

pidfile_lock(3)



pidfile(3) is pretty crap - it just writes to the file without any locking.
Also, you can only write the pidfile once - subsequent calls to the same file 
are a non-op so it's useless after forking.

pidlock(3) is a bit better, but the "lock" is just does the file exist or not 
which isn't great if the process has died - there is a chance that another 
process with the same pid could spawn later. Also, writing misc data to the 
pidfile after the pid is pretty non standard. It's also not mentioned in 
pidfile(3) and the only user of it in our tree is getty(8).

So I've created pidfile_lock (patch attached) to address these problems.
It returns 0 on success, otherwise the pid of the process who claims the lock 
and if we cannot read that then -1. This makes the error checking similar to 
fork(2).
Here is an example of it's use:

	pid_t pid;

        if ((pid = pidfile_lock(NULL)) != 0) {
                if (pid == -1)
                        syslog(LOG_ERR, "%s: pidfile_lock: %m", __func__);
                else
                        syslog(LOG_ERR, "%s already started on pid %jd",
                            getprogname(), (intmax_t)pid);
                exit(EXIT_FAILURE);
        }
        syslog(LOG_INFO, "%s starting", getprogname());

        if (daemon(0, 0) == -1) {
                syslog(LOG_ERR, "daemon: %m");
                exit(EXIT_FAILURE);
        }

        if ((pid = pidfile_lock(NULL)) != 0) {
                syslog(LOG_ERR, "%s: pidfile_lock (%jd): %m", __func__,
		    (intmax_t)pid);
                exit(EXIT_FAILURE);
        }

pidfile(3) now just calls pidfile_lock(3), but still, returns 0 on success or -1 
on error. So there is no way of knowing which process has the lock. But at 
least it now does lock the file and respect any other locks so it's still a 
massive improvement.

Comments? If none, I'll also adjust the man page and commit during next week.

Roy
Index: include/util.h
===================================================================
RCS file: /cvsroot/src/include/util.h,v
retrieving revision 1.68
diff -u -p -r1.68 util.h
--- include/util.h	24 Sep 2015 14:39:37 -0000	1.68
+++ include/util.h	19 Mar 2016 22:54:15 -0000
@@ -103,6 +103,7 @@ time_t		parsedate(const char *, const ti
     __RENAME(__parsedate50);
 #endif
 int		pidfile(const char *);
+pid_t		pidfile_lock(const char *);
 int		pidlock(const char *, int, pid_t *, const char *);
 int		pw_abort(void);
 #ifndef __LIBC12_SOURCE__
Index: lib/libutil/pidfile.c
===================================================================
RCS file: /cvsroot/src/lib/libutil/pidfile.c,v
retrieving revision 1.11
diff -u -p -r1.11 pidfile.c
--- lib/libutil/pidfile.c	22 Jan 2015 19:04:28 -0000	1.11
+++ lib/libutil/pidfile.c	19 Mar 2016 22:54:15 -0000
@@ -35,7 +35,11 @@ __RCSID("$NetBSD: pidfile.c,v 1.11 2015/
 #endif
 
 #include <sys/param.h>
+#include <sys/proc.h>
 
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
 #include <paths.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -46,14 +50,22 @@ __RCSID("$NetBSD: pidfile.c,v 1.11 2015/
 
 static pid_t pidfile_pid;
 static char *pidfile_path;
+static int   pidfile_fd = -1;
 
-/* Deletes an existent pidfile iff it was created by this process. */
+/* Deletes an existent pidfile if it was created by this process. */
 static void
 pidfile_cleanup(void)
 {
 
-	if ((pidfile_path != NULL) && (pidfile_pid == getpid()))
+	if (pidfile_path != NULL && pidfile_pid == getpid()) {
+		(void) close(pidfile_fd);
 		(void) unlink(pidfile_path);
+	}
+
+	pidfile_pid = 0;
+	free(pidfile_path);
+	pidfile_path = NULL;
+	pidfile_fd = -1;
 }
 
 /* Registers an atexit(3) handler to delete the pidfile we have generated.
@@ -75,29 +87,6 @@ register_atexit_handler(void)
 	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)
-{
-	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;
-}
-
 /* 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.
@@ -118,58 +107,115 @@ generate_varrun_path(const char *bname)
 	return path;
 }
 
-/* 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.
+/* Read the contents of path and ensure it matches our notion of a pid.
  *
- * Returns 0 on success or -1 if there is any error. */
-static int
-create_pidfile(const char* path)
+ * Returns the pid inside the file on success, otherwise -1. */
+static pid_t
+pidfile_read(const char *path)
+{
+	char buf[16], *eptr;
+	int fd, error, n, e;
+	pid_t pid;
+
+	if ((fd = open(path, O_RDONLY)) == -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, &e);
+	if (e && !(e == ENOTSUP && *eptr == '\n')) {
+		errno = e;
+		return -1;
+	}
+	return pid;
+}
+
+/* Locks the pidfile specified by path and writes the process pid to it.
+ *
+ * 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 *default_path;
 
 	if (register_atexit_handler() == -1)
 		return -1;
 
-	if (cleanup_old_pidfile(path) == 0)
-		return 0;
-
-	pidfile_path = strdup(path);
-	if (pidfile_path == NULL)
-		return -1;
+	if (path == NULL || strchr(path, '/') == NULL) {
+		if ((default_path = generate_varrun_path(path)) == NULL)
+			return -1;
+		path = default_path;
+	} else
+		default_path = NULL;
 
-	if ((f = fopen(path, "w")) == NULL) {
-		free(pidfile_path);
-		pidfile_path = NULL;
-		return -1;
+	/* If path has changed (no good reason), clean up the old pidfile. */
+	if (pidfile_path != NULL && strcmp(pidfile_path, path) != 0)
+		pidfile_cleanup();
+
+	if (pidfile_fd == -1) {
+		pid_t pid = -1;
+
+		pidfile_fd = open(path,
+		    O_WRONLY | O_CREAT | O_CLOEXEC | O_NONBLOCK | O_EXLOCK,
+		    0666);
+		if (pidfile_fd == -1) {
+			if (errno == EAGAIN) {
+				pid = pidfile_read(path);
+				if (errno == EAGAIN)
+					errno = EEXIST;
+			}
+		} else	if ((pidfile_path = strdup(path)) == NULL) {
+			int error = errno;
+
+			(void) close(pidfile_fd);
+			(void) unlink(path);
+			pidfile_fd = -1;
+			errno = error;
+		}
+		if (pidfile_fd == -1) {
+			free(default_path);
+			return pid;
+		}
 	}
 
 	pidfile_pid = getpid();
+	free(default_path);
 
-	(void) fprintf(f, "%d\n", pidfile_pid);
-	(void) fclose(f);
+	/* Truncate the file, as we could be re-writing it after forking.
+	 * Then write the pidfile. */
+	if (ftruncate(pidfile_fd, 0) == -1 ||
+	    lseek(pidfile_fd, 0, SEEK_SET) == -1 ||
+	    dprintf(pidfile_fd, "%jd\n", (intmax_t)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 pid 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;
 }


Home | Main Index | Thread Index | Old Index