#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>

#define SIXTEENMB (1024*1024*16)
#define FOURKB (1024*4)
#define EIGHTKB (1024*8)

void writeout(int fd, char *buf)
{
	int i;
	for (i = 0; i < SIXTEENMB / EIGHTKB; ++i) {
		if (write(fd, buf, EIGHTKB) != EIGHTKB) {
			fprintf(stderr, "Error in write: %m!\n");
			exit(1);
		}
	}
}

int main(int argc, char *argv[])
{
	int method, open_close_iterations, rewrite_iterations;
	const char *method_name = "unknown method";
	int fd, i, j;
	double tt;
	struct timeval tv1, tv2;
	char *buf0, *buf1;
	const char *filename; /* for convenience */
	struct stat st;
	
	if (argc != 4) {
		fprintf(stderr, "Usage: %s <filename> <open/close iterations> <rewrite iterations>\n", argv[0]);
		exit(1);
	}
	
	filename = argv[1];
	
	open_close_iterations = atoi(argv[2]);
	if (open_close_iterations < 0) {
		fprintf(stderr, "Error parsing 'open_close_iterations'!\n");
		exit(1);
	}
	
	rewrite_iterations = atoi(argv[3]);
	if (rewrite_iterations < 0) {
		fprintf(stderr, "Error parsing 'rewrite_iterations'!\n");
		exit(1);
	}
	
	buf0 = malloc(SIXTEENMB);
	if (!buf0) {
		fprintf(stderr, "Unable to allocate memory!\n");
		exit(1);
	}
	memset(buf0, 0, SIXTEENMB);
	
	buf1 = malloc(SIXTEENMB);
	if (!buf1) {
		fprintf(stderr, "Unable to allocate memory!\n");
		exit(1);
	}
	memset(buf1, 1, SIXTEENMB);
	
	for (method = 0;method < 3; ++method) {
		gettimeofday(&tv1, NULL);
		for (i = 0;i < open_close_iterations; ++i) {
			fd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0600);
			if (fd < 0) {
				fprintf(stderr, "Error opening file: %m\n");
				exit(1);
			}
			switch (method) {
				case 0:
					/* classic */
					method_name = "classic";
					writeout(fd, buf0);
					break;
				case 1:
					/* posix_fallocate */
					method_name = "posix_fallocate";
					if (posix_fallocate(fd, 0, SIXTEENMB) != 0) {
						fprintf(stderr, "Error in posix_fallocate!\n");
						exit(1);
					}
					break;
				case 2:
					/* glibc compatability */
					method_name = "glibc emulation";
					{
						for (j = 0; j < (SIXTEENMB / FOURKB) - 1; ++j) {
							if (pwrite(fd, "\0\0\0\0", 4, j * FOURKB) != 4) {
								fprintf(stderr, "Error in pwrite: %m!\n");
								exit(1);
							}
						}
						if (pwrite(fd, buf0, FOURKB, j * FOURKB) != FOURKB) {
							fprintf(stderr, "Error in pwrite: %m!\n");
							exit(1);
						}
						
					}
					break;
				default:
					fprintf(stderr, "Unknown value for 'method': %d\n", method);
					exit(1);
			}
			if (fsync(fd)) {
				fprintf(stderr, "Error in fsync: %m!\n");
				exit(1);
			}
			/* double check file size */
			if (fstat(fd, &st) != 0) {
				fprintf(stderr, "Error in fstat: %m!\n");
				exit(1);
			}
			if (st.st_size != SIXTEENMB) {
				fprintf(stderr, "File size unexpected.\n");
				exit(1);
			}
			
			for (j = 0; j < rewrite_iterations; ++j) {
				lseek(fd, 0, SEEK_SET);
				writeout(fd, buf1);
				if (fdatasync(fd)) {
					fprintf(stderr, "Error in fdatasync: %m!\n");
					exit(1);
				}
			}
			if (close(fd)) {
				fprintf(stderr, "Error in close: %m!\n");
				exit(1);
			}
			unlink(filename);		/* don't check for error */
		}
		gettimeofday(&tv2, NULL);
		tt = (tv2.tv_usec + tv2.tv_sec * 1000000) - (tv1.tv_usec + tv1.tv_sec * 1000000);
		tt /= 1000000;
		fprintf(stderr,
			"method: %s. %d open/close iterations, %d rewrite in %0.4fs\n",
			method_name, open_close_iterations, rewrite_iterations, tt);
	}
	/* cleanup */
	free(buf0);
	free(buf1);
	return 0;
}
