/* @(#)nfsstones.c	19.1 (ES0-DMD) 02/25/03 14:35:52 */
/*===========================================================================
  Copyright (C) 1995 European Southern Observatory (ESO)
 
  This program is free software; you can redistribute it and/or 
  modify it under the terms of the GNU General Public License as 
  published by the Free Software Foundation; either version 2 of 
  the License, or (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 675 Massachusetss Ave, Cambridge, 
  MA 02139, USA.
 
  Corresponding concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

#include <sys/param.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <fcntl.h>

/*
 * This program takes two arguments.  The first one is the directory to
 * run the tests in.  The second one is the lock file name.  The program
 * will set itself up, then try to obtain the lock.  This will allow
 * the benchmark to sync up.  When it has the lock, it will release it
 * right away to allow someone else to get it.  This should happen fast
 * enough that the clients will start within a second or two of each other.
 *
 */

/*
 * Note: with the below settings, each client will use about 12.2 MB of
 * disk space on the server.  It is important to keep the disk space
 * per client above the amount of memory per client.  Since we are
 * trying to test the server speed, not the client cache speed, the
 * blocks per file should be adjusted to keep the cache overflowing.
 * Note: The above observation does not apply to suns not running
 * 	SunOS >= 4.0
 */
#define	TOP_DIRS	2
#define	BOT_DIRS	3
#define	FILES_PER_DIR	1
#define FILE_CREATES	83
#define NREAD_LINK	583
#define	BLOCKS_PER_FILE	250
#define FILE_LOOKUPS	4167
#define BYTES_PER_BLOCK	8192
#define TOT_DIRS	TOP_DIRS * BOT_DIRS
#define TOT_FILES	TOT_DIRS * FILES_PER_DIR

/*
 * The number eleven comes from:
 * 1. write the file
 * 2. read sequentially
 * 3. read the file (not sequentially)
 * 4. read sequentially
 * 5. read sequentially
 * 6. read sequentially
 * 7. read the file (not sequentially)
 * 8. read sequentially
 * 9. read sequentially
 * 10. read sequentially
 * 11. read the file (not sequentially)
 */
#define TOT_FILEOPS	TOT_FILES * 11 * BLOCKS_PER_FILE + \
			(FILE_LOOKUPS + FILE_CREATES + NREAD_LINK) * TOT_FILES

/*
 * The number 2 comes from the create/delete of the directories
 */
#define TOTAL_OPS	TOT_DIRS * 2 + TOT_FILES * 2 + TOT_FILEOPS

/*
char *strcpy(), *strcat(), *sprintf();
long lseek();
*/

int buf[BYTES_PER_BLOCK/sizeof(int)];
main(argc, argv)
	int argc;
	char *argv[];
{
	char *path;
	char *lock;
	int lock_fd;
	struct timeval start_time, end_time;
	struct timezone dummy;
	int i, j, num = 0, pid, wait();
	char s[BUFSIZ], t[BUFSIZ], u[BUFSIZ];
	double total_time;
	union wait status;
	int error = 0;
	struct stat stat_buf;

	bzero((char *)buf, BYTES_PER_BLOCK);
	if (argc != 3) {
		(void)fprintf(stderr, "Usage: %s path_prefix lock_file_path\n",
			argv[0]);
		exit(-1);
	}

	path = argv[1];
	lock = argv[2];

	if (stat(path, &stat_buf) != 0) {
		perror("path_prefix");
		exit(-1);
	}

	if ((stat_buf.st_mode & S_IFMT) != S_IFDIR ||
	    access(path, W_OK|X_OK) != 0) {
		(void)fprintf(stderr,
		    "Path_prefix is not a directory with write permission.\n");
		exit(-1);
	}

	if ((lock_fd = open(lock, O_RDWR)) < 0) {
		perror("lock_file_path");
		exit(-1);
	}

	if (chdir(path) != 0) {
		perror("path_prefix");
		exit(-1);
	}

	/*
	 * Now we go get the lock to synchronise with the other clients
	 */

	if (lockf(lock_fd, F_LOCK, 0) != 0) {
		perror("lockf");
		exit(-1);
	}

	/*
	 * Now let someone else have the lock
	 */

	if (lockf(lock_fd, F_ULOCK, 0) != 0) {
		perror("lockf");
		exit(-1);
	}

	/*
	 * Start Timing
	 */
	if (gettimeofday(&start_time, &dummy) == -1) {
		perror("gettimeofday");
		exit(-1);
	}

	for (i = 0; i < TOP_DIRS; i++) {
		(void)sprintf(u, "%8d", ++num);
		if (mkdir(u, 0777) != 0) {
			perror("mkdir");
			exit(-1);
		}
		for (j = 0; j < BOT_DIRS; j++) {
			(void)sprintf(t, "%8d", ++num);
			(void)strcpy(s, u);
			(void)strcat(s, "/");
			(void)strcat(s, t);
			if (mkdir(s, 0777) != 0) {
				perror("mkdir: subdir");
				exit(-1);
			}
			if ((pid = fork()) == 0)
				do_child(s);
			else if (pid == -1) {
				perror("fork");
				exit(-1);
			}
		}
	}

	/*
	 * Now we wait for all of the children to get done.
	 */

	while(wait(&status) != -1)
		if (status.w_status != 0)
			error = 1;

	num = 0;
	for (i = 0; i < TOP_DIRS; i++) {
		(void)sprintf(u, "%8d", ++num);
		for (j = 0; j < BOT_DIRS; j++) {
			(void)sprintf(t, "%8d", ++num);
			(void)strcpy(s, u);
			(void)strcat(s, "/");
			(void)strcat(s, t);
			if (rmdir(s) != 0) {
				perror("rmdir: subdir");
				exit(-1);
			}
		}
		if (rmdir(u) != 0) {
			perror("rmdir");
			exit(-1);
		}
	}

	if (error)
		exit(-1);

	if (gettimeofday(&end_time, &dummy) == -1) {
		perror("gettimeofday");
		exit(-1);
	}

	total_time = (double)(end_time.tv_sec - start_time.tv_sec) +
		((double)(end_time.tv_usec - start_time.tv_usec)/1000000.0);

	(void)printf("Total NFSstones: %d\n", TOTAL_OPS);
	(void)printf("Total time = %f seconds, or %f NFSstones/second\n", total_time,
		((double) TOTAL_OPS)/total_time);
	exit(0);
}

