/******************************************************************************/
/*									      */
/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA    */
/*									      */
/******************************************************************************/

/******************************************************************************/
/*									      */
/* History:	July - 16 - 2001 Created by Manoj Iyer, IBM Austin TX.	      */
/*			         email:manjo@austin.ibm.com		      */
/*                                                                            */
/*              July - 24 - 2001 Modified. Added loop and pass arguments to   */
/*                               the thread function. Fixed usage function.   */
/*				 added option to MAP_PRIVATE or MAP_SHARED.   */
/*                                                                            */
/*              Aug  - 01 - 2001 Modified. Added include file signal.h        */
/*                               has defines required by signal handler.      */
/*                                                                            */
/*		Oct  - 25 - 2001 Modified. Fixed bug in main(). Pthread_join  */
/*				 sets the return value of the thread in thread*/
/*			         return_status parameter.                     */
/*		Nov - 09  - 2001 Modified. Removed compile errors             */
/*				 - incomplete comment in line 301             */
/*				 - missing argument to printf in pthread_join */
/*                                                                            */
/*              Apr  - 16 - 2003 Modified - replaced tempnam() use with       */
/*                               mkstemp(). -Robbie Williamson                */
/*                               email:robbiew@us.ibm.com                     */
/*                                                                            */
/*              May  - 12 - 2003 Modified - remove the really large files     */
/*                               when we are done with the test - Paul Larson */
/*                               email:plars@linuxtestproject.org             */
/* File:	mmap3.c							      */
/*			         					      */
/* Description: Test the LINUX memory manager. The program is aimed at        */
/*              stressing the memory manager by repeaded map/write/unmap      */
/*		of file/memory of random size (maximum 1GB) this is done by   */
/*		multiple processes.					      */
/*			         					      */
/*		Create a file of random size upto 1000 times 4096, map it,    */
/*		change the contents of the file and unmap it. This is repeated*/
/*		several times for the specified number of hours by a certain. */
/*		number of processes.					      */
/*			         					      */
/******************************************************************************/

/* Include Files							      */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sched.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include "test.h"

/* Defines								      */
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#define prtln() printf(" I AM HERE ==> %s %d\n", __FILE__, __LINE__);

/******************************************************************************/
/*                                                                            */
/* Function:    mkfile                                                        */
/*                                                                            */
/* Description: Create a temparory file of ramdom size. 		      */
/*                                                                            */
/* Input:	NONE							      */
/*                                                                            */
/* Output:      size - size of the temp file  created 			      */
/*                                                                            */
/* Return:      int fd - file descriptor if the file was created.             */
/*              -1     - if it failed to create.                              */
/*                                                                            */
/******************************************************************************/
static int mkfile(int *size)
{				/* size of the file to be generated in GB     */
	int fd;			/* file descriptior of tmpfile                */
	int index = 0;		/* index into the file, fill it with a's      */
	char buff[4096];	/* buffer that will be written to the file.   */
	char template[PATH_MAX];	/* template for temp file name                */

	/* fill the buffer with a's and open a temp file */
	memset(buff, 'a', 4096);
	snprintf(template, PATH_MAX, "ashfileXXXXXX");
	if ((fd = mkstemp(template)) == -1) {
		perror("mkfile(): mkstemp()");
		return -1;
	}
	unlink(template);

	srand(time(NULL) % 100);
	*size = (1 + (int)(1000.0 * rand() / (RAND_MAX + 1.0))) * 4096;

	/* fill the file with the character a */
	while (index < *size) {
		index += 4096;
		if (write(fd, buff, 4096) == -1) {
			perror("mkfile(): write()");
			return -1;
		}
	}

	/* make sure a's are written to the file. */
	if (fsync(fd) == -1) {
		perror("mkfile(): fsync()");
		return -1;
	}
	return fd;
}

/******************************************************************************/
/*                                                                            */
/* Function:    sig_handler                                                   */
/*                                                                            */
/* Description: handle SIGALRM raised by set_timer(), SIGALRM is raised when  */
/*              the timer expires. If any other signal is recived exit the    */
/*              test.                                                         */
										/*                                                                            *//* Input:       signal - signal number, intrested in SIGALRM!                 */
