/*
* Disktest
* Copyright (c) International Business Machines Corp., 2001
*
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*  Please send e-mail to yardleyb@us.ibm.com if you have
*  questions or comments.
*
*  Project Website:  TBD
*
* $Id: main.c,v 1.11 2009/02/26 12:14:53 subrata_modak Exp $
*
*/
#include <stdio.h>
#ifdef WINDOWS
#include <windows.h>
#include <winioctl.h>
#include <io.h>
#include <process.h>
#include <sys/stat.h>
#include "getopt.h"
#else
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>

#include "defs.h"
#include "globals.h"
#include "main.h"
#include "usage.h"
#include "sfunc.h"
#include "parse.h"
#include "childmain.h"
#include "threading.h"
#include "dump.h"
#include "timer.h"
#include "stats.h"
#include "signals.h"

/* global */
child_args_t cleanArgs;
test_env_t cleanEnv;
char hostname[HOSTNAME_SIZE];	/* global system hostname */

void linear_read_write_test(test_ll_t * test)
{
	OFF_T *pVal1 = (OFF_T *) test->env->shared_mem;
	int i;

	if (test->args->flags & CLD_FLG_W) {
		test->env->bContinue = TRUE;
		*(pVal1 + OFF_WLBA) = test->args->start_lba;
		test->args->test_state = DIRCT_INC(test->args->test_state);
		test->env->lastAction.oper = WRITER;
		test->args->test_state = SET_OPER_W(test->args->test_state);
		test->args->test_state = SET_wFST_TIME(test->args->test_state);
//              srand(test->args->seed);        /* reseed so we can re create the same random transfers */
		memset(test->env->action_list, 0,
		       sizeof(action_t) * test->args->t_kids);
		test->env->action_list_entry = 0;
		test->env->wcount = 0;
		test->env->rcount = 0;
		if (test->args->flags & CLD_FLG_CYC)
			if (test->args->cycles == 0) {
				pMsg(INFO, test->args,
				     "Starting write pass, cycle %lu\n",
				     (unsigned long)test->env->pass_count);
			} else {
				pMsg(INFO, test->args,
				     "Starting write pass, cycle %lu of %lu\n",
				     (unsigned long)test->env->pass_count,
				     test->args->cycles);
		} else {
			pMsg(INFO, test->args, "Starting write pass\n");
		}
		CreateTestChild(ChildTimer, test);
		for (i = 0; i < test->args->t_kids; i++) {
			CreateTestChild(ChildMain, test);
		}
		/* Wait for the writers to finish */
		cleanUpTestChildren(test);
	}

	/* If the write test failed don't start the read test */
	if (!(TST_STS(test->args->test_state))) {
		return;
	}

	if (test->args->flags & CLD_FLG_R) {
		test->env->bContinue = TRUE;
		*(pVal1 + OFF_RLBA) = test->args->start_lba;
		test->args->test_state = DIRCT_INC(test->args->test_state);
		test->env->lastAction.oper = READER;
		test->args->test_state = SET_OPER_R(test->args->test_state);
		test->args->test_state = SET_rFST_TIME(test->args->test_state);
//              srand(test->args->seed);        /* reseed so we can re create the same random transfers */
		memset(test->env->action_list, 0,
		       sizeof(action_t) * test->args->t_kids);
		test->env->action_list_entry = 0;
		test->env->wcount = 0;
		test->env->rcount = 0;
		if (test->args->flags & CLD_FLG_CYC)
			if (test->args->cycles == 0) {
				pMsg(INFO, test->args,
				     "Starting read pass, cycle %lu\n",
				     (unsigned long)test->env->pass_count);
			} else {
				pMsg(INFO, test->args,
				     "Starting read pass, cycle %lu of %lu\n",
				     (unsigned long)test->env->pass_count,
				     test->args->cycles);
		} else {
			pMsg(INFO, test->args, "Starting read pass\n");
		}
		CreateTestChild(ChildTimer, test);
		for (i = 0; i < test->args->t_kids; i++) {
			CreateTestChild(ChildMain, test);
		}
		/* Wait for the readers to finish */
		cleanUpTestChildren(test);
	}
}

