/*
 * crash01.c - Test OS robustness by creating a string of random bytes and then jump to it.
 *
 * New version Copyright (C) 2001 Stephane Fillod <f4cfe@free.fr>
 *
 * Original idea (c) 1990-1994 by GEORGE J. CARRETTE, CONCORD, MASSACHUSETTS.
 *	from crashme version: "2.4 20-MAY-1994" <GJC@WORLD.STD.COM>
 */
/* TODO: trapme: forge syscall with random args, and run it!! --SF */

/*
 *             COPYRIGHT (c) 1990-1994 BY        *
 *  GEORGE J. CARRETTE, CONCORD, MASSACHUSETTS.  *
 *             ALL RIGHTS RESERVED               *

Permission to use, copy, modify, distribute and sell this software
and its documentation for any purpose and without fee is hereby
granted, provided that the above copyright notice appear in all copies
and that both that copyright notice and this permission notice appear
in supporting documentation, and that the name of the author
not be used in advertising or publicity pertaining to distribution
of the software without specific, written prior permission.

THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
HE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

*/

/*

A signal handler is set up so that in most cases the machine exception
generated by the illegal instructions, bad operands, etc in the procedure
made up of random data are caught; and another round of randomness may
be tried. Eventually a random instruction may corrupt the program or
the machine state in such a way that the program must halt. This is
a test of the robustness of the hardware/software for instruction
fault handling.

Note: Running this program just a few times, using total CPU time of
less than a few seconds SHOULD NOT GIVE YOU ANY CONFIDENCE in system
robustness. Having it run for hours, with tens of thousands of cases
would be a different thing. It would also make sense to run this
stress test at the same time you run other tests, like a multi-user
benchmark.

*/

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "test.h"

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

static int x_opt = 0;
static int v_opt = 0;
static char *v_copt;
static int s_opt = 0;
static char *s_copt;
static int b_opt = 0;
static char *b_copt;
static int n_opt = 0;
static char *n_copt;

int verbose_level = 2;

/* Also, it may spend more time trapping and less time computing random bytes
 * by using the smallest incptr (while not executing already tested bits).
 */
int incptr = 80;

int nseed;
int ntries = 100;

/* compute block of nbytes at a time */
const int nbytes = 2000;

/* max time allowed per try, in seconds */
#define MAX_TRY_TIME 5

/* in % */
#define BLOCK_TRIGGER 80

void cleanup(void)
{

	tst_rmdir();

}

void setup(void)
{
	/*
	 * setup a default signal hander and a
	 * temporary working directory.
	 */
	tst_sig(FORK, DEF_HANDLER, cleanup);

	tst_tmpdir();

	TEST_PAUSE;
}

void help(void)
{
	printf("  -x      dry run, hexdump random code instead\n");
	printf("  -v x    verbose level\n");
	printf("  -s x    random seed\n");
	printf("  -n x    ntries\n");
	printf("  -b x    inc\n");
}

/*
 * crashme [+]<nbytes>[.inc] <srand> <ntries> [nsub] [verbose]"
 *
 * crashme <-b [+]<nbytes>[.inc]> <-s srand> <-n ntries> [-v verbose]"
 *  crashme +2000.80 666 100 1:10:30 2
 *	nsub  -> -c ?
 */
option_t options[] = {
	{"v:", &v_opt, &v_copt},
	{"s:", &s_opt, &s_copt},
	{"n:", &n_opt, &n_copt},
	{"b:", &b_opt, &b_copt},
	{"x", &x_opt, NULL},

	{NULL, NULL, NULL}
};

int malloc_flag = 1;		/* to be phased out */

void badboy_fork();
void badboy_loop();
void summarize_status();
void record_status(unsigned int n);

