/******************************************************************************/
/*									      */
/* Copyright (c) International Business Machines  Corp., 2001		      */
/* Copyright (c) 2001 Manoj Iyer <manjo@austin.ibm.com>                       */
/* Copyright (c) 2003 Robbie Williamson <robbiew@us.ibm.com>                  */
/* Copyright (c) 2004 Paul Larson <plars@linuxtestproject.org>                */
/* Copyright (c) 2007 <rsalveti@linux.vnet.ibm.com>                           */
/* Copyright (c) 2007 Suzuki K P <suzuki@in.ibm.com>                          */
/* Copyright (c) 2011 Cyril Hrubis <chrubis@suse.cz>                          */
/*									      */
/* 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    */
/*									      */
/******************************************************************************/
/******************************************************************************/
/* Description:	Test the LINUX memory manager. The program is aimed at        */
/*		stressing the memory manager by simultanious map/unmap/read   */
/*		by light weight processes, the test is scheduled to run for   */
/*		a mininum of 24 hours.					      */
/*									      */
/*		Create two light weight processes X and Y.                    */
/*		X - maps, writes  and unmap a file in a loop.	              */
/*		Y - read from this mapped region in a loop.		      */
/*	        read must be a success between map and unmap of the region.   */
/*									      */
/******************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.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 <setjmp.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include "test.h"

#define DISTANT_MMAP_SIZE (64*1024*1024)
#define OPT_MISSING(prog, opt) do { \
	fprintf(stderr, "%s: option -%c ", prog, opt); \
        fprintf(stderr, "requires an argument\n"); \
	usage(prog); \
} while (0)

static int verbose_print = 0;
static char *volatile map_address;
static jmp_buf jmpbuf;
static volatile char read_lock = 0;
static void *distant_area;

char *TCID = "mmap1";
int TST_TOTAL = 1;

static void sig_handler(int signal, siginfo_t * info, void *ut)
{
	switch (signal) {
	case SIGALRM:
		tst_resm(TPASS, "Test ended, success");
		_exit(TPASS);
	case SIGSEGV:
		longjmp(jmpbuf, 1);
		break;
	default:
		fprintf(stderr, "Unexpected signal - %d --- exiting\n", signal);
		_exit(TBROK);
	}
}

/*
 * Signal handler that is active, when file is mapped, eg. we do not expect
 * SIGSEGV to be delivered.
 */
static void sig_handler_mapped(int signal, siginfo_t * info, void *ut)
{
	switch (signal) {
	case SIGALRM:
		tst_resm(TPASS, "Test ended, success");
		_exit(TPASS);
	case SIGSEGV:
		tst_resm(TINFO, "[%lu] Unexpected page fault at %p",
			 pthread_self(), info->si_addr);
		_exit(TFAIL);
		break;
	default:
		fprintf(stderr, "Unexpected signal - %d --- exiting\n", signal);
		_exit(TBROK);
	}
}

int mkfile(int size)
{
	char template[] = "/tmp/ashfileXXXXXX";
	int fd, i;

	if ((fd = mkstemp(template)) == -1)
		tst_brkm(TBROK | TERRNO, NULL, "mkstemp() failed");

	unlink(template);

	for (i = 0; i < size; i++)
		if (write(fd, "a", 1) == -1)
			tst_brkm(TBROK | TERRNO, NULL, "write() failed");

	if (write(fd, "\0", 1) == -1)
		tst_brkm(TBROK | TERRNO, NULL, "write() failed");

	if (fsync(fd) == -1)
		tst_brkm(TBROK | TERRNO, NULL, "fsync() failed");

	return fd;
}