unsigned long init_data(test_ll_t * test, unsigned char **data_buffer_unaligned)
{
	int i;
	OFF_T *pVal1;

	unsigned long data_buffer_size;

#ifdef WINDOWS
	if (CreateMutex(NULL, FALSE, "gbl") == NULL) {
		pMsg(ERR, test->args,
		     "Failed to create semaphore, error = %u\n",
		     GetLastError());
		return (GetLastError());
	}
	if ((test->env->mutexs.MutexACTION =
	     CreateMutex(NULL, FALSE, NULL)) == NULL) {
		pMsg(ERR, test->args,
		     "Failed to create semaphore, error = %u\n",
		     GetLastError());
		return (GetLastError());
	}
	if ((test->env->mutexs.MutexIO =
	     CreateMutex(NULL, FALSE, NULL)) == NULL) {
		pMsg(ERR, test->args,
		     "Failed to create semaphore, error = %u\n",
		     GetLastError());
		return (GetLastError());
	}
#else

	mutexs_t mutexs =
	    { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER };
	test->env->mutexs = mutexs;

#endif

	if (test->args->seed == 0)
		test->args->seed = test->args->pid;
	srand(test->args->seed);

	/* create bitmap to hold write/read context: each bit is an LBA */
	/* the stuff before BMP_OFFSET is the data for child/thread shared context */
	test->env->bmp_siz =
	    (((((size_t) test->args->vsiz)) / 8) ==
	     0) ? 1 : ((((size_t) test->args->vsiz)) / 8);
	if ((test->args->vsiz / 8) != 0)
		test->env->bmp_siz += 1;	/* account for rounding error */

	/* We use that same data buffer for static data, so alloc here. */
	data_buffer_size = ((test->args->htrsiz * BLK_SIZE) * 2);
	if ((*data_buffer_unaligned =
	     (unsigned char *)ALLOC(data_buffer_size + ALIGNSIZE)) == NULL) {
		pMsg(ERR, test->args,
		     "Failed to allocate static data buffer memory.\n");
		return (-1);
	}
	/* create list to hold lbas currently be written */
	if ((test->env->action_list =
	     (action_t *) ALLOC(sizeof(action_t) * test->args->t_kids)) ==
	    NULL) {
		pMsg(ERR, test->args,
		     "Failed to allocate static data buffer memory.\n");
		return (-1);
	}

	test->env->data_buffer =
	    (unsigned char *)BUFALIGN(*data_buffer_unaligned);

	if ((test->env->shared_mem =
	     (void *)ALLOC(test->env->bmp_siz + BMP_OFFSET)) == NULL) {
		pMsg(ERR, test->args, "Failed to allocate bitmap memory\n");
		return (-1);
	}

	memset(test->env->shared_mem, 0, test->env->bmp_siz + BMP_OFFSET);
	memset(test->env->data_buffer, 0, data_buffer_size);
	memset(test->env->action_list, 0,
	       sizeof(action_t) * test->args->t_kids);
	test->env->action_list_entry = 0;

	pVal1 = (OFF_T *) test->env->shared_mem;
	*(pVal1 + OFF_WLBA) = test->args->start_lba;
	*(pVal1 + OFF_RLBA) = test->args->start_lba;
	test->args->test_state = SET_STS_PASS(test->args->test_state);
	test->args->test_state = SET_wFST_TIME(test->args->test_state);
	test->args->test_state = SET_rFST_TIME(test->args->test_state);
	test->args->test_state = DIRCT_INC(test->args->test_state);
	if (test->args->flags & CLD_FLG_W) {
		test->env->lastAction.oper = WRITER;
		test->args->test_state = SET_OPER_W(test->args->test_state);
	} else {
		test->env->lastAction.oper = READER;
		test->args->test_state = SET_OPER_R(test->args->test_state);
	}

	/* prefill the data buffer with data for compares and writes */
	switch (test->args->flags & CLD_FLG_PTYPS) {
	case CLD_FLG_FPTYPE:
		for (i = 0; i < sizeof(test->args->pattern); i++) {
			if ((test->args->
			     pattern & (((OFF_T) 0xff) <<
					(((sizeof(test->args->pattern) - 1) -
					  i) * 8))) != 0)
				break;
		}
		/* special case for pattern = 0 */
		if (i == sizeof(test->args->pattern))
			i = 0;
		fill_buffer(test->env->data_buffer, data_buffer_size,
			    &test->args->pattern,
			    sizeof(test->args->pattern) - i, CLD_FLG_FPTYPE);
		break;
	case CLD_FLG_RPTYPE:
		fill_buffer(test->env->data_buffer, data_buffer_size, NULL, 0,
			    CLD_FLG_RPTYPE);
		break;
	case CLD_FLG_CPTYPE:
		fill_buffer(test->env->data_buffer, data_buffer_size, 0, 0,
			    CLD_FLG_CPTYPE);
	case CLD_FLG_LPTYPE:
		break;
	default:
		pMsg(WARN, test->args, "Unknown fill pattern\n");
		return (-1);
	}

	return 0;
}

