aboutsummaryrefslogtreecommitdiff
path: root/tools/regression/sockets/sendfile/sendfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/regression/sockets/sendfile/sendfile.c')
-rw-r--r--tools/regression/sockets/sendfile/sendfile.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/tools/regression/sockets/sendfile/sendfile.c b/tools/regression/sockets/sendfile/sendfile.c
new file mode 100644
index 000000000000..40af5a67982e
--- /dev/null
+++ b/tools/regression/sockets/sendfile/sendfile.c
@@ -0,0 +1,481 @@
+/*-
+ * Copyright (c) 2006 Robert N. M. Watson
+ * 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/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <md5.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Simple regression test for sendfile. Creates a file sized at four pages
+ * and then proceeds to send it over a series of sockets, exercising a number
+ * of cases and performing limited validation.
+ */
+
+#define FAIL(msg) {printf("# %s\n", msg); \
+ return (-1);}
+
+#define FAIL_ERR(msg) {printf("# %s: %s\n", msg, strerror(errno)); \
+ return (-1);}
+
+#define TEST_PORT 5678
+#define TEST_MAGIC 0x4440f7bb
+#define TEST_PAGES 4
+#define TEST_SECONDS 30
+
+struct test_header {
+ uint32_t th_magic;
+ uint32_t th_header_length;
+ uint32_t th_offset;
+ uint32_t th_length;
+ char th_md5[33];
+};
+
+struct sendfile_test {
+ uint32_t hdr_length;
+ uint32_t offset;
+ uint32_t length;
+ uint32_t file_size;
+};
+
+static int file_fd;
+static char path[PATH_MAX];
+static int listen_socket;
+static int accept_socket;
+
+static int test_th(struct test_header *th, uint32_t *header_length,
+ uint32_t *offset, uint32_t *length);
+static void signal_alarm(int signum);
+static void setup_alarm(int seconds);
+static void cancel_alarm(void);
+static int receive_test(void);
+static void run_child(void);
+static int new_test_socket(int *connect_socket);
+static void init_th(struct test_header *th, uint32_t header_length,
+ uint32_t offset, uint32_t length);
+static int send_test(int connect_socket, struct sendfile_test);
+static int write_test_file(size_t file_size);
+static void run_parent(void);
+static void cleanup(void);
+
+
+static int
+test_th(struct test_header *th, uint32_t *header_length, uint32_t *offset,
+ uint32_t *length)
+{
+
+ if (th->th_magic != htonl(TEST_MAGIC))
+ FAIL("magic number not found in header")
+ *header_length = ntohl(th->th_header_length);
+ *offset = ntohl(th->th_offset);
+ *length = ntohl(th->th_length);
+ return (0);
+}
+
+static void
+signal_alarm(int signum)
+{
+ (void)signum;
+
+ printf("# test timeout\n");
+
+ if (accept_socket > 0)
+ close(accept_socket);
+ if (listen_socket > 0)
+ close(listen_socket);
+
+ _exit(-1);
+}
+
+static void
+setup_alarm(int seconds)
+{
+ struct itimerval itv;
+ bzero(&itv, sizeof(itv));
+ (void)seconds;
+ itv.it_value.tv_sec = seconds;
+
+ signal(SIGALRM, signal_alarm);
+ setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+static void
+cancel_alarm(void)
+{
+ struct itimerval itv;
+ bzero(&itv, sizeof(itv));
+ setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+static int
+receive_test(void)
+{
+ uint32_t header_length, offset, length, counter;
+ struct test_header th;
+ ssize_t len;
+ char buf[10240];
+ MD5_CTX md5ctx;
+ char *rxmd5;
+
+ len = read(accept_socket, &th, sizeof(th));
+ if (len < 0 || (size_t)len < sizeof(th))
+ FAIL_ERR("read")
+
+ if (test_th(&th, &header_length, &offset, &length) != 0)
+ return (-1);
+
+ MD5Init(&md5ctx);
+
+ counter = 0;
+ while (1) {
+ len = read(accept_socket, buf, sizeof(buf));
+ if (len < 0 || len == 0)
+ break;
+ counter += len;
+ MD5Update(&md5ctx, buf, len);
+ }
+
+ rxmd5 = MD5End(&md5ctx, NULL);
+
+ if ((counter != header_length+length) ||
+ memcmp(th.th_md5, rxmd5, 33) != 0)
+ FAIL("receive length mismatch")
+
+ free(rxmd5);
+ return (0);
+}
+
+static void
+run_child(void)
+{
+ struct sockaddr_in sin;
+ int rc = 0;
+
+ listen_socket = socket(PF_INET, SOCK_STREAM, 0);
+ if (listen_socket < 0) {
+ printf("# socket: %s\n", strerror(errno));
+ rc = -1;
+ }
+
+ if (!rc) {
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(TEST_PORT);
+
+ if (bind(listen_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ printf("# bind: %s\n", strerror(errno));
+ rc = -1;
+ }
+ }
+
+ if (!rc && listen(listen_socket, -1) < 0) {
+ printf("# listen: %s\n", strerror(errno));
+ rc = -1;
+ }
+
+ if (!rc) {
+ accept_socket = accept(listen_socket, NULL, NULL);
+ setup_alarm(TEST_SECONDS);
+ if (receive_test() != 0)
+ rc = -1;
+ }
+
+ cancel_alarm();
+ if (accept_socket > 0)
+ close(accept_socket);
+ if (listen_socket > 0)
+ close(listen_socket);
+
+ _exit(rc);
+}
+
+static int
+new_test_socket(int *connect_socket)
+{
+ struct sockaddr_in sin;
+ int rc = 0;
+
+ *connect_socket = socket(PF_INET, SOCK_STREAM, 0);
+ if (*connect_socket < 0)
+ FAIL_ERR("socket")
+
+ bzero(&sin, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(TEST_PORT);
+
+ if (connect(*connect_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ FAIL_ERR("connect")
+
+ return (rc);
+}
+
+static void
+init_th(struct test_header *th, uint32_t header_length, uint32_t offset,
+ uint32_t length)
+{
+ bzero(th, sizeof(*th));
+ th->th_magic = htonl(TEST_MAGIC);
+ th->th_header_length = htonl(header_length);
+ th->th_offset = htonl(offset);
+ th->th_length = htonl(length);
+
+ MD5FileChunk(path, th->th_md5, offset, length);
+}
+
+static int
+send_test(int connect_socket, struct sendfile_test test)
+{
+ struct test_header th;
+ struct sf_hdtr hdtr, *hdtrp;
+ struct iovec headers;
+ char *header;
+ ssize_t len;
+ int length;
+ off_t off;
+
+ len = lseek(file_fd, 0, SEEK_SET);
+ if (len != 0)
+ FAIL_ERR("lseek")
+
+ struct stat st;
+ if (fstat(file_fd, &st) < 0)
+ FAIL_ERR("fstat")
+ length = st.st_size - test.offset;
+ if (test.length > 0 && test.length < (uint32_t)length)
+ length = test.length;
+
+ init_th(&th, test.hdr_length, test.offset, length);
+
+ len = write(connect_socket, &th, sizeof(th));
+ if (len != sizeof(th))
+ return (-1);
+
+ if (test.hdr_length != 0) {
+ header = malloc(test.hdr_length);
+ if (header == NULL)
+ FAIL_ERR("malloc")
+
+ hdtrp = &hdtr;
+ bzero(&headers, sizeof(headers));
+ headers.iov_base = header;
+ headers.iov_len = test.hdr_length;
+ bzero(&hdtr, sizeof(hdtr));
+ hdtr.headers = &headers;
+ hdtr.hdr_cnt = 1;
+ hdtr.trailers = NULL;
+ hdtr.trl_cnt = 0;
+ } else {
+ hdtrp = NULL;
+ header = NULL;
+ }
+
+ if (sendfile(file_fd, connect_socket, test.offset, test.length,
+ hdtrp, &off, 0) < 0) {
+ if (header != NULL)
+ free(header);
+ FAIL_ERR("sendfile")
+ }
+
+ if (length == 0) {
+ struct stat sb;
+
+ if (fstat(file_fd, &sb) == 0)
+ length = sb.st_size - test.offset;
+ }
+
+ if (header != NULL)
+ free(header);
+
+ if (off != length)
+ FAIL("offset != length")
+
+ return (0);
+}
+
+static int
+write_test_file(size_t file_size)
+{
+ char *page_buffer;
+ ssize_t len;
+ static size_t current_file_size = 0;
+
+ if (file_size == current_file_size)
+ return (0);
+ else if (file_size < current_file_size) {
+ if (ftruncate(file_fd, file_size) != 0)
+ FAIL_ERR("ftruncate");
+ current_file_size = file_size;
+ return (0);
+ }
+
+ page_buffer = malloc(file_size);
+ if (page_buffer == NULL)
+ FAIL_ERR("malloc")
+ bzero(page_buffer, file_size);
+
+ len = write(file_fd, page_buffer, file_size);
+ if (len < 0)
+ FAIL_ERR("write")
+
+ len = lseek(file_fd, 0, SEEK_SET);
+ if (len < 0)
+ FAIL_ERR("lseek")
+ if (len != 0)
+ FAIL("len != 0")
+
+ free(page_buffer);
+ current_file_size = file_size;
+ return (0);
+}
+
+static void
+run_parent(void)
+{
+ int connect_socket;
+ int status;
+ int test_num;
+ int test_count;
+ int pid;
+ size_t desired_file_size = 0;
+
+ const int pagesize = getpagesize();
+
+ struct sendfile_test tests[] = {
+ { .hdr_length = 0, .offset = 0, .length = 1 },
+ { .hdr_length = 0, .offset = 0, .length = pagesize },
+ { .hdr_length = 0, .offset = 1, .length = 1 },
+ { .hdr_length = 0, .offset = 1, .length = pagesize },
+ { .hdr_length = 0, .offset = pagesize, .length = pagesize },
+ { .hdr_length = 0, .offset = 0, .length = 2*pagesize },
+ { .hdr_length = 0, .offset = 0, .length = 0 },
+ { .hdr_length = 0, .offset = pagesize, .length = 0 },
+ { .hdr_length = 0, .offset = 2*pagesize, .length = 0 },
+ { .hdr_length = 0, .offset = TEST_PAGES*pagesize, .length = 0 },
+ { .hdr_length = 0, .offset = 0, .length = pagesize,
+ .file_size = 1 }
+ };
+
+ test_count = sizeof(tests) / sizeof(tests[0]);
+ printf("1..%d\n", test_count);
+
+ for (test_num = 1; test_num <= test_count; test_num++) {
+
+ desired_file_size = tests[test_num - 1].file_size;
+ if (desired_file_size == 0)
+ desired_file_size = TEST_PAGES * pagesize;
+ if (write_test_file(desired_file_size) != 0) {
+ printf("not ok %d\n", test_num);
+ continue;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ printf("not ok %d\n", test_num);
+ continue;
+ }
+
+ if (pid == 0)
+ run_child();
+
+ usleep(250000);
+
+ if (new_test_socket(&connect_socket) != 0) {
+ printf("not ok %d\n", test_num);
+ kill(pid, SIGALRM);
+ close(connect_socket);
+ continue;
+ }
+
+ if (send_test(connect_socket, tests[test_num-1]) != 0) {
+ printf("not ok %d\n", test_num);
+ kill(pid, SIGALRM);
+ close(connect_socket);
+ continue;
+ }
+
+ close(connect_socket);
+ if (waitpid(pid, &status, 0) == pid) {
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ printf("%s %d\n", "ok", test_num);
+ else
+ printf("%s %d\n", "not ok", test_num);
+ }
+ else {
+ printf("not ok %d\n", test_num);
+ }
+ }
+}
+
+static void
+cleanup(void)
+{
+
+ unlink(path);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ path[0] = '\0';
+
+ if (argc == 1) {
+ snprintf(path, sizeof(path), "sendfile.XXXXXXXXXXXX");
+ file_fd = mkstemp(path);
+ if (file_fd == -1)
+ FAIL_ERR("mkstemp");
+ } else if (argc == 2) {
+ (void)strlcpy(path, argv[1], sizeof(path));
+ file_fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600);
+ if (file_fd == -1)
+ FAIL_ERR("open");
+ } else {
+ FAIL("usage: sendfile [path]");
+ }
+
+ atexit(cleanup);
+
+ run_parent();
+ return (0);
+}