1158910Srwatson/*- 2158910Srwatson * Copyright (c) 2006 Robert N. M. Watson 3158910Srwatson * All rights reserved. 4158910Srwatson * 5158910Srwatson * Redistribution and use in source and binary forms, with or without 6158910Srwatson * modification, are permitted provided that the following conditions 7158910Srwatson * are met: 8158910Srwatson * 1. Redistributions of source code must retain the above copyright 9158910Srwatson * notice, this list of conditions and the following disclaimer. 10158910Srwatson * 2. Redistributions in binary form must reproduce the above copyright 11158910Srwatson * notice, this list of conditions and the following disclaimer in the 12158910Srwatson * documentation and/or other materials provided with the distribution. 13158910Srwatson * 14158910Srwatson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15158910Srwatson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16158910Srwatson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17158910Srwatson * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18158910Srwatson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19158910Srwatson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20158910Srwatson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21158910Srwatson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22158910Srwatson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23158910Srwatson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24158910Srwatson * SUCH DAMAGE. 25158910Srwatson * 26158910Srwatson * $FreeBSD$ 27158910Srwatson */ 28158910Srwatson 29158910Srwatson#include <sys/types.h> 30158910Srwatson#include <sys/socket.h> 31168854Spjd#include <sys/stat.h> 32204294Sbrucec#include <sys/wait.h> 33158910Srwatson 34158910Srwatson#include <netinet/in.h> 35158910Srwatson 36158910Srwatson#include <err.h> 37204294Sbrucec#include <errno.h> 38216379Spjd#include <fcntl.h> 39158910Srwatson#include <limits.h> 40204294Sbrucec#include <md5.h> 41158910Srwatson#include <signal.h> 42168855Sdes#include <stdint.h> 43158910Srwatson#include <stdio.h> 44158910Srwatson#include <stdlib.h> 45158910Srwatson#include <string.h> 46158910Srwatson#include <unistd.h> 47158910Srwatson 48158910Srwatson/* 49182903Smaxim * Simple regression test for sendfile. Creates a file sized at four pages 50158910Srwatson * and then proceeds to send it over a series of sockets, exercising a number 51158910Srwatson * of cases and performing limited validation. 52158910Srwatson */ 53158910Srwatson 54204294Sbrucec#define FAIL(msg) {printf("# %s\n", msg); \ 55204294Sbrucec return (-1);} 56204294Sbrucec 57204294Sbrucec#define FAIL_ERR(msg) {printf("# %s: %s\n", msg, strerror(errno)); \ 58204294Sbrucec return (-1);} 59204294Sbrucec 60158910Srwatson#define TEST_PORT 5678 61158910Srwatson#define TEST_MAGIC 0x4440f7bb 62168854Spjd#define TEST_PAGES 4 63158910Srwatson#define TEST_SECONDS 30 64158910Srwatson 65158910Srwatsonstruct test_header { 66204294Sbrucec uint32_t th_magic; 67204294Sbrucec uint32_t th_header_length; 68204294Sbrucec uint32_t th_offset; 69204294Sbrucec uint32_t th_length; 70204294Sbrucec char th_md5[33]; 71158910Srwatson}; 72158910Srwatson 73204294Sbrucecstruct sendfile_test { 74204294Sbrucec uint32_t hdr_length; 75204294Sbrucec uint32_t offset; 76204294Sbrucec uint32_t length; 77255451Semaste uint32_t file_size; 78204294Sbrucec}; 79204294Sbrucec 80255451Semastestatic int file_fd; 81255451Semastestatic char path[PATH_MAX]; 82255451Semastestatic int listen_socket; 83255451Semastestatic int accept_socket; 84158910Srwatson 85204294Sbrucecstatic int test_th(struct test_header *th, uint32_t *header_length, 86204294Sbrucec uint32_t *offset, uint32_t *length); 87204294Sbrucecstatic void signal_alarm(int signum); 88204294Sbrucecstatic void setup_alarm(int seconds); 89204294Sbrucecstatic void cancel_alarm(void); 90204294Sbrucecstatic int receive_test(void); 91204294Sbrucecstatic void run_child(void); 92204294Sbrucecstatic int new_test_socket(int *connect_socket); 93204294Sbrucecstatic void init_th(struct test_header *th, uint32_t header_length, 94204294Sbrucec uint32_t offset, uint32_t length); 95204294Sbrucecstatic int send_test(int connect_socket, struct sendfile_test); 96255451Semastestatic int write_test_file(size_t file_size); 97204294Sbrucecstatic void run_parent(void); 98204294Sbrucecstatic void cleanup(void); 99204294Sbrucec 100204294Sbrucec 101158910Srwatsonstatic int 102204294Sbrucectest_th(struct test_header *th, uint32_t *header_length, uint32_t *offset, 103204294Sbrucec uint32_t *length) 104158910Srwatson{ 105158910Srwatson 106158910Srwatson if (th->th_magic != htonl(TEST_MAGIC)) 107204294Sbrucec FAIL("magic number not found in header") 108158910Srwatson *header_length = ntohl(th->th_header_length); 109158910Srwatson *offset = ntohl(th->th_offset); 110158910Srwatson *length = ntohl(th->th_length); 111204294Sbrucec return (0); 112158910Srwatson} 113158910Srwatson 114158910Srwatsonstatic void 115158910Srwatsonsignal_alarm(int signum) 116158910Srwatson{ 117204294Sbrucec (void)signum; 118158910Srwatson 119204294Sbrucec printf("# test timeout\n"); 120204294Sbrucec 121204294Sbrucec if (accept_socket > 0) 122204294Sbrucec close(accept_socket); 123204294Sbrucec if (listen_socket > 0) 124204294Sbrucec close(listen_socket); 125204294Sbrucec 126204294Sbrucec _exit(-1); 127158910Srwatson} 128158910Srwatson 129158910Srwatsonstatic void 130158910Srwatsonsetup_alarm(int seconds) 131158910Srwatson{ 132204294Sbrucec struct itimerval itv; 133204294Sbrucec bzero(&itv, sizeof(itv)); 134204294Sbrucec (void)seconds; 135204294Sbrucec itv.it_value.tv_sec = seconds; 136158910Srwatson 137158910Srwatson signal(SIGALRM, signal_alarm); 138204294Sbrucec setitimer(ITIMER_REAL, &itv, NULL); 139158910Srwatson} 140158910Srwatson 141158910Srwatsonstatic void 142158910Srwatsoncancel_alarm(void) 143158910Srwatson{ 144204294Sbrucec struct itimerval itv; 145204294Sbrucec bzero(&itv, sizeof(itv)); 146204294Sbrucec setitimer(ITIMER_REAL, &itv, NULL); 147158910Srwatson} 148158910Srwatson 149204294Sbrucecstatic int 150204294Sbrucecreceive_test(void) 151158910Srwatson{ 152204294Sbrucec uint32_t header_length, offset, length, counter; 153158910Srwatson struct test_header th; 154158910Srwatson ssize_t len; 155204294Sbrucec char buf[10240]; 156204294Sbrucec MD5_CTX md5ctx; 157204294Sbrucec char *rxmd5; 158158910Srwatson 159158910Srwatson len = read(accept_socket, &th, sizeof(th)); 160204294Sbrucec if (len < 0 || (size_t)len < sizeof(th)) 161204294Sbrucec FAIL_ERR("read") 162158910Srwatson 163204294Sbrucec if (test_th(&th, &header_length, &offset, &length) != 0) 164204294Sbrucec return (-1); 165158910Srwatson 166204294Sbrucec MD5Init(&md5ctx); 167204294Sbrucec 168158910Srwatson counter = 0; 169158910Srwatson while (1) { 170204294Sbrucec len = read(accept_socket, buf, sizeof(buf)); 171204294Sbrucec if (len < 0 || len == 0) 172158910Srwatson break; 173204294Sbrucec counter += len; 174204294Sbrucec MD5Update(&md5ctx, buf, len); 175158910Srwatson } 176204294Sbrucec 177204294Sbrucec rxmd5 = MD5End(&md5ctx, NULL); 178204294Sbrucec 179204294Sbrucec if ((counter != header_length+length) || 180204294Sbrucec memcmp(th.th_md5, rxmd5, 33) != 0) 181204294Sbrucec FAIL("receive length mismatch") 182204294Sbrucec 183204294Sbrucec free(rxmd5); 184204294Sbrucec return (0); 185158910Srwatson} 186158910Srwatson 187158910Srwatsonstatic void 188158910Srwatsonrun_child(void) 189158910Srwatson{ 190204294Sbrucec struct sockaddr_in sin; 191204294Sbrucec int rc = 0; 192158910Srwatson 193204294Sbrucec listen_socket = socket(PF_INET, SOCK_STREAM, 0); 194204294Sbrucec if (listen_socket < 0) { 195204294Sbrucec printf("# socket: %s\n", strerror(errno)); 196204294Sbrucec rc = -1; 197204294Sbrucec } 198204294Sbrucec 199204294Sbrucec if (!rc) { 200204294Sbrucec bzero(&sin, sizeof(sin)); 201204294Sbrucec sin.sin_len = sizeof(sin); 202204294Sbrucec sin.sin_family = AF_INET; 203204294Sbrucec sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 204204294Sbrucec sin.sin_port = htons(TEST_PORT); 205204294Sbrucec 206204294Sbrucec if (bind(listen_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) { 207204294Sbrucec printf("# bind: %s\n", strerror(errno)); 208204294Sbrucec rc = -1; 209204294Sbrucec } 210204294Sbrucec } 211204294Sbrucec 212204294Sbrucec if (!rc && listen(listen_socket, -1) < 0) { 213204294Sbrucec printf("# listen: %s\n", strerror(errno)); 214204294Sbrucec rc = -1; 215204294Sbrucec } 216204294Sbrucec 217204294Sbrucec if (!rc) { 218158910Srwatson accept_socket = accept(listen_socket, NULL, NULL); 219158910Srwatson setup_alarm(TEST_SECONDS); 220204294Sbrucec if (receive_test() != 0) 221204294Sbrucec rc = -1; 222204294Sbrucec } 223204294Sbrucec 224204294Sbrucec cancel_alarm(); 225204294Sbrucec if (accept_socket > 0) 226158910Srwatson close(accept_socket); 227204294Sbrucec if (listen_socket > 0) 228204294Sbrucec close(listen_socket); 229204294Sbrucec 230204294Sbrucec _exit(rc); 231158910Srwatson} 232158910Srwatson 233158910Srwatsonstatic int 234204294Sbrucecnew_test_socket(int *connect_socket) 235158910Srwatson{ 236158910Srwatson struct sockaddr_in sin; 237204294Sbrucec int rc = 0; 238158910Srwatson 239204294Sbrucec *connect_socket = socket(PF_INET, SOCK_STREAM, 0); 240204294Sbrucec if (*connect_socket < 0) 241204294Sbrucec FAIL_ERR("socket") 242158910Srwatson 243158910Srwatson bzero(&sin, sizeof(sin)); 244158910Srwatson sin.sin_len = sizeof(sin); 245158910Srwatson sin.sin_family = AF_INET; 246158910Srwatson sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 247158910Srwatson sin.sin_port = htons(TEST_PORT); 248158910Srwatson 249204294Sbrucec if (connect(*connect_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) 250204294Sbrucec FAIL_ERR("connect") 251158910Srwatson 252204294Sbrucec return (rc); 253158910Srwatson} 254158910Srwatson 255158910Srwatsonstatic void 256204294Sbrucecinit_th(struct test_header *th, uint32_t header_length, uint32_t offset, 257204294Sbrucec uint32_t length) 258158910Srwatson{ 259158910Srwatson bzero(th, sizeof(*th)); 260158910Srwatson th->th_magic = htonl(TEST_MAGIC); 261158910Srwatson th->th_header_length = htonl(header_length); 262158910Srwatson th->th_offset = htonl(offset); 263158910Srwatson th->th_length = htonl(length); 264204294Sbrucec 265204294Sbrucec MD5FileChunk(path, th->th_md5, offset, length); 266158910Srwatson} 267158910Srwatson 268204294Sbrucecstatic int 269204294Sbrucecsend_test(int connect_socket, struct sendfile_test test) 270158910Srwatson{ 271158910Srwatson struct test_header th; 272158910Srwatson struct sf_hdtr hdtr, *hdtrp; 273158910Srwatson struct iovec headers; 274158910Srwatson char *header; 275158910Srwatson ssize_t len; 276204294Sbrucec int length; 277158910Srwatson off_t off; 278158910Srwatson 279158910Srwatson len = lseek(file_fd, 0, SEEK_SET); 280158910Srwatson if (len != 0) 281204294Sbrucec FAIL_ERR("lseek") 282158910Srwatson 283255451Semaste struct stat st; 284255451Semaste if (fstat(file_fd, &st) < 0) 285255451Semaste FAIL_ERR("fstat") 286255451Semaste length = st.st_size - test.offset; 287255451Semaste if (test.length > 0 && test.length < (uint32_t)length) 288204294Sbrucec length = test.length; 289158910Srwatson 290204294Sbrucec init_th(&th, test.hdr_length, test.offset, length); 291204294Sbrucec 292158910Srwatson len = write(connect_socket, &th, sizeof(th)); 293158910Srwatson if (len != sizeof(th)) 294204294Sbrucec return (-1); 295158910Srwatson 296204294Sbrucec if (test.hdr_length != 0) { 297204294Sbrucec header = malloc(test.hdr_length); 298158910Srwatson if (header == NULL) 299204294Sbrucec FAIL_ERR("malloc") 300204294Sbrucec 301158910Srwatson hdtrp = &hdtr; 302158910Srwatson bzero(&headers, sizeof(headers)); 303158910Srwatson headers.iov_base = header; 304204294Sbrucec headers.iov_len = test.hdr_length; 305158910Srwatson bzero(&hdtr, sizeof(hdtr)); 306158910Srwatson hdtr.headers = &headers; 307158910Srwatson hdtr.hdr_cnt = 1; 308158910Srwatson hdtr.trailers = NULL; 309158910Srwatson hdtr.trl_cnt = 0; 310158910Srwatson } else { 311158910Srwatson hdtrp = NULL; 312158910Srwatson header = NULL; 313158910Srwatson } 314158910Srwatson 315204294Sbrucec if (sendfile(file_fd, connect_socket, test.offset, test.length, 316204294Sbrucec hdtrp, &off, 0) < 0) { 317204294Sbrucec if (header != NULL) 318204294Sbrucec free(header); 319204294Sbrucec FAIL_ERR("sendfile") 320204294Sbrucec } 321158910Srwatson 322168854Spjd if (length == 0) { 323168854Spjd struct stat sb; 324158910Srwatson 325204294Sbrucec if (fstat(file_fd, &sb) == 0) 326204294Sbrucec length = sb.st_size - test.offset; 327168854Spjd } 328168854Spjd 329158910Srwatson if (header != NULL) 330158910Srwatson free(header); 331204294Sbrucec 332204294Sbrucec if (off != length) 333204294Sbrucec FAIL("offset != length") 334204294Sbrucec 335204294Sbrucec return (0); 336158910Srwatson} 337158910Srwatson 338255451Semastestatic int 339255451Semastewrite_test_file(size_t file_size) 340255451Semaste{ 341255451Semaste char *page_buffer; 342255451Semaste ssize_t len; 343255451Semaste static size_t current_file_size = 0; 344255451Semaste 345255451Semaste if (file_size == current_file_size) 346255451Semaste return (0); 347255451Semaste else if (file_size < current_file_size) { 348255451Semaste if (ftruncate(file_fd, file_size) != 0) 349255451Semaste FAIL_ERR("ftruncate"); 350255451Semaste current_file_size = file_size; 351255451Semaste return (0); 352255451Semaste } 353255451Semaste 354255451Semaste page_buffer = malloc(file_size); 355255451Semaste if (page_buffer == NULL) 356255451Semaste FAIL_ERR("malloc") 357255451Semaste bzero(page_buffer, file_size); 358255451Semaste 359255451Semaste len = write(file_fd, page_buffer, file_size); 360255451Semaste if (len < 0) 361255451Semaste FAIL_ERR("write") 362255451Semaste 363255451Semaste len = lseek(file_fd, 0, SEEK_SET); 364255451Semaste if (len < 0) 365255451Semaste FAIL_ERR("lseek") 366255451Semaste if (len != 0) 367255451Semaste FAIL("len != 0") 368255451Semaste 369255451Semaste free(page_buffer); 370255451Semaste current_file_size = file_size; 371255451Semaste return (0); 372255451Semaste} 373255451Semaste 374158910Srwatsonstatic void 375158910Srwatsonrun_parent(void) 376158910Srwatson{ 377158910Srwatson int connect_socket; 378204294Sbrucec int status; 379204294Sbrucec int test_num; 380255451Semaste int test_count; 381204294Sbrucec int pid; 382255451Semaste size_t desired_file_size = 0; 383158910Srwatson 384204294Sbrucec const int pagesize = getpagesize(); 385158910Srwatson 386255451Semaste struct sendfile_test tests[] = { 387204294Sbrucec { .hdr_length = 0, .offset = 0, .length = 1 }, 388204294Sbrucec { .hdr_length = 0, .offset = 0, .length = pagesize }, 389204294Sbrucec { .hdr_length = 0, .offset = 1, .length = 1 }, 390204294Sbrucec { .hdr_length = 0, .offset = 1, .length = pagesize }, 391204294Sbrucec { .hdr_length = 0, .offset = pagesize, .length = pagesize }, 392204294Sbrucec { .hdr_length = 0, .offset = 0, .length = 2*pagesize }, 393204294Sbrucec { .hdr_length = 0, .offset = 0, .length = 0 }, 394204294Sbrucec { .hdr_length = 0, .offset = pagesize, .length = 0 }, 395204294Sbrucec { .hdr_length = 0, .offset = 2*pagesize, .length = 0 }, 396255451Semaste { .hdr_length = 0, .offset = TEST_PAGES*pagesize, .length = 0 }, 397255451Semaste { .hdr_length = 0, .offset = 0, .length = pagesize, 398255451Semaste .file_size = 1 } 399204294Sbrucec }; 400158910Srwatson 401255451Semaste test_count = sizeof(tests) / sizeof(tests[0]); 402255451Semaste printf("1..%d\n", test_count); 403158910Srwatson 404255451Semaste for (test_num = 1; test_num <= test_count; test_num++) { 405158910Srwatson 406255451Semaste desired_file_size = tests[test_num - 1].file_size; 407255451Semaste if (desired_file_size == 0) 408255451Semaste desired_file_size = TEST_PAGES * pagesize; 409255451Semaste if (write_test_file(desired_file_size) != 0) { 410255451Semaste printf("not ok %d\n", test_num); 411255451Semaste continue; 412255451Semaste } 413255451Semaste 414204294Sbrucec pid = fork(); 415204294Sbrucec if (pid == -1) { 416204294Sbrucec printf("not ok %d\n", test_num); 417204294Sbrucec continue; 418204294Sbrucec } 419158910Srwatson 420204294Sbrucec if (pid == 0) 421204294Sbrucec run_child(); 422158910Srwatson 423204294Sbrucec usleep(250000); 424158910Srwatson 425204294Sbrucec if (new_test_socket(&connect_socket) != 0) { 426204294Sbrucec printf("not ok %d\n", test_num); 427204294Sbrucec kill(pid, SIGALRM); 428204294Sbrucec close(connect_socket); 429204294Sbrucec continue; 430204294Sbrucec } 431158910Srwatson 432204294Sbrucec if (send_test(connect_socket, tests[test_num-1]) != 0) { 433204294Sbrucec printf("not ok %d\n", test_num); 434204294Sbrucec kill(pid, SIGALRM); 435204294Sbrucec close(connect_socket); 436204294Sbrucec continue; 437204294Sbrucec } 438158910Srwatson 439204294Sbrucec close(connect_socket); 440204294Sbrucec if (waitpid(pid, &status, 0) == pid) { 441204294Sbrucec if (WIFEXITED(status) && WEXITSTATUS(status) == 0) 442204294Sbrucec printf("%s %d\n", "ok", test_num); 443204294Sbrucec else 444204294Sbrucec printf("%s %d\n", "not ok", test_num); 445204294Sbrucec } 446204294Sbrucec else { 447204294Sbrucec printf("not ok %d\n", test_num); 448204294Sbrucec } 449204294Sbrucec } 450204294Sbrucec} 451158910Srwatson 452204294Sbrucecstatic void 453204294Sbruceccleanup(void) 454204294Sbrucec{ 455204294Sbrucec if (*path != '\0') 456204294Sbrucec unlink(path); 457158910Srwatson} 458158910Srwatson 459158910Srwatsonint 460216379Spjdmain(int argc, char *argv[]) 461158910Srwatson{ 462158910Srwatson int pagesize; 463158910Srwatson 464204294Sbrucec *path = '\0'; 465204294Sbrucec 466158910Srwatson pagesize = getpagesize(); 467158910Srwatson 468216379Spjd if (argc == 1) { 469216379Spjd snprintf(path, PATH_MAX, "/tmp/sendfile.XXXXXXXXXXXX"); 470216379Spjd file_fd = mkstemp(path); 471216379Spjd if (file_fd == -1) 472216379Spjd FAIL_ERR("mkstemp"); 473216379Spjd } else if (argc == 2) { 474216379Spjd (void)strlcpy(path, argv[1], sizeof(path)); 475216379Spjd file_fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600); 476216379Spjd if (file_fd == -1) 477216379Spjd FAIL_ERR("open"); 478216379Spjd } else { 479216379Spjd FAIL("usage: sendfile [path]"); 480216379Spjd } 481216379Spjd 482204294Sbrucec atexit(cleanup); 483158910Srwatson 484204294Sbrucec run_parent(); 485158910Srwatson return (0); 486158910Srwatson} 487