1 /*- 2 * Copyright (c) 2006 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/types.h> 28 #include <sys/socket.h> 29 #include <sys/stat.h> 30 #include <sys/wait.h> 31 32 #include <netinet/in.h> 33 34 #include <err.h> 35 #include <errno.h> 36 #include <fcntl.h> 37 #include <limits.h> 38 #include <md5.h> 39 #include <signal.h> 40 #include <stdint.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 46 /* 47 * Simple regression test for sendfile. Creates a file sized at four pages 48 * and then proceeds to send it over a series of sockets, exercising a number 49 * of cases and performing limited validation. 50 */ 51 52 #define FAIL(msg) {printf("# %s\n", msg); \ 53 return (-1);} 54 55 #define FAIL_ERR(msg) {printf("# %s: %s\n", msg, strerror(errno)); \ 56 return (-1);} 57 58 #define TEST_PORT 5678 59 #define TEST_MAGIC 0x4440f7bb 60 #define TEST_PAGES 4 61 #define TEST_SECONDS 30 62 63 struct test_header { 64 uint32_t th_magic; 65 uint32_t th_header_length; 66 uint32_t th_offset; 67 uint32_t th_length; 68 char th_md5[33]; 69 }; 70 71 struct sendfile_test { 72 uint32_t hdr_length; 73 uint32_t offset; 74 uint32_t length; 75 uint32_t file_size; 76 }; 77 78 static int file_fd; 79 static char path[PATH_MAX]; 80 static int listen_socket; 81 static int accept_socket; 82 83 static int test_th(struct test_header *th, uint32_t *header_length, 84 uint32_t *offset, uint32_t *length); 85 static void signal_alarm(int signum); 86 static void setup_alarm(int seconds); 87 static void cancel_alarm(void); 88 static int receive_test(void); 89 static void run_child(void); 90 static int new_test_socket(int *connect_socket); 91 static void init_th(struct test_header *th, uint32_t header_length, 92 uint32_t offset, uint32_t length); 93 static int send_test(int connect_socket, struct sendfile_test); 94 static int write_test_file(size_t file_size); 95 static void run_parent(void); 96 static void cleanup(void); 97 98 99 static int 100 test_th(struct test_header *th, uint32_t *header_length, uint32_t *offset, 101 uint32_t *length) 102 { 103 104 if (th->th_magic != htonl(TEST_MAGIC)) 105 FAIL("magic number not found in header") 106 *header_length = ntohl(th->th_header_length); 107 *offset = ntohl(th->th_offset); 108 *length = ntohl(th->th_length); 109 return (0); 110 } 111 112 static void 113 signal_alarm(int signum) 114 { 115 (void)signum; 116 117 printf("# test timeout\n"); 118 119 if (accept_socket > 0) 120 close(accept_socket); 121 if (listen_socket > 0) 122 close(listen_socket); 123 124 _exit(-1); 125 } 126 127 static void 128 setup_alarm(int seconds) 129 { 130 struct itimerval itv; 131 bzero(&itv, sizeof(itv)); 132 (void)seconds; 133 itv.it_value.tv_sec = seconds; 134 135 signal(SIGALRM, signal_alarm); 136 setitimer(ITIMER_REAL, &itv, NULL); 137 } 138 139 static void 140 cancel_alarm(void) 141 { 142 struct itimerval itv; 143 bzero(&itv, sizeof(itv)); 144 setitimer(ITIMER_REAL, &itv, NULL); 145 } 146 147 static int 148 receive_test(void) 149 { 150 uint32_t header_length, offset, length, counter; 151 struct test_header th; 152 ssize_t len; 153 char buf[10240]; 154 MD5_CTX md5ctx; 155 char *rxmd5; 156 157 len = read(accept_socket, &th, sizeof(th)); 158 if (len < 0 || (size_t)len < sizeof(th)) 159 FAIL_ERR("read") 160 161 if (test_th(&th, &header_length, &offset, &length) != 0) 162 return (-1); 163 164 MD5Init(&md5ctx); 165 166 counter = 0; 167 while (1) { 168 len = read(accept_socket, buf, sizeof(buf)); 169 if (len < 0 || len == 0) 170 break; 171 counter += len; 172 MD5Update(&md5ctx, buf, len); 173 } 174 175 rxmd5 = MD5End(&md5ctx, NULL); 176 177 if ((counter != header_length+length) || 178 memcmp(th.th_md5, rxmd5, 33) != 0) 179 FAIL("receive length mismatch") 180 181 free(rxmd5); 182 return (0); 183 } 184 185 static void 186 run_child(void) 187 { 188 struct sockaddr_in sin; 189 int rc = 0; 190 191 listen_socket = socket(PF_INET, SOCK_STREAM, 0); 192 if (listen_socket < 0) { 193 printf("# socket: %s\n", strerror(errno)); 194 rc = -1; 195 } 196 197 if (!rc) { 198 bzero(&sin, sizeof(sin)); 199 sin.sin_len = sizeof(sin); 200 sin.sin_family = AF_INET; 201 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 202 sin.sin_port = htons(TEST_PORT); 203 204 if (bind(listen_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) { 205 printf("# bind: %s\n", strerror(errno)); 206 rc = -1; 207 } 208 } 209 210 if (!rc && listen(listen_socket, -1) < 0) { 211 printf("# listen: %s\n", strerror(errno)); 212 rc = -1; 213 } 214 215 if (!rc) { 216 accept_socket = accept(listen_socket, NULL, NULL); 217 setup_alarm(TEST_SECONDS); 218 if (receive_test() != 0) 219 rc = -1; 220 } 221 222 cancel_alarm(); 223 if (accept_socket > 0) 224 close(accept_socket); 225 if (listen_socket > 0) 226 close(listen_socket); 227 228 _exit(rc); 229 } 230 231 static int 232 new_test_socket(int *connect_socket) 233 { 234 struct sockaddr_in sin; 235 int rc = 0; 236 237 *connect_socket = socket(PF_INET, SOCK_STREAM, 0); 238 if (*connect_socket < 0) 239 FAIL_ERR("socket") 240 241 bzero(&sin, sizeof(sin)); 242 sin.sin_len = sizeof(sin); 243 sin.sin_family = AF_INET; 244 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 245 sin.sin_port = htons(TEST_PORT); 246 247 if (connect(*connect_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) 248 FAIL_ERR("connect") 249 250 return (rc); 251 } 252 253 static void 254 init_th(struct test_header *th, uint32_t header_length, uint32_t offset, 255 uint32_t length) 256 { 257 bzero(th, sizeof(*th)); 258 th->th_magic = htonl(TEST_MAGIC); 259 th->th_header_length = htonl(header_length); 260 th->th_offset = htonl(offset); 261 th->th_length = htonl(length); 262 263 MD5FileChunk(path, th->th_md5, offset, length); 264 } 265 266 static int 267 send_test(int connect_socket, struct sendfile_test test) 268 { 269 struct test_header th; 270 struct sf_hdtr hdtr, *hdtrp; 271 struct iovec headers; 272 char *header; 273 ssize_t len; 274 int length; 275 off_t off; 276 277 len = lseek(file_fd, 0, SEEK_SET); 278 if (len != 0) 279 FAIL_ERR("lseek") 280 281 struct stat st; 282 if (fstat(file_fd, &st) < 0) 283 FAIL_ERR("fstat") 284 length = st.st_size - test.offset; 285 if (test.length > 0 && test.length < (uint32_t)length) 286 length = test.length; 287 288 init_th(&th, test.hdr_length, test.offset, length); 289 290 len = write(connect_socket, &th, sizeof(th)); 291 if (len != sizeof(th)) 292 return (-1); 293 294 if (test.hdr_length != 0) { 295 header = malloc(test.hdr_length); 296 if (header == NULL) 297 FAIL_ERR("malloc") 298 299 hdtrp = &hdtr; 300 bzero(&headers, sizeof(headers)); 301 headers.iov_base = header; 302 headers.iov_len = test.hdr_length; 303 bzero(&hdtr, sizeof(hdtr)); 304 hdtr.headers = &headers; 305 hdtr.hdr_cnt = 1; 306 hdtr.trailers = NULL; 307 hdtr.trl_cnt = 0; 308 } else { 309 hdtrp = NULL; 310 header = NULL; 311 } 312 313 if (sendfile(file_fd, connect_socket, test.offset, test.length, 314 hdtrp, &off, 0) < 0) { 315 if (header != NULL) 316 free(header); 317 FAIL_ERR("sendfile") 318 } 319 320 if (length == 0) { 321 struct stat sb; 322 323 if (fstat(file_fd, &sb) == 0) 324 length = sb.st_size - test.offset; 325 } 326 327 if (header != NULL) 328 free(header); 329 330 if (off != length) 331 FAIL("offset != length") 332 333 return (0); 334 } 335 336 static int 337 write_test_file(size_t file_size) 338 { 339 char *page_buffer; 340 ssize_t len; 341 static size_t current_file_size = 0; 342 343 if (file_size == current_file_size) 344 return (0); 345 else if (file_size < current_file_size) { 346 if (ftruncate(file_fd, file_size) != 0) 347 FAIL_ERR("ftruncate"); 348 current_file_size = file_size; 349 return (0); 350 } 351 352 page_buffer = malloc(file_size); 353 if (page_buffer == NULL) 354 FAIL_ERR("malloc") 355 bzero(page_buffer, file_size); 356 357 len = write(file_fd, page_buffer, file_size); 358 if (len < 0) 359 FAIL_ERR("write") 360 361 len = lseek(file_fd, 0, SEEK_SET); 362 if (len < 0) 363 FAIL_ERR("lseek") 364 if (len != 0) 365 FAIL("len != 0") 366 367 free(page_buffer); 368 current_file_size = file_size; 369 return (0); 370 } 371 372 static void 373 run_parent(void) 374 { 375 int connect_socket; 376 int status; 377 int test_num; 378 int test_count; 379 int pid; 380 size_t desired_file_size = 0; 381 382 const int pagesize = getpagesize(); 383 384 struct sendfile_test tests[] = { 385 { .hdr_length = 0, .offset = 0, .length = 1 }, 386 { .hdr_length = 0, .offset = 0, .length = pagesize }, 387 { .hdr_length = 0, .offset = 1, .length = 1 }, 388 { .hdr_length = 0, .offset = 1, .length = pagesize }, 389 { .hdr_length = 0, .offset = pagesize, .length = pagesize }, 390 { .hdr_length = 0, .offset = 0, .length = 2*pagesize }, 391 { .hdr_length = 0, .offset = 0, .length = 0 }, 392 { .hdr_length = 0, .offset = pagesize, .length = 0 }, 393 { .hdr_length = 0, .offset = 2*pagesize, .length = 0 }, 394 { .hdr_length = 0, .offset = TEST_PAGES*pagesize, .length = 0 }, 395 { .hdr_length = 0, .offset = 0, .length = pagesize, 396 .file_size = 1 } 397 }; 398 399 test_count = sizeof(tests) / sizeof(tests[0]); 400 printf("1..%d\n", test_count); 401 402 for (test_num = 1; test_num <= test_count; test_num++) { 403 404 desired_file_size = tests[test_num - 1].file_size; 405 if (desired_file_size == 0) 406 desired_file_size = TEST_PAGES * pagesize; 407 if (write_test_file(desired_file_size) != 0) { 408 printf("not ok %d\n", test_num); 409 continue; 410 } 411 412 pid = fork(); 413 if (pid == -1) { 414 printf("not ok %d\n", test_num); 415 continue; 416 } 417 418 if (pid == 0) 419 run_child(); 420 421 usleep(250000); 422 423 if (new_test_socket(&connect_socket) != 0) { 424 printf("not ok %d\n", test_num); 425 kill(pid, SIGALRM); 426 close(connect_socket); 427 continue; 428 } 429 430 if (send_test(connect_socket, tests[test_num-1]) != 0) { 431 printf("not ok %d\n", test_num); 432 kill(pid, SIGALRM); 433 close(connect_socket); 434 continue; 435 } 436 437 close(connect_socket); 438 if (waitpid(pid, &status, 0) == pid) { 439 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) 440 printf("%s %d\n", "ok", test_num); 441 else 442 printf("%s %d\n", "not ok", test_num); 443 } 444 else { 445 printf("not ok %d\n", test_num); 446 } 447 } 448 } 449 450 static void 451 cleanup(void) 452 { 453 454 unlink(path); 455 } 456 457 int 458 main(int argc, char *argv[]) 459 { 460 461 path[0] = '\0'; 462 463 if (argc == 1) { 464 snprintf(path, sizeof(path), "sendfile.XXXXXXXXXXXX"); 465 file_fd = mkstemp(path); 466 if (file_fd == -1) 467 FAIL_ERR("mkstemp"); 468 } else if (argc == 2) { 469 (void)strlcpy(path, argv[1], sizeof(path)); 470 file_fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600); 471 if (file_fd == -1) 472 FAIL_ERR("open"); 473 } else { 474 FAIL("usage: sendfile [path]"); 475 } 476 477 atexit(cleanup); 478 479 run_parent(); 480 return (0); 481 } 482