/* -*- C -*- */

/* cc -o nfs -O3 -Wformat -g nfs_fh_stale.c -pthread */

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <dirent.h>
/* #define NDEBUG */
#include <assert.h>
#include <signal.h>

extern char *optarg;
extern int optind, opterr, optopt;

typedef enum { ok = 0,
	wrong_argc,
	pthread_create_error
} ret_t;

typedef struct stats {
	pthread_mutex_t lock;
	unsigned long opens;
	unsigned long lseeks;
	unsigned long errors;
	unsigned long naps;
	unsigned long total;
	unsigned long done;
	struct timeval start;
	struct timeval end;
	int totfreq;
} stats_t;

stats_t stats;

static void *worker(void *arg);

typedef struct {
	void *start;
	int   length;
} mmapped_t;

typedef struct params {
	int         dirs;
	int         files;
	char       *buffer;
	const char *filename;
	int         fileno;
	int         dirno;
	DIR       **cwd;
	int        *fd;
	mmapped_t  *mmapped;
} params_t;

static void sync_file(params_t *params);
static void read_file(params_t *params);
static void write_file(params_t *params);
static void rename_file(params_t *params);
static void unlink_file(params_t *params);
static void link_file(params_t *params);
static void sym_file(params_t *params);
static void trunc_file(params_t *params);
static void pip_file(params_t *params);
static void mmap_file(params_t *params);
static void open_file(params_t *params);
static void gc_file(params_t *params);

static void nap(int secs, int nanos);
static void _nap(int secs, int nanos);

static void orderedname(params_t *params, char *name);

#define DEFAULT_THREADS 10
#define DEFAULT_FILES 100
#define DEFAULT_DIRS 1
#define DEFAULT_ITERATIONS 10
#define DEFAULT_DELTA 1
#define DEFAULT_MAX_SLEEP 0
#define DEFAULT_BUF_SIZE 4096 * 100
#define DEFAULT_MAX_SIZE 4096 * 100 * 1000
#define DEFAULT_VERBOSE 0
#define DEFAULT_LIMIT 0
#define DEFAULT_BENCHMARK 0
#define DEFAULT_OPERATIONS 0
#define DEFAULT_OPEN_FD    10
#define DEFAULT_MMAPED_AREAS  100

int delta = DEFAULT_DELTA;
int max_sleep = DEFAULT_MAX_SLEEP;
int max_buf_size = DEFAULT_BUF_SIZE;
int max_size = DEFAULT_MAX_SIZE;
int verbose = DEFAULT_VERBOSE;
unsigned long long limit = DEFAULT_LIMIT;
unsigned long long operations = DEFAULT_OPERATIONS;
int benchmark = DEFAULT_BENCHMARK;
int files = DEFAULT_FILES;
int dirs = DEFAULT_DIRS;
int fd_num = DEFAULT_OPEN_FD;
int nr_mmapped = DEFAULT_MMAPED_AREAS;
int pgsize;

DIR **cwds = NULL;

/* random integer number in [0, n - 1] */
#define RND(n) ((int)(((double)(n)) * rand() / (RAND_MAX + 1.0)))

#define STRICT (0)

#if STRICT
#define STEX(e) \
	pthread_mutex_lock(&stats.lock) ; e ; pthread_mutex_unlock(&stats.lock)
#else
#define STEX(e) e
#endif

typedef struct op {
	const char *label;
	int freq;
	void (*handler)(params_t *params);
	struct {
		int subtotal;
		int ok;
		int failure;
		int missed;
		int busy;
	} result;
} op_t;

typedef enum {
	syncop,
	readop,
	writeop,
	renameop,
	unlinkop,
	linkop,
	symop,
	truncop,
	pipop,
	openop,
	mmapop,
	gcop
} op_id_t;

#define DEFOPS(aname)				\
[aname ## op ] = {				\
	.label = #aname,			\
	.freq  = 1,				\
	.handler = aname ## _file		\
}

