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
test_th(struct test_header * th,uint32_t * header_length,uint32_t * offset,uint32_t * length)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
signal_alarm(int signum)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
setup_alarm(int seconds)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
cancel_alarm(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
receive_test(void)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
run_child(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
new_test_socket(int * connect_socket)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
init_th(struct test_header * th,uint32_t header_length,uint32_t offset,uint32_t length)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
send_test(int connect_socket,struct sendfile_test test)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
write_test_file(size_t file_size)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
run_parent(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
cleanup(void)451 cleanup(void)
452 {
453
454 unlink(path);
455 }
456
457 int
main(int argc,char * argv[])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