/*                                                                            */
/* Return:      exit 1 if unexpected signal is recived                        */
/*              exit 0 if SIGALRM is recieved                                 */
/*                                                                            */
/******************************************************************************/
static void sig_handler(int signal)
{				/* signal number, set to handle SIGALRM       */
	if (signal != SIGALRM) {
		fprintf(stderr,
			"sig_handlder(): unexpected signal caught [%d]\n",
			signal);
		exit(-1);
	} else
		fprintf(stdout, "Test ended, success\n");
	exit(0);
}

										/******************************************************************************//*                                                                            */
/* Function:	usage							      */
/*									      */
/* Description:	Print the usage message.				      */
/*									      */
/* Return:	exits with -1						      */
/*									      */
/******************************************************************************/
static void usage(char *progname)
{				/* name of this program                       */
	fprintf(stderr,
		"Usage: %s -h -l -n -p -x\n"
		"\t -h help, usage message.\n"
		"\t -l number of map - write - unmap.    default: 1000\n"
		"\t -n number of LWP's to create.        default: 20\n"
		"\t -p set mapping to MAP_PRIVATE.       default: MAP_SHARED\n"
		"\t -x time for which test is to be run. default: 24 Hrs\n",
		progname);
	exit(-1);
}

/******************************************************************************/
/*									      */
/* Function:	map_write_unmap						      */
/*									      */
/* Description:	map a file write a character to it and unmap, this is done for*/
/*		user defined number of times.				      */
/*									      */
/* Input:	arg[0]		   number of times map - write -unmap is done */
/* 		arg[1]		   Map type:  				      */
/*					TRUE  - MAP_PRIVATE 		      */
/*					FALSE - MAP_SHARED		      */
/*									      */
/* Return:	MWU_FAIL on error.				              */
/*              MWU_SUCCESS on error less completion of the loop.             */
/*									      */
/******************************************************************************/
void *map_write_unmap(void *args)
{				/* file descriptor of the file to be mapped.  */
	int fsize;		/* size of the file to be created.            */
	int fd;			/* temporary file descriptor                  */
	int mwu_ndx = 0;	/* index to number of map/write/unmap         */
	caddr_t *map_address;	/* pointer to file in memory                  */
	int map_type;		/* MAP_PRIVATE or MAP_SHARED                  */
	long *mwuargs =		/* local pointer to arguments                 */
	    (long *)args;

	while (mwu_ndx++ < (int)mwuargs[0]) {
		if ((fd = mkfile(&fsize)) == -1) {
			fprintf(stderr,
				"main(): mkfile(): Failed to create temp file.\n");
			pthread_exit((void *)-1);
		}

		if ((int)mwuargs[1])
			map_type = MAP_PRIVATE;
		else
			map_type = MAP_SHARED;
		if ((map_address =
		     mmap(0, (size_t) fsize, PROT_WRITE | PROT_READ, map_type,
			  (int)fd, 0))
		    == (caddr_t *) - 1) {
			perror("map_write_unmap(): mmap()");
			pthread_exit((void *)-1);
		}

		memset(map_address, 'A', fsize);

		fprintf(stdout,
			"Map address = %p\nNum iter: [%d]\nTotal Num Iter: [%d]",
			map_address, mwu_ndx, (int)mwuargs[0]);
		usleep(1);
		if (munmap(map_address, (size_t) fsize) == -1) {
			perror("map_write_unmap(): mmap()");
			pthread_exit((void *)-1);
		}
		close(fd);
	}
	pthread_exit(NULL);
}

