/* Signal handling routines.
 *
 * IRC Services is copyright (c) 1996-2009 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#include "services.h"
#include <setjmp.h>

/*************************************************************************/

/* If we get a signal, use this to jump out of the main loop. */
static sigjmp_buf *panic_ptr = NULL;

/*************************************************************************/
/*************************************************************************/

/* Various signal handlers. */

/*************************************************************************/

/* SIGHUP = rehash configuration files */
static void sighup_handler(int sig_unused)
{
#ifdef CLEAN_COMPILE
    sig_unused = sig_unused;
#endif
    log("Received SIGHUP, rehashing.");
    wallops(NULL, "Rehashing configuration files (received SIGHUP)");
    reconfigure();
    signal(SIGHUP, sighup_handler);
}

/*************************************************************************/

/* SIGTERM = save databases and shut down */
static void sigterm_handler(int sig_unused)
{
#ifdef CLEAN_COMPILE
    sig_unused = sig_unused;
#endif
    save_data = 1;
    delayed_quit = 1;
    signal(SIGTERM, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    log("Received SIGTERM, exiting.");
    strscpy(quitmsg, "Shutting down on SIGTERM", sizeof(quitmsg));
    siglongjmp(*panic_ptr, 1);
}

/*************************************************************************/

/* SIGUSR2 = close and reopen log file */
static void sigusr2_handler(int sig_unused)
{
#ifdef CLEAN_COMPILE
    sig_unused = sig_unused;
#endif
    log("Received SIGUSR2, cycling log file.");
    if (log_is_open()) {
	close_log();
	open_log();
    }
    signal(SIGUSR2, sigusr2_handler);
}

/*************************************************************************/

/* If we get a weird signal, come here. */
static void weirdsig_handler(int signum)
{
    static int dying = 0;  /* Flag to avoid infinite recursion */

    if (dying++) {
	/* Double signal, give up.  Set `servsock' to NULL to avoid a
	 * message going out that way, just in case the socket code is
	 * confused/broken */
	servsock = NULL;
	if (signum == SIGUSR2) {
	    fatal("Out of memory while shutting down");
	} else {
#if HAVE_STRSIGNAL
	    fatal("Caught signal %d (%s) while shutting down", signum,
		  strsignal(signum));
#else
	    fatal("Caught signal %d while shutting down", signum);
#endif
	}
    }

    /* Avoid spurious keyboard signals killing us while shutting down */
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);

    /* If we died processing a message, let people know about it */
    if (signum != SIGINT && signum != SIGQUIT) {
	if (*inbuf) {
	    log("PANIC! signal %d, buffer = %s", signum, inbuf);
	    /* Cut off if this would make IRC command >510 characters. */
	    if (strlen(inbuf) > 448) {
		inbuf[446] = '>';
		inbuf[447] = '>';
		inbuf[448] = 0;
	    }
	    wallops(NULL, "PANIC! buffer = %s\r\n", inbuf);
	} else {
	    log("PANIC! signal %d (no buffer)", signum);
	    wallops(NULL, "PANIC! signal %d (no buffer)", signum);
	}
    }

    /* Pick an appropriate quit message */
    if (signum == SIGUSR1) {
	strscpy(quitmsg, "Out of memory!", sizeof(quitmsg));
	quitting = 1;
    } else {
#if HAVE_STRSIGNAL
	snprintf(quitmsg, sizeof(quitmsg),
		 "Services terminating: %s", strsignal(signum));
#else
	snprintf(quitmsg, sizeof(quitmsg),
		 "Services terminating on signal %d", signum);
#endif
	quitting = 1;
    }

    /* Actually quit */
    if (panic_ptr) {
	siglongjmp(*panic_ptr, 1);
    } else {
	log("%s", quitmsg);
	if (isatty(2))
	    fprintf(stderr, "%s\n", quitmsg);
	exit(1);
    }
}

/*************************************************************************/
/*************************************************************************/

/* Set up signal handlers.  Catch certain signals to let us do things or
 * panic as necessary, and ignore all others.
 */

void init_signals(void)
{
    int i;

    /* Start out with special signals disabled */
    disable_signals();

    /* Set all signals to "ignore" */
    for (i = 1; i <= NSIG; i++) {
#ifdef DUMPCORE
	if (i != SIGSEGV)
#endif
	    if (i != SIGPROF && i != SIGCHLD)
		signal(i, SIG_IGN);
    }

    /* Specify particular signals we want to catch */
    signal(SIGINT, weirdsig_handler);
    signal(SIGTERM, weirdsig_handler);
    signal(SIGQUIT, weirdsig_handler);
#ifndef DUMPCORE
    signal(SIGSEGV, weirdsig_handler);
#endif
    signal(SIGBUS, weirdsig_handler);
    signal(SIGQUIT, weirdsig_handler);
    signal(SIGHUP, weirdsig_handler);
    signal(SIGILL, weirdsig_handler);
    signal(SIGTRAP, weirdsig_handler);
    signal(SIGFPE, weirdsig_handler);
#ifdef SIGIOT
    signal(SIGIOT, weirdsig_handler);
#endif

    /* This is our "out-of-memory" panic switch */
    signal(SIGUSR1, weirdsig_handler);

    /* Other special handlers */
    signal(SIGHUP, sighup_handler);
    signal(SIGTERM, sigterm_handler);
    signal(SIGUSR2, sigusr2_handler);
}

/*************************************************************************/

/* Helper routine for SIGSETJMP() macro; saves a pointer to the environment
 * buffer locally. */

void do_sigsetjmp(sigjmp_buf *bufptr)
{
    panic_ptr = bufptr;
}

/*************************************************************************/

/* Enable or disable receipt of certain signals (in particular, those which
 * cause us to take actions other than simply terminating the program, to
 * avoid such signals happening at inopportune times and causing things to
 * break).
 */

void enable_signals(void)
{
    sigset_t sigs;
    sigemptyset(&sigs);
    sigaddset(&sigs, SIGHUP);
    sigaddset(&sigs, SIGTERM);
    sigaddset(&sigs, SIGUSR2);
    sigprocmask(SIG_UNBLOCK, &sigs, NULL);
}


void disable_signals(void)
{
    sigset_t sigs;
    sigemptyset(&sigs);
    sigaddset(&sigs, SIGHUP);
    sigaddset(&sigs, SIGTERM);
    sigaddset(&sigs, SIGUSR2);
    sigprocmask(SIG_BLOCK, &sigs, NULL);
}

/*************************************************************************/