op_t ops[] = {
	DEFOPS(sync),
	DEFOPS(read),
	DEFOPS(write),
	DEFOPS(rename),
	DEFOPS(unlink),
	DEFOPS(link),
	DEFOPS(sym),
	DEFOPS(trunc),
	DEFOPS(pip),
	DEFOPS(mmap),
	DEFOPS(open),
	DEFOPS(gc),
	{
		.label = NULL
	}
};

const char optstring[] = "p:f:d:D:i:s:b:M:BvF:L:O:o:m:";

static double
rate(unsigned long events, int secs)
{
	return ((double) events) / secs;
}

static void
usage(char *argv0)
{
	char *progname;
	op_t *op;

	progname = strrchr(argv0, '/');
	if (progname == NULL)
		progname = argv0;
	else
		progname ++;

	fprintf(stderr,
		"usage: %s options\n"
		"\n\tRandomly creates and removes files from multiple threads."
		"\n\tCan be used to test NFS stale handle detection."
		"\n\tCompiled from " __FILE__ " at " __DATE__ "\n\n",
		progname);
	fprintf(stderr, "options, deafults in []\n"
		"\t-p N   \tlaunch N concurrent processes [%i]\n"
		"\t-f N   \toperate on N files [%i]\n"
		"\t-d N   \tduplicate file set in N directories [%i]\n"
		"\t-D N   \titeration takes N seconds [%i]\n"
		"\t-i N   \tperform N iterations [%i]\n"
		"\t-O N   \tperform N operations [%i]\n"
		"\t-s N   \tsleep [0 .. N] nanoseconds between operations [%i]\n"
		"\t-b N   \tmaximal buffer size is N bytes [%i]\n"
		"\t-M N   \tmaximal file size is N bytes [%i]\n"
		"\t-o N   \tkeep N file descriptors open [%i]\n"
		"\t-m N   \tkeep N memory areas mmapped [%i]\n"
		"\t-B     \tbenchmark mode: don't ever sleep [%i]\n"
		"\t-v     \tincrease verbosity\n"
		"\t-F op=N\tset relative frequence of op (see below) to N\n"
		"\t-L N   \tlimit amount of used disk space to N kbytes [%i]\n",

		DEFAULT_THREADS,
		DEFAULT_FILES,
		DEFAULT_DIRS,
		DEFAULT_DELTA,
		DEFAULT_ITERATIONS,
		DEFAULT_OPERATIONS,
		DEFAULT_MAX_SLEEP,
		DEFAULT_BUF_SIZE,
		DEFAULT_MAX_SIZE,
		DEFAULT_OPEN_FD,
		DEFAULT_MMAPED_AREAS,
		DEFAULT_BENCHMARK,
		DEFAULT_LIMIT);

	fprintf(stderr, "\noperations with default frequencies\n");
	for (op = &ops[0] ; op->label ; ++ op) {
		fprintf(stderr, "\t%s\t%i\n", op->label, op->freq);
	}
}

static unsigned long long
getavail()
{
	int result;

	struct statfs buf;

	result = statfs(".", &buf);
	if (result != 0) {
		perror("statfs");
		exit(1);
	}
	return buf.f_bsize * (buf.f_bavail >> 10);
}

