#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/errno.h>
#include <unistd.h>
#include <setjmp.h>
#include <ucontext.h>

/*
 * mascheck at faw.uni-ulm.de, in public domain
 *
 * This program can only be killed with SIGKILL
 *
 * Who is throwing signals at me?
 *
 * If called with arbitrary arguments, then begin with a selftest (SIGUSR2),
 *   this is slightly different from being signaled through the tty, f.i.
 * Otherwise just wait.
 *
 * See also:
 *     sigaction(2), /usr/include/sys/signal.h
 *
 * currently not reliable, using libc functions in handler
 *
 * There was another bug left, but i can't recall it now
 */

extern int errno;	/* not used */
sigjmp_buf env;		/* sigset/longjmp() */

/* "_p" means pointer */

/* when not using libc functions, but saving the interesting values */
/* siginfo_t  *siginfo_p; /* this is what we want to know */

/* ucontext_t *context_p; /* not used */

/*
 * normal signal handler, not used
 */
static void signal_handler() {
    write(1, "in signal_handler()\n", 20);
}


/*
 * extra 'signal catching function', if SA_SIGINFO is used
 *
 * libc functions shall not be used in signal handler,
 * store interesting variables and inspect them after returning from
 * siglongjmp(). "siginfo_p->si_signo = info_p->si_signo;"
 *
 * Hack, unreliable: i do use printf()
 *
 */

static void signal_catcher(int sig, siginfo_t* info_p, void* c_p) {

    char msg[] = "\nin signal_catcher()\n";
    write(1, msg, strlen(msg));

    if (info_p != NULL) {

	if (info_p->si_code == SI_EMPTY) {
	    /* printf("    signal number:       %d\n", info_p->si_signo); */
	    printf("    SI_EMPTY, no more information available.\n");
	} else if (info_p->si_code == SI_UNDEFINED) {
	    /* e.g. when sending signals via tty */
	    printf("    si_code: SI_UNDEFINED, partial information available.\n");
	} else {
	    printf("    neither SI_EMPTY nor SI_UNDEFINED, full information available.\n");
	}

	switch(info_p->si_code) {
	    case SI_USER:
		printf("    ...signal generated via: kill() or alike\n");
		break;
	    case SI_QUEUE:
		printf("    ...signal generated via: sigqueue()\n");
		break;
	    case SI_TIMER:
		printf("    ...signal from: timer expiration\n");
		break;
	    case SI_ASYNCIO:
		printf("    ...signal from: asynchronous I/O completion\n");
		break;
	    case SI_MESGQ:
		printf("    ...signal from: message arrival\n");
		break;
	    case SI_UNDEFINED:
		break;
	    default:
		printf("    ...see siginfo(2) about 'si_code' for more information\n");
		break;
	} /* switch */

	printf("    signal number:       %d\n", info_p->si_signo);
	printf("    sender uid:          %d\n", info_p->si_uid);
	printf("    sender pid:          %d\n", info_p->si_pid);
	printf("    error (errno) was:   %d\n", info_p->si_errno);
	if (info_p->si_code != SI_UNDEFINED)
	    printf("    si_code:             %d\n", info_p->si_code);

    } else /* info_p == NULL */ {
	printf("    signal from kernel\n");
	printf("    signal number:       %d\n", sig);
    }

    siglongjmp(env, 1); /* for a correct handler */
}



int main(int argc, char** argv) {
    struct sigaction sig_act;		/* ourselves */
    struct sigaction sig_act_chld;	/* a child, in case, protect it */
    int i;
    int maxsig;
    pid_t pid;

    printf("\ncurrent PID: [ %d ]\n\n", getpid());

    /* when not using libc functions in handler, but copying the values */
    /* siginfo_p = (siginfo_t*)malloc(sizeof(siginfo_p)); */

    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_handler = signal_handler; /* not used with SA_SIGINFO */
    sig_act.sa_sigaction = signal_catcher; /* this is ours */
    sig_act.sa_flags = 0;
    sig_act.sa_flags |= SA_RESTART;
    sig_act.sa_flags |= SA_SIGINFO;	/* this is what we want */
    sig_act.sa_flags |= SA_NOCLDWAIT;	/* don't wait for children */

    sigemptyset(&sig_act_chld.sa_mask);
    sig_act_chld.sa_handler = signal_handler;
    sig_act_chld.sa_flags = 0;
    sig_act_chld.sa_flags |= SA_RESTART;


    /*
     * certainly: catch all possible signals
     * ok, except SIGCHLD, avoid the dumb fork bomb when a possible child
     * terminates and we made something wrong with catching (no wait(2)
     * here yet).
     *
     * catching SIGCONT doesn't really make sense, but just for information.
     */

    maxsig = SIGMAX; /* AIX 4.3.2 */
    printf("maxsig = %d\n", maxsig);

    for (i=1; i<=maxsig; i++) {
	if (i != SIGKILL && i != SIGSTOP && /* i != SIGCONT && */ i != SIGCHLD) {
	    if (sigaction(i, &sig_act, NULL) < 0) {
		fprintf(stderr, "sigaction failed for signal %d\n", i);
		exit(1);
	    }
	}
    }

    /* sigaction/SI_NOCLWAIT  vs.  signal/SIG_IGN */
    if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
	fprintf(stderr, "sigaction failed for signal SIGCHLD\n");
	exit(1);
    }


    while (1) {
	/* direct call: == '0' , nach siglongjp() !=0 */
	if (sigsetjmp(env, 2) == 0) {
	    if (argc >= 2) {
		/*
		 * start with a selftest
		 */
		kill(getpid(), SIGUSR2); /* */
	    }

        } else /* sigsetjmp() != 0 */ {
	    printf("handler returned\n"); fflush(stdout); /* */

	/* 
	 * e.g. fork/exec a ps(1) to find out more about the current processes
	 *  - or comment out fork() and switch().
	 */

        /*
         * child: we can protect ourself against signals,
	 * but not across an exec(2)
         */
	    pid = fork();
	    switch (pid) {
		case -1:
		    fprintf(stderr, "fork failed!\n");
		    break;

		case 0:

		    sleep(1); /* care about forkbombs, comment out in case */

		    execl("/bin/ps", "ps", "-fe",
			"-ouser,pid,ppid,gid,stime,tty,time,pcpu,args",
			(char*)NULL);

		    /* was alternative test */ sleep(60); _exit(0);

		    /* was alternative test */ execl("/bin/sleep", "sleep", "60", (char*)NULL);

		    perror("execl() failed!\n");
		    break;

		default:
		    printf("parent [ %d ]: forked pid %d\n", getpid(), pid);
		    break;
	    }

        }
	pause();
    }
}
