Implement pidfile_lock, pidfile_read and pidfile_clean.

Discussed on tech-net@, ok core@.
This commit is contained in:
roy 2016-04-10 19:05:50 +00:00
parent 3133e4c397
commit 6b9006e83c
5 changed files with 276 additions and 119 deletions

View File

@ -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

View File

@ -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__

View File

@ -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

View File

@ -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.

View File

@ -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/01/22 19:04:28 christos Exp $");
#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. */
/* 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;
}