file advisory locking tests from FreeBSD. We fail to detect
authorart <art@openbsd.org>
Fri, 25 Jul 2008 11:41:22 +0000 (11:41 +0000)
committerart <art@openbsd.org>
Fri, 25 Jul 2008 11:41:22 +0000 (11:41 +0000)
two deadlocks at the moment.

regress/sys/kern/flock/Makefile [new file with mode: 0644]
regress/sys/kern/flock/flock.c [new file with mode: 0644]

diff --git a/regress/sys/kern/flock/Makefile b/regress/sys/kern/flock/Makefile
new file mode 100644 (file)
index 0000000..3d20072
--- /dev/null
@@ -0,0 +1,13 @@
+#      $OpenBSD: Makefile,v 1.1 2008/07/25 11:41:22 art Exp $
+
+PROG=  flock
+
+TESTS!=jot 15 1
+
+.for a in ${TESTS}
+t-${a}:
+       ./flock /tmp ${a}
+REGRESS_TARGETS+=t-${a}
+.endfor
+
+.include <bsd.regress.mk>
diff --git a/regress/sys/kern/flock/flock.c b/regress/sys/kern/flock/flock.c
new file mode 100644 (file)
index 0000000..e5e0bac
--- /dev/null
@@ -0,0 +1,1497 @@
+/*-
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/tools/regression/file/flock/flock.c,v 1.3 2008/06/26 10:21:54 dfr Exp $
+ */
+
+#include <sys/time.h>
+#ifdef __FreeBSD__
+#include <sys/mount.h>
+#endif
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __FreeBSD__
+#if __FreeBSD_version >= 800028
+#define HAVE_SYSID
+#endif
+#include <sys/cdefs.h>
+#else
+#ifndef __unused
+#define __unused
+#endif
+#endif
+
+int verbose = 0;
+
+static int
+make_file(const char *pathname, off_t sz)
+{
+       struct stat st;
+       const char *template = "/flocktempXXXXXX";
+       size_t len;
+       char *filename;
+       int fd;
+
+       if (stat(pathname, &st) == 0) {
+               if (S_ISREG(st.st_mode)) {
+                       fd = open(pathname, O_RDWR);
+                       if (fd < 0)
+                               err(1, "open(%s)", pathname);
+                       if (ftruncate(fd, sz) < 0)
+                               err(1, "ftruncate");
+                       return (fd);
+               }
+       }
+
+       len = strlen(pathname) + strlen(template) + 1;
+       filename = malloc(len);
+       snprintf(filename, len, "%s%s", pathname, template);
+       fd = mkstemp(filename);
+       if (fd < 0)
+               err(1, "mkstemp");
+       if (ftruncate(fd, sz) < 0)
+               err(1, "ftruncate");
+       if (unlink(filename) < 0)
+               err(1, "unlink");
+       free(filename);
+
+       return (fd);
+}
+
+static void
+ignore_alarm(int __unused sig)
+{
+}
+
+static int
+safe_waitpid(pid_t pid)
+{
+       int save_errno;
+       int status;
+
+       save_errno = errno;
+       errno = 0;
+       while (waitpid(pid, &status, 0) != pid) {
+               if (errno == EINTR)
+                       continue;
+               err(1, "waitpid");
+       }
+       errno = save_errno;
+
+       return (status);
+}
+
+static int
+safe_kill(pid_t pid, int sig)
+{
+       int save_errno;
+       int status;
+
+       save_errno = errno;
+       errno = 0;
+       status = kill(pid, sig);
+       errno = save_errno;
+
+       return (status);
+}
+
+#define FAIL(test)                                                     \
+       do {                                                            \
+               if (test) {                                             \
+                       if (verbose) printf("FAIL (%s)\n", #test);      \
+                       return -1;                                      \
+               }                                                       \
+       } while (0)
+
+#define SUCCEED \
+       do { if (verbose) printf("SUCCEED\n"); return 0; } while (0)
+
+/*
+ * Test 1 - F_GETLK on unlocked region
+ *
+ * If no lock is found that would prevent this lock from being
+ * created, the structure is left unchanged by this function call
+ * except for the lock type which is set to F_UNLCK.
+ */
+static int
+test1(int fd, __unused int argc, const __unused char **argv)
+{
+       struct flock fl1, fl2;
+
+       memset(&fl1, 1, sizeof(fl1));
+       fl1.l_type = F_WRLCK;
+       fl1.l_whence = SEEK_SET;
+       fl2 = fl1;
+
+       if (fcntl(fd, F_GETLK, &fl1) < 0)
+               err(1, "F_GETLK");
+
+       if (verbose) printf("1 - F_GETLK on unlocked region: ");
+       FAIL(fl1.l_start != fl2.l_start);
+       FAIL(fl1.l_len != fl2.l_len);
+       FAIL(fl1.l_pid != fl2.l_pid);
+       FAIL(fl1.l_type != F_UNLCK);
+       FAIL(fl1.l_whence != fl2.l_whence);
+#ifdef HAVE_SYSID
+       FAIL(fl1.l_sysid != fl2.l_sysid);
+#endif
+
+       SUCCEED;
+}
+
+/*
+ * Test 2 - F_SETLK on locked region
+ *
+ * If a shared or exclusive lock cannot be set, fcntl returns
+ * immediately with EACCES or EAGAIN.
+ */
+static int
+test2(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+       int res;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       /*
+        * fcntl should return -1 with errno set to either EACCES or
+        * EAGAIN.
+        */
+       if (verbose) printf("2 - F_SETLK on locked region: ");
+       res = fcntl(fd, F_SETLK, &fl);
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+       FAIL(res == 0);
+       FAIL(errno != EACCES && errno != EAGAIN);
+
+       SUCCEED;
+}
+
+/*
+ * Test 3 - F_SETLKW on locked region
+ *
+ * If a shared or exclusive lock is blocked by other locks, the
+ * process waits until the request can be satisfied.
+ *
+ * XXX this test hangs on FreeBSD NFS filesystems due to limitations
+ * in FreeBSD's client (and server) lockd implementation.
+ */
+static int
+test3(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+       int res;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       /*
+        * fcntl should wait until the alarm and then return -1 with
+        * errno set to EINTR.
+        */
+       if (verbose) printf("3 - F_SETLKW on locked region: ");
+
+       alarm(1);
+
+       res = fcntl(fd, F_SETLKW, &fl);
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+       FAIL(res == 0);
+       FAIL(errno != EINTR);
+
+       SUCCEED;
+}
+
+/*
+ * Test 4 - F_GETLK on locked region
+ *
+ * Get the first lock that blocks the lock.
+ */
+static int
+test4(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 99;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       /*
+        * fcntl should return a lock structure reflecting the lock we
+        * made in the child process.
+        */
+       if (fcntl(fd, F_GETLK, &fl) < 0)
+               err(1, "F_GETLK");
+
+       if (verbose) printf("4 - F_GETLK on locked region: ");
+       FAIL(fl.l_start != 0);
+       FAIL(fl.l_len != 99);
+       FAIL(fl.l_type != F_WRLCK);
+       FAIL(fl.l_pid != pid);
+#ifdef HAVE_SYSID
+       FAIL(fl.l_sysid != 0);
+#endif
+
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+
+       SUCCEED;
+}
+
+/*
+ * Test 5 - F_SETLKW simple deadlock
+ *
+ * If a blocking shared lock request would cause a deadlock (i.e. the
+ * lock request is blocked by a process which is itself blocked on a
+ * lock currently owned by the process making the new request),
+ * EDEADLK is returned.
+ */
+static int
+test5(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. Because our test relies on the child process being
+        * blocked on the parent's lock, we can't easily use a pipe to
+        * synchronize so we just sleep in the parent to given the
+        * child a chance to setup.
+        *
+        * To create the deadlock condition, we arrange for the parent
+        * to lock the first byte of the file and the child to lock
+        * the second byte.  After locking the second byte, the child
+        * will attempt to lock the first byte of the file, and
+        * block. The parent will then attempt to lock the second byte
+        * (owned by the child) which should cause deadlock.
+        */
+       int pid;
+       struct flock fl;
+       int res;
+
+       /*
+        * Lock the first byte in the parent.
+        */
+       fl.l_start = 0;
+       fl.l_len = 1;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+       if (fcntl(fd, F_SETLK, &fl) < 0)
+               err(1, "F_SETLK 1 (parent)");
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * Lock the second byte in the child and then block on
+                * the parent's lock.
+                */
+               fl.l_start = 1;
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               fl.l_start = 0;
+               if (fcntl(fd, F_SETLKW, &fl) < 0)
+                       err(1, "F_SETLKW (child)");
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       sleep(1);
+
+       /*
+        * fcntl should immediately return -1 with errno set to
+        * EDEADLK. If the alarm fires, we failed to detect the
+        * deadlock.
+        */
+       alarm(1);
+       if (verbose) printf("5 - F_SETLKW simple deadlock: ");
+
+       fl.l_start = 1;
+       res = fcntl(fd, F_SETLKW, &fl);
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       
+       FAIL(res == 0);
+       FAIL(errno != EDEADLK);
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_UNLCK;
+       if (fcntl(fd, F_SETLK, &fl) < 0)
+               err(1, "F_UNLCK");
+
+       /*
+        * Cancel the alarm to avoid confusing later tests.
+        */
+       alarm(0);
+
+       SUCCEED;
+}
+
+/*
+ * Test 6 - F_SETLKW complex deadlock.
+ *
+ * This test involves three process, P, C1 and C2. We set things up so
+ * that P locks byte zero, C1 locks byte 1 and C2 locks byte 2. We
+ * also block C2 by attempting to lock byte zero. Lastly, P attempts
+ * to lock a range including byte 1 and 2. This represents a deadlock
+ * (due to C2's blocking attempt to lock byte zero).
+ */
+static int
+test6(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * Because our test relies on the child process being blocked
+        * on the parent's lock, we can't easily use a pipe to
+        * synchronize so we just sleep in the parent to given the
+        * children a chance to setup.
+        */
+       int pid1, pid2;
+       struct flock fl;
+       int res;
+
+       /*
+        * Lock the first byte in the parent.
+        */
+       fl.l_start = 0;
+       fl.l_len = 1;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+       if (fcntl(fd, F_SETLK, &fl) < 0)
+               err(1, "F_SETLK 1 (parent)");
+
+       pid1 = fork();
+       if (pid1 < 0)
+               err(1, "fork");
+
+       if (pid1 == 0) {
+               /*
+                * C1
+                * Lock the second byte in the child and then sleep
+                */
+               fl.l_start = 1;
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child1)");
+               pause();
+               exit(0);
+       }
+
+       pid2 = fork();
+       if (pid2 < 0)
+               err(1, "fork");
+
+       if (pid2 == 0) {
+               /*
+                * C2
+                * Lock the third byte in the child and then block on
+                * the parent's lock.
+                */
+               fl.l_start = 2;
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child2)");
+               fl.l_start = 0;
+               if (fcntl(fd, F_SETLKW, &fl) < 0)
+                       err(1, "F_SETLKW (child2)");
+               exit(0);
+       }
+
+       /*
+        * Wait until the children have set their locks and then
+        * perform the test.
+        */
+       sleep(1);
+
+       /*
+        * fcntl should immediately return -1 with errno set to
+        * EDEADLK. If the alarm fires, we failed to detect the
+        * deadlock.
+        */
+       alarm(1);
+       if (verbose) printf("6 - F_SETLKW complex deadlock: ");
+
+       fl.l_start = 1;
+       fl.l_len = 2;
+       res = fcntl(fd, F_SETLKW, &fl);
+       safe_kill(pid1, SIGTERM);
+       safe_waitpid(pid1);
+       safe_kill(pid2, SIGTERM);
+       safe_waitpid(pid2);
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_UNLCK;
+       if (fcntl(fd, F_SETLK, &fl) < 0)
+               err(1, "F_UNLCK");
+
+       FAIL(res == 0);
+       FAIL(errno != EDEADLK);
+
+       /*
+        * Cancel the alarm to avoid confusing later tests.
+        */
+       alarm(0);
+
+       SUCCEED;
+}
+
+/*
+ * Test 7 - F_SETLK shared lock on exclusive locked region
+ *
+ * If a shared or exclusive lock cannot be set, fcntl returns
+ * immediately with EACCES or EAGAIN.
+ */
+static int
+test7(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+       int res;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       /*
+        * fcntl should wait until the alarm and then return -1 with
+        * errno set to EINTR.
+        */
+       if (verbose) printf("7 - F_SETLK shared lock on exclusive locked region: ");
+
+       fl.l_type = F_RDLCK;
+       res = fcntl(fd, F_SETLK, &fl);
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+
+       FAIL(res == 0);
+       FAIL(errno != EACCES && errno != EAGAIN);
+
+       SUCCEED;
+}
+
+/*
+ * Test 8 - F_SETLK shared lock on share locked region
+ *
+ * When a shared lock is set on a segment of a file, other processes
+ * shall be able to set shared locks on that segment or a portion of
+ * it.
+ */
+static int
+test8(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+       int res;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_RDLCK;
+       fl.l_whence = SEEK_SET;
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       /*
+        * fcntl should wait until the alarm and then return -1 with
+        * errno set to EINTR.
+        */
+       if (verbose) printf("8 - F_SETLK shared lock on share locked region: ");
+
+       fl.l_type = F_RDLCK;
+       res = fcntl(fd, F_SETLK, &fl);
+
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_UNLCK;
+       if (fcntl(fd, F_SETLK, &fl) < 0)
+               err(1, "F_UNLCK");
+
+       FAIL(res != 0);
+
+       SUCCEED;
+}
+
+/*
+ * Test 9 - F_SETLK exclusive lock on share locked region
+ *
+ * If a shared or exclusive lock cannot be set, fcntl returns
+ * immediately with EACCES or EAGAIN.
+ */
+static int
+test9(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+       int res;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_RDLCK;
+       fl.l_whence = SEEK_SET;
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       /*
+        * fcntl should wait until the alarm and then return -1 with
+        * errno set to EINTR.
+        */
+       if (verbose) printf("9 - F_SETLK exclusive lock on share locked region: ");
+
+       fl.l_type = F_WRLCK;
+       res = fcntl(fd, F_SETLK, &fl);
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+
+       FAIL(res == 0);
+       FAIL(errno != EACCES && errno != EAGAIN);
+
+       SUCCEED;
+}
+
+/*
+ * Test 10 - trying to set bogus pid or sysid values
+ *
+ * The l_pid and l_sysid fields are only used with F_GETLK to return
+ * the process ID of the process holding a blocking lock and the
+ * system ID of the system that owns that process
+ */
+static int
+test10(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+       fl.l_pid = 9999;
+#ifdef HAVE_SYSID
+       fl.l_sysid = 9999;
+#endif
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       if (verbose) printf("10 - trying to set bogus pid or sysid values: ");
+
+       if (fcntl(fd, F_GETLK, &fl) < 0)
+               err(1, "F_GETLK");
+
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+
+       FAIL(fl.l_pid != pid);
+#ifdef HAVE_SYSID
+       FAIL(fl.l_sysid != 0);
+#endif
+
+       SUCCEED;
+}
+
+/*
+ * Test 11 - remote locks
+ *
+ * XXX temporary interface which will be removed when the kernel lockd
+ * is added.
+ */
+static int
+test11(int fd, __unused int argc, const __unused char **argv)
+{
+#ifdef F_SETLK_REMOTE
+       struct flock fl;
+       int res;
+
+       if (geteuid() != 0)
+               return 0;
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+       fl.l_pid = 9999;
+       fl.l_sysid = 1001;
+
+       if (verbose) printf("11 - remote locks: ");
+
+       res = fcntl(fd, F_SETLK_REMOTE, &fl);
+       FAIL(res != 0);
+
+       fl.l_sysid = 1002;
+       res = fcntl(fd, F_SETLK_REMOTE, &fl);
+       FAIL(res == 0);
+       FAIL(errno != EACCES && errno != EAGAIN);
+
+       res = fcntl(fd, F_GETLK, &fl);
+       FAIL(res != 0);
+       FAIL(fl.l_pid != 9999);
+       FAIL(fl.l_sysid != 1001);
+
+       fl.l_type = F_UNLCK;
+       fl.l_sysid = 1001;
+       fl.l_start = 0;
+       fl.l_len = 0;
+       res = fcntl(fd, F_SETLK_REMOTE, &fl);
+       FAIL(res != 0);
+
+       fl.l_pid = 1234;
+       fl.l_sysid = 1001;
+       fl.l_start = 0;
+       fl.l_len = 1;
+       fl.l_whence = SEEK_SET;
+       fl.l_type = F_RDLCK;
+       res = fcntl(fd, F_SETLK_REMOTE, &fl);
+       FAIL(res != 0);
+
+       fl.l_sysid = 1002;
+       res = fcntl(fd, F_SETLK_REMOTE, &fl);
+       FAIL(res != 0);
+
+       fl.l_type = F_UNLCKSYS;
+       fl.l_sysid = 1001;
+       res = fcntl(fd, F_SETLK_REMOTE, &fl);
+       FAIL(res != 0);
+
+       fl.l_type = F_WRLCK;
+       res = fcntl(fd, F_GETLK, &fl);
+       FAIL(res != 0);
+       FAIL(fl.l_pid != 1234);
+       FAIL(fl.l_sysid != 1002);
+
+       fl.l_type = F_UNLCKSYS;
+       fl.l_sysid = 1002;
+       res = fcntl(fd, F_SETLK_REMOTE, &fl);
+       FAIL(res != 0);
+
+       SUCCEED;
+#else
+       return 0;
+#endif
+}
+
+/*
+ * Test 12 - F_SETLKW on locked region which is then unlocked
+ *
+ * If a shared or exclusive lock is blocked by other locks, the
+ * process waits until the request can be satisfied.
+ */
+static int
+test12(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+       int res;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a write lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_SETLK (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+
+               sleep(1);
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       /*
+        * fcntl should wait until the alarm and then return -1 with
+        * errno set to EINTR.
+        */
+       if (verbose) printf("12 - F_SETLKW on locked region which is then unlocked: ");
+
+       //alarm(1);
+
+       res = fcntl(fd, F_SETLKW, &fl);
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+       close(pfd[0]);
+       close(pfd[1]);
+       FAIL(res != 0);
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_UNLCK;
+       if (fcntl(fd, F_SETLK, &fl) < 0)
+               err(1, "F_UNLCK");
+
+       SUCCEED;
+}
+
+/*
+ * Test 13 - F_SETLKW on locked region, race with owner
+ *
+ * If a shared or exclusive lock is blocked by other locks, the
+ * process waits until the request can be satisfied.
+ */
+static int
+test13(int fd, __unused int argc, const __unused char **argv)
+{
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        */
+       int i;
+       int pid;
+       int pfd[2];
+       struct flock fl;
+       char ch;
+       int res;
+       struct itimerval itv;
+
+       if (verbose) printf("13 - F_SETLKW on locked region, race with owner: ");
+       fflush(stdout);
+
+       for (i = 0; i < 100; i++) {
+               if (pipe(pfd) < 0)
+                       err(1, "pipe");
+
+               fl.l_start = 0;
+               fl.l_len = 0;
+               fl.l_type = F_WRLCK;
+               fl.l_whence = SEEK_SET;
+
+               pid = fork();
+               if (pid < 0)
+                       err(1, "fork");
+
+               if (pid == 0) {
+                       /*
+                        * We are the child. We set a write lock and then
+                        * write one byte back to the parent to tell it. The
+                        * parent will kill us when its done.
+                        */
+                       if (fcntl(fd, F_SETLK, &fl) < 0)
+                               err(1, "F_SETLK (child)");
+                       if (write(pfd[1], "a", 1) < 0)
+                               err(1, "writing to pipe (child)");
+
+                       sleep(1);
+                       exit(0);
+               }
+
+               /*
+                * Wait until the child has set its lock and then perform the
+                * test.
+                */
+               while (read(pfd[0], &ch, 1) != 1) {
+                       if (errno == EINTR)
+                               continue;
+                       err(1, "reading from pipe (child)");
+               }
+
+               /*
+                * fcntl should wait until the alarm and then return -1 with
+                * errno set to EINTR.
+                */
+               itv.it_interval.tv_sec = 0;
+               itv.it_interval.tv_usec = 0;
+               itv.it_value.tv_sec = 0;
+               itv.it_value.tv_usec = 2;
+               setitimer(ITIMER_REAL, &itv, NULL);
+
+               res = fcntl(fd, F_SETLKW, &fl);
+               safe_kill(pid, SIGTERM);
+               safe_waitpid(pid);
+               close(pfd[0]);
+               close(pfd[1]);
+               FAIL(!(res == 0 || (res == -1 && errno == EINTR)));
+
+               fl.l_start = 0;
+               fl.l_len = 0;
+               fl.l_type = F_UNLCK;
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "F_UNLCK");
+       }
+       SUCCEED;
+}
+
+/*
+ * Test 14 - soak test
+ */
+static int
+test14(int fd, int argc, const char **argv)
+{
+#define CHILD_COUNT 20
+       /*
+        * We create a set of child processes and let each one run
+        * through a random sequence of locks and unlocks.
+        */
+       int i, j, id, id_base;
+       int pids[CHILD_COUNT], pid;
+       char buf[128];
+       char tbuf[128];
+       int map[128];
+       char outbuf[512];
+       struct flock fl;
+       struct itimerval itv;
+       int status;
+
+       id_base = 0;
+       if (argc >= 2)
+               id_base = strtol(argv[1], NULL, 0);
+
+       if (verbose) printf("14 - soak test: ");
+       fflush(stdout);
+
+       for (i = 0; i < 128; i++)
+               map[i] = F_UNLCK;
+
+       for (i = 0; i < CHILD_COUNT; i++) {
+
+               pid = fork();
+               if (pid < 0)
+                       err(1, "fork");
+               if (pid) {
+                       /*
+                        * Parent - record the pid and continue.
+                        */
+                       pids[i] = pid;
+                       continue;
+               }
+
+               /*
+                * Child - do some work and exit.
+                */
+               id = id_base + i;
+               srandom(getpid());
+
+               for (j = 0; j < 50; j++) {
+                       int start, end, len;
+                       int set, wrlock;
+
+                       do {
+                               start = random() & 127;
+                               end = random() & 127;
+                       } while (end <= start);
+
+                       set = random() & 1;
+                       wrlock = random() & 1;
+
+                       len = end - start;
+                       fl.l_start = start;
+                       fl.l_len = len;
+                       fl.l_whence = SEEK_SET;
+                       if (set)
+                               fl.l_type = wrlock ? F_WRLCK : F_RDLCK;
+                       else
+                               fl.l_type = F_UNLCK;
+
+                       itv.it_interval.tv_sec = 0;
+                       itv.it_interval.tv_usec = 0;
+                       itv.it_value.tv_sec = 0;
+                       itv.it_value.tv_usec = 3000;
+                       setitimer(ITIMER_REAL, &itv, NULL);
+
+                       if (fcntl(fd, F_SETLKW, &fl) < 0) {
+                               if (errno == EDEADLK || errno == EINTR) {
+                                       if (verbose) {
+                                               snprintf(outbuf, sizeof(outbuf),
+                                                   "%d[%d]: %s [%d .. %d] %s\n",
+                                                   id, j,
+                                                   set ? (wrlock ? "write lock"
+                                                       : "read lock")
+                                                   : "unlock", start, end,
+                                                   errno == EDEADLK
+                                                   ? "deadlock"
+                                                   : "interrupted");
+                                               write(1, outbuf,
+                                                   strlen(outbuf));
+                                       }
+                                       continue;
+                               } else {
+                                       perror("fcntl");
+                               }
+                       }
+
+                       itv.it_interval.tv_sec = 0;
+                       itv.it_interval.tv_usec = 0;
+                       itv.it_value.tv_sec = 0;
+                       itv.it_value.tv_usec = 0;
+                       setitimer(ITIMER_REAL, &itv, NULL);
+
+                       if (verbose) {
+                               snprintf(outbuf, sizeof(outbuf),
+                                   "%d[%d]: %s [%d .. %d] succeeded\n",
+                                   id, j,
+                                   set ? (wrlock ? "write lock" : "read lock")
+                                   : "unlock", start, end);
+                               write(1, outbuf, strlen(outbuf));
+                       }
+
+                       if (set) {
+                               if (wrlock) {
+                                       /*
+                                        * We got a write lock - write
+                                        * our ID to each byte that we
+                                        * managed to claim.
+                                        */
+                                       for (i = start; i < end; i++)
+                                               map[i] = F_WRLCK;
+                                       memset(&buf[start], id, len);
+                                       if (pwrite(fd, &buf[start], len,
+                                               start) != len) {
+                                               printf("%d: short write\n", id);
+                                               exit(1);
+                                       }
+                               } else {
+                                       /*
+                                        * We got a read lock - read
+                                        * the bytes which we claimed
+                                        * so that we can check that
+                                        * they don't change
+                                        * unexpectedly.
+                                        */
+                                       for (i = start; i < end; i++)
+                                               map[i] = F_RDLCK;
+                                       if (pread(fd, &buf[start], len,
+                                               start) != len) {
+                                               printf("%d: short read\n", id);
+                                               exit(1);
+                                       }
+                               }
+                       } else {
+                               for (i = start; i < end; i++)
+                                       map[i] = F_UNLCK;
+                       }
+
+                       usleep(1000);
+
+                       /*
+                        * Read back the whole region so that we can
+                        * check that all the bytes we have some kind
+                        * of claim to have the correct value.
+                        */
+                       if (pread(fd, tbuf, sizeof(tbuf), 0) != sizeof(tbuf)) {
+                               printf("%d: short read\n", id);
+                               exit(1);
+                       }
+
+                       for (i = 0; i < 128; i++) {
+                               if (map[i] != F_UNLCK && buf[i] != tbuf[i]) {
+                                       snprintf(outbuf, sizeof(outbuf),
+                                           "%d: byte %d expected %d, "
+                                           "got %d\n", id, i, buf[i], tbuf[i]);
+                                       write(1, outbuf, strlen(outbuf));
+                                       exit(1);
+                               }
+                       }
+               }
+               if (verbose)
+                       printf("%d[%d]: done\n", id, j);
+
+               exit(0);
+       }
+
+       status = 0;
+       for (i = 0; i < CHILD_COUNT; i++) {
+               status += safe_waitpid(pids[i]);
+       }
+       if (status)
+               FAIL(status != 0);
+
+       SUCCEED;
+}
+
+/*
+ * Test 15 - flock(2) semantcs
+ *
+ * When a lock holder has a shared lock and attempts to upgrade that
+ * shared lock to exclusive, it must drop the shared lock before
+ * blocking on the exclusive lock.
+ *
+ * To test this, we first arrange for two shared locks on the file,
+ * and then attempt to upgrade one of them to exclusive. This should
+ * drop one of the shared locks and block. We interrupt the blocking
+ * lock request and examine the lock state of the file after dropping
+ * the other shared lock - there should be no active locks at this
+ * point.
+ */
+static int
+test15(int fd, __unused int argc, const __unused char **argv)
+{
+#ifdef LOCK_EX
+       /*
+        * We create a child process to hold the lock which we will
+        * test. We use a pipe to communicate with the child.
+        *
+        * Since we only have one file descriptors and lock ownership
+        * for flock(2) goes with the file descriptor, we use fcntl to
+        * set the child's shared lock.
+        */
+       int pid;
+       int pfd[2];
+       int fd2;
+       struct flock fl;
+       char ch;
+       int res;
+
+       if (pipe(pfd) < 0)
+               err(1, "pipe");
+
+       pid = fork();
+       if (pid < 0)
+               err(1, "fork");
+
+       if (pid == 0) {
+               /*
+                * We are the child. We set a shared lock and then
+                * write one byte back to the parent to tell it. The
+                * parent will kill us when its done.
+                */
+               fl.l_start = 0;
+               fl.l_len = 0;
+               fl.l_type = F_RDLCK;
+               fl.l_whence = SEEK_SET;
+               if (fcntl(fd, F_SETLK, &fl) < 0)
+                       err(1, "fcntl(F_SETLK) (child)");
+               if (write(pfd[1], "a", 1) < 0)
+                       err(1, "writing to pipe (child)");
+               pause();
+               exit(0);
+       }
+
+       /*
+        * Wait until the child has set its lock and then perform the
+        * test.
+        */
+       if (read(pfd[0], &ch, 1) != 1)
+               err(1, "reading from pipe (child)");
+
+       fd2 = dup(fd);
+       if (flock(fd, LOCK_SH) < 0)
+               err(1, "flock shared");
+
+       /*
+        * flock should wait until the alarm and then return -1 with
+        * errno set to EINTR.
+        */
+       if (verbose) printf("15 - flock(2) semantics: ");
+
+       alarm(1);
+       flock(fd, LOCK_EX);
+
+       /*
+        * Kill the child to force it to drop its locks.
+        */
+       safe_kill(pid, SIGTERM);
+       safe_waitpid(pid);
+
+       fl.l_start = 0;
+       fl.l_len = 0;
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+       res = fcntl(fd, F_GETLK, &fl);
+
+       close(pfd[0]);
+       close(pfd[1]);
+       FAIL(res != 0);
+       FAIL(fl.l_type != F_UNLCK);
+
+       SUCCEED;
+#else
+       return 0;
+#endif
+}
+
+struct test {
+       int (*testfn)(int, int, const char **); /* function to perform the test */
+       int num;                /* test number */
+       int intr;               /* non-zero if the test interrupts a lock */
+};
+
+struct test tests[] = {
+       {       test1,          1,      0       },
+       {       test2,          2,      0       },
+       {       test3,          3,      1       },
+       {       test4,          4,      0       },
+       {       test5,          5,      1       },
+       {       test6,          6,      1       },
+       {       test7,          7,      0       },
+       {       test8,          8,      0       },
+       {       test9,          9,      0       },
+       {       test10,         10,     0       },
+       {       test11,         11,     1       },
+       {       test12,         12,     0       },
+       {       test13,         13,     1       },
+       {       test14,         14,     0       },
+       {       test15,         15,     1       },
+};
+int test_count = sizeof(tests) / sizeof(tests[0]);
+
+int
+main(int argc, const char *argv[])
+{
+       int testnum;
+       int fd;
+       int nointr;
+       int i;
+       struct sigaction sa;
+       int test_argc;
+       const char **test_argv;
+       int ret;
+
+       if (argc < 2) {
+               errx(1, "usage: flock <directory> [test number] ...");
+       }
+
+       fd = make_file(argv[1], 1024);
+       if (argc >= 3) {
+               testnum = strtol(argv[2], NULL, 0);
+               test_argc = argc - 2;
+               test_argv = argv + 2;
+       } else {
+               verbose = 1;
+               testnum = 0;
+               test_argc = 0;
+               test_argv = 0;
+       }
+
+       sa.sa_handler = ignore_alarm;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = 0;
+       sigaction(SIGALRM, &sa, 0);
+
+       nointr = 0;
+#if defined(__FreeBSD__) && __FreeBSD_version < 800040
+       {
+               /*
+                * FreeBSD with userland NLM can't interrupt a blocked
+                * lock request on an NFS mounted filesystem.
+                */
+               struct statfs st;
+               fstatfs(fd, &st);
+               nointr = !strcmp(st.f_fstypename, "nfs");
+       }
+#endif
+
+       ret = 0;
+       for (i = 0; i < test_count; i++) {
+               if (tests[i].intr && nointr)
+                       continue;
+               if (!testnum || tests[i].num == testnum)
+                       ret |= tests[i].testfn(fd, test_argc, test_argv);
+       }
+
+       return (ret ? 1 : 0);
+}