summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/sys/Makefile3
-rw-r--r--tests/sys/Makefile.inc3
-rw-r--r--tests/sys/aio/Makefile4
-rw-r--r--tests/sys/file/ftruncate_test.c2
-rw-r--r--tests/sys/posixshm/Makefile10
-rw-r--r--tests/sys/posixshm/posixshm.c627
-rw-r--r--tests/sys/posixshm/test.c128
-rw-r--r--tests/sys/posixshm/test.h59
-rw-r--r--tests/sys/socket/Makefile46
-rw-r--r--tests/sys/socket/README.unix_cmsg160
-rw-r--r--tests/sys/socket/accept_fd_leak_test.c215
-rw-r--r--tests/sys/socket/accf_data_attach_test.c184
-rw-r--r--tests/sys/socket/fstat_test.c71
-rw-r--r--tests/sys/socket/kqueue_test.c368
-rw-r--r--tests/sys/socket/listen_backlog_test.c383
-rw-r--r--tests/sys/socket/listenclose_test.c111
-rw-r--r--tests/sys/socket/pr_atomic_test.c109
-rw-r--r--tests/sys/socket/reconnect_test.c132
-rw-r--r--tests/sys/socket/rtsocket_test.c101
-rw-r--r--tests/sys/socket/sblock_test.c207
-rw-r--r--tests/sys/socket/sendfile_test.c486
-rw-r--r--tests/sys/socket/shutdown_test.c110
-rw-r--r--tests/sys/socket/sigpipe_test.c329
-rw-r--r--tests/sys/socket/so_setfib_test.c199
-rw-r--r--tests/sys/socket/socketpair_test.c161
-rw-r--r--tests/sys/socket/unix_bindconnect_test.c318
-rw-r--r--tests/sys/socket/unix_close_race_test.c143
-rw-r--r--tests/sys/socket/unix_cmsg.c1969
-rw-r--r--tests/sys/socket/unix_cmsg_test.sh85
-rw-r--r--tests/sys/socket/unix_gc_test.c808
-rw-r--r--tests/sys/socket/unix_passfd_test.c395
-rw-r--r--tests/sys/socket/unix_sendto_race_test.c215
-rw-r--r--tests/sys/socket/unix_socket_test.c84
-rw-r--r--tests/sys/socket/unix_sorflush_test.c99
-rw-r--r--tests/sys/socket/zerosend_test.c342
-rw-r--r--tests/sys/vfs/Makefile7
-rwxr-xr-xtests/sys/vfs/trailing_slash_test.sh41
37 files changed, 8711 insertions, 3 deletions
diff --git a/tests/sys/Makefile b/tests/sys/Makefile
index 066c918..8f37490 100644
--- a/tests/sys/Makefile
+++ b/tests/sys/Makefile
@@ -12,6 +12,9 @@ TESTS_SUBDIRS+= kqueue
TESTS_SUBDIRS+= mqueue
TESTS_SUBDIRS+= netinet
TESTS_SUBDIRS+= opencrypto
+TESTS_SUBDIRS+= posixshm
+TESTS_SUBDIRS+= socket
+TESTS_SUBDIRS+= vfs
TESTS_SUBDIRS+= vm
# Items not integrated into kyua runs by default
diff --git a/tests/sys/Makefile.inc b/tests/sys/Makefile.inc
new file mode 100644
index 0000000..f341842
--- /dev/null
+++ b/tests/sys/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+WARNS?= 6
diff --git a/tests/sys/aio/Makefile b/tests/sys/aio/Makefile
index 851252d..3117462 100644
--- a/tests/sys/aio/Makefile
+++ b/tests/sys/aio/Makefile
@@ -2,8 +2,8 @@
TESTSDIR= ${TESTSBASE}/sys/aio
-PLAIN_TESTS_C+= aio_kqueue_test
-PLAIN_TESTS_C+= lio_kqueue_test
+PLAIN_TESTS_C+= aio_kqueue
+PLAIN_TESTS_C+= lio_kqueue
ATF_TESTS_C+= aio_test
DPADD.aio_test+= ${LIBUTIL}
diff --git a/tests/sys/file/ftruncate_test.c b/tests/sys/file/ftruncate_test.c
index 7eaba14..b657aca 100644
--- a/tests/sys/file/ftruncate_test.c
+++ b/tests/sys/file/ftruncate_test.c
@@ -57,7 +57,7 @@ static off_t lengths[] = {0, 1, 2, 3, 4, 127, 128, 129, 511, 512, 513, 1023,
static int lengths_count = sizeof(lengths) / sizeof(off_t);
int
-main(int argc, char *argv[])
+main(void)
{
int error, fd, fds[2], i, read_only_fd;
char path[PATH_MAX];
diff --git a/tests/sys/posixshm/Makefile b/tests/sys/posixshm/Makefile
new file mode 100644
index 0000000..cb27345
--- /dev/null
+++ b/tests/sys/posixshm/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/posixshm
+
+TAP_TESTS_C= posixshm_test
+SRCS.posixshm_test= posixshm.c test.c
+
+WARNS?= 6
+
+.include <bsd.test.mk>
diff --git a/tests/sys/posixshm/posixshm.c b/tests/sys/posixshm/posixshm.c
new file mode 100644
index 0000000..e21f45a
--- /dev/null
+++ b/tests/sys/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(void)
+{
+
+ run_tests();
+ return (0);
+}
diff --git a/tests/sys/posixshm/test.c b/tests/sys/posixshm/test.c
new file mode 100644
index 0000000..8583a0d
--- /dev/null
+++ b/tests/sys/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/tests/sys/posixshm/test.h b/tests/sys/posixshm/test.h
new file mode 100644
index 0000000..505679b
--- /dev/null
+++ b/tests/sys/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/tests/sys/socket/Makefile b/tests/sys/socket/Makefile
new file mode 100644
index 0000000..a119863
--- /dev/null
+++ b/tests/sys/socket/Makefile
@@ -0,0 +1,46 @@
+# $FreeBSD$
+#
+# Some of these tests fail on 11.0-CURRENT @ r280355 with DEBUG bits on
+
+TESTSDIR= ${TESTSBASE}/sys/socket
+
+BINDIR= ${TESTSDIR}
+
+# See comment below
+#PROGS+= unix_cmsg
+
+TAP_TESTS_C+= accept_fd_leak_test
+ATF_TESTS_C+= accf_data_attach_test
+PLAIN_TESTS_C+= fstat_test
+PLAIN_TESTS_C+= kqueue_test
+PLAIN_TESTS_C+= listen_backlog_test
+PLAIN_TESTS_C+= listenclose_test
+PLAIN_TESTS_C+= pr_atomic_test
+PLAIN_TESTS_C+= reconnect_test
+PLAIN_TESTS_C+= rtsocket_test
+PLAIN_TESTS_C+= sblock_test
+TAP_TESTS_C+= sendfile_test
+PLAIN_TESTS_C+= shutdown_test
+PLAIN_TESTS_C+= sigpipe_test
+TAP_TESTS_C+= so_setfib_test
+PLAIN_TESTS_C+= socketpair_test
+PLAIN_TESTS_C+= unix_bindconnect_test
+PLAIN_TESTS_C+= unix_close_race_test
+# Lots of failures; see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=199478
+#TAP_TESTS_SH+= unix_cmsg_test
+PLAIN_TESTS_C+= unix_gc_test
+ATF_TESTS_C+= unix_passfd_test
+PLAIN_TESTS_C+= unix_sendto_race_test
+PLAIN_TESTS_C+= unix_socket_test
+PLAIN_TESTS_C+= unix_sorflush_test
+ATF_TESTS_C+= zerosend_test
+
+DPADD.sendfile_test+= ${LIBMD}
+LDADD.sendfile_test+= -lmd
+
+# XXX: unix_cmsg_test and unix_passfd_test need to be fixed
+NO_WCAST_ALIGN=
+
+WARNS?= 6
+
+.include <bsd.test.mk>
diff --git a/tests/sys/socket/README.unix_cmsg b/tests/sys/socket/README.unix_cmsg
new file mode 100644
index 0000000..df51723
--- /dev/null
+++ b/tests/sys/socket/README.unix_cmsg
@@ -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/tests/sys/socket/accept_fd_leak_test.c b/tests/sys/socket/accept_fd_leak_test.c
new file mode 100644
index 0000000..659c22d
--- /dev/null
+++ b/tests/sys/socket/accept_fd_leak_test.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/tests/sys/socket/accf_data_attach_test.c b/tests/sys/socket/accf_data_attach_test.c
new file mode 100644
index 0000000..07d1bae
--- /dev/null
+++ b/tests/sys/socket/accf_data_attach_test.c
@@ -0,0 +1,184 @@
+/*-
+ * 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>
+
+#include <atf-c.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.
+ */
+ATF_TC_WITHOUT_HEAD(accf_data_attach_test);
+ATF_TC_BODY(accf_data_attach_test, tc)
+{
+ struct accept_filter_arg afa;
+ struct sockaddr_in sin;
+ socklen_t len;
+ int lso, ret;
+
+ /*
+ * Step 0. Open socket().
+ */
+ lso = socket(PF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE_MSG(lso != -1, "socket failed: %s", strerror(errno));
+
+ /*
+ * Step 1. After socket(). Should return EINVAL, since no accept
+ * filter should be attached.
+ */
+ bzero(&afa, sizeof(afa));
+ len = sizeof(afa);
+ ATF_REQUIRE_ERRNO(EINVAL,
+ getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len) == -1);
+
+ /*
+ * 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);
+ ATF_REQUIRE_MSG(bind(lso, (struct sockaddr *)&sin, sizeof(sin)) == 0,
+ "bind failed: %s", strerror(errno));
+
+ /*
+ * Step 3: After bind(). getsockopt() should return EINVAL, since no
+ * accept filter should be attached.
+ */
+ len = sizeof(afa);
+ ATF_REQUIRE_ERRNO(EINVAL,
+ getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len) == -1);
+
+ /*
+ * 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);
+ ATF_REQUIRE_MSG(setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa,
+ sizeof(afa)) != 0, "setsockopt succeeded unexpectedly");
+
+ /*
+ * 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);
+ ATF_REQUIRE_ERRNO(EINVAL, ret != 0);
+
+ /*
+ * Step 6: listen().
+ */
+ ATF_REQUIRE_MSG(listen(lso, 1) == 0,
+ "listen failed: %s", strerror(errno));
+
+ /*
+ * 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);
+ ATF_REQUIRE_MSG(ret == -1 && errno == EINVAL,
+ "getsockopt after listen failed: %s", strerror(errno));
+
+ atf_tc_expect_fail("XXX(ngie): step 8 always fails on my system for some odd reason");
+
+ /*
+ * 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));
+ //ATF_REQUIRE_MSG(ret == 0,
+ ATF_REQUIRE_MSG(ret == 0,
+ "setsockopt after listen failed: %s", strerror(errno));
+
+ /*
+ * 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);
+ ATF_REQUIRE_MSG(ret == 0,
+ "getsockopt after listen/setsockopt failed: %s", strerror(errno));
+ ATF_REQUIRE_EQ(len, sizeof(afa));
+ ATF_REQUIRE_STREQ(afa.af_name, ACCF_NAME);
+
+ /*
+ * Step 10: Remove accept filter. After removing the accept filter
+ * getsockopt() should fail with EINVAL.
+ */
+ ret = setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0);
+ ATF_REQUIRE_MSG(ret == 0,
+ "setsockopt failed to remove accept filter: %s", strerror(errno));
+ bzero(&afa, sizeof(afa));
+ len = sizeof(afa);
+ ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len);
+ ATF_REQUIRE_MSG(ret == -1 && errno == EINVAL,
+ "getsockopt failed after removing the accept filter: %s",
+ strerror(errno));
+
+ close(lso);
+
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, accf_data_attach_test);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/socket/fstat_test.c b/tests/sys/socket/fstat_test.c
new file mode 100644
index 0000000..6ea931d
--- /dev/null
+++ b/tests/sys/socket/fstat_test.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/tests/sys/socket/kqueue_test.c b/tests/sys/socket/kqueue_test.c
new file mode 100644
index 0000000..f73704a
--- /dev/null
+++ b/tests/sys/socket/kqueue_test.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/tests/sys/socket/listen_backlog_test.c b/tests/sys/socket/listen_backlog_test.c
new file mode 100644
index 0000000..2276393
--- /dev/null
+++ b/tests/sys/socket/listen_backlog_test.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/tests/sys/socket/listenclose_test.c b/tests/sys/socket/listenclose_test.c
new file mode 100644
index 0000000..f920846
--- /dev/null
+++ b/tests/sys/socket/listenclose_test.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/tests/sys/socket/pr_atomic_test.c b/tests/sys/socket/pr_atomic_test.c
new file mode 100644
index 0000000..e902cf7
--- /dev/null
+++ b/tests/sys/socket/pr_atomic_test.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/tests/sys/socket/reconnect_test.c b/tests/sys/socket/reconnect_test.c
new file mode 100644
index 0000000..f596092
--- /dev/null
+++ b/tests/sys/socket/reconnect_test.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(void)
+{
+ 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/tests/sys/socket/rtsocket_test.c b/tests/sys/socket/rtsocket_test.c
new file mode 100644
index 0000000..f189cdc
--- /dev/null
+++ b/tests/sys/socket/rtsocket_test.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 != EPROTOTYPE)
+ 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 != EPROTOTYPE)
+ 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 != EPROTOTYPE)
+ 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 != EPROTOTYPE)
+ 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/tests/sys/socket/sblock_test.c b/tests/sys/socket/sblock_test.c
new file mode 100644
index 0000000..415c4d4
--- /dev/null
+++ b/tests/sys/socket/sblock_test.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/tests/sys/socket/sendfile_test.c b/tests/sys/socket/sendfile_test.c
new file mode 100644
index 0000000..18ae9ad
--- /dev/null
+++ b/tests/sys/socket/sendfile_test.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/tests/sys/socket/shutdown_test.c b/tests/sys/socket/shutdown_test.c
new file mode 100644
index 0000000..3d23c94
--- /dev/null
+++ b/tests/sys/socket/shutdown_test.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/tests/sys/socket/sigpipe_test.c b/tests/sys/socket/sigpipe_test.c
new file mode 100644
index 0000000..a9562f2
--- /dev/null
+++ b/tests/sys/socket/sigpipe_test.c
@@ -0,0 +1,329 @@
+/*-
+ * 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/param.h>
+#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) __dead2;
+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 == 1) {
+ srandomdev();
+
+ /* Pick a random unprivileged port 1025-65535 */
+ port = MAX((int)random() % 65535, 1025);
+ } else if (argc == 2) {
+ port = strtol(argv[1], &dummy, 10);
+ if (port < 0 || port > 65535 || *dummy != '\0')
+ usage();
+ } else
+ 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/tests/sys/socket/so_setfib_test.c b/tests/sys/socket/so_setfib_test.c
new file mode 100644
index 0000000..040ad8f
--- /dev/null
+++ b/tests/sys/socket/so_setfib_test.c
@@ -0,0 +1,199 @@
+/*-
+ * 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\n");
+ 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..%zu\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/tests/sys/socket/socketpair_test.c b/tests/sys/socket/socketpair_test.c
new file mode 100644
index 0000000..779878a
--- /dev/null
+++ b/tests/sys/socket/socketpair_test.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/tests/sys/socket/unix_bindconnect_test.c b/tests/sys/socket/unix_bindconnect_test.c
new file mode 100644
index 0000000..a2c3184
--- /dev/null
+++ b/tests/sys/socket/unix_bindconnect_test.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, "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/tests/sys/socket/unix_close_race_test.c b/tests/sys/socket/unix_close_race_test.c
new file mode 100644
index 0000000..89c1b20
--- /dev/null
+++ b/tests/sys/socket/unix_close_race_test.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/tests/sys/socket/unix_cmsg.c b/tests/sys/socket/unix_cmsg.c
new file mode 100644
index 0000000..d91cef4
--- /dev/null
+++ b/tests/sys/socket/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/tests/sys/socket/unix_cmsg_test.sh b/tests/sys/socket/unix_cmsg_test.sh
new file mode 100644
index 0000000..c4a0b34
--- /dev/null
+++ b/tests/sys/socket/unix_cmsg_test.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+# $FreeBSD$
+
+cmd=`echo $0 | sed -e 's,_test$,,'`
+
+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/tests/sys/socket/unix_gc_test.c b/tests/sys/socket/unix_gc_test.c
new file mode 100644
index 0000000..eb70dca
--- /dev/null
+++ b/tests/sys/socket/unix_gc_test.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(void)
+{
+ char cmd[sizeof(RMDIR) + PATH_MAX];
+ int serrno;
+ pid_t pid;
+
+ strlcpy(dpath, "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/tests/sys/socket/unix_passfd_test.c b/tests/sys/socket/unix_passfd_test.c
new file mode 100644
index 0000000..f39597a
--- /dev/null
+++ b/tests/sys/socket/unix_passfd_test.c
@@ -0,0 +1,395 @@
+/*-
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.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(int *fdp)
+{
+
+ ATF_REQUIRE_MSG(socketpair(PF_UNIX, SOCK_STREAM, 0, fdp) != -1,
+ "socketpair(PF_UNIX, SOCK_STREAM, ..) failed: %s, ",
+ strerror(errno));
+}
+
+static void
+closesocketpair(int *fdp)
+{
+
+ close(fdp[0]);
+ close(fdp[1]);
+}
+
+static void
+tempfile(int *fdp)
+{
+ char path[PATH_MAX];
+ int fd;
+
+ snprintf(path, PATH_MAX, "unix_passfd.XXXXXXXXXXXXXXX");
+ fd = mkstemp(path);
+ ATF_REQUIRE_MSG(fd != -1, "mkstemp failed: %s", strerror(errno));
+ (void)unlink(path);
+ *fdp = fd;
+}
+
+static void
+dofstat(int fd, struct stat *sb)
+{
+
+ ATF_REQUIRE_MSG(fstat(fd, sb) != -1, "fstat failed: %s",
+ strerror(errno));
+}
+
+static void
+samefile(struct stat *sb1, struct stat *sb2)
+{
+
+ ATF_REQUIRE_EQ_MSG(sb1->st_dev, sb2->st_dev,
+ "different devices (%d != %d)", sb1->st_dev, sb2->st_dev);
+ ATF_REQUIRE_EQ_MSG(sb1->st_dev, sb2->st_dev,
+ "different inodes (%u != %u)", sb1->st_ino, sb2->st_ino);
+}
+
+static void
+sendfd_payload(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);
+ ATF_REQUIRE_MSG(len != -1, "sendmsg failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG((size_t)len == paylen,
+ "mismatch with amount of data sent via sendmsg (%zd != %zu)",
+ len, paylen);
+}
+
+static void
+sendfd(int sock_fd, int send_fd)
+{
+ char ch;
+
+ return (sendfd_payload(sock_fd, send_fd, &ch, sizeof(ch)));
+}
+
+static void
+recvfd_payload(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);
+ ATF_REQUIRE_MSG(len != -1, "recvmsg failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG((size_t)len == buflen,
+ "mismatch with amount of data sent via recvmsg (%zd != %zu)",
+ len, buflen);
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ ATF_REQUIRE_MSG(cmsghdr != NULL, "did not receive control message");
+ *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);
+ ATF_REQUIRE(*recvfd != -1);
+ }
+ }
+ ATF_REQUIRE_MSG(*recvfd != -1,
+ "recvmsg did not receive a single fd message");
+}
+
+static void
+recvfd(int sock_fd, int *recv_fd)
+{
+ char ch;
+
+ return (recvfd_payload(sock_fd, recv_fd, &ch, sizeof(ch)));
+}
+
+/*
+ * 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.
+ */
+ATF_TC_WITHOUT_HEAD(simple_send_fd);
+ATF_TC_BODY(simple_send_fd, tc)
+{
+ struct stat getfd_1_stat, putfd_1_stat;
+ int fd[2], getfd_1, putfd_1;
+
+ domainsocketpair(fd);
+ tempfile(&putfd_1);
+ dofstat(putfd_1, &putfd_1_stat);
+ sendfd(fd[0], putfd_1);
+ recvfd(fd[1], &getfd_1);
+ dofstat(getfd_1, &getfd_1_stat);
+ samefile(&putfd_1_stat, &getfd_1_stat);
+ close(putfd_1);
+ close(getfd_1);
+ closesocketpair(fd);
+}
+
+/*
+ * 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.
+ */
+ATF_TC_WITHOUT_HEAD(send_and_close);
+ATF_TC_BODY(send_and_close, tc)
+{
+ struct stat getfd_1_stat, putfd_1_stat;
+ int fd[2], getfd_1, putfd_1;
+
+ domainsocketpair(fd);
+ tempfile(&putfd_1);
+ dofstat(putfd_1, &putfd_1_stat);
+ sendfd(fd[0], putfd_1);
+ close(putfd_1);
+ recvfd(fd[1], &getfd_1);
+ dofstat(getfd_1, &getfd_1_stat);
+ samefile(&putfd_1_stat, &getfd_1_stat);
+ close(getfd_1);
+ closesocketpair(fd);
+
+}
+
+/*
+ * Third test: put a temporary file into a UNIX domain socket, then
+ * close both endpoints causing garbage collection to kick off.
+ */
+ATF_TC_WITHOUT_HEAD(send_and_cancel);
+ATF_TC_BODY(send_and_cancel, tc)
+{
+ int fd[2], putfd_1;
+
+ domainsocketpair(fd);
+ tempfile(&putfd_1);
+ sendfd(fd[0], putfd_1);
+ close(putfd_1);
+ closesocketpair(fd);
+}
+
+/*
+ * Send two files. Then receive them. Make sure they are returned
+ * in the right order, and both get there.
+ */
+ATF_TC_WITHOUT_HEAD(two_files);
+ATF_TC_BODY(two_files, tc)
+{
+ struct stat getfd_1_stat, getfd_2_stat, putfd_1_stat, putfd_2_stat;
+ int fd[2], getfd_1, getfd_2, putfd_1, putfd_2;
+
+ domainsocketpair(fd);
+ tempfile(&putfd_1);
+ tempfile(&putfd_2);
+ dofstat(putfd_1, &putfd_1_stat);
+ dofstat(putfd_2, &putfd_2_stat);
+ sendfd(fd[0], putfd_1);
+ sendfd(fd[0], putfd_2);
+ close(putfd_1);
+ close(putfd_2);
+ recvfd(fd[1], &getfd_1);
+ recvfd(fd[1], &getfd_2);
+ dofstat(getfd_1, &getfd_1_stat);
+ dofstat(getfd_2, &getfd_2_stat);
+ samefile(&putfd_1_stat, &getfd_1_stat);
+ samefile(&putfd_2_stat, &getfd_2_stat);
+ close(getfd_1);
+ close(getfd_2);
+ closesocketpair(fd);
+}
+
+/*
+ * Big bundling test. Send an endpoint of the UNIX domain socket
+ * over itself, closing the door behind it.
+ */
+ATF_TC_WITHOUT_HEAD(bundle);
+ATF_TC_BODY(bundle, tc)
+{
+ int fd[2], getfd_1;
+
+ domainsocketpair(fd);
+
+ sendfd(fd[0], fd[0]);
+ close(fd[0]);
+ recvfd(fd[1], &getfd_1);
+ close(getfd_1);
+ close(fd[1]);
+}
+
+/*
+ * 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.
+ */
+ATF_TC_WITHOUT_HEAD(bundle_cancel);
+ATF_TC_BODY(bundle_cancel, tc)
+{
+ int fd[2];
+
+ domainsocketpair(fd);
+ sendfd(fd[0], fd[0]);
+ sendfd(fd[1], fd[0]);
+ closesocketpair(fd);
+}
+
+/*
+ * Test for PR 151758: Send an character device over the UNIX
+ * domain socket and then close both sockets to orphan the
+ * device.
+ */
+ATF_TC_WITHOUT_HEAD(devfs_orphan);
+ATF_TC_BODY(devfs_orphan, tc)
+{
+ int fd[2], putfd_1;
+
+ domainsocketpair(fd);
+ putfd_1 = open(_PATH_DEVNULL, O_RDONLY);
+ ATF_REQUIRE_MSG(putfd_1 != -1,
+ "opening %s failed: %s", _PATH_DEVNULL, strerror(errno));
+ sendfd(fd[0], putfd_1);
+ close(putfd_1);
+ closesocketpair(fd);
+}
+
+#define LOCAL_STREAM_SENDSPACE "net.local.stream.sendspace"
+
+/*
+ * 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.
+ */
+ATF_TC_WITHOUT_HEAD(rights_with_LOCAL_CREDS_and_large_payload);
+ATF_TC_BODY(rights_with_LOCAL_CREDS_and_large_payload, tc)
+{
+ void *buf;
+ int fd[2];
+ size_t len;
+ u_long sendspace;
+ const int on = 1;
+ int getfd_1, putfd_1, rc;
+
+ atf_tc_expect_fail("Bug 181741 has not been fixed yet");
+
+ len = sizeof(sendspace);
+ rc = sysctlbyname(LOCAL_STREAM_SENDSPACE, &sendspace, &len, NULL, 0);
+ ATF_REQUIRE_MSG(rc == 0, "sysctlbyname %s failed: %s",
+ LOCAL_STREAM_SENDSPACE, strerror(errno));
+
+ ATF_REQUIRE((buf = malloc(sendspace)) != NULL);
+
+ domainsocketpair(fd);
+ rc = setsockopt(fd[1], 0, LOCAL_CREDS, &on, sizeof(on));
+ ATF_REQUIRE_MSG(rc == 0, "setsockopt failed: %s", strerror(errno));
+
+ tempfile(&putfd_1);
+
+ sendfd_payload(fd[0], putfd_1, buf, sendspace);
+ recvfd_payload(fd[1], &getfd_1, buf, sendspace);
+
+ close(putfd_1);
+ close(getfd_1);
+ closesocketpair(fd);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, simple_send_fd);
+ ATF_TP_ADD_TC(tp, send_and_close);
+ ATF_TP_ADD_TC(tp, send_and_cancel);
+ ATF_TP_ADD_TC(tp, two_files);
+ ATF_TP_ADD_TC(tp, bundle);
+ ATF_TP_ADD_TC(tp, bundle_cancel);
+ ATF_TP_ADD_TC(tp, devfs_orphan);
+ ATF_TP_ADD_TC(tp, rights_with_LOCAL_CREDS_and_large_payload);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/socket/unix_sendto_race_test.c b/tests/sys/socket/unix_sendto_race_test.c
new file mode 100644
index 0000000..9fd748d
--- /dev/null
+++ b/tests/sys/socket/unix_sendto_race_test.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/tests/sys/socket/unix_socket_test.c b/tests/sys/socket/unix_socket_test.c
new file mode 100644
index 0000000..ca7225b
--- /dev/null
+++ b/tests/sys/socket/unix_socket_test.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 != EPROTOTYPE)
+ 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/tests/sys/socket/unix_sorflush_test.c b/tests/sys/socket/unix_sorflush_test.c
new file mode 100644
index 0000000..e0deb00
--- /dev/null
+++ b/tests/sys/socket/unix_sorflush_test.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/tests/sys/socket/zerosend_test.c b/tests/sys/socket/zerosend_test.c
new file mode 100644
index 0000000..7788760
--- /dev/null
+++ b/tests/sys/socket/zerosend_test.c
@@ -0,0 +1,342 @@
+/*-
+ * 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/param.h>
+#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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+static void
+try_0send(int fd)
+{
+ ssize_t len;
+ char ch;
+
+ ch = 0;
+ len = send(fd, &ch, 0, 0);
+ ATF_REQUIRE_MSG(len != -1, "send failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(len == 0, "send returned %zd (not 0): %s",
+ len, strerror(errno));
+}
+
+static void
+try_0write(int fd)
+{
+ ssize_t len;
+ char ch;
+
+ ch = 0;
+ len = write(fd, &ch, 0);
+ ATF_REQUIRE_MSG(len != -1, "write failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(len == 0, "write returned: %zd (not 0): %s",
+ len, strerror(errno));
+}
+
+static void
+setup_udp(int *fdp)
+{
+ struct sockaddr_in sin;
+ int port_base, sock1, sock2;
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ port_base = MAX((int)random() % 65535, 1025);
+
+ sin.sin_port = htons(port_base);
+ sock1 = socket(PF_INET, SOCK_DGRAM, 0);
+ ATF_REQUIRE_MSG(sock1 != -1, "socket # 1 failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(bind(sock1, (struct sockaddr *)&sin, sizeof(sin)) == 0,
+ "bind # 1 failed: %s", strerror(errno));
+ sin.sin_port = htons(port_base + 1);
+ ATF_REQUIRE_MSG(connect(sock1, (struct sockaddr *)&sin, sizeof(sin))
+ == 0, "connect # 1 failed: %s", strerror(errno));
+
+ sock2 = socket(PF_INET, SOCK_DGRAM, 0);
+ ATF_REQUIRE_MSG(sock2 != -1, "socket # 2 failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(bind(sock2, (struct sockaddr *)&sin, sizeof(sin)) == 0,
+ "bind # 2 failed: %s", strerror(errno));
+ sin.sin_port = htons(port_base);
+ ATF_REQUIRE_MSG(connect(sock2, (struct sockaddr *)&sin, sizeof(sin))
+ == 0, "connect # 2 failed: %s", strerror(errno));
+
+ fdp[0] = sock1;
+ fdp[1] = sock2;
+ fdp[2] = -1;
+}
+
+static void
+setup_tcp(int *fdp)
+{
+ fd_set writefds, exceptfds;
+ struct sockaddr_in sin;
+ int port_base, 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);
+
+ port_base = MAX((int)random() % 65535, 1025);
+
+ /*
+ * First set up the listen socket.
+ */
+ sin.sin_port = htons(port_base);
+ sock1 = socket(PF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE_MSG(sock1 != -1, "socket # 1 failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(bind(sock1, (struct sockaddr *)&sin, sizeof(sin)) == 0,
+ "bind # 1 failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(listen(sock1, -1) == 0,
+ "listen # 1 failed: %s", strerror(errno));
+
+ /*
+ * Now connect to it, non-blocking so that we don't deadlock against
+ * ourselves.
+ */
+ sock2 = socket(PF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE_MSG(sock2 != -1, "socket # 2 failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(fcntl(sock2, F_SETFL, O_NONBLOCK) == 0,
+ "setting socket as nonblocking failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(
+ (connect(sock2, (struct sockaddr *)&sin, sizeof(sin)) == 0 ||
+ errno == EINPROGRESS),
+ "connect # 2 failed: %s", strerror(errno));
+
+ /*
+ * Now pick up the connection after sleeping a moment to make sure
+ * there's been time for some packets to go back and forth.
+ */
+ ATF_REQUIRE_MSG(sleep(1) == 0, "sleep(1) <= 0");
+ sock3 = accept(sock1, NULL, NULL);
+ ATF_REQUIRE_MSG(sock3 != -1, "accept failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(sleep(1) == 0, "sleep(1) <= 0");
+
+ 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);
+ ATF_REQUIRE_MSG(ret != -1, "select failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(!FD_ISSET(sock2, &exceptfds),
+ "select: exception occurred with sock2");
+ ATF_REQUIRE_MSG(FD_ISSET(sock2, &writefds),
+ "not writable");
+
+ close(sock1);
+ fdp[0] = sock2;
+ fdp[1] = sock3;
+ fdp[2] = sock1;
+}
+
+static void
+setup_udsstream(int *fdp)
+{
+
+ ATF_REQUIRE_MSG(socketpair(PF_LOCAL, SOCK_STREAM, 0, fdp) == 0,
+ "socketpair failed: %s", strerror(errno));
+}
+
+static void
+setup_udsdgram(int *fdp)
+{
+
+ ATF_REQUIRE_MSG(socketpair(PF_LOCAL, SOCK_DGRAM, 0, fdp) == 0,
+ "socketpair failed: %s", strerror(errno));
+}
+
+static void
+setup_pipe(int *fdp)
+{
+
+ ATF_REQUIRE_MSG(pipe(fdp) == 0, "pipe failed: %s", strerror(errno));
+}
+
+static void
+setup_fifo(int *fdp)
+{
+ char path[] = "0send_fifo.XXXXXXX";
+ int fd1, fd2;
+
+ ATF_REQUIRE_MSG(mkstemp(path) != -1,
+ "mkstemp failed: %s", strerror(errno));
+ unlink(path);
+
+ ATF_REQUIRE_MSG(mkfifo(path, 0600) == 0,
+ "mkfifo(\"%s\", 0600) failed: %s", path, strerror(errno));
+
+ fd1 = open(path, O_RDONLY | O_NONBLOCK);
+ ATF_REQUIRE_MSG(fd1 != -1, "open(\"%s\", O_RDONLY)", path);
+
+ fd2 = open(path, O_WRONLY | O_NONBLOCK);
+ ATF_REQUIRE_MSG(fd2 != -1, "open(\"%s\", O_WRONLY)", path);
+
+ fdp[0] = fd2;
+ fdp[1] = fd1;
+ fdp[2] = -1;
+}
+
+static int fd[3];
+
+static void
+close_fds(int *fdp)
+{
+ unsigned int i;
+
+ for (i = 0; i < nitems(fdp); i++)
+ close(fdp[i]);
+}
+
+ATF_TC_WITHOUT_HEAD(udp_zero_send);
+ATF_TC_BODY(udp_zero_send, tc)
+{
+
+ setup_udp(fd);
+ try_0send(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(udp_zero_write);
+ATF_TC_BODY(udp_zero_write, tc)
+{
+
+ setup_udp(fd);
+ try_0write(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(tcp_zero_send);
+ATF_TC_BODY(tcp_zero_send, tc)
+{
+
+ setup_tcp(fd);
+ try_0send(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(tcp_zero_write);
+ATF_TC_BODY(tcp_zero_write, tc)
+{
+
+ setup_tcp(fd);
+ try_0write(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(udsstream_zero_send);
+ATF_TC_BODY(udsstream_zero_send, tc)
+{
+
+ setup_udsstream(fd);
+ try_0send(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(udsstream_zero_write);
+ATF_TC_BODY(udsstream_zero_write, tc)
+{
+
+ setup_udsstream(fd);
+ try_0write(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(udsdgram_zero_send);
+ATF_TC_BODY(udsdgram_zero_send, tc)
+{
+
+ setup_udsdgram(fd);
+ try_0send(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(udsdgram_zero_write);
+ATF_TC_BODY(udsdgram_zero_write, tc)
+{
+
+ setup_udsdgram(fd);
+ try_0write(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(pipe_zero_write);
+ATF_TC_BODY(pipe_zero_write, tc)
+{
+
+ setup_pipe(fd);
+ try_0write(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(fifo_zero_write);
+ATF_TC_BODY(fifo_zero_write, tc)
+{
+
+ setup_fifo(fd);
+ try_0write(fd[0]);
+ close_fds(fd);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ srandomdev();
+
+ ATF_TP_ADD_TC(tp, udp_zero_send);
+ ATF_TP_ADD_TC(tp, udp_zero_write);
+ ATF_TP_ADD_TC(tp, tcp_zero_send);
+ ATF_TP_ADD_TC(tp, tcp_zero_write);
+ ATF_TP_ADD_TC(tp, udsstream_zero_write);
+ ATF_TP_ADD_TC(tp, udsstream_zero_send);
+ ATF_TP_ADD_TC(tp, udsdgram_zero_write);
+ ATF_TP_ADD_TC(tp, udsdgram_zero_send);
+ ATF_TP_ADD_TC(tp, pipe_zero_write);
+ ATF_TP_ADD_TC(tp, fifo_zero_write);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/vfs/Makefile b/tests/sys/vfs/Makefile
new file mode 100644
index 0000000..7cd908b
--- /dev/null
+++ b/tests/sys/vfs/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/vfs
+
+TAP_TESTS_SH+= trailing_slash_test
+
+.include <bsd.test.mk>
diff --git a/tests/sys/vfs/trailing_slash_test.sh b/tests/sys/vfs/trailing_slash_test.sh
new file mode 100755
index 0000000..28c7f5f
--- /dev/null
+++ b/tests/sys/vfs/trailing_slash_test.sh
@@ -0,0 +1,41 @@
+#!/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=$(mktemp tmp.XXXXXX) || exit
+testlink="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
+"
+
+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