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