#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/gmon.h>

extern char *optarg;
extern int optind;

#ifndef O_DIRECT
#define O_DIRECT 0x00080000
#endif

int
main(int argc, char **argv)
{
       int c, i, fd, ifd, ofd;
       int mode, count, nrep, reps, seek, dofsync, gprof, rv;
       char *file, *ifile, *ofile, *buf, *cp;
       size_t size;
       struct timeval tv1, tv2;
       int mib[3];
       int gprof_state, sz;
       int value;
       int direct, trunc, touch, openflags, pgsz;
       size_t actual;

       pgsz = getpagesize();
       sz = sizeof(gprof_state);
       file = NULL;
       ifile = NULL;
       ifd = 0;
       ofile = NULL;
       ofd = 0;
       mode = 0;
       count = 300;
       size = 1024 * 1024;
       seek = 0;
       nrep = 1;
       dofsync = 0;
       direct = 0;
       gprof = 0;
       value = 'x';
       trunc = 0;
       touch = 0;
       actual = 0;
       while ((c = getopt(argc, argv, "rwdc:s:n:k:flpv:tTo:i:")) != -1) {
               switch (c) {
               case 'r':
                       mode = 1;
                       break;

               case 'w':
                       mode = 2;
                       break;

               case 'd':
                       direct = 1;
                       break;

               case 'c':
                       count = strtol(optarg, NULL, 0);
                       break;

               case 'n':
                       nrep = strtol(optarg, NULL, 0);
                       break;

               case 's':
                       size = strtol(optarg, NULL, 0);
                       break;

               case 'k':
                       seek = strtol(optarg, NULL, 0);
                       break;

               case 'f':
                       dofsync = 1;
                       break;

               case 'l':
                       if (mlockall(MCL_CURRENT|MCL_FUTURE) < 0) {
                               err(1, "mlockall");
                       }
                       break;

               case 'p':
                       gprof = 1;
                       break;

               case 'v':
                       value = *optarg;
                       break;

               case 't':
                       trunc = 1;
                       break;

               case 'T':
                       touch = 1;
                       break;

               case 'i':
                       ifile = optarg;
                       break;

               case 'o':
                       ofile = optarg;
                       break;

               default:
                       errx(1, "bogus option `%c'", c);
               }
       }
       if (optind == argc) {
               errx(1, "must specify a file");
       }
       file = argv[optind];
       if (mode == 0) {
               errx(1, "must use one of `-r' or `-w'");
       }

       openflags = (mode == 1) ? O_RDONLY : (O_WRONLY|O_CREAT);
       if (trunc) {
               openflags |= O_TRUNC;
       }
       if (direct) {
               openflags |= O_DIRECT;
       }
       fd = open(file, openflags, 0666);
       if (fd < 0) {
               err(1, "open");
       }

       if (ifile) {
               ifd = open(ifile, O_RDONLY);
               if (ifd < 0) {
                       err(1, "open ifile");
               }
       }
       if (ofile) {
               ofd = open(ofile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
               if (ofd < 0) {
                       err(1, "open ofile");
               }
       }

       buf = malloc(size);
       if (buf == NULL) {
               err(1, "malloc");
       }
       memset(buf, value, size);

       if (gprof) {
               mib[0] = CTL_KERN;
               mib[1] = KERN_PROF;
               mib[2] = GPROF_STATE;
               gprof_state = GMON_PROF_ON;
               if (sysctl(mib, 3, NULL, NULL, &gprof_state, sz) < 0) {
                       err(1, "sysctl gprof on");
               }
       }
       if (gettimeofday(&tv1, NULL) < 0) {
               err(1, "gettimeofday 1");
       }
       reps = nrep;
       while (reps--) {
               if (mode == 1) {
                       for (i = 0; i < count; i++) {
                               rv = read(fd, buf, size);
                               if (rv < 0) {
                                       err(1, "read");
                               }
                               if (rv == 0) {
                                       fprintf(stderr, "read hit EOF\n");
                                       break;
                               }
                               actual += rv;
                               if (ofile) {
                                       (void) write(ofd, buf, rv);
                               }
                               if (touch) {
                                       for (cp = buf; cp < buf + size;
                                            cp += pgsz) {
                                               *cp = 1;
                                       }
                               }
                               if (seek && lseek(fd, seek, SEEK_CUR) < 0) {
                                       err(1, "lseek");
                               }
                       }
               } else {
                       for (i = 0; i < count; i++) {
                               if (ifile) {
                                       rv = read(ifd, buf, size);
                                       if (rv < 0) {
                                               err(1, "read ifile");
                                       }
                               }
                               rv = write(fd, buf, size);
                               if (rv < 0) {
                                       err(1, "write");
                               }
                               if (touch) {
                                       for (cp = buf; cp < buf + size;
                                            cp += pgsz) {
                                               *cp = 1;
                                       }
                               }
                               actual += rv;
                               if (seek && lseek(fd, seek, SEEK_CUR) < 0) {
                                       err(1, "lseek");
                               }
                       }
               }
               if (dofsync) {
                       rv = fsync(fd);
                       if (rv < 0) {
                               err(1, "fsync");
                       }
               }
               if (lseek(fd, 0, SEEK_SET) < 0) {
                       err(1, "lseek");
               }
       }
       if (gettimeofday(&tv2, NULL) < 0) {
               err(1, "gettimeofday 1");
       }
       if (gprof) {
               gprof_state = GMON_PROF_OFF;
               if (sysctl(mib, 3, NULL, NULL, &gprof_state, sz) < 0) {
                       err(1, "sysctl gprof off");
               }
       }

       timersub(&tv2, &tv1, &tv2);
       printf("%lld bytes transferred in %ld.%03ld secs (%lld bytes/sec)\n",
              ((uint64_t)actual), tv2.tv_sec, tv2.tv_usec / 1000,
              (((uint64_t)actual * 1000000) /
               ((uint64_t)tv2.tv_sec * 1000000 + tv2.tv_usec)));
       exit(0);
}