opnsense-src/lib/libc/tests/sys/sendfile_test.c
Enji Cooper c90e23db39 Make server_cat(..) handle short receives
In short, the prior code was far too simplistic when it came to calling recv(2)
and failed intermittently (or in the case of Jenkins, deterministically).

Handle short recv(2)s by checking the return code and incrementing the window
into the buffer by the number of received bytes. If the number of received
bytes <= 0, then bail out of the loop, and test the total number of received
bytes vs the expected number of bytes sent for equality, and base whether or
not the test passes/fails on that fact.

Remove the expected failure, now that the hdtr testcases deterministically pass
on my host after this change [1].

PR:		234809 [1], 235200
Reviewed by:	asomers
Approved by:	emaste (mentor)
MFC after:	1 week
Differential Revision: https://reviews.freebsd.org/D19188
2019-02-19 22:19:31 +00:00

1155 lines
29 KiB
C

/*-
* 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)
{
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());
}