sendfile.c revision 204294
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: head/tools/regression/sockets/sendfile/sendfile.c 204294 2010-02-24 23:00:16Z brucec $
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
64struct 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
72struct sendfile_test {
73	uint32_t	hdr_length;
74	uint32_t	offset;
75	uint32_t	length;
76};
77
78int	file_fd;
79char	path[PATH_MAX];
80int	listen_socket;
81int	accept_socket;
82
83static int test_th(struct test_header *th, uint32_t *header_length,
84		uint32_t *offset, uint32_t *length);
85static void signal_alarm(int signum);
86static void setup_alarm(int seconds);
87static void cancel_alarm(void);
88static int receive_test(void);
89static void run_child(void);
90static int new_test_socket(int *connect_socket);
91static void init_th(struct test_header *th, uint32_t header_length,
92		uint32_t offset, uint32_t length);
93static int send_test(int connect_socket, struct sendfile_test);
94static void run_parent(void);
95static void cleanup(void);
96
97
98static int
99test_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
111static void
112signal_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
126static void
127setup_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
138static void
139cancel_alarm(void)
140{
141	struct itimerval itv;
142	bzero(&itv, sizeof(itv));
143	setitimer(ITIMER_REAL, &itv, NULL);
144}
145
146static int
147receive_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
184static void
185run_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
230static int
231new_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
252static void
253init_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
265static int
266send_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
338static void
339run_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
403static void
404cleanup(void)
405{
406	if (*path != '\0')
407		unlink(path);
408}
409
410int
411main(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