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 | 825 | ||||
| -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, 2750 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..c8ad225fadfc --- /dev/null +++ b/lib/libc/tests/sys/cpuset_test.c @@ -0,0 +1,825 @@ +/*- + * 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 <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.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); +} + +static void +skip_ltncpu_root(int ncpu, cpuset_t *mask) +{ + +	CPU_ZERO(mask); +	ATF_REQUIRE_EQ(0, cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, +	    -1, sizeof(*mask), mask)); +	if (CPU_COUNT(mask) < ncpu) { +		atf_tc_skip("Test requires cpuset root with %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 +create_jail(void) +{ +	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; + +	error = jail_set(iov, 2, JAIL_CREATE | JAIL_ATTACH); +	free(name); +	if (error < 0) +		return (FAILURE_JAIL); +	return (0); +} + +static int +do_jail(int sock) +{ +	struct jail_test_info info; +	int error; + +	error = create_jail(); +	if (error != 0) +		return (error); + +	/* 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); +} + +struct nproc_info { +	long		nproc_init; +	long		nproc_final; +	long		nproc_global; +}; + +ATF_TC(jail_nproc); +ATF_TC_HEAD(jail_nproc, tc) +{ +	atf_tc_set_md_var(tc, "descr", +	    "Test that _SC_PROCESSORS_ONLN reflects jail cpuset constraints"); +} +ATF_TC_BODY(jail_nproc, tc) +{ +	cpuset_t jmask; +	struct nproc_info ninfo = { }; +	int sockpair[2]; +	cpusetid_t setid; +	ssize_t readsz; +	pid_t pid; +	int fcpu, error, pfd, sock; +	char okb = 0x7f, rcvb; + +	skip_ltncpu_root(2, &jmask); +	fcpu = CPU_FFS(&jmask) - 1; + +	/* +	 * Just adjusting our affinity should not affect the number of +	 * processors considered online- we want to be sure that it's only +	 * adjusted if our jail's root set is. +	 */ +	CPU_CLR(fcpu, &jmask); +	error = cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, +	    sizeof(jmask), &jmask); +	ATF_REQUIRE_EQ(0, error); +	ATF_REQUIRE(sysconf(_SC_NPROCESSORS_ONLN) > CPU_COUNT(&jmask)); + +	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]); + +		error = create_jail(); +		if (error != 0) +			_exit(error); + +		ninfo.nproc_init = sysconf(_SC_NPROCESSORS_ONLN); + +		/* Signal the parent that we're jailed. */ +		readsz = write(sock, &okb, sizeof(okb)); +		assert(readsz == sizeof(okb)); + +		/* Wait for parent to adjust our mask and signal OK. */ +		readsz = read(sock, &rcvb, sizeof(rcvb)); +		assert(readsz == sizeof(rcvb)); +		assert(rcvb == okb); + +		ninfo.nproc_final = sysconf(_SC_NPROCESSORS_ONLN); +		ninfo.nproc_global = sysconf(_SC_NPROCESSORS_CONF); +		readsz = write(sock, &ninfo, sizeof(ninfo)); +		assert(readsz == sizeof(ninfo)); + +		_exit(0); +	} + +	close(sockpair[SP_CHILD]); +	sock = sockpair[SP_PARENT]; + +	/* Wait for signal that they are jailed. */ +	readsz = read(sock, &rcvb, sizeof(rcvb)); +	assert(readsz == sizeof(rcvb)); +	assert(rcvb == okb); + +	/* Grab the cpuset id and adjust it. */ +	error = cpuset_getid(CPU_LEVEL_ROOT, CPU_WHICH_PID, pid, &setid); +	ATF_REQUIRE_EQ(0, error); +	error = cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_CPUSET, +	    setid, sizeof(jmask), &jmask); +	ATF_REQUIRE_EQ(0, error); + +	/* Signal OK to proceed. */ +	readsz = write(sock, &okb, sizeof(okb)); +	ATF_REQUIRE_EQ(sizeof(okb), readsz); + +	/* Grab our final nproc info. */ +	readsz = read(sock, &ninfo, sizeof(ninfo)); +	ATF_REQUIRE_EQ(sizeof(ninfo), readsz); + +	/* +	 * We set our own affinity to jmask, which is derived from *our* root +	 * set, at the beginning of the test.  The jail would inherit from this +	 * set, so we just re-use that mask here to confirm that +	 * _SC_NPROCESSORS_ONLN did actually drop in response to us limiting the +	 * jail, and that its _SC_NPROCESSORS_CONF did not. +	 */ +	ATF_REQUIRE_EQ(CPU_COUNT(&jmask) + 1, ninfo.nproc_init); +	ATF_REQUIRE_EQ(CPU_COUNT(&jmask) + 1, ninfo.nproc_global); +	ATF_REQUIRE_EQ(CPU_COUNT(&jmask), ninfo.nproc_final); +} + +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, jail_nproc); +	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()); +} + | 
