xref: /freebsd/tools/regression/sockets/sendfile/sendfile.c (revision 7aa383846770374466b1dcb2cefd71bde9acf463)
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  * $FreeBSD$
27  */
28 
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/stat.h>
32 #include <sys/wait.h>
33 
34 #include <netinet/in.h>
35 
36 #include <err.h>
37 #include <errno.h>
38 #include <limits.h>
39 #include <md5.h>
40 #include <signal.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 /*
48  * Simple regression test for sendfile.  Creates a file sized at four pages
49  * and then proceeds to send it over a series of sockets, exercising a number
50  * of cases and performing limited validation.
51  */
52 
53 #define FAIL(msg)	{printf("# %s\n", msg); \
54 			return (-1);}
55 
56 #define FAIL_ERR(msg)	{printf("# %s: %s\n", msg, strerror(errno)); \
57 			return (-1);}
58 
59 #define	TEST_PORT	5678
60 #define	TEST_MAGIC	0x4440f7bb
61 #define	TEST_PAGES	4
62 #define	TEST_SECONDS	30
63 
64 struct test_header {
65 	uint32_t	th_magic;
66 	uint32_t	th_header_length;
67 	uint32_t	th_offset;
68 	uint32_t	th_length;
69 	char		th_md5[33];
70 };
71 
72 struct sendfile_test {
73 	uint32_t	hdr_length;
74 	uint32_t	offset;
75 	uint32_t	length;
76 };
77 
78 int	file_fd;
79 char	path[PATH_MAX];
80 int	listen_socket;
81 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 void run_parent(void);
95 static void cleanup(void);
96 
97 
98 static int
99 test_th(struct test_header *th, uint32_t *header_length, uint32_t *offset,
100 		uint32_t *length)
101 {
102 
103 	if (th->th_magic != htonl(TEST_MAGIC))
104 		FAIL("magic number not found in header")
105 	*header_length = ntohl(th->th_header_length);
106 	*offset = ntohl(th->th_offset);
107 	*length = ntohl(th->th_length);
108 	return (0);
109 }
110 
111 static void
112 signal_alarm(int signum)
113 {
114 	(void)signum;
115 
116 	printf("# test timeout\n");
117 
118 	if (accept_socket > 0)
119 		close(accept_socket);
120 	if (listen_socket > 0)
121 		close(listen_socket);
122 
123 	_exit(-1);
124 }
125 
126 static void
127 setup_alarm(int seconds)
128 {
129 	struct itimerval itv;
130 	bzero(&itv, sizeof(itv));
131 	(void)seconds;
132 	itv.it_value.tv_sec = seconds;
133 
134 	signal(SIGALRM, signal_alarm);
135 	setitimer(ITIMER_REAL, &itv, NULL);
136 }
137 
138 static void
139 cancel_alarm(void)
140 {
141 	struct itimerval itv;
142 	bzero(&itv, sizeof(itv));
143 	setitimer(ITIMER_REAL, &itv, NULL);
144 }
145 
146 static int
147 receive_test(void)
148 {
149 	uint32_t header_length, offset, length, counter;
150 	struct test_header th;
151 	ssize_t len;
152 	char buf[10240];
153 	MD5_CTX md5ctx;
154 	char *rxmd5;
155 
156 	len = read(accept_socket, &th, sizeof(th));
157 	if (len < 0 || (size_t)len < sizeof(th))
158 		FAIL_ERR("read")
159 
160 	if (test_th(&th, &header_length, &offset, &length) != 0)
161 		return (-1);
162 
163 	MD5Init(&md5ctx);
164 
165 	counter = 0;
166 	while (1) {
167 		len = read(accept_socket, buf, sizeof(buf));
168 		if (len < 0 || len == 0)
169 			break;
170 		counter += len;
171 		MD5Update(&md5ctx, buf, len);
172 	}
173 
174 	rxmd5 = MD5End(&md5ctx, NULL);
175 
176 	if ((counter != header_length+length) ||
177 			memcmp(th.th_md5, rxmd5, 33) != 0)
178 		FAIL("receive length mismatch")
179 
180 	free(rxmd5);
181 	return (0);
182 }
183 
184 static void
185 run_child(void)
186 {
187 	struct sockaddr_in sin;
188 	int rc = 0;
189 
190 	listen_socket = socket(PF_INET, SOCK_STREAM, 0);
191 	if (listen_socket < 0) {
192 		printf("# socket: %s\n", strerror(errno));
193 		rc = -1;
194 	}
195 
196 	if (!rc) {
197 		bzero(&sin, sizeof(sin));
198 		sin.sin_len = sizeof(sin);
199 		sin.sin_family = AF_INET;
200 		sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
201 		sin.sin_port = htons(TEST_PORT);
202 
203 		if (bind(listen_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
204 			printf("# bind: %s\n", strerror(errno));
205 			rc = -1;
206 		}
207 	}
208 
209 	if (!rc && listen(listen_socket, -1) < 0) {
210 		printf("# listen: %s\n", strerror(errno));
211 		rc = -1;
212 	}
213 
214 	if (!rc) {
215 		accept_socket = accept(listen_socket, NULL, NULL);
216 		setup_alarm(TEST_SECONDS);
217 		if (receive_test() != 0)
218 			rc = -1;
219 	}
220 
221 	cancel_alarm();
222 	if (accept_socket > 0)
223 		close(accept_socket);
224 	if (listen_socket > 0)
225 		close(listen_socket);
226 
227 	_exit(rc);
228 }
229 
230 static int
231 new_test_socket(int *connect_socket)
232 {
233 	struct sockaddr_in sin;
234 	int rc = 0;
235 
236 	*connect_socket = socket(PF_INET, SOCK_STREAM, 0);
237 	if (*connect_socket < 0)
238 		FAIL_ERR("socket")
239 
240 	bzero(&sin, sizeof(sin));
241 	sin.sin_len = sizeof(sin);
242 	sin.sin_family = AF_INET;
243 	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
244 	sin.sin_port = htons(TEST_PORT);
245 
246 	if (connect(*connect_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0)
247 		FAIL_ERR("connect")
248 
249 	return (rc);
250 }
251 
252 static void
253 init_th(struct test_header *th, uint32_t header_length, uint32_t offset,
254 		uint32_t length)
255 {
256 	bzero(th, sizeof(*th));
257 	th->th_magic = htonl(TEST_MAGIC);
258 	th->th_header_length = htonl(header_length);
259 	th->th_offset = htonl(offset);
260 	th->th_length = htonl(length);
261 
262 	MD5FileChunk(path, th->th_md5, offset, length);
263 }
264 
265 static int
266 send_test(int connect_socket, struct sendfile_test test)
267 {
268 	struct test_header th;
269 	struct sf_hdtr hdtr, *hdtrp;
270 	struct iovec headers;
271 	char *header;
272 	ssize_t len;
273 	int length;
274 	off_t off;
275 
276 	len = lseek(file_fd, 0, SEEK_SET);
277 	if (len != 0)
278 		FAIL_ERR("lseek")
279 
280 	if (test.length == 0) {
281 		struct stat st;
282 		if (fstat(file_fd, &st) < 0)
283 			FAIL_ERR("fstat")
284 		length = st.st_size - test.offset;
285 	}
286 	else {
287 		length = test.length;
288 	}
289 
290 	init_th(&th, test.hdr_length, test.offset, length);
291 
292 	len = write(connect_socket, &th, sizeof(th));
293 	if (len != sizeof(th))
294 		return (-1);
295 
296 	if (test.hdr_length != 0) {
297 		header = malloc(test.hdr_length);
298 		if (header == NULL)
299 			FAIL_ERR("malloc")
300 
301 		hdtrp = &hdtr;
302 		bzero(&headers, sizeof(headers));
303 		headers.iov_base = header;
304 		headers.iov_len = test.hdr_length;
305 		bzero(&hdtr, sizeof(hdtr));
306 		hdtr.headers = &headers;
307 		hdtr.hdr_cnt = 1;
308 		hdtr.trailers = NULL;
309 		hdtr.trl_cnt = 0;
310 	} else {
311 		hdtrp = NULL;
312 		header = NULL;
313 	}
314 
315 	if (sendfile(file_fd, connect_socket, test.offset, test.length,
316 				hdtrp, &off, 0) < 0) {
317 		if (header != NULL)
318 			free(header);
319 		FAIL_ERR("sendfile")
320 	}
321 
322 	if (length == 0) {
323 		struct stat sb;
324 
325 		if (fstat(file_fd, &sb) == 0)
326 			length = sb.st_size - test.offset;
327 	}
328 
329 	if (header != NULL)
330 		free(header);
331 
332 	if (off != length)
333 		FAIL("offset != length")
334 
335 	return (0);
336 }
337 
338 static void
339 run_parent(void)
340 {
341 	int connect_socket;
342 	int status;
343 	int test_num;
344 	int pid;
345 
346 	const int pagesize = getpagesize();
347 
348 	struct sendfile_test tests[10] = {
349  		{ .hdr_length = 0, .offset = 0, .length = 1 },
350 		{ .hdr_length = 0, .offset = 0, .length = pagesize },
351 		{ .hdr_length = 0, .offset = 1, .length = 1 },
352 		{ .hdr_length = 0, .offset = 1, .length = pagesize },
353 		{ .hdr_length = 0, .offset = pagesize, .length = pagesize },
354 		{ .hdr_length = 0, .offset = 0, .length = 2*pagesize },
355 		{ .hdr_length = 0, .offset = 0, .length = 0 },
356 		{ .hdr_length = 0, .offset = pagesize, .length = 0 },
357 		{ .hdr_length = 0, .offset = 2*pagesize, .length = 0 },
358 		{ .hdr_length = 0, .offset = TEST_PAGES*pagesize, .length = 0 }
359 	};
360 
361 	printf("1..10\n");
362 
363 	for (test_num = 1; test_num <= 10; test_num++) {
364 
365 		pid = fork();
366 		if (pid == -1) {
367 			printf("not ok %d\n", test_num);
368 			continue;
369 		}
370 
371 		if (pid == 0)
372 			run_child();
373 
374 		usleep(250000);
375 
376 		if (new_test_socket(&connect_socket) != 0) {
377 			printf("not ok %d\n", test_num);
378 			kill(pid, SIGALRM);
379 			close(connect_socket);
380 			continue;
381 		}
382 
383 		if (send_test(connect_socket, tests[test_num-1]) != 0) {
384 			printf("not ok %d\n", test_num);
385 			kill(pid, SIGALRM);
386 			close(connect_socket);
387 			continue;
388 		}
389 
390 		close(connect_socket);
391 		if (waitpid(pid, &status, 0) == pid) {
392 			if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
393 				printf("%s %d\n", "ok", test_num);
394 			else
395 				printf("%s %d\n", "not ok", test_num);
396 		}
397 		else {
398 			printf("not ok %d\n", test_num);
399 		}
400 	}
401 }
402 
403 static void
404 cleanup(void)
405 {
406 	if (*path != '\0')
407 		unlink(path);
408 }
409 
410 int
411 main(void)
412 {
413 	char *page_buffer;
414 	int pagesize;
415 	ssize_t len;
416 
417 	*path = '\0';
418 
419 	pagesize = getpagesize();
420 	page_buffer = malloc(TEST_PAGES * pagesize);
421 	if (page_buffer == NULL)
422 		FAIL_ERR("malloc")
423 	bzero(page_buffer, TEST_PAGES * pagesize);
424 
425 	snprintf(path, PATH_MAX, "/tmp/sendfile.XXXXXXXXXXXX");
426 	file_fd = mkstemp(path);
427 	atexit(cleanup);
428 
429 	len = write(file_fd, page_buffer, TEST_PAGES * pagesize);
430 	if (len < 0)
431 		FAIL_ERR("write")
432 
433 	len = lseek(file_fd, 0, SEEK_SET);
434 	if (len < 0)
435 		FAIL_ERR("lseek")
436 	if (len != 0)
437 		FAIL("len != 0")
438 
439 	run_parent();
440 	return (0);
441 }
442