/*
 * Stress test combinations of:
 *
 *	- semaphore allocation and destruction
 *	- SEM_UNDO processing at task exit time
 *	- semaphore UPs and DOWNs mixed in with the above
 *	- integer and floating point math
 *
 * This program starts up a bunch of child tasks which hammer away at
 * various aspects of the semaphore facility and the system in general.
 * The only output should be "task 7 exited with status 0" which should
 * appear immediately after the program starts executing.
 *
 * The program runs forever (i.e. kill it with a ^C when you're convinced
 * that all is well).  I usually start up two instances of this program
 * in separate windows and let them run for a few hours.
 */

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <math.h>

char *
signal_name(int sig)
{
    static char buf[100];

    switch ( sig ) {
    case SIGHUP: return("SIGHUP");
    case SIGINT: return("SIGINT");
    case SIGQUIT: return("SIGQUIT");
    case SIGILL: return("SIGILL");
    case SIGABRT: return("SIGABRT");
    case SIGEMT: return("SIGEMT");
    case SIGFPE: return("SIGFPE");
    case SIGKILL: return("SIGKILL");
    case SIGBUS: return("SIGBUS");
    case SIGSEGV: return("SIGSEGV");
    case SIGSYS: return("SIGSYS");
    case SIGPIPE: return("SIGPIPE");
    case SIGALRM: return("SIGALRM");
    case SIGTERM: return("SIGTERM");
    case SIGURG: return("SIGURG");
    case SIGSTOP: return("SIGSTOP");
    case SIGTSTP: return("SIGTSTP");
    case SIGCONT: return("SIGCONT");
    case SIGTTIN: return("SIGTTIN");
    case SIGTTOU: return("SIGTTOU");
    case SIGIO: return("SIGIO");
    case SIGXCPU: return("SIGXCPU");
    case SIGXFSZ: return("SIGXFSZ");
    case SIGVTALRM: return("SIGVTALRM");
    case SIGPROF: return("SIGPROF");
    case SIGWINCH: return("SIGWINCH");
    case SIGINFO: return("SIGINFO");
    case SIGUSR1: return("SIGUSR1");
    case SIGUSR2: return("SIGUSR2");
    default:
	sprintf(buf,"signal %d???",sig);
	return(&buf[0]);
    }
}

#define SEMAPHORE_KEY	0x16521434
int semids[3];
long start_time;
char task_id[100];

/*
 * Sleep for a few milli-seconds.
 * Used to force a task switch.
 */

void
nap(int ms)
{
    struct timeval tv;

    tv.tv_sec = ms / 1000;
    tv.tv_usec = (ms % 1000) * 1000;
    if ( select( 0, NULL, NULL, NULL, &tv ) < 0 ) {
	tperror("select failed");
    }
}

/*
 * Maybe force a task switch
 */

void
ms()
{
    if ( random() & 1 ) {
	nap(1);
    }
}

int test1(int ix)
{
    int semid;
    while (1) {
	union semun junk;
	semid = semget(SEMAPHORE_KEY, 10, 0600);
	if ( semid == -1 && errno == ENOENT ) {
	    ms();
	    semid = semget(SEMAPHORE_KEY, 10, 0600 | IPC_CREAT | IPC_EXCL);
	    if ( semid == -1 && errno != EEXIST ) {
		tperror("semget 2");
	    }
	    ms();
	}
	ms();
	if ( random() & 1 ) {
	    if ( semctl( semid, 0, IPC_RMID, junk ) < 0 ) {
		if ( errno != EINVAL ) {
		    tperror("semctl 1");
		}
	    }
	}
    }
}

int test2(int ix)
{
    int semid;
    static struct sembuf ops[] = { 0, 1, SEM_UNDO };
    int i, pid, status;
    long start = time(0);
    int task_count = 0;

    if ( ix & 1 ) { ops[0].sem_op = -2; }
    while (1) {
	switch (pid = fork()) {
	case 0:
	    for ( i = 0; i < 100; i += 1 ) {
		if ( (random() & 7) == 7 ) nap(1);	/* Low probability task switch */
		semid = semget(SEMAPHORE_KEY+1, 10, 0600 | IPC_CREAT);
		if ( semop(semid, &ops[0], 1) < 0 ) {
		    if ( errno != EINVAL ) {
			tperror("semop");
		    }
		} else {
		    /* fprintf(stderr,"task %s: semop worked\n",task_id); */
		}
	    }
	    exit(0);
	case -1:
	    tperror("can't fork");
	    exit(1);
	default:
	    /* printf("%s:  started pid %d\n",task_id,pid); */
#if DO_TIMINGS
	    if ( (++task_count) % 50 == 0 ) {
		printf("%d seconds elapsed (%d tasks)\n",time(0) - start,task_count);
	    }
#endif
	    wait(&status);
	}
    }
    return(1);
}

