From 6b9006e83cc7fe76173d6460f741af7bc100f814 Mon Sep 17 00:00:00 2001 From: roy Date: Sun, 10 Apr 2016 19:05:50 +0000 Subject: [PATCH] Implement pidfile_lock, pidfile_read and pidfile_clean. Discussed on tech-net@, ok core@. --- distrib/sets/lists/comp/mi | 11 +- include/util.h | 5 +- lib/libutil/Makefile | 5 +- lib/libutil/pidfile.3 | 103 +++++++++++--- lib/libutil/pidfile.c | 271 ++++++++++++++++++++++++------------- 5 files changed, 276 insertions(+), 119 deletions(-) diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi index 827b03f195cc..c04931d9c5ac 100644 --- a/distrib/sets/lists/comp/mi +++ b/distrib/sets/lists/comp/mi @@ -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 diff --git a/include/util.h b/include/util.h index 38d27a52ca65..ad671fddbd14 100644 --- a/include/util.h +++ b/include/util.h @@ -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 time_t *, const int *) __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__ diff --git a/lib/libutil/Makefile b/lib/libutil/Makefile index 80e4d785c7ea..4c81405742c2 100644 --- a/lib/libutil/Makefile +++ b/lib/libutil/Makefile @@ -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 diff --git a/lib/libutil/pidfile.3 b/lib/libutil/pidfile.3 index b1ca92c44a77..51a6adcab3e0 100644 --- a/lib/libutil/pidfile.3 +++ b/lib/libutil/pidfile.3 @@ -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 contains the 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 +.Pp +The +.Fn pidfile_lock , +.Fn pidfile_read +and +.Fn pidfile_clean +function calls appeared in +.Nx 8 . +.Sh CAVEATS .Fn pidfile -uses +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. diff --git a/lib/libutil/pidfile.c b/lib/libutil/pidfile.c index c0cdecc4fb48..6360aef50c41 100644 --- a/lib/libutil/pidfile.c +++ b/lib/libutil/pidfile.c @@ -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 #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 +#include +#include +#include #include #include #include @@ -45,131 +48,205 @@ __RCSID("$NetBSD: pidfile.c,v 1.11 2015/01/22 19:04:28 christos Exp $"); #include 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. */ +/* Closes pidfile resources. + * + * Returns 0 on success, otherwise -1. */ +static int +pidfile_close(void) +{ + int error; + + pidfile_pid = 0; + error = close(pidfile_fd); + pidfile_fd = -1; + pidfile_path[0] = '\0'; + return error; +} + +/* 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 truncation success, otherwise -1. */ +int +pidfile_clean(void) +{ + int error; + + 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; +} + +/* atexit shim for pidfile_clean */ static void pidfile_cleanup(void) { - if ((pidfile_path != NULL) && (pidfile_pid == getpid())) - (void) unlink(pidfile_path); + pidfile_clean(); } -/* 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. - * - * Returns 0 on success or -1 if the handler could not be registered. */ -static int -register_atexit_handler(void) -{ - static bool done = false; - - if (!done) { - if (atexit(pidfile_cleanup) < 0) - return -1; - done = true; - } - - 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 +/* 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; } -/* 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. - * - * Returns 0 on success or -1 if there is any error. */ -static int -create_pidfile(const char* path) +/* 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) { - FILE *f; + char dpath[PATH_MAX], buf[16], *eptr; + int fd, error; + ssize_t n; + pid_t pid; - if (register_atexit_handler() == -1) + 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; - - if (cleanup_old_pidfile(path) == 0) - return 0; - - pidfile_path = strdup(path); - if (pidfile_path == NULL) + } + 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; +} - if ((f = fopen(path, "w")) == NULL) { - free(pidfile_path); - pidfile_path = NULL; - return -1; +/* 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, otherwise the pid of the process who owns the + * lock if it can be read, otherwise -1. */ +pid_t +pidfile_lock(const char *path) +{ + char dpath[PATH_MAX]; + static bool registered_atexit = false; + + /* Register for cleanup with atexit. */ + if (!registered_atexit) { + if (atexit(pidfile_cleanup) == -1) + return -1; + registered_atexit = true; + } + + if (path == NULL || strchr(path, '/') == NULL) { + if (pidfile_varrun_path(dpath, sizeof(dpath), NULL) == -1) + return -1; + path = dpath; + } + + /* 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; + + 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; }