#ifdef WINDOWS
DWORD WINAPI threadedMain(test_ll_t * test)
#else
void *threadedMain(void *vtest)
#endif
{
#ifndef WINDOWS
	test_ll_t *test = (test_ll_t *) vtest;
#endif

	OFF_T *pVal1;
	unsigned char *data_buffer_unaligned = NULL;
	unsigned long ulRV;
	int i;
	unsigned char *sharedMem;

	extern unsigned short glb_run;
	extern int signal_action;

	test->args->pid = GETPID();

	init_gbl_data(test->env);

	if (make_assumptions(test->args) < 0) {
		TEXIT((uintptr_t) GETLASTERROR());
	}
	if (check_conclusions(test->args) < 0) {
		TEXIT((uintptr_t) GETLASTERROR());
	}
	if (test->args->flags & CLD_FLG_DUMP) {
		/*
		 * All we are doing is dumping filespec data to STDOUT, so
		 * we will do this here and be done.
		 */
		do_dump(test->args);
		TEXIT((uintptr_t) GETLASTERROR());
	} else {
		ulRV = init_data(test, &data_buffer_unaligned);
		if (ulRV != 0) {
			TEXIT(ulRV);
		}
		pVal1 = (OFF_T *) test->env->shared_mem;
	}

	pMsg(START, test->args, "Start args: %s\n", test->args->argstr);

	/*
	 * This loop takes care of passes
	 */
	do {
		test->env->pass_count++;
		test->env->start_time = time(NULL);
		if (test->args->flags & CLD_FLG_RPTYPE) {	/* force random data to be different each cycle */
			fill_buffer(test->env->data_buffer,
				    ((test->args->htrsiz * BLK_SIZE) * 2), NULL,
				    0, CLD_FLG_RPTYPE);
		}
		sharedMem = test->env->shared_mem;
		memset(sharedMem + BMP_OFFSET, 0, test->env->bmp_siz);
		if ((test->args->flags & CLD_FLG_LINEAR)
		    && !(test->args->flags & CLD_FLG_NTRLVD)) {
			linear_read_write_test(test);
		} else {
			/* we only reset the end time if not running a linear read / write test */
			test->env->end_time =
			    test->env->start_time + test->args->run_time;
			test->env->bContinue = TRUE;
			*(pVal1 + OFF_WLBA) = test->args->start_lba;
			test->args->test_state =
			    DIRCT_INC(test->args->test_state);
			test->args->test_state =
			    SET_wFST_TIME(test->args->test_state);
			test->args->test_state =
			    SET_rFST_TIME(test->args->test_state);
			if (test->args->flags & CLD_FLG_W) {
				test->env->lastAction.oper = WRITER;
				test->args->test_state =
				    SET_OPER_W(test->args->test_state);
			} else {
				test->env->lastAction.oper = READER;
				test->args->test_state =
				    SET_OPER_R(test->args->test_state);
			}
			memset(test->env->action_list, 0,
			       sizeof(action_t) * test->args->t_kids);
			test->env->action_list_entry = 0;
			test->env->wcount = 0;
			test->env->rcount = 0;

			if (test->args->flags & CLD_FLG_CYC)
				if (test->args->cycles == 0) {
					pMsg(INFO, test->args,
					     "Starting pass %lu\n",
					     (unsigned long)test->env->
					     pass_count);
				} else {
					pMsg(INFO, test->args,
					     "Starting pass %lu of %lu\n",
					     (unsigned long)test->env->
					     pass_count, test->args->cycles);
			} else {
				pMsg(INFO, test->args, "Starting pass\n");
			}

			CreateTestChild(ChildTimer, test);
			for (i = 0; i < test->args->t_kids; i++) {
				CreateTestChild(ChildMain, test);
			}
			/* Wait for the children to finish */
			cleanUpTestChildren(test);
		}

		update_cyc_stats(test->env);
		if ((test->args->flags & CLD_FLG_CYC)
		    && (test->args->flags & CLD_FLG_PCYC)) {
			print_stats(test->args, test->env, CYCLE);
		}
		update_gbl_stats(test->env);

		if (signal_action & SIGNAL_STOP) {
			break;
		}		/* user request to stop */
		if ((glb_run == 0)) {
			break;
		}
		/* global request to stop */
		if (!(test->args->flags & CLD_FLG_CYC)) {
			break;	/* leave, unless cycle testing */
		} else {
			if ((test->args->cycles > 0)
			    && (test->env->pass_count >= test->args->cycles)) {
				break;	/* leave, cycle testing complete */
			}
		}
	} while (TST_STS(test->args->test_state));
	print_stats(test->args, test->env, TOTAL);

	FREE(data_buffer_unaligned);
	FREE(test->env->shared_mem);
#ifdef WINDOWS
	CloseHandle(OpenMutex(SYNCHRONIZE, TRUE, "gbl"));
	CloseHandle(OpenMutex(SYNCHRONIZE, TRUE, "data"));
#endif

	if (TST_STS(test->args->test_state)) {
		if (signal_action & SIGNAL_STOP) {
			pMsg(END, test->args,
			     "User Interrupt: Test Done (Passed)\n");
		} else {
			pMsg(END, test->args, "Test Done (Passed)\n");
		}
	} else {
		if (signal_action & SIGNAL_STOP) {
			pMsg(END, test->args,
			     "User Interrupt: Test Done (Failed)\n");
		} else {
			pMsg(END, test->args, "Test Done (Failed)\n");
		}
	}
	TEXIT((uintptr_t) GETLASTERROR());
}