int
main(int argc, char **argv)
{
	ret_t result;
	int threads = DEFAULT_THREADS;
	int i;
	int iterations = DEFAULT_ITERATIONS;
	unsigned long long operations = DEFAULT_OPERATIONS;
	int opt;
	char dname[30];
	unsigned long long initiallyavail;
	unsigned long long used;
	op_t *op;

	result = ok;
	ops[gcop].freq = 0;
	do {
		opt = getopt(argc, argv, optstring);
		switch (opt) {
		case '?':
			usage(argv[0]);
			return wrong_argc;
		case 'p':
			threads = atoi(optarg);
			break;
		case 'f':
			files = atoi(optarg);
			break;
		case 'd':
			dirs = atoi(optarg);
			break;
		case 'D':
			delta = atoi(optarg);
			break;
		case 'i':
			iterations = atoi(optarg);
			break;
		case 'O':
			operations = atoll(optarg);
			break;
		case 's':
			max_sleep = atoi(optarg);
			break;
		case 'b':
			max_buf_size = atoi(optarg);
			if (max_buf_size < 255) {
				fprintf(stderr, "%s: max_buf_size too small\n",
					argv[0]);
				return 1;
			}
			break;
		case 'M':
			max_size = atoi(optarg);
			break;
		case 'o':
			fd_num = atoi(optarg);
			break;
		case 'm':
			nr_mmapped = atoi(optarg);
			break;
		case 'B':
			benchmark = 1;
			break;
		case 'v':
			++verbose;
			break;
		case 'F': {
			char *eq;
			int   opfreq;

			eq = strchr(optarg, '=');
			if (eq == NULL) {
				fprintf(stderr, "%s: Use -F op=freq\n", argv[0]);
				return 1;
			}
			*eq = 0;
			opfreq = atoi(eq + 1);
			for (op = &ops[0] ; op->label ; ++ op) {
				if (!strcmp(op->label, optarg)) {
					op->freq = opfreq;
					break;
				}
			}
			if (!op->label) {
				fprintf(stderr, "%s: Unknown op: %s\n",
					argv[0], optarg);
				return 1;
			}
			*eq = '=';
		}
			break;
		case 'L':
			limit = atoll(optarg);
			break;
		case -1:
			break;
		}
	} while (opt != -1);
	stats.total = operations;
	stats.done = 0;

	stats.totfreq = 0;
	for (op = &ops[0] ; op->label ; ++ op)
		stats.totfreq += op->freq;

	if (gettimeofday(&stats.start, NULL) != 0) {
		perror("gettimeofday");
		return 1;
	}
	pthread_mutex_init(&stats.lock, NULL);

	pgsize = getpagesize();
	initiallyavail = getavail();

	signal(SIGBUS, SIG_IGN);

	cwds = calloc(dirs, sizeof cwds[0]);
	if (cwds == NULL) {
		perror("calloc");
		return 1;
	}

	for (i = 0; i < dirs; ++i) {
		sprintf(dname, "d%x", i);
		if (mkdir(dname, 0700) == -1 && errno != EEXIST) {
			perror("mkdir");
			return 1;
		}
	}

	fprintf(stderr,
		"%s: %i processes, %i files, delta: %i"
		"\n\titerations: %i, sleep: %i, buffer: %i, max size: %i\n",
		argv[0], threads, files, delta, iterations,
		max_sleep, max_buf_size, max_size);
	for (i = 0; i < threads; ++i) {
		int rv;
		pthread_t id;

		rv = pthread_create(&id, NULL, worker, NULL);
		if (rv != 0) {
			fprintf(stderr,
				"%s: pthread_create fails: %s(%i) while creating %i-%s thread\n",
				argv[0], strerror(rv), rv, i,
				(i % 10 == 1) ? "st" : (i % 10 ==
							2) ? "nd" : "th");
			return 1;
		}
	}
	for (i = 0; i < iterations; i += delta) {
		used = initiallyavail - getavail();
		printf("\nseconds: %i\topens: %lu [%f] lseeks: %lu [%f]\n"
		       "\terrors: %lu, naps: %lu [%f], "
		       "used: %lli, gc: %i\n",
		       i,
		       stats.opens, rate(stats.opens, i),
		       stats.lseeks, rate(stats.lseeks, i),
		       stats.errors, stats.naps, rate(stats.naps, i),
		       used, ops[gcop].freq);
		printf("op:\tok\tmiss\tbusy\terr\tdone\trate\tglobal rate\n");
		for (op = &ops[0] ; op->label ; ++ op) {
			int done;
			int subtotal;
			int ratio;

			done = op->result.ok;
			subtotal = op->result.subtotal;
			op->result.subtotal = done;
			if (op->freq != 0)
				ratio = stats.totfreq / op->freq;
			else
				ratio = 0;

			printf("%s:\t%i\t%i\t%i\t%i\t%i\t%.1f\t%.1f\n",
			       op->label,
			       done,
			       op->result.missed,
			       op->result.busy,
			       op->result.failure,
			       done - subtotal,
			       rate((done - subtotal) * ratio, delta),
			       rate(done * ratio, i));
		}
		fflush(stdout);
		if (limit != 0) {
			int origfreq;

			origfreq = ops[gcop].freq;
			if (used > limit / 2) {
				if (used > limit)
					ops[gcop].freq *= 2;
				else
					ops[gcop].freq = (used - limit / 2) * 100 / (limit / 2);
			} else
				ops[gcop].freq = 0;
			stats.totfreq += (ops[gcop].freq - origfreq);
		}
		_nap(delta, 0);
	}
	return result;
}