/******************************************************************************/
/*                                                                            */
/* Function:    main                                                          */
/*                                                                            */
/* Descrption:	Create a large file of size up to a  Giga Bytes.  write to it */
/*		lower case alphabet 'a'. Map the file and change the contents */
/*		to 'A's (upper case alphabet), write the contents to the file,*/
/*		and unmap the file from memory. Spwan a certian number of     */
/*		LWP's that will do the above.				      */
/*                                                                            */
/* Return:	exits with -1 on error					      */
/*		exits with a 0 on success.				      */
/*                                                                            */
/******************************************************************************/
int main(int argc,		/* number of input parameters.                        */
	 char **argv)
{				/* pointer to the command line arguments.       */
	int c;			/* command line options                       */
	int num_iter;		/* number of iteration to perform             */
	int num_thrd;		/* number of threads to create                */
	int thrd_ndx;		/* index into the array of threads.           */
	float exec_time;	/* period for which the test is executed      */
	void *status;		/* exit status for light weight process       */
	int sig_ndx;		/* index into signal handler structure.       */
	pthread_t thid[1000];	/* pids of process that will map/write/unmap  */
	long chld_args[3];	/* arguments to funcs execed by child process */
	extern char *optarg;	/* arguments passed to each option            */
	struct sigaction sigptr;	/* set up signal, for interval timer          */
	int map_private =	/* if TRUE mapping is private, ie, MAP_PRIVATE */
	    FALSE;

	static struct signal_info {
		int signum;	/* signal number that hasto be handled                */
		char *signame;	/* name of the signal to be handled.                  */
	} sig_info[] = {
		{
		SIGHUP, "SIGHUP"}, {
		SIGINT, "SIGINT"}, {
		SIGQUIT, "SIGQUIT"}, {
		SIGABRT, "SIGABRT"}, {
		SIGBUS, "SIGBUS"}, {
		SIGSEGV, "SIGSEGV"}, {
		SIGALRM, "SIGALRM"}, {
		SIGUSR1, "SIGUSR1"}, {
		SIGUSR2, "SIGUSR2"}, {
		-1, "ENDSIG"}
	};

	/* set up the default values */
	num_iter = 1000;	/* repeate map - write - unmap operation 1000 times   */
	num_thrd = 40;		/* number of LWP's to create                          */
	exec_time = 24;		/* minimum time period for which to run the tests     */

	while ((c = getopt(argc, argv, "h:l:n:px:")) != -1) {
		switch (c) {
		case 'h':
			usage(argv[0]);
			break;
		case 'l':
			if ((num_iter = atoi(optarg)) == 0)
				num_iter = 1000;
			break;
		case 'n':
			if ((num_thrd = atoi(optarg)) == 0)
				num_thrd = 20;
			break;
		case 'p':
			map_private = TRUE;
			break;
		case 'x':
			if ((exec_time = atof(optarg)) == 0)
				exec_time = 24;
			break;
		default:
			usage(argv[0]);
			break;
		}
	}

	/* set up signals */
	sigptr.sa_handler = (void (*)(int signal))sig_handler;
	sigfillset(&sigptr.sa_mask);
	sigptr.sa_flags = SA_SIGINFO;
	for (sig_ndx = 0; sig_info[sig_ndx].signum != -1; sig_ndx++) {
		sigaddset(&sigptr.sa_mask, sig_info[sig_ndx].signum);
		if (sigaction(sig_info[sig_ndx].signum, &sigptr,
			      NULL) == -1) {
			perror("man(): sigaction()");
			fprintf(stderr,
				"could not set handler for SIGALRM, errno = %d\n",
				errno);
			exit(-1);
		}
	}
	chld_args[0] = num_iter;
	chld_args[1] = map_private;
	alarm(exec_time * 3600.00);

	fprintf(stdout,
		"\n\n\nTest is set to run with the following parameters:\n"
		"\tDuration of test: [%f]hrs\n"
		"\tNumber of threads created: [%d]\n"
		"\tnumber of map-write-unmaps: [%d]\n"
		"\tmap_private?(T=1 F=0): [%d]\n\n\n\n", exec_time, num_thrd,
		num_iter, map_private);

	for (;;) {
		/* create num_thrd number of threads. */
		for (thrd_ndx = 0; thrd_ndx < num_thrd; thrd_ndx++) {
			if (pthread_create
			    (&thid[thrd_ndx], NULL, map_write_unmap,
			     chld_args)) {
				perror("main(): pthread_create()");
				exit(-1);
			}
			sched_yield();
		}

		/* wait for the children to terminate */
		for (thrd_ndx = 0; thrd_ndx < num_thrd; thrd_ndx++) {
			if (pthread_join(thid[thrd_ndx], &status)) {
				perror("main(): pthread_create()");
				exit(-1);
			} else {
				if (status) {
					fprintf(stderr,
						"thread [%d] - process exited with errors %ld\n",
						WEXITSTATUS((long)status),
						(long)status);
					exit(-1);
				}
			}
		}
	}
	exit(0);
}