void *map_write_unmap(void *ptr)
{
	struct sigaction sa;
	long *args = ptr;
	long i;
	int j;

	tst_resm(TINFO, "[%lu] - map, change contents, unmap files %ld times",
		 pthread_self(), args[2]);

	if (verbose_print)
		tst_resm(TINFO, "map_write_unmap() arguments are: "
			 "fd - arg[0]: %ld; "
			 "size of file - arg[1]: %ld; "
			 "num of map/write/unmap - arg[2]: %ld",
			 args[0], args[1], args[2]);

	for (i = 0; i < args[2]; i++) {
		map_address = mmap(distant_area, (size_t) args[1],
			PROT_WRITE | PROT_READ, MAP_SHARED, (int)args[0], 0);

		if (map_address == (void *)-1) {
			perror("map_write_unmap(): mmap()");
			pthread_exit((void *)1);
		}

		while (read_lock)
			sched_yield();

		sigfillset(&sa.sa_mask);
		sigdelset(&sa.sa_mask, SIGSEGV);
		sa.sa_flags = SA_SIGINFO | SA_NODEFER;
		sa.sa_sigaction = sig_handler_mapped;

		if (sigaction(SIGSEGV, &sa, NULL)) {
			perror("map_write_unmap(): sigaction()");
			pthread_exit((void *)1);
		}

		if (verbose_print)
			tst_resm(TINFO, "map address = %p", map_address);

		for (j = 0; j < args[1]; j++) {
			map_address[j] = 'a';
			if (random() % 2)
				sched_yield();
		}

		if (verbose_print)
			tst_resm(TINFO,
				 "[%ld] times done: of total [%ld] iterations, "
				 "map_write_unmap():memset() content of memory = %s",
				 i, args[2], (char *)map_address);

		sigfillset(&sa.sa_mask);
		sigdelset(&sa.sa_mask, SIGSEGV);
		sa.sa_flags = SA_SIGINFO | SA_NODEFER;
		sa.sa_sigaction = sig_handler;

		if (sigaction(SIGSEGV, &sa, NULL)) {
			perror("map_write_unmap(): sigaction()");
			pthread_exit((void *)1);
		}

		if (munmap(map_address, (size_t) args[1]) == -1) {
			perror("map_write_unmap(): mmap()");
			pthread_exit((void *)1);
		}
	}

	pthread_exit(NULL);
}

void *read_mem(void *ptr)
{
	long i;
	long *args = ptr;
	int j;

	tst_resm(TINFO, "[%lu] - read contents of memory %p %ld times",
		 pthread_self(), map_address, args[2]);

	if (verbose_print)
		tst_resm(TINFO, "read_mem() arguments are: "
			 "number of reads to be performed - arg[2]: %ld; "
			 "read from address %p", args[2], map_address);

	for (i = 0; i < args[2]; i++) {

		if (verbose_print)
			tst_resm(TINFO, "read_mem() in while loop %ld times "
				 "to go %ld times", i, args[2]);

		if (setjmp(jmpbuf) == 1) {
			read_lock = 0;
			if (verbose_print)
				tst_resm(TINFO, "page fault occurred due to "
					 "a read after an unmap");
		} else {
			if (verbose_print) {
				read_lock = 1;
				tst_resm(TINFO,
					 "read_mem(): content of memory: %s",
					 (char *)map_address);
				read_lock = 0;
			}
			for (j = 0; j < args[1]; j++) {
				read_lock = 1;
				if (map_address[j] != 'a')
					pthread_exit((void *)-1);
				read_lock = 0;
				if (random() % 2)
					sched_yield();
			}
		}
	}

	pthread_exit(NULL);
}

static void usage(char *progname)
{
	fprintf(stderr, "Usage: %s -d -l -s -v -x\n"
		"\t -h help, usage message.\n"
		"\t -l number of mmap/write/unmap     default: 1000\n"
		"\t -s size of the file to be mmapped default: 1024 bytes\n"
		"\t -v print more info.               default: quiet\n"
		"\t -x test execution time            default: 24 Hrs\n",
		progname);

	exit(-1);
}

struct signal_info {
	int signum;
	char *signame;
};

static struct signal_info sig_info[] = {
	{SIGHUP, "SIGHUP"},
	{SIGINT, "SIGINT"},
	{SIGQUIT, "SIGQUIT"},
	{SIGABRT, "SIGABRT"},
	{SIGBUS, "SIGBUS"},
	{SIGSEGV, "SIGSEGV"},
	{SIGALRM, "SIGALRM"},
	{SIGUSR1, "SIGUSR1"},
	{SIGUSR2, "SIGUSR2"},
	{-1, "ENDSIG"}
};