int main(int argc, char *argv[])
{
	int lc;

	tst_parse_opts(argc, argv, options, help);

	if (v_opt)
		verbose_level = atoi(v_copt);

	if (n_opt)
		ntries = atoi(n_copt);

	if (s_opt)
		nseed = atoi(s_copt);
	else
		nseed = time(NULL);

	if (b_opt) {
		int inc;

		inc = atoi(b_copt);
		if (inc <= nbytes / 2)
			incptr = inc;
		else
			tst_brkm(TBROK, cleanup,
				 "Invalid arg for -b (max: %u): %s", nbytes / 2,
				 b_copt);
	}

	setup();

	for (lc = 0; TEST_LOOPING(lc); lc++) {
		tst_count = 0;

		tst_resm(TINFO, "crashme %s%d.%d %d %d",
			 (malloc_flag == 0) ? "" : "+", nbytes, incptr, nseed,
			 ntries);

		srand(nseed);
		badboy_fork();

		/* still there? */
		tst_resm(TPASS, "we're still here, OS seems to be robust");

		nseed++;
	}
	summarize_status();
	cleanup();
	tst_exit();
}

/* ************************* */
int badboy_pid;

void my_signal(int sig, void (*func) ());

void monitor_fcn(int sig)
{
	int status;

	if (verbose_level >= 3)
		printf("time limit reached on pid. using kill.\n");

	status = kill(badboy_pid, SIGKILL);
	if (status < 0) {
		if (verbose_level >= 3)
			printf("failed to kill process\n");
	}
}

void badboy_fork(void)
{
	int status, pid;

	status = fork();
	badboy_pid = status;
	if (status == 0) {	/* badboy */
#ifdef DEBUG_LATE_BADBOY
		sleep(ntries * MAX_TRY_TIME + 10);
#else
		badboy_loop();
#endif
		exit(0);	/* good goy, he survived! */
	} else if (status < 0)
		perror("fork");
	else {			/* parent watching over badboy */

		if (verbose_level > 3)
			printf("badboy pid = %d\n", badboy_pid);

/* don't trust the child to return at night */
		my_signal(SIGALRM, monitor_fcn);
		alarm(ntries * MAX_TRY_TIME);

		pid = wait(&status);
		if (pid <= 0) {
			perror("wait");
		} else {
			if (verbose_level > 3)
				printf("pid %d exited with status %d\n", pid,
				       status);
			record_status(status);
		}
	}			/* parent */
	alarm(0);
}

/* *************** status recording ************************* */

#define STATUS_MAX 256
static int status_table[STATUS_MAX];

void record_status(unsigned int n)
{
	if (n >= STATUS_MAX)
		return;

	status_table[n]++;
}

/* may not work with -c option */
void summarize_status(void)
{
	int i;

	if (verbose_level < 2)
		return;

	printf("exit status ... number of cases\n");
	for (i = 0; i < STATUS_MAX; i++) {
		if (status_table[i])
			printf("%11d ... %5d\n", i, status_table[i]);
	}
}

/* ************* badboy ******************************************* */

jmp_buf again_buff;

typedef void (*BADBOY) ();

BADBOY badboy;
char *the_data;

int offset = 0;
int next_offset = 0;

char *bad_malloc(int n);
void my_signal(int sig, void (*func) ());
void again_handler(int sig);
void compute_block_badboy(int n);
void compute_badboy();
BADBOY castaway(char *dat);
void try_one_crash();
void set_up_signals();

/* badboy "entry" point */
void badboy_loop(void)
{
	int i;

	if (malloc_flag == 0) {
		the_data = bad_malloc((nbytes < 0) ? -nbytes : nbytes);
		badboy = castaway(the_data);
		printf("Badboy at %p\n", badboy);
	}

	for (i = 0; i < ntries; ++i) {
		compute_badboy();
		/* level 5 */

		if (!x_opt && verbose_level >= 5) {
			if (offset)
				printf("try %d, offset %d\n", i, offset);
			else if (malloc_flag == 1)
				printf("try %d, Badboy at %p\n", i, badboy);
			else
				printf("try %d\n", i);
		}

		if (setjmp(again_buff) == 3) {
			if (verbose_level >= 5)
				printf("Barfed\n");
		} else {
			set_up_signals();
			alarm(MAX_TRY_TIME);
			try_one_crash();
			if (!x_opt && verbose_level >= 5)
				printf("didn't barf!\n");
		}
	}
}

