aboutsummaryrefslogtreecommitdiff
path: root/lib/libc/tests/sys/sendfile_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/tests/sys/sendfile_test.c')
-rw-r--r--lib/libc/tests/sys/sendfile_test.c1208
1 files changed, 1208 insertions, 0 deletions
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());
+}