int main(int argc, char **argv)
{
	int c, i;
	int file_size;
	int num_iter;
	double exec_time;
	int fd;
	void *status;
	pthread_t thid[2];
	long chld_args[3];
	extern char *optarg;
	struct sigaction sigptr;
	int ret;

	/* set up the default values */
	file_size = 1024;
	num_iter = 1000;
	exec_time = 24;

	while ((c = getopt(argc, argv, "hvl:s:x:")) != -1) {
		switch (c) {
		case 'h':
			usage(argv[0]);
			break;
		case 'l':
			if ((num_iter = atoi(optarg)) == 0)
				OPT_MISSING(argv[0], optopt);
			else if (num_iter < 0)
				printf
				    ("WARNING: bad argument. Using default %d\n",
				     (num_iter = 1000));
			break;
		case 's':
			if ((file_size = atoi(optarg)) == 0)
				OPT_MISSING(argv[0], optopt);
			else if (file_size < 0)
				printf
				    ("WARNING: bad argument. Using default %d\n",
				     (file_size = 1024));
			break;
		case 'v':
			verbose_print = 1;
			break;
		case 'x':
			exec_time = atof(optarg);
			if (exec_time == 0)
				OPT_MISSING(argv[0], optopt);
			else if (exec_time < 0)
				printf
				    ("WARNING: bad argument. Using default %.0f\n",
				     (exec_time = 24));
			break;
		default:
			usage(argv[0]);
			break;
		}
	}

	/* We don't want other mmap calls to map into same area as is
	 * used for test (mmap_address). The test expects read to return
	 * test pattern or read must fail with SIGSEGV. Find an area
	 * that we can use, which is unlikely to be chosen for other
	 * mmap calls. */
	distant_area = mmap(0, DISTANT_MMAP_SIZE, PROT_WRITE | PROT_READ,
		MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
	if (distant_area == (void *)-1)
		tst_brkm(TBROK | TERRNO, NULL, "distant_area: mmap()");
	if (munmap(distant_area, (size_t) DISTANT_MMAP_SIZE) == -1)
		tst_brkm(TBROK | TERRNO, NULL, "distant_area: munmap()");
	distant_area += DISTANT_MMAP_SIZE / 2;

	if (verbose_print)
		tst_resm(TINFO, "Input parameters are: File size:  %d; "
			 "Scheduled to run:  %lf hours; "
			 "Number of mmap/write/read:  %d",
			 file_size, exec_time, num_iter);

	alarm(exec_time * 3600);

	/* Do not mask SIGSEGV, as we are interested in handling it. */
	sigptr.sa_sigaction = sig_handler;
	sigfillset(&sigptr.sa_mask);
	sigdelset(&sigptr.sa_mask, SIGSEGV);
	sigptr.sa_flags = SA_SIGINFO | SA_NODEFER;

	for (i = 0; sig_info[i].signum != -1; i++) {
		if (sigaction(sig_info[i].signum, &sigptr, NULL) == -1) {
			perror("man(): sigaction()");
			fprintf(stderr,
				"could not set handler for %s, errno = %d\n",
				sig_info[i].signame, errno);
			exit(-1);
		}
	}

	for (;;) {
		if ((fd = mkfile(file_size)) == -1)
			tst_brkm(TBROK, NULL,
				 "main(): mkfile(): Failed to create temp file");

		if (verbose_print)
			tst_resm(TINFO, "Tmp file created");

		chld_args[0] = fd;
		chld_args[1] = file_size;
		chld_args[2] = num_iter;

		if ((ret =
		     pthread_create(&thid[0], NULL, map_write_unmap,
				    chld_args)))
			tst_brkm(TBROK, NULL, "main(): pthread_create(): %s",
				 strerror(ret));

		tst_resm(TINFO, "created writing thread[%lu]", thid[0]);

		if ((ret = pthread_create(&thid[1], NULL, read_mem, chld_args)))
			tst_brkm(TBROK, NULL, "main(): pthread_create(): %s",
				 strerror(ret));

		tst_resm(TINFO, "created reading thread[%lu]", thid[1]);

		for (i = 0; i < 2; i++) {
			if ((ret = pthread_join(thid[i], &status)))
				tst_brkm(TBROK, NULL,
					 "main(): pthread_join(): %s",
					 strerror(ret));

			if (status)
				tst_brkm(TFAIL, NULL,
					 "thread [%lu] - process exited "
					 "with %ld", thid[i], (long)status);
		}

		close(fd);
	}

	exit(0);
}
