summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorngie <ngie@FreeBSD.org>2015-05-13 10:24:23 +0000
committerngie <ngie@FreeBSD.org>2015-05-13 10:24:23 +0000
commit51336e1434aa7ef0425fba7c65b82593035fb8cb (patch)
tree91dca85fbc24e37f3dae30fcbe5c23f50c4ba156 /tests
parent559871946b1e0e49c7548d9e828172705cbb4f57 (diff)
downloadFreeBSD-src-51336e1434aa7ef0425fba7c65b82593035fb8cb.zip
FreeBSD-src-51336e1434aa7ef0425fba7c65b82593035fb8cb.tar.gz
MFC r282067:
Integrate tools/regression/fifo into the FreeBSD test suite as tests/sys/fifo and tools/regression/file into the FreeBSD test suite as tests/sys/file
Diffstat (limited to 'tests')
-rw-r--r--tests/sys/Makefile2
-rw-r--r--tests/sys/fifo/Makefile13
-rw-r--r--tests/sys/fifo/fifo_create.c282
-rw-r--r--tests/sys/fifo/fifo_io.c1399
-rw-r--r--tests/sys/fifo/fifo_misc.c335
-rw-r--r--tests/sys/fifo/fifo_open.c476
-rw-r--r--tests/sys/file/Makefile25
-rw-r--r--tests/sys/file/closefrom_test.c275
-rw-r--r--tests/sys/file/dup_test.c386
-rw-r--r--tests/sys/file/fcntlflags_test.c110
-rw-r--r--tests/sys/file/flock_helper.c1598
-rwxr-xr-xtests/sys/file/flock_test.sh57
-rw-r--r--tests/sys/file/ftruncate_test.c177
-rw-r--r--tests/sys/file/newfileops_on_fork_test.c121
14 files changed, 5256 insertions, 0 deletions
diff --git a/tests/sys/Makefile b/tests/sys/Makefile
index 4eb9a5e..5a345c4 100644
--- a/tests/sys/Makefile
+++ b/tests/sys/Makefile
@@ -4,6 +4,8 @@
TESTSDIR= ${TESTSBASE}/sys
+TESTS_SUBDIRS+= fifo
+TESTS_SUBDIRS+= file
TESTS_SUBDIRS+= kern
TESTS_SUBDIRS+= kqueue
TESTS_SUBDIRS+= netinet
diff --git a/tests/sys/fifo/Makefile b/tests/sys/fifo/Makefile
new file mode 100644
index 0000000..602bf1e
--- /dev/null
+++ b/tests/sys/fifo/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/fifo
+
+PLAIN_TESTS_C+= fifo_create
+PLAIN_TESTS_C+= fifo_io
+PLAIN_TESTS_C+= fifo_misc
+PLAIN_TESTS_C+= fifo_open
+
+TEST_METADATA.fifo_create+= required_user="root"
+TEST_METADATA.fifo_open+= required_user="root"
+
+.include <bsd.test.mk>
diff --git a/tests/sys/fifo/fifo_create.c b/tests/sys/fifo/fifo_create.c
new file mode 100644
index 0000000..2eb01e5
--- /dev/null
+++ b/tests/sys/fifo/fifo_create.c
@@ -0,0 +1,282 @@
+/*-
+ * Copyright (c) 2005-2008 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * 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$
+ */
+
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Simple regression test for the creation and destruction of POSIX fifos in
+ * the file system name space. Using a specially created directory, create
+ * a fifo in it and check that the following properties are present, as
+ * specified in IEEE Std 1003.1, 2004 Edition:
+ *
+ * - When mkfifo() or mknod(S_IFIFO) is called, on success, a fifo is
+ * created.
+ *
+ * - On an error, no fifo is created. (XXX: Not tested)
+ *
+ * - The mode bits on the fifo are a product of combining the umask and
+ * requested mode.
+ *
+ * - The fifo's owner will be the processes effective user ID. (XXX: Not
+ * tested)
+ *
+ * - The fifo's group will be the parent directory's group or the effective
+ * group ID of the process. For historical reasons, BSD prefers the group
+ * ID of the process, so we will generate an error if it's not that. (XXX:
+ * Not tested)
+ *
+ * - The st_atime, st_ctime, st_mtime of the fifo will be set appropriately,
+ * and st_ctime and st_mtime on the directory will be updated. (XXX: We
+ * test they are updated, not correct)
+ *
+ * - EEXIST is returned if the named file already exists.
+ *
+ * In addition, we check that we can unlink the fifo, and that if we do, it
+ * disappears.
+ *
+ * This test must run as root in order to usefully frob the process
+ * credential to test permission parts.
+ */
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+/*
+ * Basic creation tests: verify that mkfifo(2) (or mknod(2)) creates a fifo,
+ * that the time stamps on the directory are updated, that if we try twice we
+ * get EEXIST, and that we can unlink it.
+ */
+static void
+fifo_create_test(int use_mkfifo)
+{
+ struct stat old_dirsb, dirsb, fifosb;
+ const char *testname;
+ char path[] = "testfifo";
+ int error;
+
+ if (use_mkfifo)
+ testname = "mkfifo";
+ else
+ testname = "mknod";
+
+ /*
+ * Sleep to make sure that the time stamp on the directory will be
+ * updated.
+ */
+ if (stat(".", &old_dirsb) < 0)
+ err(-1, "basic_create_test: %s: stat: %s", testname,
+ temp_dir);
+
+ sleep(2);
+
+ if (use_mkfifo) {
+ if (mkfifo(path, 0600) < 0)
+ err(-1, "basic_create_test: %s: %s", testname, path);
+ } else {
+ if (mknod(path, S_IFIFO | 0600, 0) < 0)
+ err(-1, "basic_create_test: %s: %s", testname, path);
+ }
+
+ if (stat(path, &fifosb) < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "basic_create_test: %s: stat: %s", testname, path);
+ }
+
+ if (!(S_ISFIFO(fifosb.st_mode))) {
+ (void)unlink(path);
+ errx(-1, "basic_create_test: %s produced non-fifo",
+ testname);
+ }
+
+ if (use_mkfifo) {
+ if (mkfifo(path, 0600) == 0)
+ errx(-1, "basic_create_test: dup %s succeeded",
+ testname);
+ } else {
+ if (mknod(path, S_IFIFO | 0600, 0) == 0)
+ errx(-1, "basic_create_test: dup %s succeeded",
+ testname);
+ }
+
+ if (errno != EEXIST)
+ err(-1, "basic_create_test: dup %s unexpected error",
+ testname);
+
+ if (stat(".", &dirsb) < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "basic_create_test: %s: stat: %s", testname,
+ temp_dir);
+ }
+
+ if (old_dirsb.st_ctime == dirsb.st_ctime) {
+ (void)unlink(path);
+ errx(-1, "basic_create_test: %s: old_dirsb.st_ctime == "
+ "dirsb.st_ctime", testname);
+ }
+
+ if (old_dirsb.st_mtime == dirsb.st_mtime) {
+ (void)unlink(path);
+ errx(-1, "basic_create_test: %s: old_dirsb.st_mtime == "
+ "dirsb.st_mtime", testname);
+ }
+
+ if (unlink(path) < 0)
+ err(-1, "basic_create_test: %s: unlink: %s", testname, path);
+
+ if (stat(path, &fifosb) == 0)
+ errx(-1, "basic_create_test: %s: unlink failed to unlink",
+ testname);
+ if (errno != ENOENT)
+ err(-1, "basic_create_test: %s: unlink unexpected error",
+ testname);
+}
+
+/*
+ * Having determined that basic create/remove/etc functionality is present
+ * for fifos, now make sure that the umask, requested permissions, and
+ * resulting mode are handled properly.
+ */
+static const struct permission_test {
+ mode_t pt_umask;
+ mode_t pt_reqmode;
+ mode_t pt_mode;
+} permission_test[] = {
+ {0000, 0, S_IFIFO},
+ {0000, S_IRWXU, S_IFIFO | S_IRWXU},
+ {0000, S_IRWXU | S_IRWXG | S_IRWXO, S_IFIFO | S_IRWXU | S_IRWXG |
+ S_IRWXO },
+ {0077, S_IRWXU, S_IFIFO | S_IRWXU},
+ {0077, S_IRWXU | S_IRWXG | S_IRWXO, S_IFIFO | S_IRWXU},
+};
+static const int permission_test_count = sizeof(permission_test) /
+ sizeof(struct permission_test);
+
+static void
+fifo_permission_test(int use_mkfifo)
+{
+ const struct permission_test *ptp;
+ mode_t __unused old_umask;
+ char path[] = "testfifo";
+ const char *testname;
+ struct stat sb;
+ int error, i;
+
+ if (use_mkfifo)
+ testname = "mkfifo";
+ else
+ testname = "mknod";
+
+ old_umask = umask(0022);
+ for (i = 0; i < permission_test_count; i++) {
+ ptp = &permission_test[i];
+
+ umask(ptp->pt_umask);
+ if (use_mkfifo) {
+ if (mkfifo(path, ptp->pt_reqmode) < 0)
+ err(-1, "fifo_permission_test: %s: %08o "
+ "%08o %08o\n", testname, ptp->pt_umask,
+ ptp->pt_reqmode, ptp->pt_mode);
+ } else {
+ if (mknod(path, S_IFIFO | ptp->pt_reqmode, 0) < 0)
+ err(-1, "fifo_permission_test: %s: %08o "
+ "%08o %08o\n", testname, ptp->pt_umask,
+ ptp->pt_reqmode, ptp->pt_mode);
+ }
+
+ if (stat(path, &sb) < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "fifo_permission_test: %s: %s", testname,
+ path);
+ }
+
+ if (sb.st_mode != ptp->pt_mode) {
+ (void)unlink(path);
+ errx(-1, "fifo_permission_test: %s: %08o %08o %08o "
+ "got %08o", testname, ptp->pt_umask,
+ ptp->pt_reqmode, ptp->pt_mode, sb.st_mode);
+ }
+
+ if (unlink(path) < 0)
+ err(-1, "fifo_permission_test: %s: unlink: %s",
+ testname, path);
+ }
+ umask(old_umask);
+}
+
+int
+main(void)
+{
+ int i;
+
+ if (geteuid() != 0)
+ errx(-1, "must be run as root");
+
+ strcpy(temp_dir, "fifo_create.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ atexit(atexit_temp_dir);
+
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir");
+
+ /*
+ * Run each test twice, once with mknod(2) and a second time with
+ * mkfifo(2). Historically, BSD has not allowed mknod(2) to be used
+ * to create fifos, but the Single UNIX Specification requires it.
+ */
+ for (i = 0; i < 2; i++) {
+ fifo_create_test(i);
+ fifo_permission_test(i);
+ }
+
+ return (0);
+}
diff --git a/tests/sys/fifo/fifo_io.c b/tests/sys/fifo/fifo_io.c
new file mode 100644
index 0000000..93d4be7
--- /dev/null
+++ b/tests/sys/fifo/fifo_io.c
@@ -0,0 +1,1399 @@
+/*-
+ * Copyright (c) 2005 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * 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$
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Regression test to exercise POSIX fifo I/O.
+ *
+ * We test a number of aspect of behavior, including:
+ *
+ * - If there's no data to read, then for blocking fifos, we block, and for
+ * non-blocking, we return EAGAIN.
+ *
+ * - If we write ten bytes, ten bytes can be read, and they're the same
+ * bytes, in the same order.
+ *
+ * - If we write two batches of five bytes, we can read the same ten bytes in
+ * one read of ten bytes.
+ *
+ * - If we write ten bytes, we can read the same ten bytes in two reads of
+ * five bytes each.
+ *
+ * - If we over-fill a buffer (by writing 512k, which we take to be a large
+ * number above default buffer sizes), we block if there is no reader.
+ *
+ * - That once 512k (ish) is read from the other end, the blocked writer
+ * wakes up.
+ *
+ * - When a fifo is empty, poll, select, kqueue, and fionread report it is
+ * writable but not readable.
+ *
+ * - When a fifo has data in it, poll, select, and kqueue report that it is
+ * writable.
+ *
+ * - XXX: blocked reader semantics?
+ *
+ * - XXX: event behavior on remote close?
+ *
+ * Although behavior of O_RDWR isn't defined for fifos by POSIX, we expect
+ * "reasonable" behavior, and run some additional tests relating to event
+ * management on O_RDWR fifo descriptors.
+ */
+
+#define KQUEUE_MAX_EVENT 8
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+static void
+makefifo(const char *fifoname, const char *testname)
+{
+
+ if (mkfifo(fifoname, 0700) < 0)
+ err(-1, "%s: makefifo: mkfifo: %s", testname, fifoname);
+}
+
+static void
+cleanfifo2(const char *fifoname, int fd1, int fd2)
+{
+
+ if (fd1 != -1)
+ close(fd1);
+ if (fd2 != -1)
+ close(fd2);
+ (void)unlink(fifoname);
+}
+
+static void
+cleanfifo3(const char *fifoname, int fd1, int fd2, int fd3)
+{
+
+ if (fd3 != -1)
+ close(fd3);
+ cleanfifo2(fifoname, fd1, fd2);
+}
+
+/*
+ * Open two different file descriptors for a fifo: one read, one write. Do
+ * so using non-blocking opens in order to avoid deadlocking the process.
+ */
+static int
+openfifo(const char *fifoname, int *reader_fdp, int *writer_fdp)
+{
+ int error, fd1, fd2;
+
+ fd1 = open(fifoname, O_RDONLY | O_NONBLOCK);
+ if (fd1 < 0)
+ return (-1);
+ fd2 = open(fifoname, O_WRONLY | O_NONBLOCK);
+ if (fd2 < 0) {
+ error = errno;
+ close(fd1);
+ errno = error;
+ return (-1);
+ }
+ *reader_fdp = fd1;
+ *writer_fdp = fd2;
+
+ return (0);
+}
+
+/*
+ * Open one file descriptor for the fifo, supporting both read and write.
+ */
+static int
+openfifo_rw(const char *fifoname, int *fdp)
+{
+ int fd;
+
+ fd = open(fifoname, O_RDWR);
+ if (fd < 0)
+ return (-1);
+ *fdp = fd;
+
+ return (0);
+}
+
+static int
+set_nonblocking(int fd, const char *testname)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ warn("%s: fcntl(fd, F_GETFL)", testname);
+ return(-1);
+ }
+
+ flags |= O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) < 0) {
+ warn("%s: fcntl(fd, 0x%x)", testname, flags);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+set_blocking(int fd, const char *testname)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ warn("%s: fcntl(fd, F_GETFL)", testname);
+ return(-1);
+ }
+
+ flags &= ~O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) < 0) {
+ warn("%s: fcntl(fd, 0x%x)", testname, flags);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Drain a file descriptor (fifo) of any readable data. Note: resets the
+ * blocking state.
+ */
+static int
+drain_fd(int fd, const char *testname)
+{
+ ssize_t len;
+ u_char ch;
+
+ if (set_nonblocking(fd, testname) < 0)
+ return (-1);
+
+ while ((len = read(fd, &ch, sizeof(ch))) > 0);
+ if (len < 0) {
+ switch (errno) {
+ case EAGAIN:
+ return (0);
+ default:
+ warn("%s: drain_fd: read", testname);
+ return (-1);
+ }
+ }
+ warn("%s: drain_fd: read: returned 0 bytes", testname);
+ return (-1);
+}
+
+/*
+ * Simple I/O test: write ten integers, and make sure we get back the same
+ * integers in the same order. This assumes a minimum fifo buffer > 10
+ * bytes in order to not block and deadlock.
+ */
+static void
+test_simpleio(void)
+{
+ int i, reader_fd, writer_fd;
+ u_char buffer[10];
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd)
+ < 0) {
+ warn("test_simpleio: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ for (i = 0; i < 10; i++)
+ buffer[i] = i;
+
+ len = write(writer_fd, (char *)buffer, sizeof(buffer));
+ if (len < 0) {
+ warn("test_simpleio: write");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(buffer)) {
+ warnx("test_simplio: tried %zu but wrote %zd", sizeof(buffer),
+ len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, (char *)buffer, sizeof(buffer));
+ if (len < 0) {
+ warn("test_simpleio: read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(buffer)) {
+ warnx("test_simpleio: tried %zu but read %zd", sizeof(buffer),
+ len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ for (i = 0; i < 10; i++) {
+ if (buffer[i] == i)
+ continue;
+ warnx("test_simpleio: write byte %d as 0x%02x, but read "
+ "0x%02x", i, i, buffer[i]);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+static volatile int alarm_fired;
+/*
+ * Non-destructive SIGALRM handler.
+ */
+static void
+sigalarm(int signum __unused)
+{
+
+ alarm_fired = 1;
+}
+
+/*
+ * Wrapper function for write, which uses a timer to interrupt any blocking.
+ * Because we can't reliably detect EINTR for blocking I/O, we also track
+ * whether or not our timeout fired.
+ */
+static int __unused
+timed_write(int fd, void *data, size_t len, ssize_t *written_lenp,
+ int timeout, int *timedoutp, const char *testname)
+{
+ struct sigaction act, oact;
+ ssize_t written_len;
+ int error;
+
+ alarm_fired = 0;
+ bzero(&act, sizeof(oact));
+ act.sa_handler = sigalarm;
+ if (sigaction(SIGALRM, &act, &oact) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ alarm(timeout);
+ written_len = write(fd, data, len);
+ error = errno;
+ alarm(0);
+ if (sigaction(SIGALRM, &oact, NULL) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ if (alarm_fired)
+ *timedoutp = 1;
+ else
+ *timedoutp = 0;
+
+ errno = error;
+ if (written_len < 0)
+ return (-1);
+ *written_lenp = written_len;
+ return (0);
+}
+
+/*
+ * Wrapper function for read, which uses a timer to interrupt any blocking.
+ * Because we can't reliably detect EINTR for blocking I/O, we also track
+ * whether or not our timeout fired.
+ */
+static int
+timed_read(int fd, void *data, size_t len, ssize_t *read_lenp,
+ int timeout, int *timedoutp, const char *testname)
+{
+ struct sigaction act, oact;
+ ssize_t read_len;
+ int error;
+
+ alarm_fired = 0;
+ bzero(&act, sizeof(oact));
+ act.sa_handler = sigalarm;
+ if (sigaction(SIGALRM, &act, &oact) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ alarm(timeout);
+ read_len = read(fd, data, len);
+ error = errno;
+ alarm(0);
+ if (sigaction(SIGALRM, &oact, NULL) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ if (alarm_fired)
+ *timedoutp = 1;
+ else
+ *timedoutp = 0;
+
+ errno = error;
+ if (read_len < 0)
+ return (-1);
+ *read_lenp = read_len;
+ return (0);
+}
+
+/*
+ * This test operates on blocking and non-blocking fifo file descriptors, in
+ * order to determine whether they block at good moments or not. By good we
+ * mean: don't block for non-blocking sockets, and do block for blocking
+ * ones, assuming there isn't I/O buffer to satisfy the request.
+ *
+ * We use a timeout of 5 seconds, concluding that in 5 seconds either all I/O
+ * that can take place will, and that if we reach the end of the timeout,
+ * then blocking has occurred.
+ *
+ * We assume that the buffer size on a fifo is <512K, and as such, that
+ * writing that much data without an active reader will result in blocking.
+ */
+static void
+test_blocking_read_empty(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd)
+ < 0) {
+ warn("test_blocking_read_empty: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /*
+ * Read one byte from an empty blocking fifo, block as there is no
+ * data.
+ */
+ if (set_blocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret != -1) {
+ warnx("test_blocking_read_empty: timed_read: returned "
+ "success");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (errno != EINTR) {
+ warn("test_blocking_read_empty: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ /*
+ * Read one byte from an empty non-blocking fifo, return EAGAIN as
+ * there is no data.
+ */
+ if (set_nonblocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret != -1) {
+ warnx("test_blocking_read_empty: timed_read: returned "
+ "success");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (errno != EAGAIN) {
+ warn("test_blocking_read_empty: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * Write one byte to an empty fifo, then try to read one byte and make sure
+ * we don't block in either the write or the read. This tests both for
+ * improper blocking in the send and receive code.
+ */
+static void
+test_blocking_one_byte(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_blocking: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_blocking(writer_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (set_blocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xfe;
+ ret = timed_write(writer_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_one_byte: timed_write");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_blocking_one_byte: timed_write: tried to write "
+ "%zu, wrote %zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xab;
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_one_byte: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_blocking_one_byte: timed_read: wanted %zu, "
+ "read %zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (ch != 0xfe) {
+ warnx("test_blocking_one_byte: timed_read: expected to read "
+ "0x%02x, read 0x%02x", 0xfe, ch);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * Write one byte to an empty fifo, then try to read one byte and make sure
+ * we don't get back EAGAIN.
+ */
+static void
+test_nonblocking_one_byte(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_nonblocking: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_nonblocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xfe;
+ ret = timed_write(writer_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_nonblocking_one_byte: timed_write");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_nonblocking_one_byte: timed_write: tried to write "
+ "%zu, wrote %zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xab;
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_nonblocking_one_byte: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_nonblocking_one_byte: timed_read: wanted %zu, read "
+ "%zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (ch != 0xfe) {
+ warnx("test_nonblocking_one_byte: timed_read: expected to read "
+ "0x%02x, read 0x%02x", 0xfe, ch);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * First of two test cases involving a 512K buffer: write the buffer into a
+ * blocking file descriptor. We'd like to know it blocks, but the closest we
+ * can get is to see if SIGALRM fired during the I/O resulting in a partial
+ * write.
+ */
+static void
+test_blocking_partial_write(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ u_char *buffer;
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_blocking_partial_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_blocking(writer_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ buffer = malloc(512*1024);
+ if (buffer == NULL) {
+ warn("test_blocking_partial_write: malloc");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ bzero(buffer, 512*1024);
+
+ ret = timed_write(writer_fd, buffer, 512*1024, &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_partial_write: timed_write");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (!timedout) {
+ warnx("test_blocking_partial_write: timed_write: blocking "
+ "socket didn't time out");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ free(buffer);
+
+ if (drain_fd(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * Write a 512K buffer to an empty fifo using a non-blocking file descriptor,
+ * and make sure it doesn't block.
+ */
+static void
+test_nonblocking_partial_write(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ u_char *buffer;
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_blocking_partial_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_nonblocking(writer_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ buffer = malloc(512*1024);
+ if (buffer == NULL) {
+ warn("test_blocking_partial_write: malloc");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ bzero(buffer, 512*1024);
+
+ ret = timed_write(writer_fd, buffer, 512*1024, &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_partial_write: timed_write");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (timedout) {
+ warnx("test_blocking_partial_write: timed_write: "
+ "non-blocking socket timed out");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (len == 0 || len >= 512*1024) {
+ warnx("test_blocking_partial_write: timed_write: requested "
+ "%d, sent %zd", 512*1024, len);
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ free(buffer);
+
+ if (drain_fd(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * test_coalesce_big_read() verifies that data mingles in the fifo across
+ * message boundaries by performing two small writes, then a bigger read
+ * that should return data from both writes.
+ */
+static void
+test_coalesce_big_read(void)
+{
+ int i, reader_fd, writer_fd;
+ u_char buffer[10];
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_coalesce_big_read: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /* Write five, write five, read ten. */
+ for (i = 0; i < 10; i++)
+ buffer[i] = i;
+
+ len = write(writer_fd, buffer, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_read: write 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_read: write 5 wrote %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = write(writer_fd, buffer + 5, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_read: write 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_read: write 5 wrote %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, buffer, 10);
+ if (len < 0) {
+ warn("test_coalesce_big_read: read 10");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 10) {
+ warnx("test_coalesce_big_read: read 10 read %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ for (i = 0; i < 10; i++) {
+ if (buffer[i] == i)
+ continue;
+ warnx("test_coalesce_big_read: expected to read 0x%02x, "
+ "read 0x%02x", i, buffer[i]);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", -1, -1);
+}
+
+/*
+ * test_coalesce_big_write() verifies that data mingles in the fifo across
+ * message boundaries by performing one big write, then two smaller reads
+ * that should return sequential elements of data from the write.
+ */
+static void
+test_coalesce_big_write(void)
+{
+ int i, reader_fd, writer_fd;
+ u_char buffer[10];
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_coalesce_big_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /* Write ten, read five, read five. */
+ for (i = 0; i < 10; i++)
+ buffer[i] = i;
+
+ len = write(writer_fd, buffer, 10);
+ if (len < 0) {
+ warn("test_coalesce_big_write: write 10");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 10) {
+ warnx("test_coalesce_big_write: write 10 wrote %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, buffer, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_write: read 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_write: read 5 read %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, buffer + 5, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_write: read 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_write: read 5 read %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ for (i = 0; i < 10; i++) {
+ if (buffer[i] == i)
+ continue;
+ warnx("test_coalesce_big_write: expected to read 0x%02x, "
+ "read 0x%02x", i, buffer[i]);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", -1, -1);
+}
+
+static int
+poll_status(int fd, int *readable, int *writable, int *exception,
+ const char *testname)
+{
+ struct pollfd fds[1];
+
+ fds[0].fd = fd;
+ fds[0].events = POLLIN | POLLOUT | POLLERR;
+ fds[0].revents = 0;
+
+ if (poll(fds, 1, 0) < 0) {
+ warn("%s: poll", testname);
+ return (-1);
+ }
+ *readable = (fds[0].revents & POLLIN) ? 1 : 0;
+ *writable = (fds[0].revents & POLLOUT) ? 1 : 0;
+ *exception = (fds[0].revents & POLLERR) ? 1 : 0;
+ return (0);
+}
+
+static int
+select_status(int fd, int *readable, int *writable, int *exception,
+ const char *testname)
+{
+ struct fd_set readfds, writefds, exceptfds;
+ struct timeval timeout;
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+ FD_SET(fd, &readfds);
+ FD_SET(fd, &writefds);
+ FD_SET(fd, &exceptfds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if (select(fd+1, &readfds, &writefds, &exceptfds, &timeout) < 0) {
+ warn("%s: select", testname);
+ return (-1);
+ }
+ *readable = FD_ISSET(fd, &readfds) ? 1 : 0;
+ *writable = FD_ISSET(fd, &writefds) ? 1 : 0;
+ *exception = FD_ISSET(fd, &exceptfds) ? 1 : 0;
+ return (0);
+}
+
+/*
+ * Given an existing kqueue, set up read and write event filters for the
+ * passed file descriptor. Typically called once for the read endpoint, and
+ * once for the write endpoint.
+ */
+static int
+kqueue_setup(int kqueue_fd, int fd, const char *testname)
+{
+ struct kevent kevent_changelist[2];
+ struct kevent kevent_eventlist[KQUEUE_MAX_EVENT], *kp;
+ struct timespec timeout;
+ int i, ret;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+
+ bzero(&kevent_changelist, sizeof(kevent_changelist));
+ EV_SET(&kevent_changelist[0], fd, EVFILT_READ, EV_ADD, 0, 0, 0);
+ EV_SET(&kevent_changelist[1], fd, EVFILT_WRITE, EV_ADD, 0, 0, 0);
+
+ bzero(&kevent_eventlist, sizeof(kevent_eventlist));
+ ret = kevent(kqueue_fd, kevent_changelist, 2, kevent_eventlist,
+ KQUEUE_MAX_EVENT, &timeout);
+ if (ret < 0) {
+ warn("%s:%s: kevent initial register", testname, __func__);
+ return (-1);
+ }
+
+ /*
+ * Verify that the events registered alright.
+ */
+ for (i = 0; i < ret; i++) {
+ kp = &kevent_eventlist[i];
+ if (kp->flags != EV_ERROR)
+ continue;
+ errno = kp->data;
+ warn("%s:%s: kevent register index %d", testname, __func__,
+ i);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+kqueue_status(int kqueue_fd, int fd, int *readable, int *writable,
+ int *exception, const char *testname)
+{
+ struct kevent kevent_eventlist[KQUEUE_MAX_EVENT], *kp;
+ struct timespec timeout;
+ int i, ret;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+
+ ret = kevent(kqueue_fd, NULL, 0, kevent_eventlist, KQUEUE_MAX_EVENT,
+ &timeout);
+ if (ret < 0) {
+ warn("%s: %s: kevent", testname, __func__);
+ return (-1);
+ }
+
+ *readable = *writable = *exception = 0;
+ for (i = 0; i < ret; i++) {
+ kp = &kevent_eventlist[i];
+ if (kp->ident != (u_int)fd)
+ continue;
+ if (kp->filter == EVFILT_READ)
+ *readable = 1;
+ if (kp->filter == EVFILT_WRITE)
+ *writable = 1;
+ }
+
+ return (0);
+}
+
+static int
+fionread_status(int fd, int *readable, const char *testname)
+{
+ int i;
+
+ if (ioctl(fd, FIONREAD, &i) < 0) {
+ warn("%s: ioctl(FIONREAD)", testname);
+ return (-1);
+ }
+
+ if (i > 0)
+ *readable = 1;
+ else
+ *readable = 0;
+ return (0);
+}
+
+#define READABLE 1
+#define WRITABLE 1
+#define EXCEPTION 1
+
+#define NOT_READABLE 0
+#define NOT_WRITABLE 0
+#define NOT_EXCEPTION 0
+
+static int
+assert_status(int fd, int kqueue_fd, int assert_readable,
+ int assert_writable, int assert_exception, const char *testname,
+ const char *conditionname, const char *fdname)
+{
+ int readable, writable, exception;
+
+ if (poll_status(fd, &readable, &writable, &exception, testname) < 0)
+ return (-1);
+
+ if (readable != assert_readable || writable != assert_writable ||
+ exception != assert_exception) {
+ warnx("%s: %s polls r:%d, w:%d, e:%d on %s", testname,
+ fdname, readable, writable, exception, conditionname);
+ return (-1);
+ }
+
+ if (select_status(fd, &readable, &writable, &exception, testname) < 0)
+ return (-1);
+
+ if (readable != assert_readable || writable != assert_writable ||
+ exception != assert_exception) {
+ warnx("%s: %s selects r:%d, w:%d, e:%d on %s", testname,
+ fdname, readable, writable, exception, conditionname);
+ return (-1);
+ }
+
+ if (kqueue_status(kqueue_fd, fd, &readable, &writable, &exception,
+ testname) < 0)
+ return (-1);
+
+ if (readable != assert_readable || writable != assert_writable ||
+ exception != assert_exception) {
+ warnx("%s: %s kevent r:%d, w:%d, e:%d on %s", testname,
+ fdname, readable, writable, exception, conditionname);
+ return (-1);
+ }
+
+ if (fionread_status(fd, &readable, __func__) < 0)
+ return (-1);
+
+ if (readable != assert_readable) {
+ warnx("%s: %s fionread r:%d on %s", testname, fdname,
+ readable, conditionname);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * test_events() uses poll(), select(), and kevent() to query the status of
+ * fifo file descriptors and determine whether they match expected state
+ * based on earlier semantic tests: specifically, whether or not poll/select/
+ * kevent will correctly inform on readable/writable state following I/O.
+ *
+ * It would be nice to also test status changes as a result of closing of one
+ * or another fifo endpoint.
+ */
+static void
+test_events_outofbox(void)
+{
+ int kqueue_fd, reader_fd, writer_fd;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_events_outofbox: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Make sure that fresh, out-of-the-box fifo file descriptors have
+ * good initial states. The reader_fd should have no active state,
+ * since it will not be readable (no data in pipe), writable (it's
+ * a read-only descriptor), and there's no reason for error yet.
+ */
+ if (assert_status(reader_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "create", "reader_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Make sure that fresh, out-of-the-box fifo file descriptors have
+ * good initial states. The writer_fd should be ready to write.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "create", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+}
+
+static void
+test_events_write_read_byte(void)
+{
+ int kqueue_fd, reader_fd, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_events_write_read_byte: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Write a byte to the fifo, and make sure that the read end becomes
+ * readable, and that the write end remains writable (small write).
+ */
+ ch = 0x00;
+ len = write(writer_fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: write", __func__);
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(reader_fd, kqueue_fd, READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "write", "reader_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * the writer_fd should remain writable.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "write", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Read the byte from the reader_fd, and now confirm that that fifo
+ * becomes unreadable.
+ */
+ len = read(reader_fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: read", __func__);
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(reader_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "write+read", "reader_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * The writer_fd should remain writable.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "write+read", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+}
+
+/*
+ * Write a 512k buffer to the fifo in non-blocking mode, and make sure that
+ * the write end becomes un-writable as a result of a partial write that
+ * fills the fifo buffer.
+ */
+static void
+test_events_partial_write(void)
+{
+ int kqueue_fd, reader_fd, writer_fd;
+ u_char *buffer;
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_events_partial_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (set_nonblocking(writer_fd, "test_events") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ buffer = malloc(512*1024);
+ if (buffer == NULL) {
+ warn("test_events_partial_write: malloc");
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+ bzero(buffer, 512*1024);
+
+ len = write(writer_fd, buffer, 512*1024);
+ if (len < 0) {
+ warn("test_events_partial_write: write");
+ free(buffer);
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ free(buffer);
+
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "big write", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (drain_fd(reader_fd, "test_events") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Test that the writer_fd has been restored to writable state after
+ * draining.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "big write + drain", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+}
+
+/*
+ * We don't comprehensively test O_RDWR file descriptors, but do run a couple
+ * of event tests to make sure that the fifo implementation doesn't mixed up
+ * status checks. In particular, at least one past FreeBSD bug exists in
+ * which the FIONREAD test was performed on the wrong socket implementing the
+ * fifo, resulting in the fifo never returning readable.
+ */
+static void
+test_events_rdwr(void)
+{
+ int fd, kqueue_fd;
+ ssize_t len;
+ char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo_rw("testfifo", &fd) < 0) {
+ warn("%s: openfifo_rw: testfifo", __func__);
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testifo", fd, -1);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, fd, __func__) < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * On first creation, the O_RDWR descriptor should be writable but
+ * not readable.
+ */
+ if (assert_status(fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "create", "fd") < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Write a byte, which should cause the file descriptor to become
+ * readable and writable.
+ */
+ ch = 0x00;
+ len = write(fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: write", __func__);
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(fd, kqueue_fd, READABLE, WRITABLE, NOT_EXCEPTION,
+ __func__, "write", "fd") < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Read a byte, which should cause the file descriptor to return to
+ * simply being writable.
+ */
+ len = read(fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: read", __func__);
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "write+read", "fd") < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", fd, kqueue_fd);
+}
+
+int
+main(void)
+{
+
+ strcpy(temp_dir, "fifo_io.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ atexit(atexit_temp_dir);
+
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir %s", temp_dir);
+
+ test_simpleio();
+ test_blocking_read_empty();
+ test_blocking_one_byte();
+ test_nonblocking_one_byte();
+ test_blocking_partial_write();
+ test_nonblocking_partial_write();
+ test_coalesce_big_read();
+ test_coalesce_big_write();
+ test_events_outofbox();
+ test_events_write_read_byte();
+ test_events_partial_write();
+ test_events_rdwr();
+
+ return (0);
+}
diff --git a/tests/sys/fifo/fifo_misc.c b/tests/sys/fifo/fifo_misc.c
new file mode 100644
index 0000000..888547e
--- /dev/null
+++ b/tests/sys/fifo/fifo_misc.c
@@ -0,0 +1,335 @@
+/*-
+ * Copyright (c) 2005 Robert N. M. Watson
+ * Copyright (c) 2012 Jilles Tjoelker
+ * All rights reserved.
+ *
+ * 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$
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/filio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Regression test for piddling details of fifos.
+ */
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+static void
+makefifo(const char *fifoname, const char *testname)
+{
+
+ if (mkfifo(fifoname, 0700) < 0)
+ err(-1, "%s: makefifo: mkfifo: %s", testname, fifoname);
+}
+
+static void
+cleanfifo(const char *fifoname, int fd1, int fd2)
+{
+
+ if (fd1 != -1)
+ close(fd1);
+ if (fd2 != -1)
+ close(fd2);
+ (void)unlink(fifoname);
+}
+
+static int
+openfifo(const char *fifoname, int *reader_fdp, int *writer_fdp)
+{
+ int error, fd1, fd2;
+
+ fd1 = open(fifoname, O_RDONLY | O_NONBLOCK);
+ if (fd1 < 0)
+ return (-1);
+ fd2 = open(fifoname, O_WRONLY | O_NONBLOCK);
+ if (fd2 < 0) {
+ error = errno;
+ close(fd1);
+ errno = error;
+ return (-1);
+ }
+ *reader_fdp = fd1;
+ *writer_fdp = fd2;
+
+ return (0);
+}
+
+/*
+ * POSIX does not allow lseek(2) on fifos, so we expect ESPIPE as a result.
+ */
+static void
+test_lseek(void)
+{
+ int reader_fd, writer_fd;
+
+ makefifo("testfifo", __func__);
+
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("%s: openfifo", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (lseek(reader_fd, 1, SEEK_CUR) >= 0) {
+ warnx("%s: lseek succeeded instead of returning ESPIPE",
+ __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (errno != ESPIPE) {
+ warn("%s: lseek returned instead of ESPIPE", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * truncate(2) on FIFO should silently return success.
+ */
+static void
+test_truncate(void)
+{
+
+ makefifo("testfifo", __func__);
+
+ if (truncate("testfifo", 1024) != 0) {
+ warn("%s: truncate", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", -1, -1);
+}
+
+static int
+test_ioctl_setclearflag(int fd, int flag, const char *testname,
+ const char *fdname, const char *flagname)
+{
+ int i;
+
+ i = 1;
+ if (ioctl(fd, flag, &i) < 0) {
+ warn("%s:%s: ioctl(%s, %s, 1)", testname, __func__, fdname,
+ flagname);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ i = 0;
+ if (ioctl(fd, flag, &i) < 0) {
+ warn("%s:%s: ioctl(%s, %s, 0)", testname, __func__, fdname,
+ flagname);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Test that various ioctls can be issued against the file descriptor. We
+ * don't currently test the semantics of these changes here.
+ */
+static void
+test_ioctl(void)
+{
+ int reader_fd, writer_fd;
+
+ makefifo("testfifo", __func__);
+
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("%s: openfifo", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /*
+ * Set and remove the non-blocking I/O flag.
+ */
+ if (test_ioctl_setclearflag(reader_fd, FIONBIO, __func__,
+ "reader_fd", "FIONBIO") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (test_ioctl_setclearflag(writer_fd, FIONBIO, __func__,
+ "writer_fd", "FIONBIO") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ /*
+ * Set and remove the async I/O flag.
+ */
+ if (test_ioctl_setclearflag(reader_fd, FIOASYNC, __func__,
+ "reader_fd", "FIOASYNC") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (test_ioctl_setclearflag(writer_fd, FIOASYNC, __func__,
+ "writer_fd", "FIONASYNC") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * fchmod(2)/fchown(2) on FIFO should work.
+ */
+static void
+test_chmodchown(void)
+{
+ struct stat sb;
+ int reader_fd, writer_fd;
+ uid_t u;
+ gid_t g;
+
+ makefifo("testfifo", __func__);
+
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("%s: openfifo", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (fchmod(reader_fd, 0666) != 0) {
+ warn("%s: fchmod", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (stat("testfifo", &sb) != 0) {
+ warn("%s: stat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if ((sb.st_mode & 0777) != 0666) {
+ warnx("%s: stat chmod result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (fstat(writer_fd, &sb) != 0) {
+ warn("%s: fstat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if ((sb.st_mode & 0777) != 0666) {
+ warnx("%s: fstat chmod result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (fchown(reader_fd, -1, -1) != 0) {
+ warn("%s: fchown 1", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ u = geteuid();
+ if (u == 0)
+ u = 1;
+ g = getegid();
+ if (fchown(reader_fd, u, g) != 0) {
+ warn("%s: fchown 2", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (stat("testfifo", &sb) != 0) {
+ warn("%s: stat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (sb.st_uid != u || sb.st_gid != g) {
+ warnx("%s: stat chown result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (fstat(writer_fd, &sb) != 0) {
+ warn("%s: fstat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (sb.st_uid != u || sb.st_gid != g) {
+ warnx("%s: fstat chown result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", -1, -1);
+}
+
+int
+main(void)
+{
+
+ strcpy(temp_dir, "fifo_misc.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ atexit(atexit_temp_dir);
+
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir %s", temp_dir);
+
+ test_lseek();
+ test_truncate();
+ test_ioctl();
+ test_chmodchown();
+
+ return (0);
+}
diff --git a/tests/sys/fifo/fifo_open.c b/tests/sys/fifo/fifo_open.c
new file mode 100644
index 0000000..892f481
--- /dev/null
+++ b/tests/sys/fifo/fifo_open.c
@@ -0,0 +1,476 @@
+/*-
+ * Copyright (c) 2005 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * 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$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Regression test to exercise various POSIX-defined parts of fifo behavior
+ * described for open(2):
+ *
+ * O_NONBLOCK
+ * When opening a FIFO with O_RDONLY or O_WRONLY set:
+ *
+ * - If O_NONBLOCK is set, an open() for reading-only shall return without
+ * delay. An open() for writing-only shall return an error if no process
+ * currently has the file open for reading.
+ *
+ * - If O_NONBLOCK is clear, an open() for reading-only shall block the
+ * calling thread until a thread opens the file for writing. An open()
+ * for writing-only shall block the calling thread until a thread opens
+ * the file for reading.
+ *
+ * When opening a block special or character special file that supports
+ * non-blocking opens:
+ *
+ * - If O_NONBLOCK is set, the open() function shall return without blocking
+ * for the device to be ready or available. Subsequent behavior of the
+ * device is device-specific.
+ *
+ * - If O_NONBLOCK is clear, the open() function shall block the calling
+ * thread until the device is ready or available before returning.
+ *
+ * Special errors:
+ *
+ * [ENXIO]
+ * O_NONBLOCK is set, the named file is a FIFO, O_WRONLY is set, and no
+ * process has the file open for reading.
+ */
+
+/*
+ * In order to test blocking/non-blocking behavior, test processes must
+ * potentially block themselves until released. As bugs in blocking result
+ * in processes that won't un-block, we must sacrifice a process to the task,
+ * watching and potentially killing it after a time-out. The main test
+ * process is never used to open or act directly on a fifo (other than to
+ * create or unlink it) in order to avoid the main test process being
+ * blocked.
+ */
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+/*
+ * Run a function in a particular test process.
+ */
+static int
+run_in_process(int (*func)(void), pid_t *pidp, const char *errstr)
+{
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0) {
+ warn("%s: run_in_process: fork", errstr);
+ return (-1);
+ }
+
+ if (pid == 0)
+ exit(func());
+
+ if (pidp != NULL)
+ *pidp = pid;
+
+ return (0);
+}
+
+/*
+ * Wait for a process on a timeout, and if the timeout expires, kill the
+ * process. Test each second rather than waiting the full timeout at once to
+ * minimize the amount of time spent hanging around unnecessarily.
+ */
+static int
+wait_and_timeout(pid_t pid, int timeout, int *status, const char *errstr)
+{
+ pid_t wpid;
+ int i;
+
+ /*
+ * Count up to the timeout, but do a non-hanging waitpid() after each
+ * second so we can avoid waiting a lot of extra time.
+ */
+ for (i = 0; i < timeout; i++) {
+ wpid = waitpid(pid, status, WNOHANG);
+ if (wpid < 0) {
+ warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
+ return (-1);
+ }
+
+ if (wpid == pid)
+ return (0);
+
+ sleep(1);
+ }
+
+ wpid = waitpid(pid, status, WNOHANG);
+ if (wpid < 0) {
+ warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
+ return (-1);
+ }
+
+ if (wpid == pid)
+ return (0);
+
+ if (kill(pid, SIGTERM) < 0) {
+ warn("%s: wait_and_timeout: kill %d", errstr, pid);
+ return (-1);
+ }
+
+ wpid = waitpid(pid, status, 0);
+ if (wpid < 0) {
+ warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
+ return (-1);
+ }
+
+ if (wpid != pid) {
+ warn("%s: waitpid: returned %d not %d", errstr, wpid, pid);
+ return (-1);
+ }
+
+ warnx("%s: process blocked", errstr);
+ return (-1);
+}
+
+static int
+non_blocking_open_reader(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_RDONLY | O_NONBLOCK);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static int
+non_blocking_open_writer(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_WRONLY | O_NONBLOCK);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static int
+blocking_open_reader(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_RDONLY);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static int
+blocking_open_writer(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_WRONLY);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static void
+test_blocking_reader(void)
+{
+ pid_t reader_pid, writer_pid, wpid;
+ int error, status;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_blocking_reader: mkfifo: testfifo");
+
+ /*
+ * Block a process in opening the fifo.
+ */
+ if (run_in_process(blocking_open_reader, &reader_pid,
+ "test_blocking_reader: blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ /*
+ * Test that it blocked.
+ */
+ sleep(5);
+ wpid = waitpid(reader_pid, &status, WNOHANG);
+ if (wpid < 0) {
+ error = errno;
+ (void)unlink("testfifo");
+ errno = error;
+ err(-1, "test_blocking_reader: waitpid %d", reader_pid);
+ }
+
+ if (wpid != 0 && wpid != reader_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_reader: waitpid %d returned %d",
+ reader_pid, wpid);
+ }
+
+ if (wpid == reader_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_reader: blocking child didn't "
+ "block");
+ }
+
+ /*
+ * Unblock the blocking reader.
+ */
+ if (run_in_process(blocking_open_writer, &writer_pid,
+ "test_blocking_reader: blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ (void)kill(reader_pid, SIGTERM);
+ (void)waitpid(reader_pid, &status, 0);
+ exit(-1);
+ }
+
+ /*
+ * Make sure both processes exited quickly (<1 second) to make sure
+ * they didn't block, and GC.
+ */
+ if (wait_and_timeout(reader_pid, 1, &status,
+ "test_blocking_reader: blocking_open_reader") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(reader_pid, SIGTERM);
+ (void)kill(writer_pid, SIGTERM);
+ exit(-1);
+ }
+
+ if (wait_and_timeout(writer_pid, 1, &status,
+ "test_blocking_reader: blocking_open_writer") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(writer_pid, SIGTERM);
+ exit(-1);
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_blocking_reader: unlink: testfifo");
+}
+static void
+test_blocking_writer(void)
+{
+ pid_t reader_pid, writer_pid, wpid;
+ int error, status;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_blocking_writer: mkfifo: testfifo");
+
+ /*
+ * Block a process in opening the fifo.
+ */
+ if (run_in_process(blocking_open_writer, &writer_pid,
+ "test_blocking_writer: blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ /*
+ * Test that it blocked.
+ */
+ sleep(5);
+ wpid = waitpid(writer_pid, &status, WNOHANG);
+ if (wpid < 0) {
+ error = errno;
+ (void)unlink("testfifo");
+ errno = error;
+ err(-1, "test_blocking_writer: waitpid %d", writer_pid);
+ }
+
+ if (wpid != 0 && wpid != writer_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_writer: waitpid %d returned %d",
+ writer_pid, wpid);
+ }
+
+ if (wpid == writer_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_writer: blocking child didn't "
+ "block");
+ }
+
+ /*
+ * Unblock the blocking writer.
+ */
+ if (run_in_process(blocking_open_reader, &reader_pid,
+ "test_blocking_writer: blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ (void)kill(writer_pid, SIGTERM);
+ (void)waitpid(writer_pid, &status, 0);
+ exit(-1);
+ }
+
+ /*
+ * Make sure both processes exited quickly (<1 second) to make sure
+ * they didn't block, and GC.
+ */
+ if (wait_and_timeout(writer_pid, 1, &status,
+ "test_blocking_writer: blocking_open_writer") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(writer_pid, SIGTERM);
+ (void)kill(reader_pid, SIGTERM);
+ (void)waitpid(writer_pid, &status, 0);
+ (void)waitpid(reader_pid, &status, 0);
+ exit(-1);
+ }
+
+ if (wait_and_timeout(reader_pid, 1, &status,
+ "test_blocking_writer: blocking_open_reader") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(reader_pid, SIGTERM);
+ (void)waitpid(reader_pid, &status, 0);
+ exit(-1);
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_blocking_writer: unlink: testfifo");
+}
+
+static void
+test_non_blocking_reader(void)
+{
+ int status;
+ pid_t pid;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_non_blocking_reader: mkfifo: testfifo");
+
+ if (run_in_process(non_blocking_open_reader, &pid,
+ "test_non_blocking_reader: non_blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ status = -1;
+ if (wait_and_timeout(pid, 5, &status,
+ "test_non_blocking_reader: non_blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ if (WEXITSTATUS(status) != 0) {
+ (void)unlink("testfifo");
+ errno = WEXITSTATUS(status);
+ err(-1, "test_non_blocking_reader: "
+ "non_blocking_open_reader: open: testfifo");
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_non_blocking_reader: unlink: testfifo");
+}
+
+static void
+test_non_blocking_writer(void)
+{
+ int status;
+ pid_t pid;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_non_blocking_writer: mkfifo: testfifo");
+
+ if (run_in_process(non_blocking_open_writer, &pid,
+ "test_non_blocking_writer: non_blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ status = -1;
+ if (wait_and_timeout(pid, 5, &status,
+ "test_non_blocking_writer: non_blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ if (WEXITSTATUS(status) != ENXIO) {
+ (void)unlink("testfifo");
+
+ errno = WEXITSTATUS(status);
+ if (errno == 0)
+ errx(-1, "test_non_blocking_writer: "
+ "non_blocking_open_writer: open succeeded");
+ err(-1, "test_non_blocking_writer: "
+ "non_blocking_open_writer: open: testfifo");
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_non_blocking_writer: unlink: testfifo");
+}
+
+int
+main(void)
+{
+
+ if (geteuid() != 0)
+ errx(-1, "must be run as root");
+
+ strcpy(temp_dir, "fifo_open.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir: %s", temp_dir);
+ atexit(atexit_temp_dir);
+
+ test_non_blocking_reader();
+ test_non_blocking_writer();
+
+ test_blocking_reader();
+ test_blocking_writer();
+
+ return (0);
+}
diff --git a/tests/sys/file/Makefile b/tests/sys/file/Makefile
new file mode 100644
index 0000000..6151c9f
--- /dev/null
+++ b/tests/sys/file/Makefile
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/file
+
+BINDIR= ${TESTSDIR}
+
+TAP_TESTS_C+= closefrom_test
+TAP_TESTS_C+= dup_test
+TAP_TESTS_C+= fcntlflags_test
+TAP_TESTS_SH+= flock_test
+PLAIN_TESTS_C+= ftruncate_test
+PLAIN_TESTS_C+= newfileops_on_fork_test
+
+PROGS+= flock_helper
+
+DPADD.closefrom_test= ${LIBUTIL}
+LDADD.closefrom_test= -lutil
+
+DPADD.flock_helper= ${LIBPTHREAD}
+LDADD.flock_helper= -lpthread
+
+DPADD.newfileops_on_fork_test= ${LIBPTHREAD}
+LDADD.newfileops_on_fork_test= -lpthread
+
+.include <bsd.test.mk>
diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c
new file mode 100644
index 0000000..78cfeec
--- /dev/null
+++ b/tests/sys/file/closefrom_test.c
@@ -0,0 +1,275 @@
+/*-
+ * Copyright (c) 2009 Hudson River Trading LLC
+ * Written by: John H. Baldwin <jhb@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Regression tests for the closefrom(2) system call.
+ */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct shared_info {
+ int failed;
+ char tag[64];
+ char message[0];
+};
+
+static int test = 1;
+
+static void
+ok(const char *descr)
+{
+
+ printf("ok %d - %s\n", test, descr);
+ test++;
+}
+
+static void
+fail(const char *descr, const char *fmt, ...)
+{
+ va_list ap;
+
+ printf("not ok %d - %s", test, descr);
+ test++;
+ if (fmt) {
+ va_start(ap, fmt);
+ printf(" # ");
+ vprintf(fmt, ap);
+ va_end(ap);
+ }
+ printf("\n");
+ exit(1);
+}
+
+#define fail_err(descr) fail((descr), "%s", strerror(errno))
+
+static void
+cok(struct shared_info *info, const char *descr)
+{
+
+ info->failed = 0;
+ strlcpy(info->tag, descr, sizeof(info->tag));
+ exit(0);
+}
+
+static void
+cfail(struct shared_info *info, const char *descr, const char *fmt, ...)
+{
+ va_list ap;
+
+ info->failed = 1;
+ strlcpy(info->tag, descr, sizeof(info->tag));
+ if (fmt) {
+ va_start(ap, fmt);
+ vsprintf(info->message, fmt, ap);
+ va_end(ap);
+ }
+ exit(0);
+}
+
+#define cfail_err(info, descr) cfail((info), (descr), "%s", strerror(errno))
+
+/*
+ * Use kinfo_getfile() to fetch the list of file descriptors and figure out
+ * the highest open file descriptor.
+ */
+static int
+highest_fd(void)
+{
+ struct kinfo_file *kif;
+ int cnt, i, highest;
+
+ kif = kinfo_getfile(getpid(), &cnt);
+ if (kif == NULL)
+ fail_err("kinfo_getfile");
+ highest = INT_MIN;
+ for (i = 0; i < cnt; i++)
+ if (kif[i].kf_fd > highest)
+ highest = kif[i].kf_fd;
+ free(kif);
+ return (highest);
+}
+
+static int
+devnull(void)
+{
+ int fd;
+
+ fd = open(_PATH_DEVNULL, O_RDONLY);
+ if (fd < 0)
+ fail_err("open(\" "_PATH_DEVNULL" \")");
+ return (fd);
+}
+
+int
+main(void)
+{
+ struct shared_info *info;
+ pid_t pid;
+ int fd, i, start;
+
+ printf("1..15\n");
+
+ /* We better start up with fd's 0, 1, and 2 open. */
+ start = devnull();
+ if (start == -1)
+ fail("open", "bad descriptor %d", start);
+ ok("open");
+
+ /* Make sure highest_fd() works. */
+ fd = highest_fd();
+ if (start != fd)
+ fail("highest_fd", "bad descriptor %d != %d", start, fd);
+ ok("highest_fd");
+
+ /* Try to use closefrom() for just closing fd 3. */
+ closefrom(start + 1);
+ fd = highest_fd();
+ if (fd != start)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Eat up 16 descriptors. */
+ for (i = 0; i < 16; i++)
+ (void)devnull();
+ fd = highest_fd();
+ if (fd != start + 16)
+ fail("open 16", "highest fd %d", fd);
+ ok("open 16");
+
+ /* Close half of them. */
+ closefrom(11);
+ fd = highest_fd();
+ if (fd != 10)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Explicitly close descriptors 6 and 8 to create holes. */
+ if (close(6) < 0 || close(8) < 0)
+ fail_err("close2 ");
+ ok("close 2");
+
+ /* Verify that close on 6 and 8 fails with EBADF. */
+ if (close(6) == 0)
+ fail("close(6)", "did not fail");
+ if (errno != EBADF)
+ fail_err("close(6)");
+ ok("close(6)");
+ if (close(8) == 0)
+ fail("close(8)", "did not fail");
+ if (errno != EBADF)
+ fail_err("close(8)");
+ ok("close(8)");
+
+ /* Close from 4 on. */
+ closefrom(4);
+ fd = highest_fd();
+ if (fd != 3)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Allocate a small SHM region for IPC with our child. */
+ info = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANON |
+ MAP_SHARED, -1, 0);
+ if (info == MAP_FAILED)
+ fail_err("mmap");
+ ok("mmap");
+
+ /* Fork a child process to test closefrom(0). */
+ pid = fork();
+ if (pid < 0)
+ fail_err("fork");
+ if (pid == 0) {
+ /* Child. */
+ closefrom(0);
+ fd = highest_fd();
+ if (fd >= 0)
+ cfail(info, "closefrom(0)", "highest fd %d", fd);
+ cok(info, "closefrom(0)");
+ }
+ if (wait(NULL) < 0)
+ fail_err("wait");
+ if (info->failed)
+ fail(info->tag, "%s", info->message);
+ ok(info->tag);
+
+ /* Fork a child process to test closefrom(-1). */
+ pid = fork();
+ if (pid < 0)
+ fail_err("fork");
+ if (pid == 0) {
+ /* Child. */
+ closefrom(-1);
+ fd = highest_fd();
+ if (fd >= 0)
+ cfail(info, "closefrom(-1)", "highest fd %d", fd);
+ cok(info, "closefrom(-1)");
+ }
+ if (wait(NULL) < 0)
+ fail_err("wait");
+ if (info->failed)
+ fail(info->tag, "%s", info->message);
+ ok(info->tag);
+
+ /* Dup stdout to 6. */
+ if (dup2(1, 6) < 0)
+ fail_err("dup2");
+ fd = highest_fd();
+ if (fd != 6)
+ fail("dup2", "highest fd %d", fd);
+ ok("dup2");
+
+ /* Do a closefrom() starting in a hole. */
+ closefrom(4);
+ fd = highest_fd();
+ if (fd != 3)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Do a closefrom() beyond our highest open fd. */
+ closefrom(32);
+ fd = highest_fd();
+ if (fd != 3)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ return (0);
+}
diff --git a/tests/sys/file/dup_test.c b/tests/sys/file/dup_test.c
new file mode 100644
index 0000000..8173818
--- /dev/null
+++ b/tests/sys/file/dup_test.c
@@ -0,0 +1,386 @@
+/*
+ * $OpenBSD: dup2test.c,v 1.3 2003/07/31 21:48:08 deraadt Exp $
+ * $OpenBSD: dup2_self.c,v 1.3 2003/07/31 21:48:08 deraadt Exp $
+ * $OpenBSD: fcntl_dup.c,v 1.2 2003/07/31 21:48:08 deraadt Exp $
+ *
+ * Written by Artur Grabowski <art@openbsd.org> 2002 Public Domain.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Test #1: check if dup(2) works.
+ * Test #2: check if dup2(2) works.
+ * Test #3: check if dup2(2) returned a fd we asked for.
+ * Test #4: check if dup2(2) cleared close-on-exec flag for duped fd.
+ * Test #5: check if dup2(2) allows to dup fd to itself.
+ * Test #6: check if dup2(2) returned a fd we asked for.
+ * Test #7: check if dup2(2) did not clear close-on-exec flag for duped fd.
+ * Test #8: check if fcntl(F_DUPFD) works.
+ * Test #9: check if fcntl(F_DUPFD) cleared close-on-exec flag for duped fd.
+ * Test #10: check if dup2() to a fd > current maximum number of open files
+ * limit work.
+ * Test #11: check if fcntl(F_DUP2FD) works.
+ * Test #12: check if fcntl(F_DUP2FD) returned a fd we asked for.
+ * Test #13: check if fcntl(F_DUP2FD) cleared close-on-exec flag for duped fd.
+ * Test #14: check if fcntl(F_DUP2FD) allows to dup fd to itself.
+ * Test #15: check if fcntl(F_DUP2FD) returned a fd we asked for.
+ * Test #16: check if fcntl(F_DUP2FD) did not clear close-on-exec flag for
+ * duped fd.
+ * Test #17: check if fcntl(F_DUP2FD) to a fd > current maximum number of open
+ * files limit work.
+ * Test #18: check if fcntl(F_DUPFD_CLOEXEC) works.
+ * Test #19: check if fcntl(F_DUPFD_CLOEXEC) set close-on-exec flag for duped
+ * fd.
+ * Test #20: check if fcntl(F_DUP2FD_CLOEXEC) works.
+ * Test #21: check if fcntl(F_DUP2FD_CLOEXEC) returned a fd we asked for.
+ * Test #22: check if fcntl(F_DUP2FD_CLOEXEC) set close-on-exec flag for duped
+ * fd.
+ * Test #23: check if fcntl(F_DUP2FD_CLOEXEC) to a fd > current maximum number
+ * of open files limit work.
+ * Test #24: check if dup3(O_CLOEXEC) works.
+ * Test #25: check if dup3(O_CLOEXEC) returned a fd we asked for.
+ * Test #26: check if dup3(O_CLOEXEC) set close-on-exec flag for duped fd.
+ * Test #27: check if dup3(0) works.
+ * Test #28: check if dup3(0) returned a fd we asked for.
+ * Test #29: check if dup3(0) cleared close-on-exec flag for duped fd.
+ * Test #30: check if dup3(O_CLOEXEC) fails if oldfd == newfd.
+ * Test #31: check if dup3(0) fails if oldfd == newfd.
+ * Test #32: check if dup3(O_CLOEXEC) to a fd > current maximum number of
+ * open files limit work.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static int getafile(void);
+
+static int
+getafile(void)
+{
+ int fd;
+
+ char temp[] = "/tmp/dup2XXXXXXXXX";
+ if ((fd = mkstemp(temp)) < 0)
+ err(1, "mkstemp");
+ remove(temp);
+ if (ftruncate(fd, 1024) != 0)
+ err(1, "ftruncate");
+ return (fd);
+}
+
+int
+main(int __unused argc, char __unused *argv[])
+{
+ struct rlimit rlp;
+ int orgfd, fd1, fd2, test = 0;
+
+ orgfd = getafile();
+
+ printf("1..32\n");
+
+ /* If dup(2) ever work? */
+ if ((fd1 = dup(orgfd)) < 0)
+ err(1, "dup");
+ printf("ok %d - dup(2) works\n", ++test);
+
+ /* Set close-on-exec */
+ if (fcntl(fd1, F_SETFD, 1) != 0)
+ err(1, "fcntl(F_SETFD)");
+
+ /* If dup2(2) ever work? */
+ if ((fd2 = dup2(fd1, fd1 + 1)) < 0)
+ err(1, "dup2");
+ printf("ok %d - dup2(2) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf("no ok %d - dup2(2) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup2(2) returned a correct fd\n", test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf("not ok %d - dup2(2) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - dup2(2) cleared close-on-exec\n", test);
+
+ /*
+ * Dup to itself.
+ *
+ * We're testing a small tweak in dup2 semantics.
+ * Normally dup and dup2 will clear the close-on-exec
+ * flag on the new fd (which appears to be an implementation
+ * mistake from start and not some planned behavior).
+ * In today's implementations of dup and dup2 we have to make
+ * an effort to really clear that flag. But all tested
+ * implementations of dup2 have another tweak. If we
+ * dup2(old, new) when old == new, the syscall short-circuits
+ * and returns early (because there is no need to do all the
+ * work (and there is a risk for serious mistakes)).
+ * So although the docs say that dup2 should "take 'old',
+ * close 'new' perform a dup(2) of 'old' into 'new'"
+ * the docs are not really followed because close-on-exec
+ * is not cleared on 'new'.
+ *
+ * Since everyone has this bug, we pretend that this is
+ * the way it is supposed to be and test here that it really
+ * works that way.
+ *
+ * This is a fine example on where two separate implementation
+ * fuckups take out each other and make the end-result the way
+ * it was meant to be.
+ */
+ if ((fd2 = dup2(fd1, fd1)) < 0)
+ err(1, "dup2");
+ printf("ok %d - dup2(2) to itself works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1)
+ printf("not ok %d - dup2(2) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup2(2) to itself returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) == 0)
+ printf("not ok %d - dup2(2) cleared close-on-exec\n", test);
+ else
+ printf("ok %d - dup2(2) didn't clear close-on-exec\n", test);
+
+ /* Does fcntl(F_DUPFD) work? */
+ if ((fd2 = fcntl(fd1, F_DUPFD, 10)) < 0)
+ err(1, "fcntl(F_DUPFD)");
+ if (fd2 < 10)
+ printf("not ok %d - fcntl(F_DUPFD) returned wrong fd %d\n",
+ ++test, fd2);
+ else
+ printf("ok %d - fcntl(F_DUPFD) works\n", ++test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf(
+ "not ok %d - fcntl(F_DUPFD) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUPFD) cleared close-on-exec\n", test);
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = dup2(fd1, rlp.rlim_cur + 1)) >= 0)
+ printf("not ok %d - dup2(2) bypassed NOFILE limit\n", test);
+ else
+ printf("ok %d - dup2(2) didn't bypass NOFILE limit\n", test);
+
+ /* If fcntl(F_DUP2FD) ever work? */
+ if ((fd2 = fcntl(fd1, F_DUP2FD, fd1 + 1)) < 0)
+ err(1, "fcntl(F_DUP2FD)");
+ printf("ok %d - fcntl(F_DUP2FD) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - fcntl(F_DUP2FD) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf(
+ "not ok %d - fcntl(F_DUP2FD) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) cleared close-on-exec\n",
+ test);
+
+ /* Dup to itself */
+ if ((fd2 = fcntl(fd1, F_DUP2FD, fd1)) < 0)
+ err(1, "fcntl(F_DUP2FD)");
+ printf("ok %d - fcntl(F_DUP2FD) to itself works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1)
+ printf(
+ "not ok %d - fcntl(F_DUP2FD) didn't give us the right fd\n",
+ test);
+ else
+ printf(
+ "ok %d - fcntl(F_DUP2FD) to itself returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) == 0)
+ printf("not ok %d - fcntl(F_DUP2FD) cleared close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) didn't clear close-on-exec\n",
+ test);
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = fcntl(fd1, F_DUP2FD, rlp.rlim_cur + 1)) >= 0)
+ printf("not ok %d - fcntl(F_DUP2FD) bypassed NOFILE limit\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) didn't bypass NOFILE limit\n",
+ test);
+
+ /* Does fcntl(F_DUPFD_CLOEXEC) work? */
+ if ((fd2 = fcntl(fd1, F_DUPFD_CLOEXEC, 10)) < 0)
+ err(1, "fcntl(F_DUPFD_CLOEXEC)");
+ if (fd2 < 10)
+ printf("not ok %d - fcntl(F_DUPFD_CLOEXEC) returned wrong fd %d\n",
+ ++test, fd2);
+ else
+ printf("ok %d - fcntl(F_DUPFD_CLOEXEC) works\n", ++test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 1)
+ printf(
+ "not ok %d - fcntl(F_DUPFD_CLOEXEC) didn't set close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUPFD_CLOEXEC) set close-on-exec\n",
+ test);
+
+ /* If fcntl(F_DUP2FD_CLOEXEC) ever work? */
+ if ((fd2 = fcntl(fd1, F_DUP2FD_CLOEXEC, fd1 + 1)) < 0)
+ err(1, "fcntl(F_DUP2FD_CLOEXEC)");
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec set? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != FD_CLOEXEC)
+ printf(
+ "not ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't set close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) set close-on-exec\n",
+ test);
+
+ /*
+ * It is unclear what F_DUP2FD_CLOEXEC should do when duplicating a
+ * file descriptor onto itself.
+ */
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = fcntl(fd1, F_DUP2FD_CLOEXEC, rlp.rlim_cur + 1)) >= 0)
+ printf("not ok %d - fcntl(F_DUP2FD_CLOEXEC) bypassed NOFILE limit\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't bypass NOFILE limit\n",
+ test);
+
+ /* Does dup3(O_CLOEXEC) ever work? */
+ if ((fd2 = dup3(fd1, fd1 + 1, O_CLOEXEC)) < 0)
+ err(1, "dup3(O_CLOEXEC)");
+ printf("ok %d - dup3(O_CLOEXEC) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - dup3(O_CLOEXEC) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup3(O_CLOEXEC) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec set? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != FD_CLOEXEC)
+ printf(
+ "not ok %d - dup3(O_CLOEXEC) didn't set close-on-exec\n",
+ test);
+ else
+ printf("ok %d - dup3(O_CLOEXEC) set close-on-exec\n",
+ test);
+
+ /* Does dup3(0) ever work? */
+ if ((fd2 = dup3(fd1, fd1 + 1, 0)) < 0)
+ err(1, "dup3(0)");
+ printf("ok %d - dup3(0) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - dup3(0) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup3(0) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf(
+ "not ok %d - dup3(0) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - dup3(0) cleared close-on-exec\n",
+ test);
+
+ /* dup3() does not allow duplicating to the same fd */
+ ++test;
+ if (dup3(fd1, fd1, O_CLOEXEC) != -1)
+ printf(
+ "not ok %d - dup3(fd1, fd1, O_CLOEXEC) succeeded\n", test);
+ else
+ printf("ok %d - dup3(fd1, fd1, O_CLOEXEC) failed\n", test);
+
+ ++test;
+ if (dup3(fd1, fd1, 0) != -1)
+ printf(
+ "not ok %d - dup3(fd1, fd1, 0) succeeded\n", test);
+ else
+ printf("ok %d - dup3(fd1, fd1, 0) failed\n", test);
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = dup3(fd1, rlp.rlim_cur + 1, O_CLOEXEC)) >= 0)
+ printf("not ok %d - dup3(O_CLOEXEC) bypassed NOFILE limit\n",
+ test);
+ else
+ printf("ok %d - dup3(O_CLOEXEC) didn't bypass NOFILE limit\n",
+ test);
+
+ return (0);
+}
diff --git a/tests/sys/file/fcntlflags_test.c b/tests/sys/file/fcntlflags_test.c
new file mode 100644
index 0000000..bcb3b54
--- /dev/null
+++ b/tests/sys/file/fcntlflags_test.c
@@ -0,0 +1,110 @@
+/*-
+ * Copyright (c) 2013 Jilles Tjoelker
+ * All rights reserved.
+ *
+ * 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$
+ */
+
+#include <sys/cdefs.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * O_ACCMODE is currently defined incorrectly. This is what it should be.
+ * Various code depends on the incorrect value.
+ */
+#define CORRECT_O_ACCMODE (O_ACCMODE | O_EXEC)
+
+static int testnum;
+
+static void
+subtests(const char *path, int omode, const char *omodetext)
+{
+ int fd, flags1, flags2, flags3;
+
+ fd = open(path, omode);
+ if (fd == -1)
+ printf("not ok %d - open(\"%s\", %s) failed\n",
+ testnum++, path, omodetext);
+ else
+ printf("ok %d - open(\"%s\", %s) succeeded\n",
+ testnum++, path, omodetext);
+ flags1 = fcntl(fd, F_GETFL);
+ if (flags1 == -1)
+ printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++);
+ else if ((flags1 & CORRECT_O_ACCMODE) == omode)
+ printf("ok %d - fcntl(F_GETFL) gave correct result\n",
+ testnum++);
+ else
+ printf("not ok %d - fcntl(F_GETFL) gave incorrect result "
+ "(%#x & %#x != %#x)\n",
+ testnum++, flags1, CORRECT_O_ACCMODE, omode);
+ if (fcntl(fd, F_SETFL, flags1) == -1)
+ printf("not ok %d - fcntl(F_SETFL) same flags failed\n",
+ testnum++);
+ else
+ printf("ok %d - fcntl(F_SETFL) same flags succeeded\n",
+ testnum++);
+ flags2 = fcntl(fd, F_GETFL);
+ if (flags2 == -1)
+ printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++);
+ else if (flags2 == flags1)
+ printf("ok %d - fcntl(F_GETFL) gave same result\n",
+ testnum++);
+ else
+ printf("not ok %d - fcntl(F_SETFL) caused fcntl(F_GETFL) to "
+ "change from %#x to %#x\n",
+ testnum++, flags1, flags2);
+ if (fcntl(fd, F_SETFL, flags2 | O_NONBLOCK) == -1)
+ printf("not ok %d - fcntl(F_SETFL) O_NONBLOCK failed\n",
+ testnum++);
+ else
+ printf("ok %d - fcntl(F_SETFL) O_NONBLOCK succeeded\n",
+ testnum++);
+ flags3 = fcntl(fd, F_GETFL);
+ if (flags3 == -1)
+ printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++);
+ else if (flags3 == (flags2 | O_NONBLOCK))
+ printf("ok %d - fcntl(F_GETFL) gave expected result\n",
+ testnum++);
+ else
+ printf("not ok %d - fcntl(F_SETFL) gave unexpected result "
+ "(%#x != %#x)\n",
+ testnum++, flags3, flags2 | O_NONBLOCK);
+ (void)close(fd);
+}
+
+int
+main(int argc __unused, char **argv __unused)
+{
+ printf("1..24\n");
+ testnum = 1;
+ subtests("/dev/null", O_RDONLY, "O_RDONLY");
+ subtests("/dev/null", O_WRONLY, "O_WRONLY");
+ subtests("/dev/null", O_RDWR, "O_RDWR");
+ subtests("/bin/sh", O_EXEC, "O_EXEC");
+ return (0);
+}
diff --git a/tests/sys/file/flock_helper.c b/tests/sys/file/flock_helper.c
new file mode 100644
index 0000000..49e47b8
--- /dev/null
+++ b/tests/sys/file/flock_helper.c
@@ -0,0 +1,1598 @@
+/*-
+ * 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$
+ */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#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 <pthread.h>
+#include <signal.h>
+#include <stdint.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 nitems
+#define nitems(x) (sizeof((x)) / sizeof((x)[0]))
+#endif
+
+#ifndef __unused
+#ifdef __GNUC__
+#define __unused __attribute__((__unused__))
+#else
+#define __unused
+#endif
+#endif
+#endif
+
+static 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);
+ strcpy(filename, pathname);
+ strcat(filename, 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);
+}
+
+#define FAIL(test) \
+ do { \
+ if (test) { \
+ printf("FAIL (%s)\n", #test); \
+ return -1; \
+ } \
+ } while (0)
+
+#define SUCCEED \
+ do { 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");
+
+ 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.
+ */
+ printf("2 - F_SETLK on locked region: ");
+ res = fcntl(fd, F_SETLK, &fl);
+ 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.
+ */
+ printf("3 - F_SETLKW on locked region: ");
+
+ alarm(1);
+
+ res = fcntl(fd, F_SETLKW, &fl);
+ 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");
+
+ 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
+
+ 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);
+ printf("5 - F_SETLKW simple deadlock: ");
+
+ fl.l_start = 1;
+ res = fcntl(fd, F_SETLKW, &fl);
+ 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);
+ printf("6 - F_SETLKW complex deadlock: ");
+
+ fl.l_start = 1;
+ fl.l_len = 2;
+ res = fcntl(fd, F_SETLKW, &fl);
+ kill(pid1, SIGTERM);
+ safe_waitpid(pid1);
+ 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.
+ */
+ printf("7 - F_SETLK shared lock on exclusive locked region: ");
+
+ fl.l_type = F_RDLCK;
+ res = fcntl(fd, F_SETLK, &fl);
+ 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.
+ */
+ printf("8 - F_SETLK shared lock on share locked region: ");
+
+ fl.l_type = F_RDLCK;
+ res = fcntl(fd, F_SETLK, &fl);
+
+ 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.
+ */
+ printf("9 - F_SETLK exclusive lock on share locked region: ");
+
+ fl.l_type = F_WRLCK;
+ res = fcntl(fd, F_SETLK, &fl);
+ 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)");
+
+ printf("10 - trying to set bogus pid or sysid values: ");
+
+ if (fcntl(fd, F_GETLK, &fl) < 0)
+ err(1, "F_GETLK");
+
+ 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;
+
+ 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.
+ */
+ printf("12 - F_SETLKW on locked region which is then unlocked: ");
+
+ //alarm(1);
+
+ res = fcntl(fd, F_SETLKW, &fl);
+ 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;
+
+ 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)");
+
+ usleep(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);
+ 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);
+
+ 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];
+ 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)");
+
+ (void)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.
+ */
+ printf("15 - flock(2) semantics: ");
+
+ alarm(1);
+ flock(fd, LOCK_EX);
+
+ /*
+ * Kill the child to force it to drop its locks.
+ */
+ 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_ctx {
+ struct flock tc_fl;
+ int tc_fd;
+};
+
+static void *
+test16_func(void *tc_in)
+{
+ uintptr_t error;
+ struct test_ctx *tc = tc_in;
+
+ error = fcntl(tc->tc_fd, F_SETLKW, &tc->tc_fl);
+
+ pthread_exit((void *)error);
+}
+
+#define THREADS 10
+
+/*
+ * Test 16 - F_SETLKW from two threads
+ *
+ * If two threads within a process are blocked on a lock and the lock
+ * is granted, make sure things are sane.
+ */
+static int
+test16(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 test_ctx tc = { .tc_fd = fd };
+ char ch;
+ int i;
+ int error;
+ pthread_t thr[THREADS];
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ tc.tc_fl.l_start = 0;
+ tc.tc_fl.l_len = 0;
+ tc.tc_fl.l_type = F_WRLCK;
+ tc.tc_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, &tc.tc_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.
+ */
+ printf("16 - F_SETLKW on locked region by two threads: ");
+
+ for (i = 0; i < THREADS; i++) {
+ error = pthread_create(&thr[i], NULL, test16_func, &tc);
+ if (error)
+ err(1, "pthread_create");
+ }
+
+ /*
+ * Sleep, then kill the child. This makes me a little sad, but it's
+ * tricky to tell whether the threads are all really blocked by this
+ * point.
+ */
+ sleep(1);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ for (i = 0; i < THREADS; i++) {
+ void *res;
+ error = pthread_join(thr[i], &res);
+ if (error)
+ err(1, "pthread_join");
+ FAIL((uintptr_t)res != 0);
+ }
+
+ SUCCEED;
+}
+
+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 */
+};
+
+static 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 },
+ { test16, 16, 1 },
+};
+
+int
+main(int argc, const char *argv[])
+{
+ int testnum;
+ int fd;
+ int nointr;
+ unsigned i;
+ struct sigaction sa;
+ int test_argc;
+ const char **test_argv;
+
+ 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 {
+ 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
+
+ for (i = 0; i < nitems(tests); i++) {
+ if (tests[i].intr && nointr)
+ continue;
+ if (!testnum || tests[i].num == testnum)
+ tests[i].testfn(fd, test_argc, test_argv);
+ }
+
+ return 0;
+}
diff --git a/tests/sys/file/flock_test.sh b/tests/sys/file/flock_test.sh
new file mode 100755
index 0000000..ac3e799
--- /dev/null
+++ b/tests/sys/file/flock_test.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright 2014 EMC Corp.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+# OWNER 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$
+
+# Testcase # 11 is racy; uses an undocumented kernel interface for testing
+# locking
+# Testcase # 16 is racy/doesn't handle EINTR properly
+last_testcase=16
+
+echo "1..$last_testcase"
+
+for n in `seq 1 $last_testcase`; do
+ todomsg=""
+
+ if [ $n -eq 11 ]; then
+ todomsg=" # TODO: racy testcase"
+ # Test 16 fails:
+ # F_SETLKW on locked region by two threads: FAIL ((uintptr_t)res != 0)
+ elif [ $n -eq 16 ]; then
+ todomsg=" # TODO: racy testcase (doesn't handle EINTR properly)"
+ fi
+
+ $(dirname $0)/flock_helper . $n | grep -q SUCCEED
+ if [ $? -eq 0 ]; then
+ echo "ok $n$todomsg"
+ else
+ echo "not ok $n$todomsg"
+ fi
+done
diff --git a/tests/sys/file/ftruncate_test.c b/tests/sys/file/ftruncate_test.c
new file mode 100644
index 0000000..7eaba14
--- /dev/null
+++ b/tests/sys/file/ftruncate_test.c
@@ -0,0 +1,177 @@
+/*-
+ * Copyright (c) 2006 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * 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$
+ */
+
+/*
+ * Very simple regression test.
+ *
+ * Future tests that might be of interest:
+ *
+ * - Make sure we get EISDIR on a directory.
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * Select various potentially interesting lengths at and around power of 2
+ * edges.
+ */
+static off_t lengths[] = {0, 1, 2, 3, 4, 127, 128, 129, 511, 512, 513, 1023,
+ 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097, 8191, 8192, 8193, 16383,
+ 16384, 16385};
+static int lengths_count = sizeof(lengths) / sizeof(off_t);
+
+int
+main(int argc, char *argv[])
+{
+ int error, fd, fds[2], i, read_only_fd;
+ char path[PATH_MAX];
+ struct stat sb;
+ ssize_t size;
+ off_t len;
+ char ch;
+
+ /*
+ * Tests using a writable temporary file: grow and then shrink a file
+ * using ftruncate and various lengths. Make sure that a negative
+ * file length is rejected. Make sure that when we grow the file,
+ * bytes now in the range of the file size return 0.
+ *
+ * Save a read-only reference to the file to use later for read-only
+ * descriptor tests.
+ */
+ snprintf(path, PATH_MAX, "/tmp/ftruncate.XXXXXXXXXXXXX");
+ fd = mkstemp(path);
+ if (fd < 0)
+ err(-1, "mkstemp");
+ read_only_fd = open(path, O_RDONLY);
+ if (read_only_fd < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "open(%s, O_RDONLY)", path);
+ }
+ (void)unlink(path);
+
+ if (ftruncate(fd, -1) == 0)
+ errx(-1, "ftruncate(fd, -1) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(fd, -1) returned wrong error");
+
+ for (i = 0; i < lengths_count; i++) {
+ len = lengths[i];
+ if (ftruncate(fd, len) < 0)
+ err(-1, "ftruncate(%jd) up", (intmax_t)len);
+ if (fstat(fd, &sb) < 0)
+ err(-1, "stat");
+ if (sb.st_size != len)
+ errx(-1, "fstat with len=%jd returned len %jd up",
+ (intmax_t)len, (intmax_t)sb.st_size);
+ if (len != 0) {
+ size = pread(fd, &ch, sizeof(ch), len - 1);
+ if (size < 0)
+ err(-1, "pread on len %jd up", (intmax_t)len);
+ if (size != sizeof(ch))
+ errx(-1, "pread len %jd size %jd up",
+ (intmax_t)len, (intmax_t)size);
+ if (ch != 0)
+ errx(-1,
+ "pread length %jd size %jd ch %d up",
+ (intmax_t)len, (intmax_t)size, ch);
+ }
+ }
+
+ for (i = lengths_count - 1; i >= 0; i--) {
+ len = lengths[i];
+ if (ftruncate(fd, len) < 0)
+ err(-1, "ftruncate(%jd) down", (intmax_t)len);
+ if (fstat(fd, &sb) < 0)
+ err(-1, "stat");
+ if (sb.st_size != len)
+ errx(-1, "fstat(%jd) returned %jd down", (intmax_t)len,
+ sb.st_size);
+ }
+ close(fd);
+
+ /*
+ * Make sure that a read-only descriptor can't be truncated.
+ */
+ if (ftruncate(read_only_fd, 0) == 0)
+ errx(-1, "ftruncate(read_only_fd) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(read_only_fd) returned wrong error");
+ close(read_only_fd);
+
+ /*
+ * Make sure that ftruncate on sockets doesn't work.
+ */
+ fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ err(-1, "socket(PF_UNIX, SOCK_STREAM, 0)");
+ if (ftruncate(fd, 0) == 0)
+ errx(-1, "ftruncate(socket) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(socket) returned wrong error");
+ close(fd);
+
+ /*
+ * Make sure that ftruncate on pipes doesn't work.
+ */
+ if (pipe(fds) < 0)
+ err(-1, "pipe");
+ if (ftruncate(fds[0], 0) == 0)
+ errx(-1, "ftruncate(pipe) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(pipe) returned wrong error");
+ close(fds[0]);
+ close(fds[1]);
+
+ /*
+ * Make sure that ftruncate on kqueues doesn't work.
+ */
+ fd = kqueue();
+ if (fd < 0)
+ err(-1, "kqueue");
+ if (ftruncate(fds[0], 0) == 0)
+ errx(-1, "ftruncate(kqueue) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(kqueue) returned wrong error");
+ close(fd);
+
+ return (0);
+}
diff --git a/tests/sys/file/newfileops_on_fork_test.c b/tests/sys/file/newfileops_on_fork_test.c
new file mode 100644
index 0000000..8713a82
--- /dev/null
+++ b/tests/sys/file/newfileops_on_fork_test.c
@@ -0,0 +1,121 @@
+/*-
+ * Copyright (c) 2009 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * This software was developed at the University of Cambridge Computer
+ * Laboratory with support from a grant from Google, Inc.
+ *
+ * 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$
+ */
+
+/*
+ * When a multi-threaded application calls fork(2) from one thread while
+ * another thread is blocked in accept(2), we prefer that the file descriptor
+ * to be returned by accept(2) not appear in the child process. Test this by
+ * creating a thread blocked in accept(2), then forking a child and seeing if
+ * the fd it would have returned is defined in the child or not.
+ */
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PORT 9000
+
+static int listen_fd;
+
+static void *
+do_accept(__unused void *arg)
+{
+ int accept_fd;
+
+ accept_fd = accept(listen_fd, NULL, NULL);
+ if (accept_fd < 0)
+ err(-1, "accept");
+
+ return (NULL);
+}
+
+static void
+do_fork(void)
+{
+ int pid;
+
+ pid = fork();
+ if (pid < 0)
+ err(-1, "fork");
+ if (pid > 0) {
+ waitpid(pid, NULL, 0);
+ exit(0);
+ }
+
+ /*
+ * We will call ftruncate(2) on the next available file descriptor,
+ * listen_fd+1, and get back EBADF if it's not a valid descriptor,
+ * and EINVAL if it is. This (currently) works fine in practice.
+ */
+ if (ftruncate(listen_fd + 1, 0 < 0)) {
+ if (errno == EBADF)
+ exit(0);
+ else if (errno == EINVAL)
+ errx(-1, "file descriptor still open in child");
+ else
+ err(-1, "unexpected error");
+ } else
+ errx(-1, "ftruncate succeeded");
+}
+
+int
+main(__unused int argc, __unused char *argv[])
+{
+ struct sockaddr_in sin;
+ pthread_t accept_thread;
+
+ listen_fd = socket(PF_INET, SOCK_STREAM, 0);
+ if (listen_fd < 0)
+ err(-1, "socket");
+ bzero(&sin, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof(sin);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(PORT);
+ if (bind(listen_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "bind");
+ if (listen(listen_fd, -1) <0)
+ err(-1, "listen");
+ if (pthread_create(&accept_thread, NULL, do_accept, NULL) != 0)
+ err(-1, "pthread_create");
+ sleep(1); /* Easier than using a CV. */;
+ do_fork();
+ exit(0);
+}
OpenPOWER on IntegriCloud