1/*
2 * Copyright (C) 1991, NeXT Computer, Inc.  All Rights Reserverd.
3 * Copyright (c) 1998-2001 Apple Computer, Inc. All rights reserved.
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
7 * The contents of this file constitute Original Code as defined in and
8 * are subject to the Apple Public Source License Version 1.1 (the
9 * "License").  You may not use this file except in compliance with the
10 * License.  Please obtain a copy of the License at
11 * http://www.apple.com/publicsource and read it before using this file.
12 *
13 * This Original Code and all software distributed under the License are
14 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
18 * License for the specific language governing rights and limitations
19 * under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 *	File:	fsx.c
24 *	Author:	Avadis Tevanian, Jr.
25 *
26 *	File system exerciser.
27 *
28 *	Rewrite and enhancements 1998-2001 Conrad Minshall -- conrad@mac.com
29 *
30 *	Various features from Joe Sokol, Pat Dirks, and Clark Warner.
31 *
32 *	Small changes to work under Linux -- davej@suse.de
33 *
34 *	Sundry porting patches from Guy Harris 12/2001
35 * $FreeBSD: src/tools/regression/fsx/fsx.c,v 1.1 2001/12/20 04:15:57 jkh Exp $
36 *
37 *	Add multi-file testing feature -- Zach Brown <zab@clusterfs.com>
38 */
39
40#include <sys/types.h>
41#include <sys/stat.h>
42#if defined(_UWIN) || defined(__linux__)
43#include <sys/param.h>
44#include <limits.h>
45#include <time.h>
46#include <strings.h>
47#include <sys/time.h>
48#endif
49#include <fcntl.h>
50#include <sys/mman.h>
51#ifndef MAP_FILE
52#define MAP_FILE 0
53#endif
54#include <limits.h>
55#include <signal.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59#include <unistd.h>
60#include <stdarg.h>
61#include <errno.h>
62
63/*
64 *	A log entry is an operation and a bunch of arguments.
65 */
66
67struct log_entry {
68	int operation;
69	struct timeval tv;
70	int args[3];
71};
72
73#define	LOGSIZE	1000
74
75struct log_entry oplog[LOGSIZE];	/* the log */
76int logptr = 0;			/* current position in log */
77int logcount = 0;		/* total ops */
78
79/*
80 *	Define operations
81 */
82
83#define	OP_READ		1
84#define OP_WRITE	2
85#define OP_TRUNCATE	3
86#define OP_CLOSEOPEN	4
87#define OP_MAPREAD	5
88#define OP_MAPWRITE	6
89#define OP_SKIPPED	7
90
91int page_size;
92int page_mask;
93
94char *original_buf;		/* a pointer to the original data */
95char *good_buf;			/* a pointer to the correct data */
96char *temp_buf;			/* a pointer to the current data */
97char *fname;			/* name of our test file */
98char logfile[1024];		/* name of our log file */
99char goodfile[1024];		/* name of our test file */
100
101off_t file_size = 0;
102off_t biggest = 0;
103char state[256];
104unsigned long testcalls = 0;	/* calls to function "test" */
105
106unsigned long simulatedopcount = 0;	/* -b flag */
107int closeprob = 0;		/* -c flag */
108int debug = 0;			/* -d flag */
109unsigned long debugstart = 0;	/* -D flag */
110unsigned long maxfilelen = 256 * 1024;	/* -l flag */
111int sizechecks = 1;		/* -n flag disables them */
112int maxoplen = 64 * 1024;	/* -o flag */
113int quiet = 0;			/* -q flag */
114unsigned long progressinterval = 0;	/* -p flag */
115int readbdy = 1;		/* -r flag */
116int style = 0;			/* -s flag */
117int truncbdy = 1;		/* -t flag */
118int writebdy = 1;		/* -w flag */
119long monitorstart = -1;		/* -m flag */
120long monitorend = -1;		/* -m flag */
121int lite = 0;			/* -L flag */
122long numops = -1;		/* -N flag */
123int randomoplen = 1;		/* -O flag disables it */
124int seed = 1;			/* -S flag */
125int mapped_writes = 1;		/* -W flag disables */
126int mapped_reads = 1;		/* -R flag disables it */
127int fsxgoodfd = 0;
128FILE *fsxlogf = NULL;
129int badoff = -1;
130
131void vwarnc(code, fmt, ap)
132int code;
133const char *fmt;
134va_list ap;
135{
136	fprintf(stderr, "fsx: ");
137	if (fmt != NULL) {
138		vfprintf(stderr, fmt, ap);
139		fprintf(stderr, ": ");
140	}
141	fprintf(stderr, "%s\n", strerror(code));
142}
143
144void warn(const char *fmt, ...)
145{
146	va_list ap;
147	va_start(ap, fmt);
148	vwarnc(errno, fmt, ap);
149	va_end(ap);
150}
151
152void
153    __attribute__ ((format(printf, 1, 2)))
154    prt(char *fmt, ...)
155{
156	va_list args;
157
158	va_start(args, fmt);
159	vfprintf(stdout, fmt, args);
160	va_end(args);
161
162	if (fsxlogf) {
163		va_start(args, fmt);
164		vfprintf(fsxlogf, fmt, args);
165		va_end(args);
166	}
167}
168
169void prterr(char *prefix)
170{
171	prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno));
172}
173
174void log4(int operation, int arg0, int arg1, int arg2, struct timeval *tv)
175{
176	struct log_entry *le;
177
178	le = &oplog[logptr];
179	le->tv = *tv;
180	le->operation = operation;
181	le->args[0] = arg0;
182	le->args[1] = arg1;
183	le->args[2] = arg2;
184	logptr++;
185	logcount++;
186	if (logptr >= LOGSIZE)
187		logptr = 0;
188}
189
190void logdump(void)
191{
192	int i, count, down;
193	struct log_entry *lp;
194
195	prt("LOG DUMP (%d total operations):\n", logcount);
196	if (logcount < LOGSIZE) {
197		i = 0;
198		count = logcount;
199	} else {
200		i = logptr;
201		count = LOGSIZE;
202	}
203	for (; count > 0; count--) {
204		int opnum;
205
206		opnum = i + 1 + (logcount / LOGSIZE) * LOGSIZE;
207		lp = &oplog[i];
208		prt("%d: %lu.%06lu ", opnum, lp->tv.tv_sec, lp->tv.tv_usec);
209
210		switch (lp->operation) {
211		case OP_MAPREAD:
212			prt("MAPREAD  0x%x thru 0x%x (0x%x bytes)",
213			    lp->args[0], lp->args[0] + lp->args[1] - 1,
214			    lp->args[1]);
215			if (badoff >= lp->args[0] && badoff <
216			    lp->args[0] + lp->args[1])
217				prt("\t***RRRR***");
218			break;
219		case OP_MAPWRITE:
220			prt("MAPWRITE 0x%x thru 0x%x (0x%x bytes)",
221			    lp->args[0], lp->args[0] + lp->args[1] - 1,
222			    lp->args[1]);
223			if (badoff >= lp->args[0] && badoff <
224			    lp->args[0] + lp->args[1])
225				prt("\t******WWWW");
226			break;
227		case OP_READ:
228			prt("READ     0x%x thru 0x%x (0x%x bytes)",
229			    lp->args[0], lp->args[0] + lp->args[1] - 1,
230			    lp->args[1]);
231			if (badoff >= lp->args[0] &&
232			    badoff < lp->args[0] + lp->args[1])
233				prt("\t***RRRR***");
234			break;
235		case OP_WRITE:
236			prt("WRITE    0x%x thru 0x%x (0x%x bytes)",
237			    lp->args[0], lp->args[0] + lp->args[1] - 1,
238			    lp->args[1]);
239			if (lp->args[0] > lp->args[2])
240				prt(" HOLE");
241			else if (lp->args[0] + lp->args[1] > lp->args[2])
242				prt(" EXTEND");
243			if ((badoff >= lp->args[0] || badoff >= lp->args[2]) &&
244			    badoff < lp->args[0] + lp->args[1])
245				prt("\t***WWWW");
246			break;
247		case OP_TRUNCATE:
248			down = lp->args[0] < lp->args[1];
249			prt("TRUNCATE %s\tfrom 0x%x to 0x%x",
250			    down ? "DOWN" : "UP", lp->args[1], lp->args[0]);
251			if (badoff >= lp->args[!down] &&
252			    badoff < lp->args[! !down])
253				prt("\t******WWWW");
254			break;
255		case OP_CLOSEOPEN:
256			prt("CLOSE/OPEN");
257			break;
258		case OP_SKIPPED:
259			prt("SKIPPED (no operation)");
260			break;
261		default:
262			prt("BOGUS LOG ENTRY (operation code = %d)!",
263			    lp->operation);
264		}
265		prt("\n");
266		i++;
267		if (i == LOGSIZE)
268			i = 0;
269	}
270}
271
272void save_buffer(char *buffer, off_t bufferlength, int fd)
273{
274	off_t ret;
275	ssize_t byteswritten;
276
277	if (fd <= 0 || bufferlength == 0)
278		return;
279
280	if (bufferlength > INT_MAX) {
281		prt("fsx flaw: overflow in save_buffer\n");
282		exit(67);
283	}
284	if (lite) {
285		off_t size_by_seek = lseek(fd, (off_t) 0, SEEK_END);
286		if (size_by_seek == (off_t) - 1)
287			prterr("save_buffer: lseek eof");
288		else if (bufferlength > size_by_seek) {
289			warn("save_buffer: .fsxgood file too short... will"
290			     "save 0x%llx bytes instead of 0x%llx\n",
291			     (unsigned long long)size_by_seek,
292			     (unsigned long long)bufferlength);
293			bufferlength = size_by_seek;
294		}
295	}
296
297	ret = lseek(fd, (off_t) 0, SEEK_SET);
298	if (ret == (off_t) - 1)
299		prterr("save_buffer: lseek 0");
300
301	byteswritten = write(fd, buffer, (size_t) bufferlength);
302	if (byteswritten != bufferlength) {
303		if (byteswritten == -1)
304			prterr("save_buffer write");
305		else
306			warn("save_buffer: short write, 0x%x bytes instead"
307			     "of 0x%llx\n",
308			     (unsigned)byteswritten,
309			     (unsigned long long)bufferlength);
310	}
311}
312
313void report_failure(int status)
314{
315	logdump();
316
317	if (fsxgoodfd) {
318		if (good_buf) {
319			save_buffer(good_buf, file_size, fsxgoodfd);
320			prt("Correct content saved for comparison\n");
321			prt("(maybe hexdump \"%s\" vs \"%s\")\n",
322			    fname, goodfile);
323		}
324		close(fsxgoodfd);
325	}
326	exit(status);
327}
328
329#define short_at(cp) ((unsigned short)((*((unsigned char *)(cp)) << 8) | \
330				        *(((unsigned char *)(cp)) + 1)))
331
332void check_buffers(unsigned offset, unsigned size)
333{
334	unsigned char c, t;
335	unsigned i = 0;
336	unsigned n = 0;
337	unsigned op = 0;
338	unsigned bad = 0;
339
340	if (memcmp(good_buf + offset, temp_buf, size) != 0) {
341		prt("READ BAD DATA: offset = 0x%x, size = 0x%x\n",
342		    offset, size);
343		prt("OFFSET\tGOOD\tBAD\tRANGE\n");
344		while (size > 0) {
345			c = good_buf[offset];
346			t = temp_buf[i];
347			if (c != t) {
348				if (n == 0) {
349					bad = short_at(&temp_buf[i]);
350					prt("%#07x\t%#06x\t%#06x", offset,
351					    short_at(&good_buf[offset]), bad);
352					op = temp_buf[offset & 1 ? i + 1 : i];
353				}
354				n++;
355				badoff = offset;
356			}
357			offset++;
358			i++;
359			size--;
360		}
361		if (n) {
362			prt("\t%#7x\n", n);
363			if (bad)
364				prt("operation# (mod 256) for the bad data"
365				    "may be %u\n", ((unsigned)op & 0xff));
366			else
367				prt("operation# (mod 256) for the bad data"
368				    "unknown, check HOLE and EXTEND ops\n");
369		} else
370			prt("????????????????\n");
371		report_failure(110);
372	}
373}
374
375struct test_file {
376	char *path;
377	int fd;
378} *test_files = NULL;
379
380int num_test_files = 0;
381enum fd_iteration_policy {
382	FD_SINGLE,
383	FD_ROTATE,
384	FD_RANDOM,
385};
386int fd_policy = FD_RANDOM;
387int fd_last = 0;
388
389struct test_file *get_tf(void)
390{
391	unsigned index = 0;
392
393	switch (fd_policy) {
394	case FD_ROTATE:
395		index = fd_last++;
396		break;
397	case FD_RANDOM:
398		index = random();
399		break;
400	case FD_SINGLE:
401		index = 0;
402		break;
403	default:
404		prt("unknown policy");
405		exit(1);
406		break;
407	}
408	return &test_files[index % num_test_files];
409}
410
411void assign_fd_policy(char *policy)
412{
413	if (!strcmp(policy, "random"))
414		fd_policy = FD_RANDOM;
415	else if (!strcmp(policy, "rotate"))
416		fd_policy = FD_ROTATE;
417	else {
418		prt("unknown -I policy: '%s'\n", policy);
419		exit(1);
420	}
421}
422
423int get_fd(void)
424{
425	struct test_file *tf = get_tf();
426	return tf->fd;
427}
428
429void open_test_files(char **argv, int argc)
430{
431	struct test_file *tf;
432	int i;
433
434	num_test_files = argc;
435	if (num_test_files == 1)
436		fd_policy = FD_SINGLE;
437
438	test_files = calloc(num_test_files, sizeof(*test_files));
439	if (test_files == NULL) {
440		prterr("reallocating space for test files");
441		exit(1);
442	}
443
444	for (i = 0, tf = test_files; i < num_test_files; i++, tf++) {
445
446		tf->path = argv[i];
447		tf->fd = open(tf->path, O_RDWR | (lite ? 0 : O_CREAT | O_TRUNC),
448			      0666);
449		if (tf->fd < 0) {
450			prterr(tf->path);
451			exit(91);
452		}
453	}
454
455	if (quiet || fd_policy == FD_SINGLE)
456		return;
457
458	for (i = 0, tf = test_files; i < num_test_files; i++, tf++)
459		prt("fd %d: %s\n", i, tf->path);
460}
461
462void close_test_files(void)
463{
464	int i;
465	struct test_file *tf;
466
467	for (i = 0, tf = test_files; i < num_test_files; i++, tf++) {
468		if (close(tf->fd)) {
469			prterr("close");
470			report_failure(99);
471		}
472	}
473}
474
475void check_size(void)
476{
477	struct stat statbuf;
478	off_t size_by_seek;
479	int fd = get_fd();
480
481	if (fstat(fd, &statbuf)) {
482		prterr("check_size: fstat");
483		statbuf.st_size = -1;
484	}
485	size_by_seek = lseek(fd, (off_t) 0, SEEK_END);
486	if (file_size != statbuf.st_size || file_size != size_by_seek) {
487		prt("Size error: expected 0x%llx stat 0x%llx seek 0x%llx\n",
488		    (unsigned long long)file_size,
489		    (unsigned long long)statbuf.st_size,
490		    (unsigned long long)size_by_seek);
491		report_failure(120);
492	}
493}
494
495void check_trunc_hack(void)
496{
497	struct stat statbuf;
498	int fd = get_fd();
499
500	ftruncate(fd, (off_t) 0);
501	ftruncate(fd, (off_t) 100000);
502	if (fstat(fd, &statbuf)) {
503		prterr("trunc_hack: fstat");
504		statbuf.st_size = -1;
505	}
506	if (statbuf.st_size != (off_t) 100000) {
507		prt("no extend on truncate! not posix!\n");
508		exit(130);
509	}
510	ftruncate(fd, 0);
511}
512
513static char *tf_buf = NULL;
514static int max_tf_len = 0;
515
516void alloc_tf_buf(void)
517{
518	char dummy = '\0';
519	int highest = num_test_files - 1;
520	int len;
521
522	len = snprintf(&dummy, 0, "%u ", highest);
523	if (len < 1) {
524		prterr("finding max tf_buf");
525		exit(1);
526	}
527	len++;
528	tf_buf = malloc(len);
529	if (tf_buf == NULL) {
530		prterr("allocating tf_buf");
531		exit(1);
532	}
533	max_tf_len = snprintf(tf_buf, len, "%u ", highest);
534	if (max_tf_len < 1) {
535		prterr("fiding max_tv_len\n");
536		exit(1);
537	}
538	if (max_tf_len != len - 1) {
539		warn("snprintf() gave %d instead of %d?\n",
540		     max_tf_len, len - 1);
541		exit(1);
542	}
543}
544
545char *fill_tf_buf(struct test_file *tf)
546{
547	if (tf_buf == NULL)
548		alloc_tf_buf();
549
550	sprintf(tf_buf, "%lu ", (unsigned long)(tf - test_files));
551	return tf_buf;
552}
553
554void
555output_line(struct test_file *tf, int op, unsigned offset,
556	    unsigned size, struct timeval *tv)
557{
558	char *tf_num = "";
559
560	char *ops[] = {
561		[OP_READ] = "read",
562		[OP_WRITE] = "write",
563		[OP_TRUNCATE] = "trunc from",
564		[OP_MAPREAD] = "mapread",
565		[OP_MAPWRITE] = "mapwrite",
566	};
567
568	/* W. */
569	if (!(!quiet && ((progressinterval &&
570			  testcalls % progressinterval == 0) ||
571			 (debug &&
572			  (monitorstart == -1 ||
573			   (offset + size > monitorstart &&
574			    (monitorend == -1 || offset <= monitorend)))))))
575		return;
576
577	if (fd_policy != FD_SINGLE)
578		tf_num = fill_tf_buf(tf);
579
580	prt("%06lu %lu.%06lu %.*s%-10s %#08x %s %#08x\t(0x%x bytes)\n",
581	    testcalls, tv->tv_sec, tv->tv_usec, max_tf_len,
582	    tf_num, ops[op],
583	    offset, op == OP_TRUNCATE ? " to " : "thru",
584	    offset + size - 1, size);
585}
586
587void doread(unsigned offset, unsigned size)
588{
589	struct timeval t;
590	off_t ret;
591	unsigned iret;
592	struct test_file *tf = get_tf();
593	int fd = tf->fd;
594
595	offset -= offset % readbdy;
596	gettimeofday(&t, NULL);
597	if (size == 0) {
598		if (!quiet && testcalls > simulatedopcount)
599			prt("skipping zero size read\n");
600		log4(OP_SKIPPED, OP_READ, offset, size, &t);
601		return;
602	}
603	if (size + offset > file_size) {
604		if (!quiet && testcalls > simulatedopcount)
605			prt("skipping seek/read past end of file\n");
606		log4(OP_SKIPPED, OP_READ, offset, size, &t);
607		return;
608	}
609
610	log4(OP_READ, offset, size, 0, &t);
611
612	if (testcalls <= simulatedopcount)
613		return;
614
615	output_line(tf, OP_READ, offset, size, &t);
616
617	ret = lseek(fd, (off_t) offset, SEEK_SET);
618	if (ret == (off_t) - 1) {
619		prterr("doread: lseek");
620		report_failure(140);
621	}
622	iret = read(fd, temp_buf, size);
623	if (!quiet && (debug > 1 &&
624		       (monitorstart == -1 ||
625			(offset + size > monitorstart &&
626			 (monitorend == -1 || offset <= monitorend))))) {
627		gettimeofday(&t, NULL);
628		prt("       %lu.%06lu read done\n", t.tv_sec, t.tv_usec);
629	}
630	if (iret != size) {
631		if (iret == -1)
632			prterr("doread: read");
633		else
634			prt("short read: 0x%x bytes instead of 0x%x\n",
635			    iret, size);
636		report_failure(141);
637	}
638	check_buffers(offset, size);
639}
640
641void domapread(unsigned offset, unsigned size)
642{
643	struct timeval t;
644	unsigned pg_offset;
645	unsigned map_size;
646	char *p;
647	struct test_file *tf = get_tf();
648	int fd = tf->fd;
649
650	offset -= offset % readbdy;
651	gettimeofday(&t, NULL);
652	if (size == 0) {
653		if (!quiet && testcalls > simulatedopcount)
654			prt("skipping zero size read\n");
655		log4(OP_SKIPPED, OP_MAPREAD, offset, size, &t);
656		return;
657	}
658	if (size + offset > file_size) {
659		if (!quiet && testcalls > simulatedopcount)
660			prt("skipping seek/read past end of file\n");
661		log4(OP_SKIPPED, OP_MAPREAD, offset, size, &t);
662		return;
663	}
664
665	log4(OP_MAPREAD, offset, size, 0, &t);
666
667	if (testcalls <= simulatedopcount)
668		return;
669
670	output_line(tf, OP_MAPREAD, offset, size, &t);
671
672	pg_offset = offset & page_mask;
673	map_size = pg_offset + size;
674
675	if ((p = mmap(0, map_size, PROT_READ, MAP_FILE | MAP_SHARED, fd,
676		      (off_t) (offset - pg_offset))) == MAP_FAILED) {
677		prterr("domapread: mmap");
678		report_failure(190);
679	}
680	if (!quiet && (debug > 1 &&
681		       (monitorstart == -1 ||
682			(offset + size > monitorstart &&
683			 (monitorend == -1 || offset <= monitorend))))) {
684		gettimeofday(&t, NULL);
685		prt("       %lu.%06lu mmap done\n", t.tv_sec, t.tv_usec);
686	}
687	memcpy(temp_buf, p + pg_offset, size);
688	if (!quiet && (debug > 1 &&
689		       (monitorstart == -1 ||
690			(offset + size > monitorstart &&
691			 (monitorend == -1 || offset <= monitorend))))) {
692		gettimeofday(&t, NULL);
693		prt("       %lu.%06lu memcpy done\n", t.tv_sec, t.tv_usec);
694	}
695	if (munmap(p, map_size) != 0) {
696		prterr("domapread: munmap");
697		report_failure(191);
698	}
699	if (!quiet && (debug > 1 &&
700		       (monitorstart == -1 ||
701			(offset + size > monitorstart &&
702			 (monitorend == -1 || offset <= monitorend))))) {
703		gettimeofday(&t, NULL);
704		prt("       %lu.%06lu munmap done\n", t.tv_sec, t.tv_usec);
705	}
706
707	check_buffers(offset, size);
708}
709
710void gendata(char *original_buf, char *good_buf, unsigned offset, unsigned size)
711{
712	while (size--) {
713		good_buf[offset] = testcalls % 256;
714		if (offset % 2)
715			good_buf[offset] += original_buf[offset];
716		offset++;
717	}
718}
719
720void dowrite(unsigned offset, unsigned size)
721{
722	struct timeval t;
723	off_t ret;
724	unsigned iret;
725	struct test_file *tf = get_tf();
726	int fd = tf->fd;
727
728	offset -= offset % writebdy;
729	gettimeofday(&t, NULL);
730	if (size == 0) {
731		if (!quiet && testcalls > simulatedopcount)
732			prt("skipping zero size write\n");
733		log4(OP_SKIPPED, OP_WRITE, offset, size, &t);
734		return;
735	}
736
737	log4(OP_WRITE, offset, size, file_size, &t);
738
739	gendata(original_buf, good_buf, offset, size);
740	if (file_size < offset + size) {
741		if (file_size < offset)
742			memset(good_buf + file_size, '\0', offset - file_size);
743		file_size = offset + size;
744		if (lite) {
745			warn("Lite file size bug in fsx!");
746			report_failure(149);
747		}
748	}
749
750	if (testcalls <= simulatedopcount)
751		return;
752
753	output_line(tf, OP_WRITE, offset, size, &t);
754
755	ret = lseek(fd, (off_t) offset, SEEK_SET);
756	if (ret == (off_t) - 1) {
757		prterr("dowrite: lseek");
758		report_failure(150);
759	}
760	iret = write(fd, good_buf + offset, size);
761	if (!quiet && (debug > 1 &&
762		       (monitorstart == -1 ||
763			(offset + size > monitorstart &&
764			 (monitorend == -1 || offset <= monitorend))))) {
765		gettimeofday(&t, NULL);
766		prt("       %lu.%06lu write done\n", t.tv_sec, t.tv_usec);
767	}
768	if (iret != size) {
769		if (iret == -1)
770			prterr("dowrite: write");
771		else
772			prt("short write: 0x%x bytes instead of 0x%x\n",
773			    iret, size);
774		report_failure(151);
775	}
776}
777
778void domapwrite(unsigned offset, unsigned size)
779{
780	struct timeval t;
781	unsigned pg_offset;
782	unsigned map_size;
783	off_t cur_filesize;
784	char *p;
785	struct test_file *tf = get_tf();
786	int fd = tf->fd;
787
788	offset -= offset % writebdy;
789	gettimeofday(&t, NULL);
790	if (size == 0) {
791		if (!quiet && testcalls > simulatedopcount)
792			prt("skipping zero size write\n");
793		log4(OP_SKIPPED, OP_MAPWRITE, offset, size, &t);
794		return;
795	}
796	cur_filesize = file_size;
797
798	log4(OP_MAPWRITE, offset, size, 0, &t);
799
800	gendata(original_buf, good_buf, offset, size);
801	if (file_size < offset + size) {
802		if (file_size < offset)
803			memset(good_buf + file_size, '\0', offset - file_size);
804		file_size = offset + size;
805		if (lite) {
806			warn("Lite file size bug in fsx!");
807			report_failure(200);
808		}
809	}
810
811	if (testcalls <= simulatedopcount)
812		return;
813
814	output_line(tf, OP_MAPWRITE, offset, size, &t);
815
816	if (file_size > cur_filesize) {
817		if (ftruncate(fd, file_size) == -1) {
818			prterr("domapwrite: ftruncate");
819			exit(201);
820		}
821		if (!quiet && (debug > 1 &&
822			       (monitorstart == -1 ||
823				(offset + size > monitorstart &&
824				 (monitorend == -1
825				  || offset <= monitorend))))) {
826			gettimeofday(&t, NULL);
827			prt("       %lu.%06lu truncate done\n", t.tv_sec,
828			    t.tv_usec);
829		}
830	}
831	pg_offset = offset & page_mask;
832	map_size = pg_offset + size;
833
834	if ((p =
835	     mmap(0, map_size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED,
836		  fd, (off_t) (offset - pg_offset))) == MAP_FAILED) {
837		prterr("domapwrite: mmap");
838		report_failure(202);
839	}
840	if (!quiet && (debug > 1 &&
841		       (monitorstart == -1 ||
842			(offset + size > monitorstart &&
843			 (monitorend == -1 || offset <= monitorend))))) {
844		gettimeofday(&t, NULL);
845		prt("       %lu.%06lu mmap done\n", t.tv_sec, t.tv_usec);
846	}
847	memcpy(p + pg_offset, good_buf + offset, size);
848	if (!quiet && (debug > 1 &&
849		       (monitorstart == -1 ||
850			(offset + size > monitorstart &&
851			 (monitorend == -1 || offset <= monitorend))))) {
852		gettimeofday(&t, NULL);
853		prt("       %lu.%06lu memcpy done\n", t.tv_sec, t.tv_usec);
854	}
855	if (msync(p, map_size, 0) != 0) {
856		prterr("domapwrite: msync");
857		report_failure(203);
858	}
859	if (!quiet && (debug > 1 &&
860		       (monitorstart == -1 ||
861			(offset + size > monitorstart &&
862			 (monitorend == -1 || offset <= monitorend))))) {
863		gettimeofday(&t, NULL);
864		prt("       %lu.%06lu msync done\n", t.tv_sec, t.tv_usec);
865	}
866	if (munmap(p, map_size) != 0) {
867		prterr("domapwrite: munmap");
868		report_failure(204);
869	}
870	if (!quiet && (debug > 1 &&
871		       (monitorstart == -1 ||
872			(offset + size > monitorstart &&
873			 (monitorend == -1 || offset <= monitorend))))) {
874		gettimeofday(&t, NULL);
875		prt("       %lu.%06lu munmap done\n", t.tv_sec, t.tv_usec);
876	}
877}
878
879void dotruncate(unsigned size)
880{
881	struct timeval t;
882	int oldsize = file_size;
883	struct test_file *tf = get_tf();
884	int fd = tf->fd;
885
886	size -= size % truncbdy;
887	gettimeofday(&t, NULL);
888	if (size > biggest) {
889		biggest = size;
890		if (!quiet && testcalls > simulatedopcount)
891			prt("truncating to largest ever: 0x%x\n", size);
892	}
893
894	log4(OP_TRUNCATE, size, (unsigned)file_size, 0, &t);
895
896	if (size > file_size)
897		memset(good_buf + file_size, '\0', size - file_size);
898	file_size = size;
899
900	if (testcalls <= simulatedopcount)
901		return;
902
903	output_line(tf, OP_TRUNCATE, oldsize, size, &t);
904
905	if (ftruncate(fd, (off_t) size) == -1) {
906		prt("ftruncate1: %x\n", size);
907		prterr("dotruncate: ftruncate");
908		report_failure(160);
909	}
910	if (!quiet && debug > 1) {
911		gettimeofday(&t, NULL);
912		prt("       %lu.%06lu trunc done\n", t.tv_sec, t.tv_usec);
913	}
914}
915
916void writefileimage()
917{
918	ssize_t iret;
919	int fd = get_fd();
920
921	if (lseek(fd, (off_t) 0, SEEK_SET) == (off_t) - 1) {
922		prterr("writefileimage: lseek");
923		report_failure(171);
924	}
925	iret = write(fd, good_buf, file_size);
926	if ((off_t) iret != file_size) {
927		if (iret == -1)
928			prterr("writefileimage: write");
929		else
930			prt("short write: 0x%lx bytes instead of 0x%llx\n",
931			    (unsigned long)iret, (unsigned long long)file_size);
932		report_failure(172);
933	}
934	if (lite ? 0 : ftruncate(fd, file_size) == -1) {
935		prt("ftruncate2: %llx\n", (unsigned long long)file_size);
936		prterr("writefileimage: ftruncate");
937		report_failure(173);
938	}
939}
940
941void docloseopen(void)
942{
943	struct timeval t;
944	struct test_file *tf = get_tf();
945
946	if (testcalls <= simulatedopcount)
947		return;
948
949	gettimeofday(&t, NULL);
950	log4(OP_CLOSEOPEN, file_size, (unsigned)file_size, 0, &t);
951
952	if (debug)
953		prt("%06lu %lu.%06lu close/open\n", testcalls, t.tv_sec,
954		    t.tv_usec);
955	if (close(tf->fd)) {
956		prterr("docloseopen: close");
957		report_failure(180);
958	}
959	if (!quiet && debug > 1) {
960		gettimeofday(&t, NULL);
961		prt("       %lu.%06lu close done\n", t.tv_sec, t.tv_usec);
962	}
963	tf->fd = open(tf->path, O_RDWR, 0);
964	if (tf->fd < 0) {
965		prterr("docloseopen: open");
966		report_failure(181);
967	}
968	if (!quiet && debug > 1) {
969		gettimeofday(&t, NULL);
970		prt("       %lu.%06lu open done\n", t.tv_sec, t.tv_usec);
971	}
972}
973
974void test(void)
975{
976	unsigned long offset;
977	unsigned long size = maxoplen;
978	unsigned long rv = random();
979	unsigned long op = rv % (3 + !lite + mapped_writes);
980
981	/* turn off the map read if necessary */
982
983	if (op == 2 && !mapped_reads)
984		op = 0;
985
986	if (simulatedopcount > 0 && testcalls == simulatedopcount)
987		writefileimage();
988
989	testcalls++;
990
991	if (debugstart > 0 && testcalls >= debugstart)
992		debug = 1;
993
994	if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0)
995		prt("%lu...\n", testcalls);
996
997	/*
998	 * READ:        op = 0
999	 * WRITE:       op = 1
1000	 * MAPREAD:     op = 2
1001	 * TRUNCATE:    op = 3
1002	 * MAPWRITE:    op = 3 or 4
1003	 */
1004	if (lite ? 0 : op == 3 && (style & 1) == 0)	/* vanilla truncate? */
1005		dotruncate(random() % maxfilelen);
1006	else {
1007		if (randomoplen)
1008			size = random() % (maxoplen + 1);
1009		if (lite ? 0 : op == 3)
1010			dotruncate(size);
1011		else {
1012			offset = random();
1013			if (op == 1 || op == (lite ? 3 : 4)) {
1014				offset %= maxfilelen;
1015				if (offset + size > maxfilelen)
1016					size = maxfilelen - offset;
1017				if (op != 1)
1018					domapwrite(offset, size);
1019				else
1020					dowrite(offset, size);
1021			} else {
1022				if (file_size)
1023					offset %= file_size;
1024				else
1025					offset = 0;
1026				if (offset + size > file_size)
1027					size = file_size - offset;
1028				if (op != 0)
1029					domapread(offset, size);
1030				else
1031					doread(offset, size);
1032			}
1033		}
1034	}
1035	if (sizechecks && testcalls > simulatedopcount)
1036		check_size();
1037	if (closeprob && (rv >> 3) < (1 << 28) / closeprob)
1038		docloseopen();
1039}
1040
1041void cleanup(sig)
1042int sig;
1043{
1044	if (sig)
1045		prt("signal %d\n", sig);
1046	prt("testcalls = %lu\n", testcalls);
1047	exit(sig);
1048}
1049
1050void usage(void)
1051{
1052	fprintf(stdout, "usage: %s",
1053		"fsx [-dnqLOW] [-b opnum] [-c Prob] [-l flen] [-m "
1054		"start:end] [-o oplen] [-p progressinterval] [-r readbdy] [-s style] [-t "
1055		"truncbdy] [-w writebdy] [-D startingop] [-N numops] [-P dirpath] [-S seed] "
1056		"[ -I random|rotate ] fname [additional paths to fname..]\n"
1057		"	-b opnum: beginning operation number (default 1)\n"
1058		"	-c P: 1 in P chance of file close+open at each op (default infinity)\n"
1059		"	-d: debug output for all operations [-d -d = more debugging]\n"
1060		"	-l flen: the upper bound on file size (default 262144)\n"
1061		"	-m start:end: monitor (print debug) specified byte range (default 0:infinity)\n"
1062		"	-n: no verifications of file size\n"
1063		"	-o oplen: the upper bound on operation size (default 65536)\n"
1064		"	-p progressinterval: debug output at specified operation interval\n"
1065		"	-q: quieter operation\n"
1066		"	-r readbdy: 4096 would make reads page aligned (default 1)\n"
1067		"	-s style: 1 gives smaller truncates (default 0)\n"
1068		"	-t truncbdy: 4096 would make truncates page aligned (default 1)\n"
1069		"	-w writebdy: 4096 would make writes page aligned (default 1)\n"
1070		"	-D startingop: debug output starting at specified operation\n"
1071		"	-L: fsxLite - no file creations & no file size changes\n"
1072		"	-N numops: total # operations to do (default infinity)\n"
1073		"	-O: use oplen (see -o flag) for every op (default random)\n"
1074		"	-P: save .fsxlog and .fsxgood files in dirpath (default ./)\n"
1075		"	-S seed: for random # generator (default 1) 0 gets timestamp\n"
1076		"	-W: mapped write operations DISabled\n"
1077		"	-R: read() system calls only (mapped reads disabled)\n"
1078		"	-I: When multiple paths to the file are given each operation uses\n"
1079		"	    a different path.  Iterate through them in order with 'rotate'\n"
1080		"	    or chose then at 'random'.  (defaults to random)\n"
1081		"	fname: this filename is REQUIRED (no default)\n");
1082	exit(90);
1083}
1084
1085int getnum(char *s, char **e)
1086{
1087	int ret = -1;
1088
1089	*e = NULL;
1090	ret = strtol(s, e, 0);
1091	if (*e)
1092		switch (**e) {
1093		case 'b':
1094		case 'B':
1095			ret *= 512;
1096			*e = *e + 1;
1097			break;
1098		case 'k':
1099		case 'K':
1100			ret *= 1024;
1101			*e = *e + 1;
1102			break;
1103		case 'm':
1104		case 'M':
1105			ret *= 1024 * 1024;
1106			*e = *e + 1;
1107			break;
1108		case 'w':
1109		case 'W':
1110			ret *= 4;
1111			*e = *e + 1;
1112			break;
1113		}
1114	return (ret);
1115}
1116
1117int main(int argc, char **argv)
1118{
1119	int i, style, ch;
1120	char *endp;
1121	int dirpath = 0;
1122
1123	goodfile[0] = 0;
1124	logfile[0] = 0;
1125
1126	page_size = getpagesize();
1127	page_mask = page_size - 1;
1128
1129	setvbuf(stdout, NULL, _IOLBF, 0);	/* line buffered stdout */
1130
1131	while ((ch = getopt(argc, argv,
1132			    "b:c:dl:m:no:p:qr:s:t:w:D:I:LN:OP:RS:W"))
1133	       != EOF)
1134		switch (ch) {
1135		case 'b':
1136			simulatedopcount = getnum(optarg, &endp);
1137			if (!quiet)
1138				fprintf(stdout, "Will begin at operation"
1139					"%ld\n", simulatedopcount);
1140			if (simulatedopcount == 0)
1141				usage();
1142			simulatedopcount -= 1;
1143			break;
1144		case 'c':
1145			closeprob = getnum(optarg, &endp);
1146			if (!quiet)
1147				fprintf(stdout,
1148					"Chance of close/open is 1 in %d\n",
1149					closeprob);
1150			if (closeprob <= 0)
1151				usage();
1152			break;
1153		case 'd':
1154			debug++;
1155			break;
1156		case 'l':
1157			maxfilelen = getnum(optarg, &endp);
1158			if (maxfilelen <= 0)
1159				usage();
1160			break;
1161		case 'm':
1162			monitorstart = getnum(optarg, &endp);
1163			if (monitorstart < 0)
1164				usage();
1165			if (!endp || *endp++ != ':')
1166				usage();
1167			monitorend = getnum(endp, &endp);
1168			if (monitorend < 0)
1169				usage();
1170			if (monitorend == 0)
1171				monitorend = -1;	/* aka infinity */
1172			debug = 1;
1173		case 'n':
1174			sizechecks = 0;
1175			break;
1176		case 'o':
1177			maxoplen = getnum(optarg, &endp);
1178			if (maxoplen <= 0)
1179				usage();
1180			break;
1181		case 'p':
1182			progressinterval = getnum(optarg, &endp);
1183			if (progressinterval < 0)
1184				usage();
1185			break;
1186		case 'q':
1187			quiet = 1;
1188			break;
1189		case 'r':
1190			readbdy = getnum(optarg, &endp);
1191			if (readbdy <= 0)
1192				usage();
1193			break;
1194		case 's':
1195			style = getnum(optarg, &endp);
1196			if (style < 0 || style > 1)
1197				usage();
1198			break;
1199		case 't':
1200			truncbdy = getnum(optarg, &endp);
1201			if (truncbdy <= 0)
1202				usage();
1203			break;
1204		case 'w':
1205			writebdy = getnum(optarg, &endp);
1206			if (writebdy <= 0)
1207				usage();
1208			break;
1209		case 'D':
1210			debugstart = getnum(optarg, &endp);
1211			if (debugstart < 1)
1212				usage();
1213			break;
1214		case 'I':
1215			assign_fd_policy(optarg);
1216			break;
1217		case 'L':
1218			lite = 1;
1219			break;
1220		case 'N':
1221			numops = getnum(optarg, &endp);
1222			if (numops < 0)
1223				usage();
1224			break;
1225		case 'O':
1226			randomoplen = 0;
1227			break;
1228		case 'P':
1229			strncpy(goodfile, optarg, sizeof(goodfile));
1230			strcat(goodfile, "/");
1231			strncpy(logfile, optarg, sizeof(logfile));
1232			strcat(logfile, "/");
1233			dirpath = 1;
1234			break;
1235		case 'R':
1236			mapped_reads = 0;
1237			break;
1238		case 'S':
1239			seed = getnum(optarg, &endp);
1240			if (seed == 0)
1241				seed = time(0) % 10000;
1242			if (!quiet)
1243				fprintf(stdout, "Seed set to %d\n", seed);
1244			if (seed < 0)
1245				usage();
1246			break;
1247		case 'W':
1248			mapped_writes = 0;
1249			if (!quiet)
1250				fprintf(stdout, "mapped writes DISABLED\n");
1251			break;
1252
1253		default:
1254			usage();
1255
1256		}
1257	argc -= optind;
1258	argv += optind;
1259	if (argc < 1)
1260		usage();
1261	fname = argv[0];
1262
1263	signal(SIGHUP, cleanup);
1264	signal(SIGINT, cleanup);
1265	signal(SIGPIPE, cleanup);
1266	signal(SIGALRM, cleanup);
1267	signal(SIGTERM, cleanup);
1268	signal(SIGXCPU, cleanup);
1269	signal(SIGXFSZ, cleanup);
1270	signal(SIGVTALRM, cleanup);
1271	signal(SIGUSR1, cleanup);
1272	signal(SIGUSR2, cleanup);
1273
1274	initstate(seed, state, 256);
1275	setstate(state);
1276
1277	open_test_files(argv, argc);
1278
1279	strncat(goodfile, dirpath ? basename(fname) : fname, 256);
1280	strcat(goodfile, ".fsxgood");
1281	fsxgoodfd = open(goodfile, O_RDWR | O_CREAT | O_TRUNC, 0666);
1282	if (fsxgoodfd < 0) {
1283		prterr(goodfile);
1284		exit(92);
1285	}
1286	strncat(logfile, dirpath ? basename(fname) : fname, 256);
1287	strcat(logfile, ".fsxlog");
1288	fsxlogf = fopen(logfile, "w");
1289	if (fsxlogf == NULL) {
1290		prterr(logfile);
1291		exit(93);
1292	}
1293	if (lite) {
1294		off_t ret;
1295		int fd = get_fd();
1296		file_size = maxfilelen = lseek(fd, (off_t) 0, SEEK_END);
1297		if (file_size == (off_t) - 1) {
1298			prterr(fname);
1299			warn("main: lseek eof");
1300			exit(94);
1301		}
1302		ret = lseek(fd, (off_t) 0, SEEK_SET);
1303		if (ret == (off_t) - 1) {
1304			prterr(fname);
1305			warn("main: lseek 0");
1306			exit(95);
1307		}
1308	}
1309	original_buf = malloc(maxfilelen);
1310	if (original_buf == NULL)
1311		exit(96);
1312	for (i = 0; i < maxfilelen; i++)
1313		original_buf[i] = random() % 256;
1314
1315	good_buf = malloc(maxfilelen);
1316	if (good_buf == NULL)
1317		exit(97);
1318	memset(good_buf, '\0', maxfilelen);
1319
1320	temp_buf = malloc(maxoplen);
1321	if (temp_buf == NULL)
1322		exit(99);
1323	memset(temp_buf, '\0', maxoplen);
1324
1325	if (lite) {		/* zero entire existing file */
1326		ssize_t written;
1327		int fd = get_fd();
1328
1329		written = write(fd, good_buf, (size_t) maxfilelen);
1330		if (written != maxfilelen) {
1331			if (written == -1) {
1332				prterr(fname);
1333				warn("main: error on write");
1334			} else
1335				warn("main: short write, 0x%x bytes instead"
1336				     "of 0x%x\n",
1337				     (unsigned)written, maxfilelen);
1338			exit(98);
1339		}
1340	} else
1341		check_trunc_hack();
1342
1343	while (numops == -1 || numops--)
1344		test();
1345
1346	close_test_files();
1347	prt("All operations completed A-OK!\n");
1348
1349	if (tf_buf)
1350		free(tf_buf);
1351
1352	free(original_buf);
1353	free(good_buf);
1354	free(temp_buf);
1355
1356	return 0;
1357}
1358