/*
 * Copyright (C) 2003  Sam Horrocks
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include "perperl.h"

/*
 * Signal handling routines
 */

static volatile int got_sig[PERPERL_MAXSIG];
static sigset_t blockall_save;
static int all_blocked;

void perperl_sig_blockall(void) {
    sigset_t full_set;

    sigfillset(&full_set);
    sigprocmask(SIG_BLOCK, &full_set, &blockall_save);
    all_blocked = 1;
}

void perperl_sig_blockall_undo(void) {
    sigprocmask(SIG_SETMASK, &blockall_save, NULL);
    all_blocked = 0;
}

static int sig_find(const volatile int sig_rcvd[PERPERL_MAXSIG], int sig) {
    register int i;

    for (i = 0; i < PERPERL_MAXSIG && sig_rcvd[i]; ++i) {
	if (sig_rcvd[i] == sig)
	    return -1;
    }
    return i;
}

static void sig_handler(int sig) {
    int i;

    if ((i = sig_find(got_sig, sig)) >= 0 && i < PERPERL_MAXSIG) {
	got_sig[i++] = sig;
	if (i < PERPERL_MAXSIG)
	    got_sig[i] = 0;
    }
}

static void sig_wait_basic(const SigList *sl) {
    for (got_sig[0] = 0; !got_sig[0];)
	sigsuspend(&sl->unblock_sigs);
}

void perperl_sig_wait(SigList *sl) {
    sig_wait_basic(sl);
    perperl_util_time_invalidate();
    perperl_memcpy(sl->sig_rcvd, got_sig, sizeof(got_sig));
}

int perperl_sig_got(const SigList *sl, int sig) {
    return sig_find(sl->sig_rcvd, sig) == -1;
}

static void sig_init2(SigList *sl, int how) {
    int i;

    /* Set up handlers and save old action setting */
    {
	struct sigaction sigact;
	sigact.sa_handler = &sig_handler;
	sigact.sa_flags = 0;
	sigemptyset(&sigact.sa_mask);
	for (i = 0; i < sl->numsigs; ++i)
	    sigaction(sl->signum[i],  &sigact, &(sl->sigact_save[i]));
    }

    /* Block or unblock our signals.  Save original mask */
    if (all_blocked) {
	sl->sigset_save = blockall_save;
	for (i = 0; i < sl->numsigs; ++i) {
	    if (how == SIG_BLOCK)
		sigaddset(&blockall_save, sl->signum[i]);
	    else
		sigdelset(&blockall_save, sl->signum[i]);
	}
    } else {
	sigset_t block_sigs;
	sigemptyset(&block_sigs);
	for (i = 0; i < sl->numsigs; ++i)
	    sigaddset(&block_sigs, sl->signum[i]);
	sigprocmask(how, &block_sigs, &sl->sigset_save);
    }

    /* Make an unblock mask for our signals */
    sl->unblock_sigs = sl->sigset_save;
    for (i = 0; i < sl->numsigs; ++i)
	sigdelset(&sl->unblock_sigs, sl->signum[i]);
}

void perperl_sig_init(SigList *sl, const int *sigs, int numsigs, int how) {

    /* Copy in args */
    if (numsigs > PERPERL_MAXSIG)
	DIE_QUIET("Too many sigs passed to sig_init");
    perperl_memcpy(sl->signum, sigs, numsigs * sizeof(int));
    sl->numsigs = numsigs;

    /* Finish init */
    sig_init2(sl, how);
}

void perperl_sig_free(const SigList *sl) {
    int i;
    
    /* Get rid of any pending signals.  On Sun/apache-2 we don't get pending
     * signals as soon as they are unblocked - instead they get delivered
     * after the action is restored, which is not what we want.
     */
    do {
	sigset_t set;
	
	/* Bug in Mac OS X 10.1 and earlier - sigpending is essentially a
	 * no-op, so we get garbage, and get stuck in sigsuspend.
	 * Workaround by clearing out the set initially so we get no pending
	 * signals back.
	 */
	sigemptyset(&set);

	if (sigpending(&set) == -1)
	    break;
	for (i = 0; i < sl->numsigs; ++i) {
	    if (sigismember(&set, sl->signum[i])) {
		sig_wait_basic(sl);
		break;
	    }
	}
    } while (i < sl->numsigs);

    /* Unblock sigs */
    if (all_blocked)
	blockall_save = sl->sigset_save;
    else
	sigprocmask(SIG_SETMASK, &sl->sigset_save, NULL);

    /* Install old handlers */
    for (i = 0; i < sl->numsigs; ++i)
	sigaction(sl->signum[i], &(sl->sigact_save[i]), NULL);
}