static void *
worker(void *arg)
{
	params_t params;
	char fileName[30];

	memset(&params, 0, sizeof params);
	params.files    = files;
	params.dirs     = dirs;
	params.buffer   = malloc(max_buf_size);
	params.filename = fileName;
	params.cwd      = cwds;
	params.fd       = calloc(fd_num, sizeof params.fd[0]);
	params.mmapped  = calloc(nr_mmapped, sizeof params.mmapped[0]);

	while (1) {
		op_t *op;
		int   randum;
		int   freqreached;

		params.fileno = RND(params.files);
		params.dirno = RND(params.dirs);
		sprintf(fileName, "d%x/%x", params.dirno, params.fileno);
		randum = RND(stats.totfreq);
		freqreached = 0;
		for (op = &ops[0] ; op->label ; ++ op) {
			freqreached += op->freq;
			if (randum < freqreached) {
				op->handler(&params);
				break;
			}
		}
		STEX(++stats.done);
		if (!benchmark)
			nap(0, RND(max_sleep));
		else if (stats.total && stats.done >= stats.total) {
			pthread_mutex_lock(&stats.lock);
			gettimeofday(&stats.end, NULL);
			printf("start: %li.%li, end: %li.%li, diff: %li, %li\n",
			       stats.start.tv_sec, stats.start.tv_usec,
			       stats.end.tv_sec, stats.end.tv_usec,
			       stats.end.tv_sec - stats.start.tv_sec,
			       stats.end.tv_usec - stats.start.tv_usec);
			exit(0);
		}
	}
}

static void
sync_file(params_t *params)
{
	int fd;
	const char *fileName;

	fileName = params->filename;
	fd = open(fileName, O_WRONLY);
	if (fd == -1) {
		if (errno != ENOENT) {
			fprintf(stderr, "%s open/sync: %s(%i)\n", fileName,
				strerror(errno), errno);
			STEX(++stats.errors);
		} else {
			STEX(++ops[syncop].result.missed);
		}
		return;
	}
	if (fsync(fd)) {
		fprintf(stderr, "%s sync: %s(%i)\n",
			fileName, strerror(errno), errno);
		STEX(++stats.errors);
		STEX(++ops[syncop].result.failure);
		return;
	}
	STEX(++ops[syncop].result.ok);
	if (verbose) {
		printf("[%li] SYNC: %s\n", pthread_self(), fileName);
	}
	close(fd);
}

static void
read_file(params_t *params)
{
	int fd;
	char *buf;
	int bufSize;
	int offset;
	const char *fileName;

	fileName = params->filename;
	fd = open(fileName, O_CREAT | O_APPEND | O_RDWR, 0700);
	if (fd == -1) {
		fprintf(stderr, "%s open/read: %s(%i)\n", fileName, strerror(errno),
			errno);
		STEX(++stats.errors);
		STEX(++ops[readop].result.missed);
		return;
	}
	STEX(++stats.opens);
	nap(0, RND(max_sleep));
	if (lseek(fd, RND(max_size), SEEK_SET) == -1) {
		fprintf(stderr, "%s lseek: %s(%i)\n",
			fileName, strerror(errno), errno);
		STEX(++stats.errors);
		close(fd);
		return;
	}
	STEX(++stats.lseeks);
	nap(0, RND(max_sleep));
	bufSize = RND(max_buf_size / 3) + 30;
	offset = RND(max_buf_size / 3);
	buf = params->buffer;
	if (read(fd, buf, bufSize + offset) == -1) {
		fprintf(stderr, "%s read: %s(%i)\n", fileName, strerror(errno),
			errno);
		STEX(++stats.errors);
		STEX(++ops[readop].result.failure);
		close(fd);
		return;
	}
	STEX(++ops[readop].result.ok);
	if (verbose) {
		printf("[%li] R: %s\n", pthread_self(), fileName);
	}
	close(fd);
}