/*
 * Creates a new test structure and adds it to the list of
 * test structures already available.  Allocate all memory
 * needed by the new test.
 *
 * Returns the newly created test structure
 */
test_ll_t *getNewTest(test_ll_t * testList)
{
	test_ll_t *pNewTest;

	if ((pNewTest = (test_ll_t *) ALLOC(sizeof(test_ll_t))) == NULL) {
		pMsg(ERR, &cleanArgs,
		     "%d : Could not allocate memory for new test.\n",
		     GETLASTERROR());
		return NULL;
	}

	memset(pNewTest, 0, sizeof(test_ll_t));

	if ((pNewTest->args =
	     (child_args_t *) ALLOC(sizeof(child_args_t))) == NULL) {
		pMsg(ERR, &cleanArgs,
		     "%d : Could not allocate memory for new test.\n",
		     GETLASTERROR());
		FREE(pNewTest);
		return NULL;
	}
	if ((pNewTest->env = (test_env_t *) ALLOC(sizeof(test_env_t))) == NULL) {
		pMsg(ERR, &cleanArgs,
		     "%d : Could not allocate memory for new test.\n",
		     GETLASTERROR());
		FREE(pNewTest->args);
		FREE(pNewTest);
		return NULL;
	}
	memcpy(pNewTest->args, &cleanArgs, sizeof(child_args_t));
	memcpy(pNewTest->env, &cleanEnv, sizeof(test_env_t));

	pNewTest->next = testList;
	testList = pNewTest;
	return pNewTest;
}

