Subject: Re: [jkh@winston.freebsd.org: NFS: How to make FreeBSD fall on
To: void <float@firedrake.org>
From: Conrad Minshall <conrad@apple.com>
List: tech-kern
Date: 12/15/2001 11:06:30
The attached code also has a divide by zero bug.  Please make this change
to your copy:

diff -u -d -b -w -r1.21 fsx.c
--- fsx.c       2001/12/11 23:27:20     1.21
+++ fsx.c       2001/12/15 15:31:19
@@ -701,6 +701,7 @@

        testcalls++;

+       if (closeprob)
        closeopen = (rv >> 3) < (1 << 28) / closeprob;

        if (debugstart > 0 && testcalls >= debugstart)


At 11:15 AM -0800 12/13/01, void wrote:
>The attached code has exposed several bugs in FreeBSD's NFS client
>code, and at least one general filesystem bug, related to soft updates.
>The full details are in the freebsd-hackers archive.
>
>--
> Ben
>
>"An art scene of delight
> I created this to be ..."		-- Sun Ra
>
>Return-path: <owner-freebsd-hackers@FreeBSD.ORG>
>Envelope-to: float@firedrake.org
>Received: from mx2.freebsd.org ([216.136.204.119])
>	by parhelion.firedrake.org with esmtp (Exim 3.32 #1 (Debian))
>	id 16ECCT-0001C6-00
>	for <float@firedrake.org>; Wed, 12 Dec 2001 16:25:41 +0000
>Received: from hub.freebsd.org (hub.FreeBSD.org [216.136.204.18])
>	by mx2.freebsd.org (Postfix) with ESMTP
>	id 819F15545D; Wed, 12 Dec 2001 08:25:00 -0800 (PST)
>	(envelope-from owner-freebsd-hackers@FreeBSD.ORG)
>Received: by hub.freebsd.org (Postfix, from userid 538)
>	id D111137B405; Wed, 12 Dec 2001 08:24:57 -0800 (PST)
>Received: from localhost (localhost [127.0.0.1])
>	by hub.freebsd.org (Postfix) with SMTP
>	id A4F252E8013; Wed, 12 Dec 2001 08:24:57 -0800 (PST)
>Received: by hub.freebsd.org (bulk_mailer v1.12); Wed, 12 Dec 2001
>08:24:57 -0800
>Received: from winston.freebsd.org
>(adsl-64-173-15-98.dsl.sntc01.pacbell.net [64.173.15.98])
>	by hub.freebsd.org (Postfix) with ESMTP id 303D537B417
>	for <hackers@freebsd.org>; Wed, 12 Dec 2001 08:24:22 -0800 (PST)
>Received: (from jkh@localhost)
>	by winston.freebsd.org (8.11.6/8.11.6) id fBCGNuY52543;
>	Wed, 12 Dec 2001 08:23:56 -0800 (PST)
>	(envelope-from jkh)
>Date: Wed, 12 Dec 2001 08:23:56 -0800 (PST)
>From: Jordan Hubbard <jkh@winston.freebsd.org>
>Message-Id: <200112121623.fBCGNuY52543@winston.freebsd.org>
>To: hackers@freebsd.org
>Subject: NFS: How to make FreeBSD fall on its face in one easy step
>Sender: owner-freebsd-hackers@FreeBSD.ORG
>List-ID: <freebsd-hackers.FreeBSD.ORG>
>List-Archive: <http://docs.freebsd.org/mail/> (Web Archive)
>List-Help: <mailto:majordomo@FreeBSD.ORG?subject=help> (List Instructions)
>List-Subscribe:
><mailto:majordomo@FreeBSD.ORG?subject=subscribe%20freebsd-hackers>
>List-Unsubscribe:
><mailto:majordomo@FreeBSD.ORG?subject=unsubscribe%20freebsd-hackers>
>X-Loop: FreeBSD.ORG
>Precedence: bulk
>
>It came up in a meeting today at Apple just how fragile the BSD NFS
>implementation was before significant work was put in to stabilizing it,
>and in that discussion came up a little test tool written originally by
>Avie Tevanian and subsequently improved by one of the folks here.
>
>This tool basically tries to do everything it can (legally) to confuse an
>NFS server.  It seeks around, does I/O to and truncates/changes the size
>of a test file, all while doing everything it can to detect data corruption
>or other signs of misbehavior which might result from out-of-order replies
>or any other previously-observed NFS pathology.  Very few NFS implementations
>apparently survive this test and FreeBSD's is no exception.  The sources are
>provided below, courtesy of Avie, for the education and enjoyment(?) of
>anyone who's motivated to play with (or even pretends to understand) NFS.
>
>Usage:
>	cc fsx.c -o fsx
>	./fsx /some/nfs/mounted/scratchfile
>	[ ** kaboom! ** ]
>
>I'm also trying to determine which of the fixes Apple has made to NFS might
>be adapted to FreeBSD, something which is made more difficult by the fact
>that much of the code was taken straight from 4.4 Lite some time back and
>both operating systems have diverged significantly since then.  Anyone
>really keen on investigating this further themselves on it can also go to
>http://www.opensource.apple.com/projects/darwin and register themselves
>online (it's easy) to access the Darwin CVS repository, the module in
>question being "xnu" (the Darwin kernel).  Thanks.
>
>- Jordan
>
>/*
> *	Copyright (C) 1991, NeXT Computer, Inc.  All Rights Reserverd.
> *
> *	File:	fsx.c
> *	Author:	Avadis Tevanian, Jr.
> *
> *	File system exerciser.
> *
> *	Rewritten 8/98 by Conrad Minshall.
> */
>
>#include <sys/types.h>
>#include <sys/stat.h>
>#ifdef _UWIN
># include <sys/param.h>
># include <limits.h>
># include <time.h>
># include <strings.h>
># define MAP_FILE 0
>#else
># include <sys/dirent.h>
>#endif
>#include <sys/file.h>
>#include <sys/mman.h>
>#include <limits.h>
>#include <err.h>
>#include <signal.h>
>#include <stdio.h>
>#include <stdlib.h>
>#include <string.h>
>#include <unistd.h>
>#include <stdarg.h>
>#include <errno.h>
>
>#define NUMPRINTCOLUMNS 32	/* # columns of data to print on each line */
>
>/*
> *	A log entry is an operation and a bunch of arguments.
> */
>
>struct log_entry {
>	int	operation;
>	int	args[3];
>};
>
>#define	LOGSIZE	1000
>
>struct log_entry	oplog[LOGSIZE];	/* the log */
>int			logptr = 0;	/* current position in log */
>int			logcount = 0;	/* total ops */
>
>/*
> *	Define operations
> */
>
>#define	OP_READ		1
>#define OP_WRITE	2
>#define OP_TRUNCATE	3
>#define OP_CLOSEOPEN	4
>#define OP_MAPREAD	5
>#define OP_MAPWRITE	6
>#define OP_SKIPPED	7
>
>#ifndef PAGE_SIZE
>#define PAGE_SIZE       4096
>#endif
>#define PAGE_MASK       (PAGE_SIZE - 1)
>
>char	*original_buf;			/* a pointer to the original data */
>char	*good_buf;			/* a pointer to the correct data */
>char	*temp_buf;			/* a pointer to the current data */
>char	*fname;				/* name of our test file */
>int	fd;				/* fd for our test file */
>
>off_t		file_size = 0;
>off_t		biggest = 0;
>char		state[256];
>unsigned long	testcalls = 0;		/* calls to function "test" */
>
>unsigned long	simulatedopcount = 0;	/* -b flag */
>int	closeprob = 0;			/* -c flag */
>int	debug = 0;			/* -d flag */
>unsigned long	debugstart = 0;		/* -D flag */
>unsigned long	maxfilelen = 256 * 1024;	/* -l flag */
>int	sizechecks = 1;			/* -n flag disables them */
>int	maxoplen = 64 * 1024;		/* -o flag */
>int	quiet = 0;			/* -q flag */
>unsigned long progressinterval = 0;	/* -p flag */
>int	readbdy = 1;			/* -r flag */
>int	style = 0;			/* -s flag */
>int	truncbdy = 1;			/* -t flag */
>int	writebdy = 1;			/* -w flag */
>long	monitorstart = -1;		/* -m flag */
>long	monitorend = -1;		/* -m flag */
>int	lite = 0;			/* -L flag */
>long	numops = -1;			/* -N flag */
>int	randomoplen = 1;		/* -O flag disables it */
>int	seed = 1;			/* -S flag */
>int     mapped_writes = 1;              /* -W flag disables */
>int 	mapped_reads = 1;		/* -R flag disables it */
>int	fsxgoodfd = 0;
>FILE *	fsxlogf = NULL;
>int badoff = -1;
>int closeopen = 0;
>
>
>void
>prt(char *fmt, ...)
>{
>	va_list args;
>
>	va_start(args, fmt);
>	vfprintf(stdout, fmt, args);
>	if (fsxlogf)
>		vfprintf(fsxlogf, fmt, args);
>	va_end(args);
>}
>
>void
>prterr(char *prefix)
>{
>	prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno));
>}
>
>
>void
>log4(int operation, int arg0, int arg1, int arg2)
>{
>	struct log_entry *le;
>
>	le = &oplog[logptr];
>	le->operation = operation;
>	if (closeopen)
>		le->operation = ~ le->operation;
>	le->args[0] = arg0;
>	le->args[1] = arg1;
>	le->args[2] = arg2;
>	logptr++;
>	logcount++;
>	if (logptr >= LOGSIZE)
>		logptr = 0;
>}
>
>
>void
>logdump(void)
>{
>	int	i, count, down;
>	struct log_entry	*lp;
>
>	prt("LOG DUMP (%d total operations):\n", logcount);
>	if (logcount < LOGSIZE) {
>		i = 0;
>		count = logcount;
>	} else {
>		i = logptr;
>		count = LOGSIZE;
>	}
>	for ( ; count > 0; count--) {
>		int opnum;
>
>		opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE;
>		prt("%d(%d mod 256): ", opnum, opnum%256);
>		lp = &oplog[i];
>		if ((closeopen = lp->operation < 0))
>			lp->operation = ~ lp->operation;
>
>		switch (lp->operation) {
>		case OP_MAPREAD:
>			prt("MAPREAD\t0x%x thru 0x%x\t(0x%x bytes)",
>			    lp->args[0], lp->args[0] + lp->args[1] - 1,
>			    lp->args[1]);
>			if (badoff >= lp->args[0] && badoff <
>						     lp->args[0] + lp->args[1])
>				prt("\t***RRRR***");
>			break;
>		case OP_MAPWRITE:
>			prt("MAPWRITE 0x%x thru 0x%x\t(0x%x bytes)",
>			    lp->args[0], lp->args[0] + lp->args[1] - 1,
>			    lp->args[1]);
>			if (badoff >= lp->args[0] && badoff <
>						     lp->args[0] + lp->args[1])
>				prt("\t******WWWW");
>			break;
>		case OP_READ:
>			prt("READ\t0x%x thru 0x%x\t(0x%x bytes)",
>			    lp->args[0], lp->args[0] + lp->args[1] - 1,
>			    lp->args[1]);
>			if (badoff >= lp->args[0] &&
>			    badoff < lp->args[0] + lp->args[1])
>				prt("\t***RRRR***");
>			break;
>		case OP_WRITE:
>			prt("WRITE\t0x%x thru 0x%x\t(0x%x bytes)",
>			    lp->args[0], lp->args[0] + lp->args[1] - 1,
>			    lp->args[1]);
>			if (lp->args[0] > lp->args[2])
>				prt(" HOLE");
>			else if (lp->args[0] + lp->args[1] > lp->args[2])
>				prt(" EXTEND");
>			if ((badoff >= lp->args[0] || badoff >=lp->args[2]) &&
>			    badoff < lp->args[0] + lp->args[1])
>				prt("\t***WWWW");
>			break;
>		case OP_TRUNCATE:
>			down = lp->args[0] < lp->args[1];
>			prt("TRUNCATE %s\tfrom 0x%x to 0x%x",
>			    down ? "DOWN" : "UP", lp->args[1], lp->args[0]);
>			if (badoff >= lp->args[!down] &&
>			    badoff < lp->args[!!down])
>				prt("\t******WWWW");
>			break;
>		case OP_SKIPPED:
>			prt("SKIPPED (no operation)");
>			break;
>		default:
>			prt("BOGUS LOG ENTRY (operation code = %d)!",
>			    lp->operation);
>		}
>		if (closeopen)
>			prt("\n\t\tCLOSE/OPEN");
>		prt("\n");
>		i++;
>		if (i == LOGSIZE)
>			i = 0;
>	}
>}
>
>
>void
>save_buffer(char *buffer, off_t bufferlength, int fd)
>{
>	off_t ret;
>	ssize_t byteswritten;
>
>	if (fd <= 0 || bufferlength == 0)
>		return;
>
>	if (bufferlength > SSIZE_MAX) {
>		prt("fsx flaw: overflow in save_buffer\n");
>		exit(67);
>	}
>	if (lite) {
>		off_t size_by_seek = lseek(fd, (off_t)0, L_XTND);
>		if (size_by_seek == (off_t)-1)
>			prterr("save_buffer: lseek eof");
>		else if (bufferlength > size_by_seek) {
>			warn("save_buffer: .fsxgood file too short... will
>save 0x%qx bytes instead of 0x%qx\n", (unsigned long long)size_by_seek,
>			     (unsigned long long)bufferlength);
>			bufferlength = size_by_seek;
>		}
>	}
>
>	ret = lseek(fd, (off_t)0, SEEK_SET);
>	if (ret == (off_t)-1)
>		prterr("save_buffer: lseek 0");
>
>	byteswritten = write(fd, buffer, (size_t)bufferlength);
>	if (byteswritten != bufferlength) {
>		if (byteswritten == -1)
>			prterr("save_buffer write");
>		else
>			warn("save_buffer: short write, 0x%x bytes instead
>of 0x%qx\n",
>			     (unsigned)byteswritten,
>			     (unsigned long long)bufferlength);
>	}
>}
>
>
>void
>report_failure(int status)
>{
>	logdump();
>
>	if (fsxgoodfd) {
>		if (good_buf) {
>			save_buffer(good_buf, file_size, fsxgoodfd);
>			prt("Correct content saved for comparison\n");
>			prt("(maybe hexdump \"%s\" vs \"%s.fsxgood\")\n",
>			    fname, fname);
>		}
>		close(fsxgoodfd);
>	}
>	exit(status);
>}
>
>
>#define short_at(cp) ((unsigned short)((*((unsigned char *)(cp)) << 8) | \
>				        *(((unsigned char *)(cp)) + 1)))
>
>void
>check_buffers(unsigned offset, unsigned size)
>{
>	unsigned char c, t;
>	unsigned i = 0;
>	unsigned n = 0;
>	unsigned op = 0;
>	unsigned bad = 0;
>
>	if (bcmp(good_buf + offset, temp_buf, size) != 0) {
>		prt("READ BAD DATA: offset = 0x%x, size = 0x%x\n",
>		    offset, size);
>		prt("OFFSET\tGOOD\tBAD\tRANGE\n");
>		while (size > 0) {
>			c = good_buf[offset];
>			t = temp_buf[i];
>			if (c != t) {
>			        if (n == 0) {
>					bad = short_at(&temp_buf[i]);
>				        prt("0x%5x\t0x%04x\t0x%04x", offset,
>				            short_at(&good_buf[offset]), bad);
>					op = temp_buf[offset & 1 ? i+1 : i];
>				}
>				n++;
>				badoff = offset;
>			}
>			offset++;
>			i++;
>			size--;
>		}
>		if (n) {
>		        prt("\t0x%5x\n", n);
>			if (bad)
>				prt("operation# (mod 256) for the bad data
>may be %u\n", ((unsigned)op & 0xff));
>			else
>				prt("operation# (mod 256) for the bad data
>unknown, check HOLE and EXTEND ops\n");
>		} else
>		        prt("????????????????\n");
>		report_failure(110);
>	}
>}
>
>
>void
>check_size(void)
>{
>	struct stat	statbuf;
>	off_t	size_by_seek;
>
>	if (fstat(fd, &statbuf)) {
>		prterr("check_size: fstat");
>		statbuf.st_size = -1;
>	}
>	size_by_seek = lseek(fd, (off_t)0, L_XTND);
>	if (file_size != statbuf.st_size || file_size != size_by_seek) {
>		prt("Size error: expected 0x%qx stat 0x%qx seek 0x%qx\n",
>		    (unsigned long long)file_size,
>		    (unsigned long long)statbuf.st_size,
>		    (unsigned long long)size_by_seek);
>		report_failure(120);
>	}
>}
>
>
>void
>check_trunc_hack(void)
>{
>	struct stat statbuf;
>
>	ftruncate(fd, (off_t)0);
>	ftruncate(fd, (off_t)100000);
>	fstat(fd, &statbuf);
>	if (statbuf.st_size != (off_t)100000) {
>		prt("no extend on truncate! not posix!\n");
>		exit(130);
>	}
>	ftruncate(fd, 0);
>}
>
>
>void
>doread(unsigned offset, unsigned size)
>{
>	off_t ret;
>	unsigned iret;
>
>	offset -= offset % readbdy;
>	if (size == 0) {
>		if (!quiet && testcalls > simulatedopcount)
>			prt("skipping zero size read\n");
>		log4(OP_SKIPPED, OP_READ, offset, size);
>		return;
>	}
>	if (size + offset > file_size) {
>		if (!quiet && testcalls > simulatedopcount)
>			prt("skipping seek/read past end of file\n");
>		log4(OP_SKIPPED, OP_READ, offset, size);
>		return;
>	}
>
>	log4(OP_READ, offset, size, 0);
>
>	if (testcalls <= simulatedopcount)
>		return;
>
>	if (!quiet && (progressinterval && testcalls % progressinterval == 0 ||
>		       debug &&
>		       (monitorstart == -1 ||
>			offset + size > monitorstart &&
>			(monitorend == -1 || offset <= monitorend))))
>		prt("%lu read\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
>		    offset, offset + size - 1, size);
>	ret = lseek(fd, (off_t)offset, SEEK_SET);
>	if (ret == (off_t)-1) {
>		prterr("doread: lseek");
>		report_failure(140);
>	}
>	iret = read(fd, temp_buf, size);
>	if (iret != size) {
>		if (iret == -1)
>			prterr("doread: read");
>		else
>			prt("short read: 0x%x bytes instead of 0x%x\n",
>			    iret, size);
>		report_failure(141);
>	}
>	check_buffers(offset, size);
>}
>
>
>void
>domapread(unsigned offset, unsigned size)
>{
>	unsigned pg_offset;
>	unsigned map_size;
>	char    *p;
>
>	offset -= offset % readbdy;
>	if (size == 0) {
>		if (!quiet && testcalls > simulatedopcount)
>			prt("skipping zero size read\n");
>		log4(OP_SKIPPED, OP_MAPREAD, offset, size);
>		return;
>	}
>	if (size + offset > file_size) {
>		if (!quiet && testcalls > simulatedopcount)
>			prt("skipping seek/read past end of file\n");
>		log4(OP_SKIPPED, OP_MAPREAD, offset, size);
>		return;
>	}
>
>	log4(OP_MAPREAD, offset, size, 0);
>
>	if (testcalls <= simulatedopcount)
>		return;
>
>	if (!quiet && (progressinterval && testcalls % progressinterval == 0 ||
>		       debug &&
>		       (monitorstart == -1 ||
>			offset + size > monitorstart &&
>			(monitorend == -1 || offset <= monitorend))))
>		prt("%lu mapread\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
>		    offset, offset + size - 1, size);
>
>	pg_offset = offset & PAGE_MASK;
>	map_size  = pg_offset + size;
>
>	if ((p = (char *)mmap(0, map_size, PROT_READ, MAP_FILE, fd,
>			      (off_t)(offset - pg_offset))) == (char *)-1) {
>	        prterr("domapread: mmap");
>		report_failure(190);
>	}
>	memcpy(temp_buf, p + pg_offset, size);
>	if (munmap(p, map_size) != 0) {
>		prterr("domapread: munmap");
>		report_failure(191);
>	}
>
>	check_buffers(offset, size);
>}
>
>
>void
>gendata(char *original_buf, char *good_buf, unsigned offset, unsigned size)
>{
>	while (size--) {
>		good_buf[offset] = testcalls % 256;
>		if (offset % 2)
>			good_buf[offset] += original_buf[offset];
>		offset++;
>	}
>}
>
>
>void
>dowrite(unsigned offset, unsigned size)
>{
>	off_t ret;
>	unsigned iret;
>
>	offset -= offset % writebdy;
>	if (size == 0) {
>		if (!quiet && testcalls > simulatedopcount)
>			prt("skipping zero size write\n");
>		log4(OP_SKIPPED, OP_WRITE, offset, size);
>		return;
>	}
>
>	log4(OP_WRITE, offset, size, file_size);
>
>	gendata(original_buf, good_buf, offset, size);
>	if (file_size < offset + size) {
>		if (file_size < offset)
>			bzero(good_buf + file_size, offset - file_size);
>		file_size = offset + size;
>		if (lite) {
>			warn("Lite file size bug in fsx!");
>			report_failure(149);
>		}
>	}
>
>	if (testcalls <= simulatedopcount)
>		return;
>
>	if (!quiet && (progressinterval && testcalls % progressinterval == 0 ||
>		       debug &&
>		       (monitorstart == -1 ||
>			offset + size > monitorstart &&
>			(monitorend == -1 || offset <= monitorend))))
>		prt("%lu write\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
>		    offset, offset + size - 1, size);
>	ret = lseek(fd, (off_t)offset, SEEK_SET);
>	if (ret == (off_t)-1) {
>		prterr("dowrite: lseek");
>		report_failure(150);
>	}
>	iret = write(fd, good_buf + offset, size);
>	if (iret != size) {
>		if (iret == -1)
>			prterr("dowrite: write");
>		else
>			prt("short write: 0x%x bytes instead of 0x%x\n",
>			    iret, size);
>		report_failure(151);
>	}
>}
>
>
>void
>domapwrite(unsigned offset, unsigned size)
>{
>	unsigned pg_offset;
>	unsigned map_size;
>	off_t    cur_filesize;
>	char    *p;
>
>	offset -= offset % writebdy;
>	if (size == 0) {
>		if (!quiet && testcalls > simulatedopcount)
>			prt("skipping zero size write\n");
>		log4(OP_SKIPPED, OP_MAPWRITE, offset, size);
>		return;
>	}
>	cur_filesize = file_size;
>
>	log4(OP_MAPWRITE, offset, size, 0);
>
>	gendata(original_buf, good_buf, offset, size);
>	if (file_size < offset + size) {
>		if (file_size < offset)
>			bzero(good_buf + file_size, offset - file_size);
>		file_size = offset + size;
>		if (lite) {
>			warn("Lite file size bug in fsx!");
>			report_failure(200);
>		}
>	}
>
>	if (testcalls <= simulatedopcount)
>		return;
>
>	if (!quiet && (progressinterval && testcalls % progressinterval == 0 ||
>		       debug &&
>		       (monitorstart == -1 ||
>			offset + size > monitorstart &&
>			(monitorend == -1 || offset <= monitorend))))
>		prt("%lu mapwrite\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
>		    offset, offset + size - 1, size);
>
>	if (file_size > cur_filesize) {
>	        if (ftruncate(fd, file_size) == -1) {
>		        prterr("domapwrite: ftruncate");
>			exit(201);
>		}
>	}
>	pg_offset = offset & PAGE_MASK;
>	map_size  = pg_offset + size;
>
>	if ((p = (char *)mmap(0, map_size, PROT_READ | PROT_WRITE,
>			      MAP_FILE | MAP_SHARED, fd,
>			      (off_t)(offset - pg_offset))) == (char *)-1) {
>	        prterr("domapwrite: mmap");
>		report_failure(202);
>	}
>	memcpy(p + pg_offset, good_buf + offset, size);
>	if (msync(p, map_size, 0) != 0) {
>		prterr("domapwrite: msync");
>		report_failure(203);
>	}
>	if (munmap(p, map_size) != 0) {
>		prterr("domapwrite: munmap");
>		report_failure(204);
>	}
>}
>
>
>void
>dotruncate(unsigned size)
>{
>	int oldsize = file_size;
>
>	size -= size % truncbdy;
>	if (size > biggest) {
>		biggest = size;
>		if (!quiet && testcalls > simulatedopcount)
>			prt("truncating to largest ever: 0x%x\n", size);
>	}
>
>	log4(OP_TRUNCATE, size, (unsigned)file_size, 0);
>
>	if (size > file_size)
>		bzero(good_buf + file_size, size - file_size);
>	file_size = size;
>
>	if (testcalls <= simulatedopcount)
>		return;
>
>	if (progressinterval && testcalls % progressinterval == 0 ||
>	    debug && (monitorstart == -1 || monitorend == -1 ||
>		      size <= monitorend))
>		prt("%lu trunc\tfrom 0x%x to 0x%x\n", testcalls, oldsize,
>size);
>	if (ftruncate(fd, (off_t)size) == -1) {
>	        prt("ftruncate1: %x\n", size);
>		prterr("dotruncate: ftruncate");
>		report_failure(160);
>	}
>}
>
>
>void
>writefileimage()
>{
>	ssize_t iret;
>
>	if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
>		prterr("writefileimage: lseek");
>		report_failure(171);
>	}
>	iret = write(fd, good_buf, file_size);
>	if ((off_t)iret != file_size) {
>		if (iret == -1)
>			prterr("writefileimage: write");
>		else
>			prt("short write: 0x%x bytes instead of 0x%qx\n",
>			    iret, (unsigned long long)file_size);
>		report_failure(172);
>	}
>	if (lite ? 0 : ftruncate(fd, file_size) == -1) {
>	        prt("ftruncate2: %qx\n", (unsigned long long)file_size);
>		prterr("writefileimage: ftruncate");
>		report_failure(173);
>	}
>}
>
>
>void
>docloseopen(void)
>{
>	if (testcalls <= simulatedopcount)
>		return;
>
>	if (debug)
>		prt("%lu close/open\n", testcalls);
>	if (close(fd)) {
>		prterr("docloseopen: close");
>		report_failure(180);
>	}
>	fd = open(fname, O_RDWR, 0);
>	if (fd < 0) {
>		prterr("docloseopen: open");
>		report_failure(181);
>	}
>}
>
>
>void
>test(void)
>{
>	unsigned long	offset;
>	unsigned long	size = maxoplen;
>	unsigned long	rv = random();
>	unsigned long	op = rv % (3 + !lite + mapped_writes);
>
>        /* turn off the map read if necessary */
>
>        if (op == 2 && !mapped_reads)
>            op = 0;
>
>	if (simulatedopcount > 0 && testcalls == simulatedopcount)
>		writefileimage();
>
>	testcalls++;
>
>	closeopen = (rv >> 3) < (1 << 28) / closeprob;
>
>	if (debugstart > 0 && testcalls >= debugstart)
>		debug = 1;
>
>	if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0)
>		prt("%lu...\n", testcalls);
>
>	/*
>	 * READ:	op = 0
>	 * WRITE:	op = 1
>	 * MAPREAD:     op = 2
>	 * TRUNCATE:	op = 3
>	 * MAPWRITE:    op = 3 or 4
>	 */
>	if (lite ? 0 : op == 3 && (style & 1) == 0) /* vanilla truncate? */
>		dotruncate(random() % maxfilelen);
>	else {
>		if (randomoplen)
>			size = random() % (maxoplen+1);
>		if (lite ? 0 : op == 3)
>			dotruncate(size);
>		else {
>			offset = random();
>			if (op == 1 || op == (lite ? 3 : 4)) {
>				offset %= maxfilelen;
>				if (offset + size > maxfilelen)
>					size = maxfilelen - offset;
>				if (op != 1)
>					domapwrite(offset, size);
>				else
>					dowrite(offset, size);
>			} else {
>				if (file_size)
>					offset %= file_size;
>				else
>					offset = 0;
>				if (offset + size > file_size)
>					size = file_size - offset;
>				if (op != 0)
>					domapread(offset, size);
>				else
>					doread(offset, size);
>			}
>		}
>	}
>	if (sizechecks && testcalls > simulatedopcount)
>		check_size();
>	if (closeopen)
>		docloseopen();
>}
>
>
>void
>cleanup(sig)
>	int	sig;
>{
>	if (sig)
>		prt("signal %d\n", sig);
>	prt("testcalls = %lu\n", testcalls);
>	exit(sig);
>}
>
>
>void
>usage(void)
>{
>	fprintf(stdout, "usage: %s",
>		"fsx [-dnqLOW] [-b opnum] [-c Prob] [-l flen] [-m
>start:end] [-o oplen] [-p progressinterval] [-r readbdy] [-s style] [-t
>truncbdy] [-w writebdy] [-D startingop] [-N numops] [-P dirpath] [-S seed]
>fname\n\
>	-b opnum: beginning operation number (default 1)\n\
>	-c P: 1 in P chance of file close+open at each op (default infinity)\n\
>	-d: debug output for all operations\n\
>	-l flen: the upper bound on file size (default 262144)\n\
>	-m startop:endop: monitor (print debug output) specified byte range
>(default 0:infinity)\n\
>	-n: no verifications of file size\n\
>	-o oplen: the upper bound on operation size (default 65536)\n\
>	-p progressinterval: debug output at specified operation interval\n\
>	-q: quieter operation\n\
>	-r readbdy: 4096 would make reads page aligned (default 1)\n\
>	-s style: 1 gives smaller truncates (default 0)\n\
>	-t truncbdy: 4096 would make truncates page aligned (default 1)\n\
>	-w writebdy: 4096 would make writes page aligned (default 1)\n\
>	-D startingop: debug output starting at specified operation\n\
>	-L: fsxLite - no file creations & no file size changes\n\
>	-N numops: total # operations to do (default infinity)\n\
>	-O: use oplen (see -o flag) for every op (default random)\n\
>	-P: save .fsxlog and .fsxgood files in dirpath (default ./)\n\
>	-S seed: for random # generator (default 1) 0 gets timestamp\n\
>	-W: mapped write operations DISabled\n\
>        -R: read() system calls only (mapped reads disabled)\n\
>	fname: this filename is REQUIRED (no default)\n");
>	exit(90);
>}
>
>
>int
>getnum(char *s, char **e)
>{
>	int ret = -1;
>
>	*e = (char *) 0;
>	ret = strtol(s, e, 0);
>	if (*e)
>		switch (**e) {
>		case 'b':
>		case 'B':
>			ret *= 512;
>			*e = *e + 1;
>			break;
>		case 'k':
>		case 'K':
>			ret *= 1024;
>			*e = *e + 1;
>			break;
>		case 'm':
>		case 'M':
>			ret *= 1024*1024;
>			*e = *e + 1;
>			break;
>		case 'w':
>		case 'W':
>			ret *= 4;
>			*e = *e + 1;
>			break;
>		}
>	return (ret);
>}
>
>
>int
>main(int argc, char **argv)
>{
>	int	i, style, ch;
>	char	*endp;
>	char goodfile[1024];
>	char logfile[1024];
>
>	goodfile[0] = 0;
>	logfile[0] = 0;
>
>	setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
>
>	while ((ch = getopt(argc, argv, "b:c:dl:m:no:p:qr:s:t:w:D:LN:OP:RS:W"))
>	       != EOF)
>		switch (ch) {
>		case 'b':
>			simulatedopcount = getnum(optarg, &endp);
>			if (!quiet)
>				fprintf(stdout, "Will begin at operation
>%ld\n",
>					simulatedopcount);
>			if (simulatedopcount == 0)
>				usage();
>			simulatedopcount -= 1;
>			break;
>		case 'c':
>			closeprob = getnum(optarg, &endp);
>			if (!quiet)
>				fprintf(stdout,
>					"Chance of close/open is 1 in %d\n",
>					closeprob);
>			if (closeprob <= 0)
>				usage();
>			break;
>		case 'd':
>			debug = 1;
>			break;
>		case 'l':
>			maxfilelen = getnum(optarg, &endp);
>			if (maxfilelen <= 0)
>				usage();
>			break;
>		case 'm':
>			monitorstart = getnum(optarg, &endp);
>			if (monitorstart < 0)
>				usage();
>			if (!endp || *endp++ != ':')
>				usage();
>			monitorend = getnum(endp, &endp);
>			if (monitorend < 0)
>				usage();
>			if (monitorend == 0)
>				monitorend = -1; /* aka infinity */
>			debug = 1;
>		case 'n':
>			sizechecks = 0;
>			break;
>		case 'o':
>			maxoplen = getnum(optarg, &endp);
>			if (maxoplen <= 0)
>				usage();
>			break;
>		case 'p':
>			progressinterval = getnum(optarg, &endp);
>			if (progressinterval < 0)
>				usage();
>			break;
>		case 'q':
>			quiet = 1;
>			break;
>		case 'r':
>			readbdy = getnum(optarg, &endp);
>			if (readbdy <= 0)
>				usage();
>			break;
>		case 's':
>			style = getnum(optarg, &endp);
>			if (style < 0 || style > 1)
>				usage();
>			break;
>		case 't':
>			truncbdy = getnum(optarg, &endp);
>			if (truncbdy <= 0)
>				usage();
>			break;
>		case 'w':
>			writebdy = getnum(optarg, &endp);
>			if (writebdy <= 0)
>				usage();
>			break;
>		case 'D':
>			debugstart = getnum(optarg, &endp);
>			if (debugstart < 1)
>				usage();
>			break;
>		case 'L':
>		        lite = 1;
>			break;
>		case 'N':
>			numops = getnum(optarg, &endp);
>			if (numops < 0)
>				usage();
>			break;
>		case 'O':
>			randomoplen = 0;
>			break;
>		case 'P':
>			strncpy(goodfile, optarg, sizeof(goodfile));
>			strcat(goodfile, "/");
>			strncpy(logfile, optarg, sizeof(logfile));
>			strcat(logfile, "/");
>			break;
>                case 'R':
>                        mapped_reads = 0;
>                        break;
>		case 'S':
>                        seed = getnum(optarg, &endp);
>			if (seed == 0)
>				seed = time(0) % 10000;
>			if (!quiet)
>				fprintf(stdout, "Seed set to %d\n", seed);
>			if (seed < 0)
>				usage();
>			break;
>		case 'W':
>		        mapped_writes = 0;
>			if (!quiet)
>				fprintf(stdout, "mapped writes DISABLED\n");
>			break;
>
>		default:
>			usage();
>			/* NOTREACHED */
>		}
>	argc -= optind;
>	argv += optind;
>	if (argc != 1)
>		usage();
>	fname = argv[0];
>
>	signal(SIGHUP,	cleanup);
>	signal(SIGINT,	cleanup);
>	signal(SIGPIPE,	cleanup);
>	signal(SIGALRM,	cleanup);
>	signal(SIGTERM,	cleanup);
>	signal(SIGXCPU,	cleanup);
>	signal(SIGXFSZ,	cleanup);
>	signal(SIGVTALRM,	cleanup);
>	signal(SIGUSR1,	cleanup);
>	signal(SIGUSR2,	cleanup);
>
>	initstate(seed, state, 256);
>	setstate(state);
>	fd = open(fname, O_RDWR|(lite ? 0 : O_CREAT|O_TRUNC), 0666);
>	if (fd < 0) {
>		prterr(fname);
>		exit(91);
>	}
>	strncat(goodfile, fname, 256);
>	strcat (goodfile, ".fsxgood");
>	fsxgoodfd = open(goodfile, O_RDWR|O_CREAT|O_TRUNC, 0666);
>	if (fsxgoodfd < 0) {
>		prterr(goodfile);
>		exit(92);
>	}
>	strncat(logfile, fname, 256);
>	strcat (logfile, ".fsxlog");
>	fsxlogf = fopen(logfile, "w");
>	if (fsxlogf == NULL) {
>		prterr(logfile);
>		exit(93);
>	}
>	if (lite) {
>		off_t ret;
>		file_size = maxfilelen = lseek(fd, (off_t)0, L_XTND);
>		if (file_size == (off_t)-1) {
>			prterr(fname);
>			warn("main: lseek eof");
>			exit(94);
>		}
>		ret = lseek(fd, (off_t)0, SEEK_SET);
>		if (ret == (off_t)-1) {
>			prterr(fname);
>			warn("main: lseek 0");
>			exit(95);
>		}
>	}
>	original_buf = (char *) malloc(maxfilelen);
>	for (i = 0; i < maxfilelen; i++)
>		original_buf[i] = random() % 256;
>	good_buf = (char *) malloc(maxfilelen);
>	bzero(good_buf, maxfilelen);
>	temp_buf = (char *) malloc(maxoplen);
>	bzero(temp_buf, maxoplen);
>	if (lite) {	/* zero entire existing file */
>		ssize_t written;
>
>		written = write(fd, good_buf, (size_t)maxfilelen);
>		if (written != maxfilelen) {
>			if (written == -1) {
>				prterr(fname);
>				warn("main: error on write");
>			} else
>				warn("main: short write, 0x%x bytes instead
>of 0x%x\n",
>				     (unsigned)written, maxfilelen);
>			exit(98);
>		}
>	} else
>		check_trunc_hack();
>
>	while (numops == -1 || numops--)
>		test();
>
>	if (close(fd)) {
>		prterr("close");
>		report_failure(99);
>	}
>	prt("All operations completed A-OK!\n");
>
>	exit(0);
>	return 0;
>}
>
>
>To Unsubscribe: send mail to majordomo@FreeBSD.org
>with "unsubscribe freebsd-hackers" in the body of the message



--
Conrad Minshall, conrad@apple.com, 408 974-2749
Apple Computer, Mac OS X Core Operating Systems