static void
write_file(params_t *params)
{
	int fd;
	char *buf;
	int bufSize;
	int offset;
	const char *fileName;

	fileName = params->filename;
	fd = open(fileName, O_CREAT | O_APPEND | O_RDWR, 0700);
	if (fd == -1) {
		fprintf(stderr, "%s open/write: %s(%i)\n", fileName, strerror(errno),
			errno);
		STEX(++stats.errors);
		STEX(++ops[writeop].result.missed);
		return;
	}
	STEX(++stats.opens);
	nap(0, RND(max_sleep));
	if (lseek(fd, RND(max_size), SEEK_SET) == -1) {
		fprintf(stderr, "%s lseek: %s(%i)\n",
			fileName, strerror(errno), errno);
		STEX(++stats.errors);
		close(fd);
		return;
	}
	STEX(++stats.lseeks);
	nap(0, RND(max_sleep));
	bufSize = RND(max_buf_size / 3) + 30;
	offset = RND(max_buf_size / 3);
	buf = params->buffer;
	memset(buf, 0xfe + stats.opens, max_buf_size);
	sprintf(buf + offset, "---%lx+++", time(NULL));
	if (write(fd, buf, bufSize + offset) == -1) {
		fprintf(stderr, "%s write: %s(%i)\n",
			fileName, strerror(errno), errno);
		STEX(++stats.errors);
		STEX(++ops[writeop].result.failure);
		close(fd);
		return;
	}
	STEX(++ops[writeop].result.ok);
	if (verbose) {
		printf("[%li] W: %s\n", pthread_self(), fileName);
	}
	close(fd);
}

static void
rename_file(params_t *params)
{
	char target[30];
	const char *fileName;
	int files;

	fileName = params->filename;
	files = params->files;
	orderedname(params, target);
	if (rename(fileName, target) == -1) {
		switch (errno) {
		case ENOENT:
			STEX(++ops[renameop].result.missed);
			break;
		default:
			{
				fprintf(stderr, "rename( %s, %s ): %s(%i)\n",
					fileName, target, strerror(errno),
					errno);
				STEX(++stats.errors);
				STEX(++ops[renameop].result.failure);
			}
		}
	} else {
		if (verbose) {
			printf("[%li] %s -> %s\n", pthread_self(), fileName,
			       target);
		}
		STEX(++ops[renameop].result.ok);
	}
}

static void
unlink_file(params_t *params)
{
	const char *fileName;

	fileName = params->filename;
	if (unlink(fileName) == -1) {
		switch (errno) {
		case ENOENT:
			STEX(++ops[unlinkop].result.missed);
			break;
		default:
			{
				fprintf(stderr, "%s unlink: %s(%i)\n",
					fileName, strerror(errno), errno);
				STEX(++stats.errors);
				STEX(++ops[unlinkop].result.failure);
			}
		}
	} else {
		if (verbose) {
			printf("[%li] U: %s\n", pthread_self(), fileName);
		}
		STEX(++ops[unlinkop].result.ok);
	}
}