do_child(dir)
	char *dir;
{
	int i, fd, cfd;
	long j;
	char s[BUFSIZ];
	char b[BUFSIZ];
	extern int errno;

	if (chdir(dir) != 0) {
		perror("Subdirectory");
		exit(-1);
	}
	for (i = 0; i < FILES_PER_DIR; i++) {
		(void)sprintf(s, "%8d", i);
		if ((fd = open(s, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0) {
			perror("open");
			exit(-1);
		}

		seq_write(fd);
		seq_read(fd);

		/*
		 * Now lets through in some readlink calls
		 */
		
		if (symlink("/aaa/bbb/ccc/ddd/eee/fff", "Test_link") != 0) {
			perror("symlink");
			if (errno != EEXIST)
				exit(-1);
		}

		for (j = 0; j < NREAD_LINK; j++) {
			if (readlink("Test_link", b, BUFSIZ) == -1) {
				perror("readlink");
				exit(-1);
			}
		}


		nseq_read(fd);

		/*
		 * We put the unlink out here because a create retransmit
		 * could get us.
		 */
		(void)unlink("Test_link");
		/*
		 * Now lets throw in create requests
		 */

		for (j = 0; j < FILE_CREATES; j++) {
			if ((cfd = open("test_create", O_RDWR|O_CREAT|O_TRUNC,
				0666)) < 0) {
				perror("open");
				exit(-1);
			}
			/*
			 * Force the create to get out of cache
			 */
			(void)fsync(cfd);
			(void)close(cfd);
		}

		seq_read(fd);
		/*
		 * We put the unlink out here because a create retransmit
		 * could get us.
		 */

		(void)unlink("test_create");
		seq_read(fd);

		/*
		 * Now we look up non existent files
		 */
		for (j = 0; j < FILE_LOOKUPS; j++) {
			char sb[BUFSIZ];
			int pp = getpid();

			(void)sprintf(sb, "xx%d%d%d", j,pp,i);
			(void)access(sb, F_OK);
		}

		seq_read(fd);
		nseq_read(fd);
		seq_read(fd);
		seq_read(fd);
		seq_read(fd);
		nseq_read(fd);

		(void)close(fd);

		/*
		 * Now we rename the file
		 */
		if (rename(s, "Test_of_rename") != 0) {
			perror("rename");
			exit(-1);
		}

		/*
		 * Finally get rid of the file
		 */
		if (unlink("Test_of_rename") != 0) {
			perror("unlink");

			/*
			 * Busy servers cause the client to retransmit the
			 * request, giving the ENOENT since the file was
			 * removed by the first request, so we ignore this.
			 * (Maybe we should penalize 1 NFS stone/sec ??)
			 */
			if (errno != ENOENT)
				exit(-1);
		}
		sync();
	}

	exit(0);
}

/*
 * Write a file descriptor sequentially
 */
seq_write(fd)
	int fd;
{
	long j;

	/*
	 * Write the file sequentially
	 */

	if (lseek(fd, 0L, L_SET) == -1) {
		perror("lseek");
		exit(-1);
	}

	for (j = 0; j < BLOCKS_PER_FILE; j++) {
		buf[0] = j; /* Minimal sanity check */
		if (write(fd,(char *)buf, BYTES_PER_BLOCK) != BYTES_PER_BLOCK) {
			perror("write");
			exit(-1);
		}
	}

	(void)fsync(fd);
}

seq_read(fd)
	int fd;
{
	long j;

	/*
	 * Read sequentially
	 */
	if (lseek(fd, 0L, L_SET) == -1) {
		perror("lseek");
		exit(-1);
	}

	for (j = 0; j < BLOCKS_PER_FILE; j++) {
		if (read(fd, (char *)buf, BYTES_PER_BLOCK) != BYTES_PER_BLOCK) {
			perror("read");
			exit(-1);
		}

		if (buf[0] != j) {
			(void)fprintf(stderr, "Data corruption error\n");
			exit(-1);
		}
	}
}

nseq_read(fd)
	int fd;
{
	long j;

	/*
	 * Read back the file not in order.
	 */
	for (j = 0; j < BLOCKS_PER_FILE/2; j++) {
		if (lseek(fd, j * BYTES_PER_BLOCK, L_SET) == -1) {
			perror("lseek");
			exit(-1);
		}

		if (read(fd, (char *)buf, BYTES_PER_BLOCK) != BYTES_PER_BLOCK) {
			perror("read");
			exit(-1);
		}

		if (buf[0] != j) {
			(void)fprintf(stderr, "Data corruption error\n");
			exit(-1);
		}

		if (lseek(fd, (BLOCKS_PER_FILE - (j + 1)) *
			BYTES_PER_BLOCK, L_SET) == -1) {
			perror("lseek");
			exit(-1);
		}

		if (read(fd, (char *)buf, BYTES_PER_BLOCK) != BYTES_PER_BLOCK) {
			perror("read");
			exit(-1);
		}

		if (buf[0] != (BLOCKS_PER_FILE - (j + 1))) {
			(void)fprintf(stderr, "Data corruption error\n");
			exit(-1);
		}
	}
}
