#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>
#include <sys/time.h>

void
usage(char *name) {
	fprintf(stderr, "%s: -rwsx -c [count]\n", name);
	fprintf(stderr, "  r/w: perform read/write operations\n");
	fprintf(stderr, "  s/x: perform sequential/random operations\n");
	fprintf(stderr, "    c: how man ops to perform\n");
}

int
blkgetsize(int fd, unsigned long *nblockp)
{
    int error;
    int sz;

    /* This will get us the size of the device in 512-byte blocks. I
       want to get them in BLOCK_SIZE-byte blocks */
    if ((error = ioctl(fd, BLKGETSIZE, &sz)) < 0) {
	perror("ioctl BLKGETSIZE failed");
	return error;
    }
    *nblockp = (sz * 512) / BLOCK_SIZE;
    return 0;
}

int
gettime(struct timeval *tvp)
{
    struct timezone tz;
    int error;

    if ((error = gettimeofday(tvp, &tz)) < 0) {
	perror("gettimeofday failed");
	return error;
    }

    return 0;
}

long
tvdiffms(struct timeval *startp, struct timeval *finishp)
{
    long dsec  = finishp->tv_sec  - startp->tv_sec;
    long dusec = finishp->tv_usec - startp->tv_usec;
    dusec += 1000000 * dsec;
    return dusec;
}

/* The total number of blocks in the device */
unsigned long nblock;

/* The offset that the next read/write will occur at */
off_t offset;

/* The total number of read/write operations to perform */
int noperation = 256;

off_t
generate_sequential_address()
{
	offset++;
}


off_t
generate_random_address()
{
	offset = (rand() % nblock) * BLOCK_SIZE;
}

/* The function that is used to generate the next address block */
off_t (*generate_next_offset)() = generate_random_address;

/* The physical i/o operation to perform */
int (*operation)() = read;

/* The device on which to perform the operations */
char *device_name;


void
parse_args(int argc, char* argv[])
{
	char c;

	if (argc < 2) {
		usage(argv[0]);
		exit(1);
	}

	while ((c = getopt(argc, argv, "rwsxc:")) != -1) {
		switch (c) {
		case 'r':
			operation = read;
			break;

		case 'w':
			operation = write;
			break;

		case 's':
			generate_next_offset = generate_sequential_address;
			break;

		case 'x':
			generate_next_offset = generate_random_address;
			break;

		case 'c':
			noperation = atoi(optarg);
			break;

		default:
			usage(argv[0]);
			exit(1);
		}
	}
	device_name = argv[argc - 1];
}


int
main(int argc, char* argv[])
{
	int fd = 0;
	int i;
	struct timeval start, finish;
	long total, avg;

	parse_args(argc, argv);

	if ((fd = open(device_name, O_SYNC | O_RDWR)) < 0) {
		perror("open failed");
		return 1;
	}

	if (blkgetsize(fd, &nblock) < 0)
		goto done;

	printf("%s: there are %ld %d-byte blocks on the device.\n",
	       argv[0], nblock, BLOCK_SIZE);

	printf("%s: %d %s operations to be performed on %s\n",
	       argv[0], noperation,
	       (operation == read) ? "read" : "write",
	       device_name);

	if (generate_next_offset == generate_sequential_address) {
		/* Give offset a random value to begin with, in case
		   we do a sequential read/write */
		offset = (rand() % (nblock - noperation)) * BLOCK_SIZE;

		printf("%s: sequential operations will begin at offset "
		       "%ld\n", argv[0], offset);
	}

	if (gettime(&start) < 0)
		goto done;

	srand((unsigned int) start.tv_usec);

	for (i = 0; i < noperation; ++i) {
		char buf[BLOCK_SIZE];

		generate_next_offset();

		if (lseek(fd, offset + i, SEEK_SET) < 0) {
			perror("lseek failed");
			goto done;
		}

		if (operation(fd, buf, BLOCK_SIZE) < 0) {
			perror("write failed");
			goto done;
		}
	}

	if (gettime(&finish) < 0)
		goto done;

	total = tvdiffms(&start, &finish);
	avg   = total / noperation;

	printf("%s: total=%ldus avg=%ldus\n", argv[0], total, avg);

 done:
	if (fd) close(fd);
	return 0;
}

