Index: stable/11/lib/libc/tests/sys/Makefile =================================================================== --- stable/11/lib/libc/tests/sys/Makefile +++ stable/11/lib/libc/tests/sys/Makefile @@ -5,6 +5,7 @@ .include ATF_TESTS_C+= queue_test +ATF_TESTS_C+= sendfile_test # TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg, # swapcontext Index: stable/11/lib/libc/tests/sys/sendfile_test.c =================================================================== --- stable/11/lib/libc/tests/sys/sendfile_test.c +++ stable/11/lib/libc/tests/sys/sendfile_test.c @@ -0,0 +1,1155 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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) +{ + 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) +{ + + 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) +{ + 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) +{ + + 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; + + 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) +{ + + 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); +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) +{ + + 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) +{ + + 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; + + client_sock = -1; + + 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(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; + + fd = open(SOURCE_FILE, O_CREAT|O_RDWR); + ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno)); + + client_sock = open(_PATH_DEVNULL, O_WRONLY); + 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(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); + 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) +{ + + 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) +{ + + 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()); +}