aboutsummaryrefslogtreecommitdiff
path: root/lib/libc/tests/sys
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/tests/sys')
-rw-r--r--lib/libc/tests/sys/Makefile105
-rw-r--r--lib/libc/tests/sys/Makefile.depend22
-rw-r--r--lib/libc/tests/sys/brk_test.c146
-rw-r--r--lib/libc/tests/sys/cpuset_test.c691
-rw-r--r--lib/libc/tests/sys/errno_test.c36
-rw-r--r--lib/libc/tests/sys/mlock_helper.c111
-rw-r--r--lib/libc/tests/sys/queue_test.c234
-rw-r--r--lib/libc/tests/sys/sendfile_test.c1208
-rw-r--r--lib/libc/tests/sys/swapcontext_test.c63
9 files changed, 2616 insertions, 0 deletions
diff --git a/lib/libc/tests/sys/Makefile b/lib/libc/tests/sys/Makefile
new file mode 100644
index 000000000000..88f8191a16eb
--- /dev/null
+++ b/lib/libc/tests/sys/Makefile
@@ -0,0 +1,105 @@
+PACKAGE= tests
+
+.include <bsd.own.mk>
+
+.if ${MACHINE_CPUARCH} != "aarch64" && ${MACHINE_CPUARCH} != "riscv"
+ATF_TESTS_C+= brk_test
+.endif
+ATF_TESTS_C+= cpuset_test
+ATF_TESTS_C+= errno_test
+ATF_TESTS_C+= swapcontext_test
+ATF_TESTS_C+= queue_test
+ATF_TESTS_C+= sendfile_test
+
+# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg
+NETBSD_ATF_TESTS_C+= access_test
+NETBSD_ATF_TESTS_C+= bind_test
+NETBSD_ATF_TESTS_C+= chroot_test
+NETBSD_ATF_TESTS_C+= clock_gettime_test
+NETBSD_ATF_TESTS_C+= clock_nanosleep_test
+NETBSD_ATF_TESTS_C+= connect_test
+NETBSD_ATF_TESTS_C+= dup_test
+NETBSD_ATF_TESTS_C+= fsync_test
+NETBSD_ATF_TESTS_C+= getcontext_test
+NETBSD_ATF_TESTS_C+= getgroups_test
+NETBSD_ATF_TESTS_C+= getitimer_test
+NETBSD_ATF_TESTS_C+= getlogin_test
+NETBSD_ATF_TESTS_C+= getpid_test
+NETBSD_ATF_TESTS_C+= getrusage_test
+NETBSD_ATF_TESTS_C+= getsid_test
+NETBSD_ATF_TESTS_C+= getsockname_test
+NETBSD_ATF_TESTS_C+= gettimeofday_test
+NETBSD_ATF_TESTS_C+= issetugid_test
+NETBSD_ATF_TESTS_C+= kevent_test
+NETBSD_ATF_TESTS_C+= kill_test
+NETBSD_ATF_TESTS_C+= link_test
+NETBSD_ATF_TESTS_C+= listen_test
+NETBSD_ATF_TESTS_C+= mincore_test
+NETBSD_ATF_TESTS_C+= mkdir_test
+NETBSD_ATF_TESTS_C+= mkfifo_test
+NETBSD_ATF_TESTS_C+= mknod_test
+NETBSD_ATF_TESTS_C+= mlock_test
+NETBSD_ATF_TESTS_C+= mmap_test
+NETBSD_ATF_TESTS_C+= mprotect_test
+NETBSD_ATF_TESTS_C+= msgctl_test
+NETBSD_ATF_TESTS_C+= msgget_test
+NETBSD_ATF_TESTS_C+= msgrcv_test
+NETBSD_ATF_TESTS_C+= msgsnd_test
+NETBSD_ATF_TESTS_C+= msync_test
+NETBSD_ATF_TESTS_C+= nanosleep_test
+NETBSD_ATF_TESTS_C+= pipe_test
+NETBSD_ATF_TESTS_C+= pipe2_test
+NETBSD_ATF_TESTS_C+= poll_test
+NETBSD_ATF_TESTS_C+= posix_fallocate_test
+NETBSD_ATF_TESTS_C+= revoke_test
+NETBSD_ATF_TESTS_C+= select_test
+NETBSD_ATF_TESTS_C+= setrlimit_test
+NETBSD_ATF_TESTS_C+= setuid_test
+NETBSD_ATF_TESTS_C+= sigaction_test
+NETBSD_ATF_TESTS_C+= sigqueue_test
+NETBSD_ATF_TESTS_C+= sigtimedwait_test
+NETBSD_ATF_TESTS_C+= socketpair_test
+NETBSD_ATF_TESTS_C+= stat_test
+NETBSD_ATF_TESTS_C+= timer_create_test
+NETBSD_ATF_TESTS_C+= truncate_test
+NETBSD_ATF_TESTS_C+= ucontext_test
+NETBSD_ATF_TESTS_C+= umask_test
+NETBSD_ATF_TESTS_C+= unlink_test
+NETBSD_ATF_TESTS_C+= wait_test
+NETBSD_ATF_TESTS_C+= wait_noproc_test
+NETBSD_ATF_TESTS_C+= wait_noproc_wnohang_test
+NETBSD_ATF_TESTS_C+= write_test
+
+LIBADD.getpid_test+= pthread
+LIBADD.timer_create_test+= rt
+
+# Message queue IPC tests need to be executed serially since they variously
+# use global keys and exhaust global IPC limits.
+TEST_METADATA.msgctl_test+= is_exclusive="true"
+TEST_METADATA.msgget_test+= is_exclusive="true"
+TEST_METADATA.msgsnd_test+= is_exclusive="true"
+TEST_METADATA.msgrcv_test+= is_exclusive="true"
+
+.include "../Makefile.netbsd-tests"
+
+SRCS.mlock_test+= mlock_helper.c
+SRCS.setrlimit_test+= mlock_helper.c
+
+FILESGROUPS+= truncate_test_FILES
+
+truncate_test_FILES= truncate_test.root_owned
+truncate_test_FILESDIR= ${TESTSDIR}
+truncate_test_FILESMODE= 0600
+truncate_test_FILESOWN= root
+truncate_test_FILESGRP= wheel
+truncate_test_FILESPACKAGE= ${PACKAGE}
+
+CLEANFILES= truncate_test.root_owned
+# The dd status=none option is non-standard. Only use it when this test succeeds
+# rather than require dd to be a bootstrap tool.
+DD_NOSTATUS!=(dd status=none count=0 2> /dev/null && echo status=none) || true
+DD=dd ${DD_NOSTATUS}
+truncate_test.root_owned:
+ ${DD} if=/dev/null bs=1 count=1 of=${.TARGET}
+
+.include <bsd.test.mk>
diff --git a/lib/libc/tests/sys/Makefile.depend b/lib/libc/tests/sys/Makefile.depend
new file mode 100644
index 000000000000..c9d1296c4e9c
--- /dev/null
+++ b/lib/libc/tests/sys/Makefile.depend
@@ -0,0 +1,22 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libkvm \
+ lib/libnetbsd \
+ lib/librt \
+ lib/libthr \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libc/tests/sys/brk_test.c b/lib/libc/tests/sys/brk_test.c
new file mode 100644
index 000000000000..2d8c7af38ff7
--- /dev/null
+++ b/lib/libc/tests/sys/brk_test.c
@@ -0,0 +1,146 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2018 Mark Johnston <markj@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+ATF_TC(brk_basic);
+ATF_TC_HEAD(brk_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Verify basic brk() functionality");
+}
+ATF_TC_BODY(brk_basic, tc)
+{
+ void *oldbrk, *newbrk;
+ int error;
+
+ /* Reset the break. */
+ error = brk(0);
+ ATF_REQUIRE_MSG(error == 0, "brk: %s", strerror(errno));
+
+ oldbrk = sbrk(0);
+ ATF_REQUIRE(oldbrk != (void *)-1);
+
+ /* Try to allocate a page. */
+ error = brk((void *)((intptr_t)oldbrk + PAGE_SIZE * 2));
+ ATF_REQUIRE_MSG(error == 0, "brk: %s", strerror(errno));
+
+ /*
+ * Attempt to set the break below minbrk. This should have no effect.
+ */
+ error = brk((void *)((intptr_t)oldbrk - 1));
+ ATF_REQUIRE_MSG(error == 0, "brk: %s", strerror(errno));
+ newbrk = sbrk(0);
+ ATF_REQUIRE_MSG(newbrk != (void *)-1, "sbrk: %s", strerror(errno));
+ ATF_REQUIRE(newbrk == oldbrk);
+}
+
+ATF_TC(sbrk_basic);
+ATF_TC_HEAD(sbrk_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Verify basic sbrk() functionality");
+}
+ATF_TC_BODY(sbrk_basic, tc)
+{
+ void *newbrk, *oldbrk;
+ int *p;
+
+ oldbrk = sbrk(0);
+ ATF_REQUIRE_MSG(oldbrk != (void *)-1, "sbrk: %s", strerror(errno));
+ p = sbrk(sizeof(*p));
+ *p = 0;
+ ATF_REQUIRE(oldbrk == p);
+
+ newbrk = sbrk(-sizeof(*p));
+ ATF_REQUIRE_MSG(newbrk != (void *)-1, "sbrk: %s", strerror(errno));
+ ATF_REQUIRE(oldbrk == sbrk(0));
+
+ oldbrk = sbrk(PAGE_SIZE * 2 + 1);
+ ATF_REQUIRE_MSG(oldbrk != (void *)-1, "sbrk: %s", strerror(errno));
+ memset(oldbrk, 0, PAGE_SIZE * 2 + 1);
+ newbrk = sbrk(-(PAGE_SIZE * 2 + 1));
+ ATF_REQUIRE_MSG(newbrk != (void *)-1, "sbrk: %s", strerror(errno));
+ ATF_REQUIRE(sbrk(0) == oldbrk);
+}
+
+ATF_TC(mlockfuture);
+ATF_TC_HEAD(mlockfuture, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that mlockall(MCL_FUTURE) applies to the data segment");
+}
+ATF_TC_BODY(mlockfuture, tc)
+{
+ void *oldbrk, *n, *newbrk;
+ int error;
+ char v;
+
+ error = mlockall(MCL_FUTURE);
+ ATF_REQUIRE_MSG(error == 0,
+ "mlockall: %s", strerror(errno));
+
+ /*
+ * Advance the break so that at least one page is added to the data
+ * segment. This page should be automatically faulted in to the address
+ * space.
+ */
+ oldbrk = sbrk(0);
+ ATF_REQUIRE(oldbrk != (void *)-1);
+ newbrk = sbrk(PAGE_SIZE * 2);
+ ATF_REQUIRE(newbrk != (void *)-1);
+
+ n = (void *)(((uintptr_t)oldbrk + PAGE_SIZE) & ~PAGE_SIZE);
+ v = 0;
+ error = mincore(n, PAGE_SIZE, &v);
+ ATF_REQUIRE_MSG(error == 0,
+ "mincore: %s", strerror(errno));
+ ATF_REQUIRE_MSG((v & MINCORE_INCORE) != 0,
+ "unexpected page flags %#x", v);
+
+ error = brk(oldbrk);
+ ATF_REQUIRE(error == 0);
+
+ error = munlockall();
+ ATF_REQUIRE_MSG(error == 0,
+ "munlockall: %s", strerror(errno));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, brk_basic);
+ ATF_TP_ADD_TC(tp, sbrk_basic);
+ ATF_TP_ADD_TC(tp, mlockfuture);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/sys/cpuset_test.c b/lib/libc/tests/sys/cpuset_test.c
new file mode 100644
index 000000000000..53d6a8215bbc
--- /dev/null
+++ b/lib/libc/tests/sys/cpuset_test.c
@@ -0,0 +1,691 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020-2021 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/cpuset.h>
+#include <sys/jail.h>
+#include <sys/procdesc.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define SP_PARENT 0
+#define SP_CHILD 1
+
+struct jail_test_info {
+ cpuset_t jail_tidmask;
+ cpusetid_t jail_cpuset;
+ cpusetid_t jail_child_cpuset;
+};
+
+struct jail_test_cb_params {
+ struct jail_test_info info;
+ cpuset_t mask;
+ cpusetid_t rootid;
+ cpusetid_t setid;
+};
+
+typedef void (*jail_test_cb)(struct jail_test_cb_params *);
+
+#define FAILURE_JAIL 42
+#define FAILURE_MASK 43
+#define FAILURE_JAILSET 44
+#define FAILURE_PIDSET 45
+#define FAILURE_SEND 46
+#define FAILURE_DEADLK 47
+#define FAILURE_ATTACH 48
+#define FAILURE_BADAFFIN 49
+#define FAILURE_SUCCESS 50
+
+static const char *
+do_jail_errstr(int error)
+{
+
+ switch (error) {
+ case FAILURE_JAIL:
+ return ("jail_set(2) failed");
+ case FAILURE_MASK:
+ return ("Failed to get the thread cpuset mask");
+ case FAILURE_JAILSET:
+ return ("Failed to get the jail setid");
+ case FAILURE_PIDSET:
+ return ("Failed to get the pid setid");
+ case FAILURE_SEND:
+ return ("Failed to send(2) cpuset information");
+ case FAILURE_DEADLK:
+ return ("Deadlock hit trying to attach to jail");
+ case FAILURE_ATTACH:
+ return ("jail_attach(2) failed");
+ case FAILURE_BADAFFIN:
+ return ("Unexpected post-attach affinity");
+ case FAILURE_SUCCESS:
+ return ("jail_attach(2) succeeded, but should have failed.");
+ default:
+ return (NULL);
+ }
+}
+
+static void
+skip_ltncpu(int ncpu, cpuset_t *mask)
+{
+
+ CPU_ZERO(mask);
+ ATF_REQUIRE_EQ(0, cpuset_getaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID,
+ -1, sizeof(*mask), mask));
+ if (CPU_COUNT(mask) < ncpu)
+ atf_tc_skip("Test requires %d or more cores.", ncpu);
+}
+
+ATF_TC(newset);
+ATF_TC_HEAD(newset, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test cpuset(2)");
+}
+ATF_TC_BODY(newset, tc)
+{
+ cpusetid_t nsetid, setid, qsetid;
+
+ /* Obtain our initial set id. */
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1,
+ &setid));
+
+ /* Create a new one. */
+ ATF_REQUIRE_EQ(0, cpuset(&nsetid));
+ ATF_CHECK(nsetid != setid);
+
+ /* Query id again, make sure it's equal to the one we just got. */
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1,
+ &qsetid));
+ ATF_CHECK_EQ(nsetid, qsetid);
+}
+
+ATF_TC(transient);
+ATF_TC_HEAD(transient, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test that transient cpusets are freed.");
+}
+ATF_TC_BODY(transient, tc)
+{
+ cpusetid_t isetid, scratch, setid;
+
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_PID, -1,
+ &isetid));
+
+ ATF_REQUIRE_EQ(0, cpuset(&setid));
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET,
+ setid, &scratch));
+
+ /*
+ * Return back to our initial cpuset; the kernel should free the cpuset
+ * we just created.
+ */
+ ATF_REQUIRE_EQ(0, cpuset_setid(CPU_WHICH_PID, -1, isetid));
+ ATF_REQUIRE_EQ(-1, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET,
+ setid, &scratch));
+ ATF_CHECK_EQ(ESRCH, errno);
+}
+
+ATF_TC(deadlk);
+ATF_TC_HEAD(deadlk, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test against disjoint cpusets.");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(deadlk, tc)
+{
+ cpusetid_t setid;
+ cpuset_t dismask, mask, omask;
+ int fcpu, i, found, ncpu, second;
+
+ /* Make sure we have 3 cpus, so we test partial overlap. */
+ skip_ltncpu(3, &omask);
+
+ ATF_REQUIRE_EQ(0, cpuset(&setid));
+ CPU_ZERO(&mask);
+ CPU_ZERO(&dismask);
+ CPU_COPY(&omask, &mask);
+ CPU_COPY(&omask, &dismask);
+ fcpu = CPU_FFS(&mask);
+ ncpu = CPU_COUNT(&mask);
+
+ /*
+ * Turn off all but the first two for mask, turn off the first for
+ * dismask and turn them all off for both after the third.
+ */
+ for (i = fcpu - 1, found = 0; i < CPU_MAXSIZE && found != ncpu; i++) {
+ if (CPU_ISSET(i, &omask)) {
+ found++;
+ if (found == 1) {
+ CPU_CLR(i, &dismask);
+ } else if (found == 2) {
+ second = i;
+ } else if (found >= 3) {
+ CPU_CLR(i, &mask);
+ if (found > 3)
+ CPU_CLR(i, &dismask);
+ }
+ }
+ }
+
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID,
+ -1, sizeof(mask), &mask));
+
+ /* Must be a strict subset! */
+ ATF_REQUIRE_EQ(-1, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID,
+ -1, sizeof(dismask), &dismask));
+ ATF_REQUIRE_EQ(EINVAL, errno);
+
+ /*
+ * We'll set our anonymous set to the 0,1 set that currently matches
+ * the process. If we then set the process to the 1,2 set that's in
+ * dismask, we should then personally be restricted down to the single
+ * overlapping CPOU.
+ */
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID,
+ -1, sizeof(mask), &mask));
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID,
+ -1, sizeof(dismask), &dismask));
+ ATF_REQUIRE_EQ(0, cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID,
+ -1, sizeof(mask), &mask));
+ ATF_REQUIRE_EQ(1, CPU_COUNT(&mask));
+ ATF_REQUIRE(CPU_ISSET(second, &mask));
+
+ /*
+ * Finally, clearing the overlap and attempting to set the process
+ * cpuset to a completely disjoint mask should fail, because this
+ * process will then not have anything to run on.
+ */
+ CPU_CLR(second, &dismask);
+ ATF_REQUIRE_EQ(-1, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID,
+ -1, sizeof(dismask), &dismask));
+ ATF_REQUIRE_EQ(EDEADLK, errno);
+}
+
+static int
+do_jail(int sock)
+{
+ struct jail_test_info info;
+ struct iovec iov[2];
+ char *name;
+ int error;
+
+ if (asprintf(&name, "cpuset_%d", getpid()) == -1)
+ _exit(42);
+
+ iov[0].iov_base = "name";
+ iov[0].iov_len = 5;
+
+ iov[1].iov_base = name;
+ iov[1].iov_len = strlen(name) + 1;
+
+ if (jail_set(iov, 2, JAIL_CREATE | JAIL_ATTACH) < 0)
+ return (FAILURE_JAIL);
+
+ /* Record parameters, kick them over, then make a swift exit. */
+ CPU_ZERO(&info.jail_tidmask);
+ error = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID,
+ -1, sizeof(info.jail_tidmask), &info.jail_tidmask);
+ if (error != 0)
+ return (FAILURE_MASK);
+
+ error = cpuset_getid(CPU_LEVEL_ROOT, CPU_WHICH_TID, -1,
+ &info.jail_cpuset);
+ if (error != 0)
+ return (FAILURE_JAILSET);
+ error = cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1,
+ &info.jail_child_cpuset);
+ if (error != 0)
+ return (FAILURE_PIDSET);
+ if (send(sock, &info, sizeof(info), 0) != sizeof(info))
+ return (FAILURE_SEND);
+ return (0);
+}
+
+static void
+do_jail_test(int ncpu, bool newset, jail_test_cb prologue,
+ jail_test_cb epilogue)
+{
+ struct jail_test_cb_params cbp;
+ const char *errstr;
+ pid_t pid;
+ int error, sock, sockpair[2], status;
+
+ memset(&cbp.info, '\0', sizeof(cbp.info));
+
+ skip_ltncpu(ncpu, &cbp.mask);
+
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
+ &cbp.rootid));
+ if (newset)
+ ATF_REQUIRE_EQ(0, cpuset(&cbp.setid));
+ else
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_PID,
+ -1, &cbp.setid));
+ /* Special hack for prison0; it uses cpuset 1 as the root. */
+ if (cbp.rootid == 0)
+ cbp.rootid = 1;
+
+ /* Not every test needs early setup. */
+ if (prologue != NULL)
+ (*prologue)(&cbp);
+
+ ATF_REQUIRE_EQ(0, socketpair(PF_UNIX, SOCK_STREAM, 0, sockpair));
+ ATF_REQUIRE((pid = fork()) != -1);
+
+ if (pid == 0) {
+ /* Child */
+ close(sockpair[SP_PARENT]);
+ sock = sockpair[SP_CHILD];
+
+ _exit(do_jail(sock));
+ } else {
+ /* Parent */
+ sock = sockpair[SP_PARENT];
+ close(sockpair[SP_CHILD]);
+
+ while ((error = waitpid(pid, &status, 0)) == -1 &&
+ errno == EINTR) {
+ }
+
+ ATF_REQUIRE_EQ(sizeof(cbp.info), recv(sock, &cbp.info,
+ sizeof(cbp.info), 0));
+
+ /* Sanity check the exit info. */
+ ATF_REQUIRE_EQ(pid, error);
+ ATF_REQUIRE(WIFEXITED(status));
+ if (WEXITSTATUS(status) != 0) {
+ errstr = do_jail_errstr(WEXITSTATUS(status));
+ if (errstr != NULL)
+ atf_tc_fail("%s", errstr);
+ else
+ atf_tc_fail("Unknown error '%d'",
+ WEXITSTATUS(status));
+ }
+
+ epilogue(&cbp);
+ }
+}
+
+static void
+jail_attach_mutate_pro(struct jail_test_cb_params *cbp)
+{
+ cpuset_t *mask;
+ int count;
+
+ mask = &cbp->mask;
+
+ /* Knock out the first cpu. */
+ count = CPU_COUNT(mask);
+ CPU_CLR(CPU_FFS(mask) - 1, mask);
+ ATF_REQUIRE_EQ(count - 1, CPU_COUNT(mask));
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID,
+ -1, sizeof(*mask), mask));
+}
+
+static void
+jail_attach_newbase_epi(struct jail_test_cb_params *cbp)
+{
+ struct jail_test_info *info;
+ cpuset_t *mask;
+
+ info = &cbp->info;
+ mask = &cbp->mask;
+
+ /*
+ * The rootid test has been thrown in because a bug was discovered
+ * where any newly derived cpuset during attach would be parented to
+ * the wrong cpuset. Otherwise, we should observe that a new cpuset
+ * has been created for this process.
+ */
+ ATF_REQUIRE(info->jail_cpuset != cbp->rootid);
+ ATF_REQUIRE(info->jail_cpuset != cbp->setid);
+ ATF_REQUIRE(info->jail_cpuset != info->jail_child_cpuset);
+ ATF_REQUIRE_EQ(0, CPU_CMP(mask, &info->jail_tidmask));
+}
+
+ATF_TC(jail_attach_newbase);
+ATF_TC_HEAD(jail_attach_newbase, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test jail attachment effect on affinity with a new base cpuset.");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(jail_attach_newbase, tc)
+{
+
+ /* Need >= 2 cpus to test restriction. */
+ do_jail_test(2, true, &jail_attach_mutate_pro,
+ &jail_attach_newbase_epi);
+}
+
+ATF_TC(jail_attach_newbase_plain);
+ATF_TC_HEAD(jail_attach_newbase_plain, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test jail attachment effect on affinity with a new, unmodified base cpuset.");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(jail_attach_newbase_plain, tc)
+{
+
+ do_jail_test(2, true, NULL, &jail_attach_newbase_epi);
+}
+
+/*
+ * Generic epilogue for tests that are expecting to use the jail's root cpuset
+ * with their own mask, whether that's been modified or not.
+ */
+static void
+jail_attach_jset_epi(struct jail_test_cb_params *cbp)
+{
+ struct jail_test_info *info;
+ cpuset_t *mask;
+
+ info = &cbp->info;
+ mask = &cbp->mask;
+
+ ATF_REQUIRE(info->jail_cpuset != cbp->setid);
+ ATF_REQUIRE_EQ(info->jail_cpuset, info->jail_child_cpuset);
+ ATF_REQUIRE_EQ(0, CPU_CMP(mask, &info->jail_tidmask));
+}
+
+ATF_TC(jail_attach_prevbase);
+ATF_TC_HEAD(jail_attach_prevbase, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test jail attachment effect on affinity without a new base.");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(jail_attach_prevbase, tc)
+{
+
+ do_jail_test(2, false, &jail_attach_mutate_pro, &jail_attach_jset_epi);
+}
+
+static void
+jail_attach_plain_pro(struct jail_test_cb_params *cbp)
+{
+
+ if (cbp->setid != cbp->rootid)
+ atf_tc_skip("Must be running with the root cpuset.");
+}
+
+ATF_TC(jail_attach_plain);
+ATF_TC_HEAD(jail_attach_plain, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test jail attachment effect on affinity without specialization.");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(jail_attach_plain, tc)
+{
+
+ do_jail_test(1, false, &jail_attach_plain_pro, &jail_attach_jset_epi);
+}
+
+static int
+jail_attach_disjoint_newjail(int fd)
+{
+ struct iovec iov[2];
+ char *name;
+ int jid;
+
+ if (asprintf(&name, "cpuset_%d", getpid()) == -1)
+ _exit(42);
+
+ iov[0].iov_base = "name";
+ iov[0].iov_len = sizeof("name");
+
+ iov[1].iov_base = name;
+ iov[1].iov_len = strlen(name) + 1;
+
+ if ((jid = jail_set(iov, 2, JAIL_CREATE | JAIL_ATTACH)) < 0)
+ return (FAILURE_JAIL);
+
+ /* Signal that we're ready. */
+ write(fd, &jid, sizeof(jid));
+ for (;;) {
+ /* Spin */
+ }
+}
+
+static int
+wait_jail(int fd, int pfd)
+{
+ fd_set lset;
+ struct timeval tv;
+ int error, jid, maxfd;
+
+ FD_ZERO(&lset);
+ FD_SET(fd, &lset);
+ FD_SET(pfd, &lset);
+
+ maxfd = MAX(fd, pfd);
+
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+
+ /* Wait for jid to be written. */
+ do {
+ error = select(maxfd + 1, &lset, NULL, NULL, &tv);
+ } while (error == -1 && errno == EINTR);
+
+ if (error == 0) {
+ atf_tc_fail("Jail creator did not respond in time.");
+ }
+
+ ATF_REQUIRE_MSG(error > 0, "Unexpected error %d from select()", errno);
+
+ if (FD_ISSET(pfd, &lset)) {
+ /* Process died */
+ atf_tc_fail("Jail creator died unexpectedly.");
+ }
+
+ ATF_REQUIRE(FD_ISSET(fd, &lset));
+ ATF_REQUIRE_EQ(sizeof(jid), recv(fd, &jid, sizeof(jid), 0));
+
+ return (jid);
+}
+
+static int
+try_attach_child(int jid, cpuset_t *expected_mask)
+{
+ cpuset_t mask;
+
+ if (jail_attach(jid) == -1) {
+ if (errno == EDEADLK)
+ return (FAILURE_DEADLK);
+ return (FAILURE_ATTACH);
+ }
+
+ if (expected_mask == NULL)
+ return (FAILURE_SUCCESS);
+
+ /* If we had an expected mask, check it against the new process mask. */
+ CPU_ZERO(&mask);
+ if (cpuset_getaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID,
+ -1, sizeof(mask), &mask) != 0) {
+ return (FAILURE_MASK);
+ }
+
+ if (CPU_CMP(expected_mask, &mask) != 0)
+ return (FAILURE_BADAFFIN);
+
+ return (0);
+}
+
+static void
+try_attach(int jid, cpuset_t *expected_mask)
+{
+ const char *errstr;
+ pid_t pid;
+ int error, fail, status;
+
+ ATF_REQUIRE(expected_mask != NULL);
+ ATF_REQUIRE((pid = fork()) != -1);
+ if (pid == 0)
+ _exit(try_attach_child(jid, expected_mask));
+
+ while ((error = waitpid(pid, &status, 0)) == -1 && errno == EINTR) {
+ /* Try again. */
+ }
+
+ /* Sanity check the exit info. */
+ ATF_REQUIRE_EQ(pid, error);
+ ATF_REQUIRE(WIFEXITED(status));
+ if ((fail = WEXITSTATUS(status)) != 0) {
+ errstr = do_jail_errstr(fail);
+ if (errstr != NULL)
+ atf_tc_fail("%s", errstr);
+ else
+ atf_tc_fail("Unknown error '%d'", WEXITSTATUS(status));
+ }
+}
+
+ATF_TC(jail_attach_disjoint);
+ATF_TC_HEAD(jail_attach_disjoint, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test root attachment into completely disjoint jail cpuset.");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(jail_attach_disjoint, tc)
+{
+ cpuset_t smask, jmask;
+ int sockpair[2];
+ cpusetid_t setid;
+ pid_t pid;
+ int fcpu, jid, pfd, sock, scpu;
+
+ ATF_REQUIRE_EQ(0, cpuset(&setid));
+
+ skip_ltncpu(2, &jmask);
+ fcpu = CPU_FFS(&jmask) - 1;
+ ATF_REQUIRE_EQ(0, socketpair(PF_UNIX, SOCK_STREAM, 0, sockpair));
+
+ /* We'll wait on the procdesc, too, so we can fail faster if it dies. */
+ ATF_REQUIRE((pid = pdfork(&pfd, 0)) != -1);
+
+ if (pid == 0) {
+ /* First child sets up the jail. */
+ sock = sockpair[SP_CHILD];
+ close(sockpair[SP_PARENT]);
+
+ _exit(jail_attach_disjoint_newjail(sock));
+ }
+
+ close(sockpair[SP_CHILD]);
+ sock = sockpair[SP_PARENT];
+
+ ATF_REQUIRE((jid = wait_jail(sock, pfd)) > 0);
+
+ /*
+ * This process will be clamped down to the first cpu, while the jail
+ * will simply have the first CPU removed to make it a completely
+ * disjoint operation.
+ */
+ CPU_ZERO(&smask);
+ CPU_SET(fcpu, &smask);
+ CPU_CLR(fcpu, &jmask);
+
+ /*
+ * We'll test with the first and second cpu set as well. Only the
+ * second cpu should be used.
+ */
+ scpu = CPU_FFS(&jmask) - 1;
+
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_ROOT, CPU_WHICH_JAIL,
+ jid, sizeof(jmask), &jmask));
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET,
+ setid, sizeof(smask), &smask));
+
+ try_attach(jid, &jmask);
+
+ CPU_SET(scpu, &smask);
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_CPUSET,
+ setid, sizeof(smask), &smask));
+
+ CPU_CLR(fcpu, &smask);
+ try_attach(jid, &smask);
+}
+
+ATF_TC(badparent);
+ATF_TC_HEAD(badparent, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test parent assignment when assigning a new cpuset.");
+}
+ATF_TC_BODY(badparent, tc)
+{
+ cpuset_t mask;
+ cpusetid_t finalsetid, origsetid, setid;
+
+ /* Need to mask off at least one CPU. */
+ skip_ltncpu(2, &mask);
+
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1,
+ &origsetid));
+
+ ATF_REQUIRE_EQ(0, cpuset(&setid));
+
+ /*
+ * Mask off the first CPU, then we'll reparent ourselves to our original
+ * set.
+ */
+ CPU_CLR(CPU_FFS(&mask) - 1, &mask);
+ ATF_REQUIRE_EQ(0, cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID,
+ -1, sizeof(mask), &mask));
+
+ ATF_REQUIRE_EQ(0, cpuset_setid(CPU_WHICH_PID, -1, origsetid));
+ ATF_REQUIRE_EQ(0, cpuset_getid(CPU_LEVEL_CPUSET, CPU_WHICH_TID, -1,
+ &finalsetid));
+
+ ATF_REQUIRE_EQ(finalsetid, origsetid);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, newset);
+ ATF_TP_ADD_TC(tp, transient);
+ ATF_TP_ADD_TC(tp, deadlk);
+ ATF_TP_ADD_TC(tp, jail_attach_newbase);
+ ATF_TP_ADD_TC(tp, jail_attach_newbase_plain);
+ ATF_TP_ADD_TC(tp, jail_attach_prevbase);
+ ATF_TP_ADD_TC(tp, jail_attach_plain);
+ ATF_TP_ADD_TC(tp, jail_attach_disjoint);
+ ATF_TP_ADD_TC(tp, badparent);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/sys/errno_test.c b/lib/libc/tests/sys/errno_test.c
new file mode 100644
index 000000000000..27d0548fc29d
--- /dev/null
+++ b/lib/libc/tests/sys/errno_test.c
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 2024 The FreeBSD Foundation
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * This software were developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+ATF_TC(errno_basic);
+ATF_TC_HEAD(errno_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Verify basic functionality of errno");
+}
+
+ATF_TC_BODY(errno_basic, tc)
+{
+ int res;
+
+ res = unlink("/non/existent/file");
+ ATF_REQUIRE(res == -1);
+ ATF_REQUIRE(errno == ENOENT);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, errno_basic);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/sys/mlock_helper.c b/lib/libc/tests/sys/mlock_helper.c
new file mode 100644
index 000000000000..e7a3d5e39c3f
--- /dev/null
+++ b/lib/libc/tests/sys/mlock_helper.c
@@ -0,0 +1,111 @@
+/*-
+ * Copyright (C) 2016 Bryan Drewery <bdrewery@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Helper for mlock(3) to avoid EAGAIN errors
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+
+#define VM_MAX_WIRED "vm.max_user_wired"
+
+static void
+vm_max_wired_sysctl(u_long *old_value, u_long *new_value)
+{
+ size_t old_len;
+ size_t new_len = (new_value == NULL ? 0 : sizeof(*new_value));
+
+ if (old_value == NULL)
+ printf("Setting the new value to %lu\n", *new_value);
+ else {
+ ATF_REQUIRE_MSG(sysctlbyname(VM_MAX_WIRED, NULL, &old_len,
+ new_value, new_len) == 0,
+ "sysctlbyname(%s) failed: %s", VM_MAX_WIRED, strerror(errno));
+ }
+
+ ATF_REQUIRE_MSG(sysctlbyname(VM_MAX_WIRED, old_value, &old_len,
+ new_value, new_len) == 0,
+ "sysctlbyname(%s) failed: %s", VM_MAX_WIRED, strerror(errno));
+
+ if (old_value != NULL)
+ printf("Saved the old value (%lu)\n", *old_value);
+}
+
+void
+set_vm_max_wired(u_long new_value)
+{
+ FILE *fp;
+ u_long old_value;
+
+ fp = fopen(VM_MAX_WIRED, "w");
+ if (fp == NULL) {
+ atf_tc_skip("could not open %s for writing: %s",
+ VM_MAX_WIRED, strerror(errno));
+ return;
+ }
+
+ vm_max_wired_sysctl(&old_value, NULL);
+
+ ATF_REQUIRE_MSG(fprintf(fp, "%lu", old_value) > 0,
+ "saving %s failed", VM_MAX_WIRED);
+
+ fclose(fp);
+
+ vm_max_wired_sysctl(NULL, &new_value);
+}
+
+void
+restore_vm_max_wired(void)
+{
+ FILE *fp;
+ u_long saved_max_wired;
+
+ fp = fopen(VM_MAX_WIRED, "r");
+ if (fp == NULL) {
+ perror("fopen failed\n");
+ return;
+ }
+
+ if (fscanf(fp, "%lu", &saved_max_wired) != 1) {
+ perror("fscanf failed\n");
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+ printf("old value in %s: %lu\n", VM_MAX_WIRED, saved_max_wired);
+
+ if (saved_max_wired == 0) /* This will cripple the test host */
+ return;
+
+ vm_max_wired_sysctl(NULL, &saved_max_wired);
+}
diff --git a/lib/libc/tests/sys/queue_test.c b/lib/libc/tests/sys/queue_test.c
new file mode 100644
index 000000000000..cfe9ac934cbd
--- /dev/null
+++ b/lib/libc/tests/sys/queue_test.c
@@ -0,0 +1,234 @@
+/*-
+ * Copyright (c) 2015 EMC Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 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/queue.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+ATF_TC(slist_test);
+ATF_TC_HEAD(slist_test, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr", "SLIST macro feature tests");
+}
+
+ATF_TC_BODY(slist_test, tc)
+{
+ SLIST_HEAD(stailhead, entry) head = SLIST_HEAD_INITIALIZER(head);
+ struct entry {
+ SLIST_ENTRY(entry) entries;
+ int i;
+ } *n1, *n2, *n3, *np;
+ int i, j, length;
+
+ SLIST_INIT(&head);
+
+ printf("Ensuring SLIST_EMPTY works\n");
+
+ ATF_REQUIRE(SLIST_EMPTY(&head));
+
+ i = length = 0;
+
+ SLIST_FOREACH(np, &head, entries) {
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 0);
+
+ printf("Ensuring SLIST_INSERT_HEAD works\n");
+
+ n1 = malloc(sizeof(struct entry));
+ ATF_REQUIRE(n1 != NULL);
+ n1->i = i++;
+
+ SLIST_INSERT_HEAD(&head, n1, entries);
+
+ printf("Ensuring SLIST_FIRST returns element 1\n");
+ ATF_REQUIRE_EQ(SLIST_FIRST(&head), n1);
+
+ j = length = 0;
+ SLIST_FOREACH(np, &head, entries) {
+ ATF_REQUIRE_EQ_MSG(np->i, j,
+ "%d (entry counter) != %d (counter)", np->i, j);
+ j++;
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 1);
+
+ printf("Ensuring SLIST_INSERT_AFTER works\n");
+
+ n2 = malloc(sizeof(struct entry));
+ ATF_REQUIRE(n2 != NULL);
+ n2->i = i++;
+
+ SLIST_INSERT_AFTER(n1, n2, entries);
+
+ n3 = malloc(sizeof(struct entry));
+ ATF_REQUIRE(n3 != NULL);
+ n3->i = i++;
+
+ SLIST_INSERT_AFTER(n2, n3, entries);
+
+ j = length = 0;
+ SLIST_FOREACH(np, &head, entries) {
+ ATF_REQUIRE_EQ_MSG(np->i, j,
+ "%d (entry counter) != %d (counter)", np->i, j);
+ j++;
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 3);
+
+ printf("Ensuring SLIST_REMOVE_HEAD works\n");
+
+ printf("Ensuring SLIST_FIRST returns element 1\n");
+ ATF_REQUIRE_EQ(SLIST_FIRST(&head), n1);
+
+ SLIST_REMOVE_HEAD(&head, entries);
+
+ printf("Ensuring SLIST_FIRST now returns element 2\n");
+ ATF_REQUIRE_EQ(SLIST_FIRST(&head), n2);
+
+ j = 1; /* Starting point's 1 this time */
+ length = 0;
+ SLIST_FOREACH(np, &head, entries) {
+ ATF_REQUIRE_EQ_MSG(np->i, j,
+ "%d (entry counter) != %d (counter)", np->i, j);
+ j++;
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 2);
+
+ printf("Ensuring SLIST_REMOVE_AFTER works by removing the tail\n");
+
+ SLIST_REMOVE_AFTER(n2, entries);
+
+ j = 1; /* Starting point's 1 this time */
+ length = 0;
+ SLIST_FOREACH(np, &head, entries) {
+ ATF_REQUIRE_EQ_MSG(np->i, j,
+ "%d (entry counter) != %d (counter)", np->i, j);
+ j++;
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 1);
+
+ printf("Ensuring SLIST_FIRST returns element 2\n");
+ ATF_REQUIRE_EQ(SLIST_FIRST(&head), n2);
+
+}
+
+ATF_TC(stailq_test);
+ATF_TC_HEAD(stailq_test, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr", "STAILQ macro feature tests");
+}
+
+ATF_TC_BODY(stailq_test, tc)
+{
+ STAILQ_HEAD(stailhead, entry) head = STAILQ_HEAD_INITIALIZER(head);
+ struct entry {
+ STAILQ_ENTRY(entry) entries;
+ int i;
+ } *n1, *n2, *n3, *np;
+ int i, j, length;
+
+ printf("Ensuring empty STAILQs are treated properly\n");
+ STAILQ_INIT(&head);
+ ATF_REQUIRE(STAILQ_EMPTY(&head));
+
+ i = length = 0;
+
+ STAILQ_FOREACH(np, &head, entries) {
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 0);
+
+ printf("Ensuring STAILQ_INSERT_HEAD works\n");
+
+ n1 = malloc(sizeof(struct entry));
+ ATF_REQUIRE(n1 != NULL);
+ n1->i = i++;
+
+ STAILQ_INSERT_HEAD(&head, n1, entries);
+
+ j = length = 0;
+ STAILQ_FOREACH(np, &head, entries) {
+ ATF_REQUIRE_EQ_MSG(np->i, j,
+ "%d (entry counter) != %d (counter)", np->i, j);
+ j++;
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 1);
+
+ printf("Ensuring STAILQ_INSERT_TAIL works\n");
+
+ n2 = malloc(sizeof(struct entry));
+ ATF_REQUIRE(n2 != NULL);
+ n2->i = i++;
+
+ STAILQ_INSERT_TAIL(&head, n2, entries);
+
+ n3 = malloc(sizeof(struct entry));
+ ATF_REQUIRE(n3 != NULL);
+ n3->i = i++;
+
+ STAILQ_INSERT_TAIL(&head, n3, entries);
+
+ j = length = 0;
+ STAILQ_FOREACH(np, &head, entries) {
+ ATF_REQUIRE_EQ_MSG(np->i, j,
+ "%d (entry counter) != %d (counter)", np->i, j);
+ j++;
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 3);
+
+ printf("Ensuring STAILQ_REMOVE_HEAD works\n");
+
+ STAILQ_REMOVE_HEAD(&head, entries);
+
+ j = 1; /* Starting point's 1 this time */
+ length = 0;
+ STAILQ_FOREACH(np, &head, entries) {
+ ATF_REQUIRE_EQ_MSG(np->i, j,
+ "%d (entry counter) != %d (counter)", np->i, j);
+ j++;
+ length++;
+ }
+ ATF_REQUIRE_EQ(length, 2);
+
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, slist_test);
+ ATF_TP_ADD_TC(tp, stailq_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/sys/sendfile_test.c b/lib/libc/tests/sys/sendfile_test.c
new file mode 100644
index 000000000000..d46e7b0cb186
--- /dev/null
+++ b/lib/libc/tests/sys/sendfile_test.c
@@ -0,0 +1,1208 @@
+/*-
+ * Copyright (c) 2018 Enji Cooper.
+ * 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/param.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+const char DETERMINISTIC_PATTERN[] =
+ "The past is already gone, the future is not yet here. There's only one moment for you to live.\n";
+
+#define SOURCE_FILE "source"
+#define DESTINATION_FILE "dest"
+
+#define PORTRANGE_FIRST "net.inet.ip.portrange.first"
+#define PORTRANGE_LAST "net.inet.ip.portrange.last"
+
+static int portrange_first, portrange_last;
+
+static int
+get_int_via_sysctlbyname(const char *oidname)
+{
+ size_t oldlen;
+ int int_value;
+
+ oldlen = sizeof(int_value);
+
+ ATF_REQUIRE_EQ_MSG(sysctlbyname(oidname, &int_value, &oldlen, NULL, 0),
+ 0, "sysctlbyname(%s, ...) failed: %s", oidname, strerror(errno));
+ ATF_REQUIRE_EQ_MSG(sizeof(int_value), oldlen, "sanity check failed");
+
+ return (int_value);
+}
+
+static int
+generate_random_port(int seed)
+{
+ int random_port;
+
+ printf("Generating a random port with seed=%d\n", seed);
+ if (portrange_first == 0) {
+ portrange_first = get_int_via_sysctlbyname(PORTRANGE_FIRST);
+ printf("Port range lower bound: %d\n", portrange_first);
+ }
+
+ if (portrange_last == 0) {
+ portrange_last = get_int_via_sysctlbyname(PORTRANGE_LAST);
+ printf("Port range upper bound: %d\n", portrange_last);
+ }
+
+ srand((unsigned)seed);
+
+ random_port = rand() % (portrange_last - portrange_first) +
+ portrange_first;
+
+ printf("Random port generated: %d\n", random_port);
+ return (random_port);
+}
+
+static void
+resolve_localhost(struct addrinfo **res, int domain, int type, int port)
+{
+ const char *host;
+ char *serv;
+ struct addrinfo hints;
+ int error;
+
+ switch (domain) {
+ case AF_INET:
+ host = "127.0.0.1";
+ break;
+ case AF_INET6:
+ host = "::1";
+ break;
+ default:
+ atf_tc_fail("unhandled domain: %d", domain);
+ }
+
+ ATF_REQUIRE_MSG(asprintf(&serv, "%d", port) >= 0,
+ "asprintf failed: %s", strerror(errno));
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = domain;
+ hints.ai_flags = AI_ADDRCONFIG|AI_NUMERICSERV|AI_NUMERICHOST;
+ hints.ai_socktype = type;
+
+ error = getaddrinfo(host, serv, &hints, res);
+ ATF_REQUIRE_EQ_MSG(error, 0,
+ "getaddrinfo failed: %s", gai_strerror(error));
+ free(serv);
+}
+
+static int
+make_socket(int domain, int type, int protocol)
+{
+ int sock;
+
+ sock = socket(domain, type, protocol);
+ ATF_REQUIRE_MSG(sock != -1, "socket(%d, %d, 0) failed: %s",
+ domain, type, strerror(errno));
+
+ return (sock);
+}
+
+static int
+setup_client(int domain, int type, int port)
+{
+ struct addrinfo *res;
+ char host[NI_MAXHOST+1];
+ int error, sock;
+
+ resolve_localhost(&res, domain, type, port);
+ error = getnameinfo(
+ (const struct sockaddr*)res->ai_addr, res->ai_addrlen,
+ host, nitems(host) - 1, NULL, 0, NI_NUMERICHOST);
+ ATF_REQUIRE_EQ_MSG(error, 0,
+ "getnameinfo failed: %s", gai_strerror(error));
+ printf(
+ "Will try to connect to host='%s', address_family=%d, "
+ "socket_type=%d\n",
+ host, res->ai_family, res->ai_socktype);
+ /* Avoid a double print when forked by flushing. */
+ fflush(stdout);
+ sock = make_socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ error = connect(sock, (struct sockaddr*)res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ ATF_REQUIRE_EQ_MSG(error, 0, "connect failed: %s", strerror(errno));
+ return (sock);
+}
+
+/*
+ * XXX: use linear probing to find a free port and eliminate `port` argument as
+ * a [const] int (it will need to be a pointer so it can be passed back out of
+ * the function and can influence which port `setup_client(..)` connects on.
+ */
+static int
+setup_server(int domain, int type, int port)
+{
+ struct addrinfo *res;
+ char host[NI_MAXHOST+1];
+ int error, sock;
+
+ resolve_localhost(&res, domain, type, port);
+ sock = make_socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+
+ error = getnameinfo(
+ (const struct sockaddr*)res->ai_addr, res->ai_addrlen,
+ host, nitems(host) - 1, NULL, 0, NI_NUMERICHOST);
+ ATF_REQUIRE_EQ_MSG(error, 0,
+ "getnameinfo failed: %s", gai_strerror(error));
+ printf(
+ "Will try to bind socket to host='%s', address_family=%d, "
+ "socket_type=%d\n",
+ host, res->ai_family, res->ai_socktype);
+ /* Avoid a double print when forked by flushing. */
+ fflush(stdout);
+ error = bind(sock, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ ATF_REQUIRE_EQ_MSG(error, 0, "bind failed: %s", strerror(errno));
+ error = listen(sock, 1);
+ ATF_REQUIRE_EQ_MSG(error, 0, "listen failed: %s", strerror(errno));
+
+ return (sock);
+}
+
+/*
+ * This function is a helper routine for taking data being sent by `sendfile` via
+ * `server_sock`, and pushing the received stream out to a file, denoted by
+ * `dest_filename`.
+ */
+static void
+server_cat(const char *dest_filename, int server_sock, size_t len)
+{
+ char *buffer, *buf_window_ptr;
+ int recv_sock;
+ size_t buffer_size;
+ ssize_t received_bytes, recv_ret;
+
+ /*
+ * Ensure that there isn't excess data sent across the wire by
+ * capturing 10 extra bytes (plus 1 for nul).
+ */
+ buffer_size = len + 10 + 1;
+ buffer = calloc(buffer_size, sizeof(char));
+ if (buffer == NULL)
+ err(1, "malloc failed");
+
+ recv_sock = accept(server_sock, NULL, 0);
+ if (recv_sock == -1)
+ err(1, "accept failed");
+
+ buf_window_ptr = buffer;
+ received_bytes = 0;
+ do {
+ recv_ret = recv(recv_sock, buf_window_ptr,
+ buffer_size - received_bytes, 0);
+ if (recv_ret <= 0)
+ break;
+ buf_window_ptr += recv_ret;
+ received_bytes += recv_ret;
+ } while (received_bytes < buffer_size);
+
+ atf_utils_create_file(dest_filename, "%s", buffer);
+
+ (void)close(recv_sock);
+ (void)close(server_sock);
+ free(buffer);
+
+ if (received_bytes != len)
+ errx(1, "received unexpected data: %zd != %zd", received_bytes,
+ len);
+}
+
+static int
+setup_tcp_server(int domain, int port)
+{
+
+ return (setup_server(domain, SOCK_STREAM, port));
+}
+
+static int
+setup_tcp_client(int domain, int port)
+{
+
+ return (setup_client(domain, SOCK_STREAM, port));
+}
+
+static off_t
+file_size_from_fd(int fd)
+{
+ struct stat st;
+
+ ATF_REQUIRE_EQ_MSG(0, fstat(fd, &st),
+ "fstat failed: %s", strerror(errno));
+
+ return (st.st_size);
+}
+
+/*
+ * NB: `nbytes` == 0 has special connotations given the sendfile(2) API
+ * contract. In short, "send the whole file" (paraphrased).
+ */
+static void
+verify_source_and_dest(const char* dest_filename, int src_fd, off_t offset,
+ size_t nbytes)
+{
+ char *dest_pointer, *src_pointer;
+ off_t dest_file_size, src_file_size;
+ size_t length;
+ int dest_fd;
+
+ atf_utils_cat_file(dest_filename, "dest_file: ");
+
+ dest_fd = open(dest_filename, O_RDONLY);
+ ATF_REQUIRE_MSG(dest_fd != -1, "open failed");
+
+ dest_file_size = file_size_from_fd(dest_fd);
+ src_file_size = file_size_from_fd(src_fd);
+
+ /*
+ * Per sendfile(2), "send the whole file" (paraphrased). This means
+ * that we need to grab the file size, as passing in length = 0 with
+ * mmap(2) will result in a failure with EINVAL (length = 0 is invalid).
+ */
+ length = (nbytes == 0) ? (size_t)(src_file_size - offset) : nbytes;
+
+ ATF_REQUIRE_EQ_MSG(dest_file_size, length,
+ "number of bytes written out to %s (%ju) doesn't match the "
+ "expected number of bytes (%zu)", dest_filename, dest_file_size,
+ length);
+
+ ATF_REQUIRE_EQ_MSG(0, lseek(src_fd, offset, SEEK_SET),
+ "lseek failed: %s", strerror(errno));
+
+ dest_pointer = mmap(NULL, length, PROT_READ, MAP_PRIVATE, dest_fd, 0);
+ ATF_REQUIRE_MSG(dest_pointer != MAP_FAILED, "mmap failed: %s",
+ strerror(errno));
+
+ printf("Will mmap in the source file from offset=%jd to length=%zu\n",
+ offset, length);
+
+ src_pointer = mmap(NULL, length, PROT_READ, MAP_PRIVATE, src_fd, offset);
+ ATF_REQUIRE_MSG(src_pointer != MAP_FAILED, "mmap failed: %s",
+ strerror(errno));
+
+ ATF_REQUIRE_EQ_MSG(0, memcmp(src_pointer, dest_pointer, length),
+ "Contents of source and destination do not match. '%s' != '%s'",
+ src_pointer, dest_pointer);
+
+ (void)munmap(src_pointer, length);
+ (void)munmap(dest_pointer, length);
+ (void)close(dest_fd);
+}
+
+static void
+fd_positive_file_test(int domain)
+{
+ off_t offset;
+ size_t nbytes, pattern_size;
+ int client_sock, error, fd, port, server_sock;
+ pid_t server_pid;
+
+ pattern_size = strlen(DETERMINISTIC_PATTERN);
+
+ atf_utils_create_file(SOURCE_FILE, "%s", DETERMINISTIC_PATTERN);
+ fd = open(SOURCE_FILE, O_RDONLY);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ port = generate_random_port(__LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ server_pid = atf_utils_fork();
+ if (server_pid == 0) {
+ (void)close(client_sock);
+ server_cat(DESTINATION_FILE, server_sock, pattern_size);
+ _exit(0);
+ } else
+ (void)close(server_sock);
+
+ nbytes = 0;
+ offset = 0;
+ error = sendfile(fd, client_sock, offset, nbytes, NULL, NULL,
+ SF_FLAGS(0, 0));
+ ATF_REQUIRE_EQ_MSG(0, error, "sendfile failed: %s", strerror(errno));
+ (void)close(client_sock);
+
+ atf_utils_wait(server_pid, 0, "", "");
+ verify_source_and_dest(DESTINATION_FILE, fd, offset, nbytes);
+
+ (void)close(fd);
+}
+
+ATF_TC(fd_positive_file_v4);
+ATF_TC_HEAD(fd_positive_file_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify regular file as file descriptor support (IPv4)");
+}
+ATF_TC_BODY(fd_positive_file_v4, tc)
+{
+
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ fd_positive_file_test(AF_INET);
+}
+
+ATF_TC(fd_positive_file_v6);
+ATF_TC_HEAD(fd_positive_file_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify regular file as file descriptor support (IPv6)");
+}
+ATF_TC_BODY(fd_positive_file_v6, tc)
+{
+
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ fd_positive_file_test(AF_INET6);
+}
+
+static void
+fd_positive_shm_test(int domain)
+{
+ char *shm_pointer;
+ off_t offset;
+ size_t nbytes, pattern_size;
+ pid_t server_pid;
+ int client_sock, error, fd, port, server_sock;
+
+ pattern_size = strlen(DETERMINISTIC_PATTERN);
+
+ printf("pattern size: %zu\n", pattern_size);
+
+ fd = shm_open(SHM_ANON, O_RDWR|O_CREAT, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "shm_open failed: %s", strerror(errno));
+ ATF_REQUIRE_EQ_MSG(0, ftruncate(fd, pattern_size),
+ "ftruncate failed: %s", strerror(errno));
+ shm_pointer = mmap(NULL, pattern_size, PROT_READ|PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ ATF_REQUIRE_MSG(shm_pointer != MAP_FAILED,
+ "mmap failed: %s", strerror(errno));
+ memcpy(shm_pointer, DETERMINISTIC_PATTERN, pattern_size);
+ ATF_REQUIRE_EQ_MSG(0,
+ memcmp(shm_pointer, DETERMINISTIC_PATTERN, pattern_size),
+ "memcmp showed data mismatch: '%s' != '%s'",
+ DETERMINISTIC_PATTERN, shm_pointer);
+
+ port = generate_random_port(__LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ server_pid = atf_utils_fork();
+ if (server_pid == 0) {
+ (void)close(client_sock);
+ server_cat(DESTINATION_FILE, server_sock, pattern_size);
+ _exit(0);
+ } else
+ (void)close(server_sock);
+
+ nbytes = 0;
+ offset = 0;
+ error = sendfile(fd, client_sock, offset, nbytes, NULL, NULL,
+ SF_FLAGS(0, 0));
+ ATF_REQUIRE_EQ_MSG(0, error, "sendfile failed: %s", strerror(errno));
+ (void)close(client_sock);
+
+ atf_utils_wait(server_pid, 0, "", "");
+ verify_source_and_dest(DESTINATION_FILE, fd, offset, nbytes);
+
+ (void)munmap(shm_pointer, sizeof(DETERMINISTIC_PATTERN));
+ (void)close(fd);
+}
+
+ATF_TC(fd_positive_shm_v4);
+ATF_TC_HEAD(fd_positive_shm_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify shared memory as file descriptor support (IPv4)");
+}
+ATF_TC_BODY(fd_positive_shm_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ fd_positive_shm_test(AF_INET);
+}
+
+ATF_TC(fd_positive_shm_v6);
+ATF_TC_HEAD(fd_positive_shm_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify shared memory as file descriptor support (IPv6))");
+}
+ATF_TC_BODY(fd_positive_shm_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ fd_positive_shm_test(AF_INET6);
+}
+
+static void
+fd_negative_bad_fd_test(int domain)
+{
+ int client_sock, error, fd, port, server_sock;
+
+ port = generate_random_port(__LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ fd = -1;
+
+ error = sendfile(fd, client_sock, 0, 0, NULL, NULL, SF_FLAGS(0, 0));
+ ATF_REQUIRE_ERRNO(EBADF, error == -1);
+
+ (void)close(client_sock);
+ (void)close(server_sock);
+}
+
+ATF_TC(fd_negative_bad_fd_v4);
+ATF_TC_HEAD(fd_negative_bad_fd_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify bad file descriptor returns EBADF (IPv4)");
+}
+ATF_TC_BODY(fd_negative_bad_fd_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ fd_negative_bad_fd_test(AF_INET);
+}
+
+ATF_TC(fd_negative_bad_fd_v6);
+ATF_TC_HEAD(fd_negative_bad_fd_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify bad file descriptor returns EBADF (IPv6)");
+}
+ATF_TC_BODY(fd_negative_bad_fd_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ fd_negative_bad_fd_test(AF_INET6);
+}
+
+static void
+flags_test(int domain)
+{
+ off_t offset;
+ size_t nbytes, pattern_size;
+ int client_sock, error, fd, i, port, server_sock;
+ pid_t server_pid;
+ int16_t number_pages = 10;
+
+ pattern_size = strlen(DETERMINISTIC_PATTERN);
+
+ struct testcase {
+ int16_t readahead_pages, flags;
+ } testcases[] = {
+ /* This is covered in `:fd_positive_file` */
+#if 0
+ {
+ .readahead_pages = 0,
+ .flags = 0
+ },
+#endif
+ {
+ .readahead_pages = 0,
+ .flags = SF_NOCACHE
+ },
+#ifdef SF_USER_READAHEAD
+ {
+ .readahead_pages = 0,
+ .flags = SF_NOCACHE|SF_USER_READAHEAD
+ },
+ {
+ .readahead_pages = 0,
+ .flags = SF_USER_READAHEAD
+ },
+#endif
+ {
+ .readahead_pages = number_pages,
+ .flags = 0
+ },
+ {
+ .readahead_pages = number_pages,
+ .flags = SF_NOCACHE
+ },
+#ifdef SF_USER_READAHEAD
+ {
+ .readahead_pages = number_pages,
+ .flags = SF_NOCACHE|SF_USER_READAHEAD
+ },
+#endif
+ {
+ .readahead_pages = number_pages,
+ .flags = SF_NOCACHE
+ },
+ {
+ .readahead_pages = number_pages,
+ .flags = SF_NODISKIO
+ }
+ };
+
+ atf_utils_create_file(SOURCE_FILE, "%s", DETERMINISTIC_PATTERN);
+ for (i = 0; i < nitems(testcases); i++) {
+ fd = open(SOURCE_FILE, O_RDONLY);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ port = generate_random_port(i * __LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ server_pid = atf_utils_fork();
+ if (server_pid == 0) {
+ (void)close(client_sock);
+ server_cat(DESTINATION_FILE, server_sock, pattern_size);
+ _exit(0);
+ } else
+ (void)close(server_sock);
+
+ nbytes = 0;
+ offset = 0;
+ error = sendfile(fd, client_sock, offset, nbytes, NULL, NULL,
+ SF_FLAGS(testcases[i].readahead_pages, testcases[i].flags));
+ ATF_CHECK_EQ_MSG(error, 0, "sendfile testcase #%d failed: %s",
+ i, strerror(errno));
+ (void)close(client_sock);
+
+ atf_utils_wait(server_pid, 0, "", "");
+ verify_source_and_dest(DESTINATION_FILE, fd, offset, nbytes);
+
+ (void)close(fd);
+ }
+}
+
+ATF_TC(flags_v4);
+ATF_TC_HEAD(flags_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr", "Verify flags functionality (IPv4)");
+}
+ATF_TC_BODY(flags_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ flags_test(AF_INET);
+}
+
+ATF_TC(flags_v6);
+ATF_TC_HEAD(flags_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr", "Verify flags functionality (IPv6)");
+}
+ATF_TC_BODY(flags_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ flags_test(AF_INET6);
+}
+
+static void
+hdtr_positive_test(int domain)
+{
+ struct iovec headers[1], trailers[1];
+ struct testcase {
+ bool include_headers, include_trailers;
+ } testcases[] = {
+ /* This is covered in `:fd_positive_file` */
+#if 0
+ {
+ .include_headers = false,
+ .include_trailers = false
+ },
+#endif
+ {
+ .include_headers = true,
+ .include_trailers = false
+ },
+ {
+ .include_headers = false,
+ .include_trailers = true
+ },
+ {
+ .include_headers = true,
+ .include_trailers = true
+ }
+ };
+ off_t offset;
+ size_t nbytes;
+ int client_sock, error, fd, fd2, i, port, rc, server_sock;
+ pid_t server_pid;
+
+ headers[0].iov_base = "This is a header";
+ headers[0].iov_len = strlen(headers[0].iov_base);
+ trailers[0].iov_base = "This is a trailer";
+ trailers[0].iov_len = strlen(trailers[0].iov_base);
+ offset = 0;
+ nbytes = 0;
+
+ for (i = 0; i < nitems(testcases); i++) {
+ struct sf_hdtr hdtr;
+ char *pattern;
+
+ if (testcases[i].include_headers) {
+ hdtr.headers = headers;
+ hdtr.hdr_cnt = nitems(headers);
+ } else {
+ hdtr.headers = NULL;
+ hdtr.hdr_cnt = 0;
+ }
+
+ if (testcases[i].include_trailers) {
+ hdtr.trailers = trailers;
+ hdtr.trl_cnt = nitems(trailers);
+ } else {
+ hdtr.trailers = NULL;
+ hdtr.trl_cnt = 0;
+ }
+
+ port = generate_random_port(i * __LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ rc = asprintf(&pattern, "%s%s%s",
+ testcases[i].include_headers ? (char *)headers[0].iov_base : "",
+ DETERMINISTIC_PATTERN,
+ testcases[i].include_trailers ? (char *)trailers[0].iov_base : "");
+ ATF_REQUIRE_MSG(rc != -1, "asprintf failed: %s", strerror(errno));
+
+ atf_utils_create_file(SOURCE_FILE ".full", "%s", pattern);
+ atf_utils_create_file(SOURCE_FILE, "%s", DETERMINISTIC_PATTERN);
+
+ fd = open(SOURCE_FILE, O_RDONLY);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ fd2 = open(SOURCE_FILE ".full", O_RDONLY);
+ ATF_REQUIRE_MSG(fd2 != -1, "open failed: %s", strerror(errno));
+
+ server_pid = atf_utils_fork();
+ if (server_pid == 0) {
+ (void)close(client_sock);
+ server_cat(DESTINATION_FILE, server_sock,
+ strlen(pattern));
+ _exit(0);
+ } else
+ (void)close(server_sock);
+
+ error = sendfile(fd, client_sock, offset, nbytes, &hdtr,
+ NULL, SF_FLAGS(0, 0));
+ ATF_CHECK_EQ_MSG(error, 0, "sendfile testcase #%d failed: %s",
+ i, strerror(errno));
+ (void)close(client_sock);
+
+ atf_utils_wait(server_pid, 0, "", "");
+ verify_source_and_dest(DESTINATION_FILE, fd2, offset, nbytes);
+
+ (void)close(fd);
+ (void)close(fd2);
+ free(pattern);
+ pattern = NULL;
+ }
+}
+
+ATF_TC(hdtr_positive_v4);
+ATF_TC_HEAD(hdtr_positive_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify positive hdtr functionality (IPv4)");
+}
+ATF_TC_BODY(hdtr_positive_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ hdtr_positive_test(AF_INET);
+}
+
+ATF_TC(hdtr_positive_v6);
+ATF_TC_HEAD(hdtr_positive_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify positive hdtr functionality (IPv6)");
+}
+ATF_TC_BODY(hdtr_positive_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ hdtr_positive_test(AF_INET);
+}
+
+static void
+hdtr_negative_bad_pointers_test(int domain)
+{
+ int client_sock, error, fd, port, server_sock;
+ struct sf_hdtr *hdtr1, hdtr2, hdtr3;
+
+ port = generate_random_port(__LINE__ + domain);
+
+ hdtr1 = (struct sf_hdtr*)-1;
+
+ memset(&hdtr2, 0, sizeof(hdtr2));
+ hdtr2.hdr_cnt = 1;
+ hdtr2.headers = (struct iovec*)-1;
+
+ memset(&hdtr3, 0, sizeof(hdtr3));
+ hdtr3.trl_cnt = 1;
+ hdtr3.trailers = (struct iovec*)-1;
+
+ fd = open(SOURCE_FILE, O_CREAT|O_RDWR, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ error = sendfile(fd, client_sock, 0, 0, hdtr1, NULL, SF_FLAGS(0, 0));
+ ATF_CHECK_ERRNO(EFAULT, error == -1);
+
+ error = sendfile(fd, client_sock, 0, 0, &hdtr2, NULL, SF_FLAGS(0, 0));
+ ATF_CHECK_ERRNO(EFAULT, error == -1);
+
+ error = sendfile(fd, client_sock, 0, 0, &hdtr3, NULL, SF_FLAGS(0, 0));
+ ATF_CHECK_ERRNO(EFAULT, error == -1);
+
+ (void)close(fd);
+ (void)close(client_sock);
+ (void)close(server_sock);
+}
+
+ATF_TC(hdtr_negative_bad_pointers_v4);
+ATF_TC_HEAD(hdtr_negative_bad_pointers_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that bad pointers for hdtr storage result in EFAULT (IPv4)");
+}
+ATF_TC_BODY(hdtr_negative_bad_pointers_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ hdtr_negative_bad_pointers_test(AF_INET);
+}
+
+ATF_TC(hdtr_negative_bad_pointers_v6);
+ATF_TC_HEAD(hdtr_negative_bad_pointers_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that bad pointers for hdtr storage result in EFAULT (IPv6)");
+}
+ATF_TC_BODY(hdtr_negative_bad_pointers_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ hdtr_negative_bad_pointers_test(AF_INET6);
+}
+
+static void
+offset_negative_value_less_than_zero_test(int domain)
+{
+ int client_sock, error, fd, port, server_sock;
+
+ port = generate_random_port(__LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ fd = open(SOURCE_FILE, O_CREAT|O_RDWR, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ error = sendfile(fd, client_sock, -1, 0, NULL, NULL, SF_FLAGS(0, 0));
+ ATF_REQUIRE_ERRNO(EINVAL, error == -1);
+
+ (void)close(fd);
+ (void)close(client_sock);
+ (void)close(server_sock);
+}
+
+ATF_TC(offset_negative_value_less_than_zero_v4);
+ATF_TC_HEAD(offset_negative_value_less_than_zero_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that a negative offset results in EINVAL (IPv4)");
+}
+ATF_TC_BODY(offset_negative_value_less_than_zero_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ offset_negative_value_less_than_zero_test(AF_INET);
+}
+
+ATF_TC(offset_negative_value_less_than_zero_v6);
+ATF_TC_HEAD(offset_negative_value_less_than_zero_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that a negative offset results in EINVAL (IPv6)");
+}
+ATF_TC_BODY(offset_negative_value_less_than_zero_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ offset_negative_value_less_than_zero_test(AF_INET6);
+}
+
+static void
+sbytes_positive_test(int domain)
+{
+ size_t pattern_size = strlen(DETERMINISTIC_PATTERN);
+ off_t sbytes;
+ int client_sock, error, fd, port, server_sock;
+
+ port = generate_random_port(__LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ atf_utils_create_file(SOURCE_FILE, "%s", DETERMINISTIC_PATTERN);
+ fd = open(SOURCE_FILE, O_RDONLY);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ error = sendfile(fd, client_sock, 0, 0, NULL, &sbytes, SF_FLAGS(0, 0));
+ ATF_CHECK_EQ_MSG(error, 0, "sendfile failed: %s", strerror(errno));
+
+ (void)close(fd);
+ (void)close(client_sock);
+ (void)close(server_sock);
+
+ ATF_CHECK_EQ_MSG(pattern_size, sbytes,
+ "the value returned by sbytes does not match the expected pattern "
+ "size");
+}
+
+ATF_TC(sbytes_positive_v4);
+ATF_TC_HEAD(sbytes_positive_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify positive `sbytes` functionality (IPv4)");
+}
+ATF_TC_BODY(sbytes_positive_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ sbytes_positive_test(AF_INET);
+}
+
+ATF_TC(sbytes_positive_v6);
+ATF_TC_HEAD(sbytes_positive_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify positive `sbytes` functionality (IPv6)");
+}
+ATF_TC_BODY(sbytes_positive_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ sbytes_positive_test(AF_INET6);
+}
+
+static void
+sbytes_negative_test(int domain)
+{
+ off_t *sbytes_p = (off_t*)-1;
+ int client_sock, error, fd, port, server_sock;
+
+ port = generate_random_port(__LINE__ + domain);
+ server_sock = setup_tcp_server(domain, port);
+ client_sock = setup_tcp_client(domain, port);
+
+ atf_utils_create_file(SOURCE_FILE, "%s", DETERMINISTIC_PATTERN);
+ fd = open(SOURCE_FILE, O_RDONLY);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ atf_tc_expect_fail(
+ "bug 232210: EFAULT assert fails because copyout(9) call is not checked");
+
+ error = sendfile(fd, client_sock, 0, 0, NULL, sbytes_p, SF_FLAGS(0, 0));
+ ATF_REQUIRE_ERRNO(EFAULT, error == -1);
+
+ (void)close(fd);
+ (void)close(client_sock);
+ (void)close(server_sock);
+}
+
+ATF_TC(sbytes_negative_v4);
+ATF_TC_HEAD(sbytes_negative_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify negative `sbytes` functionality (IPv4)");
+}
+ATF_TC_BODY(sbytes_negative_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ sbytes_negative_test(AF_INET);
+}
+
+ATF_TC(sbytes_negative_v6);
+ATF_TC_HEAD(sbytes_negative_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify negative `sbytes` functionality (IPv6)");
+}
+ATF_TC_BODY(sbytes_negative_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ sbytes_negative_test(AF_INET6);
+}
+
+static void
+s_negative_not_connected_socket_test(int domain)
+{
+ int client_sock, error, fd, port;
+
+ port = generate_random_port(__LINE__ + domain);
+ client_sock = setup_tcp_server(domain, port);
+
+ fd = open(SOURCE_FILE, O_CREAT|O_RDWR, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ error = sendfile(fd, client_sock, 0, 0, NULL, NULL, SF_FLAGS(0, 0));
+ ATF_REQUIRE_ERRNO(ENOTCONN, error == -1);
+
+ (void)close(fd);
+ (void)close(client_sock);
+}
+
+ATF_TC(s_negative_not_connected_socket_v4);
+ATF_TC_HEAD(s_negative_not_connected_socket_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that a non-connected SOCK_STREAM socket results in ENOTCONN (IPv4)");
+}
+
+ATF_TC_BODY(s_negative_not_connected_socket_v4, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ s_negative_not_connected_socket_test(AF_INET);
+}
+
+ATF_TC(s_negative_not_connected_socket_v6);
+ATF_TC_HEAD(s_negative_not_connected_socket_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that a non-connected SOCK_STREAM socket results in ENOTCONN (IPv6)");
+}
+
+ATF_TC_BODY(s_negative_not_connected_socket_v6, tc)
+{
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ s_negative_not_connected_socket_test(AF_INET6);
+}
+
+ATF_TC(s_negative_not_descriptor);
+ATF_TC_HEAD(s_negative_not_descriptor, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that an invalid file descriptor, e.g., -1, fails with EBADF");
+}
+
+ATF_TC_BODY(s_negative_not_descriptor, tc)
+{
+ int client_sock, error, fd;
+
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ client_sock = -1;
+
+ fd = open(SOURCE_FILE, O_CREAT|O_RDWR, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ error = sendfile(fd, client_sock, 0, 0, NULL, NULL, SF_FLAGS(0, 0));
+ ATF_REQUIRE_ERRNO(EBADF, error == -1);
+
+ (void)close(fd);
+}
+
+ATF_TC(s_negative_not_socket_file_descriptor);
+ATF_TC_HEAD(s_negative_not_socket_file_descriptor, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that a non-socket file descriptor fails with ENOTSOCK");
+}
+
+ATF_TC_BODY(s_negative_not_socket_file_descriptor, tc)
+{
+ int client_sock, error, fd;
+
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ fd = open(SOURCE_FILE, O_CREAT|O_RDWR, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ client_sock = open(_PATH_DEVNULL, O_WRONLY);
+ ATF_REQUIRE_MSG(client_sock != -1, "open failed: %s", strerror(errno));
+
+ error = sendfile(fd, client_sock, 0, 0, NULL, NULL, SF_FLAGS(0, 0));
+ ATF_REQUIRE_ERRNO(ENOTSOCK, error == -1);
+
+ (void)close(fd);
+ (void)close(client_sock);
+}
+
+static void
+s_negative_udp_socket_test(int domain)
+{
+ int client_sock, error, fd, port;
+
+ port = generate_random_port(__LINE__ + domain);
+ client_sock = setup_client(domain, SOCK_DGRAM, port);
+
+ fd = open(SOURCE_FILE, O_CREAT|O_RDWR, 0600);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+
+ error = sendfile(fd, client_sock, 0, 0, NULL, NULL, SF_FLAGS(0, 0));
+ ATF_REQUIRE_ERRNO(EINVAL, error == -1);
+
+ (void)close(fd);
+ (void)close(client_sock);
+}
+
+ATF_TC(s_negative_udp_socket_v4);
+ATF_TC_HEAD(s_negative_udp_socket_v4, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that a non-SOCK_STREAM type socket results in EINVAL (IPv4)");
+}
+ATF_TC_BODY(s_negative_udp_socket_v4, tc)
+{
+
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ s_negative_udp_socket_test(AF_INET);
+}
+
+ATF_TC(s_negative_udp_socket_v6);
+ATF_TC_HEAD(s_negative_udp_socket_v6, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr",
+ "Verify that a non-SOCK_STREAM type socket results in EINVAL (IPv6)");
+}
+ATF_TC_BODY(s_negative_udp_socket_v6, tc)
+{
+
+ if (atf_tc_get_config_var_as_bool_wd(tc, "qemu", false))
+ atf_tc_skip("Sendfile(4) unimplemented. https://github.com/qemu-bsd-user/qemu-bsd-user/issues/25");
+
+ s_negative_udp_socket_test(AF_INET6);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, fd_positive_file_v4);
+ ATF_TP_ADD_TC(tp, fd_positive_file_v6);
+ ATF_TP_ADD_TC(tp, fd_positive_shm_v4);
+ ATF_TP_ADD_TC(tp, fd_positive_shm_v6);
+ ATF_TP_ADD_TC(tp, fd_negative_bad_fd_v4);
+ ATF_TP_ADD_TC(tp, fd_negative_bad_fd_v6);
+ ATF_TP_ADD_TC(tp, flags_v4);
+ ATF_TP_ADD_TC(tp, flags_v6);
+ /*
+ * TODO: the negative case for SF_NODISKIO (returns EBUSY if file in
+ * use) is not covered yet.
+ *
+ * Need to lock a file in a subprocess in write mode, then try and
+ * send the data in read mode with sendfile.
+ *
+ * This should work with FFS/UFS, but there are no guarantees about
+ * other filesystem implementations of sendfile(2), e.g., ZFS.
+ */
+ ATF_TP_ADD_TC(tp, hdtr_positive_v4);
+ ATF_TP_ADD_TC(tp, hdtr_positive_v6);
+ ATF_TP_ADD_TC(tp, hdtr_negative_bad_pointers_v4);
+ ATF_TP_ADD_TC(tp, hdtr_negative_bad_pointers_v6);
+ ATF_TP_ADD_TC(tp, offset_negative_value_less_than_zero_v4);
+ ATF_TP_ADD_TC(tp, offset_negative_value_less_than_zero_v6);
+ ATF_TP_ADD_TC(tp, sbytes_positive_v4);
+ ATF_TP_ADD_TC(tp, sbytes_positive_v6);
+ ATF_TP_ADD_TC(tp, sbytes_negative_v4);
+ ATF_TP_ADD_TC(tp, sbytes_negative_v6);
+ ATF_TP_ADD_TC(tp, s_negative_not_connected_socket_v4);
+ ATF_TP_ADD_TC(tp, s_negative_not_connected_socket_v6);
+ ATF_TP_ADD_TC(tp, s_negative_not_descriptor);
+ ATF_TP_ADD_TC(tp, s_negative_not_socket_file_descriptor);
+ ATF_TP_ADD_TC(tp, s_negative_udp_socket_v4);
+ ATF_TP_ADD_TC(tp, s_negative_udp_socket_v6);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/sys/swapcontext_test.c b/lib/libc/tests/sys/swapcontext_test.c
new file mode 100644
index 000000000000..f341a746e515
--- /dev/null
+++ b/lib/libc/tests/sys/swapcontext_test.c
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2025 Raptor Computing Systems, LLC
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ucontext.h>
+#include <errno.h>
+
+#include <atf-c.h>
+
+#define STACK_SIZE (64ull << 10)
+
+static volatile int callback_reached = 0;
+
+static ucontext_t uctx_save, uctx_switch;
+
+static void swapcontext_callback()
+{
+ // Increment callback reached variable
+ // If this is called multiple times, we will fail the test
+ // If this is not called at all, we will fail the test
+ callback_reached++;
+}
+
+ATF_TC(swapcontext_basic);
+ATF_TC_HEAD(swapcontext_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Verify basic functionality of swapcontext");
+}
+
+ATF_TC_BODY(swapcontext_basic, tc)
+{
+ char *stack;
+ int res;
+
+ stack = malloc(STACK_SIZE);
+ ATF_REQUIRE_MSG(stack != NULL, "malloc failed: %s", strerror(errno));
+ res = getcontext(&uctx_switch);
+ ATF_REQUIRE_MSG(res == 0, "getcontext failed: %s", strerror(errno));
+
+ uctx_switch.uc_stack.ss_sp = stack;
+ uctx_switch.uc_stack.ss_size = STACK_SIZE;
+ uctx_switch.uc_link = &uctx_save;
+ makecontext(&uctx_switch, swapcontext_callback, 0);
+
+ res = swapcontext(&uctx_save, &uctx_switch);
+
+ ATF_REQUIRE_MSG(res == 0, "swapcontext failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(callback_reached == 1,
+ "callback failed, reached %d times", callback_reached);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, swapcontext_basic);
+
+ return (atf_no_error());
+}
+