/* -*- 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:
*/