char *bad_malloc(int n)
{
	char *data;
	data = malloc(n);
#ifdef pyr
	if (mprotect(((int)data / PAGSIZ) * PAGSIZ, (n / PAGSIZ + 1) * PAGSIZ,
		     PROT_READ | PROT_WRITE | PROT_EXEC))
		perror("mprotect");
#endif
	return (data);
}

void again_handler(int sig)
{
	char *ss;

	switch (sig) {
	case SIGILL:
		ss = " illegal instruction";
		break;
#ifdef SIGTRAP
	case SIGTRAP:
		ss = " trace trap";
		break;
#endif
	case SIGFPE:
		ss = " arithmetic exception";
		break;
#ifdef SIGBUS
	case SIGBUS:
		ss = " bus error";
		break;
#endif
	case SIGSEGV:
		ss = " segmentation violation";
		break;
#ifdef SIGIOT
	case SIGIOT:
		ss = " IOT instruction";
		break;
#endif
#ifdef SIGEMT
	case SIGEMT:
		ss = " EMT instruction";
		break;
#endif
#ifdef SIGALRM
	case SIGALRM:
		ss = " alarm clock";
		break;
#endif
	case SIGINT:
		ss = " interrupt";
		break;
	default:
		ss = "";
	}
	if (verbose_level >= 5)
		printf("Got signal %d%s\n", sig, ss);

	longjmp(again_buff, 3);
}

void my_signal(int sig, void (*func) ())
{
	struct sigaction act;

	act.sa_handler = func;
	memset(&act.sa_mask, 0x00, sizeof(sigset_t));
	act.sa_flags = SA_NOMASK | SA_RESTART;
	sigaction(sig, &act, 0);
}

void set_up_signals(void)
{
	my_signal(SIGILL, again_handler);
#ifdef SIGTRAP
	my_signal(SIGTRAP, again_handler);
#endif
	my_signal(SIGFPE, again_handler);
#ifdef SIGBUS
	my_signal(SIGBUS, again_handler);
#endif
	my_signal(SIGSEGV, again_handler);
#ifdef SIGIOT
	my_signal(SIGIOT, again_handler);
#endif
#ifdef SIGEMT
	my_signal(SIGEMT, again_handler);
#endif
#ifdef SIGALRM
	my_signal(SIGALRM, again_handler);
#endif
	my_signal(SIGINT, again_handler);
}

void compute_block_badboy(int n)
{
	int j;

	if (malloc_flag == 1) {
		free(the_data);
		the_data = bad_malloc(n);
	}

	for (j = 0; j < n; ++j) {
#ifdef WANT_SLOW_RAND
		the_data[j] = 0xFF & (int)(256.0 * rand() / (RAND_MAX + 1.0));
#else
		the_data[j] = (rand() >> 7) & 0xFF;
#endif
#ifdef __powerpc__
		__asm__
		    __volatile__("dcbst 0,%0 ; icbi 0,%0 ; isync"::"r"
				 (&the_data[j]));
#endif

	}

	/* was (nbytes < 0) */
	if (x_opt) {
		if (verbose_level >= 1)
			printf("Dump of %d bytes of data\n", n);
		for (j = 0; j < n; ++j) {
			if ((j % 16) == 0)
				printf("\n%04d: ", j);

			printf("%02x ", the_data[j]);
		}
		putc('\n', stdout);
	}
}

BADBOY castaway(char *dat)
{
	return ((BADBOY) dat);
}

void compute_badboy(void)
{
	if (incptr == 0) {
		compute_block_badboy(nbytes);
		badboy = castaway(the_data);
	}
	/* trigger block generation at xx % of the current block */
	else if ((next_offset == 0)
		 || (next_offset > ((nbytes * BLOCK_TRIGGER) / 100))) {
		compute_block_badboy(nbytes);
		offset = 0;
		next_offset = offset + incptr;
		badboy = castaway(the_data);
	} else {
		offset = next_offset;
		next_offset = offset + incptr;
		badboy = castaway(&the_data[offset]);
	}
}

void try_one_crash(void)
{
	/* was (nbytes < 0) */
	if (!x_opt)
		(*badboy) ();
	else if (nbytes == 0)
		while (1) ;
}
