summaryrefslogtreecommitdiffstats
path: root/tools/regression
diff options
context:
space:
mode:
Diffstat (limited to 'tools/regression')
-rw-r--r--tools/regression/fifo/fifo_create/Makefile7
-rw-r--r--tools/regression/fifo/fifo_create/fifo_create.c285
-rw-r--r--tools/regression/fifo/fifo_io/Makefile7
-rw-r--r--tools/regression/fifo/fifo_io/fifo_io.c1409
-rw-r--r--tools/regression/fifo/fifo_misc/Makefile7
-rw-r--r--tools/regression/fifo/fifo_misc/fifo_misc.c336
-rw-r--r--tools/regression/fifo/fifo_open/Makefile7
-rw-r--r--tools/regression/fifo/fifo_open/fifo_open.c476
-rw-r--r--tools/regression/file/closefrom/Makefile9
-rw-r--r--tools/regression/file/closefrom/closefrom.c274
-rw-r--r--tools/regression/file/closefrom/closefrom.t10
-rw-r--r--tools/regression/file/dup/Makefile7
-rw-r--r--tools/regression/file/dup/dup.c386
-rw-r--r--tools/regression/file/dup/dup.t10
-rw-r--r--tools/regression/file/fcntlflags/Makefile7
-rw-r--r--tools/regression/file/fcntlflags/fcntlflags.c110
-rw-r--r--tools/regression/file/fcntlflags/fcntlflags.t10
-rw-r--r--tools/regression/file/flock/Makefile9
-rw-r--r--tools/regression/file/flock/flock.c1598
-rw-r--r--tools/regression/file/ftruncate/Makefile7
-rw-r--r--tools/regression/file/ftruncate/ftruncate.c177
-rw-r--r--tools/regression/file/newfileops_on_fork/Makefile8
-rw-r--r--tools/regression/file/newfileops_on_fork/newfileops_on_fork.c121
-rw-r--r--tools/regression/kqueue/Makefile21
-rw-r--r--tools/regression/kqueue/common.h78
-rw-r--r--tools/regression/kqueue/config.h13
-rw-r--r--tools/regression/kqueue/main.c284
-rw-r--r--tools/regression/kqueue/proc.c255
-rw-r--r--tools/regression/kqueue/read.c324
-rw-r--r--tools/regression/kqueue/signal.c199
-rw-r--r--tools/regression/kqueue/timer.c178
-rw-r--r--tools/regression/kqueue/user.c129
-rw-r--r--tools/regression/kqueue/vnode.c266
-rw-r--r--tools/regression/lib/libc/stdio/test-open_memstream.c2
-rw-r--r--tools/regression/lib/libc/stdio/test-open_wmemstream.c2
-rw-r--r--tools/regression/mmap/Makefile6
-rw-r--r--tools/regression/mmap/mmap.c96
-rw-r--r--tools/regression/mqueue/Makefile5
-rw-r--r--tools/regression/mqueue/mqtest1/Makefile9
-rw-r--r--tools/regression/mqueue/mqtest1/mqtest1.c52
-rw-r--r--tools/regression/mqueue/mqtest2/Makefile9
-rw-r--r--tools/regression/mqueue/mqtest2/mqtest2.c93
-rw-r--r--tools/regression/mqueue/mqtest3/Makefile9
-rw-r--r--tools/regression/mqueue/mqtest3/mqtest3.c109
-rw-r--r--tools/regression/mqueue/mqtest4/Makefile9
-rw-r--r--tools/regression/mqueue/mqtest4/mqtest4.c115
-rw-r--r--tools/regression/mqueue/mqtest5/Makefile9
-rw-r--r--tools/regression/mqueue/mqtest5/mqtest5.c122
-rw-r--r--tools/regression/netinet/arphold/arphold.c2
-rw-r--r--tools/regression/posixshm/Makefile9
-rw-r--r--tools/regression/posixshm/posixshm.c627
-rw-r--r--tools/regression/posixshm/posixshm.t5
-rw-r--r--tools/regression/posixshm/test.c128
-rw-r--r--tools/regression/posixshm/test.h59
-rw-r--r--tools/regression/sockets/accept_fd_leak/Makefile9
-rw-r--r--tools/regression/sockets/accept_fd_leak/accept_fd_leak.c215
-rw-r--r--tools/regression/sockets/accept_fd_leak/accept_fd_leak.t10
-rw-r--r--tools/regression/sockets/accf_data_attach/Makefile9
-rw-r--r--tools/regression/sockets/accf_data_attach/accf_data_attach.c216
-rw-r--r--tools/regression/sockets/accf_data_attach/accf_data_attach.t10
-rw-r--r--tools/regression/sockets/fstat/Makefile9
-rw-r--r--tools/regression/sockets/fstat/fstat.c71
-rw-r--r--tools/regression/sockets/kqueue/Makefile9
-rw-r--r--tools/regression/sockets/kqueue/kqueue.c368
-rw-r--r--tools/regression/sockets/kqueue/kqueue.t10
-rw-r--r--tools/regression/sockets/listen_backlog/Makefile7
-rw-r--r--tools/regression/sockets/listen_backlog/listen_backlog.c383
-rw-r--r--tools/regression/sockets/listenclose/Makefile9
-rw-r--r--tools/regression/sockets/listenclose/listenclose.c111
-rw-r--r--tools/regression/sockets/pr_atomic/Makefile7
-rw-r--r--tools/regression/sockets/pr_atomic/pr_atomic.c109
-rw-r--r--tools/regression/sockets/reconnect/Makefile9
-rw-r--r--tools/regression/sockets/reconnect/reconnect.c132
-rw-r--r--tools/regression/sockets/rtsocket/Makefile7
-rw-r--r--tools/regression/sockets/rtsocket/rtsocket.c101
-rw-r--r--tools/regression/sockets/sblock/Makefile9
-rw-r--r--tools/regression/sockets/sblock/sblock.c207
-rw-r--r--tools/regression/sockets/sendfile/Makefile11
-rw-r--r--tools/regression/sockets/sendfile/sendfile.c486
-rw-r--r--tools/regression/sockets/shutdown/Makefile9
-rw-r--r--tools/regression/sockets/shutdown/shutdown.c110
-rw-r--r--tools/regression/sockets/sigpipe/Makefile9
-rw-r--r--tools/regression/sockets/sigpipe/sigpipe.c322
-rw-r--r--tools/regression/sockets/so_setfib/Makefile16
-rw-r--r--tools/regression/sockets/so_setfib/so_setfib.c200
-rw-r--r--tools/regression/sockets/socketpair/Makefile9
-rw-r--r--tools/regression/sockets/socketpair/socketpair.c161
-rw-r--r--tools/regression/sockets/unix_bindconnect/Makefile7
-rw-r--r--tools/regression/sockets/unix_bindconnect/unix_bindconnect.c318
-rw-r--r--tools/regression/sockets/unix_close_race/Makefile7
-rw-r--r--tools/regression/sockets/unix_close_race/unix_close_race.c143
-rw-r--r--tools/regression/sockets/unix_cmsg/Makefile7
-rw-r--r--tools/regression/sockets/unix_cmsg/README160
-rw-r--r--tools/regression/sockets/unix_cmsg/unix_cmsg.c1969
-rw-r--r--tools/regression/sockets/unix_cmsg/unix_cmsg.t88
-rw-r--r--tools/regression/sockets/unix_gc/Makefile7
-rw-r--r--tools/regression/sockets/unix_gc/unix_gc.c808
-rw-r--r--tools/regression/sockets/unix_passfd/Makefile7
-rw-r--r--tools/regression/sockets/unix_passfd/unix_passfd.c389
-rw-r--r--tools/regression/sockets/unix_sendtorace/Makefile7
-rw-r--r--tools/regression/sockets/unix_sendtorace/unix_sendtorace.c215
-rw-r--r--tools/regression/sockets/unix_socket/Makefile7
-rw-r--r--tools/regression/sockets/unix_socket/unix_socket.c84
-rw-r--r--tools/regression/sockets/unix_sorflush/Makefile7
-rw-r--r--tools/regression/sockets/unix_sorflush/unix_sorflush.c99
-rw-r--r--tools/regression/sockets/zerosend/Makefile7
-rw-r--r--tools/regression/sockets/zerosend/zerosend.c290
-rwxr-xr-xtools/regression/vfs/trailing_slash.t42
108 files changed, 16526 insertions, 3 deletions
diff --git a/tools/regression/fifo/fifo_create/Makefile b/tools/regression/fifo/fifo_create/Makefile
new file mode 100644
index 0000000..10e2b14
--- /dev/null
+++ b/tools/regression/fifo/fifo_create/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= fifo_create
+MAN=
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/fifo/fifo_create/fifo_create.c b/tools/regression/fifo/fifo_create/fifo_create.c
new file mode 100644
index 0000000..4e8a8a4
--- /dev/null
+++ b/tools/regression/fifo/fifo_create/fifo_create.c
@@ -0,0 +1,285 @@
+/*-
+ * 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.
+ */
+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[PATH_MAX];
+ 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(temp_dir, &old_dirsb) < 0)
+ err(-1, "basic_create_test: %s: stat: %s", testname,
+ temp_dir);
+
+ sleep(2);
+
+ snprintf(path, PATH_MAX, "%s/testfifo", temp_dir);
+
+ 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(temp_dir, &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[PATH_MAX];
+ const char *testname;
+ struct stat sb;
+ int error, i;
+
+ if (use_mkfifo)
+ testname = "mkfifo";
+ else
+ testname = "mknod";
+
+ snprintf(path, PATH_MAX, "%s/testfifo", temp_dir);
+ 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(int argc, char *argv[])
+{
+ int i;
+
+ if (geteuid() != 0)
+ errx(-1, "must be run as root");
+
+ strcpy(temp_dir, "/tmp/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/tools/regression/fifo/fifo_io/Makefile b/tools/regression/fifo/fifo_io/Makefile
new file mode 100644
index 0000000..49bf5bd
--- /dev/null
+++ b/tools/regression/fifo/fifo_io/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= fifo_io
+MAN=
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/fifo/fifo_io/fifo_io.c b/tools/regression/fifo/fifo_io/fifo_io.c
new file mode 100644
index 0000000..4d3c54e
--- /dev/null
+++ b/tools/regression/fifo/fifo_io/fifo_io.c
@@ -0,0 +1,1409 @@
+/*-
+ * 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.
+ */
+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, const char *testname, 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, const char *testname, 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", "test_simpleio", &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 int alarm_fired;
+/*
+ * Non-destructive SIGALRM handler.
+ */
+static void
+sigalarm(int signum)
+{
+
+ 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", __func__, &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", __func__, &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", __func__, &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", __func__, &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", __func__, &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", __func__, &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", __func__, &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", __func__, &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", __func__, &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", __func__, &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", __func__, &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(int argc, char *argv[])
+{
+
+ strcpy(temp_dir, "/tmp/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/tools/regression/fifo/fifo_misc/Makefile b/tools/regression/fifo/fifo_misc/Makefile
new file mode 100644
index 0000000..97f85ec
--- /dev/null
+++ b/tools/regression/fifo/fifo_misc/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= fifo_misc
+MAN=
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/fifo/fifo_misc/fifo_misc.c b/tools/regression/fifo/fifo_misc/fifo_misc.c
new file mode 100644
index 0000000..4215212
--- /dev/null
+++ b/tools/regression/fifo/fifo_misc/fifo_misc.c
@@ -0,0 +1,336 @@
+/*-
+ * 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.
+ */
+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, const char *testname, 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", __func__, &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", __func__, &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", __func__, &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(int argc, char *argv[])
+{
+
+ strcpy(temp_dir, "/tmp/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/tools/regression/fifo/fifo_open/Makefile b/tools/regression/fifo/fifo_open/Makefile
new file mode 100644
index 0000000..d90811c
--- /dev/null
+++ b/tools/regression/fifo/fifo_open/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= fifo_open
+MAN=
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/fifo/fifo_open/fifo_open.c b/tools/regression/fifo/fifo_open/fifo_open.c
new file mode 100644
index 0000000..6899a3a
--- /dev/null
+++ b/tools/regression/fifo/fifo_open/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.
+ */
+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(int argc, char *argv[])
+{
+
+ if (geteuid() != 0)
+ errx(-1, "must be run as root");
+
+ strcpy(temp_dir, "/tmp/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/tools/regression/file/closefrom/Makefile b/tools/regression/file/closefrom/Makefile
new file mode 100644
index 0000000..4b8829c
--- /dev/null
+++ b/tools/regression/file/closefrom/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= closefrom
+MAN=
+WARNS?= 6
+DPADD= ${LIBUTIL}
+LDADD= -lutil
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/file/closefrom/closefrom.c b/tools/regression/file/closefrom/closefrom.c
new file mode 100644
index 0000000..b27ec51
--- /dev/null
+++ b/tools/regression/file/closefrom/closefrom.c
@@ -0,0 +1,274 @@
+/*-
+ * 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 <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("/dev/null", O_RDONLY);
+ if (fd < 0)
+ fail_err("open(\"/dev/null\")");
+ return (fd);
+}
+
+int
+main(int __unused argc, char __unused *argv[])
+{
+ struct shared_info *info;
+ pid_t pid;
+ int fd, i;
+
+ printf("1..15\n");
+
+ /* We better start up with fd's 0, 1, and 2 open. */
+ fd = devnull();
+ if (fd != 3)
+ fail("open", "bad descriptor %d", fd);
+ ok("open");
+
+ /* Make sure highest_fd() works. */
+ fd = highest_fd();
+ if (fd != 3)
+ fail("highest_fd", "bad descriptor %d", fd);
+ ok("highest_fd");
+
+ /* Try to use closefrom() for just closing fd 3. */
+ closefrom(3);
+ fd = highest_fd();
+ if (fd != 2)
+ 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 != 18)
+ 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/tools/regression/file/closefrom/closefrom.t b/tools/regression/file/closefrom/closefrom.t
new file mode 100644
index 0000000..8bdfd03
--- /dev/null
+++ b/tools/regression/file/closefrom/closefrom.t
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+executable=`basename $0 .t`
+
+make $executable 2>&1 > /dev/null
+
+exec ./$executable
diff --git a/tools/regression/file/dup/Makefile b/tools/regression/file/dup/Makefile
new file mode 100644
index 0000000..225c2e2
--- /dev/null
+++ b/tools/regression/file/dup/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= dup
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/file/dup/dup.c b/tools/regression/file/dup/dup.c
new file mode 100644
index 0000000..8173818
--- /dev/null
+++ b/tools/regression/file/dup/dup.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/tools/regression/file/dup/dup.t b/tools/regression/file/dup/dup.t
new file mode 100644
index 0000000..8bdfd03
--- /dev/null
+++ b/tools/regression/file/dup/dup.t
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+executable=`basename $0 .t`
+
+make $executable 2>&1 > /dev/null
+
+exec ./$executable
diff --git a/tools/regression/file/fcntlflags/Makefile b/tools/regression/file/fcntlflags/Makefile
new file mode 100644
index 0000000..9e7fc3e
--- /dev/null
+++ b/tools/regression/file/fcntlflags/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= fcntlflags
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/file/fcntlflags/fcntlflags.c b/tools/regression/file/fcntlflags/fcntlflags.c
new file mode 100644
index 0000000..bcb3b54
--- /dev/null
+++ b/tools/regression/file/fcntlflags/fcntlflags.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/tools/regression/file/fcntlflags/fcntlflags.t b/tools/regression/file/fcntlflags/fcntlflags.t
new file mode 100644
index 0000000..8bdfd03
--- /dev/null
+++ b/tools/regression/file/fcntlflags/fcntlflags.t
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+executable=`basename $0 .t`
+
+make $executable 2>&1 > /dev/null
+
+exec ./$executable
diff --git a/tools/regression/file/flock/Makefile b/tools/regression/file/flock/Makefile
new file mode 100644
index 0000000..cd1a46d
--- /dev/null
+++ b/tools/regression/file/flock/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= flock
+MAN=
+WARNS?= 6
+DPADD= ${LIBPTHREAD}
+LDADD= -lpthread
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/file/flock/flock.c b/tools/regression/file/flock/flock.c
new file mode 100644
index 0000000..49e47b8
--- /dev/null
+++ b/tools/regression/file/flock/flock.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/tools/regression/file/ftruncate/Makefile b/tools/regression/file/ftruncate/Makefile
new file mode 100644
index 0000000..40b753e
--- /dev/null
+++ b/tools/regression/file/ftruncate/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= ftruncate
+MAN=
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/file/ftruncate/ftruncate.c b/tools/regression/file/ftruncate/ftruncate.c
new file mode 100644
index 0000000..aebcdcd
--- /dev/null
+++ b/tools/regression/file/ftruncate/ftruncate.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;
+ size_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, "makestemp");
+ 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(%llu) up", len);
+ if (fstat(fd, &sb) < 0)
+ err(-1, "stat");
+ if (sb.st_size != len)
+ errx(-1, "fstat(%llu) returned len %llu up", len,
+ sb.st_size);
+ if (len != 0) {
+ size = pread(fd, &ch, sizeof(ch), len - 1);
+ if (size < 0)
+ err(-1, "pread on len %llu up", len);
+ if (size != sizeof(ch))
+ errx(-1, "pread len %llu size %jd up",
+ len, (intmax_t)size);
+ if (ch != 0)
+ errx(-1,
+ "pread length %llu size %jd ch %d up",
+ len, (intmax_t)size, ch);
+ }
+ }
+
+ for (i = lengths_count - 1; i >= 0; i--) {
+ len = lengths[i];
+ if (ftruncate(fd, len) < 0)
+ err(-1, "ftruncate(%llu) down", len);
+ if (fstat(fd, &sb) < 0)
+ err(-1, "stat");
+ if (sb.st_size != len)
+ errx(-1, "fstat(%llu) returned %llu down", 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/tools/regression/file/newfileops_on_fork/Makefile b/tools/regression/file/newfileops_on_fork/Makefile
new file mode 100644
index 0000000..be0c5fe
--- /dev/null
+++ b/tools/regression/file/newfileops_on_fork/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= newfileops_on_fork
+MAN=
+WARNS?= 6
+LDFLAGS= -lpthread
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/file/newfileops_on_fork/newfileops_on_fork.c b/tools/regression/file/newfileops_on_fork/newfileops_on_fork.c
new file mode 100644
index 0000000..8713a82
--- /dev/null
+++ b/tools/regression/file/newfileops_on_fork/newfileops_on_fork.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);
+}
diff --git a/tools/regression/kqueue/Makefile b/tools/regression/kqueue/Makefile
new file mode 100644
index 0000000..12b7527
--- /dev/null
+++ b/tools/regression/kqueue/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+#
+# svn://mark.heily.com/libkqueue/trunk/test
+# Last update: r114
+#
+# libkqueue and test suite by Mark Heily <mark@heily.com>
+#
+
+PROG=kqtest
+SRCS= \
+ main.c \
+ read.c \
+ timer.c \
+ vnode.c \
+ proc.c \
+ signal.c \
+ user.c
+MAN=
+WARNS?= 2
+
+.include "bsd.prog.mk"
diff --git a/tools/regression/kqueue/common.h b/tools/regression/kqueue/common.h
new file mode 100644
index 0000000..aada778
--- /dev/null
+++ b/tools/regression/kqueue/common.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+
+#if HAVE_ERR_H
+# include <err.h>
+#else
+# define err(rc,msg,...) do { perror(msg); exit(rc); } while (0)
+# define errx(rc,msg,...) do { puts(msg); exit(rc); } while (0)
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sys/event.h>
+
+#include "config.h"
+
+extern char *cur_test_id;
+int vnode_fd;
+
+extern const char * kevent_to_str(struct kevent *);
+struct kevent * kevent_get(int);
+
+
+void kevent_cmp(struct kevent *, struct kevent *);
+
+void
+kevent_add(int kqfd, struct kevent *kev,
+ uintptr_t ident,
+ short filter,
+ u_short flags,
+ u_int fflags,
+ intptr_t data,
+ void *udata);
+
+/* DEPRECATED: */
+#define KEV_CMP(kev,_ident,_filter,_flags) do { \
+ if (kev.ident != (_ident) || \
+ kev.filter != (_filter) || \
+ kev.flags != (_flags)) \
+ err(1, "kevent mismatch: got [%d,%d,%d] but expecting [%d,%d,%d]", \
+ (int)_ident, (int)_filter, (int)_flags,\
+ (int)kev.ident, kev.filter, kev.flags);\
+} while (0);
+
+/* Checks if any events are pending, which is an error. */
+extern void test_no_kevents(void);
+
+extern void test_begin(const char *);
+extern void success(void);
+
+#endif /* _COMMON_H */
diff --git a/tools/regression/kqueue/config.h b/tools/regression/kqueue/config.h
new file mode 100644
index 0000000..a204092
--- /dev/null
+++ b/tools/regression/kqueue/config.h
@@ -0,0 +1,13 @@
+/* $FreeBSD$ */
+
+#define HAVE_ERR_H 1
+#define HAVE_SYS_EVENT_H 1
+#define HAVE_EV_DISPATCH 1
+#define HAVE_EV_RECEIPT 1
+#undef HAVE_NOTE_TRUNCATE
+#define HAVE_EVFILT_TIMER 1
+#define HAVE_EVFILT_USER 1
+#define PROGRAM "libkqueue-test"
+#define VERSION "0.1"
+#define TARGET "freebsd"
+#define CFLAGS "-g -O0 -Wall -Werror"
diff --git a/tools/regression/kqueue/main.c b/tools/regression/kqueue/main.c
new file mode 100644
index 0000000..f76c4e2
--- /dev/null
+++ b/tools/regression/kqueue/main.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+
+#include "config.h"
+#include "common.h"
+
+int testnum = 1;
+char *cur_test_id = NULL;
+int kqfd;
+
+extern void test_evfilt_read();
+extern void test_evfilt_signal();
+extern void test_evfilt_vnode();
+extern void test_evfilt_timer();
+extern void test_evfilt_proc();
+#if HAVE_EVFILT_USER
+extern void test_evfilt_user();
+#endif
+
+/* Checks if any events are pending, which is an error. */
+void
+test_no_kevents(void)
+{
+ int nfds;
+ struct timespec timeo;
+ struct kevent kev;
+
+ puts("confirming that there are no events pending");
+ memset(&timeo, 0, sizeof(timeo));
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, &timeo);
+ if (nfds != 0) {
+ puts("\nUnexpected event:");
+ puts(kevent_to_str(&kev));
+ errx(1, "%d event(s) pending, but none expected:", nfds);
+ }
+}
+
+/* Retrieve a single kevent */
+struct kevent *
+kevent_get(int kqfd)
+{
+ int nfds;
+ struct kevent *kev;
+
+ if ((kev = calloc(1, sizeof(*kev))) == NULL)
+ err(1, "out of memory");
+
+ nfds = kevent(kqfd, NULL, 0, kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "kevent(2)");
+
+ return (kev);
+}
+
+char *
+kevent_fflags_dump(struct kevent *kev)
+{
+ char *buf;
+
+#define KEVFFL_DUMP(attrib) \
+ if (kev->fflags & attrib) \
+ strncat(buf, #attrib" ", 64);
+
+ if ((buf = calloc(1, 1024)) == NULL)
+ abort();
+
+ /* Not every filter has meaningful fflags */
+ if (kev->filter != EVFILT_VNODE) {
+ snprintf(buf, 1024, "fflags = %d", kev->fflags);
+ return (buf);
+ }
+
+ snprintf(buf, 1024, "fflags = %d (", kev->fflags);
+ KEVFFL_DUMP(NOTE_DELETE);
+ KEVFFL_DUMP(NOTE_WRITE);
+ KEVFFL_DUMP(NOTE_EXTEND);
+#if HAVE_NOTE_TRUNCATE
+ KEVFFL_DUMP(NOTE_TRUNCATE);
+#endif
+ KEVFFL_DUMP(NOTE_ATTRIB);
+ KEVFFL_DUMP(NOTE_LINK);
+ KEVFFL_DUMP(NOTE_RENAME);
+#if HAVE_NOTE_REVOKE
+ KEVFFL_DUMP(NOTE_REVOKE);
+#endif
+ buf[strlen(buf) - 1] = ')';
+
+ return (buf);
+}
+
+char *
+kevent_flags_dump(struct kevent *kev)
+{
+ char *buf;
+
+#define KEVFL_DUMP(attrib) \
+ if (kev->flags & attrib) \
+ strncat(buf, #attrib" ", 64);
+
+ if ((buf = calloc(1, 1024)) == NULL)
+ abort();
+
+ snprintf(buf, 1024, "flags = %d (", kev->flags);
+ KEVFL_DUMP(EV_ADD);
+ KEVFL_DUMP(EV_ENABLE);
+ KEVFL_DUMP(EV_DISABLE);
+ KEVFL_DUMP(EV_DELETE);
+ KEVFL_DUMP(EV_ONESHOT);
+ KEVFL_DUMP(EV_CLEAR);
+ KEVFL_DUMP(EV_EOF);
+ KEVFL_DUMP(EV_ERROR);
+#if HAVE_EV_DISPATCH
+ KEVFL_DUMP(EV_DISPATCH);
+#endif
+#if HAVE_EV_RECEIPT
+ KEVFL_DUMP(EV_RECEIPT);
+#endif
+ buf[strlen(buf) - 1] = ')';
+
+ return (buf);
+}
+
+/* Copied from ../kevent.c kevent_dump() and improved */
+const char *
+kevent_to_str(struct kevent *kev)
+{
+ char buf[512];
+
+ snprintf(&buf[0], sizeof(buf),
+ "[ident=%d, filter=%d, %s, %s, data=%d, udata=%p]",
+ (u_int) kev->ident,
+ kev->filter,
+ kevent_flags_dump(kev),
+ kevent_fflags_dump(kev),
+ (int) kev->data,
+ kev->udata);
+
+ return (strdup(buf));
+}
+
+void
+kevent_add(int kqfd, struct kevent *kev,
+ uintptr_t ident,
+ short filter,
+ u_short flags,
+ u_int fflags,
+ intptr_t data,
+ void *udata)
+{
+ EV_SET(kev, ident, filter, flags, fflags, data, NULL);
+ if (kevent(kqfd, kev, 1, NULL, 0, NULL) < 0) {
+ printf("Unable to add the following kevent:\n%s\n",
+ kevent_to_str(kev));
+ err(1, "kevent(): %s", strerror(errno));
+ }
+}
+
+void
+kevent_cmp(struct kevent *k1, struct kevent *k2)
+{
+/* XXX-
+ Workaround for inconsistent implementation of kevent(2)
+ */
+#ifdef __FreeBSD__
+ if (k1->flags & EV_ADD)
+ k2->flags |= EV_ADD;
+#endif
+ if (memcmp(k1, k2, sizeof(*k1)) != 0) {
+ printf("kevent_cmp: mismatch:\n %s !=\n %s\n",
+ kevent_to_str(k1), kevent_to_str(k2));
+ abort();
+ }
+}
+
+void
+test_begin(const char *func)
+{
+ if (cur_test_id)
+ free(cur_test_id);
+ cur_test_id = strdup(func);
+ if (!cur_test_id)
+ err(1, "strdup failed");
+
+ printf("\n\nTest %d: %s\n", testnum++, func);
+}
+
+void
+success(void)
+{
+ printf("%-70s %s\n", cur_test_id, "passed");
+ free(cur_test_id);
+ cur_test_id = NULL;
+}
+
+void
+test_kqueue(void)
+{
+ test_begin("kqueue()");
+ if ((kqfd = kqueue()) < 0)
+ err(1, "kqueue()");
+ test_no_kevents();
+ success();
+}
+
+void
+test_kqueue_close(void)
+{
+ test_begin("close(kq)");
+ if (close(kqfd) < 0)
+ err(1, "close()");
+ success();
+}
+
+int
+main(int argc, char **argv)
+{
+ int test_proc = 1;
+ int test_socket = 1;
+ int test_signal = 1;
+ int test_vnode = 1;
+ int test_timer = 1;
+#ifdef __FreeBSD__
+ int test_user = 1;
+#else
+ /* XXX-FIXME temporary */
+ int test_user = 0;
+#endif
+
+ while (argc) {
+ if (strcmp(argv[0], "--no-proc") == 0)
+ test_proc = 0;
+ if (strcmp(argv[0], "--no-socket") == 0)
+ test_socket = 0;
+ if (strcmp(argv[0], "--no-timer") == 0)
+ test_timer = 0;
+ if (strcmp(argv[0], "--no-signal") == 0)
+ test_signal = 0;
+ if (strcmp(argv[0], "--no-vnode") == 0)
+ test_vnode = 0;
+ if (strcmp(argv[0], "--no-user") == 0)
+ test_user = 0;
+ argv++;
+ argc--;
+ }
+
+ test_kqueue();
+ test_kqueue_close();
+
+ if (test_socket)
+ test_evfilt_read();
+ if (test_signal)
+ test_evfilt_signal();
+ if (test_vnode)
+ test_evfilt_vnode();
+#if HAVE_EVFILT_USER
+ if (test_user)
+ test_evfilt_user();
+#endif
+ if (test_timer)
+ test_evfilt_timer();
+ if (test_proc)
+ test_evfilt_proc();
+
+ printf("\n---\n"
+ "+OK All %d tests completed.\n", testnum - 1);
+ return (0);
+}
diff --git a/tools/regression/kqueue/proc.c b/tools/regression/kqueue/proc.c
new file mode 100644
index 0000000..6288ee6
--- /dev/null
+++ b/tools/regression/kqueue/proc.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/stat.h>
+
+#include <err.h>
+
+#include "config.h"
+#include "common.h"
+
+static int sigusr1_caught = 0;
+
+int kqfd;
+
+static void
+sig_handler(int signum)
+{
+ sigusr1_caught = 1;
+}
+
+static void
+add_and_delete(void)
+{
+ struct kevent kev;
+ pid_t pid;
+
+ /* Create a child that waits to be killed and then exits */
+ pid = fork();
+ if (pid == 0) {
+ struct stat s;
+ if (fstat(kqfd, &s) != -1)
+ errx(1, "kqueue inherited across fork! (%s() at %s:%d)",
+ __func__, __FILE__, __LINE__);
+
+ pause();
+ exit(2);
+ }
+ printf(" -- child created (pid %d)\n", (int) pid);
+
+ test_begin("kevent(EVFILT_PROC, EV_ADD)");
+
+ test_no_kevents();
+ kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL);
+ test_no_kevents();
+
+ success();
+
+ test_begin("kevent(EVFILT_PROC, EV_DELETE)");
+
+ sleep(1);
+ test_no_kevents();
+ kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_DELETE, 0, 0, NULL);
+ if (kill(pid, SIGKILL) < 0)
+ err(1, "kill");
+ sleep(1);
+ test_no_kevents();
+
+ success();
+
+}
+
+#ifdef TODO
+static void
+event_trigger(void)
+{
+ struct kevent kev;
+ pid_t pid;
+
+ test_begin("kevent(EVFILT_PROC, wait)");
+
+ /* Create a child that waits to be killed and then exits */
+ pid = fork();
+ if (pid == 0) {
+ pause();
+ printf(" -- child caught signal, exiting\n");
+ exit(2);
+ }
+ printf(" -- child created (pid %d)\n", (int) pid);
+
+ test_no_kevents();
+ kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL);
+
+ /* Cause the child to exit, then retrieve the event */
+ printf(" -- killing process %d\n", (int) pid);
+ if (kill(pid, SIGUSR1) < 0)
+ err(1, "kill");
+ kevent_cmp(&kev, kevent_get(kqfd));
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_signal_disable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGKILL) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_signal_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags = EV_ADD | EV_CLEAR;
+#if LIBKQUEUE
+ kev.data = 1; /* WORKAROUND */
+#else
+ kev.data = 2; // one extra time from test_kevent_signal_disable()
+#endif
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the watch */
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_signal_del(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Delete the kevent */
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+ success();
+}
+
+void
+test_kevent_signal_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Send another one and make sure we get no events */
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+ test_no_kevents();
+
+ success();
+}
+#endif
+
+void
+test_evfilt_proc()
+{
+ kqfd = kqueue();
+
+ signal(SIGUSR1, sig_handler);
+
+ add_and_delete();
+
+#if TODO
+ event_trigger();
+#endif
+
+ signal(SIGUSR1, SIG_DFL);
+
+#if TODO
+ test_kevent_signal_add();
+ test_kevent_signal_del();
+ test_kevent_signal_get();
+ test_kevent_signal_disable();
+ test_kevent_signal_enable();
+ test_kevent_signal_oneshot();
+#endif
+ close(kqfd);
+}
diff --git a/tools/regression/kqueue/read.c b/tools/regression/kqueue/read.c
new file mode 100644
index 0000000..cc65427
--- /dev/null
+++ b/tools/regression/kqueue/read.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+int sockfd[2];
+
+static void
+kevent_socket_drain(void)
+{
+ char buf[1];
+
+ /* Drain the read buffer, then make sure there are no more events. */
+ puts("draining the read buffer");
+ if (read(sockfd[0], &buf[0], 1) < 1)
+ err(1, "read(2)");
+}
+
+static void
+kevent_socket_fill(void)
+{
+ puts("filling the read buffer");
+ if (write(sockfd[1], ".", 1) < 1)
+ err(1, "write(2)");
+}
+
+
+void
+test_kevent_socket_add(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_ADD)";
+ struct kevent kev;
+
+ test_begin(test_id);
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_get(void)
+{
+ const char *test_id = "kevent(EVFILT_READ) wait";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_fill();
+
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ kevent_socket_drain();
+ test_no_kevents();
+
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_clear(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_CLEAR)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_fill();
+ kevent_socket_fill();
+
+ kev.data = 2;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* We filled twice, but drain once. Edge-triggered would not generate
+ additional events.
+ */
+ kevent_socket_drain();
+ test_no_kevents();
+
+ kevent_socket_drain();
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_DISABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Add an event, then disable it. */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DISABLE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_fill();
+ test_no_kevents();
+
+ /* Re-enable the knote, then see if an event is generated */
+ kev.flags = EV_ENABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ kev.flags = EV_ADD;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ kevent_socket_drain();
+
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_del(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_fill();
+ test_no_kevents();
+ kevent_socket_drain();
+
+ success();
+}
+
+void
+test_kevent_socket_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Re-add the watch and make sure no events are pending */
+ puts("-- re-adding knote");
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ puts("-- getting one event");
+ kevent_socket_fill();
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ puts("-- checking knote disabled");
+ test_no_kevents();
+
+ /* Try to delete the knote, it should already be deleted */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_drain();
+
+ success();
+}
+
+
+#if HAVE_EV_DISPATCH
+void
+test_kevent_socket_dispatch(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_DISPATCH)";
+
+ test_begin(test_id);
+
+ struct kevent kev;
+
+ /* Re-add the watch and make sure no events are pending */
+ puts("-- re-adding knote");
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_DISPATCH, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ /* The event will occur only once, even though EV_CLEAR is not
+ specified. */
+ kevent_socket_fill();
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+ test_no_kevents();
+
+ /* Since the knote is disabled, the EV_DELETE operation succeeds. */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_drain();
+
+ success();
+}
+#endif /* HAVE_EV_DISPATCH */
+
+#if BROKEN
+void
+test_kevent_socket_lowat(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, NOTE_LOWAT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Re-add the watch and make sure no events are pending */
+ puts("-- re-adding knote, setting low watermark to 2 bytes");
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_ONESHOT, NOTE_LOWAT, 2, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ puts("-- checking that one byte does not trigger an event..");
+ kevent_socket_fill();
+ test_no_kevents();
+
+ puts("-- checking that two bytes triggers an event..");
+ kevent_socket_fill();
+ if (kevent(kqfd, NULL, 0, &kev, 1, NULL) != 1)
+ err(1, "%s", test_id);
+ KEV_CMP(kev, sockfd[0], EVFILT_READ, 0);
+ test_no_kevents();
+
+ kevent_socket_drain();
+ kevent_socket_drain();
+
+ success();
+}
+#endif
+
+void
+test_kevent_socket_eof(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_EOF)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Re-add the watch and make sure no events are pending */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ if (close(sockfd[1]) < 0)
+ err(1, "close(2)");
+
+ kev.flags |= EV_EOF;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the watch */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_evfilt_read()
+{
+ /* Create a connected pair of full-duplex sockets for testing socket events */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) < 0)
+ abort();
+
+ kqfd = kqueue();
+ test_kevent_socket_add();
+ test_kevent_socket_del();
+ test_kevent_socket_get();
+ test_kevent_socket_disable_and_enable();
+ test_kevent_socket_oneshot();
+ test_kevent_socket_clear();
+#if HAVE_EV_DISPATCH
+ test_kevent_socket_dispatch();
+#endif
+ test_kevent_socket_eof();
+ close(kqfd);
+}
diff --git a/tools/regression/kqueue/signal.c b/tools/regression/kqueue/signal.c
new file mode 100644
index 0000000..14e751d
--- /dev/null
+++ b/tools/regression/kqueue/signal.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+
+void
+test_kevent_signal_add(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ADD)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_signal_get(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, wait)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_kevent_signal_disable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_signal_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags = EV_ADD | EV_CLEAR;
+#if LIBKQUEUE
+ kev.data = 1; /* WORKAROUND */
+#else
+ kev.data = 2; // one extra time from test_kevent_signal_disable()
+#endif
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the watch */
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_signal_del(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Delete the kevent */
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+ success();
+}
+
+void
+test_kevent_signal_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Send another one and make sure we get no events */
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_evfilt_signal()
+{
+ kqfd = kqueue();
+ test_kevent_signal_add();
+ test_kevent_signal_del();
+ test_kevent_signal_get();
+ test_kevent_signal_disable();
+ test_kevent_signal_enable();
+ test_kevent_signal_oneshot();
+ close(kqfd);
+}
diff --git a/tools/regression/kqueue/timer.c b/tools/regression/kqueue/timer.c
new file mode 100644
index 0000000..766125d
--- /dev/null
+++ b/tools/regression/kqueue/timer.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+
+void
+test_kevent_timer_add(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_timer_del(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_timer_get(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, wait)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+static void
+test_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500,NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Retrieve the event */
+ kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Check if the event occurs again */
+ sleep(3);
+ test_no_kevents();
+
+
+ success();
+}
+
+static void
+test_periodic(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, periodic)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000,NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Retrieve the event */
+ kev.flags = EV_ADD | EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Check if the event occurs again */
+ sleep(1);
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the event */
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+static void
+disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_DISABLE and EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /* Add the watch and immediately disable it */
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 2000,NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ kev.flags = EV_DISABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ /* Re-enable and check again */
+ kev.flags = EV_ENABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_evfilt_timer()
+{
+ kqfd = kqueue();
+ test_kevent_timer_add();
+ test_kevent_timer_del();
+ test_kevent_timer_get();
+ test_oneshot();
+ test_periodic();
+ disable_and_enable();
+ close(kqfd);
+}
diff --git a/tools/regression/kqueue/user.c b/tools/regression/kqueue/user.c
new file mode 100644
index 0000000..9ba25f9
--- /dev/null
+++ b/tools/regression/kqueue/user.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+
+static void
+add_and_delete(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, EV_ADD and EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD, 0, 0, NULL);
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_DELETE, 0, 0, NULL);
+ test_no_kevents();
+
+ success();
+}
+
+static void
+event_wait(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, wait)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /* Add the event, and then trigger it */
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL);
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+
+ kev.fflags &= ~NOTE_FFCTRLMASK;
+ kev.fflags &= ~NOTE_TRIGGER;
+ kev.flags = EV_CLEAR;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ test_no_kevents();
+
+ success();
+}
+
+static void
+disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, EV_DISABLE and EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD, 0, 0, NULL);
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_DISABLE, 0, 0, NULL);
+
+ /* Trigger the event, but since it is disabled, nothing will happen. */
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ENABLE, 0, 0, NULL);
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+
+ kev.flags = EV_CLEAR;
+ kev.fflags &= ~NOTE_FFCTRLMASK;
+ kev.fflags &= ~NOTE_TRIGGER;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+static void
+oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 2, EVFILT_USER, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+
+ puts(" -- event 1");
+ kevent_add(kqfd, &kev, 2, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+
+ kev.flags = EV_ONESHOT;
+ kev.fflags &= ~NOTE_FFCTRLMASK;
+ kev.fflags &= ~NOTE_TRIGGER;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_evfilt_user()
+{
+ kqfd = kqueue();
+
+ add_and_delete();
+ event_wait();
+ disable_and_enable();
+ oneshot();
+ /* TODO: try different fflags operations */
+
+ close(kqfd);
+}
diff --git a/tools/regression/kqueue/vnode.c b/tools/regression/kqueue/vnode.c
new file mode 100644
index 0000000..dfa0b5e
--- /dev/null
+++ b/tools/regression/kqueue/vnode.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+int vnode_fd;
+
+void
+test_kevent_vnode_add(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_ADD)";
+ const char *testfile = "/tmp/kqueue-test.tmp";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ system("touch /tmp/kqueue-test.tmp");
+ vnode_fd = open(testfile, O_RDONLY);
+ if (vnode_fd < 0)
+ err(1, "open of %s", testfile);
+ else
+ printf("vnode_fd = %d\n", vnode_fd);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD,
+ NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME | NOTE_DELETE, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_vnode_note_delete(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_DELETE, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (unlink("/tmp/kqueue-test.tmp") < 0)
+ err(1, "unlink");
+
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_kevent_vnode_note_write(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_WRITE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_WRITE, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("echo hello >> /tmp/kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ /* BSD kqueue adds NOTE_EXTEND even though it was not requested */
+ /* BSD kqueue removes EV_ENABLE */
+ kev.flags &= ~EV_ENABLE; // XXX-FIXME compatibility issue
+ kev.fflags |= NOTE_EXTEND; // XXX-FIXME compatibility issue
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_kevent_vnode_note_attrib(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_ATTRIB)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("touch /tmp/kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_ATTRIB)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ success();
+}
+
+void
+test_kevent_vnode_note_rename(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_RENAME)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_RENAME, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("mv /tmp/kqueue-test.tmp /tmp/kqueue-test2.tmp") < 0)
+ err(1, "system");
+
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_RENAME)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ if (system("mv /tmp/kqueue-test2.tmp /tmp/kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ success();
+}
+
+void
+test_kevent_vnode_del(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_vnode_disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_DISABLE and EV_ENABLE)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /* Add the watch and immediately disable it */
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ kev.flags = EV_DISABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Confirm that the watch is disabled */
+ if (system("touch /tmp/kqueue-test.tmp") < 0)
+ err(1, "system");
+ test_no_kevents();
+
+ /* Re-enable and check again */
+ kev.flags = EV_ENABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ if (system("touch /tmp/kqueue-test.tmp") < 0)
+ err(1, "system");
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_ATTRIB)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ success();
+}
+
+#if HAVE_EV_DISPATCH
+void
+test_kevent_vnode_dispatch(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_DISPATCH)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_DISPATCH, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("touch /tmp/kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_ATTRIB)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ /* Confirm that the watch is disabled automatically */
+ puts("-- checking that watch is disabled");
+ if (system("touch /tmp/kqueue-test.tmp") < 0)
+ err(1, "system");
+ test_no_kevents();
+
+ /* Delete the watch */
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_DELETE, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "remove watch failed: %s", test_id);
+
+ success();
+}
+#endif /* HAVE_EV_DISPATCH */
+
+void
+test_evfilt_vnode()
+{
+ kqfd = kqueue();
+ test_kevent_vnode_add();
+ test_kevent_vnode_del();
+ test_kevent_vnode_disable_and_enable();
+#if HAVE_EV_DISPATCH
+ test_kevent_vnode_dispatch();
+#endif
+ test_kevent_vnode_note_write();
+ test_kevent_vnode_note_attrib();
+ test_kevent_vnode_note_rename();
+ test_kevent_vnode_note_delete();
+ close(kqfd);
+}
diff --git a/tools/regression/lib/libc/stdio/test-open_memstream.c b/tools/regression/lib/libc/stdio/test-open_memstream.c
index 1a168c6..fdbda30 100644
--- a/tools/regression/lib/libc/stdio/test-open_memstream.c
+++ b/tools/regression/lib/libc/stdio/test-open_memstream.c
@@ -1,5 +1,5 @@
/*-
- * Copyright (c) 2013 Advanced Computing Technologies LLC
+ * Copyright (c) 2013 Hudson River Trading LLC
* Written by: John H. Baldwin <jhb@FreeBSD.org>
* All rights reserved.
*
diff --git a/tools/regression/lib/libc/stdio/test-open_wmemstream.c b/tools/regression/lib/libc/stdio/test-open_wmemstream.c
index 4cd0ab9..2a90fcd 100644
--- a/tools/regression/lib/libc/stdio/test-open_wmemstream.c
+++ b/tools/regression/lib/libc/stdio/test-open_wmemstream.c
@@ -1,5 +1,5 @@
/*-
- * Copyright (c) 2013 Advanced Computing Technologies LLC
+ * Copyright (c) 2013 Hudson River Trading LLC
* Written by: John H. Baldwin <jhb@FreeBSD.org>
* All rights reserved.
*
diff --git a/tools/regression/mmap/Makefile b/tools/regression/mmap/Makefile
new file mode 100644
index 0000000..c9bb5c4
--- /dev/null
+++ b/tools/regression/mmap/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= mmap
+MAN=
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/mmap/mmap.c b/tools/regression/mmap/mmap.c
new file mode 100644
index 0000000..14c55a1
--- /dev/null
+++ b/tools/regression/mmap/mmap.c
@@ -0,0 +1,96 @@
+/*-
+ * Copyright (c) 2009 Simon L. Nielsen <simon@FreeBSD.org>,
+ * Bjoern A. Zeeb <bz@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/mman.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <err.h>
+
+const struct tests {
+ void *addr;
+ int ok[2]; /* Depending on security.bsd.map_at_zero {0, !=0}. */
+} tests[] = {
+ { (void *)0, { 0, 1 } }, /* Test sysctl. */
+ { (void *)1, { 0, 0 } },
+ { (void *)(PAGE_SIZE - 1), { 0, 0 } },
+ { (void *)PAGE_SIZE, { 1, 1 } },
+ { (void *)-1, { 0, 0 } },
+ { (void *)(-PAGE_SIZE), { 0, 0 } },
+ { (void *)(-1 - PAGE_SIZE), { 0, 0 } },
+ { (void *)(-1 - PAGE_SIZE - 1), { 0, 0 } },
+ { (void *)(0x1000 * PAGE_SIZE), { 1, 1 } },
+};
+
+int
+main(void)
+{
+ void *p;
+ size_t len;
+ int i, error, mib[3], map_at_zero;
+
+ error = 0;
+
+ /* Get the current sysctl value of security.bsd.map_at_zero. */
+ len = sizeof(mib) / sizeof(*mib);
+ if (sysctlnametomib("security.bsd.map_at_zero", mib, &len) == -1)
+ err(1, "sysctlnametomib(security.bsd.map_at_zero)");
+
+ len = sizeof(map_at_zero);
+ if (sysctl(mib, 3, &map_at_zero, &len, NULL, 0) == -1)
+ err(1, "sysctl(security.bsd.map_at_zero)");
+
+ /* Normalize to 0 or 1 for array access. */
+ map_at_zero = !!map_at_zero;
+
+ for (i=0; i < (sizeof(tests) / sizeof(*tests)); i++) {
+ p = mmap((void *)tests[i].addr, PAGE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_FIXED,
+ -1, 0);
+ if (p == MAP_FAILED) {
+ if (tests[i].ok[map_at_zero] != 0)
+ error++;
+ warnx("%s: mmap(%p, ...) failed.",
+ (tests[i].ok[map_at_zero] == 0) ? "OK " : "ERR",
+ tests[i].addr);
+ } else {
+ if (tests[i].ok[map_at_zero] != 1)
+ error++;
+ warnx("%s: mmap(%p, ...) succeeded: p=%p",
+ (tests[i].ok[map_at_zero] == 1) ? "OK " : "ERR",
+ tests[i].addr, p);
+ }
+ }
+
+ if (error)
+ err(1, "---\nERROR: %d unexpected results.", error);
+
+ return (error != 0);
+}
diff --git a/tools/regression/mqueue/Makefile b/tools/regression/mqueue/Makefile
new file mode 100644
index 0000000..a4f386b
--- /dev/null
+++ b/tools/regression/mqueue/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR=mqtest1 mqtest2 mqtest3 mqtest4 mqtest5
+
+.include <bsd.subdir.mk>
diff --git a/tools/regression/mqueue/mqtest1/Makefile b/tools/regression/mqueue/mqtest1/Makefile
new file mode 100644
index 0000000..3a50cee
--- /dev/null
+++ b/tools/regression/mqueue/mqtest1/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG=mqtest1
+DPADD= ${LIBRT}
+LDADD= -lrt
+MAN=
+DEBUG_FLAGS=-g
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/mqueue/mqtest1/mqtest1.c b/tools/regression/mqueue/mqtest1/mqtest1.c
new file mode 100644
index 0000000..25fc1ba
--- /dev/null
+++ b/tools/regression/mqueue/mqtest1/mqtest1.c
@@ -0,0 +1,52 @@
+/* $FreeBSD$ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+
+#define MQNAME "/mytstqueue1"
+
+int main()
+{
+ struct mq_attr attr, attr2;
+ struct sigevent sigev;
+ mqd_t mq;
+ int status;
+
+ attr.mq_maxmsg = 2;
+ attr.mq_msgsize = 100;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open");
+ status = mq_unlink(MQNAME);
+ if (status)
+ err(1, "mq_unlink");
+ status = mq_getattr(mq, &attr2);
+ if (status)
+ err(1, "mq_getattr");
+ if (attr.mq_maxmsg != attr2.mq_maxmsg)
+ err(1, "mq_maxmsg changed");
+ if (attr.mq_msgsize != attr2.mq_msgsize)
+ err(1, "mq_msgsize changed");
+
+ sigev.sigev_notify = SIGEV_SIGNAL;
+ sigev.sigev_signo = SIGRTMIN;
+ status = mq_notify(mq, &sigev);
+ if (status)
+ err(1, "mq_notify");
+ status = mq_notify(mq, &sigev);
+ if (status == 0)
+ err(1, "mq_notify 2");
+ else if (errno != EBUSY)
+ err(1, "mq_notify 3");
+ status = mq_notify(mq, NULL);
+ if (status)
+ err(1, "mq_notify 4");
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ return (0);
+}
diff --git a/tools/regression/mqueue/mqtest2/Makefile b/tools/regression/mqueue/mqtest2/Makefile
new file mode 100644
index 0000000..0709854
--- /dev/null
+++ b/tools/regression/mqueue/mqtest2/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG=mqtest2
+DPADD= ${LIBRT}
+LDADD= -lrt
+MAN=
+DEBUG_FLAGS=-g
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/mqueue/mqtest2/mqtest2.c b/tools/regression/mqueue/mqtest2/mqtest2.c
new file mode 100644
index 0000000..bfe6d97
--- /dev/null
+++ b/tools/regression/mqueue/mqtest2/mqtest2.c
@@ -0,0 +1,93 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MQNAME "/mytstqueue2"
+#define LOOPS 1000
+#define PRIO 10
+
+void alarmhandler(int sig)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int main()
+{
+ struct mq_attr attr;
+ mqd_t mq;
+ int status, pid;
+
+ mq_unlink(MQNAME);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr");
+ pid = fork();
+ if (pid == 0) { /* child */
+ int prio, j, i;
+ char *buf;
+
+ mq_close(mq);
+
+ signal(SIGALRM, alarmhandler);
+
+ mq = mq_open(MQNAME, O_RDWR);
+ if (mq == (mqd_t)-1)
+ err(1, "child: mq_open");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ alarm(3);
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "child: message data corrupted");
+ if (prio != PRIO)
+ err(4, "child: priority is incorrect: %d",
+ prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j, prio;
+
+ signal(SIGALRM, alarmhandler);
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ buf[i] = i;
+ alarm(3);
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status)
+ err(1, "mq_send");
+ }
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tools/regression/mqueue/mqtest3/Makefile b/tools/regression/mqueue/mqtest3/Makefile
new file mode 100644
index 0000000..514cbac
--- /dev/null
+++ b/tools/regression/mqueue/mqtest3/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG=mqtest3
+DPADD= ${LIBRT}
+LDADD= -lrt
+MAN=
+DEBUG_FLAGS=-g
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/mqueue/mqtest3/mqtest3.c b/tools/regression/mqueue/mqtest3/mqtest3.c
new file mode 100644
index 0000000..aa47ffa
--- /dev/null
+++ b/tools/regression/mqueue/mqtest3/mqtest3.c
@@ -0,0 +1,109 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MQNAME "/mytstqueue3"
+#define LOOPS 1000
+#define PRIO 10
+
+void sighandler(int sig)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int main()
+{
+ mqd_t mq;
+ int status;
+ struct mq_attr attr;
+ int pid;
+ fd_set set;
+
+ mq_unlink(MQNAME);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open()");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr()");
+
+ pid = fork();
+ if (pid == 0) { /* child */
+ int prio, j, i;
+ char *buf;
+
+ mq_close(mq);
+
+ signal(SIGALRM, sighandler);
+
+ mq = mq_open(MQNAME, O_RDWR);
+ if (mq == (mqd_t)-1)
+ err(1, "child process: mq_open");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ FD_ZERO(&set);
+ FD_SET(__mq_oshandle(mq), &set);
+ alarm(3);
+ status = select(__mq_oshandle(mq)+1, &set, NULL, NULL, NULL);
+ if (status != 1)
+ err(1, "child process: select()");
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child process: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "message data corrupted");
+ if (prio != PRIO)
+ err(4, "priority is incorrect: %d", prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j, prio;
+
+ signal(SIGALRM, sighandler);
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i) {
+ buf[i] = i;
+ }
+ alarm(3);
+ FD_ZERO(&set);
+ FD_SET(__mq_oshandle(mq), &set);
+ status = select(__mq_oshandle(mq)+1, NULL, &set, NULL, NULL);
+ if (status != 1)
+ err(1, "select()");
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status) {
+ kill(pid, SIGKILL);
+ err(2, "mq_send()");
+ }
+ }
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tools/regression/mqueue/mqtest4/Makefile b/tools/regression/mqueue/mqtest4/Makefile
new file mode 100644
index 0000000..781d76d
--- /dev/null
+++ b/tools/regression/mqueue/mqtest4/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG=mqtest4
+DPADD= ${LIBRT}
+LDADD= -lrt
+MAN=
+DEBUG_FLAGS=-g
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/mqueue/mqtest4/mqtest4.c b/tools/regression/mqueue/mqtest4/mqtest4.c
new file mode 100644
index 0000000..80a7f88
--- /dev/null
+++ b/tools/regression/mqueue/mqtest4/mqtest4.c
@@ -0,0 +1,115 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MQNAME "/mytstqueue4"
+#define LOOPS 1000
+#define PRIO 10
+
+void sighandler(int sig)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int main()
+{
+ mqd_t mq;
+ int status;
+ struct mq_attr attr;
+ int pid;
+ fd_set set;
+ int kq;
+ struct kevent kev;
+
+ mq_unlink(MQNAME);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t) -1)
+ err(1, "mq_open()");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr()");
+ pid = fork();
+ if (pid == 0) { /* child */
+ int prio, j, i;
+ char *buf;
+
+ mq_close(mq);
+ kq = kqueue();
+ mq = mq_open(MQNAME, O_RDWR);
+ if (mq == (mqd_t)-1)
+ err(1, "child: mq_open");
+ EV_SET(&kev, __mq_oshandle(mq), EVFILT_READ, EV_ADD, 0, 0, 0);
+ status = kevent(kq, &kev, 1, NULL, 0, NULL);
+ if (status == -1)
+ err(1, "child: kevent");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ alarm(3);
+ status = kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (status != 1)
+ err(1, "child: kevent 2");
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "child: message data corrupted");
+ if (prio != PRIO)
+ err(4, "child: priority is incorrect: %d",
+ prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j, prio;
+
+ signal(SIGALRM, sighandler);
+ kq = kqueue();
+ EV_SET(&kev, __mq_oshandle(mq), EVFILT_WRITE, EV_ADD, 0, 0, 0);
+ status = kevent(kq, &kev, 1, NULL, 0, NULL);
+ if (status == -1)
+ err(1, "kevent");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i) {
+ buf[i] = i;
+ }
+ alarm(3);
+ status = kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (status != 1)
+ err(1, "child: kevent 2");
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status) {
+ err(2, "mq_send()");
+ }
+ }
+ free(buf);
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tools/regression/mqueue/mqtest5/Makefile b/tools/regression/mqueue/mqtest5/Makefile
new file mode 100644
index 0000000..d94e541
--- /dev/null
+++ b/tools/regression/mqueue/mqtest5/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG=mqtest5
+DPADD= ${LIBRT}
+LDADD= -lrt
+MAN=
+DEBUG_FLAGS=-g
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/mqueue/mqtest5/mqtest5.c b/tools/regression/mqueue/mqtest5/mqtest5.c
new file mode 100644
index 0000000..354a7bb
--- /dev/null
+++ b/tools/regression/mqueue/mqtest5/mqtest5.c
@@ -0,0 +1,122 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MQNAME "/mytstqueue5"
+#define LOOPS 1000
+#define PRIO 10
+
+void sighandler(int sig)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int main()
+{
+ mqd_t mq;
+ int status;
+ struct mq_attr attr;
+ int pid;
+ sigset_t set;
+ struct sigaction sa;
+ siginfo_t info;
+
+ mq_unlink(MQNAME);
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGRTMIN);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = (void *) SIG_DFL;
+ sigaction(SIGRTMIN, &sa, NULL);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open()");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr()");
+ pid = fork();
+ if (pid == 0) { /* child */
+ int prio, j, i;
+ char *buf;
+ struct sigevent sigev;
+
+ signal(SIGALRM, sighandler);
+
+ sigev.sigev_notify = SIGEV_SIGNAL;
+ sigev.sigev_signo = SIGRTMIN;
+ sigev.sigev_value.sival_int = 2;
+
+ mq_close(mq);
+ mq = mq_open(MQNAME, O_RDWR | O_NONBLOCK);
+ if (mq == (mqd_t)-1)
+ err(1, "child: mq_open");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ alarm(3);
+ status = mq_notify(mq, &sigev);
+ if (status)
+ err(1, "child: mq_notify");
+ status = sigwaitinfo(&set, &info);
+ if (status == -1)
+ err(1, "child: sigwaitinfo");
+ if (info.si_value.sival_int != 2)
+ err(1, "child: sival_int");
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "child: message data corrupted");
+ if (prio != PRIO)
+ err(4, "child: priority is incorrect: %d",
+ prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j, prio;
+
+ signal(SIGALRM, sighandler);
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i) {
+ buf[i] = i;
+ }
+ alarm(3);
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status) {
+ kill(pid, SIGKILL);
+ err(2, "mq_send()");
+ }
+ }
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tools/regression/netinet/arphold/arphold.c b/tools/regression/netinet/arphold/arphold.c
index 8d694fe..417a2d8 100644
--- a/tools/regression/netinet/arphold/arphold.c
+++ b/tools/regression/netinet/arphold/arphold.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Advanced Computing Technologies LLC
+ * Copyright (c) 2010 Hudson River Trading LLC
* Written by George Neville-Neil gnn@freebsd.org
* All rights reserved.
*
diff --git a/tools/regression/posixshm/Makefile b/tools/regression/posixshm/Makefile
new file mode 100644
index 0000000..5ef70c9
--- /dev/null
+++ b/tools/regression/posixshm/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= posixshm
+SRCS= posixshm.c test.c
+MAN=
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/posixshm/posixshm.c b/tools/regression/posixshm/posixshm.c
new file mode 100644
index 0000000..6f4c306
--- /dev/null
+++ b/tools/regression/posixshm/posixshm.c
@@ -0,0 +1,627 @@
+/*-
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "test.h"
+
+#define TEST_PATH "/tmp/posixshm_regression_test"
+
+/*
+ * Attempt a shm_open() that should fail with an expected error of 'error'.
+ */
+static void
+shm_open_should_fail(const char *path, int flags, mode_t mode, int error)
+{
+ int fd;
+
+ fd = shm_open(path, flags, mode);
+ if (fd >= 0) {
+ fail_err("shm_open() didn't fail");
+ close(fd);
+ return;
+ }
+ if (errno != error) {
+ fail_errno("shm_open");
+ return;
+ }
+ pass();
+}
+
+/*
+ * Attempt a shm_unlink() that should fail with an expected error of 'error'.
+ */
+static void
+shm_unlink_should_fail(const char *path, int error)
+{
+
+ if (shm_unlink(path) >= 0) {
+ fail_err("shm_unlink() didn't fail");
+ return;
+ }
+ if (errno != error) {
+ fail_errno("shm_unlink");
+ return;
+ }
+ pass();
+}
+
+/*
+ * Open the test object and write '1' to the first byte. Returns valid fd
+ * on success and -1 on failure.
+ */
+static int
+scribble_object(void)
+{
+ char *page;
+ int fd;
+
+ fd = shm_open(TEST_PATH, O_CREAT | O_EXCL | O_RDWR, 0777);
+ if (fd < 0 && errno == EEXIST) {
+ if (shm_unlink(TEST_PATH) < 0) {
+ fail_errno("shm_unlink");
+ return (-1);
+ }
+ fd = shm_open(TEST_PATH, O_CREAT | O_EXCL | O_RDWR, 0777);
+ }
+ if (fd < 0) {
+ fail_errno("shm_open");
+ return (-1);
+ }
+ if (ftruncate(fd, getpagesize()) < 0) {
+ fail_errno("ftruncate");
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return (-1);
+ }
+
+ page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd,
+ 0);
+ if (page == MAP_FAILED) {
+ fail_errno("mmap");
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return (-1);
+ }
+
+ page[0] = '1';
+
+ if (munmap(page, getpagesize()) < 0) {
+ fail_errno("munmap");
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return (-1);
+ }
+
+ return (fd);
+}
+
+static void
+remap_object(void)
+{
+ char *page;
+ int fd;
+
+ fd = scribble_object();
+ if (fd < 0)
+ return;
+
+ page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd,
+ 0);
+ if (page == MAP_FAILED) {
+ fail_errno("mmap(2)");
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return;
+ }
+
+ if (page[0] != '1') {
+ fail_err("missing data");
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return;
+ }
+
+ close(fd);
+ if (munmap(page, getpagesize()) < 0) {
+ fail_errno("munmap");
+ shm_unlink(TEST_PATH);
+ return;
+ }
+
+ if (shm_unlink(TEST_PATH) < 0) {
+ fail_errno("shm_unlink");
+ return;
+ }
+
+ pass();
+}
+TEST(remap_object, "remap object");
+
+static void
+reopen_object(void)
+{
+ char *page;
+ int fd;
+
+ fd = scribble_object();
+ if (fd < 0)
+ return;
+ close(fd);
+
+ fd = shm_open(TEST_PATH, O_RDONLY, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open(2)");
+ shm_unlink(TEST_PATH);
+ return;
+ }
+ page = mmap(0, getpagesize(), PROT_READ, MAP_SHARED, fd, 0);
+ if (page == MAP_FAILED) {
+ fail_errno("mmap(2)");
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return;
+ }
+
+ if (page[0] != '1') {
+ fail_err("missing data");
+ munmap(page, getpagesize());
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return;
+ }
+
+ munmap(page, getpagesize());
+ close(fd);
+ shm_unlink(TEST_PATH);
+ pass();
+}
+TEST(reopen_object, "reopen object");
+
+static void
+readonly_mmap_write(void)
+{
+ char *page;
+ int fd;
+
+ fd = shm_open(TEST_PATH, O_RDONLY | O_CREAT, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open");
+ return;
+ }
+
+ /* PROT_WRITE should fail with EACCES. */
+ page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd,
+ 0);
+ if (page != MAP_FAILED) {
+ fail_err("mmap(PROT_WRITE) succeeded");
+ munmap(page, getpagesize());
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return;
+ }
+ if (errno != EACCES) {
+ fail_errno("mmap");
+ close(fd);
+ shm_unlink(TEST_PATH);
+ return;
+ }
+
+ close(fd);
+ shm_unlink(TEST_PATH);
+ pass();
+}
+TEST(readonly_mmap_write, "RDONLY object");
+
+static void
+open_after_unlink(void)
+{
+ int fd;
+
+ fd = shm_open(TEST_PATH, O_RDONLY | O_CREAT, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open(1)");
+ return;
+ }
+ close(fd);
+
+ if (shm_unlink(TEST_PATH) < 0) {
+ fail_errno("shm_unlink");
+ return;
+ }
+
+ shm_open_should_fail(TEST_PATH, O_RDONLY, 0777, ENOENT);
+}
+TEST(open_after_unlink, "open after unlink");
+
+static void
+open_invalid_path(void)
+{
+
+ shm_open_should_fail("blah", O_RDONLY, 0777, EINVAL);
+}
+TEST(open_invalid_path, "open invalid path");
+
+static void
+open_write_only(void)
+{
+
+ shm_open_should_fail(TEST_PATH, O_WRONLY, 0777, EINVAL);
+}
+TEST(open_write_only, "open with O_WRONLY");
+
+static void
+open_extra_flags(void)
+{
+
+ shm_open_should_fail(TEST_PATH, O_RDONLY | O_DIRECT, 0777, EINVAL);
+}
+TEST(open_extra_flags, "open with extra flags");
+
+static void
+open_anon(void)
+{
+ int fd;
+
+ fd = shm_open(SHM_ANON, O_RDWR, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open");
+ return;
+ }
+ close(fd);
+ pass();
+}
+TEST(open_anon, "open anonymous object");
+
+static void
+open_anon_readonly(void)
+{
+
+ shm_open_should_fail(SHM_ANON, O_RDONLY, 0777, EINVAL);
+}
+TEST(open_anon_readonly, "open SHM_ANON with O_RDONLY");
+
+static void
+open_bad_path_pointer(void)
+{
+
+ shm_open_should_fail((char *)1024, O_RDONLY, 0777, EFAULT);
+}
+TEST(open_bad_path_pointer, "open bad path pointer");
+
+static void
+open_path_too_long(void)
+{
+ char *page;
+
+ page = malloc(MAXPATHLEN + 1);
+ memset(page, 'a', MAXPATHLEN);
+ page[MAXPATHLEN] = '\0';
+ shm_open_should_fail(page, O_RDONLY, 0777, ENAMETOOLONG);
+ free(page);
+}
+TEST(open_path_too_long, "open pathname too long");
+
+static void
+open_nonexisting_object(void)
+{
+
+ shm_open_should_fail("/notreallythere", O_RDONLY, 0777, ENOENT);
+}
+TEST(open_nonexisting_object, "open nonexistent object");
+
+static void
+exclusive_create_existing_object(void)
+{
+ int fd;
+
+ fd = shm_open("/tmp/notreallythere", O_RDONLY | O_CREAT, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open(O_CREAT)");
+ return;
+ }
+ close(fd);
+
+ shm_open_should_fail("/tmp/notreallythere", O_RDONLY | O_CREAT | O_EXCL,
+ 0777, EEXIST);
+
+ shm_unlink("/tmp/notreallythere");
+}
+TEST(exclusive_create_existing_object, "O_EXCL of existing object");
+
+static void
+trunc_resets_object(void)
+{
+ struct stat sb;
+ int fd;
+
+ /* Create object and set size to 1024. */
+ fd = shm_open(TEST_PATH, O_RDWR | O_CREAT, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open(1)");
+ return;
+ }
+ if (ftruncate(fd, 1024) < 0) {
+ fail_errno("ftruncate");
+ close(fd);
+ return;
+ }
+ if (fstat(fd, &sb) < 0) {
+ fail_errno("fstat(1)");
+ close(fd);
+ return;
+ }
+ if (sb.st_size != 1024) {
+ fail_err("size %d != 1024", (int)sb.st_size);
+ close(fd);
+ return;
+ }
+ close(fd);
+
+ /* Open with O_TRUNC which should reset size to 0. */
+ fd = shm_open(TEST_PATH, O_RDWR | O_TRUNC, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open(2)");
+ return;
+ }
+ if (fstat(fd, &sb) < 0) {
+ fail_errno("fstat(2)");
+ close(fd);
+ return;
+ }
+ if (sb.st_size != 0) {
+ fail_err("size after O_TRUNC %d != 0", (int)sb.st_size);
+ close(fd);
+ return;
+ }
+ close(fd);
+ if (shm_unlink(TEST_PATH) < 0) {
+ fail_errno("shm_unlink");
+ return;
+ }
+ pass();
+}
+TEST(trunc_resets_object, "O_TRUNC resets size");
+
+static void
+unlink_bad_path_pointer(void)
+{
+
+ shm_unlink_should_fail((char *)1024, EFAULT);
+}
+TEST(unlink_bad_path_pointer, "unlink bad path pointer");
+
+static void
+unlink_path_too_long(void)
+{
+ char *page;
+
+ page = malloc(MAXPATHLEN + 1);
+ memset(page, 'a', MAXPATHLEN);
+ page[MAXPATHLEN] = '\0';
+ shm_unlink_should_fail(page, ENAMETOOLONG);
+ free(page);
+}
+TEST(unlink_path_too_long, "unlink pathname too long");
+
+static void
+test_object_resize(void)
+{
+ pid_t pid;
+ struct stat sb;
+ char *page;
+ int fd, status;
+
+ /* Start off with a size of a single page. */
+ fd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0777);
+ if (fd < 0) {
+ fail_errno("shm_open");
+ return;
+ }
+ if (ftruncate(fd, getpagesize()) < 0) {
+ fail_errno("ftruncate(1)");
+ close(fd);
+ return;
+ }
+ if (fstat(fd, &sb) < 0) {
+ fail_errno("fstat(1)");
+ close(fd);
+ return;
+ }
+ if (sb.st_size != getpagesize()) {
+ fail_err("first resize failed");
+ close(fd);
+ return;
+ }
+
+ /* Write a '1' to the first byte. */
+ page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd,
+ 0);
+ if (page == MAP_FAILED) {
+ fail_errno("mmap(1)");
+ close(fd);
+ return;
+ }
+
+ page[0] = '1';
+
+ if (munmap(page, getpagesize()) < 0) {
+ fail_errno("munmap(1)");
+ close(fd);
+ return;
+ }
+
+ /* Grow the object to 2 pages. */
+ if (ftruncate(fd, getpagesize() * 2) < 0) {
+ fail_errno("ftruncate(2)");
+ close(fd);
+ return;
+ }
+ if (fstat(fd, &sb) < 0) {
+ fail_errno("fstat(2)");
+ close(fd);
+ return;
+ }
+ if (sb.st_size != getpagesize() * 2) {
+ fail_err("second resize failed");
+ close(fd);
+ return;
+ }
+
+ /* Check for '1' at the first byte. */
+ page = mmap(0, getpagesize() * 2, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0);
+ if (page == MAP_FAILED) {
+ fail_errno("mmap(2)");
+ close(fd);
+ return;
+ }
+
+ if (page[0] != '1') {
+ fail_err("missing data at 0");
+ close(fd);
+ return;
+ }
+
+ /* Write a '2' at the start of the second page. */
+ page[getpagesize()] = '2';
+
+ /* Shrink the object back to 1 page. */
+ if (ftruncate(fd, getpagesize()) < 0) {
+ fail_errno("ftruncate(3)");
+ close(fd);
+ return;
+ }
+ if (fstat(fd, &sb) < 0) {
+ fail_errno("fstat(3)");
+ close(fd);
+ return;
+ }
+ if (sb.st_size != getpagesize()) {
+ fail_err("third resize failed");
+ close(fd);
+ return;
+ }
+
+ /*
+ * Fork a child process to make sure the second page is no
+ * longer valid.
+ */
+ pid = fork();
+ if (pid < 0) {
+ fail_errno("fork");
+ close(fd);
+ return;
+ }
+
+ if (pid == 0) {
+ struct rlimit lim;
+ char c;
+
+ /* Don't generate a core dump. */
+ getrlimit(RLIMIT_CORE, &lim);
+ lim.rlim_cur = 0;
+ setrlimit(RLIMIT_CORE, &lim);
+
+ /*
+ * The previous ftruncate(2) shrunk the backing object
+ * so that this address is no longer valid, so reading
+ * from it should trigger a SIGSEGV.
+ */
+ c = page[getpagesize()];
+ fprintf(stderr, "child: page 1: '%c'\n", c);
+ exit(0);
+ }
+ if (wait(&status) < 0) {
+ fail_errno("wait");
+ close(fd);
+ return;
+ }
+ if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGSEGV) {
+ fail_err("child terminated with status %x", status);
+ close(fd);
+ return;
+ }
+
+ /* Grow the object back to 2 pages. */
+ if (ftruncate(fd, getpagesize() * 2) < 0) {
+ fail_errno("ftruncate(4)");
+ close(fd);
+ return;
+ }
+ if (fstat(fd, &sb) < 0) {
+ fail_errno("fstat(4)");
+ close(fd);
+ return;
+ }
+ if (sb.st_size != getpagesize() * 2) {
+ fail_err("second resize failed");
+ close(fd);
+ return;
+ }
+
+ /*
+ * Note that the mapping at 'page' for the second page is
+ * still valid, and now that the shm object has been grown
+ * back up to 2 pages, there is now memory backing this page
+ * so the read will work. However, the data should be zero
+ * rather than '2' as the old data was thrown away when the
+ * object was shrunk and the new pages when an object are
+ * grown are zero-filled.
+ */
+ if (page[getpagesize()] != 0) {
+ fail_err("invalid data at %d", getpagesize());
+ close(fd);
+ return;
+ }
+
+ close(fd);
+ pass();
+}
+TEST(test_object_resize, "object resize");
+
+int
+main(int argc, char *argv[])
+{
+
+ run_tests();
+ return (0);
+}
diff --git a/tools/regression/posixshm/posixshm.t b/tools/regression/posixshm/posixshm.t
new file mode 100644
index 0000000..a2159ac
--- /dev/null
+++ b/tools/regression/posixshm/posixshm.t
@@ -0,0 +1,5 @@
+#!/bin/sh
+#
+# $FreeBSD$
+
+./posixshm
diff --git a/tools/regression/posixshm/test.c b/tools/regression/posixshm/test.c
new file mode 100644
index 0000000..8583a0d
--- /dev/null
+++ b/tools/regression/posixshm/test.c
@@ -0,0 +1,128 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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$");
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "test.h"
+
+static int test_index;
+static struct regression_test *test;
+static int test_acknowleged;
+
+SET_DECLARE(regression_tests_set, struct regression_test);
+
+/*
+ * Outputs a test summary of the following:
+ *
+ * <status> <test #> [name] [# <fmt> [fmt args]]
+ */
+static void
+vprint_status(const char *status, const char *fmt, va_list ap)
+{
+
+ printf("%s %d", status, test_index);
+ if (test->rt_name)
+ printf(" - %s", test->rt_name);
+ if (fmt) {
+ printf(" # ");
+ vprintf(fmt, ap);
+ }
+ printf("\n");
+ test_acknowleged = 1;
+}
+
+static void
+print_status(const char *status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vprint_status(status, fmt, ap);
+ va_end(ap);
+}
+
+void
+pass(void)
+{
+
+ print_status("ok", NULL);
+}
+
+void
+fail(void)
+{
+
+ print_status("not ok", NULL);
+}
+
+void
+fail_err(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vprint_status("not ok", fmt, ap);
+ va_end(ap);
+}
+
+void
+skip(const char *reason)
+{
+
+ print_status("ok", "skip %s", reason);
+}
+
+void
+todo(const char *reason)
+{
+
+ print_status("not ok", "TODO %s", reason);
+}
+
+void
+run_tests(void)
+{
+ struct regression_test **testp;
+
+ printf("1..%td\n", SET_COUNT(regression_tests_set));
+ test_index = 1;
+ SET_FOREACH(testp, regression_tests_set) {
+ test_acknowleged = 0;
+ test = *testp;
+ test->rt_function();
+ if (!test_acknowleged)
+ print_status("not ok", "unknown status");
+ test_index++;
+ }
+}
diff --git a/tools/regression/posixshm/test.h b/tools/regression/posixshm/test.h
new file mode 100644
index 0000000..505679b
--- /dev/null
+++ b/tools/regression/posixshm/test.h
@@ -0,0 +1,59 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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$
+ */
+
+#ifndef __TEST_H__
+#define __TEST_H__
+
+#include <sys/linker_set.h>
+
+struct regression_test {
+ void (*rt_function)(void);
+ const char *rt_name;
+};
+
+#define TEST(function, name) \
+ static struct regression_test _regtest_##function = { \
+ (function), \
+ (name) \
+ }; \
+ DATA_SET(regression_tests_set, _regtest_##function)
+
+void fail(void);
+void fail_err(const char *fmt, ...);
+void pass(void);
+void run_tests(void);
+void skip(const char *reason);
+void todo(const char *reason);
+
+#define fail_errno(tag) fail_err("%s: %s", (tag), strerror(errno))
+
+#endif /* !__TEST_H__ */
diff --git a/tools/regression/sockets/accept_fd_leak/Makefile b/tools/regression/sockets/accept_fd_leak/Makefile
new file mode 100644
index 0000000..e9bfba6
--- /dev/null
+++ b/tools/regression/sockets/accept_fd_leak/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= accept_fd_leak
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/accept_fd_leak/accept_fd_leak.c b/tools/regression/sockets/accept_fd_leak/accept_fd_leak.c
new file mode 100644
index 0000000..659c22d
--- /dev/null
+++ b/tools/regression/sockets/accept_fd_leak/accept_fd_leak.c
@@ -0,0 +1,215 @@
+/*-
+ * Copyright (c) 2004 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/param.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define BIND_ATTEMPTS 10
+#define LOOPS 500
+#define NUM_ATTEMPTS 1000
+
+static volatile int quit;
+
+static void
+child_died(int sig __unused)
+{
+
+ quit = 1;
+}
+
+/*
+ * This test is intended to detect a leak of a file descriptor in the process
+ * following a failed non-blocking accept. It measures an available fd
+ * baseline, then performs 1000 failing accepts, then checks to see what the
+ * next fd is. It relies on sequential fd allocation, and will test for it
+ * briefly before beginning (not 100% reliable, but a good start).
+ */
+int
+main(void)
+{
+ struct sockaddr_in sin;
+ socklen_t size;
+ pid_t child;
+ int fd1, fd2, fd3, i, listen_port, s, status;
+
+ printf("1..2\n");
+
+ /*
+ * Check for sequential fd allocation, and give up early if not.
+ */
+ fd1 = dup(STDIN_FILENO);
+ fd2 = dup(STDIN_FILENO);
+ if (fd2 != fd1 + 1)
+ errx(-1, "Non-sequential fd allocation\n");
+
+ s = socket(PF_INET, SOCK_STREAM, 0);
+ if (s == -1)
+ errx(-1, "socket: %s", strerror(errno));
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ srandomdev();
+
+ for (i = 0; i < BIND_ATTEMPTS; i++) {
+ /* Pick a random unprivileged port 1025-65535 */
+ listen_port = MAX((int)random() % 65535, 1025);
+ sin.sin_port = htons(listen_port);
+ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0)
+ break;
+ warn("bind with %d failed", listen_port);
+ usleep(1000);
+ }
+ if (i >= BIND_ATTEMPTS) {
+ printf("Bail out!\n");
+ exit(1);
+ }
+
+ if (listen(s, -1) != 0)
+ errx(-1, "listen: %s", strerror(errno));
+
+ i = fcntl(s, F_GETFL);
+ if (i == -1)
+ errx(-1, "ioctl(F_GETFL): %s", strerror(errno));
+ i |= O_NONBLOCK;
+ if (fcntl(s, F_SETFL, i) != 0)
+ errx(-1, "ioctl(F_SETFL): %s", strerror(errno));
+ i = fcntl(s, F_GETFL);
+ if (i == -1)
+ errx(-1, "ioctl(F_GETFL): %s", strerror(errno));
+ if ((i & O_NONBLOCK) != O_NONBLOCK)
+ errx(-1, "Failed to set O_NONBLOCK (i=0x%x)\n", i);
+
+ for (i = 0; i < LOOPS; i++) {
+ size = sizeof(sin);
+ if (accept(s, (struct sockaddr *)&sin, &size) != -1)
+ errx(-1, "accept succeeded\n");
+ if (errno != EAGAIN)
+ errx(-1, "accept: %s", strerror(errno));
+ }
+
+ /*
+ * Allocate a file descriptor and make sure it's fd2+2. 2 because
+ * we allocate an fd for the socket.
+ */
+ fd3 = dup(STDIN_FILENO);
+ if (fd3 != fd2 + 2)
+ printf("not ok 1 - (%d, %d, %d)\n", fd1, fd2, fd3);
+ else
+ printf("ok 1\n");
+
+ /*
+ * Try failing accept's w/o non-blocking where the destination
+ * address pointer is invalid.
+ */
+ close(fd3);
+ signal(SIGCHLD, child_died);
+ child = fork();
+ if (child < 0)
+ errx(-1, "fork: %s", strerror(errno));
+
+ /*
+ * Child process does `NUM_ATTEMPTS` connects.
+ */
+ if (child == 0) {
+ close(fd1);
+ close(fd2);
+ close(s);
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(listen_port);
+
+ for (i = 0; i < NUM_ATTEMPTS; i++) {
+ s = socket(PF_INET, SOCK_STREAM, 0);
+ if (s == -1)
+ errx(-1, "socket: %s", strerror(errno));
+ if (connect(s, (struct sockaddr *)&sin,
+ sizeof(sin)) < 0)
+ errx(-1, "connect: %s", strerror(errno));
+ close(s);
+ }
+ _exit(0);
+ }
+
+ /* Reset back to a blocking socket. */
+ i = fcntl(s, F_GETFL);
+ if (i == -1)
+ errx(-1, "ioctl(F_GETFL): %s", strerror(errno));
+ i &= ~O_NONBLOCK;
+ if (fcntl(s, F_SETFL, i) != 0)
+ errx(-1, "ioctl(F_SETFL): %s", strerror(errno));
+ i = fcntl(s, F_GETFL);
+ if (i == -1)
+ errx(-1, "ioctl(F_GETFL): %s", strerror(errno));
+ if (i & O_NONBLOCK)
+ errx(-1, "Failed to clear O_NONBLOCK (i=0x%x)\n", i);
+
+ /* Do `NUM_ATTEMPTS` accepts with an invalid pointer. */
+ for (i = 0; !quit && i < NUM_ATTEMPTS; i++) {
+ size = sizeof(sin);
+ if (accept(s, (struct sockaddr *)(uintptr_t)(0x100),
+ &size) != -1)
+ errx(-1, "accept succeeded\n");
+ if (errno != EFAULT)
+ errx(-1, "accept: %s", strerror(errno));
+ }
+
+ if (waitpid(child, &status, 0) < 0)
+ errx(-1, "waitpid: %s", strerror(errno));
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ warnx("child process died");
+
+ /*
+ * Allocate a file descriptor and make sure it's fd2+2. 2 because
+ * we allocate an fd for the socket.
+ */
+ fd3 = dup(STDIN_FILENO);
+ if (fd3 != fd2 + 2)
+ printf("not ok 2 - (%d, %d, %d)\n", fd1, fd2, fd3);
+ else
+ printf("ok 2\n");
+
+ return (0);
+}
diff --git a/tools/regression/sockets/accept_fd_leak/accept_fd_leak.t b/tools/regression/sockets/accept_fd_leak/accept_fd_leak.t
new file mode 100644
index 0000000..8bdfd03
--- /dev/null
+++ b/tools/regression/sockets/accept_fd_leak/accept_fd_leak.t
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+executable=`basename $0 .t`
+
+make $executable 2>&1 > /dev/null
+
+exec ./$executable
diff --git a/tools/regression/sockets/accf_data_attach/Makefile b/tools/regression/sockets/accf_data_attach/Makefile
new file mode 100644
index 0000000..4d33728
--- /dev/null
+++ b/tools/regression/sockets/accf_data_attach/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= accf_data_attach
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/accf_data_attach/accf_data_attach.c b/tools/regression/sockets/accf_data_attach/accf_data_attach.c
new file mode 100644
index 0000000..59ea68c
--- /dev/null
+++ b/tools/regression/sockets/accf_data_attach/accf_data_attach.c
@@ -0,0 +1,216 @@
+/*-
+ * Copyright (c) 2004 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/socket.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ACCF_NAME "dataready"
+
+/*
+ * A number of small tests to confirm that attaching ACCF_DATA accept filters
+ * to inet4 ports works as expected. We test:
+ *
+ * - That no accept filter is attached on a newly created socket.
+ * - That bind() has no affect on the accept filter state.
+ * - That we can't attach an accept filter to a socket that isn't in the
+ * listen state.
+ * - That after we fail to attach the filter, querying the kernel shows no
+ * filter attached.
+ * - That we can attach an accept filter to a socket that is in the listen
+ * state.
+ * - That once an accept filter is attached, we can query to make sure it is
+ * attached.
+ * - That once an accept filter is attached, we can remove it and query to
+ * make sure it is removed.
+ */
+int
+main(void)
+{
+ struct accept_filter_arg afa;
+ struct sockaddr_in sin;
+ socklen_t len;
+ int lso, ret;
+
+ printf("1..11\n");
+
+ /*
+ * Step 0. Open socket().
+ */
+ lso = socket(PF_INET, SOCK_STREAM, 0);
+ if (lso == -1)
+ errx(-1, "not ok 1 - socket: %s", strerror(errno));
+ printf("ok 1 - socket\n");
+
+ /*
+ * Step 1. After socket(). Should return EINVAL, since no accept
+ * filter should be attached.
+ */
+ bzero(&afa, sizeof(afa));
+ len = sizeof(afa);
+ ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len);
+ if (ret != -1)
+ errx(-1, "not ok 2 - getsockopt() after socket() succeeded");
+ if (errno != EINVAL)
+ errx(-1, "not ok 2 - getsockopt() after socket() failed with "
+ "%d (%s)", errno, strerror(errno));
+ printf("ok 2 - getsockopt\n");
+
+ /*
+ * Step 2. Bind(). Ideally this will succeed.
+ */
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(8080);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (bind(lso, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ errx(-1, "not ok 3 - bind %s", strerror(errno));
+ printf("ok 3 - bind\n");
+
+ /*
+ * Step 3: After bind(). getsockopt() should return EINVAL, since no
+ * accept filter should be attached.
+ */
+ len = sizeof(afa);
+ ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len);
+ if (ret != -1)
+ errx(-1, "not ok 4 - getsockopt() after bind() succeeded");
+ if (errno != EINVAL)
+ errx(-1, "not ok 4 - getsockopt() after bind() failed with %d (%s)",
+ errno, strerror(errno));
+ printf("ok 4 - getsockopt\n");
+
+ /*
+ * Step 4: Setsockopt() before listen(). Should fail, since it's not
+ * yet a listen() socket.
+ */
+ bzero(&afa, sizeof(afa));
+ strcpy(afa.af_name, ACCF_NAME);
+ ret = setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa));
+ if (ret == 0)
+ errx(-1, "not ok 5 - setsockopt() before listen() succeeded");
+ printf("ok 5 - setsockopt\n");
+
+ /*
+ * Step 5: Getsockopt() after pre-listen() setsockopt(). Should
+ * fail with EINVAL, since setsockopt() should have failed.
+ */
+ len = sizeof(afa);
+ ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len);
+ if (ret == 0)
+ errx(-1, "not ok 6 - getsockopt() after pre-listen() setsockopt() "
+ "succeeded");
+ if (errno != EINVAL)
+ errx(-1, "not ok 6 - pre-listen() getsockopt() failed with %d (%s)",
+ errno, strerror(errno));
+ printf("ok 6 - getsockopt\n");
+
+ /*
+ * Step 6: listen().
+ */
+ if (listen(lso, -1) < 0)
+ errx(-1, "not ok 7 - listen: %s", strerror(errno));
+ printf("ok 7 - listen\n");
+
+ /*
+ * Step 7: Getsockopt() after listen(). Should fail with EINVAL,
+ * since we have not installed accept filter yet.
+ */
+ len = sizeof(afa);
+ ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len);
+ if (ret == 0)
+ errx(-1, "not ok 8 - getsockopt() after listen() but before "
+ "setsockopt() succeeded");
+ if (errno != EINVAL)
+ errx(-1, "not ok 8 - getsockopt() after listen() but before "
+ "setsockopt() failed with %d (%s)", errno, strerror(errno));
+ printf("ok 8 - getsockopt\n");
+
+ /*
+ * Step 8: After listen(). This call to setsockopt() should succeed.
+ */
+ bzero(&afa, sizeof(afa));
+ strcpy(afa.af_name, ACCF_NAME);
+ ret = setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa));
+ if (ret != 0)
+ errx(-1, "not ok 9 - setsockopt() after listen() failed with %d "
+ "(%s)", errno, strerror(errno));
+ printf("ok 9 - setsockopt\n");
+
+ /*
+ * Step 9: After setsockopt(). Should succeed and identify
+ * ACCF_NAME.
+ */
+ bzero(&afa, sizeof(afa));
+ len = sizeof(afa);
+ ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len);
+ if (ret != 0)
+ errx(-1, "not ok 10 - getsockopt() after listen() setsockopt() "
+ "failed with %d (%s)", errno, strerror(errno));
+ if (len != sizeof(afa))
+ errx(-1, "not ok 10 - getsockopt() after setsockopet() after "
+ "listen() returned wrong size (got %d expected %zd)", len,
+ sizeof(afa));
+ if (strcmp(afa.af_name, ACCF_NAME) != 0)
+ errx(-1, "not ok 10 - getsockopt() after setsockopt() after "
+ "listen() mismatch (got %s expected %s)", afa.af_name,
+ ACCF_NAME);
+ printf("ok 10 - getsockopt\n");
+
+ /*
+ * Step 10: Remove accept filter. After removing the accept filter
+ * getsockopt() should fail with EINVAL.
+ */
+ ret = setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0);
+ if (ret != 0)
+ errx(-1, "not ok 11 - setsockopt() after listen() "
+ "failed with %d (%s)", errno, strerror(errno));
+ bzero(&afa, sizeof(afa));
+ len = sizeof(afa);
+ ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len);
+ if (ret == 0)
+ errx(-1, "not ok 11 - getsockopt() after removing "
+ "the accept filter returns valid accept filter %s",
+ afa.af_name);
+ if (errno != EINVAL)
+ errx(-1, "not ok 11 - getsockopt() after removing the accept"
+ "filter failed with %d (%s)", errno, strerror(errno));
+ printf("ok 11 - setsockopt\n");
+
+ close(lso);
+ return (0);
+}
diff --git a/tools/regression/sockets/accf_data_attach/accf_data_attach.t b/tools/regression/sockets/accf_data_attach/accf_data_attach.t
new file mode 100644
index 0000000..8bdfd03
--- /dev/null
+++ b/tools/regression/sockets/accf_data_attach/accf_data_attach.t
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+executable=`basename $0 .t`
+
+make $executable 2>&1 > /dev/null
+
+exec ./$executable
diff --git a/tools/regression/sockets/fstat/Makefile b/tools/regression/sockets/fstat/Makefile
new file mode 100644
index 0000000..a583166
--- /dev/null
+++ b/tools/regression/sockets/fstat/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= fstat
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/fstat/fstat.c b/tools/regression/sockets/fstat/fstat.c
new file mode 100644
index 0000000..6ea931d
--- /dev/null
+++ b/tools/regression/sockets/fstat/fstat.c
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (c) 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/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <unistd.h>
+
+/*
+ * Basic test to make sure that fstat(2) returns success on various socket
+ * types. In the future we should also validate the fields, confirming
+ * expected results such as the effect of shutdown(2) on permissions, etc.
+ */
+
+static void
+dotest(int domain, int type, int protocol)
+{
+ struct stat sb;
+ int sock;
+
+ sock = socket(domain, type, protocol);
+ if (sock < 0)
+ err(-1, "socket(%d, %d, %d)", domain, type, protocol);
+
+ if (fstat(sock, &sb) < 0)
+ err(-1, "fstat on socket(%d, %d, %d)", domain, type,
+ protocol);
+
+ close(sock);
+}
+
+int
+main(void)
+{
+
+ dotest(PF_INET, SOCK_DGRAM, 0);
+ dotest(PF_INET, SOCK_STREAM, 0);
+ dotest(PF_INET6, SOCK_DGRAM, 0);
+ dotest(PF_INET6, SOCK_STREAM, 0);
+ dotest(PF_LOCAL, SOCK_DGRAM, 0);
+ dotest(PF_LOCAL, SOCK_STREAM, 0);
+
+ return (0);
+}
diff --git a/tools/regression/sockets/kqueue/Makefile b/tools/regression/sockets/kqueue/Makefile
new file mode 100644
index 0000000..6771d25
--- /dev/null
+++ b/tools/regression/sockets/kqueue/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= kqueue
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/kqueue/kqueue.c b/tools/regression/sockets/kqueue/kqueue.c
new file mode 100644
index 0000000..f73704a
--- /dev/null
+++ b/tools/regression/sockets/kqueue/kqueue.c
@@ -0,0 +1,368 @@
+/*-
+ * Copyright (c) 2004 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/socket.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int curtest = 1;
+
+/*-
+ * This test uses UNIX domain socket pairs to perform some basic exercising
+ * of kqueue functionality on sockets. In particular, testing that for read
+ * and write filters, we see the correct detection of whether reads and
+ * writes should actually be able to occur.
+ *
+ * TODO:
+ * - Test read/write filters for listen/accept sockets.
+ * - Handle the XXXRW below regarding datagram sockets.
+ * - Test that watermark/buffer size "data" fields returned by kqueue are
+ * correct.
+ * - Check that kqueue does something sensible when the remote endpoing is
+ * closed.
+ */
+
+#define OK(testname) printf("ok %d - %s\n", curtest, testname); \
+ curtest++;
+
+static void
+fail(int error, const char *func, const char *socktype, const char *rest)
+{
+
+ printf("not ok %d\n", curtest);
+
+ if (socktype == NULL)
+ printf("# %s(): %s\n", func, strerror(error));
+ else if (rest == NULL)
+ printf("# %s(%s): %s\n", func, socktype,
+ strerror(error));
+ else
+ printf("# %s(%s, %s): %s\n", func, socktype, rest,
+ strerror(error));
+ exit(-1);
+}
+
+static void
+fail_assertion(const char *func, const char *socktype, const char *rest,
+ const char *assertion)
+{
+
+ printf("not ok %d - %s\n", curtest, assertion);
+
+ if (socktype == NULL)
+ printf("# %s(): assertion %s failed\n", func,
+ assertion);
+ else if (rest == NULL)
+ printf("# %s(%s): assertion %s failed\n", func,
+ socktype, assertion);
+ else
+ printf("# %s(%s, %s): assertion %s failed\n", func,
+ socktype, rest, assertion);
+ exit(-1);
+}
+
+/*
+ * Test read kevent on a socket pair: check to make sure endpoint 0 isn't
+ * readable when we start, then write to endpoint 1 and confirm that endpoint
+ * 0 is now readable. Drain the write, then check that it's not readable
+ * again. Use non-blocking kqueue operations and socket operations.
+ */
+static void
+test_evfilt_read(int kq, int fd[2], const char *socktype)
+{
+ struct timespec ts;
+ struct kevent ke;
+ ssize_t len;
+ char ch;
+ int i;
+
+ EV_SET(&ke, fd[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1)
+ fail(errno, "kevent", socktype, "EVFILT_READ, EV_ADD");
+ OK("EVFILT_READ, EV_ADD");
+
+ /*
+ * Confirm not readable to begin with, no I/O yet.
+ */
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ i = kevent(kq, NULL, 0, &ke, 1, &ts);
+ if (i == -1)
+ fail(errno, "kevent", socktype, "EVFILT_READ");
+ OK("EVFILT_READ");
+ if (i != 0)
+ fail_assertion("kevent", socktype, "EVFILT_READ",
+ "empty socket unreadable");
+ OK("empty socket unreadable");
+
+ /*
+ * Write a byte to one end.
+ */
+ ch = 'a';
+ len = write(fd[1], &ch, sizeof(ch));
+ if (len == -1)
+ fail(errno, "write", socktype, NULL);
+ OK("write one byte");
+ if (len != sizeof(ch))
+ fail_assertion("write", socktype, NULL, "write length");
+ OK("write one byte length");
+
+ /*
+ * Other end should now be readable.
+ */
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ i = kevent(kq, NULL, 0, &ke, 1, &ts);
+ if (i == -1)
+ fail(errno, "kevent", socktype, "EVFILT_READ");
+ OK("EVFILT_READ");
+ if (i != 1)
+ fail_assertion("kevent", socktype, "EVFILT_READ",
+ "non-empty socket unreadable");
+ OK("non-empty socket unreadable");
+
+ /*
+ * Read a byte to clear the readable state.
+ */
+ len = read(fd[0], &ch, sizeof(ch));
+ if (len == -1)
+ fail(errno, "read", socktype, NULL);
+ OK("read one byte");
+ if (len != sizeof(ch))
+ fail_assertion("read", socktype, NULL, "read length");
+ OK("read one byte length");
+
+ /*
+ * Now re-check for readability.
+ */
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ i = kevent(kq, NULL, 0, &ke, 1, &ts);
+ if (i == -1)
+ fail(errno, "kevent", socktype, "EVFILT_READ");
+ OK("EVFILT_READ");
+ if (i != 0)
+ fail_assertion("kevent", socktype, "EVFILT_READ",
+ "empty socket unreadable");
+ OK("empty socket unreadable");
+
+ EV_SET(&ke, fd[0], EVFILT_READ, EV_DELETE, 0, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1)
+ fail(errno, "kevent", socktype, "EVFILT_READ, EV_DELETE");
+ OK("EVFILT_READ, EV_DELETE");
+}
+
+static void
+test_evfilt_write(int kq, int fd[2], const char *socktype)
+{
+ struct timespec ts;
+ struct kevent ke;
+ ssize_t len;
+ char ch;
+ int i;
+
+ EV_SET(&ke, fd[0], EVFILT_WRITE, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1)
+ fail(errno, "kevent", socktype, "EVFILT_WRITE, EV_ADD");
+ OK("EVFILE_WRITE, EV_ADD");
+
+ /*
+ * Confirm writable to begin with, no I/O yet.
+ */
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ i = kevent(kq, NULL, 0, &ke, 1, &ts);
+ if (i == -1)
+ fail(errno, "kevent", socktype, "EVFILT_WRITE");
+ OK("EVFILE_WRITE");
+ if (i != 1)
+ fail_assertion("kevent", socktype, "EVFILT_WRITE",
+ "empty socket unwritable");
+ OK("empty socket unwritable");
+
+ /*
+ * Write bytes into the socket until we can't write anymore.
+ */
+ ch = 'a';
+ while ((len = write(fd[0], &ch, sizeof(ch))) == sizeof(ch)) {};
+ if (len == -1 && errno != EAGAIN && errno != ENOBUFS)
+ fail(errno, "write", socktype, NULL);
+ OK("write");
+ if (len != -1 && len != sizeof(ch))
+ fail_assertion("write", socktype, NULL, "write length");
+ OK("write length");
+
+ /*
+ * Check to make sure the socket is no longer writable.
+ */
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ i = kevent(kq, NULL, 0, &ke, 1, &ts);
+ if (i == -1)
+ fail(errno, "kevent", socktype, "EVFILT_WRITE");
+ OK("EVFILT_WRITE");
+ if (i != 0)
+ fail_assertion("kevent", socktype, "EVFILT_WRITE",
+ "full socket writable");
+ OK("full socket writable");
+
+ EV_SET(&ke, fd[0], EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1)
+ fail(errno, "kevent", socktype, "EVFILT_WRITE, EV_DELETE");
+ OK("EVFILT_WRITE, EV_DELETE");
+}
+
+/*
+ * Basic registration exercise for kqueue(2). Create several types/brands of
+ * sockets, and confirm that we can register for various events on them.
+ */
+int
+main(void)
+{
+ int kq, sv[2];
+
+ printf("1..49\n");
+
+ kq = kqueue();
+ if (kq == -1)
+ fail(errno, "kqueue", NULL, NULL);
+ OK("kqueue()");
+
+ /*
+ * Create a UNIX domain datagram socket, and attach/test/detach a
+ * read filter on it.
+ */
+ if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) == -1)
+ fail(errno, "socketpair", "PF_UNIX, SOCK_DGRAM", NULL);
+ OK("socketpair() 1");
+
+ if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK");
+ OK("fcntl() 1");
+ if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK");
+ OK("fnctl() 2");
+
+ test_evfilt_read(kq, sv, "PF_UNIX, SOCK_DGRAM");
+
+ if (close(sv[0]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[0]");
+ OK("close() 1");
+ if (close(sv[1]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[1]");
+ OK("close() 2");
+
+#if 0
+ /*
+ * XXXRW: We disable the write test in the case of datagram sockets,
+ * as kqueue can't tell when the remote socket receive buffer is
+ * full, whereas the UNIX domain socket implementation can tell and
+ * returns ENOBUFS.
+ */
+ /*
+ * Create a UNIX domain datagram socket, and attach/test/detach a
+ * write filter on it.
+ */
+ if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) == -1)
+ fail(errno, "socketpair", "PF_UNIX, SOCK_DGRAM", NULL);
+
+ if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK");
+ if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK");
+
+ test_evfilt_write(kq, sv, "PF_UNIX, SOCK_DGRAM");
+
+ if (close(sv[0]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[0]");
+ if (close(sv[1]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[1]");
+#endif
+
+ /*
+ * Create a UNIX domain stream socket, and attach/test/detach a
+ * read filter on it.
+ */
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
+ fail(errno, "socketpair", "PF_UNIX, SOCK_STREAM", NULL);
+ OK("socketpair() 2");
+
+ if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK");
+ OK("fcntl() 3");
+ if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK");
+ OK("fcntl() 4");
+
+ test_evfilt_read(kq, sv, "PF_UNIX, SOCK_STREAM");
+
+ if (close(sv[0]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[0]");
+ OK("close() 3");
+ if (close(sv[1]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[1]");
+ OK("close() 4");
+
+ /*
+ * Create a UNIX domain stream socket, and attach/test/detach a
+ * write filter on it.
+ */
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
+ fail(errno, "socketpair", "PF_UNIX, SOCK_STREAM", NULL);
+ OK("socketpair() 3");
+
+ if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK");
+ OK("fcntl() 5");
+ if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0)
+ fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK");
+ OK("fcntl() 6");
+
+ test_evfilt_write(kq, sv, "PF_UNIX, SOCK_STREAM");
+
+ if (close(sv[0]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[0]");
+ OK("close() 5");
+ if (close(sv[1]) == -1)
+ fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[1]");
+ OK("close() 6");
+
+ if (close(kq) == -1)
+ fail(errno, "close", "kq", NULL);
+ OK("close() 7");
+
+ return (0);
+}
diff --git a/tools/regression/sockets/kqueue/kqueue.t b/tools/regression/sockets/kqueue/kqueue.t
new file mode 100644
index 0000000..8bdfd03
--- /dev/null
+++ b/tools/regression/sockets/kqueue/kqueue.t
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+executable=`basename $0 .t`
+
+make $executable 2>&1 > /dev/null
+
+exec ./$executable
diff --git a/tools/regression/sockets/listen_backlog/Makefile b/tools/regression/sockets/listen_backlog/Makefile
new file mode 100644
index 0000000..e2eb135
--- /dev/null
+++ b/tools/regression/sockets/listen_backlog/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= listen_backlog
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/listen_backlog/listen_backlog.c b/tools/regression/sockets/listen_backlog/listen_backlog.c
new file mode 100644
index 0000000..2276393
--- /dev/null
+++ b/tools/regression/sockets/listen_backlog/listen_backlog.c
@@ -0,0 +1,383 @@
+/*-
+ * 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/socket.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * This regression test is intended to validate that the backlog parameter
+ * set by listen() is properly set, can be retrieved using SO_LISTENQLIMIT,
+ * and that it can be updated by later calls to listen(). We also check that
+ * SO_LISTENQLIMIT cannot be set.
+ *
+ * Future things to test:
+ *
+ * - That if we change the value of kern.ipc.somaxconn, the limits really
+ * do change.
+ *
+ * - That limits are, approximately, enforced and implemented.
+ *
+ * - All this on multiple socket types -- i.e., PF_LOCAL.
+ *
+ * - That we also test SO_LISTENQLEN and SO_LISTENINCQLEN.
+ */
+
+/*
+ * We retrieve kern.ipc.somaxconn before running the tests in order to use a
+ * run-time set value of SOMAXCONN, rather than compile-time set. We assume
+ * that no other process will be simultaneously frobbing it, and these tests
+ * may fail if that assumption is not held.
+ */
+static int somaxconn;
+
+/*
+ * Retrieve the current socket listen queue limit using SO_LISTENQLIMIT.
+ */
+static int
+socket_get_backlog(int sock, int *backlogp, const char *testclass,
+ const char *test, const char *testfunc)
+{
+ socklen_t len;
+ int i;
+
+ len = sizeof(i);
+ if (getsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, &len) < 0) {
+ warn("%s: %s: %s: socket_get_backlog: getsockopt("
+ "SOL_SOCKET, SO_LISTENQLIMIT)", testclass, test,
+ testfunc);
+ return (-1);
+ }
+
+ if (len != sizeof(i)) {
+ warnx("%s: %s: %s: socket_get_backlog: getsockopt("
+ "SOL_SOCKET, SO_LISTENQLIMIT): returned size %d",
+ testclass, test, testfunc, len);
+ return (-1);
+ }
+
+ *backlogp = i;
+
+ return (0);
+}
+
+/*
+ * Create a socket, check the queue limit on creation, perform a listen(),
+ * and make sure that the limit was set as expected by listen().
+ */
+static int
+socket_listen(int domain, int type, int protocol, int backlog,
+ int create_backlog_assertion, int listen_backlog_assertion, int *sockp,
+ const char *domainstring, const char *typestring, const char *testclass,
+ const char *test)
+{
+ int backlog_retrieved, sock;
+
+ sock = socket(domain, type, protocol);
+ if (sock < 0) {
+ warn("%s: %s: socket_listen: socket(%s, %s)", testclass,
+ test, domainstring, typestring);
+ close(sock);
+ return (-1);
+ }
+
+ if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
+ "socket_listen") < 0) {
+ close(sock);
+ return (-1);
+ }
+
+ if (backlog_retrieved != create_backlog_assertion) {
+ warnx("%s: %s: socket_listen: create backlog is %d not %d",
+ testclass, test, backlog_retrieved,
+ create_backlog_assertion);
+ close(sock);
+ return (-1);
+ }
+
+ if (listen(sock, backlog) < 0) {
+ warn("%s: %s: socket_listen: listen(, %d)", testclass, test,
+ backlog);
+ close(sock);
+ return (-1);
+ }
+
+ if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
+ "socket_listen") < 0) {
+ close(sock);
+ return (-1);
+ }
+
+ if (backlog_retrieved != listen_backlog_assertion) {
+ warnx("%s: %s: socket_listen: listen backlog is %d not %d",
+ testclass, test, backlog_retrieved,
+ listen_backlog_assertion);
+ close(sock);
+ return (-1);
+ }
+
+ *sockp = sock;
+ return (0);
+}
+
+/*
+ * This test creates sockets and tests default states before and after
+ * listen(). Specifically, we expect a queue limit of 0 before listen, and
+ * then various settings for after listen(). If the passed backlog was
+ * either < 0 or > somaxconn, it should be set to somaxconn; otherwise, the
+ * passed queue depth.
+ */
+static void
+test_defaults(void)
+{
+ int sock;
+
+ /*
+ * First pass. Confirm the default is 0. Listen with a backlog of
+ * 0 and confirm it gets set that way.
+ */
+ if (socket_listen(PF_INET, SOCK_STREAM, 0, 0, 0, 0, &sock, "PF_INET",
+ "SOCK_STREAM", "test_defaults", "default_0_listen_0") < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Second pass. Listen with a backlog of -1 and make sure it is set
+ * to somaxconn.
+ */
+ if (socket_listen(PF_INET, SOCK_STREAM, 0, -1, 0, somaxconn, &sock,
+ "PF_INET", "SOCK_STREAM", "test_defaults", "default_0_listen_-1")
+ < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Third pass. Listen with a backlog of 1 and make sure it is set to
+ * 1.
+ */
+ if (socket_listen(PF_INET, SOCK_STREAM, 0, 1, 0, 1, &sock, "PF_INET",
+ "SOCK_STREAM", "test_defaults", "default_0_listen_1") < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Fourth pass. Listen with a backlog of somaxconn and make sure it
+ * is set to somaxconn.
+ */
+ if (socket_listen(PF_INET, SOCK_STREAM, 0, somaxconn, 0, somaxconn,
+ &sock, "PF_INET", "SOCK_STREAM", "test_defaults",
+ "default_0_listen_somaxconn") < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Fifth pass. Listen with a backlog of somaxconn+1 and make sure it
+ * is set to somaxconn.
+ */
+ if (socket_listen(PF_INET, SOCK_STREAM, 0, somaxconn+1, 0, somaxconn,
+ &sock, "PF_INET", "SOCK_STREAM", "test_defaults",
+ "default_0_listen_somaxconn+1") < 0)
+ exit(-1);
+ close(sock);
+}
+
+/*
+ * Create a socket, set the initial listen() state, then update the queue
+ * depth using listen(). Check that the backlog is as expected after both
+ * the first and second listen().
+ */
+static int
+socket_listen_update(int domain __unused, int type __unused,
+ int protocol __unused, int backlog,
+ int update_backlog, int listen_backlog_assertion,
+ int update_backlog_assertion, int *sockp, const char *domainstring,
+ const char *typestring, const char *testclass, const char *test)
+{
+ int backlog_retrieved, sock;
+
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ warn("%s: %s: socket_listen_update: socket(%s, %s)",
+ testclass, test, domainstring, typestring);
+ return (-1);
+ }
+
+ if (listen(sock, backlog) < 0) {
+ warn("%s: %s: socket_listen_update: initial listen(, %d)",
+ testclass, test, backlog);
+ close(sock);
+ return (-1);
+ }
+
+ if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
+ "socket_listen_update") < 0) {
+ close(sock);
+ return (-1);
+ }
+
+ if (backlog_retrieved != listen_backlog_assertion) {
+ warnx("%s: %s: socket_listen_update: initial backlog is %d "
+ "not %d", testclass, test, backlog_retrieved,
+ listen_backlog_assertion);
+ close(sock);
+ return (-1);
+ }
+
+ if (listen(sock, update_backlog) < 0) {
+ warn("%s: %s: socket_listen_update: update listen(, %d)",
+ testclass, test, update_backlog);
+ close(sock);
+ return (-1);
+ }
+
+ if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
+ "socket_listen_update") < 0) {
+ close(sock);
+ return (-1);
+ }
+
+ if (backlog_retrieved != update_backlog_assertion) {
+ warnx("%s: %s: socket_listen_update: updated backlog is %d "
+ "not %d", testclass, test, backlog_retrieved,
+ update_backlog_assertion);
+ close(sock);
+ return (-1);
+ }
+
+ *sockp = sock;
+ return (0);
+}
+
+/*
+ * This test tests using listen() to update the queue depth after a socket
+ * has already been marked as listening. We test several cases: setting the
+ * socket < 0, 0, 1, somaxconn, and somaxconn + 1.
+ */
+static void
+test_listen_update(void)
+{
+ int sock;
+
+ /*
+ * Set to 5, update to -1, which should give somaxconn.
+ */
+ if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, -1, 5, somaxconn,
+ &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
+ "update_5,-1") < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Set to 5, update to 0, which should give 0.
+ */
+ if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 0, 5, 0, &sock,
+ "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,0")
+ < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Set to 5, update to 1, which should give 1.
+ */
+ if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 1, 5, 1, &sock,
+ "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,1")
+ < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Set to 5, update to somaxconn, which should give somaxconn.
+ */
+ if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn, 5,
+ somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
+ "update_5,somaxconn") < 0)
+ exit(-1);
+ close(sock);
+
+ /*
+ * Set to 5, update to somaxconn+1, which should give somaxconn.
+ */
+ if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn+1, 5,
+ somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
+ "update_5,somaxconn+1") < 0)
+ exit(-1);
+ close(sock);
+}
+
+/*
+ * SO_LISTENQLIMIT is a read-only socket option, so make sure we get an error
+ * if we try to write it.
+ */
+static void
+test_set_qlimit(void)
+{
+ int i, ret, sock;
+
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
+ err(-1, "test_set_qlimit: socket(PF_INET, SOCK_STREAM)");
+
+ i = 0;
+ ret = setsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, sizeof(i));
+ if (ret < 0 && errno != ENOPROTOOPT) {
+ warn("test_set_qlimit: setsockopt(SOL_SOCKET, "
+ "SO_LISTENQLIMIT, 0): unexpected error");
+ close(sock);
+ }
+
+ if (ret == 0) {
+ warnx("test_set_qlimit: setsockopt(SOL_SOCKET, "
+ "SO_LISTENQLIMIT, 0) succeeded");
+ close(sock);
+ exit(-1);
+ }
+ close(sock);
+}
+
+int
+main(void)
+{
+ size_t len;
+
+ len = sizeof(somaxconn);
+ if (sysctlbyname("kern.ipc.somaxconn", &somaxconn, &len, NULL, 0)
+ < 0)
+ err(-1, "sysctlbyname(kern.ipc.somaxconn)");
+
+ test_defaults();
+ test_listen_update();
+ test_set_qlimit();
+
+ return (0);
+}
diff --git a/tools/regression/sockets/listenclose/Makefile b/tools/regression/sockets/listenclose/Makefile
new file mode 100644
index 0000000..8f4dc4e
--- /dev/null
+++ b/tools/regression/sockets/listenclose/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= listenclose
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/listenclose/listenclose.c b/tools/regression/sockets/listenclose/listenclose.c
new file mode 100644
index 0000000..f920846
--- /dev/null
+++ b/tools/regression/sockets/listenclose/listenclose.c
@@ -0,0 +1,111 @@
+/*-
+ * Copyright (c) 2004-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/socket.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * The listenclose regression test is designed to catch kernel bugs that may
+ * trigger as a result of performing a close on a listen() socket with as-yet
+ * unaccepted connections in its queues. This results in the connections
+ * being aborted, which is a not-often-followed code path. To do this, we
+ * create a local TCP socket, build a non-blocking connection to it, and then
+ * close the accept socket. The connection must be non-blocking or the
+ * program will block and as such connect() will not return as accept() is
+ * never called.
+ */
+
+int
+main(void)
+{
+ int listen_sock, connect_sock;
+ struct sockaddr_in sin;
+ socklen_t len;
+ u_short port;
+ int arg;
+
+ listen_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (listen_sock == -1)
+ errx(-1,
+ "socket(PF_INET, SOCK_STREAM, 0) for listen socket: %s",
+ strerror(errno));
+
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = 0;
+
+ if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ errx(-1, "bind(%s, %d) for listen socket: %s",
+ inet_ntoa(sin.sin_addr), 0, strerror(errno));
+
+ len = sizeof(sin);
+ if (getsockname(listen_sock, (struct sockaddr *)&sin, &len) < 0)
+ errx(-1, "getsockname() for listen socket: %s",
+ strerror(errno));
+ port = sin.sin_port;
+
+ if (listen(listen_sock, -1) < 0)
+ errx(-1, "listen() for listen socket: %s", strerror(errno));
+
+ connect_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (connect_sock == -1)
+ errx(-1, "socket(PF_INET, SOCK_STREAM, 0) for connect "
+ "socket: %s", strerror(errno));
+
+ arg = O_NONBLOCK;
+ if (fcntl(connect_sock, F_SETFL, &arg) < 0)
+ errx(-1, "socket(PF_INET, SOCK_STREAM, 0) for connect socket"
+ ": %s", strerror(errno));
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = port;
+
+ if (connect(connect_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ errx(-1, "connect() for connect socket: %s", strerror(errno));
+ close(connect_sock);
+ close(listen_sock);
+
+ return (0);
+}
diff --git a/tools/regression/sockets/pr_atomic/Makefile b/tools/regression/sockets/pr_atomic/Makefile
new file mode 100644
index 0000000..1dc85ff
--- /dev/null
+++ b/tools/regression/sockets/pr_atomic/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= pr_atomic
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/pr_atomic/pr_atomic.c b/tools/regression/sockets/pr_atomic/pr_atomic.c
new file mode 100644
index 0000000..e902cf7
--- /dev/null
+++ b/tools/regression/sockets/pr_atomic/pr_atomic.c
@@ -0,0 +1,109 @@
+/*-
+ * Copyright (c) 2006 Bruce M. Simpson
+ * 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$
+ */
+
+/*
+ * Regression test for uiomove in kernel; specifically for PR kern/38495.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdlib.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <string.h>
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+
+static char socket_path[] = "tmp.XXXXXX";
+
+static jmp_buf myjmpbuf;
+
+static void handle_sigalrm(int signo __unused)
+{
+ longjmp(myjmpbuf, 1);
+}
+
+int
+main(void)
+{
+ struct sockaddr_un un;
+ pid_t pid;
+ int s;
+
+ if (mkstemp(socket_path) == -1)
+ err(1, "mkstemp");
+ s = socket(PF_LOCAL, SOCK_DGRAM, 0);
+ if (s == -1)
+ errx(-1, "socket");
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_LOCAL;
+ unlink(socket_path);
+ strcpy(un.sun_path, socket_path);
+ if (bind(s, (struct sockaddr *)&un, sizeof(un)) == -1)
+ errx(-1, "bind");
+ pid = fork();
+ if (pid == -1)
+ errx(-1, "fork");
+ if (pid == 0) {
+ int conn;
+ char buf[] = "AAAAAAAAA";
+
+ close(s);
+ conn = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (conn == -1)
+ errx(-1,"socket");
+ if (sendto(conn, buf, sizeof(buf), 0, (struct sockaddr *)&un,
+ sizeof(un)) != sizeof(buf))
+ errx(-1,"sendto");
+ close(conn);
+ _exit(0);
+ }
+
+ sleep(5);
+
+ /* Make sure the data is there when we try to receive it. */
+ if (recvfrom(s, (void *)-1, 1, 0, NULL, NULL) != -1)
+ errx(-1,"recvfrom succeeded when failure expected");
+
+ (void)signal(SIGALRM, handle_sigalrm);
+ if (setjmp(myjmpbuf) == 0) {
+ /*
+ * This recvfrom will panic an unpatched system, and block
+ * a patched one.
+ */
+ alarm(5);
+ (void)recvfrom(s, (void *)-1, 1, 0, NULL, NULL);
+ }
+
+ /* We should reach here via longjmp() and all should be well. */
+
+ return (0);
+}
diff --git a/tools/regression/sockets/reconnect/Makefile b/tools/regression/sockets/reconnect/Makefile
new file mode 100644
index 0000000..d8fa5e0
--- /dev/null
+++ b/tools/regression/sockets/reconnect/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= reconnect
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/reconnect/reconnect.c b/tools/regression/sockets/reconnect/reconnect.c
new file mode 100644
index 0000000..27f32cc
--- /dev/null
+++ b/tools/regression/sockets/reconnect/reconnect.c
@@ -0,0 +1,132 @@
+/*-
+ * Copyright (c) 2005 Maxim Sobolev
+ * 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$
+ */
+
+/*
+ * The reconnect regression test is designed to catch kernel bug that may
+ * prevent changing association of already associated datagram unix domain
+ * socket when server side of connection has been closed.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+static char uds_name1[] = "reconnect.XXXXXXXX";
+static char uds_name2[] = "reconnect.XXXXXXXX";
+
+#define sstosa(ss) ((struct sockaddr *)(ss))
+
+static void
+prepare_ifsun(struct sockaddr_un *ifsun, const char *path)
+{
+
+ memset(ifsun, '\0', sizeof(*ifsun));
+#if !defined(__linux__) && !defined(__solaris__)
+ ifsun->sun_len = strlen(path);
+#endif
+ ifsun->sun_family = AF_LOCAL;
+ strcpy(ifsun->sun_path, path);
+}
+
+static int
+create_uds_server(const char *path)
+{
+ struct sockaddr_un ifsun;
+ int sock;
+
+ prepare_ifsun(&ifsun, path);
+
+ unlink(ifsun.sun_path);
+
+ sock = socket(PF_LOCAL, SOCK_DGRAM, 0);
+ if (sock == -1)
+ err(1, "can't create socket");
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sock, sizeof(sock));
+ if (bind(sock, sstosa(&ifsun), sizeof(ifsun)) < 0)
+ err(1, "can't bind to a socket");
+
+ return sock;
+}
+
+static void
+connect_uds_server(int sock, const char *path)
+{
+ struct sockaddr_un ifsun;
+ int e;
+
+ prepare_ifsun(&ifsun, path);
+
+ e = connect(sock, sstosa(&ifsun), sizeof(ifsun));
+ if (e < 0)
+ err(1, "can't connect to a socket");
+}
+
+static void
+cleanup(void)
+{
+
+ unlink(uds_name1);
+ unlink(uds_name2);
+}
+
+int
+main()
+{
+ int s_sock1, s_sock2, c_sock;
+
+ atexit(cleanup);
+
+ if (mkstemp(uds_name1) == -1)
+ err(1, "mkstemp");
+ unlink(uds_name1);
+ s_sock1 = create_uds_server(uds_name1);
+
+ if (mkstemp(uds_name2) == -1)
+ err(1, "mkstemp");
+ unlink(uds_name2);
+ s_sock2 = create_uds_server(uds_name2);
+
+ c_sock = socket(PF_LOCAL, SOCK_DGRAM, 0);
+ if (c_sock < 0)
+ err(1, "can't create socket");
+
+ connect_uds_server(c_sock, uds_name1);
+ close(s_sock1);
+ connect_uds_server(c_sock, uds_name2);
+
+ exit (0);
+}
diff --git a/tools/regression/sockets/rtsocket/Makefile b/tools/regression/sockets/rtsocket/Makefile
new file mode 100644
index 0000000..f68b6c3
--- /dev/null
+++ b/tools/regression/sockets/rtsocket/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= rtsocket
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/rtsocket/rtsocket.c b/tools/regression/sockets/rtsocket/rtsocket.c
new file mode 100644
index 0000000..b5824b3
--- /dev/null
+++ b/tools/regression/sockets/rtsocket/rtsocket.c
@@ -0,0 +1,101 @@
+/*-
+ * 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$
+ */
+
+/*
+ * Simple routing socket regression test: create and destroy a raw routing
+ * socket, and make sure that dgram and stream don't work, socketpair, etc.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <net/route.h>
+
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+ int sock, socks[2];
+
+ sock = socket(PF_ROUTE, SOCK_STREAM, 0);
+ if (sock >= 0) {
+ close(sock);
+ errx(-1, "socket(PF_ROUTE, SOCK_STREAM, 0) returned %d",
+ sock);
+ }
+
+ if (errno != EPROTONOSUPPORT)
+ err(-1, "socket(PF_ROUTE, SOCK_STREAM, 0)");
+
+ sock = socket(PF_ROUTE, SOCK_DGRAM, 0);
+ if (sock >= 0) {
+ close(sock);
+ errx(-1, "socket(PF_ROUTE, SOCK_DGRAM, 0) returned %d",
+ sock);
+ }
+
+ if (errno != EPROTONOSUPPORT)
+ err(-1, "socket(PF_ROUTE, SOCK_DGRAM, 0)");
+
+ sock = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (sock < 0)
+ err(-1, "socket(PF_ROUTE, SOCK_RAW, 0)");
+ close(sock);
+
+ if (socketpair(PF_ROUTE, SOCK_STREAM, 0, socks) == 0) {
+ close(socks[0]);
+ close(socks[1]);
+ errx(-1,
+ "socketpair(PF_ROUTE, SOCK_STREAM, 0, socks) success");
+ }
+
+ if (errno != EPROTONOSUPPORT)
+ err(-1, "socketpair(PF_ROUTE, SOCK_STREAM, 0, socks)");
+
+ if (socketpair(PF_ROUTE, SOCK_DGRAM, 0, socks) == 0) {
+ close(socks[0]);
+ close(socks[1]);
+ errx(-1,
+ "socketpair(PF_ROUTE, SOCK_DGRAM, 0, socks) success");
+ }
+
+ if (errno != EPROTONOSUPPORT)
+ err(-1, "socketpair(PF_ROUTE, SOCK_DGRAM, 0, socks)");
+
+ if (socketpair(PF_ROUTE, SOCK_RAW, 0, socks) == 0) {
+ close(socks[0]);
+ close(socks[1]);
+ errx(-1,
+ "socketpair(PF_ROUTE, SOCK_STREAM, 0, socks) success");
+ }
+
+ return (0);
+}
diff --git a/tools/regression/sockets/sblock/Makefile b/tools/regression/sockets/sblock/Makefile
new file mode 100644
index 0000000..aa2f890
--- /dev/null
+++ b/tools/regression/sockets/sblock/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= sblock
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/sblock/sblock.c b/tools/regression/sockets/sblock/sblock.c
new file mode 100644
index 0000000..415c4d4
--- /dev/null
+++ b/tools/regression/sockets/sblock/sblock.c
@@ -0,0 +1,207 @@
+/*-
+ * Copyright (c) 2007 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$
+ */
+
+/*
+ * Sockets serialize I/O in each direction in order to avoid interlacing of
+ * I/O by multiple processes or threcvs recving or sending the socket. This
+ * is done using some form of kernel lock (varies by kernel version), called
+ * "sblock" in FreeBSD. However, to avoid unkillable processes waiting on
+ * I/O that may be entirely controlled by a remote network endpoint, that
+ * lock acquisition must be interruptible.
+ *
+ * To test this, set up a local domain stream socket pair and a set of three
+ * processes. Two processes block in recv(), the first on sbwait (wait for
+ * I/O), and the second on the sblock waiting for the first to finish. A
+ * third process is responsible for signalling the second process, then
+ * writing to the socket. Depending on the error returned in the second
+ * process, we can tell whether the sblock wait was interrupted, or if
+ * instead the process only woke up when the write was performed.
+ */
+
+#include <sys/socket.h>
+
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static int interrupted;
+static void
+signal_handler(int signum __unused)
+{
+
+ interrupted++;
+}
+
+/*
+ * Process that will perform a blocking recv on a UNIX domain socket. This
+ * should return one byte of data.
+ */
+static void
+blocking_recver(int fd)
+{
+ ssize_t len;
+ char ch;
+
+ len = recv(fd, &ch, sizeof(ch), 0);
+ if (len < 0)
+ err(-1, "FAIL: blocking_recver: recv");
+ if (len == 0)
+ errx(-1, "FAIL: blocking_recver: recv: eof");
+ if (len != 1)
+ errx(-1, "FAIL: blocking_recver: recv: %zd bytes", len);
+ if (interrupted)
+ errx(-1, "FAIL: blocking_recver: interrupted wrong pid");
+}
+
+/*
+ * Process that will perform a locking recv on a UNIX domain socket.
+ *
+ * This is where we figure out if the test worked or not. If it has failed,
+ * then recv() will return EOF, as the close() arrives before the signal,
+ * meaning that the wait for the sblock was not interrupted; if it has
+ * succeeded, we get EINTR as the signal interrupts the lock request.
+ */
+static void
+locking_recver(int fd)
+{
+ ssize_t len;
+ char ch;
+
+ if (sleep(1) != 0)
+ err(-1, "FAIL: locking_recver: sleep");
+ len = recv(fd, &ch, sizeof(ch), 0);
+ if (len < 0 && errno != EINTR)
+ err(-1, "FAIL: locking_recver: recv");
+ if (len < 0 && errno == EINTR) {
+ fprintf(stderr, "PASS\n");
+ exit(0);
+ }
+ if (len == 0)
+ errx(-1, "FAIL: locking_recver: recv: eof");
+ if (!interrupted)
+ errx(-1, "FAIL: locking_recver: not interrupted");
+}
+
+static void
+signaller(pid_t locking_recver_pid, int fd)
+{
+ ssize_t len;
+ char ch;
+
+ if (sleep(2) != 0) {
+ warn("signaller sleep(2)");
+ return;
+ }
+ if (kill(locking_recver_pid, SIGHUP) < 0) {
+ warn("signaller kill(%d)", locking_recver_pid);
+ return;
+ }
+ if (sleep(1) != 0) {
+ warn("signaller sleep(1)");
+ return;
+ }
+ len = send(fd, &ch, sizeof(ch), 0);
+ if (len < 0) {
+ warn("signaller send");
+ return;
+ }
+ if (len != sizeof(ch)) {
+ warnx("signaller send ret %zd", len);
+ return;
+ }
+ if (close(fd) < 0) {
+ warn("signaller close");
+ return;
+ }
+ if (sleep(1) != 0) {
+ warn("signaller sleep(1)");
+ return;
+ }
+}
+
+int
+main(void)
+{
+ int error, fds[2], recver_fd, sender_fd;
+ pid_t blocking_recver_pid;
+ pid_t locking_recver_pid;
+ struct sigaction sa;
+
+ if (sigaction(SIGHUP, NULL, &sa) < 0)
+ err(-1, "FAIL: sigaction(SIGHUP, NULL, &sa)");
+
+ sa.sa_handler = signal_handler;
+ if (sa.sa_flags & SA_RESTART)
+ printf("SIGHUP restartable by default (cleared)\n");
+ sa.sa_flags &= ~SA_RESTART;
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ err(-1, "FAIL: sigaction(SIGHUP, &sa, NULL)");
+
+#if 0
+ if (signal(SIGHUP, signal_handler) == SIG_ERR)
+ err(-1, "FAIL: signal(SIGHUP)");
+#endif
+
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0)
+ err(-1, "FAIL: socketpair(PF_LOCAL, SOGK_STREAM, 0)");
+
+ sender_fd = fds[0];
+ recver_fd = fds[1];
+
+ blocking_recver_pid = fork();
+ if (blocking_recver_pid < 0)
+ err(-1, "FAIL: fork");
+ if (blocking_recver_pid == 0) {
+ close(sender_fd);
+ blocking_recver(recver_fd);
+ exit(0);
+ }
+
+ locking_recver_pid = fork();
+ if (locking_recver_pid < 0) {
+ error = errno;
+ kill(blocking_recver_pid, SIGKILL);
+ errno = error;
+ err(-1, "FAIL: fork");
+ }
+ if (locking_recver_pid == 0) {
+ close(sender_fd);
+ locking_recver(recver_fd);
+ exit(0);
+ }
+
+ signaller(locking_recver_pid, sender_fd);
+
+ kill(blocking_recver_pid, SIGKILL);
+ kill(locking_recver_pid, SIGKILL);
+ exit(0);
+}
diff --git a/tools/regression/sockets/sendfile/Makefile b/tools/regression/sockets/sendfile/Makefile
new file mode 100644
index 0000000..9d6f185
--- /dev/null
+++ b/tools/regression/sockets/sendfile/Makefile
@@ -0,0 +1,11 @@
+#
+# $FreeBSD$
+#
+
+PROG= sendfile
+MAN=
+WARNS?= 6
+DPADD= ${LIBMD}
+LDADD= -lmd
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/sendfile/sendfile.c b/tools/regression/sockets/sendfile/sendfile.c
new file mode 100644
index 0000000..18ae9ad
--- /dev/null
+++ b/tools/regression/sockets/sendfile/sendfile.c
@@ -0,0 +1,486 @@
+/*-
+ * 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$
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <md5.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Simple regression test for sendfile. Creates a file sized at four pages
+ * and then proceeds to send it over a series of sockets, exercising a number
+ * of cases and performing limited validation.
+ */
+
+#define FAIL(msg) {printf("# %s\n", msg); \
+ return (-1);}
+
+#define FAIL_ERR(msg) {printf("# %s: %s\n", msg, strerror(errno)); \
+ return (-1);}
+
+#define TEST_PORT 5678
+#define TEST_MAGIC 0x4440f7bb
+#define TEST_PAGES 4
+#define TEST_SECONDS 30
+
+struct test_header {
+ uint32_t th_magic;
+ uint32_t th_header_length;
+ uint32_t th_offset;
+ uint32_t th_length;
+ char th_md5[33];
+};
+
+struct sendfile_test {
+ uint32_t hdr_length;
+ uint32_t offset;
+ uint32_t length;
+ uint32_t file_size;
+};
+
+static int file_fd;
+static char path[PATH_MAX];
+static int listen_socket;
+static int accept_socket;
+
+static int test_th(struct test_header *th, uint32_t *header_length,
+ uint32_t *offset, uint32_t *length);
+static void signal_alarm(int signum);
+static void setup_alarm(int seconds);
+static void cancel_alarm(void);
+static int receive_test(void);
+static void run_child(void);
+static int new_test_socket(int *connect_socket);
+static void init_th(struct test_header *th, uint32_t header_length,
+ uint32_t offset, uint32_t length);
+static int send_test(int connect_socket, struct sendfile_test);
+static int write_test_file(size_t file_size);
+static void run_parent(void);
+static void cleanup(void);
+
+
+static int
+test_th(struct test_header *th, uint32_t *header_length, uint32_t *offset,
+ uint32_t *length)
+{
+
+ if (th->th_magic != htonl(TEST_MAGIC))
+ FAIL("magic number not found in header")
+ *header_length = ntohl(th->th_header_length);
+ *offset = ntohl(th->th_offset);
+ *length = ntohl(th->th_length);
+ return (0);
+}
+
+static void
+signal_alarm(int signum)
+{
+ (void)signum;
+
+ printf("# test timeout\n");
+
+ if (accept_socket > 0)
+ close(accept_socket);
+ if (listen_socket > 0)
+ close(listen_socket);
+
+ _exit(-1);
+}
+
+static void
+setup_alarm(int seconds)
+{
+ struct itimerval itv;
+ bzero(&itv, sizeof(itv));
+ (void)seconds;
+ itv.it_value.tv_sec = seconds;
+
+ signal(SIGALRM, signal_alarm);
+ setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+static void
+cancel_alarm(void)
+{
+ struct itimerval itv;
+ bzero(&itv, sizeof(itv));
+ setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+static int
+receive_test(void)
+{
+ uint32_t header_length, offset, length, counter;
+ struct test_header th;
+ ssize_t len;
+ char buf[10240];
+ MD5_CTX md5ctx;
+ char *rxmd5;
+
+ len = read(accept_socket, &th, sizeof(th));
+ if (len < 0 || (size_t)len < sizeof(th))
+ FAIL_ERR("read")
+
+ if (test_th(&th, &header_length, &offset, &length) != 0)
+ return (-1);
+
+ MD5Init(&md5ctx);
+
+ counter = 0;
+ while (1) {
+ len = read(accept_socket, buf, sizeof(buf));
+ if (len < 0 || len == 0)
+ break;
+ counter += len;
+ MD5Update(&md5ctx, buf, len);
+ }
+
+ rxmd5 = MD5End(&md5ctx, NULL);
+
+ if ((counter != header_length+length) ||
+ memcmp(th.th_md5, rxmd5, 33) != 0)
+ FAIL("receive length mismatch")
+
+ free(rxmd5);
+ return (0);
+}
+
+static void
+run_child(void)
+{
+ struct sockaddr_in sin;
+ int rc = 0;
+
+ listen_socket = socket(PF_INET, SOCK_STREAM, 0);
+ if (listen_socket < 0) {
+ printf("# socket: %s\n", strerror(errno));
+ rc = -1;
+ }
+
+ if (!rc) {
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(TEST_PORT);
+
+ if (bind(listen_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ printf("# bind: %s\n", strerror(errno));
+ rc = -1;
+ }
+ }
+
+ if (!rc && listen(listen_socket, -1) < 0) {
+ printf("# listen: %s\n", strerror(errno));
+ rc = -1;
+ }
+
+ if (!rc) {
+ accept_socket = accept(listen_socket, NULL, NULL);
+ setup_alarm(TEST_SECONDS);
+ if (receive_test() != 0)
+ rc = -1;
+ }
+
+ cancel_alarm();
+ if (accept_socket > 0)
+ close(accept_socket);
+ if (listen_socket > 0)
+ close(listen_socket);
+
+ _exit(rc);
+}
+
+static int
+new_test_socket(int *connect_socket)
+{
+ struct sockaddr_in sin;
+ int rc = 0;
+
+ *connect_socket = socket(PF_INET, SOCK_STREAM, 0);
+ if (*connect_socket < 0)
+ FAIL_ERR("socket")
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(TEST_PORT);
+
+ if (connect(*connect_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ FAIL_ERR("connect")
+
+ return (rc);
+}
+
+static void
+init_th(struct test_header *th, uint32_t header_length, uint32_t offset,
+ uint32_t length)
+{
+ bzero(th, sizeof(*th));
+ th->th_magic = htonl(TEST_MAGIC);
+ th->th_header_length = htonl(header_length);
+ th->th_offset = htonl(offset);
+ th->th_length = htonl(length);
+
+ MD5FileChunk(path, th->th_md5, offset, length);
+}
+
+static int
+send_test(int connect_socket, struct sendfile_test test)
+{
+ struct test_header th;
+ struct sf_hdtr hdtr, *hdtrp;
+ struct iovec headers;
+ char *header;
+ ssize_t len;
+ int length;
+ off_t off;
+
+ len = lseek(file_fd, 0, SEEK_SET);
+ if (len != 0)
+ FAIL_ERR("lseek")
+
+ struct stat st;
+ if (fstat(file_fd, &st) < 0)
+ FAIL_ERR("fstat")
+ length = st.st_size - test.offset;
+ if (test.length > 0 && test.length < (uint32_t)length)
+ length = test.length;
+
+ init_th(&th, test.hdr_length, test.offset, length);
+
+ len = write(connect_socket, &th, sizeof(th));
+ if (len != sizeof(th))
+ return (-1);
+
+ if (test.hdr_length != 0) {
+ header = malloc(test.hdr_length);
+ if (header == NULL)
+ FAIL_ERR("malloc")
+
+ hdtrp = &hdtr;
+ bzero(&headers, sizeof(headers));
+ headers.iov_base = header;
+ headers.iov_len = test.hdr_length;
+ bzero(&hdtr, sizeof(hdtr));
+ hdtr.headers = &headers;
+ hdtr.hdr_cnt = 1;
+ hdtr.trailers = NULL;
+ hdtr.trl_cnt = 0;
+ } else {
+ hdtrp = NULL;
+ header = NULL;
+ }
+
+ if (sendfile(file_fd, connect_socket, test.offset, test.length,
+ hdtrp, &off, 0) < 0) {
+ if (header != NULL)
+ free(header);
+ FAIL_ERR("sendfile")
+ }
+
+ if (length == 0) {
+ struct stat sb;
+
+ if (fstat(file_fd, &sb) == 0)
+ length = sb.st_size - test.offset;
+ }
+
+ if (header != NULL)
+ free(header);
+
+ if (off != length)
+ FAIL("offset != length")
+
+ return (0);
+}
+
+static int
+write_test_file(size_t file_size)
+{
+ char *page_buffer;
+ ssize_t len;
+ static size_t current_file_size = 0;
+
+ if (file_size == current_file_size)
+ return (0);
+ else if (file_size < current_file_size) {
+ if (ftruncate(file_fd, file_size) != 0)
+ FAIL_ERR("ftruncate");
+ current_file_size = file_size;
+ return (0);
+ }
+
+ page_buffer = malloc(file_size);
+ if (page_buffer == NULL)
+ FAIL_ERR("malloc")
+ bzero(page_buffer, file_size);
+
+ len = write(file_fd, page_buffer, file_size);
+ if (len < 0)
+ FAIL_ERR("write")
+
+ len = lseek(file_fd, 0, SEEK_SET);
+ if (len < 0)
+ FAIL_ERR("lseek")
+ if (len != 0)
+ FAIL("len != 0")
+
+ free(page_buffer);
+ current_file_size = file_size;
+ return (0);
+}
+
+static void
+run_parent(void)
+{
+ int connect_socket;
+ int status;
+ int test_num;
+ int test_count;
+ int pid;
+ size_t desired_file_size = 0;
+
+ const int pagesize = getpagesize();
+
+ struct sendfile_test tests[] = {
+ { .hdr_length = 0, .offset = 0, .length = 1 },
+ { .hdr_length = 0, .offset = 0, .length = pagesize },
+ { .hdr_length = 0, .offset = 1, .length = 1 },
+ { .hdr_length = 0, .offset = 1, .length = pagesize },
+ { .hdr_length = 0, .offset = pagesize, .length = pagesize },
+ { .hdr_length = 0, .offset = 0, .length = 2*pagesize },
+ { .hdr_length = 0, .offset = 0, .length = 0 },
+ { .hdr_length = 0, .offset = pagesize, .length = 0 },
+ { .hdr_length = 0, .offset = 2*pagesize, .length = 0 },
+ { .hdr_length = 0, .offset = TEST_PAGES*pagesize, .length = 0 },
+ { .hdr_length = 0, .offset = 0, .length = pagesize,
+ .file_size = 1 }
+ };
+
+ test_count = sizeof(tests) / sizeof(tests[0]);
+ printf("1..%d\n", test_count);
+
+ for (test_num = 1; test_num <= test_count; test_num++) {
+
+ desired_file_size = tests[test_num - 1].file_size;
+ if (desired_file_size == 0)
+ desired_file_size = TEST_PAGES * pagesize;
+ if (write_test_file(desired_file_size) != 0) {
+ printf("not ok %d\n", test_num);
+ continue;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ printf("not ok %d\n", test_num);
+ continue;
+ }
+
+ if (pid == 0)
+ run_child();
+
+ usleep(250000);
+
+ if (new_test_socket(&connect_socket) != 0) {
+ printf("not ok %d\n", test_num);
+ kill(pid, SIGALRM);
+ close(connect_socket);
+ continue;
+ }
+
+ if (send_test(connect_socket, tests[test_num-1]) != 0) {
+ printf("not ok %d\n", test_num);
+ kill(pid, SIGALRM);
+ close(connect_socket);
+ continue;
+ }
+
+ close(connect_socket);
+ if (waitpid(pid, &status, 0) == pid) {
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ printf("%s %d\n", "ok", test_num);
+ else
+ printf("%s %d\n", "not ok", test_num);
+ }
+ else {
+ printf("not ok %d\n", test_num);
+ }
+ }
+}
+
+static void
+cleanup(void)
+{
+
+ unlink(path);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int pagesize;
+
+ path[0] = '\0';
+
+ pagesize = getpagesize();
+
+ if (argc == 1) {
+ snprintf(path, sizeof(path), "sendfile.XXXXXXXXXXXX");
+ file_fd = mkstemp(path);
+ if (file_fd == -1)
+ FAIL_ERR("mkstemp");
+ } else if (argc == 2) {
+ (void)strlcpy(path, argv[1], sizeof(path));
+ file_fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600);
+ if (file_fd == -1)
+ FAIL_ERR("open");
+ } else {
+ FAIL("usage: sendfile [path]");
+ }
+
+ atexit(cleanup);
+
+ run_parent();
+ return (0);
+}
diff --git a/tools/regression/sockets/shutdown/Makefile b/tools/regression/sockets/shutdown/Makefile
new file mode 100644
index 0000000..63045f5
--- /dev/null
+++ b/tools/regression/sockets/shutdown/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= shutdown
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/shutdown/shutdown.c b/tools/regression/sockets/shutdown/shutdown.c
new file mode 100644
index 0000000..3d23c94
--- /dev/null
+++ b/tools/regression/sockets/shutdown/shutdown.c
@@ -0,0 +1,110 @@
+/*-
+ * Copyright (C) 2005 The FreeBSD Project. 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/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+ struct sockaddr_in sock;
+ socklen_t len;
+ int listen_sock, connect_sock;
+ u_short port;
+
+ listen_sock = -1;
+
+ /* Shutdown(2) on an invalid file descriptor has to return EBADF. */
+ if ((shutdown(listen_sock, SHUT_RDWR) != -1) && (errno != EBADF))
+ errx(-1, "shutdown() for invalid file descriptor does not "
+ "return EBADF");
+
+ listen_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (listen_sock == -1)
+ errx(-1,
+ "socket(PF_INET, SOCK_STREAM, 0) for listen socket: %s",
+ strerror(errno));
+
+ bzero(&sock, sizeof(sock));
+ sock.sin_len = sizeof(sock);
+ sock.sin_family = AF_INET;
+ sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sock.sin_port = 0;
+
+ if (bind(listen_sock, (struct sockaddr *)&sock, sizeof(sock)) < 0)
+ errx(-1, "bind(%s, %d) for listen socket: %s",
+ inet_ntoa(sock.sin_addr), sock.sin_port, strerror(errno));
+
+ len = sizeof(sock);
+ if (getsockname(listen_sock, (struct sockaddr *)&sock, &len) < 0)
+ errx(-1, "getsockname() for listen socket: %s",
+ strerror(errno));
+ port = sock.sin_port;
+
+ if (listen(listen_sock, -1) < 0)
+ errx(-1, "listen() for listen socket: %s", strerror(errno));
+
+ connect_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (connect_sock == -1)
+ errx(-1, "socket(PF_INET, SOCK_STREAM, 0) for connect "
+ "socket: %s", strerror(errno));
+
+ bzero(&sock, sizeof(sock));
+ sock.sin_len = sizeof(sock);
+ sock.sin_family = AF_INET;
+ sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sock.sin_port = port;
+
+ if (connect(connect_sock, (struct sockaddr *)&sock, sizeof(sock)) < 0)
+ errx(-1, "connect() for connect socket: %s", strerror(errno));
+ /* Try to pass an invalid flags. */
+ if ((shutdown(connect_sock, SHUT_RD - 1) != -1) && (errno != EINVAL))
+ errx(-1, "shutdown(SHUT_RD - 1) does not return EINVAL");
+ if ((shutdown(connect_sock, SHUT_RDWR + 1) != -1) && (errno != EINVAL))
+ errx(-1, "shutdown(SHUT_RDWR + 1) does not return EINVAL");
+
+ if (shutdown(connect_sock, SHUT_RD) < 0)
+ errx(-1, "shutdown(SHUT_RD) for connect socket: %s",
+ strerror(errno));
+ if (shutdown(connect_sock, SHUT_WR) < 0)
+ errx(-1, "shutdown(SHUT_WR) for connect socket: %s",
+ strerror(errno));
+
+ close(connect_sock);
+ close(listen_sock);
+
+ return (0);
+}
diff --git a/tools/regression/sockets/sigpipe/Makefile b/tools/regression/sockets/sigpipe/Makefile
new file mode 100644
index 0000000..5116701
--- /dev/null
+++ b/tools/regression/sockets/sigpipe/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= sigpipe
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/sigpipe/sigpipe.c b/tools/regression/sockets/sigpipe/sigpipe.c
new file mode 100644
index 0000000..894d9de
--- /dev/null
+++ b/tools/regression/sockets/sigpipe/sigpipe.c
@@ -0,0 +1,322 @@
+/*-
+ * 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/socket.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * This regression test is intended to verify whether or not SIGPIPE is
+ * properly generated in several simple test cases, as well as testing
+ * whether SO_NOSIGPIPE disables SIGPIPE, if available on the system.
+ * SIGPIPE is generated if a write or send is attempted on a socket that has
+ * been shutdown for write. This test runs several test cases with UNIX
+ * domain sockets and TCP sockets to confirm that either EPIPE or SIGPIPE is
+ * properly returned.
+ *
+ * For the purposes of testing TCP, an unused port number must be specified.
+ */
+static void
+usage(void)
+{
+
+ errx(-1, "usage: sigpipe tcpport");
+}
+
+/*
+ * Signal catcher. Set a global flag that can be tested by the caller.
+ */
+static int signaled;
+static int
+got_signal(void)
+{
+
+ return (signaled);
+}
+
+static void
+signal_handler(int signum __unused)
+{
+
+ signaled = 1;
+}
+
+static void
+signal_setup(const char *testname)
+{
+
+ signaled = 0;
+ if (signal(SIGPIPE, signal_handler) == SIG_ERR)
+ err(-1, "%s: signal(SIGPIPE)", testname);
+}
+
+static void
+test_send(const char *testname, int sock)
+{
+ ssize_t len;
+ char ch;
+
+ ch = 0;
+ len = send(sock, &ch, sizeof(ch), 0);
+ if (len < 0) {
+ if (errno == EPIPE)
+ return;
+ err(-1, "%s: send", testname);
+ }
+ errx(-1, "%s: send: returned %zd", testname, len);
+}
+
+static void
+test_write(const char *testname, int sock)
+{
+ ssize_t len;
+ char ch;
+
+ ch = 0;
+ len = write(sock, &ch, sizeof(ch));
+ if (len < 0) {
+ if (errno == EPIPE)
+ return;
+ err(-1, "%s: write", testname);
+ }
+ errx(-1, "%s: write: returned %zd", testname, len);
+}
+
+static void
+test_send_wantsignal(const char *testname, int sock1, int sock2)
+{
+
+ if (shutdown(sock2, SHUT_WR) < 0)
+ err(-1, "%s: shutdown", testname);
+ signal_setup(testname);
+ test_send(testname, sock2);
+ if (!got_signal())
+ errx(-1, "%s: send: didn't receive SIGPIPE", testname);
+ close(sock1);
+ close(sock2);
+}
+
+#ifdef SO_NOSIGPIPE
+static void
+test_send_dontsignal(const char *testname, int sock1, int sock2)
+{
+ int i;
+
+ i = 1;
+ if (setsockopt(sock2, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) < 0)
+ err(-1, "%s: setsockopt(SOL_SOCKET, SO_NOSIGPIPE)", testname);
+ if (shutdown(sock2, SHUT_WR) < 0)
+ err(-1, "%s: shutdown", testname);
+ signal_setup(testname);
+ test_send(testname, sock2);
+ if (got_signal())
+ errx(-1, "%s: send: got SIGPIPE", testname);
+ close(sock1);
+ close(sock2);
+}
+#endif
+
+static void
+test_write_wantsignal(const char *testname, int sock1, int sock2)
+{
+
+ if (shutdown(sock2, SHUT_WR) < 0)
+ err(-1, "%s: shutdown", testname);
+ signal_setup(testname);
+ test_write(testname, sock2);
+ if (!got_signal())
+ errx(-1, "%s: write: didn't receive SIGPIPE", testname);
+ close(sock1);
+ close(sock2);
+}
+
+#ifdef SO_NOSIGPIPE
+static void
+test_write_dontsignal(const char *testname, int sock1, int sock2)
+{
+ int i;
+
+ i = 1;
+ if (setsockopt(sock2, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) < 0)
+ err(-1, "%s: setsockopt(SOL_SOCKET, SO_NOSIGPIPE)", testname);
+ if (shutdown(sock2, SHUT_WR) < 0)
+ err(-1, "%s: shutdown", testname);
+ signal_setup(testname);
+ test_write(testname, sock2);
+ if (got_signal())
+ errx(-1, "%s: write: got SIGPIPE", testname);
+ close(sock1);
+ close(sock2);
+}
+#endif
+
+static int listen_sock;
+static void
+tcp_setup(u_short port)
+{
+ struct sockaddr_in sin;
+
+ listen_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (listen_sock < 0)
+ err(-1, "tcp_setup: listen");
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(port);
+
+ if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "tcp_setup: bind");
+
+ if (listen(listen_sock, -1) < 0)
+ err(-1, "tcp_setup: listen");
+}
+
+static void
+tcp_teardown(void)
+{
+
+ close(listen_sock);
+}
+
+static void
+tcp_pair(u_short port, int sock[2])
+{
+ int accept_sock, connect_sock;
+ struct sockaddr_in sin;
+ socklen_t len;
+
+ connect_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (connect_sock < 0)
+ err(-1, "tcp_pair: socket");
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(port);
+
+ if (connect(connect_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "tcp_pair: connect");
+
+ sleep(1); /* Time for TCP to settle. */
+
+ len = sizeof(sin);
+ accept_sock = accept(listen_sock, (struct sockaddr *)&sin, &len);
+ if (accept_sock < 0)
+ err(-1, "tcp_pair: accept");
+
+ sleep(1); /* Time for TCP to settle. */
+
+ sock[0] = accept_sock;
+ sock[1] = connect_sock;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *dummy;
+ int sock[2];
+ long port;
+
+ if (argc != 2)
+ usage();
+
+ port = strtol(argv[1], &dummy, 10);
+ if (port < 0 || port > 65535 || *dummy != '\0')
+ usage();
+
+#ifndef SO_NOSIGPIPE
+ warnx("sigpipe: SO_NOSIGPIPE not defined, skipping some tests");
+#endif
+
+ /*
+ * UNIX domain socketpair().
+ */
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0)
+ err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)");
+ test_send_wantsignal("test_send_wantsignal(PF_LOCAL)", sock[0],
+ sock[1]);
+
+#ifdef SO_NOSIGPIPE
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0)
+ err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)");
+ test_send_dontsignal("test_send_dontsignal(PF_LOCAL)", sock[0],
+ sock[1]);
+#endif
+
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0)
+ err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)");
+ test_write_wantsignal("test_write_wantsignal(PF_LOCAL)", sock[0],
+ sock[1]);
+
+#ifdef SO_NOSIGPIPE
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0)
+ err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)");
+ test_write_dontsignal("test_write_dontsignal(PF_LOCAL)", sock[0],
+ sock[1]);
+#endif
+
+ /*
+ * TCP.
+ */
+ tcp_setup(port);
+ tcp_pair(port, sock);
+ test_send_wantsignal("test_send_wantsignal(PF_INET)", sock[0],
+ sock[1]);
+
+#ifdef SO_NOSIGPIPE
+ tcp_pair(port, sock);
+ test_send_dontsignal("test_send_dontsignal(PF_INET)", sock[0],
+ sock[1]);
+#endif
+
+ tcp_pair(port, sock);
+ test_write_wantsignal("test_write_wantsignal(PF_INET)", sock[0],
+ sock[1]);
+
+#ifdef SO_NOSIGPIPE
+ tcp_pair(port, sock);
+ test_write_dontsignal("test_write_dontsignal(PF_INET)", sock[0],
+ sock[1]);
+#endif
+ tcp_teardown();
+
+ fprintf(stderr, "PASS\n");
+ return (0);
+}
diff --git a/tools/regression/sockets/so_setfib/Makefile b/tools/regression/sockets/so_setfib/Makefile
new file mode 100644
index 0000000..90111fb
--- /dev/null
+++ b/tools/regression/sockets/so_setfib/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= so_setfib
+MAN=
+WARNS?= 6
+
+.if ${MK_INET} != "no"
+CFLAGS+= -DINET
+.endif
+.if ${MK_INET6} != "no"
+CFLAGS+= -DINET6
+.endif
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/so_setfib/so_setfib.c b/tools/regression/sockets/so_setfib/so_setfib.c
new file mode 100644
index 0000000..50cb020
--- /dev/null
+++ b/tools/regression/sockets/so_setfib/so_setfib.c
@@ -0,0 +1,200 @@
+/*-
+ * Copyright (c) 2012 Cisco Systems, Inc.
+ * All rights reserved.
+ *
+ * This software was developed by Bjoern Zeeb under contract to
+ * Cisco Systems, 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$
+ */
+
+/*
+ * Regression test on SO_SETFIB setsockopt(2).
+ *
+ * Check that the expected domain(9) families all handle the socket option
+ * correctly and do proper bounds checks.
+ *
+ * Test plan:
+ * 1. Get system wide number of FIBs from sysctl and convert to index (-= 1).
+ * 2. For each protocol family (INET, INET6, ROUTE and LOCAL) open socketes of
+ * type (STREAM, DGRAM and RAW) as supported.
+ * 3. Do a sequence of -2, -1, 0, .. n, n+1, n+2 SO_SETFIB sockopt calls,
+ * expecting the first two and last two to fail (valid 0 ... n).
+ * 4. Try 3 random numbers. Calculate result based on valid range.
+ * 5. Repeat for next domain family and type from (2) on.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static struct t_dom {
+ int domain;
+ const char *name;
+} t_dom[] = {
+#ifdef INET6
+ { .domain = PF_INET6, .name = "PF_INET6" },
+#endif
+#ifdef INET
+ { .domain = PF_INET, .name = "PF_INET" },
+#endif
+ { .domain = PF_ROUTE, .name = "PF_ROUTE" },
+ { .domain = PF_LOCAL, .name = "PF_LOCAL" },
+};
+
+static struct t_type {
+ int type;
+ const char *name;
+} t_type[] = {
+ { .type = SOCK_STREAM, .name = "SOCK_STREAM" },
+ { .type = SOCK_DGRAM, .name = "SOCK_DGRAM" },
+ { .type = SOCK_RAW, .name = "SOCK_RAW" },
+};
+
+/*
+ * Number of FIBs as read from net.fibs sysctl - 1. Initialize to clear out of
+ * bounds value to not accidentally run on a limited range.
+ */
+static int rt_numfibs = -42;
+
+/* Number of test case. */
+static int testno = 1;
+
+
+/*
+ * Try the setsockopt with given FIB number i on socket s.
+ * Handle result given on error and valid range and errno.
+ */
+static void
+so_setfib(int s, int i, u_int dom, u_int type)
+{
+ int error;
+
+ error = setsockopt(s, SOL_SOCKET, SO_SETFIB, &i, sizeof(i));
+ /* For out of bounds we expect an error. */
+ if (error == -1 && (i < 0 || i > rt_numfibs))
+ printf("ok %d %s_%s_%d\n", testno, t_dom[dom].name,
+ t_type[type].name, i);
+ else if (error != -1 && (i < 0 || i > rt_numfibs))
+ printf("not ok %d %s_%s_%d # setsockopt(%d, SOL_SOCKET, "
+ "SO_SETFIB, %d, ..) unexpectedly succeeded\n", testno,
+ t_dom[dom].name, t_type[type].name, i, s, i);
+ else if (error == 0)
+ printf("ok %d %s_%s_%d\n", testno, t_dom[dom].name,
+ t_type[type].name, i);
+ else if (errno != EINVAL)
+ printf("not ok %d %s_%s_%d # setsockopt(%d, SOL_SOCKET, "
+ "SO_SETFIB, %d, ..) unexpected error: %s\n", testno,
+ t_dom[dom].name, t_type[type].name, i, s, i,
+ strerror(errno));
+ else
+ printf("not ok %d %s_%s_%d\n", testno, t_dom[dom].name,
+ t_type[type].name, i);
+
+ /* Test run done, next please. */
+ testno++;
+}
+
+/*
+ * Main test. Open socket given domain family and type. For each FIB, out of
+ * bounds FIB numbers and 3 random FIB numbers set the socket option.
+ */
+static void
+t(u_int dom, u_int type)
+{
+ int i, s;
+
+ /* PF_ROUTE only supports RAW socket types, while PF_LOCAL does not. */
+ if (t_dom[dom].domain == PF_ROUTE && t_type[type].type != SOCK_RAW)
+ return;
+ if (t_dom[dom].domain == PF_LOCAL && t_type[type].type == SOCK_RAW)
+ return;
+
+ /* Open socket for given combination. */
+ s = socket(t_dom[dom].domain, t_type[type].type, 0);
+ if (s == -1) {
+ printf("not ok %d %s_%s # socket(): %s\n", testno,
+ t_dom[dom].name, t_type[type].name, strerror(errno));
+ testno++;
+ return;
+ }
+
+ /* Test FIBs -2, -1, 0, .. n, n + 1, n + 2. */
+ for (i = -2; i <= (rt_numfibs + 2); i++)
+ so_setfib(s, i, dom, type);
+
+ /* Test 3 random FIB numbers. */
+ for (i = 0; i < 3; i++)
+ so_setfib(s, (int)random(), dom, type);
+
+ /* Close socket. */
+ close(s);
+}
+
+/*
+ * Returns 0 if no program error, 1 on sysctlbyname error.
+ * Test results are communicated by printf("[not ]ok <n> ..").
+ */
+int
+main(int argc __unused, char *argv[] __unused)
+{
+ u_int i, j;
+ size_t s;
+
+ if (geteuid() != 0) {
+ printf("1..0 # SKIP: must be root");
+ return (0);
+ }
+
+ /* Initalize randomness. */
+ srandomdev();
+
+ /* Get number of FIBs supported by kernel. */
+ s = sizeof(rt_numfibs);
+ if (sysctlbyname("net.fibs", &rt_numfibs, &s, NULL, 0) == -1)
+ err(1, "sysctlbyname(net.fibs, ..)");
+
+ printf("1..%lu\n",
+ (nitems(t_dom) - 1) * nitems(t_type) * (2 + rt_numfibs + 2 + 3));
+
+ /* Adjust from number to index. */
+ rt_numfibs -= 1;
+
+ /* Run tests. */
+ for (i = 0; i < sizeof(t_dom) / sizeof(struct t_dom); i++)
+ for (j = 0; j < sizeof(t_type) / sizeof(struct t_type); j++)
+ t(i, j);
+
+ return (0);
+}
+
+/* end */
diff --git a/tools/regression/sockets/socketpair/Makefile b/tools/regression/sockets/socketpair/Makefile
new file mode 100644
index 0000000..ed66e3c
--- /dev/null
+++ b/tools/regression/sockets/socketpair/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+
+PROG= socketpair
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/socketpair/socketpair.c b/tools/regression/sockets/socketpair/socketpair.c
new file mode 100644
index 0000000..779878a
--- /dev/null
+++ b/tools/regression/sockets/socketpair/socketpair.c
@@ -0,0 +1,161 @@
+/*-
+ * Copyright (c) 2004 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/socket.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Open, then close a set of UNIX domain socket pairs for datagram and
+ * stream.
+ *
+ * Confirm that we can't open INET datagram or stream socket pairs.
+ *
+ * More tests should be added, including confirming that sending on either
+ * endpoint results in data at the other, that the right kind of socket was
+ * created (stream vs. datagram), and that message boundaries fall in the
+ * right places.
+ */
+int
+main(void)
+{
+ int fd1, fd2, fd3;
+ int sv[2];
+
+ /*
+ * UNIX domain socket pair, datagram.
+ */
+ if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM): %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+ if (close(sv[0]) != 0) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM) close 0: %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+ if (close(sv[1]) != 0) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM) close 1: %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+
+ /*
+ * UNIX domain socket pair, stream.
+ */
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) != 0) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_STREAM): %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+ if (close(sv[0]) != 0) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_STREAM) close 0: %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+ if (close(sv[1]) != 0) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_STREAM) close 1: "
+ "%s\n", strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+
+ /*
+ * Confirm that PF_INET datagram socket pair creation fails.
+ */
+ if (socketpair(PF_INET, SOCK_DGRAM, 0, sv) == 0) {
+ fprintf(stderr, "socketpair(PF_INET, SOCK_DGRAM): opened\n");
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+ if (errno != EOPNOTSUPP) {
+ fprintf(stderr, "socketpair(PF_INET, SOCK_DGRAM): %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ }
+
+ /*
+ * Confirm that PF_INET stream socket pair creation fails.
+ */
+ if (socketpair(PF_INET, SOCK_STREAM, 0, sv) == 0) {
+ fprintf(stderr, "socketpair(PF_INET, SOCK_STREAM): opened\n");
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+ if (errno != EOPNOTSUPP) {
+ fprintf(stderr, "socketpair(PF_INET, SOCK_STREAM): %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ }
+
+ /*
+ * Check for sequential fd allocation, and give up early if not.
+ */
+ fd1 = dup(STDIN_FILENO);
+ fd2 = dup(STDIN_FILENO);
+ if (fd2 != fd1 + 1) {
+ fprintf(stderr, "Non-sequential fd allocation\n");
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+
+ /* Allocate a socketpair using a bad destination address. */
+ if (socketpair(PF_UNIX, SOCK_DGRAM, 0, NULL) == 0) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM, NULL): opened\n");
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+ if (errno != EFAULT) {
+ fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM, NULL): %s\n",
+ strerror(errno));
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+
+ /* Allocate a file descriptor and make sure it's fd2+1. */
+ fd3 = dup(STDIN_FILENO);
+ if (fd3 != fd2 + 1) {
+ fprintf(stderr, "socketpair(..., NULL) allocated descriptors\n");
+ fprintf(stderr, "FAIL\n");
+ exit(-1);
+ }
+
+ fprintf(stderr, "PASS\n");
+ exit(0);
+}
diff --git a/tools/regression/sockets/unix_bindconnect/Makefile b/tools/regression/sockets/unix_bindconnect/Makefile
new file mode 100644
index 0000000..52cc844
--- /dev/null
+++ b/tools/regression/sockets/unix_bindconnect/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_bindconnect
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_bindconnect/unix_bindconnect.c b/tools/regression/sockets/unix_bindconnect/unix_bindconnect.c
new file mode 100644
index 0000000..079dc4d
--- /dev/null
+++ b/tools/regression/sockets/unix_bindconnect/unix_bindconnect.c
@@ -0,0 +1,318 @@
+/*-
+ * 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/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Simple regression test to exercise some error cases relating to the use of
+ * bind() and connect() on UNIX domain sockets. In particular, make sure
+ * that when two sockets rendezvous using the file system name space, they
+ * get the expected success/failure cases.
+ *
+ * TODO:
+ * - Check that the resulting file mode/owner are right.
+ * - Do the same tests with UNIX domain sockets.
+ * - Check the results of getsockaddr() and getpeeraddr().
+ */
+
+#define SOCK_NAME_ONE "socket.1"
+#define SOCK_NAME_TWO "socket.2"
+
+#define UNWIND_MAX 1024
+
+static int unwind_len;
+static struct unwind {
+ char u_path[PATH_MAX];
+} unwind_list[UNWIND_MAX];
+
+static void
+push_path(const char *path)
+{
+
+ if (unwind_len >= UNWIND_MAX)
+ err(-1, "push_path: one path too many (%s)", path);
+
+ strlcpy(unwind_list[unwind_len].u_path, path, PATH_MAX);
+ unwind_len++;
+}
+
+static void
+unwind(void)
+{
+ int i;
+
+ for (i = unwind_len - 1; i >= 0; i--) {
+ unlink(unwind_list[i].u_path);
+ rmdir(unwind_list[i].u_path);
+ }
+}
+
+static int
+bind_test(const char *directory_path)
+{
+ char socket_path[PATH_MAX];
+ struct sockaddr_un sun;
+ int sock1, sock2;
+
+ sock1 = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock1 < 0) {
+ warn("bind_test: socket(PF_UNIX, SOCK_STREAM, 0)");
+ return (-1);
+ }
+
+ if (snprintf(socket_path, sizeof(socket_path), "%s/%s",
+ directory_path, SOCK_NAME_ONE) >= PATH_MAX) {
+ warn("bind_test: snprintf(socket_path)");
+ close(sock1);
+ return (-1);
+ }
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_len = sizeof(sun);
+ sun.sun_family = AF_UNIX;
+ if (snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", socket_path)
+ >= (int)sizeof(sun.sun_path)) {
+ warn("bind_test: snprintf(sun.sun_path)");
+ close(sock1);
+ return (-1);
+ }
+
+ if (bind(sock1, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
+ warn("bind_test: bind(sun) #1");
+ close(sock1);
+ return (-1);
+ }
+
+ push_path(socket_path);
+
+ /*
+ * Once a STREAM UNIX domain socket has been bound, it can't be
+ * rebound. Expected error is EINVAL.
+ */
+ if (bind(sock1, (struct sockaddr *)&sun, sizeof(sun)) == 0) {
+ warnx("bind_test: bind(sun) #2 succeeded");
+ close(sock1);
+ return (-1);
+ }
+ if (errno != EINVAL) {
+ warn("bind_test: bind(sun) #2");
+ close(sock1);
+ return (-1);
+ }
+
+ sock2 = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock2 < 0) {
+ warn("bind_test: socket(PF_UNIX, SOCK_STREAM, 0)");
+ close(sock1);
+ return (-1);
+ }
+
+ /*
+ * Since a socket is already bound to the pathname, it can't be bound
+ * to a second socket. Expected error is EADDRINUSE.
+ */
+ if (bind(sock2, (struct sockaddr *)&sun, sizeof(sun)) == 0) {
+ warnx("bind_test: bind(sun) #3 succeeded");
+ close(sock1);
+ close(sock2);
+ return (-1);
+ }
+ if (errno != EADDRINUSE) {
+ warn("bind_test: bind(sun) #2");
+ close(sock1);
+ close(sock2);
+ return (-1);
+ }
+
+ close(sock1);
+
+ /*
+ * The socket bound to the pathname has been closed, but the pathname
+ * can't be reused without first being unlinked. Expected error is
+ * EADDRINUSE.
+ */
+ if (bind(sock2, (struct sockaddr *)&sun, sizeof(sun)) == 0) {
+ warnx("bind_test: bind(sun) #4 succeeded");
+ close(sock2);
+ return (-1);
+ }
+ if (errno != EADDRINUSE) {
+ warn("bind_test: bind(sun) #4");
+ close(sock2);
+ return (-1);
+ }
+
+ unlink(socket_path);
+
+ /*
+ * The pathname is now free, so the socket should be able to bind to
+ * it.
+ */
+ if (bind(sock2, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
+ warn("bind_test: bind(sun) #5");
+ close(sock2);
+ return (-1);
+ }
+
+ close(sock2);
+ return (0);
+}
+
+static int
+connect_test(const char *directory_path)
+{
+ char socket_path[PATH_MAX];
+ struct sockaddr_un sun;
+ int sock1, sock2;
+
+ sock1 = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock1 < 0) {
+ warn("connect_test: socket(PF_UNIX, SOCK_STREAM, 0)");
+ return (-1);
+ }
+
+ if (snprintf(socket_path, sizeof(socket_path), "%s/%s",
+ directory_path, SOCK_NAME_TWO) >= PATH_MAX) {
+ warn("connect_test: snprintf(socket_path)");
+ close(sock1);
+ return (-1);
+ }
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_len = sizeof(sun);
+ sun.sun_family = AF_UNIX;
+ if (snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", socket_path)
+ >= (int)sizeof(sun.sun_path)) {
+ warn("connect_test: snprintf(sun.sun_path)");
+ close(sock1);
+ return (-1);
+ }
+
+ /*
+ * Try connecting to a path that doesn't yet exist. Should fail with
+ * ENOENT.
+ */
+ if (connect(sock1, (struct sockaddr *)&sun, sizeof(sun)) == 0) {
+ warnx("connect_test: connect(sun) #1 succeeded");
+ close(sock1);
+ return (-1);
+ }
+ if (errno != ENOENT) {
+ warn("connect_test: connect(sun) #1");
+ close(sock1);
+ return (-1);
+ }
+
+ if (bind(sock1, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
+ warn("connect_test: bind(sun) #1");
+ close(sock1);
+ return (-1);
+ }
+
+ if (listen(sock1, 3) < 0) {
+ warn("connect_test: listen(sock1)");
+ close(sock1);
+ return (-1);
+ }
+
+ push_path(socket_path);
+
+ sock2 = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock2 < 0) {
+ warn("socket(PF_UNIX, SOCK_STREAM, 0)");
+ close(sock1);
+ return (-1);
+ }
+
+ /*
+ * Do a simple connect and make sure that works.
+ */
+ if (connect(sock2, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
+ warn("connect(sun) #2");
+ close(sock1);
+ return (-1);
+ }
+
+ close(sock2);
+
+ close(sock1);
+
+ sock2 = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock2 < 0) {
+ warn("socket(PF_UNIX, SOCK_STREAM, 0)");
+ return (-1);
+ }
+
+ /*
+ * Confirm that once the listen socket is closed, we get a
+ * connection refused (ECONNREFUSED) when attempting to connect to
+ * the pathname.
+ */
+ if (connect(sock2, (struct sockaddr *)&sun, sizeof(sun)) == 0) {
+ warnx("connect(sun) #3 succeeded");
+ close(sock2);
+ return (-1);
+ }
+ if (errno != ECONNREFUSED) {
+ warn("connect(sun) #3");
+ close(sock2);
+ return (-1);
+ }
+
+ close(sock2);
+ unlink(socket_path);
+ return (0);
+}
+int
+main(void)
+{
+ char directory_path[PATH_MAX];
+ int error;
+
+ strlcpy(directory_path, "/tmp/unix_bind.XXXXXXX", PATH_MAX);
+ if (mkdtemp(directory_path) == NULL)
+ err(-1, "mkdtemp");
+ push_path(directory_path);
+
+ error = bind_test(directory_path);
+
+ if (error == 0)
+ error = connect_test(directory_path);
+
+ unwind();
+ return (error);
+}
diff --git a/tools/regression/sockets/unix_close_race/Makefile b/tools/regression/sockets/unix_close_race/Makefile
new file mode 100644
index 0000000..370adc4
--- /dev/null
+++ b/tools/regression/sockets/unix_close_race/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_close_race
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_close_race/unix_close_race.c b/tools/regression/sockets/unix_close_race/unix_close_race.c
new file mode 100644
index 0000000..89c1b20
--- /dev/null
+++ b/tools/regression/sockets/unix_close_race/unix_close_race.c
@@ -0,0 +1,143 @@
+/*-
+ * Copyright (c) 2010 Mikolaj Golub
+ * 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$
+ */
+
+/*
+ * This regression test attempts to trigger a race that occurs when both
+ * endpoints of a connected UNIX domain socket are closed at once. The two
+ * close paths may run concurrently leading to a call to sodisconnect() on an
+ * already-closed socket in kernel. Before it was fixed, this might lead to
+ * ENOTCONN being returned improperly from close().
+ *
+ * This race is fairly timing-dependent, so it effectively requires SMP, and
+ * may not even trigger then.
+ */
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+static char socket_path[] = "tmp.XXXXXXXX";
+
+#define USLEEP 100
+#define LOOPS 100000
+
+int
+main(void)
+{
+ struct sockaddr_un servaddr;
+ int listenfd, connfd, pid;
+ u_int counter, ncpus;
+ size_t len;
+
+ len = sizeof(ncpus);
+ if (sysctlbyname("kern.smp.cpus", &ncpus, &len, NULL, 0) < 0)
+ err(1, "kern.smp.cpus");
+ if (len != sizeof(ncpus))
+ errx(1, "kern.smp.cpus: invalid length");
+ if (ncpus < 2)
+ warnx("SMP not present, test may be unable to trigger race");
+
+ if (mkstemp(socket_path) == -1)
+ err(1, "mkstemp failed");
+ unlink(socket_path);
+
+ /*
+ * Create a UNIX domain socket that the child will repeatedly
+ * accept() from, and that the parent will repeatedly connect() to.
+ */
+ if ((listenfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
+ err(1, "parent: socket error");
+ (void)unlink(socket_path);
+ bzero(&servaddr, sizeof(servaddr));
+ servaddr.sun_family = AF_LOCAL;
+ strcpy(servaddr.sun_path, socket_path);
+ if (bind(listenfd, (struct sockaddr *) &servaddr,
+ sizeof(servaddr)) < 0)
+ err(1, "parent: bind error");
+ if (listen(listenfd, 1024) < 0)
+ err(1, "parent: listen error");
+
+ pid = fork();
+ if (pid == -1)
+ err(1, "fork()");
+ if (pid != 0) {
+ /*
+ * In the parent, repeatedly connect and disconnect from the
+ * socket, attempting to induce the race.
+ */
+ close(listenfd);
+ sleep(1);
+ bzero(&servaddr, sizeof(servaddr));
+ servaddr.sun_family = AF_LOCAL;
+ strcpy(servaddr.sun_path, socket_path);
+ for (counter = 0; counter < LOOPS; counter++) {
+ if ((connfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
+ (void)kill(pid, SIGTERM);
+ err(1, "parent: socket error");
+ }
+ if (connect(connfd, (struct sockaddr *)&servaddr,
+ sizeof(servaddr)) < 0) {
+ (void)kill(pid, SIGTERM);
+ err(1, "parent: connect error");
+ }
+ if (close(connfd) < 0) {
+ (void)kill(pid, SIGTERM);
+ err(1, "parent: close error");
+ }
+ usleep(USLEEP);
+ }
+ (void)kill(pid, SIGTERM);
+ } else {
+ /*
+ * In the child, loop accepting and closing. We may pick up
+ * the race here so report errors from close().
+ */
+ for ( ; ; ) {
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)NULL, NULL)) < 0)
+ err(1, "child: accept error");
+ if (close(connfd) < 0)
+ err(1, "child: close error");
+ }
+ }
+ printf("OK\n");
+ exit(0);
+}
diff --git a/tools/regression/sockets/unix_cmsg/Makefile b/tools/regression/sockets/unix_cmsg/Makefile
new file mode 100644
index 0000000..9ccab6d
--- /dev/null
+++ b/tools/regression/sockets/unix_cmsg/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_cmsg
+MAN=
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_cmsg/README b/tools/regression/sockets/unix_cmsg/README
new file mode 100644
index 0000000..df51723
--- /dev/null
+++ b/tools/regression/sockets/unix_cmsg/README
@@ -0,0 +1,160 @@
+$FreeBSD$
+
+About unix_cmsg
+===============
+
+This program is a collection of regression tests for ancillary data
+(control information) for PF_LOCAL sockets (local domain or Unix domain
+sockets). There are tests for stream and datagram sockets.
+
+Usually each test does following steps: creates Server, forks Client,
+Client sends something to Server, Server verifies whether everything is
+correct in received message(s).
+
+It is better to change the owner of unix_cmsg to some safe user
+(eg. nobody:nogroup) and set SUID and SGID bits, else some tests that
+check credentials can give correct results for wrong implementation.
+
+It is better to run this program by a user that belongs to more
+than 16 groups.
+
+Available options
+=================
+
+usage: unix_cmsg [-dh] [-n num] [-s size] [-t type] [-z value] [testno]
+
+ Options are:
+ -d Output debugging information
+ -h Output the help message and exit
+ -n num Number of messages to send
+ -s size Specify size of data for IPC
+ -t type Specify socket type (stream, dgram) for tests
+ -z value Do not send data in a message (bit 0x1), do not send
+ data array associated with a cmsghdr structure (bit 0x2)
+ testno Run one test by its number (require the -t option)
+
+Description
+===========
+
+If Client sends something to Server, then it sends 5 messages by default.
+Number of messages can be changed in the -n command line option. Number
+of messages will be given as N in the following descriptions.
+
+If Client sends something to Server, then it sends some data (few bytes)
+in each message by default. The size of this data can be changed by the -s
+command line option. The "-s 0" command line option means, that Client will
+send zero bytes represented by { NULL, 0 } value of struct iovec{}, referenced
+by the msg_iov field from struct msghdr{}. The "-z 1" or "-z 3" command line
+option means, that Client will send zero bytes represented by the NULL value
+in the msg_iov field from struct msghdr{}.
+
+If Client sends some ancillary data object, then this ancillary data object
+always has associated data array by default. The "-z 2" or "-z 3" option
+means, that Client will not send associated data array if possible.
+
+For SOCK_STREAM sockets:
+-----------------------
+
+ 1: Sending, receiving cmsgcred
+
+ Client connects to Server and sends N messages with SCM_CREDS ancillary
+ data object. Server should receive N messages, each message should
+ have SCM_CREDS ancillary data object followed by struct cmsgcred{}.
+
+ 2: Receiving sockcred (listening socket)
+
+ Server creates a listening stream socket and sets the LOCAL_CREDS
+ socket option for it. Client connects to Server two times, each time
+ it sends N messages. Server accepts two connections and receives N
+ messages from each connection. The first message from each connection
+ should have SCM_CREDS ancillary data object followed by struct sockcred{},
+ next messages from the same connection should not have ancillary data.
+
+ 3: Receiving sockcred (accepted socket)
+
+ Client connects to Server. Server accepts connection and sets the
+ LOCAL_CREDS socket option for just accepted socket. Client sends N
+ messages to Server. Server should receive N messages, the first
+ message should have SCM_CREDS ancillary data object followed by
+ struct sockcred{}, next messages should not have ancillary data.
+
+ 4: Sending cmsgcred, receiving sockcred
+
+ Server creates a listening stream socket and sets the LOCAL_CREDS
+ socket option for it. Client connects to Server and sends N messages
+ with SCM_CREDS ancillary data object. Server should receive N messages,
+ the first message should have SCM_CREDS ancillary data object followed
+ by struct sockcred{}, each of next messages should have SCM_CREDS
+ ancillary data object followed by struct cmsgcred{}.
+
+ 5: Sending, receiving timeval
+
+ Client connects to Server and sends message with SCM_TIMESTAMP ancillary
+ data object. Server should receive one message with SCM_TIMESTAMP
+ ancillary data object followed by struct timeval{}.
+
+ 6: Sending, receiving bintime
+
+ Client connects to Server and sends message with SCM_BINTIME ancillary
+ data object. Server should receive one message with SCM_BINTIME
+ ancillary data object followed by struct bintime{}.
+
+ 7: Checking cmsghdr.cmsg_len
+
+ Client connects to Server and tries to send several messages with
+ SCM_CREDS ancillary data object that has wrong cmsg_len field in its
+ struct cmsghdr{}. All these attempts should fail, since cmsg_len
+ in all requests is less than CMSG_LEN(0).
+
+ 8: Check LOCAL_PEERCRED socket option
+
+ This test does not use ancillary data, but can be implemented here.
+ Client connects to Server. Both Client and Server verify that
+ credentials of the peer are correct using LOCAL_PEERCRED socket option.
+
+For SOCK_DGRAM sockets:
+----------------------
+
+ 1: Sending, receiving cmsgcred
+
+ Client connects to Server and sends N messages with SCM_CREDS ancillary
+ data object. Server should receive N messages, each message should
+ have SCM_CREDS ancillary data object followed by struct cmsgcred{}.
+
+ 2: Receiving sockcred
+
+ Server creates datagram socket and sets the LOCAL_CREDS socket option
+ for it. Client sends N messages to Server. Server should receive N
+ messages, each message should have SCM_CREDS ancillary data object
+ followed by struct sockcred{}.
+
+ 3: Sending cmsgcred, receiving sockcred
+
+ Server creates datagram socket and sets the LOCAL_CREDS socket option
+ for it. Client sends N messages with SCM_CREDS ancillary data object
+ to Server. Server should receive N messages, the first message should
+ have SCM_CREDS ancillary data object followed by struct sockcred{},
+ each of next messages should have SCM_CREDS ancillary data object
+ followed by struct cmsgcred{}.
+
+ 4: Sending, receiving timeval
+
+ Client sends one message with SCM_TIMESTAMP ancillary data object
+ to Server. Server should receive one message with SCM_TIMESTAMP
+ ancillary data object followed by struct timeval{}.
+
+ 5: Sending, receiving bintime
+
+ Client sends one message with SCM_BINTIME ancillary data object
+ to Server. Server should receive one message with SCM_BINTIME
+ ancillary data object followed by struct bintime{}.
+
+ 6: Checking cmsghdr.cmsg_len
+
+ Client tries to send Server several messages with SCM_CREDS ancillary
+ data object that has wrong cmsg_len field in its struct cmsghdr{}.
+ All these attempts should fail, since cmsg_len in all requests is less
+ than CMSG_LEN(0).
+
+- Andrey Simonenko
+andreysimonenko@users.sourceforge.net
diff --git a/tools/regression/sockets/unix_cmsg/unix_cmsg.c b/tools/regression/sockets/unix_cmsg/unix_cmsg.c
new file mode 100644
index 0000000..d91cef4
--- /dev/null
+++ b/tools/regression/sockets/unix_cmsg/unix_cmsg.c
@@ -0,0 +1,1969 @@
+/*-
+ * Copyright (c) 2005 Andrey Simonenko
+ * 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$");
+
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/ucred.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * There are tables with tests descriptions and pointers to test
+ * functions. Each t_*() function returns 0 if its test passed,
+ * -1 if its test failed, -2 if some system error occurred.
+ * If a test function returns -2, then a program exits.
+ *
+ * If a test function forks a client process, then it waits for its
+ * termination. If a return code of a client process is not equal
+ * to zero, or if a client process was terminated by a signal, then
+ * a test function returns -1 or -2 depending on exit status of
+ * a client process.
+ *
+ * Each function which can block, is run under TIMEOUT. If timeout
+ * occurs, then a test function returns -2 or a client process exits
+ * with a non-zero return code.
+ */
+
+#ifndef LISTENQ
+# define LISTENQ 1
+#endif
+
+#ifndef TIMEOUT
+# define TIMEOUT 2
+#endif
+
+static int t_cmsgcred(void);
+static int t_sockcred_1(void);
+static int t_sockcred_2(void);
+static int t_cmsgcred_sockcred(void);
+static int t_timeval(void);
+static int t_bintime(void);
+static int t_cmsg_len(void);
+static int t_peercred(void);
+
+struct test_func {
+ int (*func)(void);
+ const char *desc;
+};
+
+static const struct test_func test_stream_tbl[] = {
+ {
+ .func = NULL,
+ .desc = "All tests"
+ },
+ {
+ .func = t_cmsgcred,
+ .desc = "Sending, receiving cmsgcred"
+ },
+ {
+ .func = t_sockcred_1,
+ .desc = "Receiving sockcred (listening socket)"
+ },
+ {
+ .func = t_sockcred_2,
+ .desc = "Receiving sockcred (accepted socket)"
+ },
+ {
+ .func = t_cmsgcred_sockcred,
+ .desc = "Sending cmsgcred, receiving sockcred"
+ },
+ {
+ .func = t_timeval,
+ .desc = "Sending, receiving timeval"
+ },
+ {
+ .func = t_bintime,
+ .desc = "Sending, receiving bintime"
+ },
+ {
+ .func = t_cmsg_len,
+ .desc = "Check cmsghdr.cmsg_len"
+ },
+ {
+ .func = t_peercred,
+ .desc = "Check LOCAL_PEERCRED socket option"
+ }
+};
+
+#define TEST_STREAM_TBL_SIZE \
+ (sizeof(test_stream_tbl) / sizeof(test_stream_tbl[0]))
+
+static const struct test_func test_dgram_tbl[] = {
+ {
+ .func = NULL,
+ .desc = "All tests"
+ },
+ {
+ .func = t_cmsgcred,
+ .desc = "Sending, receiving cmsgcred"
+ },
+ {
+ .func = t_sockcred_2,
+ .desc = "Receiving sockcred"
+ },
+ {
+ .func = t_cmsgcred_sockcred,
+ .desc = "Sending cmsgcred, receiving sockcred"
+ },
+ {
+ .func = t_timeval,
+ .desc = "Sending, receiving timeval"
+ },
+ {
+ .func = t_bintime,
+ .desc = "Sending, receiving bintime"
+ },
+ {
+ .func = t_cmsg_len,
+ .desc = "Check cmsghdr.cmsg_len"
+ }
+};
+
+#define TEST_DGRAM_TBL_SIZE \
+ (sizeof(test_dgram_tbl) / sizeof(test_dgram_tbl[0]))
+
+static bool debug = false;
+static bool server_flag = true;
+static bool send_data_flag = true;
+static bool send_array_flag = true;
+static bool failed_flag = false;
+
+static int sock_type;
+static const char *sock_type_str;
+
+static const char *proc_name;
+
+static char work_dir[] = _PATH_TMP "unix_cmsg.XXXXXXX";
+static int serv_sock_fd;
+static struct sockaddr_un serv_addr_sun;
+
+static struct {
+ char *buf_send;
+ char *buf_recv;
+ size_t buf_size;
+ u_int msg_num;
+} ipc_msg;
+
+#define IPC_MSG_NUM_DEF 5
+#define IPC_MSG_NUM_MAX 10
+#define IPC_MSG_SIZE_DEF 7
+#define IPC_MSG_SIZE_MAX 128
+
+static struct {
+ uid_t uid;
+ uid_t euid;
+ gid_t gid;
+ gid_t egid;
+ gid_t *gid_arr;
+ int gid_num;
+} proc_cred;
+
+static pid_t client_pid;
+
+#define SYNC_SERVER 0
+#define SYNC_CLIENT 1
+#define SYNC_RECV 0
+#define SYNC_SEND 1
+
+static int sync_fd[2][2];
+
+#define LOGMSG_SIZE 128
+
+static void logmsg(const char *, ...) __printflike(1, 2);
+static void logmsgx(const char *, ...) __printflike(1, 2);
+static void dbgmsg(const char *, ...) __printflike(1, 2);
+static void output(const char *, ...) __printflike(1, 2);
+
+static void
+usage(bool verbose)
+{
+ u_int i;
+
+ printf("usage: %s [-dh] [-n num] [-s size] [-t type] "
+ "[-z value] [testno]\n", getprogname());
+ if (!verbose)
+ return;
+ printf("\n Options are:\n\
+ -d Output debugging information\n\
+ -h Output the help message and exit\n\
+ -n num Number of messages to send\n\
+ -s size Specify size of data for IPC\n\
+ -t type Specify socket type (stream, dgram) for tests\n\
+ -z value Do not send data in a message (bit 0x1), do not send\n\
+ data array associated with a cmsghdr structure (bit 0x2)\n\
+ testno Run one test by its number (require the -t option)\n\n");
+ printf(" Available tests for stream sockets:\n");
+ for (i = 0; i < TEST_STREAM_TBL_SIZE; ++i)
+ printf(" %u: %s\n", i, test_stream_tbl[i].desc);
+ printf("\n Available tests for datagram sockets:\n");
+ for (i = 0; i < TEST_DGRAM_TBL_SIZE; ++i)
+ printf(" %u: %s\n", i, test_dgram_tbl[i].desc);
+}
+
+static void
+output(const char *format, ...)
+{
+ char buf[LOGMSG_SIZE];
+ va_list ap;
+
+ va_start(ap, format);
+ if (vsnprintf(buf, sizeof(buf), format, ap) < 0)
+ err(EXIT_FAILURE, "output: vsnprintf failed");
+ write(STDOUT_FILENO, buf, strlen(buf));
+ va_end(ap);
+}
+
+static void
+logmsg(const char *format, ...)
+{
+ char buf[LOGMSG_SIZE];
+ va_list ap;
+ int errno_save;
+
+ errno_save = errno;
+ va_start(ap, format);
+ if (vsnprintf(buf, sizeof(buf), format, ap) < 0)
+ err(EXIT_FAILURE, "logmsg: vsnprintf failed");
+ if (errno_save == 0)
+ output("%s: %s\n", proc_name, buf);
+ else
+ output("%s: %s: %s\n", proc_name, buf, strerror(errno_save));
+ va_end(ap);
+ errno = errno_save;
+}
+
+static void
+vlogmsgx(const char *format, va_list ap)
+{
+ char buf[LOGMSG_SIZE];
+
+ if (vsnprintf(buf, sizeof(buf), format, ap) < 0)
+ err(EXIT_FAILURE, "logmsgx: vsnprintf failed");
+ output("%s: %s\n", proc_name, buf);
+
+}
+
+static void
+logmsgx(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vlogmsgx(format, ap);
+ va_end(ap);
+}
+
+static void
+dbgmsg(const char *format, ...)
+{
+ va_list ap;
+
+ if (debug) {
+ va_start(ap, format);
+ vlogmsgx(format, ap);
+ va_end(ap);
+ }
+}
+
+static int
+run_tests(int type, u_int testno1)
+{
+ const struct test_func *tf;
+ u_int i, testno2, failed_num;
+
+ sock_type = type;
+ if (type == SOCK_STREAM) {
+ sock_type_str = "SOCK_STREAM";
+ tf = test_stream_tbl;
+ i = TEST_STREAM_TBL_SIZE - 1;
+ } else {
+ sock_type_str = "SOCK_DGRAM";
+ tf = test_dgram_tbl;
+ i = TEST_DGRAM_TBL_SIZE - 1;
+ }
+ if (testno1 == 0) {
+ testno1 = 1;
+ testno2 = i;
+ } else
+ testno2 = testno1;
+
+ output("Running tests for %s sockets:\n", sock_type_str);
+ failed_num = 0;
+ for (i = testno1, tf += testno1; i <= testno2; ++tf, ++i) {
+ output(" %u: %s\n", i, tf->desc);
+ switch (tf->func()) {
+ case -1:
+ ++failed_num;
+ break;
+ case -2:
+ logmsgx("some system error or timeout occurred");
+ return (-1);
+ }
+ }
+
+ if (failed_num != 0)
+ failed_flag = true;
+
+ if (testno1 != testno2) {
+ if (failed_num == 0)
+ output("-- all tests passed!\n");
+ else
+ output("-- %u test%s failed!\n",
+ failed_num, failed_num == 1 ? "" : "s");
+ } else {
+ if (failed_num == 0)
+ output("-- test passed!\n");
+ else
+ output("-- test failed!\n");
+ }
+
+ return (0);
+}
+
+static int
+init(void)
+{
+ struct sigaction sigact;
+ size_t idx;
+ int rv;
+
+ proc_name = "SERVER";
+
+ sigact.sa_handler = SIG_IGN;
+ sigact.sa_flags = 0;
+ sigemptyset(&sigact.sa_mask);
+ if (sigaction(SIGPIPE, &sigact, (struct sigaction *)NULL) < 0) {
+ logmsg("init: sigaction");
+ return (-1);
+ }
+
+ if (ipc_msg.buf_size == 0)
+ ipc_msg.buf_send = ipc_msg.buf_recv = NULL;
+ else {
+ ipc_msg.buf_send = malloc(ipc_msg.buf_size);
+ ipc_msg.buf_recv = malloc(ipc_msg.buf_size);
+ if (ipc_msg.buf_send == NULL || ipc_msg.buf_recv == NULL) {
+ logmsg("init: malloc");
+ return (-1);
+ }
+ for (idx = 0; idx < ipc_msg.buf_size; ++idx)
+ ipc_msg.buf_send[idx] = (char)idx;
+ }
+
+ proc_cred.uid = getuid();
+ proc_cred.euid = geteuid();
+ proc_cred.gid = getgid();
+ proc_cred.egid = getegid();
+ proc_cred.gid_num = getgroups(0, (gid_t *)NULL);
+ if (proc_cred.gid_num < 0) {
+ logmsg("init: getgroups");
+ return (-1);
+ }
+ proc_cred.gid_arr = malloc(proc_cred.gid_num *
+ sizeof(*proc_cred.gid_arr));
+ if (proc_cred.gid_arr == NULL) {
+ logmsg("init: malloc");
+ return (-1);
+ }
+ if (getgroups(proc_cred.gid_num, proc_cred.gid_arr) < 0) {
+ logmsg("init: getgroups");
+ return (-1);
+ }
+
+ memset(&serv_addr_sun, 0, sizeof(serv_addr_sun));
+ rv = snprintf(serv_addr_sun.sun_path, sizeof(serv_addr_sun.sun_path),
+ "%s/%s", work_dir, proc_name);
+ if (rv < 0) {
+ logmsg("init: snprintf");
+ return (-1);
+ }
+ if ((size_t)rv >= sizeof(serv_addr_sun.sun_path)) {
+ logmsgx("init: not enough space for socket pathname");
+ return (-1);
+ }
+ serv_addr_sun.sun_family = PF_LOCAL;
+ serv_addr_sun.sun_len = SUN_LEN(&serv_addr_sun);
+
+ return (0);
+}
+
+static int
+client_fork(void)
+{
+ int fd1, fd2;
+
+ if (pipe(sync_fd[SYNC_SERVER]) < 0 ||
+ pipe(sync_fd[SYNC_CLIENT]) < 0) {
+ logmsg("client_fork: pipe");
+ return (-1);
+ }
+ client_pid = fork();
+ if (client_pid == (pid_t)-1) {
+ logmsg("client_fork: fork");
+ return (-1);
+ }
+ if (client_pid == 0) {
+ proc_name = "CLIENT";
+ server_flag = false;
+ fd1 = sync_fd[SYNC_SERVER][SYNC_RECV];
+ fd2 = sync_fd[SYNC_CLIENT][SYNC_SEND];
+ } else {
+ fd1 = sync_fd[SYNC_SERVER][SYNC_SEND];
+ fd2 = sync_fd[SYNC_CLIENT][SYNC_RECV];
+ }
+ if (close(fd1) < 0 || close(fd2) < 0) {
+ logmsg("client_fork: close");
+ return (-1);
+ }
+ return (client_pid != 0);
+}
+
+static void
+client_exit(int rv)
+{
+ if (close(sync_fd[SYNC_SERVER][SYNC_SEND]) < 0 ||
+ close(sync_fd[SYNC_CLIENT][SYNC_RECV]) < 0) {
+ logmsg("client_exit: close");
+ rv = -1;
+ }
+ rv = rv == 0 ? EXIT_SUCCESS : -rv;
+ dbgmsg("exit: code %d", rv);
+ _exit(rv);
+}
+
+static int
+client_wait(void)
+{
+ int status;
+ pid_t pid;
+
+ dbgmsg("waiting for client");
+
+ if (close(sync_fd[SYNC_SERVER][SYNC_RECV]) < 0 ||
+ close(sync_fd[SYNC_CLIENT][SYNC_SEND]) < 0) {
+ logmsg("client_wait: close");
+ return (-1);
+ }
+
+ pid = waitpid(client_pid, &status, 0);
+ if (pid == (pid_t)-1) {
+ logmsg("client_wait: waitpid");
+ return (-1);
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+ logmsgx("client exit status is %d",
+ WEXITSTATUS(status));
+ return (-WEXITSTATUS(status));
+ }
+ } else {
+ if (WIFSIGNALED(status))
+ logmsgx("abnormal termination of client, signal %d%s",
+ WTERMSIG(status), WCOREDUMP(status) ?
+ " (core file generated)" : "");
+ else
+ logmsgx("termination of client, unknown status");
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *errstr;
+ u_int testno, zvalue;
+ int opt, rv;
+ bool dgram_flag, stream_flag;
+
+ ipc_msg.buf_size = IPC_MSG_SIZE_DEF;
+ ipc_msg.msg_num = IPC_MSG_NUM_DEF;
+ dgram_flag = stream_flag = false;
+ while ((opt = getopt(argc, argv, "dhn:s:t:z:")) != -1)
+ switch (opt) {
+ case 'd':
+ debug = true;
+ break;
+ case 'h':
+ usage(true);
+ return (EXIT_SUCCESS);
+ case 'n':
+ ipc_msg.msg_num = strtonum(optarg, 1,
+ IPC_MSG_NUM_MAX, &errstr);
+ if (errstr != NULL)
+ errx(EXIT_FAILURE, "option -n: number is %s",
+ errstr);
+ break;
+ case 's':
+ ipc_msg.buf_size = strtonum(optarg, 0,
+ IPC_MSG_SIZE_MAX, &errstr);
+ if (errstr != NULL)
+ errx(EXIT_FAILURE, "option -s: number is %s",
+ errstr);
+ break;
+ case 't':
+ if (strcmp(optarg, "stream") == 0)
+ stream_flag = true;
+ else if (strcmp(optarg, "dgram") == 0)
+ dgram_flag = true;
+ else
+ errx(EXIT_FAILURE, "option -t: "
+ "wrong socket type");
+ break;
+ case 'z':
+ zvalue = strtonum(optarg, 0, 3, &errstr);
+ if (errstr != NULL)
+ errx(EXIT_FAILURE, "option -z: number is %s",
+ errstr);
+ if (zvalue & 0x1)
+ send_data_flag = false;
+ if (zvalue & 0x2)
+ send_array_flag = false;
+ break;
+ default:
+ usage(false);
+ return (EXIT_FAILURE);
+ }
+
+ if (optind < argc) {
+ if (optind + 1 != argc)
+ errx(EXIT_FAILURE, "too many arguments");
+ testno = strtonum(argv[optind], 0, UINT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(EXIT_FAILURE, "test number is %s", errstr);
+ if (stream_flag && testno >= TEST_STREAM_TBL_SIZE)
+ errx(EXIT_FAILURE, "given test %u for stream "
+ "sockets does not exist", testno);
+ if (dgram_flag && testno >= TEST_DGRAM_TBL_SIZE)
+ errx(EXIT_FAILURE, "given test %u for datagram "
+ "sockets does not exist", testno);
+ } else
+ testno = 0;
+
+ if (!dgram_flag && !stream_flag) {
+ if (testno != 0)
+ errx(EXIT_FAILURE, "particular test number "
+ "can be used with the -t option only");
+ dgram_flag = stream_flag = true;
+ }
+
+ if (mkdtemp(work_dir) == NULL)
+ err(EXIT_FAILURE, "mkdtemp(%s)", work_dir);
+
+ rv = EXIT_FAILURE;
+ if (init() < 0)
+ goto done;
+
+ if (stream_flag)
+ if (run_tests(SOCK_STREAM, testno) < 0)
+ goto done;
+ if (dgram_flag)
+ if (run_tests(SOCK_DGRAM, testno) < 0)
+ goto done;
+
+ rv = EXIT_SUCCESS;
+done:
+ if (rmdir(work_dir) < 0) {
+ logmsg("rmdir(%s)", work_dir);
+ rv = EXIT_FAILURE;
+ }
+ return (failed_flag ? EXIT_FAILURE : rv);
+}
+
+static int
+socket_close(int fd)
+{
+ int rv;
+
+ rv = 0;
+ if (close(fd) < 0) {
+ logmsg("socket_close: close");
+ rv = -1;
+ }
+ if (server_flag && fd == serv_sock_fd)
+ if (unlink(serv_addr_sun.sun_path) < 0) {
+ logmsg("socket_close: unlink(%s)",
+ serv_addr_sun.sun_path);
+ rv = -1;
+ }
+ return (rv);
+}
+
+static int
+socket_create(void)
+{
+ struct timeval tv;
+ int fd;
+
+ fd = socket(PF_LOCAL, sock_type, 0);
+ if (fd < 0) {
+ logmsg("socket_create: socket(PF_LOCAL, %s, 0)", sock_type_str);
+ return (-1);
+ }
+ if (server_flag)
+ serv_sock_fd = fd;
+
+ tv.tv_sec = TIMEOUT;
+ tv.tv_usec = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ||
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
+ logmsg("socket_create: setsockopt(SO_RCVTIMEO/SO_SNDTIMEO)");
+ goto failed;
+ }
+
+ if (server_flag) {
+ if (bind(fd, (struct sockaddr *)&serv_addr_sun,
+ serv_addr_sun.sun_len) < 0) {
+ logmsg("socket_create: bind(%s)",
+ serv_addr_sun.sun_path);
+ goto failed;
+ }
+ if (sock_type == SOCK_STREAM) {
+ int val;
+
+ if (listen(fd, LISTENQ) < 0) {
+ logmsg("socket_create: listen");
+ goto failed;
+ }
+ val = fcntl(fd, F_GETFL, 0);
+ if (val < 0) {
+ logmsg("socket_create: fcntl(F_GETFL)");
+ goto failed;
+ }
+ if (fcntl(fd, F_SETFL, val | O_NONBLOCK) < 0) {
+ logmsg("socket_create: fcntl(F_SETFL)");
+ goto failed;
+ }
+ }
+ }
+
+ return (fd);
+
+failed:
+ if (close(fd) < 0)
+ logmsg("socket_create: close");
+ if (server_flag)
+ if (unlink(serv_addr_sun.sun_path) < 0)
+ logmsg("socket_close: unlink(%s)",
+ serv_addr_sun.sun_path);
+ return (-1);
+}
+
+static int
+socket_connect(int fd)
+{
+ dbgmsg("connect");
+
+ if (connect(fd, (struct sockaddr *)&serv_addr_sun,
+ serv_addr_sun.sun_len) < 0) {
+ logmsg("socket_connect: connect(%s)", serv_addr_sun.sun_path);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+sync_recv(void)
+{
+ ssize_t ssize;
+ int fd;
+ char buf;
+
+ dbgmsg("sync: wait");
+
+ fd = sync_fd[server_flag ? SYNC_SERVER : SYNC_CLIENT][SYNC_RECV];
+
+ ssize = read(fd, &buf, 1);
+ if (ssize < 0) {
+ logmsg("sync_recv: read");
+ return (-1);
+ }
+ if (ssize < 1) {
+ logmsgx("sync_recv: read %zd of 1 byte", ssize);
+ return (-1);
+ }
+
+ dbgmsg("sync: received");
+
+ return (0);
+}
+
+static int
+sync_send(void)
+{
+ ssize_t ssize;
+ int fd;
+
+ dbgmsg("sync: send");
+
+ fd = sync_fd[server_flag ? SYNC_CLIENT : SYNC_SERVER][SYNC_SEND];
+
+ ssize = write(fd, "", 1);
+ if (ssize < 0) {
+ logmsg("sync_send: write");
+ return (-1);
+ }
+ if (ssize < 1) {
+ logmsgx("sync_send: sent %zd of 1 byte", ssize);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+message_send(int fd, const struct msghdr *msghdr)
+{
+ const struct cmsghdr *cmsghdr;
+ size_t size;
+ ssize_t ssize;
+
+ size = msghdr->msg_iov != 0 ? msghdr->msg_iov->iov_len : 0;
+ dbgmsg("send: data size %zu", size);
+ dbgmsg("send: msghdr.msg_controllen %u",
+ (u_int)msghdr->msg_controllen);
+ cmsghdr = CMSG_FIRSTHDR(msghdr);
+ if (cmsghdr != NULL)
+ dbgmsg("send: cmsghdr.cmsg_len %u",
+ (u_int)cmsghdr->cmsg_len);
+
+ ssize = sendmsg(fd, msghdr, 0);
+ if (ssize < 0) {
+ logmsg("message_send: sendmsg");
+ return (-1);
+ }
+ if ((size_t)ssize != size) {
+ logmsgx("message_send: sendmsg: sent %zd of %zu bytes",
+ ssize, size);
+ return (-1);
+ }
+
+ if (!send_data_flag)
+ if (sync_send() < 0)
+ return (-1);
+
+ return (0);
+}
+
+static int
+message_sendn(int fd, struct msghdr *msghdr)
+{
+ u_int i;
+
+ for (i = 1; i <= ipc_msg.msg_num; ++i) {
+ dbgmsg("message #%u", i);
+ if (message_send(fd, msghdr) < 0)
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+message_recv(int fd, struct msghdr *msghdr)
+{
+ const struct cmsghdr *cmsghdr;
+ size_t size;
+ ssize_t ssize;
+
+ if (!send_data_flag)
+ if (sync_recv() < 0)
+ return (-1);
+
+ size = msghdr->msg_iov != NULL ? msghdr->msg_iov->iov_len : 0;
+ ssize = recvmsg(fd, msghdr, MSG_WAITALL);
+ if (ssize < 0) {
+ logmsg("message_recv: recvmsg");
+ return (-1);
+ }
+ if ((size_t)ssize != size) {
+ logmsgx("message_recv: recvmsg: received %zd of %zu bytes",
+ ssize, size);
+ return (-1);
+ }
+
+ dbgmsg("recv: data size %zd", ssize);
+ dbgmsg("recv: msghdr.msg_controllen %u",
+ (u_int)msghdr->msg_controllen);
+ cmsghdr = CMSG_FIRSTHDR(msghdr);
+ if (cmsghdr != NULL)
+ dbgmsg("recv: cmsghdr.cmsg_len %u",
+ (u_int)cmsghdr->cmsg_len);
+
+ if (memcmp(ipc_msg.buf_recv, ipc_msg.buf_send, size) != 0) {
+ logmsgx("message_recv: received message has wrong content");
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+socket_accept(int listenfd)
+{
+ fd_set rset;
+ struct timeval tv;
+ int fd, rv, val;
+
+ dbgmsg("accept");
+
+ FD_ZERO(&rset);
+ FD_SET(listenfd, &rset);
+ tv.tv_sec = TIMEOUT;
+ tv.tv_usec = 0;
+ rv = select(listenfd + 1, &rset, (fd_set *)NULL, (fd_set *)NULL, &tv);
+ if (rv < 0) {
+ logmsg("socket_accept: select");
+ return (-1);
+ }
+ if (rv == 0) {
+ logmsgx("socket_accept: select timeout");
+ return (-1);
+ }
+
+ fd = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL);
+ if (fd < 0) {
+ logmsg("socket_accept: accept");
+ return (-1);
+ }
+
+ val = fcntl(fd, F_GETFL, 0);
+ if (val < 0) {
+ logmsg("socket_accept: fcntl(F_GETFL)");
+ goto failed;
+ }
+ if (fcntl(fd, F_SETFL, val & ~O_NONBLOCK) < 0) {
+ logmsg("socket_accept: fcntl(F_SETFL)");
+ goto failed;
+ }
+
+ return (fd);
+
+failed:
+ if (close(fd) < 0)
+ logmsg("socket_accept: close");
+ return (-1);
+}
+
+static int
+check_msghdr(const struct msghdr *msghdr, size_t size)
+{
+ if (msghdr->msg_flags & MSG_TRUNC) {
+ logmsgx("msghdr.msg_flags has MSG_TRUNC");
+ return (-1);
+ }
+ if (msghdr->msg_flags & MSG_CTRUNC) {
+ logmsgx("msghdr.msg_flags has MSG_CTRUNC");
+ return (-1);
+ }
+ if (msghdr->msg_controllen < size) {
+ logmsgx("msghdr.msg_controllen %u < %zu",
+ (u_int)msghdr->msg_controllen, size);
+ return (-1);
+ }
+ if (msghdr->msg_controllen > 0 && size == 0) {
+ logmsgx("msghdr.msg_controllen %u > 0",
+ (u_int)msghdr->msg_controllen);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+check_cmsghdr(const struct cmsghdr *cmsghdr, int type, size_t size)
+{
+ if (cmsghdr == NULL) {
+ logmsgx("cmsghdr is NULL");
+ return (-1);
+ }
+ if (cmsghdr->cmsg_level != SOL_SOCKET) {
+ logmsgx("cmsghdr.cmsg_level %d != SOL_SOCKET",
+ cmsghdr->cmsg_level);
+ return (-1);
+ }
+ if (cmsghdr->cmsg_type != type) {
+ logmsgx("cmsghdr.cmsg_type %d != %d",
+ cmsghdr->cmsg_type, type);
+ return (-1);
+ }
+ if (cmsghdr->cmsg_len != CMSG_LEN(size)) {
+ logmsgx("cmsghdr.cmsg_len %u != %zu",
+ (u_int)cmsghdr->cmsg_len, CMSG_LEN(size));
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+check_groups(const char *gid_arr_str, const gid_t *gid_arr,
+ const char *gid_num_str, int gid_num, bool all_gids)
+{
+ int i;
+
+ for (i = 0; i < gid_num; ++i)
+ dbgmsg("%s[%d] %lu", gid_arr_str, i, (u_long)gid_arr[i]);
+
+ if (all_gids) {
+ if (gid_num != proc_cred.gid_num) {
+ logmsgx("%s %d != %d", gid_num_str, gid_num,
+ proc_cred.gid_num);
+ return (-1);
+ }
+ } else {
+ if (gid_num > proc_cred.gid_num) {
+ logmsgx("%s %d > %d", gid_num_str, gid_num,
+ proc_cred.gid_num);
+ return (-1);
+ }
+ }
+ if (memcmp(gid_arr, proc_cred.gid_arr,
+ gid_num * sizeof(*gid_arr)) != 0) {
+ logmsgx("%s content is wrong", gid_arr_str);
+ for (i = 0; i < gid_num; ++i)
+ if (gid_arr[i] != proc_cred.gid_arr[i]) {
+ logmsgx("%s[%d] %lu != %lu",
+ gid_arr_str, i, (u_long)gid_arr[i],
+ (u_long)proc_cred.gid_arr[i]);
+ break;
+ }
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+check_xucred(const struct xucred *xucred, socklen_t len)
+{
+ if (len != sizeof(*xucred)) {
+ logmsgx("option value size %zu != %zu",
+ (size_t)len, sizeof(*xucred));
+ return (-1);
+ }
+
+ dbgmsg("xucred.cr_version %u", xucred->cr_version);
+ dbgmsg("xucred.cr_uid %lu", (u_long)xucred->cr_uid);
+ dbgmsg("xucred.cr_ngroups %d", xucred->cr_ngroups);
+
+ if (xucred->cr_version != XUCRED_VERSION) {
+ logmsgx("xucred.cr_version %u != %d",
+ xucred->cr_version, XUCRED_VERSION);
+ return (-1);
+ }
+ if (xucred->cr_uid != proc_cred.euid) {
+ logmsgx("xucred.cr_uid %lu != %lu (EUID)",
+ (u_long)xucred->cr_uid, (u_long)proc_cred.euid);
+ return (-1);
+ }
+ if (xucred->cr_ngroups == 0) {
+ logmsgx("xucred.cr_ngroups == 0");
+ return (-1);
+ }
+ if (xucred->cr_ngroups < 0) {
+ logmsgx("xucred.cr_ngroups < 0");
+ return (-1);
+ }
+ if (xucred->cr_ngroups > XU_NGROUPS) {
+ logmsgx("xucred.cr_ngroups %hu > %u (max)",
+ xucred->cr_ngroups, XU_NGROUPS);
+ return (-1);
+ }
+ if (xucred->cr_groups[0] != proc_cred.egid) {
+ logmsgx("xucred.cr_groups[0] %lu != %lu (EGID)",
+ (u_long)xucred->cr_groups[0], (u_long)proc_cred.egid);
+ return (-1);
+ }
+ if (check_groups("xucred.cr_groups", xucred->cr_groups,
+ "xucred.cr_ngroups", xucred->cr_ngroups, false) < 0)
+ return (-1);
+ return (0);
+}
+
+static int
+check_scm_creds_cmsgcred(struct cmsghdr *cmsghdr)
+{
+ const struct cmsgcred *cmsgcred;
+
+ if (check_cmsghdr(cmsghdr, SCM_CREDS, sizeof(*cmsgcred)) < 0)
+ return (-1);
+
+ cmsgcred = (struct cmsgcred *)CMSG_DATA(cmsghdr);
+
+ dbgmsg("cmsgcred.cmcred_pid %ld", (long)cmsgcred->cmcred_pid);
+ dbgmsg("cmsgcred.cmcred_uid %lu", (u_long)cmsgcred->cmcred_uid);
+ dbgmsg("cmsgcred.cmcred_euid %lu", (u_long)cmsgcred->cmcred_euid);
+ dbgmsg("cmsgcred.cmcred_gid %lu", (u_long)cmsgcred->cmcred_gid);
+ dbgmsg("cmsgcred.cmcred_ngroups %d", cmsgcred->cmcred_ngroups);
+
+ if (cmsgcred->cmcred_pid != client_pid) {
+ logmsgx("cmsgcred.cmcred_pid %ld != %ld",
+ (long)cmsgcred->cmcred_pid, (long)client_pid);
+ return (-1);
+ }
+ if (cmsgcred->cmcred_uid != proc_cred.uid) {
+ logmsgx("cmsgcred.cmcred_uid %lu != %lu",
+ (u_long)cmsgcred->cmcred_uid, (u_long)proc_cred.uid);
+ return (-1);
+ }
+ if (cmsgcred->cmcred_euid != proc_cred.euid) {
+ logmsgx("cmsgcred.cmcred_euid %lu != %lu",
+ (u_long)cmsgcred->cmcred_euid, (u_long)proc_cred.euid);
+ return (-1);
+ }
+ if (cmsgcred->cmcred_gid != proc_cred.gid) {
+ logmsgx("cmsgcred.cmcred_gid %lu != %lu",
+ (u_long)cmsgcred->cmcred_gid, (u_long)proc_cred.gid);
+ return (-1);
+ }
+ if (cmsgcred->cmcred_ngroups == 0) {
+ logmsgx("cmsgcred.cmcred_ngroups == 0");
+ return (-1);
+ }
+ if (cmsgcred->cmcred_ngroups < 0) {
+ logmsgx("cmsgcred.cmcred_ngroups %d < 0",
+ cmsgcred->cmcred_ngroups);
+ return (-1);
+ }
+ if (cmsgcred->cmcred_ngroups > CMGROUP_MAX) {
+ logmsgx("cmsgcred.cmcred_ngroups %d > %d",
+ cmsgcred->cmcred_ngroups, CMGROUP_MAX);
+ return (-1);
+ }
+ if (cmsgcred->cmcred_groups[0] != proc_cred.egid) {
+ logmsgx("cmsgcred.cmcred_groups[0] %lu != %lu (EGID)",
+ (u_long)cmsgcred->cmcred_groups[0], (u_long)proc_cred.egid);
+ return (-1);
+ }
+ if (check_groups("cmsgcred.cmcred_groups", cmsgcred->cmcred_groups,
+ "cmsgcred.cmcred_ngroups", cmsgcred->cmcred_ngroups, false) < 0)
+ return (-1);
+ return (0);
+}
+
+static int
+check_scm_creds_sockcred(struct cmsghdr *cmsghdr)
+{
+ const struct sockcred *sockcred;
+
+ if (check_cmsghdr(cmsghdr, SCM_CREDS,
+ SOCKCREDSIZE(proc_cred.gid_num)) < 0)
+ return (-1);
+
+ sockcred = (struct sockcred *)CMSG_DATA(cmsghdr);
+
+ dbgmsg("sockcred.sc_uid %lu", (u_long)sockcred->sc_uid);
+ dbgmsg("sockcred.sc_euid %lu", (u_long)sockcred->sc_euid);
+ dbgmsg("sockcred.sc_gid %lu", (u_long)sockcred->sc_gid);
+ dbgmsg("sockcred.sc_egid %lu", (u_long)sockcred->sc_egid);
+ dbgmsg("sockcred.sc_ngroups %d", sockcred->sc_ngroups);
+
+ if (sockcred->sc_uid != proc_cred.uid) {
+ logmsgx("sockcred.sc_uid %lu != %lu",
+ (u_long)sockcred->sc_uid, (u_long)proc_cred.uid);
+ return (-1);
+ }
+ if (sockcred->sc_euid != proc_cred.euid) {
+ logmsgx("sockcred.sc_euid %lu != %lu",
+ (u_long)sockcred->sc_euid, (u_long)proc_cred.euid);
+ return (-1);
+ }
+ if (sockcred->sc_gid != proc_cred.gid) {
+ logmsgx("sockcred.sc_gid %lu != %lu",
+ (u_long)sockcred->sc_gid, (u_long)proc_cred.gid);
+ return (-1);
+ }
+ if (sockcred->sc_egid != proc_cred.egid) {
+ logmsgx("sockcred.sc_egid %lu != %lu",
+ (u_long)sockcred->sc_egid, (u_long)proc_cred.egid);
+ return (-1);
+ }
+ if (sockcred->sc_ngroups == 0) {
+ logmsgx("sockcred.sc_ngroups == 0");
+ return (-1);
+ }
+ if (sockcred->sc_ngroups < 0) {
+ logmsgx("sockcred.sc_ngroups %d < 0",
+ sockcred->sc_ngroups);
+ return (-1);
+ }
+ if (sockcred->sc_ngroups != proc_cred.gid_num) {
+ logmsgx("sockcred.sc_ngroups %d != %u",
+ sockcred->sc_ngroups, proc_cred.gid_num);
+ return (-1);
+ }
+ if (check_groups("sockcred.sc_groups", sockcred->sc_groups,
+ "sockcred.sc_ngroups", sockcred->sc_ngroups, true) < 0)
+ return (-1);
+ return (0);
+}
+
+static int
+check_scm_timestamp(struct cmsghdr *cmsghdr)
+{
+ const struct timeval *timeval;
+
+ if (check_cmsghdr(cmsghdr, SCM_TIMESTAMP, sizeof(struct timeval)) < 0)
+ return (-1);
+
+ timeval = (struct timeval *)CMSG_DATA(cmsghdr);
+
+ dbgmsg("timeval.tv_sec %"PRIdMAX", timeval.tv_usec %"PRIdMAX,
+ (intmax_t)timeval->tv_sec, (intmax_t)timeval->tv_usec);
+
+ return (0);
+}
+
+static int
+check_scm_bintime(struct cmsghdr *cmsghdr)
+{
+ const struct bintime *bintime;
+
+ if (check_cmsghdr(cmsghdr, SCM_BINTIME, sizeof(struct bintime)) < 0)
+ return (-1);
+
+ bintime = (struct bintime *)CMSG_DATA(cmsghdr);
+
+ dbgmsg("bintime.sec %"PRIdMAX", bintime.frac %"PRIu64,
+ (intmax_t)bintime->sec, bintime->frac);
+
+ return (0);
+}
+
+static void
+msghdr_init_generic(struct msghdr *msghdr, struct iovec *iov, void *cmsg_data)
+{
+ msghdr->msg_name = NULL;
+ msghdr->msg_namelen = 0;
+ if (send_data_flag) {
+ iov->iov_base = server_flag ?
+ ipc_msg.buf_recv : ipc_msg.buf_send;
+ iov->iov_len = ipc_msg.buf_size;
+ msghdr->msg_iov = iov;
+ msghdr->msg_iovlen = 1;
+ } else {
+ msghdr->msg_iov = NULL;
+ msghdr->msg_iovlen = 0;
+ }
+ msghdr->msg_control = cmsg_data;
+ msghdr->msg_flags = 0;
+}
+
+static void
+msghdr_init_server(struct msghdr *msghdr, struct iovec *iov,
+ void *cmsg_data, size_t cmsg_size)
+{
+ msghdr_init_generic(msghdr, iov, cmsg_data);
+ msghdr->msg_controllen = cmsg_size;
+ dbgmsg("init: data size %zu", msghdr->msg_iov != NULL ?
+ msghdr->msg_iov->iov_len : (size_t)0);
+ dbgmsg("init: msghdr.msg_controllen %u",
+ (u_int)msghdr->msg_controllen);
+}
+
+static void
+msghdr_init_client(struct msghdr *msghdr, struct iovec *iov,
+ void *cmsg_data, size_t cmsg_size, int type, size_t arr_size)
+{
+ struct cmsghdr *cmsghdr;
+
+ msghdr_init_generic(msghdr, iov, cmsg_data);
+ if (cmsg_data != NULL) {
+ msghdr->msg_controllen = send_array_flag ?
+ cmsg_size : CMSG_SPACE(0);
+ cmsghdr = CMSG_FIRSTHDR(msghdr);
+ cmsghdr->cmsg_level = SOL_SOCKET;
+ cmsghdr->cmsg_type = type;
+ cmsghdr->cmsg_len = CMSG_LEN(send_array_flag ? arr_size : 0);
+ } else
+ msghdr->msg_controllen = 0;
+}
+
+static int
+t_generic(int (*client_func)(int), int (*server_func)(int))
+{
+ int fd, rv, rv_client;
+
+ switch (client_fork()) {
+ case 0:
+ fd = socket_create();
+ if (fd < 0)
+ rv = -2;
+ else {
+ rv = client_func(fd);
+ if (socket_close(fd) < 0)
+ rv = -2;
+ }
+ client_exit(rv);
+ break;
+ case 1:
+ fd = socket_create();
+ if (fd < 0)
+ rv = -2;
+ else {
+ rv = server_func(fd);
+ rv_client = client_wait();
+ if (rv == 0 || (rv == -2 && rv_client != 0))
+ rv = rv_client;
+ if (socket_close(fd) < 0)
+ rv = -2;
+ }
+ break;
+ default:
+ rv = -2;
+ }
+ return (rv);
+}
+
+static int
+t_cmsgcred_client(int fd)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ void *cmsg_data;
+ size_t cmsg_size;
+ int rv;
+
+ if (sync_recv() < 0)
+ return (-2);
+
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+ msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size,
+ SCM_CREDS, sizeof(struct cmsgcred));
+
+ if (socket_connect(fd) < 0)
+ goto done;
+
+ if (message_sendn(fd, &msghdr) < 0)
+ goto done;
+
+ rv = 0;
+done:
+ free(cmsg_data);
+ return (rv);
+}
+
+static int
+t_cmsgcred_server(int fd1)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ struct cmsghdr *cmsghdr;
+ void *cmsg_data;
+ size_t cmsg_size;
+ u_int i;
+ int fd2, rv;
+
+ if (sync_send() < 0)
+ return (-2);
+
+ fd2 = -1;
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+
+ if (sock_type == SOCK_STREAM) {
+ fd2 = socket_accept(fd1);
+ if (fd2 < 0)
+ goto done;
+ } else
+ fd2 = fd1;
+
+ rv = -1;
+ for (i = 1; i <= ipc_msg.msg_num; ++i) {
+ dbgmsg("message #%u", i);
+
+ msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size);
+ if (message_recv(fd2, &msghdr) < 0) {
+ rv = -2;
+ break;
+ }
+
+ if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0)
+ break;
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ if (check_scm_creds_cmsgcred(cmsghdr) < 0)
+ break;
+ }
+ if (i > ipc_msg.msg_num)
+ rv = 0;
+done:
+ free(cmsg_data);
+ if (sock_type == SOCK_STREAM && fd2 >= 0)
+ if (socket_close(fd2) < 0)
+ rv = -2;
+ return (rv);
+}
+
+static int
+t_cmsgcred(void)
+{
+ return (t_generic(t_cmsgcred_client, t_cmsgcred_server));
+}
+
+static int
+t_sockcred_client(int type, int fd)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ int rv;
+
+ if (sync_recv() < 0)
+ return (-2);
+
+ rv = -2;
+
+ msghdr_init_client(&msghdr, iov, NULL, 0, 0, 0);
+
+ if (socket_connect(fd) < 0)
+ goto done;
+
+ if (type == 2)
+ if (sync_recv() < 0)
+ goto done;
+
+ if (message_sendn(fd, &msghdr) < 0)
+ goto done;
+
+ rv = 0;
+done:
+ return (rv);
+}
+
+static int
+t_sockcred_server(int type, int fd1)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ struct cmsghdr *cmsghdr;
+ void *cmsg_data;
+ size_t cmsg_size;
+ u_int i;
+ int fd2, rv, val;
+
+ fd2 = -1;
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(SOCKCREDSIZE(proc_cred.gid_num));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+
+ if (type == 1) {
+ dbgmsg("setting LOCAL_CREDS");
+ val = 1;
+ if (setsockopt(fd1, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) {
+ logmsg("setsockopt(LOCAL_CREDS)");
+ goto done;
+ }
+ }
+
+ if (sync_send() < 0)
+ goto done;
+
+ if (sock_type == SOCK_STREAM) {
+ fd2 = socket_accept(fd1);
+ if (fd2 < 0)
+ goto done;
+ } else
+ fd2 = fd1;
+
+ if (type == 2) {
+ dbgmsg("setting LOCAL_CREDS");
+ val = 1;
+ if (setsockopt(fd2, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) {
+ logmsg("setsockopt(LOCAL_CREDS)");
+ goto done;
+ }
+ if (sync_send() < 0)
+ goto done;
+ }
+
+ rv = -1;
+ for (i = 1; i <= ipc_msg.msg_num; ++i) {
+ dbgmsg("message #%u", i);
+
+ msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size);
+ if (message_recv(fd2, &msghdr) < 0) {
+ rv = -2;
+ break;
+ }
+
+ if (i > 1 && sock_type == SOCK_STREAM) {
+ if (check_msghdr(&msghdr, 0) < 0)
+ break;
+ } else {
+ if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0)
+ break;
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ if (check_scm_creds_sockcred(cmsghdr) < 0)
+ break;
+ }
+ }
+ if (i > ipc_msg.msg_num)
+ rv = 0;
+done:
+ free(cmsg_data);
+ if (sock_type == SOCK_STREAM && fd2 >= 0)
+ if (socket_close(fd2) < 0)
+ rv = -2;
+ return (rv);
+}
+
+static int
+t_sockcred_1(void)
+{
+ u_int i;
+ int fd, rv, rv_client;
+
+ switch (client_fork()) {
+ case 0:
+ for (i = 1; i <= 2; ++i) {
+ dbgmsg("client #%u", i);
+ fd = socket_create();
+ if (fd < 0)
+ rv = -2;
+ else {
+ rv = t_sockcred_client(1, fd);
+ if (socket_close(fd) < 0)
+ rv = -2;
+ }
+ if (rv != 0)
+ break;
+ }
+ client_exit(rv);
+ break;
+ case 1:
+ fd = socket_create();
+ if (fd < 0)
+ rv = -2;
+ else {
+ rv = t_sockcred_server(1, fd);
+ if (rv == 0)
+ rv = t_sockcred_server(3, fd);
+ rv_client = client_wait();
+ if (rv == 0 || (rv == -2 && rv_client != 0))
+ rv = rv_client;
+ if (socket_close(fd) < 0)
+ rv = -2;
+ }
+ break;
+ default:
+ rv = -2;
+ }
+
+ return (rv);
+}
+
+static int
+t_sockcred_2_client(int fd)
+{
+ return (t_sockcred_client(2, fd));
+}
+
+static int
+t_sockcred_2_server(int fd)
+{
+ return (t_sockcred_server(2, fd));
+}
+
+static int
+t_sockcred_2(void)
+{
+ return (t_generic(t_sockcred_2_client, t_sockcred_2_server));
+}
+
+static int
+t_cmsgcred_sockcred_server(int fd1)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ struct cmsghdr *cmsghdr;
+ void *cmsg_data, *cmsg1_data, *cmsg2_data;
+ size_t cmsg_size, cmsg1_size, cmsg2_size;
+ u_int i;
+ int fd2, rv, val;
+
+ fd2 = -1;
+ rv = -2;
+
+ cmsg1_size = CMSG_SPACE(SOCKCREDSIZE(proc_cred.gid_num));
+ cmsg2_size = CMSG_SPACE(sizeof(struct cmsgcred));
+ cmsg1_data = malloc(cmsg1_size);
+ cmsg2_data = malloc(cmsg2_size);
+ if (cmsg1_data == NULL || cmsg2_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+
+ dbgmsg("setting LOCAL_CREDS");
+ val = 1;
+ if (setsockopt(fd1, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) {
+ logmsg("setsockopt(LOCAL_CREDS)");
+ goto done;
+ }
+
+ if (sync_send() < 0)
+ goto done;
+
+ if (sock_type == SOCK_STREAM) {
+ fd2 = socket_accept(fd1);
+ if (fd2 < 0)
+ goto done;
+ } else
+ fd2 = fd1;
+
+ cmsg_data = cmsg1_data;
+ cmsg_size = cmsg1_size;
+ rv = -1;
+ for (i = 1; i <= ipc_msg.msg_num; ++i) {
+ dbgmsg("message #%u", i);
+
+ msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size);
+ if (message_recv(fd2, &msghdr) < 0) {
+ rv = -2;
+ break;
+ }
+
+ if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0)
+ break;
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ if (i == 1 || sock_type == SOCK_DGRAM) {
+ if (check_scm_creds_sockcred(cmsghdr) < 0)
+ break;
+ } else {
+ if (check_scm_creds_cmsgcred(cmsghdr) < 0)
+ break;
+ }
+
+ cmsg_data = cmsg2_data;
+ cmsg_size = cmsg2_size;
+ }
+ if (i > ipc_msg.msg_num)
+ rv = 0;
+done:
+ free(cmsg1_data);
+ free(cmsg2_data);
+ if (sock_type == SOCK_STREAM && fd2 >= 0)
+ if (socket_close(fd2) < 0)
+ rv = -2;
+ return (rv);
+}
+
+static int
+t_cmsgcred_sockcred(void)
+{
+ return (t_generic(t_cmsgcred_client, t_cmsgcred_sockcred_server));
+}
+
+static int
+t_timeval_client(int fd)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ void *cmsg_data;
+ size_t cmsg_size;
+ int rv;
+
+ if (sync_recv() < 0)
+ return (-2);
+
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(sizeof(struct timeval));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+ msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size,
+ SCM_TIMESTAMP, sizeof(struct timeval));
+
+ if (socket_connect(fd) < 0)
+ goto done;
+
+ if (message_sendn(fd, &msghdr) < 0)
+ goto done;
+
+ rv = 0;
+done:
+ free(cmsg_data);
+ return (rv);
+}
+
+static int
+t_timeval_server(int fd1)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ struct cmsghdr *cmsghdr;
+ void *cmsg_data;
+ size_t cmsg_size;
+ u_int i;
+ int fd2, rv;
+
+ if (sync_send() < 0)
+ return (-2);
+
+ fd2 = -1;
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(sizeof(struct timeval));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+
+ if (sock_type == SOCK_STREAM) {
+ fd2 = socket_accept(fd1);
+ if (fd2 < 0)
+ goto done;
+ } else
+ fd2 = fd1;
+
+ rv = -1;
+ for (i = 1; i <= ipc_msg.msg_num; ++i) {
+ dbgmsg("message #%u", i);
+
+ msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size);
+ if (message_recv(fd2, &msghdr) < 0) {
+ rv = -2;
+ break;
+ }
+
+ if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0)
+ break;
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ if (check_scm_timestamp(cmsghdr) < 0)
+ break;
+ }
+ if (i > ipc_msg.msg_num)
+ rv = 0;
+done:
+ free(cmsg_data);
+ if (sock_type == SOCK_STREAM && fd2 >= 0)
+ if (socket_close(fd2) < 0)
+ rv = -2;
+ return (rv);
+}
+
+static int
+t_timeval(void)
+{
+ return (t_generic(t_timeval_client, t_timeval_server));
+}
+
+static int
+t_bintime_client(int fd)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ void *cmsg_data;
+ size_t cmsg_size;
+ int rv;
+
+ if (sync_recv() < 0)
+ return (-2);
+
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(sizeof(struct bintime));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+ msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size,
+ SCM_BINTIME, sizeof(struct bintime));
+
+ if (socket_connect(fd) < 0)
+ goto done;
+
+ if (message_sendn(fd, &msghdr) < 0)
+ goto done;
+
+ rv = 0;
+done:
+ free(cmsg_data);
+ return (rv);
+}
+
+static int
+t_bintime_server(int fd1)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ struct cmsghdr *cmsghdr;
+ void *cmsg_data;
+ size_t cmsg_size;
+ u_int i;
+ int fd2, rv;
+
+ if (sync_send() < 0)
+ return (-2);
+
+ fd2 = -1;
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(sizeof(struct bintime));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+
+ if (sock_type == SOCK_STREAM) {
+ fd2 = socket_accept(fd1);
+ if (fd2 < 0)
+ goto done;
+ } else
+ fd2 = fd1;
+
+ rv = -1;
+ for (i = 1; i <= ipc_msg.msg_num; ++i) {
+ dbgmsg("message #%u", i);
+
+ msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size);
+ if (message_recv(fd2, &msghdr) < 0) {
+ rv = -2;
+ break;
+ }
+
+ if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0)
+ break;
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ if (check_scm_bintime(cmsghdr) < 0)
+ break;
+ }
+ if (i > ipc_msg.msg_num)
+ rv = 0;
+done:
+ free(cmsg_data);
+ if (sock_type == SOCK_STREAM && fd2 >= 0)
+ if (socket_close(fd2) < 0)
+ rv = -2;
+ return (rv);
+}
+
+static int
+t_bintime(void)
+{
+ return (t_generic(t_bintime_client, t_bintime_server));
+}
+
+static int
+t_cmsg_len_client(int fd)
+{
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ struct cmsghdr *cmsghdr;
+ void *cmsg_data;
+ size_t size, cmsg_size;
+ socklen_t socklen;
+ int rv;
+
+ if (sync_recv() < 0)
+ return (-2);
+
+ rv = -2;
+
+ cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred));
+ cmsg_data = malloc(cmsg_size);
+ if (cmsg_data == NULL) {
+ logmsg("malloc");
+ goto done;
+ }
+ msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size,
+ SCM_CREDS, sizeof(struct cmsgcred));
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+
+ if (socket_connect(fd) < 0)
+ goto done;
+
+ size = msghdr.msg_iov != NULL ? msghdr.msg_iov->iov_len : 0;
+ rv = -1;
+ for (socklen = 0; socklen < CMSG_LEN(0); ++socklen) {
+ cmsghdr->cmsg_len = socklen;
+ dbgmsg("send: data size %zu", size);
+ dbgmsg("send: msghdr.msg_controllen %u",
+ (u_int)msghdr.msg_controllen);
+ dbgmsg("send: cmsghdr.cmsg_len %u",
+ (u_int)cmsghdr->cmsg_len);
+ if (sendmsg(fd, &msghdr, 0) < 0)
+ continue;
+ logmsgx("sent message with cmsghdr.cmsg_len %u < %u",
+ (u_int)cmsghdr->cmsg_len, (u_int)CMSG_LEN(0));
+ break;
+ }
+ if (socklen == CMSG_LEN(0))
+ rv = 0;
+
+ if (sync_send() < 0) {
+ rv = -2;
+ goto done;
+ }
+done:
+ free(cmsg_data);
+ return (rv);
+}
+
+static int
+t_cmsg_len_server(int fd1)
+{
+ int fd2, rv;
+
+ if (sync_send() < 0)
+ return (-2);
+
+ rv = -2;
+
+ if (sock_type == SOCK_STREAM) {
+ fd2 = socket_accept(fd1);
+ if (fd2 < 0)
+ goto done;
+ } else
+ fd2 = fd1;
+
+ if (sync_recv() < 0)
+ goto done;
+
+ rv = 0;
+done:
+ if (sock_type == SOCK_STREAM && fd2 >= 0)
+ if (socket_close(fd2) < 0)
+ rv = -2;
+ return (rv);
+}
+
+static int
+t_cmsg_len(void)
+{
+ return (t_generic(t_cmsg_len_client, t_cmsg_len_server));
+}
+
+static int
+t_peercred_client(int fd)
+{
+ struct xucred xucred;
+ socklen_t len;
+
+ if (sync_recv() < 0)
+ return (-1);
+
+ if (socket_connect(fd) < 0)
+ return (-1);
+
+ len = sizeof(xucred);
+ if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) {
+ logmsg("getsockopt(LOCAL_PEERCRED)");
+ return (-1);
+ }
+
+ if (check_xucred(&xucred, len) < 0)
+ return (-1);
+
+ return (0);
+}
+
+static int
+t_peercred_server(int fd1)
+{
+ struct xucred xucred;
+ socklen_t len;
+ int fd2, rv;
+
+ if (sync_send() < 0)
+ return (-2);
+
+ fd2 = socket_accept(fd1);
+ if (fd2 < 0)
+ return (-2);
+
+ len = sizeof(xucred);
+ if (getsockopt(fd2, 0, LOCAL_PEERCRED, &xucred, &len) < 0) {
+ logmsg("getsockopt(LOCAL_PEERCRED)");
+ rv = -2;
+ goto done;
+ }
+
+ if (check_xucred(&xucred, len) < 0) {
+ rv = -1;
+ goto done;
+ }
+
+ rv = 0;
+done:
+ if (socket_close(fd2) < 0)
+ rv = -2;
+ return (rv);
+}
+
+static int
+t_peercred(void)
+{
+ return (t_generic(t_peercred_client, t_peercred_server));
+}
diff --git a/tools/regression/sockets/unix_cmsg/unix_cmsg.t b/tools/regression/sockets/unix_cmsg/unix_cmsg.t
new file mode 100644
index 0000000..70d9277
--- /dev/null
+++ b/tools/regression/sockets/unix_cmsg/unix_cmsg.t
@@ -0,0 +1,88 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+cmd="./`basename $0 .t`"
+
+make ${cmd} >/dev/null 2>&1
+
+IFS=
+n=0
+
+run()
+{
+ result=`${cmd} -t $2 $3 ${5%% *} 2>&1`
+ if [ $? -ne 0 ]; then
+ echo -n "not "
+ fi
+ echo "ok $1 - $4 ${5#* }"
+ echo ${result} | grep -E "SERVER|CLIENT" | while read line; do
+ echo "# ${line}"
+ done
+}
+
+echo "1..47"
+
+for t1 in \
+ "1 Sending, receiving cmsgcred" \
+ "4 Sending cmsgcred, receiving sockcred" \
+ "5 Sending, receiving timeval" \
+ "6 Sending, receiving bintime" \
+ "7 Check cmsghdr.cmsg_len"
+do
+ for t2 in \
+ "0 " \
+ "1 (no data)" \
+ "2 (no array)" \
+ "3 (no data, array)"
+ do
+ n=$((n + 1))
+ run ${n} stream "-z ${t2%% *}" STREAM "${t1} ${t2#* }"
+ done
+done
+
+for t1 in \
+ "2 Receiving sockcred (listening socket)" \
+ "3 Receiving sockcred (accepted socket)"
+do
+ for t2 in \
+ "0 " \
+ "1 (no data)"
+ do
+ n=$((n + 1))
+ run ${n} stream "-z ${t2%% *}" STREAM "${t1} ${t2#* }"
+ done
+done
+
+n=$((n + 1))
+run ${n} stream "-z 0" STREAM "8 Check LOCAL_PEERCRED socket option"
+
+for t1 in \
+ "1 Sending, receiving cmsgcred" \
+ "3 Sending cmsgcred, receiving sockcred" \
+ "4 Sending, receiving timeval" \
+ "5 Sending, receiving bintime" \
+ "6 Check cmsghdr.cmsg_len"
+do
+ for t2 in \
+ "0 " \
+ "1 (no data)" \
+ "2 (no array)" \
+ "3 (no data, array)"
+ do
+ n=$((n + 1))
+ run ${n} dgram "-z ${t2%% *}" DGRAM "${t1} ${t2#* }"
+ done
+done
+
+for t1 in \
+ "2 Receiving sockcred"
+do
+ for t2 in \
+ "0 " \
+ "1 (no data)"
+ do
+ n=$((n + 1))
+ run ${n} dgram "-z ${t2%% *}" DGRAM "${t1} ${t2#* }"
+ done
+done
diff --git a/tools/regression/sockets/unix_gc/Makefile b/tools/regression/sockets/unix_gc/Makefile
new file mode 100644
index 0000000..0c66493
--- /dev/null
+++ b/tools/regression/sockets/unix_gc/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_gc
+MAN=
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_gc/unix_gc.c b/tools/regression/sockets/unix_gc/unix_gc.c
new file mode 100644
index 0000000..8cae2d9
--- /dev/null
+++ b/tools/regression/sockets/unix_gc/unix_gc.c
@@ -0,0 +1,808 @@
+/*-
+ * Copyright (c) 2007 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$
+ */
+
+/*
+ * A few regression tests for UNIX domain sockets. Run from single-user mode
+ * as it checks the openfiles sysctl to look for leaks, and we don't want that
+ * changing due to other processes doing stuff.
+ */
+
+#include <sys/types.h>
+#include <sys/signal.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <netinet/in.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>
+
+static int forcegc = 1;
+static char dpath[PATH_MAX];
+static const char *test;
+
+static int
+getsysctl(const char *name)
+{
+ size_t len;
+ int i;
+
+ len = sizeof(i);
+ if (sysctlbyname(name, &i, &len, NULL, 0) < 0)
+ err(-1, "%s", name);
+ return (i);
+}
+
+static int
+getopenfiles(void)
+{
+
+ return (getsysctl("kern.openfiles"));
+}
+
+static int
+getinflight(void)
+{
+
+ return (getsysctl("net.local.inflight"));
+}
+
+static int
+getdeferred(void)
+{
+
+ return (getsysctl("net.local.deferred"));
+}
+
+static void
+sendfd(int fd, int fdtosend)
+{
+ struct msghdr mh;
+ struct message { struct cmsghdr msg_hdr; int fd; } m;
+ ssize_t len;
+ int after_inflight, before_inflight;
+
+ before_inflight = getinflight();
+
+ bzero(&mh, sizeof(mh));
+ bzero(&m, sizeof(m));
+ mh.msg_control = &m;
+ mh.msg_controllen = sizeof(m);
+ m.msg_hdr.cmsg_len = sizeof(m);
+ m.msg_hdr.cmsg_level = SOL_SOCKET;
+ m.msg_hdr.cmsg_type = SCM_RIGHTS;
+ m.fd = fdtosend;
+ len = sendmsg(fd, &mh, 0);
+ if (len < 0)
+ err(-1, "%s: sendmsg", test);
+ after_inflight = getinflight();
+ if (after_inflight != before_inflight + 1)
+ errx(-1, "%s: sendfd: before %d after %d\n", test,
+ before_inflight, after_inflight);
+}
+
+static void
+close2(int fd1, int fd2)
+{
+
+ close(fd1);
+ close(fd2);
+}
+
+static void
+close3(int fd1, int fd2, int fd3)
+{
+
+ close2(fd1, fd2);
+ close(fd3);
+}
+
+static void
+close4(int fd1, int fd2, int fd3, int fd4)
+{
+
+ close2(fd1, fd2);
+ close2(fd3, fd4);
+}
+
+static void
+close5(int fd1, int fd2, int fd3, int fd4, int fd5)
+{
+
+ close3(fd1, fd2, fd3);
+ close2(fd4, fd5);
+}
+
+static int
+my_socket(int domain, int type, int proto)
+{
+ int sock;
+
+ sock = socket(domain, type, proto);
+ if (sock < 0)
+ err(-1, "%s: socket", test);
+ return (sock);
+}
+
+static void
+my_bind(int sock, struct sockaddr *sa, socklen_t len)
+{
+
+ if (bind(sock, sa, len) < 0)
+ err(-1, "%s: bind", test);
+}
+
+static void
+my_connect(int sock, struct sockaddr *sa, socklen_t len)
+{
+
+ if (connect(sock, sa, len) < 0 && errno != EINPROGRESS)
+ err(-1, "%s: connect", test);
+}
+
+static void
+my_listen(int sock, int backlog)
+{
+
+ if (listen(sock, backlog) < 0)
+ err(-1, "%s: listen", test);
+}
+
+static void
+my_socketpair(int *sv)
+{
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0)
+ err(-1, "%s: socketpair", test);
+}
+
+static void
+my_getsockname(int s, struct sockaddr *sa, socklen_t *salen)
+{
+
+ if (getsockname(s, sa, salen) < 0)
+ err(-1, "%s: getsockname", test);
+}
+
+static void
+setnonblock(int s)
+{
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) < 0)
+ err(-1, "%s: fcntl(F_SETFL, O_NONBLOCK)", test);
+}
+
+static void
+alloc3fds(int *s, int *sv)
+{
+
+ if ((*s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+ err(-1, "%s: socket", test);
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0)
+ err(-1, "%s: socketpair", test);
+}
+
+static void
+alloc5fds(int *s, int *sva, int *svb)
+{
+
+ if ((*s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+ err(-1, "%s: socket", test);
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sva) < 0)
+ err(-1, "%s: socketpair", test);
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, svb) < 0)
+ err(-1, "%s: socketpair", test);
+}
+
+static void
+save_sysctls(int *before_inflight, int *before_openfiles)
+{
+
+ *before_inflight = getinflight();
+ *before_openfiles = getopenfiles();
+}
+
+/*
+ * Try hard to make sure that the GC does in fact run before we test the
+ * condition of things.
+ */
+static void
+trigger_gc(void)
+{
+ int s;
+
+ if (forcegc) {
+ if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+ err(-1, "trigger_gc: socket");
+ close(s);
+ }
+ sleep(1);
+}
+
+static void
+test_sysctls(int before_inflight, int before_openfiles)
+{
+ int after_inflight, after_openfiles;
+
+ trigger_gc();
+ after_inflight = getinflight();
+ if (after_inflight != before_inflight)
+ warnx("%s: before inflight: %d, after inflight: %d",
+ test, before_inflight, after_inflight);
+
+ after_openfiles = getopenfiles();
+ if (after_openfiles != before_openfiles)
+ warnx("%s: before: %d, after: %d", test, before_openfiles,
+ after_openfiles);
+}
+
+static void
+twosome_nothing(void)
+{
+ int inflight, openfiles;
+ int sv[2];
+
+ /*
+ * Create a pair, close in one order.
+ */
+ test = "twosome_nothing1";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ my_socketpair(sv);
+ close2(sv[0], sv[1]);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * Create a pair, close in the other order.
+ */
+ test = "twosome_nothing2";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ my_socketpair(sv);
+ close2(sv[0], sv[1]);
+ test_sysctls(inflight, openfiles);
+}
+
+/*
+ * Using a socket pair, send various endpoints over the pair and close in
+ * various orders.
+ */
+static void
+twosome_drop_work(const char *testname, int sendvia, int tosend, int closefirst)
+{
+ int inflight, openfiles;
+ int sv[2];
+
+ printf("%s\n", testname);
+ test = testname;
+ save_sysctls(&inflight, &openfiles);
+ my_socketpair(sv);
+ sendfd(sv[sendvia], sv[tosend]);
+ if (closefirst == 0)
+ close2(sv[0], sv[1]);
+ else
+ close2(sv[1], sv[0]);
+ test_sysctls(inflight, openfiles);
+}
+
+static void
+twosome_drop(void)
+{
+
+ /*
+ * In various combations, some wastefully symmetric, create socket
+ * pairs and send one or another endpoint over one or another
+ * endpoint, closing the endpoints in various orders.
+ */
+ twosome_drop_work("twosome_drop1", 0, 0, 0);
+ twosome_drop_work("twosome_drop2", 0, 0, 1);
+ twosome_drop_work("twosome_drop3", 0, 1, 0);
+ twosome_drop_work("twosome_drop4", 0, 1, 1);
+ twosome_drop_work("twosome_drop5", 1, 0, 0);
+ twosome_drop_work("twosome_drop6", 1, 0, 1);
+ twosome_drop_work("twosome_drop7", 1, 1, 0);
+ twosome_drop_work("twosome_drop8", 1, 1, 1);
+}
+
+static void
+threesome_nothing(void)
+{
+ int inflight, openfiles;
+ int s, sv[2];
+
+ test = "threesome_nothing";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc3fds(&s, sv);
+ close3(s, sv[0], sv[1]);
+ test_sysctls(inflight, openfiles);
+}
+
+/*
+ * threesome_drop: create a pair and a spare, send the spare over the pair, and
+ * close in various orders and make sure all the fds went away.
+ */
+static void
+threesome_drop(void)
+{
+ int inflight, openfiles;
+ int s, sv[2];
+
+ /*
+ * threesome_drop1: close sent send receive
+ */
+ test = "threesome_drop1";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc3fds(&s, sv);
+ sendfd(sv[0], s);
+ close3(s, sv[0], sv[1]);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * threesome_drop2: close sent receive send
+ */
+ test = "threesome_drop2";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc3fds(&s, sv);
+ sendfd(sv[0], s);
+ close3(s, sv[1], sv[0]);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * threesome_drop3: close receive sent send
+ */
+ test = "threesome_drop3";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc3fds(&s, sv);
+ sendfd(sv[0], s);
+ close3(sv[1], s, sv[0]);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * threesome_drop4: close receive send sent
+ */
+ test = "threesome_drop4";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc3fds(&s, sv);
+ sendfd(sv[0], s);
+ close3(sv[1], sv[0], s);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * threesome_drop5: close send receive sent
+ */
+ test = "threesome_drop5";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc3fds(&s, sv);
+ sendfd(sv[0], s);
+ close3(sv[0], sv[1], s);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * threesome_drop6: close send sent receive
+ */
+ test = "threesome_drop6";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc3fds(&s, sv);
+ close3(sv[0], s, sv[1]);
+ test_sysctls(inflight, openfiles);
+}
+
+/*
+ * Fivesome tests: create two socket pairs and a spare, send the spare over
+ * the first socket pair, then send the first socket pair over the second
+ * socket pair, and GC. Do various closes at various points to exercise
+ * various cases.
+ */
+static void
+fivesome_nothing(void)
+{
+ int inflight, openfiles;
+ int spare, sva[2], svb[2];
+
+ test = "fivesome_nothing";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc5fds(&spare, sva, svb);
+ close5(spare, sva[0], sva[1], svb[0], svb[1]);
+ test_sysctls(inflight, openfiles);
+}
+
+static void
+fivesome_drop_work(const char *testname, int close_spare_after_send,
+ int close_sva_after_send)
+{
+ int inflight, openfiles;
+ int spare, sva[2], svb[2];
+
+ printf("%s\n", testname);
+ test = testname;
+ save_sysctls(&inflight, &openfiles);
+ alloc5fds(&spare, sva, svb);
+
+ /*
+ * Send spare over sva.
+ */
+ sendfd(sva[0], spare);
+ if (close_spare_after_send)
+ close(spare);
+
+ /*
+ * Send sva over svb.
+ */
+ sendfd(svb[0], sva[0]);
+ sendfd(svb[0], sva[1]);
+ if (close_sva_after_send)
+ close2(sva[0], sva[1]);
+
+ close2(svb[0], svb[1]);
+
+ if (!close_sva_after_send)
+ close2(sva[0], sva[1]);
+ if (!close_spare_after_send)
+ close(spare);
+
+ test_sysctls(inflight, openfiles);
+}
+
+static void
+fivesome_drop(void)
+{
+
+ fivesome_drop_work("fivesome_drop1", 0, 0);
+ fivesome_drop_work("fivesome_drop2", 0, 1);
+ fivesome_drop_work("fivesome_drop3", 1, 0);
+ fivesome_drop_work("fivesome_drop4", 1, 1);
+}
+
+/*
+ * Create a somewhat nasty dual-socket socket intended to upset the garbage
+ * collector if mark-and-sweep is wrong.
+ */
+static void
+complex_cycles(void)
+{
+ int inflight, openfiles;
+ int spare, sva[2], svb[2];
+
+ test = "complex_cycles";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ alloc5fds(&spare, sva, svb);
+ sendfd(sva[0], svb[0]);
+ sendfd(sva[0], svb[1]);
+ sendfd(svb[0], sva[0]);
+ sendfd(svb[0], sva[1]);
+ sendfd(svb[0], spare);
+ sendfd(sva[0], spare);
+ close5(spare, sva[0], sva[1], svb[0], svb[1]);
+ test_sysctls(inflight, openfiles);
+}
+
+/*
+ * Listen sockets can also be passed over UNIX domain sockets, so test
+ * various cases, including ones where listen sockets have waiting sockets
+ * hanging off them...
+ */
+static void
+listen_nothing(void)
+{
+ struct sockaddr_un sun;
+ struct sockaddr_in sin;
+ int inflight, openfiles;
+ int s;
+
+ test = "listen_nothing_unp";
+ printf("%s\n", test);
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test);
+ save_sysctls(&inflight, &openfiles);
+ s = my_socket(PF_LOCAL, SOCK_STREAM, 0);
+ my_bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ my_listen(s, -1);
+ close(s);
+ (void)unlink(sun.sun_path);
+ test_sysctls(inflight, openfiles);
+
+ test = "listen_nothing_inet";
+ printf("%s\n", test);
+ 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(0);
+ save_sysctls(&inflight, &openfiles);
+ s = my_socket(PF_INET, SOCK_STREAM, 0);
+ my_bind(s, (struct sockaddr *)&sin, sizeof(sin));
+ my_listen(s, -1);
+ close(s);
+ test_sysctls(inflight, openfiles);
+}
+
+/*
+ * Send a listen UDP socket over a UNIX domain socket.
+ *
+ * Send a listen TCP socket over a UNIX domain socket.
+ *
+ * Do each twice, with closing of the listen socket vs. socketpair in
+ * different orders.
+ */
+static void
+listen_drop(void)
+{
+ struct sockaddr_un sun;
+ struct sockaddr_in sin;
+ int inflight, openfiles;
+ int s, sv[2];
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+
+ /*
+ * Close listen socket first.
+ */
+ test = "listen_drop_unp1";
+ printf("%s\n", test);
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test);
+ save_sysctls(&inflight, &openfiles);
+ s = my_socket(PF_LOCAL, SOCK_STREAM, 0);
+ my_bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ my_listen(s, -1);
+ my_socketpair(sv);
+ sendfd(sv[0], s);
+ close3(s, sv[0], sv[1]);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * Close socketpair first.
+ */
+ test = "listen_drop_unp2";
+ printf("%s\n", test);
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test);
+ save_sysctls(&inflight, &openfiles);
+ s = my_socket(PF_LOCAL, SOCK_STREAM, 0);
+ my_bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ my_listen(s, -1);
+ my_socketpair(sv);
+ sendfd(sv[0], s);
+ close3(sv[0], sv[1], s);
+ test_sysctls(inflight, openfiles);
+
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof(sin);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(0);
+
+ /*
+ * Close listen socket first.
+ */
+ test = "listen_drop_inet1";
+ printf("%s\n", test);
+ bzero(&sun, sizeof(sun));
+ save_sysctls(&inflight, &openfiles);
+ s = my_socket(PF_INET, SOCK_STREAM, 0);
+ my_bind(s, (struct sockaddr *)&sin, sizeof(sin));
+ my_listen(s, -1);
+ my_socketpair(sv);
+ sendfd(sv[0], s);
+ close3(s, sv[0], sv[1]);
+ test_sysctls(inflight, openfiles);
+
+ /*
+ * Close socketpair first.
+ */
+ test = "listen_drop_inet2";
+ printf("%s\n", test);
+ bzero(&sun, sizeof(sun));
+ save_sysctls(&inflight, &openfiles);
+ s = my_socket(PF_INET, SOCK_STREAM, 0);
+ my_bind(s, (struct sockaddr *)&sin, sizeof(sin));
+ my_listen(s, -1);
+ my_socketpair(sv);
+ sendfd(sv[0], s);
+ close3(sv[0], sv[1], s);
+ test_sysctls(inflight, openfiles);
+}
+
+/*
+ * Up things a notch with listen sockets: add connections that can be
+ * accepted to the listen queues.
+ */
+static void
+listen_connect_nothing(void)
+{
+ struct sockaddr_in sin;
+ int slisten, sconnect, sv[2];
+ int inflight, openfiles;
+ socklen_t len;
+
+ test = "listen_connect_nothing";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+
+ slisten = my_socket(PF_INET, SOCK_STREAM, 0);
+ my_bind(slisten, (struct sockaddr *)&sin, sizeof(sin));
+ my_listen(slisten, -1);
+
+ my_socketpair(sv);
+
+ len = sizeof(sin);
+ my_getsockname(slisten, (struct sockaddr *)&sin, &len);
+
+ sconnect = my_socket(PF_INET, SOCK_STREAM, 0);
+ setnonblock(sconnect);
+ my_connect(sconnect, (struct sockaddr *)&sin, len);
+
+ sleep(1);
+
+ close4(slisten, sconnect, sv[0], sv[1]);
+
+ test_sysctls(inflight, openfiles);
+}
+
+static void
+listen_connect_drop(void)
+{
+ struct sockaddr_in sin;
+ int slisten, sconnect, sv[2];
+ int inflight, openfiles;
+ socklen_t len;
+
+ test = "listen_connect_drop";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+
+ slisten = my_socket(PF_INET, SOCK_STREAM, 0);
+ my_bind(slisten, (struct sockaddr *)&sin, sizeof(sin));
+ my_listen(slisten, -1);
+
+ my_socketpair(sv);
+
+ len = sizeof(sin);
+ my_getsockname(slisten, (struct sockaddr *)&sin, &len);
+
+ sconnect = my_socket(PF_INET, SOCK_STREAM, 0);
+ setnonblock(sconnect);
+ my_connect(sconnect, (struct sockaddr *)&sin, len);
+
+ sleep(1);
+ sendfd(sv[0], slisten);
+ close3(slisten, sv[0], sv[1]);
+ sleep(1);
+ close(sconnect);
+
+ test_sysctls(inflight, openfiles);
+}
+
+static void
+recursion(void)
+{
+ int fd[2], ff[2];
+ int inflight, openfiles, deferred, deferred1;
+
+ test = "recursion";
+ printf("%s\n", test);
+ save_sysctls(&inflight, &openfiles);
+ deferred = getdeferred();
+
+ my_socketpair(fd);
+
+ for (;;) {
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, ff) == -1) {
+ if (errno == EMFILE || errno == ENFILE)
+ break;
+ err(-1, "socketpair");
+ }
+ sendfd(ff[0], fd[0]);
+ sendfd(ff[0], fd[1]);
+ close2(fd[1], fd[0]);
+ fd[0] = ff[0];
+ fd[1] = ff[1];
+ }
+ close2(fd[0], fd[1]);
+ sleep(1);
+ test_sysctls(inflight, openfiles);
+ deferred1 = getdeferred();
+ if (deferred != deferred1)
+ errx(-1, "recursion: deferred before %d after %d", deferred,
+ deferred1);
+}
+
+#define RMDIR "rm -Rf "
+int
+main(int argc, char *argv[])
+{
+ char cmd[sizeof(RMDIR) + PATH_MAX];
+ int serrno;
+ pid_t pid;
+
+ strlcpy(dpath, "/tmp/unpgc.XXXXXXXX", sizeof(dpath));
+ if (mkdtemp(dpath) == NULL)
+ err(-1, "mkdtemp");
+
+ /*
+ * Set up a parent process to GC temporary storage when we're done.
+ */
+ pid = fork();
+ if (pid < 0) {
+ serrno = errno;
+ (void)rmdir(dpath);
+ errno = serrno;
+ err(-1, "fork");
+ }
+ if (pid > 0) {
+ signal(SIGINT, SIG_IGN);
+ while (waitpid(pid, NULL, 0) != pid);
+ snprintf(cmd, sizeof(cmd), "%s %s", RMDIR, dpath);
+ (void)system(cmd);
+ exit(0);
+ }
+
+ printf("Start: inflight %d open %d\n", getinflight(),
+ getopenfiles());
+
+ twosome_nothing();
+ twosome_drop();
+
+ threesome_nothing();
+ threesome_drop();
+
+ fivesome_nothing();
+ fivesome_drop();
+
+ complex_cycles();
+
+ listen_nothing();
+ listen_drop();
+
+ listen_connect_nothing();
+ listen_connect_drop();
+
+ recursion();
+
+ printf("Finish: inflight %d open %d\n", getinflight(),
+ getopenfiles());
+ return (0);
+}
diff --git a/tools/regression/sockets/unix_passfd/Makefile b/tools/regression/sockets/unix_passfd/Makefile
new file mode 100644
index 0000000..600b7b1
--- /dev/null
+++ b/tools/regression/sockets/unix_passfd/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_passfd
+MAN=
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_passfd/unix_passfd.c b/tools/regression/sockets/unix_passfd/unix_passfd.c
new file mode 100644
index 0000000..07ef589
--- /dev/null
+++ b/tools/regression/sockets/unix_passfd/unix_passfd.c
@@ -0,0 +1,389 @@
+/*-
+ * 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/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * UNIX domain sockets allow file descriptors to be passed via "ancillary
+ * data", or control messages. This regression test is intended to exercise
+ * this facility, both performing some basic tests that it operates, and also
+ * causing some kernel edge cases to execute, such as garbage collection when
+ * there are cyclic file descriptor references. Right now we test only with
+ * stream sockets, but ideally we'd also test with datagram sockets.
+ */
+
+static void
+domainsocketpair(const char *test, int *fdp)
+{
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, fdp) < 0)
+ err(-1, "%s: socketpair(PF_UNIX, SOCK_STREAM)", test);
+}
+
+static void
+closesocketpair(int *fdp)
+{
+
+ close(fdp[0]);
+ close(fdp[1]);
+}
+
+static void
+devnull(const char *test, int *fdp)
+{
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY);
+ if (fd < 0)
+ err(-1, "%s: open(/dev/null)", test);
+ *fdp = fd;
+}
+
+static void
+tempfile(const char *test, int *fdp)
+{
+ char path[PATH_MAX];
+ int fd;
+
+ snprintf(path, PATH_MAX, "/tmp/unix_passfd.XXXXXXXXXXXXXXX");
+ fd = mkstemp(path);
+ if (fd < 0)
+ err(-1, "%s: mkstemp(%s)", test, path);
+ (void)unlink(path);
+ *fdp = fd;
+}
+
+static void
+dofstat(const char *test, int fd, struct stat *sb)
+{
+
+ if (fstat(fd, sb) < 0)
+ err(-1, "%s: fstat", test);
+}
+
+static void
+samefile(const char *test, struct stat *sb1, struct stat *sb2)
+{
+
+ if (sb1->st_dev != sb2->st_dev)
+ errx(-1, "%s: samefile: different device", test);
+ if (sb1->st_ino != sb2->st_ino)
+ errx(-1, "%s: samefile: different inode", test);
+}
+
+static void
+sendfd_payload(const char *test, int sockfd, int sendfd,
+ void *payload, size_t paylen)
+{
+ struct iovec iovec;
+ char message[CMSG_SPACE(sizeof(int))];
+ struct cmsghdr *cmsghdr;
+ struct msghdr msghdr;
+ ssize_t len;
+
+ bzero(&msghdr, sizeof(msghdr));
+ bzero(&message, sizeof(message));
+
+ msghdr.msg_control = message;
+ msghdr.msg_controllen = sizeof(message);
+
+ iovec.iov_base = payload;
+ iovec.iov_len = paylen;
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ cmsghdr = (struct cmsghdr *)message;
+ cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsghdr->cmsg_level = SOL_SOCKET;
+ cmsghdr->cmsg_type = SCM_RIGHTS;
+ *(int *)CMSG_DATA(cmsghdr) = sendfd;
+
+ len = sendmsg(sockfd, &msghdr, 0);
+ if (len < 0)
+ err(-1, "%s: sendmsg", test);
+ if ((size_t)len != paylen)
+ errx(-1, "%s: sendmsg: %zd bytes sent", test, len);
+}
+
+static void
+sendfd(const char *test, int sockfd, int sendfd)
+{
+ char ch;
+
+ return (sendfd_payload(test, sockfd, sendfd, &ch, sizeof(ch)));
+}
+
+static void
+recvfd_payload(const char *test, int sockfd, int *recvfd,
+ void *buf, size_t buflen)
+{
+ struct cmsghdr *cmsghdr;
+ char message[CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + sizeof(int)];
+ struct msghdr msghdr;
+ struct iovec iovec;
+ ssize_t len;
+
+ bzero(&msghdr, sizeof(msghdr));
+
+ msghdr.msg_control = message;
+ msghdr.msg_controllen = sizeof(message);
+
+ iovec.iov_base = buf;
+ iovec.iov_len = buflen;
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ len = recvmsg(sockfd, &msghdr, 0);
+ if (len < 0)
+ err(-1, "%s: recvmsg", test);
+ if ((size_t)len != buflen)
+ errx(-1, "%s: recvmsg: %zd bytes received", test, len);
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ if (cmsghdr == NULL)
+ errx(-1, "%s: recvmsg: did not receive control message", test);
+ *recvfd = -1;
+ for (; cmsghdr != NULL; cmsghdr = CMSG_NXTHDR(&msghdr, cmsghdr)) {
+ if (cmsghdr->cmsg_level == SOL_SOCKET &&
+ cmsghdr->cmsg_type == SCM_RIGHTS &&
+ cmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) {
+ *recvfd = *(int *)CMSG_DATA(cmsghdr);
+ if (*recvfd == -1)
+ errx(-1, "%s: recvmsg: received fd -1", test);
+ }
+ }
+ if (*recvfd == -1)
+ errx(-1, "%s: recvmsg: did not receive single-fd message",
+ test);
+}
+
+static void
+recvfd(const char *test, int sockfd, int *recvfd)
+{
+ char ch;
+
+ return (recvfd_payload(test, sockfd, recvfd, &ch, sizeof(ch)));
+}
+
+int
+main(void)
+{
+ struct stat putfd_1_stat, putfd_2_stat, getfd_1_stat, getfd_2_stat;
+ int fd[2], putfd_1, putfd_2, getfd_1, getfd_2;
+ const char *test;
+
+ /*
+ * First test: put a temporary file into a UNIX domain socket, then
+ * take it out and make sure it's the same file. First time around,
+ * don't close the reference after sending.
+ */
+ test = "test1-simplesendfd";
+ printf("beginning %s\n", test);
+
+ domainsocketpair(test, fd);
+ tempfile(test, &putfd_1);
+ dofstat(test, putfd_1, &putfd_1_stat);
+ sendfd(test, fd[0], putfd_1);
+ recvfd(test, fd[1], &getfd_1);
+ dofstat(test, getfd_1, &getfd_1_stat);
+ samefile(test, &putfd_1_stat, &getfd_1_stat);
+ close(putfd_1);
+ close(getfd_1);
+ closesocketpair(fd);
+
+ printf("%s passed\n", test);
+
+ /*
+ * Second test: same as first, only close the file reference after
+ * sending, so that the only reference is the descriptor in the UNIX
+ * domain socket buffer.
+ */
+ test = "test2-sendandclose";
+ printf("beginning %s\n", test);
+
+ domainsocketpair(test, fd);
+ tempfile(test, &putfd_1);
+ dofstat(test, putfd_1, &putfd_1_stat);
+ sendfd(test, fd[0], putfd_1);
+ close(putfd_1);
+ recvfd(test, fd[1], &getfd_1);
+ dofstat(test, getfd_1, &getfd_1_stat);
+ samefile(test, &putfd_1_stat, &getfd_1_stat);
+ close(getfd_1);
+ closesocketpair(fd);
+
+ printf("%s passed\n", test);
+
+ /*
+ * Third test: put a temporary file into a UNIX domain socket, then
+ * close both endpoints causing garbage collection to kick off.
+ */
+ test = "test3-sendandcancel";
+ printf("beginning %s\n", test);
+
+ domainsocketpair(test, fd);
+ tempfile(test, &putfd_1);
+ sendfd(test, fd[0], putfd_1);
+ close(putfd_1);
+ closesocketpair(fd);
+
+ printf("%s passed\n", test);
+
+ /*
+ * Send two files. Then receive them. Make sure they are returned
+ * in the right order, and both get there.
+ */
+
+ test = "test4-twofile";
+ printf("beginning %s\n", test);
+
+ domainsocketpair(test, fd);
+ tempfile(test, &putfd_1);
+ tempfile(test, &putfd_2);
+ dofstat(test, putfd_1, &putfd_1_stat);
+ dofstat(test, putfd_2, &putfd_2_stat);
+ sendfd(test, fd[0], putfd_1);
+ sendfd(test, fd[0], putfd_2);
+ close(putfd_1);
+ close(putfd_2);
+ recvfd(test, fd[1], &getfd_1);
+ recvfd(test, fd[1], &getfd_2);
+ dofstat(test, getfd_1, &getfd_1_stat);
+ dofstat(test, getfd_2, &getfd_2_stat);
+ samefile(test, &putfd_1_stat, &getfd_1_stat);
+ samefile(test, &putfd_2_stat, &getfd_2_stat);
+ close(getfd_1);
+ close(getfd_2);
+ closesocketpair(fd);
+
+ printf("%s passed\n", test);
+
+ /*
+ * Big bundling test. Send an endpoint of the UNIX domain socket
+ * over itself, closing the door behind it.
+ */
+
+ test = "test5-bundle";
+ printf("beginning %s\n", test);
+
+ domainsocketpair(test, fd);
+
+ sendfd(test, fd[0], fd[0]);
+ close(fd[0]);
+ recvfd(test, fd[1], &getfd_1);
+ close(getfd_1);
+ close(fd[1]);
+
+ printf("%s passed\n", test);
+
+ /*
+ * Big bundling test part two: Send an endpoint of the UNIX domain
+ * socket over itself, close the door behind it, and never remove it
+ * from the other end.
+ */
+
+ test = "test6-bundlecancel";
+ printf("beginning %s\n", test);
+
+ domainsocketpair(test, fd);
+ sendfd(test, fd[0], fd[0]);
+ sendfd(test, fd[1], fd[0]);
+ closesocketpair(fd);
+
+ printf("%s passed\n", test);
+
+ /*
+ * Test for PR 151758: Send an character device over the UNIX
+ * domain socket and then close both sockets to orphan the
+ * device.
+ */
+
+ test = "test7-devfsorphan";
+ printf("beginning %s\n", test);
+
+ domainsocketpair(test, fd);
+ devnull(test, &putfd_1);
+ sendfd(test, fd[0], putfd_1);
+ close(putfd_1);
+ closesocketpair(fd);
+
+ printf("%s passed\n", test);
+
+ /*
+ * Test for PR 181741. Receiver sets LOCAL_CREDS, and kernel
+ * prepends a control message to the data. Sender sends large
+ * payload. Payload + SCM_RIGHTS + LOCAL_CREDS hit socket buffer
+ * limit, and receiver receives truncated data.
+ */
+ test = "test8-rights+creds+payload";
+ printf("beginning %s\n", test);
+
+ {
+ const int on = 1;
+ u_long sendspace;
+ size_t len;
+ void *buf;
+
+ len = sizeof(sendspace);
+ if (sysctlbyname("net.local.stream.sendspace", &sendspace,
+ &len, NULL, 0) < 0)
+ err(-1, "%s: sysctlbyname(net.local.stream.sendspace)",
+ test);
+
+ if ((buf = malloc(sendspace)) == NULL)
+ err(-1, "%s: malloc", test);
+
+ domainsocketpair(test, fd);
+ if (setsockopt(fd[1], 0, LOCAL_CREDS, &on, sizeof(on)) < 0)
+ err(-1, "%s: setsockopt(LOCAL_CREDS)", test);
+ tempfile(test, &putfd_1);
+ sendfd_payload(test, fd[0], putfd_1, buf, sendspace);
+ recvfd_payload(test, fd[1], &getfd_1, buf, sendspace);
+ close(putfd_1);
+ close(getfd_1);
+ closesocketpair(fd);
+ }
+
+ printf("%s passed\n", test);
+
+ return (0);
+}
diff --git a/tools/regression/sockets/unix_sendtorace/Makefile b/tools/regression/sockets/unix_sendtorace/Makefile
new file mode 100644
index 0000000..75e7b9f
--- /dev/null
+++ b/tools/regression/sockets/unix_sendtorace/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_sendtorace
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_sendtorace/unix_sendtorace.c b/tools/regression/sockets/unix_sendtorace/unix_sendtorace.c
new file mode 100644
index 0000000..9fd748d
--- /dev/null
+++ b/tools/regression/sockets/unix_sendtorace/unix_sendtorace.c
@@ -0,0 +1,215 @@
+/*-
+ * 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$
+ */
+
+/*
+ * Attempts to exercise UNIX domain socket races relating to the non-atomic
+ * connect-and-send properties of sendto(). As the result of such a race is
+ * a kernel panic, this test simply completes or doesn't.
+ *
+ * XXX: Despite implementing support for sendto() on stream sockets with
+ * implied connect, the appropriate flag isn't set in the FreeBSD kernel so
+ * it does not work. For now, don't call the stream test.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ITERATIONS 1000000
+
+static char socket_path[] = "tmp.XXXXXX";
+
+static void
+stream_server(int listenfd)
+{
+ int acceptfd;
+
+ while (1) {
+ acceptfd = accept(listenfd, NULL, NULL);
+ if (acceptfd < 0) {
+ warn("stream_server: accept");
+ continue;
+ }
+ sleep(1);
+ close(acceptfd);
+ }
+}
+
+static void
+stream_client(void)
+{
+ struct sockaddr_un sun;
+ ssize_t len;
+ char c = 0;
+ int fd, i;
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_len = sizeof(sun);
+ sun.sun_family = AF_UNIX;
+ strcpy(sun.sun_path, socket_path);
+ for (i = 0; i < ITERATIONS; i++) {
+ fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ warn("stream_client: socket");
+ return;
+ }
+ len = sendto(fd, &c, sizeof(c), 0, (struct sockaddr *)&sun,
+ sizeof(sun));
+ if (len < 0)
+ warn("stream_client: sendto");
+ close(fd);
+ }
+}
+
+static void
+stream_test(void)
+{
+ struct sockaddr_un sun;
+ pid_t childpid;
+ int listenfd;
+
+ listenfd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (listenfd < 0)
+ err(-1, "stream_test: socket");
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_len = sizeof(sun);
+ sun.sun_family = AF_UNIX;
+ strcpy(sun.sun_path, socket_path);
+
+ if (bind(listenfd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
+ err(-1, "stream_test: bind");
+
+ if (listen(listenfd, -1) < 0)
+ err(-1, "stream_test: listen");
+
+ childpid = fork();
+ if (childpid < 0)
+ err(-1, "stream_test: fork");
+
+ if (childpid != 0) {
+ sleep(1);
+ stream_client();
+ kill(childpid, SIGTERM);
+ sleep(1);
+ } else
+ stream_server(listenfd);
+
+ (void)unlink(socket_path);
+}
+
+static void
+datagram_server(int serverfd)
+{
+ ssize_t len;
+ char c;
+
+ while (1) {
+ len = recv(serverfd, &c, sizeof(c), 0);
+ if (len < 0)
+ warn("datagram_server: recv");
+ }
+}
+
+static void
+datagram_client(void)
+{
+ struct sockaddr_un sun;
+ ssize_t len;
+ char c = 0;
+ int fd, i;
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_len = sizeof(sun);
+ sun.sun_family = AF_UNIX;
+ strcpy(sun.sun_path, socket_path);
+ for (i = 0; i < ITERATIONS; i++) {
+ fd = socket(PF_UNIX, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ warn("datagram_client: socket");
+ return;
+ }
+ len = sendto(fd, &c, sizeof(c), 0, (struct sockaddr *)&sun,
+ sizeof(sun));
+ if (len < 0)
+ warn("datagram_client: sendto");
+ close(fd);
+ }
+}
+
+static void
+datagram_test(void)
+{
+ struct sockaddr_un sun;
+ pid_t childpid;
+ int serverfd;
+
+ serverfd = socket(PF_UNIX, SOCK_DGRAM, 0);
+ if (serverfd < 0)
+ err(-1, "datagram_test: socket");
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_len = sizeof(sun);
+ sun.sun_family = AF_UNIX;
+ strcpy(sun.sun_path, socket_path);
+
+ if (bind(serverfd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
+ err(-1, "datagram_test: bind");
+
+ childpid = fork();
+ if (childpid < 0)
+ err(-1, "datagram_test: fork");
+
+ if (childpid != 0) {
+ sleep(1);
+ datagram_client();
+ kill(childpid, SIGTERM);
+ sleep(1);
+ } else
+ datagram_server(serverfd);
+
+ (void)unlink(socket_path);
+}
+
+int
+main(void)
+{
+
+ if (mkstemp(socket_path) == -1)
+ err(1, "mkstemp failed");
+ (void)unlink(socket_path);
+ datagram_test();
+ if (0)
+ stream_test();
+ return (0);
+}
diff --git a/tools/regression/sockets/unix_socket/Makefile b/tools/regression/sockets/unix_socket/Makefile
new file mode 100644
index 0000000..bb60456
--- /dev/null
+++ b/tools/regression/sockets/unix_socket/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_socket
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_socket/unix_socket.c b/tools/regression/sockets/unix_socket/unix_socket.c
new file mode 100644
index 0000000..085366f
--- /dev/null
+++ b/tools/regression/sockets/unix_socket/unix_socket.c
@@ -0,0 +1,84 @@
+/*-
+ * 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$
+ */
+
+/*
+ * Simple UNIX domain socket regression test: create and tear down various
+ * supported and unsupported socket types.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+ int sock, socks[2];
+
+ sock = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (sock < 0)
+ err(-1, "socket(PF_LOCAL, SOCK_STREAM, 0)");
+ close(sock);
+
+ sock = socket(PF_LOCAL, SOCK_DGRAM, 0);
+ if (sock < 0)
+ err(-1, "socket(PF_LOCAL, SOCK_DGRAM, 0)");
+ close(sock);
+
+ sock = socket(PF_LOCAL, SOCK_RAW, 0);
+ if (sock >= 0) {
+ close(sock);
+ errx(-1, "socket(PF_LOCAL, SOCK_RAW, 0) returned %d", sock);
+ }
+ if (errno != EPROTONOSUPPORT)
+ err(-1, "socket(PF_LOCAL, SOCK_RAW, 0)");
+
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, socks) < 0)
+ err(-1, "socketpair(PF_LOCAL, SOCK_STREAM, 0, socks)");
+ if (socks[0] < 0)
+ errx(-1, "socketpair(PF_LOCAL, SOCK_STREAM, 0, socks) [0] < 0");
+ if (socks[1] < 0)
+ errx(-1, "socketpair(PF_LOCAL, SOCK_STREAM, 0, socks) [1] < 1");
+ close(socks[0]);
+ close(socks[1]);
+
+ if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks) < 0)
+ err(-1, "socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks)");
+ if (socks[0] < 0)
+ errx(-1, "socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks) [0] < 0");
+ if (socks[1] < 0)
+ errx(-1, "socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks) [1] < 1");
+ close(socks[0]);
+ close(socks[1]);
+
+ return (0);
+}
diff --git a/tools/regression/sockets/unix_sorflush/Makefile b/tools/regression/sockets/unix_sorflush/Makefile
new file mode 100644
index 0000000..db0aa6a
--- /dev/null
+++ b/tools/regression/sockets/unix_sorflush/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= unix_sorflush
+MAN=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/unix_sorflush/unix_sorflush.c b/tools/regression/sockets/unix_sorflush/unix_sorflush.c
new file mode 100644
index 0000000..e0deb00
--- /dev/null
+++ b/tools/regression/sockets/unix_sorflush/unix_sorflush.c
@@ -0,0 +1,99 @@
+/*-
+ * Copyright (c) 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.
+ */
+
+/*
+ * Reproduce a race in which:
+ *
+ * - Process (a) is blocked in read on a socket waiting on data.
+ * - Process (b) is blocked in shutdown() on a socket waiting on (a).
+ * - Process (c) delivers a signal to (b) interrupting its wait.
+ *
+ * This race is premised on shutdown() not interrupting (a) properly, and the
+ * signal to (b) causing problems in the kernel.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/socket.h>
+
+#include <err.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void
+receive_and_exit(int s)
+{
+ ssize_t ssize;
+ char ch;
+
+ ssize = recv(s, &ch, sizeof(ch), 0);
+ if (ssize < 0)
+ err(-1, "receive_and_exit: recv");
+ exit(0);
+}
+
+static void
+shutdown_and_exit(int s)
+{
+
+ if (shutdown(s, SHUT_RD) < 0)
+ err(-1, "shutdown_and_exit: shutdown");
+ exit(0);
+}
+
+int
+main(void)
+{
+ pid_t pida, pidb;
+ int sv[2];
+
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sv) < 0)
+ err(-1, "socketpair");
+
+ pida = fork();
+ if (pida < 0)
+ err(-1, "fork");
+ if (pida == 0)
+ receive_and_exit(sv[1]);
+ sleep(1);
+ pidb = fork();
+ if (pidb < 0) {
+ warn("fork");
+ (void)kill(pida, SIGKILL);
+ exit(-1);
+ }
+ if (pidb == 0)
+ shutdown_and_exit(sv[1]);
+ sleep(1);
+ if (kill(pidb, SIGKILL) < 0)
+ err(-1, "kill");
+ sleep(1);
+ printf("ok 1 - unix_sorflush\n");
+ exit(0);
+}
diff --git a/tools/regression/sockets/zerosend/Makefile b/tools/regression/sockets/zerosend/Makefile
new file mode 100644
index 0000000..1562acc
--- /dev/null
+++ b/tools/regression/sockets/zerosend/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= zerosend
+MAN=
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/sockets/zerosend/zerosend.c b/tools/regression/sockets/zerosend/zerosend.c
new file mode 100644
index 0000000..1f3217a
--- /dev/null
+++ b/tools/regression/sockets/zerosend/zerosend.c
@@ -0,0 +1,290 @@
+/*-
+ * Copyright (c) 2007 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/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PORT1 10001
+#define PORT2 10002
+
+static void
+try_0send(const char *test, int fd)
+{
+ ssize_t len;
+ char ch;
+
+ ch = 0;
+ len = send(fd, &ch, 0, 0);
+ if (len < 0)
+ err(-1, "%s: try_0send", test);
+ if (len != 0)
+ errx(-1, "%s: try_0send: returned %zd", test, len);
+}
+
+static void
+try_0write(const char *test, int fd)
+{
+ ssize_t len;
+ char ch;
+
+ ch = 0;
+ len = write(fd, &ch, 0);
+ if (len < 0)
+ err(-1, "%s: try_0write", test);
+ if (len != 0)
+ errx(-1, "%s: try_0write: returned %zd", test, len);
+}
+
+static void
+setup_udp(const char *test, int *fdp)
+{
+ struct sockaddr_in sin;
+ int sock1, sock2;
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ sin.sin_port = htons(PORT1);
+ sock1 = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sock1 < 0)
+ err(-1, "%s: setup_udp: socket", test);
+ if (bind(sock1, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "%s: setup_udp: bind(%s, %d)", test,
+ inet_ntoa(sin.sin_addr), PORT1);
+ sin.sin_port = htons(PORT2);
+ if (connect(sock1, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "%s: setup_udp: connect(%s, %d)", test,
+ inet_ntoa(sin.sin_addr), PORT2);
+
+ sock2 = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sock2 < 0)
+ err(-1, "%s: setup_udp: socket", test);
+ if (bind(sock2, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "%s: setup_udp: bind(%s, %d)", test,
+ inet_ntoa(sin.sin_addr), PORT2);
+ sin.sin_port = htons(PORT1);
+ if (connect(sock2, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "%s: setup_udp: connect(%s, %d)", test,
+ inet_ntoa(sin.sin_addr), PORT1);
+
+ fdp[0] = sock1;
+ fdp[1] = sock2;
+}
+
+static void
+setup_tcp(const char *test, int *fdp)
+{
+ fd_set writefds, exceptfds;
+ struct sockaddr_in sin;
+ int ret, sock1, sock2, sock3;
+ struct timeval tv;
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ /*
+ * First set up the listen socket.
+ */
+ sin.sin_port = htons(PORT1);
+ sock1 = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock1 < 0)
+ err(-1, "%s: setup_tcp: socket", test);
+ if (bind(sock1, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "%s: bind(%s, %d)", test, inet_ntoa(sin.sin_addr),
+ PORT1);
+ if (listen(sock1, -1) < 0)
+ err(-1, "%s: listen", test);
+
+ /*
+ * Now connect to it, non-blocking so that we don't deadlock against
+ * ourselves.
+ */
+ sock2 = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock2 < 0)
+ err(-1, "%s: setup_tcp: socket", test);
+ if (fcntl(sock2, F_SETFL, O_NONBLOCK) < 0)
+ err(-1, "%s: setup_tcp: fcntl(O_NONBLOCK)", test);
+ if (connect(sock2, (struct sockaddr *)&sin, sizeof(sin)) < 0 &&
+ errno != EINPROGRESS)
+ err(-1, "%s: setup_tcp: connect(%s, %d)", test,
+ inet_ntoa(sin.sin_addr), PORT1);
+
+ /*
+ * Now pick up the connection after sleeping a moment to make sure
+ * there's been time for some packets to go back and forth.
+ */
+ if (sleep(1) < 0)
+ err(-1, "%s: sleep(1)", test);
+ sock3 = accept(sock1, NULL, NULL);
+ if (sock3 < 0)
+ err(-1, "%s: accept", test);
+ if (sleep(1) < 0)
+ err(-1, "%s: sleep(1)", test);
+
+ FD_ZERO(&writefds);
+ FD_SET(sock2, &writefds);
+ FD_ZERO(&exceptfds);
+ FD_SET(sock2, &exceptfds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ ret = select(sock2 + 1, NULL, &writefds, &exceptfds, &tv);
+ if (ret < 0)
+ err(-1, "%s: setup_tcp: select", test);
+ if (FD_ISSET(sock2, &exceptfds))
+ errx(-1, "%s: setup_tcp: select: exception", test);
+ if (!FD_ISSET(sock2, &writefds))
+ errx(-1, "%s: setup_tcp: select: not writable", test);
+
+ close(sock1);
+ fdp[0] = sock2;
+ fdp[1] = sock3;
+}
+
+static void
+setup_udsstream(const char *test, int *fdp)
+{
+
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fdp) < 0)
+ err(-1, "%s: setup_udsstream: socketpair", test);
+}
+
+static void
+setup_udsdgram(const char *test, int *fdp)
+{
+
+ if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, fdp) < 0)
+ err(-1, "%s: setup_udsdgram: socketpair", test);
+}
+
+static void
+setup_pipe(const char *test, int *fdp)
+{
+
+ if (pipe(fdp) < 0)
+ err(-1, "%s: setup_pipe: pipe", test);
+}
+
+static void
+setup_fifo(const char *test, int *fdp)
+{
+ char path[] = "0send_fifo.XXXXXXX";
+ int fd1, fd2;
+
+ if (mkstemp(path) == -1)
+ err(-1, "%s: setup_fifo: mktemp", test);
+ unlink(path);
+
+ if (mkfifo(path, 0600) < 0)
+ err(-1, "%s: setup_fifo: mkfifo(%s)", test, path);
+
+ fd1 = open(path, O_RDONLY | O_NONBLOCK);
+ if (fd1 < 0)
+ err(-1, "%s: setup_fifo: open(%s, O_RDONLY)", test, path);
+
+ fd2 = open(path, O_WRONLY | O_NONBLOCK);
+ if (fd2 < 0)
+ err(-1, "%s: setup_fifo: open(%s, O_WRONLY)", test, path);
+
+ fdp[0] = fd2;
+ fdp[1] = fd1;
+}
+
+static void
+close_both(int *fdp)
+{
+
+ close(fdp[0]);
+ fdp[0] = -1;
+ close(fdp[1]);
+ fdp[1] = -1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd[2];
+
+ setup_udp("udp_0send", fd);
+ try_0send("udp_0send", fd[0]);
+ close_both(fd);
+
+ setup_udp("udp_0write", fd);
+ try_0write("udp_0write", fd[0]);
+ close_both(fd);
+
+ setup_tcp("tcp_0send", fd);
+ try_0send("tcp_0send", fd[0]);
+ close_both(fd);
+
+ setup_tcp("tcp_0write", fd);
+ try_0write("tcp_0write", fd[0]);
+ close_both(fd);
+
+ setup_udsstream("udsstream_0send", fd);
+ try_0send("udsstream_0send", fd[0]);
+ close_both(fd);
+
+ setup_udsstream("udsstream_0write", fd);
+ try_0write("udsstream_0write", fd[0]);
+ close_both(fd);
+
+ setup_udsdgram("udsdgram_0send", fd);
+ try_0send("udsdgram_0send", fd[0]);
+ close_both(fd);
+
+ setup_udsdgram("udsdgram_0write", fd);
+ try_0write("udsdgram_0write", fd[0]);
+ close_both(fd);
+
+ setup_pipe("pipe_0write", fd);
+ try_0write("pipd_0write", fd[0]);
+ close_both(fd);
+
+ setup_fifo("fifo_0write", fd);
+ try_0write("fifo_0write", fd[0]);
+ close_both(fd);
+
+ return (0);
+}
diff --git a/tools/regression/vfs/trailing_slash.t b/tools/regression/vfs/trailing_slash.t
new file mode 100755
index 0000000..b1b8523
--- /dev/null
+++ b/tools/regression/vfs/trailing_slash.t
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+# Tests vfs_lookup()'s handling of trailing slashes for symlinks that
+# point to files. See kern/21768 for details. Fixed in r193028.
+#
+
+testfile="/tmp/testfile-$$"
+testlink="/tmp/testlink-$$"
+
+tests="
+$testfile:$testlink:$testfile:0
+$testfile:$testlink:$testfile/:1
+$testfile:$testlink:$testlink:0
+$testfile:$testlink:$testlink/:1
+$testfile/:$testlink:$testlink:1
+$testfile/:$testlink:$testlink/:1
+"
+
+touch $testfile || exit 1
+trap "rm $testfile $testlink" EXIT
+
+set $tests
+echo "1..$#"
+n=1
+for testspec ; do
+ (
+ IFS=:
+ set $testspec
+ unset IFS
+ ln -fs "$1" "$2" || exit 1
+ cat "$3" >/dev/null 2>&1
+ ret=$?
+ if [ "$ret" -eq "$4" ] ; then
+ echo "ok $n"
+ else
+ echo "fail $n - expected $4, got $ret"
+ fi
+ )
+ n=$((n+1))
+done
OpenPOWER on IntegriCloud