test_ll_t *run()
{
	test_ll_t *newTest = NULL, *lastTest = NULL;

	if (cleanArgs.flags & CLD_FLG_FSLIST) {
		char *filespec = cleanArgs.device;
		char *aFilespec = NULL;
		FILE *file = NULL;

		if ((aFilespec = (char *)ALLOC(80)) == NULL) {
			pMsg(ERR, &cleanArgs,
			     "Could not allocate memory to read file");
			return newTest;
		}

		file = fopen(filespec, "r");
		if (file == NULL) {
			pMsg(ERR,
			     &cleanArgs,
			     "%s is not a regular file, could not be opened for reading, or was not found.",
			     filespec);

			return newTest;
		}

		while (!feof(file)) {
			memset(aFilespec, 0, 80);
			fscanf(file, "%79s", aFilespec);
			if (aFilespec[0] != 0) {	/* if we read something useful */
				lastTest = newTest;
				newTest = getNewTest(lastTest);
				if (newTest != lastTest) {
					memset(newTest->args->device, 0,
					       DEV_NAME_LEN);
					strncpy(newTest->args->device,
						aFilespec, strlen(aFilespec));
					createChild(threadedMain, newTest);
				} else {
					newTest = lastTest;
					break;
				}
			}
		}

		fclose(file);
		FREE(aFilespec);
	} else {
		newTest = getNewTest(newTest);
		if (newTest != NULL) {
			createChild(threadedMain, newTest);
		}
	}

	return newTest;
}

int main(int argc, char **argv)
{
	extern time_t global_start_time;
	extern unsigned long glb_flags;	/* global flags GLB_FLG_xxx */
	int i;

#ifdef WINDOWS
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		pMsg(WARN, &cleanArgs,
		     "Windows setup of Winsock failed, can't retrieve host name, continuing");
	}
#endif

	setup_sig_mask();

	memset(hostname, 0, HOSTNAME_SIZE);
	gethostname(hostname, HOSTNAME_SIZE);

	setbuf(stdout, NULL);

	glb_flags = 0;
	global_start_time = time(NULL);

	strncpy(cleanArgs.device, "No filespec", strlen("No filespec"));
	cleanArgs.stop_lba = -1;
	cleanArgs.stop_blk = -1;
	cleanArgs.ioTimeout = DEFAULT_IO_TIMEOUT;
	cleanArgs.flags |= CLD_FLG_ALLDIE;
	cleanArgs.flags |= CLD_FLG_ERR_REREAD;
	cleanArgs.flags |= CLD_FLG_LBA_SYNC;

	for (i = 1; i < argc - 1; i++) {
		strncat(cleanArgs.argstr, argv[i],
			(MAX_ARG_LEN - 1) - strlen(cleanArgs.argstr));
		strncat(cleanArgs.argstr, " ",
			(MAX_ARG_LEN - 1) - strlen(cleanArgs.argstr));
	}

	if (fill_cld_args(argc, argv, &cleanArgs) < 0)
		exit(1);

	cleanUp(run());

#ifdef WINDOWS
	WSACleanup();
#endif

	return 0;
}
