diff options
Diffstat (limited to 'lib/libc/tests/sys')
-rw-r--r-- | lib/libc/tests/sys/Makefile | 105 | ||||
-rw-r--r-- | lib/libc/tests/sys/Makefile.depend | 22 | ||||
-rw-r--r-- | lib/libc/tests/sys/brk_test.c | 146 | ||||
-rw-r--r-- | lib/libc/tests/sys/cpuset_test.c | 691 | ||||
-rw-r--r-- | lib/libc/tests/sys/errno_test.c | 36 | ||||
-rw-r--r-- | lib/libc/tests/sys/mlock_helper.c | 111 | ||||
-rw-r--r-- | lib/libc/tests/sys/queue_test.c | 234 | ||||
-rw-r--r-- | lib/libc/tests/sys/sendfile_test.c | 1208 | ||||
-rw-r--r-- | lib/libc/tests/sys/swapcontext_test.c | 63 |
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()); +} + |