static void
link_file(params_t *params)
{
	char target[30];
	const char *fileName;
	int files;

	fileName = params->filename;
	files = params->files;
	orderedname(params, target);
	if (link(fileName, target) == -1) {
		switch (errno) {
		case ENOENT:
			STEX(++ops[linkop].result.missed);
			break;
		case EEXIST:
			STEX(++ops[linkop].result.busy);
			break;
		default:
			{
				fprintf(stderr, "link( %s, %s ): %s(%i)\n",
					fileName, target, strerror(errno),
					errno);
				STEX(++stats.errors);
				STEX(++ops[linkop].result.failure);
			}
		}
	} else {
		if (verbose) {
			printf("[%li] %s -> %s\n", pthread_self(), fileName,
			       target);
		}
		STEX(++ops[linkop].result.ok);
	}
}

static void
sym_file(params_t *params)
{
	char target[30];
	char source[30];
	const char *fileName;
	int files;

	fileName = params->filename;
	files = params->files;
	orderedname(params, target);
	sprintf(source, "../%s", fileName);
	if (symlink(source, target) == -1) {
		switch (errno) {
		case ENOENT:
			STEX(++ops[symop].result.missed);
			break;
		case EEXIST:
			STEX(++ops[symop].result.busy);
			break;
		default:
			{
				fprintf(stderr, "link( %s, %s ): %s(%i)\n",
					fileName, target, strerror(errno),
					errno);
				STEX(++stats.errors);
				STEX(++ops[symop].result.failure);
			}
		}
	} else {
		if (verbose) {
			printf("[%li] %s -> %s\n", pthread_self(), fileName,
			       target);
		}
		STEX(++ops[symop].result.ok);
	}
}

static void
trunc_file(params_t *params)
{
	const char *fileName;

	fileName = params->filename;
	if (truncate(fileName, RND(max_size)) == -1) {
		switch (errno) {
		case ENOENT:
			STEX(++ops[truncop].result.missed);
			break;
		default:
			{
				fprintf(stderr, "%s trunc: %s(%i)\n",
					fileName, strerror(errno), errno);
				STEX(++stats.errors);
				STEX(++ops[truncop].result.failure);
			}
		}
	} else {
		if (verbose) {
			printf("[%li] T: %s\n", pthread_self(), fileName);
		}
		STEX(++ops[truncop].result.ok);
	}
}

static void
pip_file(params_t *params)
{
	struct dirent  entry;
	struct dirent *ptr;
	int result;
	int dirno;
	int dogc;

	dirno = RND(params->dirs);

	if (params->cwd[dirno] == NULL) {
		char dname[30];

		sprintf(dname, "d%x", dirno);
		params->cwd[dirno] = opendir(dname);
		if (params->cwd[dirno] == NULL) {
			perror("opendir");
			exit(1);
		}
	}

	dogc = (params->buffer[0] == 0x66);
	errno = 0;
	result = readdir_r(params->cwd[dirno], &entry, &ptr);
	if (result == 0 && errno == 0 && ptr == &entry) {
		char fname[100];

		sprintf(fname, "d%x/%s", dirno, entry.d_name);
		if (dogc) {
			if (unlink(fname) == -1) {
				switch (errno) {
				case ENOENT:
					STEX(++ops[gcop].result.missed);
					break;
				case EISDIR:
					break;
				default:
					STEX(++stats.errors);
					STEX(++ops[gcop].result.failure);
				}
			} else
				STEX(++ops[gcop].result.ok);
		} else {
			STEX(++ops[pipop].result.ok);
			if (verbose)
				printf("[%li] P: %s\n", pthread_self(), fname);
		}
	} else if (errno == ENOENT || ptr == NULL)
		rewinddir(params->cwd[dirno]);
	else if (verbose) {
		printf("[%li] P: %i, %i, %p, %s\n",
		       pthread_self(), result, errno, ptr, entry.d_name);
		STEX(++ops[dogc ? gcop : pipop].result.failure);
	}
}

static void
open_file(params_t *params)
{
	int fdno;

	fdno = RND(fd_num);
	if (params->fd[fdno] == 0) {
		int fd;
		const char *fileName;

		fileName = params->filename;
		fd = open(fileName, O_CREAT | O_APPEND | O_RDWR, 0700);
		if (fd == -1) {
			fprintf(stderr, "%s open/open: %s(%i)\n",
				fileName, strerror(errno), errno);
			STEX(++stats.errors);
			STEX(++ops[openop].result.missed);
			return;
		}
		params->fd[fdno] = fd;
		STEX(++stats.opens);
		STEX(++ops[openop].result.ok);
	} else {
		close(params->fd[fdno]);
		params->fd[fdno] = 0;
	}
}

