svn commit: r343362 - head/lib/libc/tests/sys

Enji Cooper ngie at FreeBSD.org
Wed Jan 23 22:00:24 UTC 2019


Author: ngie
Date: Wed Jan 23 22:00:17 2019
New Revision: 343362
URL: https://svnweb.freebsd.org/changeset/base/343362

Log:
  Add [initial] functional tests for sendfile(2) as lib/libc/sys/sendfile
  
  These testcases exercise a number of functional requirements for sendfile(2).
  
  The testcases use IPv4 and IPv6 domain sockets with TCP, and were confirmed
  functional on UFS and ZFS. UDP address family sockets cannot be used per the
  sendfile(2) contract, thus using UDP sockets is outside the scope of
  testing the syscall in positive cases. As seen in
  `:s_negative_udp_socket_test`, UDP is used to test the sendfile(2) contract
  to ensure that EINVAL is returned by sendfile(2).
  
  The testcases added explicitly avoid testing out `SF_SYNC` due to the
  complexity of verifying that support. However, this is a good next logical
  item to verify.
  
  The `hdtr_positive*` testcases work to a certain degree (the header
  testcases pass), but the trailer testcases do not work (it is an expected
  failure). In particular, the value received by the mock server doesn't match
  the expected value, and instead looks something like the following (using
  python array notation):
  
  `trailer[:]message[1:]`
  
  instead of:
  
  `message[:]trailer[:]`
  
  This makes me think there's a buffer overrun issue or problem with the
  offset somewhere in the sendfile(2) system call, but I need to do some
  other testing first to verify that the code is indeed sane, and my
  assumptions/code isn't buggy.
  
  The `sbytes_negative` testcases that check `sbytes` being set to an
  invalid value resulting in `EFAULT` fails today as the other change
  (which checks `copyout(9)`) has not been committed [1]. Thus, it
  should remain an expected failure (see bug 232210 for more details
  on this item).
  
  Next steps for testing sendfile(2):
  1. Fix the header/trailer testcases so that they pass.
  2. Setup if_tap interface and test with it, instead of using "localhost", per
     @asomers's suggestion.
  3. Handle short recv(2)'s in `server_cat(..)`.
  4. Add `SF_SYNC` support.
  5. Add some more negative tests outside the scope of the functional contract.
  
  MFC after:	1 month
  Reviewed by:	asomers
  Approved by:	emaste (mentor)
  PR: 		232210
  Sponsored by:   Netflix, Inc
  Differential Revision: https://reviews.freebsd.org/D18625

Added:
  head/lib/libc/tests/sys/sendfile_test.c   (contents, props changed)
Modified:
  head/lib/libc/tests/sys/Makefile

Modified: head/lib/libc/tests/sys/Makefile
==============================================================================
--- head/lib/libc/tests/sys/Makefile	Wed Jan 23 20:49:14 2019	(r343361)
+++ head/lib/libc/tests/sys/Makefile	Wed Jan 23 22:00:17 2019	(r343362)
@@ -8,6 +8,7 @@ PACKAGE=			tests
 ATF_TESTS_C+=			brk_test
 .endif
 ATF_TESTS_C+=			queue_test
+ATF_TESTS_C+=			sendfile_test
 
 # TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg,
 # swapcontext

Added: head/lib/libc/tests/sys/sendfile_test.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/lib/libc/tests/sys/sendfile_test.c	Wed Jan 23 22:00:17 2019	(r343362)
@@ -0,0 +1,1146 @@
+/*-
+ * 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#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;
+
+	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)
+{
+	char *serv;
+	struct addrinfo hints;
+	int error;
+
+	ATF_REQUIRE_MSG(domain == AF_INET || domain == AF_INET6,
+	    "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;
+	hints.ai_socktype = type;
+
+	error = getaddrinfo("localhost", serv, &hints, res);
+	ATF_REQUIRE_EQ_MSG(error, 0,
+	    "getaddrinfo failed: %s", gai_strerror(errno));
+	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);
+	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);
+	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)
+{
+	void *buffer;
+	int recv_sock;
+	ssize_t received_bytes;
+
+	buffer = calloc(len + 1, sizeof(char));
+	if (buffer == NULL)
+		err(1, "malloc failed");
+
+	recv_sock = accept(server_sock, NULL, 0);
+	if (recv_sock == -1)
+		err(1, "accept failed");
+
+	/*
+	 * XXX: this assumes the simplest case where all data is received in a
+	 * single recv(2) call.
+	 */
+	if (recv(recv_sock, buffer, len, 0) == -1)
+		err(1, "recv failed");
+
+	atf_utils_create_file(dest_filename, "%s", buffer);
+
+	/*
+	 * This recv(2) call helps ensure the amount of sent data is exactly
+	 * what was specified by `len`.
+	 */
+	received_bytes = recv(recv_sock, buffer, len, 0);
+	switch (received_bytes) {
+	case -1:
+		err(1, "recv failed");
+	case 0:
+		break;
+	default:
+		errx(1, "received unexpected data: %s", buffer);
+	}
+
+	(void)close(recv_sock);
+	(void)close(server_sock);
+	free(buffer);
+}
+
+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)
+{
+	void *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 (%ju)", 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)
+{
+
+	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)
+{
+
+	fd_positive_file_test(AF_INET6);
+}
+
+static void
+fd_positive_shm_test(int domain)
+{
+	void *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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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;
+
+	atf_tc_expect_fail(
+	    "The header/trailer testcases fail today with a data mismatch; "
+	    "bug # 234809");
+
+	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 ? headers[0].iov_base : "",
+		    DETERMINISTIC_PATTERN,
+		    testcases[i].include_trailers ? 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)
+{
+
+	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)
+{
+
+	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);
+	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)
+{
+
+	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)
+{
+
+	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);
+	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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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)
+{
+
+	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);
+	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);

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-all mailing list