/*
 * Blow away the test2 semaphore every 0.1 seconds.
 */

int test3(int ix)
{
    int semid;
    while (1) {
	union semun junk;
	nap(100);
	semid = semget(SEMAPHORE_KEY+1, 10, 0600 | IPC_CREAT);
	if ( semctl( semid, 0, IPC_RMID, junk ) < 0 ) {
	    if ( errno != EINVAL ) {
		tperror("semctl 2");
	    }
	}
    }
}

int test4(int ix)
{
    return(0);
}

/*  1000 digits of PI                                      */
/*  `Spigot' algorithm originally due to Stanly Rabinowitz */

void
compute_pi()
{
  int d = 4, r = 10000, n = 251, m = 3.322*n*d;
  int i, j, k, q;
  int a[3340];
  int csum;

  memset(a,0,sizeof(a));

  for (i = 0; i <= m; i++) a[i] = 2;
  a[m] = 4;

  for (i = 1; i <= n; i++) {
    q = 0;
    for (k = m; k > 0; k--) {
      a[k] = a[k]*r+q;
      q = a[k]/(2*k+1);
      a[k] -= (2*k+1)*q;
      q *= k;
    }
    a[0] = a[0]*r+q;
    q = a[0]/r;
    a[0] -= q*r;
    /* printf("%04d%s",q, i & 7 ? "  " : "\n"); */
  }

  csum = 0;
  for ( i = 0; i < 3340; i += 1 ) {
      csum += a[i];
  }

  if ( csum != 5641684 ) {
      printf("compute_pi failed (got csum=%d, expected csum=%d)\n",csum,5641684);
      exit(1);
  }

}

/*
 * Soak up most left over cycles with integer and floating point calculations.
 *
 * I've done a few timings.  It doesn't look like the existence of this
 * process has any measurable impact on the rate that the rest of the test
 * progresses (which is what is intended by the nice calls below).
 */

int test5(int ix)
{
    double sum = 0;
    nice(1);		/* Some nice's fail if incr is too big so try a little */
    nice(20);		/* and then try a lot (at least the first should work */
    			/* so this task should be at least a bit nicer than */
    			/* the rest regardless of the original 'nice' value */
    			/* of this process group). */
    while (1) {
	double x;
	compute_pi();
	for ( x = 0.0; x < 100.0; x += 1.0 ) {
	    sum += ( sin(x) * log(x) ) / (1 + sqrt(x));
	}
    }
}

int (*task_vector[])(int) = { test1, test1, test1, test1, test2, test2, test3, test4, test5, NULL };
int task_status[100];	/* Must be larger than task_vector */
int task_pids[100];	/* Must be larger than task_vector */

void
stop_tests()
{
    int i;

    for ( i = 0; task_pids[i] > 0; i += 1 ) {
	kill(task_pids[i],SIGTERM);
    }
    sleep(2);
    for ( i = 0; task_pids[i] > 0; i += 1 ) {
	kill(task_pids[i],SIGKILL);
    }
}

void
alarm_catcher(int sig)
{
}

main(int argc,char *argv[])
{
    int i;
    int children_count, pid;

    start_time = time(0);

    for ( i = 0; task_vector[i] != NULL; i += 1 ) {
	switch ( task_pids[i] = fork() ) {
	case 0:
	    sprintf(task_id,"%d",i);
	    sprintf(argv[0],"task %d",i);
	    close(2);		/* Make stderr */
	    dup(1);		/* identical to stdout */
	    exit( (*task_vector[i])(i) );
	case -1:
	    perror("fork failed");
	    stop_tests();
	    exit(1);
	default:
	    /* Parent - just continue ... */
	    children_count += 1;
	}
    }

    signal(SIGINT,SIG_IGN);

    while ( children_count > 0 ) {
	int status;
	signal(SIGALRM,alarm_catcher);
	alarm(1);
	if ( (pid = wait(&status)) == -1 ) {
	    if ( errno == EINTR ) {
		fprintf(stderr,"got an EINTR from wait\n"); /* nothing to worry about */
	    } else {
		perror("wait failed");
		stop_tests();
		exit(1);
	    }
	} else {
	    for ( i = 0; task_pids[i] != 0; i += 1 ) {
		if ( task_pids[i] == pid ) {
		    task_status[i] = status;
		    if ( WIFEXITED(status) ) {
			fprintf(stderr,"task %d exited with status %d\n",i,WEXITSTATUS(status));
		    } else if ( WIFSIGNALED(status) ) {
			fprintf(stderr,"task %d terminated by %s%s\n",i,signal_name(WTERMSIG(status)),
			WCOREDUMP(status) ? " (core dumped)" : "");
		    } else {
			fprintf(stderr,"task %d stopped by %s\n",i,signal_name(WSTOPSIG(status)));
		    }
		    children_count -= 1;
		}
	    }
	}
    }

    stop_tests();
    exit(0);
}