static char *minp(char *p1, char *p2)
{
	if (p1 < p2)
		return p1;
	else
		return p2;
}

static roundtopage(unsigned long val)
{
	return (val + pgsize - 1) / pgsize * pgsize;
}

static void
mmap_file(params_t *params)
{
	int areano;

	areano = RND(nr_mmapped);
	if (params->mmapped[areano].start == NULL) {
		int fd;

		fd = params->fd[RND(fd_num)];
		if (fd != 0) {
			int result;
			size_t length;
			int flags;
			off_t offset;
			void *addr;

			length = roundtopage(RND(max_buf_size) + 30);
			offset = roundtopage(RND(max_size));
			flags  = RND(1) == 0 ? MAP_SHARED : MAP_PRIVATE;
			result = ftruncate(fd, offset + length + 1);
			if (result == -1) {
				fprintf(stderr,
					"%i mmap/truncate: %s(%i)\n",
					fd, strerror(errno), errno);
				STEX(++stats.errors);
				STEX(++ops[mmapop].result.missed);
			} else {
				addr = mmap(NULL, length, PROT_READ | PROT_WRITE,
					    flags, fd, offset);
				if (addr == MAP_FAILED) {
					fprintf(stderr, "%i mmap: %s(%i)\n",
						fd, strerror(errno), errno);
					STEX(++stats.errors);
					STEX(++ops[mmapop].result.missed);
				} else {
					params->mmapped[areano].start  = addr;
					params->mmapped[areano].length = length;
				}
			}
			STEX(++ops[mmapop].result.ok);
		}
	} else if (RND(100) == 0) {
		munmap(params->mmapped[areano].start,
		       params->mmapped[areano].length);
		params->mmapped[areano].start = NULL;
		STEX(++ops[mmapop].result.ok);
	} else {
		char *scan;
		char *end;
		int length;

		scan = params->mmapped[areano].start;
		scan += RND(params->mmapped[areano].length);
		end = minp(scan + RND(max_buf_size) + 30,
			   params->mmapped[areano].start +
			   params->mmapped[areano].length - 1);
		if (RND(1) == 0) {
			char x;

			x = 0;
			for (;scan < end; ++scan)
				x += *scan;
		} else {
			for (;scan < end; ++scan)
				*scan = ((int)scan) ^ (*scan);
		}
		STEX(++ops[mmapop].result.ok);
	}
}

static void
gc_file(params_t *params)
{
	params->buffer[0] = 0x66;
	pip_file(params);
	params->buffer[0] = 0x00;
}

static void
nap(int secs, int nanos)
{
	if (!benchmark)
		_nap(secs, nanos);
}

static void
_nap(int secs, int nanos)
{
	if ((secs > 0) || (nanos > 0)) {
		struct timespec delay;

		delay.tv_sec = secs;
		delay.tv_nsec = nanos;

		if (nanosleep(&delay, NULL) == -1) {
			fprintf(stderr, "nanosleep: %s(%i)\n", strerror(errno),
				errno);
		}
		STEX(++stats.naps);
	}
}

/*
 * When renaming or linking file (through link, rename, or symlink), maintain
 * certain order, so that infinite loops of symlinks are avoided.
 */
static void orderedname(params_t *params, char *name)
{
	int targetno;
	int dirno;

	targetno = params->fileno +
		RND(params->files - params->fileno - 1) + 1;

	dirno = RND(params->dirs);
	sprintf(name, "d%x/%x", dirno, targetno);
}

/*
  Local variables:
  c-indentation-style: "K&R"
  mode-name: "LC"
  c-basic-offset: 8
  tab-width: 8
  fill-column: 79
  End:
*/
