#include <config.h>
#include <sstream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#ifdef HAVE_SIGNAL
#include <signal.h>
#endif
#include "IMSignal.hh"
#include "IMSvr.hh"
#include "IMLog.hh"

IMSignal* IMSignal::psinst = NULL;

/*
  handlers
 */

RETSIGTYPE
IMSignal::sighup()
{
    psvr->reload();
}

RETSIGTYPE
IMSignal::term()
{
    psvr->terminate();
}

RETSIGTYPE
IMSignal::segv()
{
    _segv(SIGSEGV);
}

RETSIGTYPE
IMSignal::abrt()
{
    _segv(SIGABRT);
}

RETSIGTYPE
IMSignal::trap()
{
    _segv(SIGTRAP);
}

RETSIGTYPE
IMSignal::fpe()
{
    _segv(SIGFPE);
}

RETSIGTYPE
IMSignal::bus()
{
    _segv(SIGBUS);
}

RETSIGTYPE
IMSignal::_segv(int num)
{
    static int in_segv = 0;
    pid_t pid;
    stringstream s;
    string spid, signum;

    s << getpid();
    spid = s.str();
    s.str("");
    s << num;
    signum = s.str();

    in_segv++;

    if (in_segv > 1) {
	/* avoid the multiple segfaulting */
	LOG_CRITICAL("Multiple crashes have occurred.  Give up to storing logs.");
	_exit(255);
    }

    pid = fork();
    if (pid < 0) {
	LOG_CRITICAL("Crash has occurred.  Unable to store logs.");
	_exit(255);
    } else if (pid > 0) {
	/* parent */
	int status;
	pid_t retval;

	retval = waitpid(pid, &status, 0);
	LOG_CRITICAL("Crash has occurred.  Please see /var/log/iiim/ or $HOME/.iiim/logs/ for details.");
	_exit(255);
    } else {
	/* child */
	int retval;

	retval = execl(SEGVLOGGER,
		       SEGVLOGGER,
		       spid.c_str(), signum.c_str(), NULL);
	if (retval == -1)
	    LOG_CRITICAL("Unable to run logger program `%s'.", SEGVLOGGER);
	_exit(99);
    }

    in_segv--;
}

/*
  management functions.
 */

RETSIGTYPE
signal_handler(
    int num
)
{
    IMSignal *pims = IMSignal::get_instance();
    IMSignal_handler ph;

    if (!pims) return;
    ph = pims->getfunc(num);
    if (!ph) return;
    (pims->*ph)();
    return;
}

IMSignal_handler
IMSignal::getfunc(
    int num
)
{
    IMSignalHandlerMap::iterator i;

    i = hmap.find(num);
    if (i == hmap.end()) return NULL;

    return i->second;
}

void
IMSignal::add(
    int num,
    IMSignal_handler h
)
{
    hmap[num] = h;
#ifdef HAVE_SIGACTION
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sa.sa_flags = SA_NOCLDSTOP;
#ifdef SA_RESTART
    sa.sa_flags |= SA_RESTART;
#endif
    ::sigaction(num, &sa, NULL);
#elif defined(HAVE_SIGNAL)
    ::signal(num, signal_handler);
#endif
}

void
IMSignal::ignore(
    int num
)
{
    hmap[num] = NULL;
#ifdef HAVE_SIGACTION
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = SIG_IGN;
#ifdef SA_RESTART
    sa.sa_flags = SA_RESTART;
#endif
    ::sigaction(num, &sa, NULL);
#elif defined(HAVE_SIGNAL)
    ::signal(num, SIG_IGN);
#endif
}

IMSignal::IMSignal(
    IMSvr *pimsvr
) : psvr(pimsvr)
{
    add(SIGHUP, &IMSignal::sighup);
    add(SIGINT, &IMSignal::term);
    add(SIGSEGV, &IMSignal::segv);
    add(SIGABRT, &IMSignal::abrt);
    add(SIGTRAP, &IMSignal::trap);
    add(SIGFPE, &IMSignal::fpe);
    add(SIGBUS, &IMSignal::bus);
    ignore(SIGPIPE);
}

IMSignal*
IMSignal::construct(
    IMSvr *pimsvr
)
{
    if (psinst) return psinst;
    psinst = new IMSignal(pimsvr);

    return psinst;
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
