/*
 * signals.c --
 *
 * Handles signals, such as stop, start, interrupt.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/utils/signals.c,v 1.2 2010/03/08 13:33:34 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>	/* for getpid() */
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <fcntl.h>

#include "tcltk/tclmagic.h"
#include "utils/main.h"
#include "utils/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "utils/signals.h"
#include "windows/windows.h"
#include "graphics/graphics.h"

#ifndef	SIGEMT
#define	SIGEMT	7	/* EMT instruction (SIGUNUSED) */
#endif

#ifndef	SIGBUS
#define	SIGBUS	10	/* bus error (SIGUSR1) */
#endif

#ifndef	SIGSYS
#define	SIGSYS	12	/* bad argument to system call (SIGUSR2) */
#endif

#if !defined(SIGIOT) && defined(SIGABRT)
#define SIGIOT SIGABRT	/* io-trap signal redefined */
#endif

#if defined(linux) || defined(EMSCRIPTEN)
#if SIGBUS == SIGUNUSED
#undef SIGBUS
#define SIGBUS SIGUSR1
#endif

#if SIGSYS == SIGUNUSED
#undef SIGSYS
#define SIGSYS SIGUSR2
#endif
#endif

#ifndef FASYNC
#  define	FASYNC	00100	/* kludge for SUN2s */
#endif

/* specially imported */
/* DBWriteBackup declared in database/database.h */

/* macs support BSD4.2 signals, so turn off the SYSV flag for this module */
#ifdef __APPLE__
#undef SYSV
#endif

void sigSetAction(int, sigRetVal (*)(int));

/* becomes true when we get an interrupt */
global bool SigInterruptPending = FALSE;

/* Becomes true when IO is possible on one of the files passed to SigWatchFile.
 * Spurious signals are sometimes generated -- use select() to make
 * sure that what you want is really there.
 */
global bool SigIOReady = FALSE;

/* If set to 1, we will set SigInterruptPending whenever we set SigIOReady. */
/* If set to -1, then SigInterruptPending is never set */
global char SigInterruptOnSigIO;

/*
 * Set to true when we recieve a SIGWINCH/SIGWINDOW signal
 * (indicating that a window has changed size or otherwise needs attention).
 */
global bool SigGotSigWinch = FALSE;


/*
 * Local data structures
 */
static bool sigInterruptReceived = FALSE;
static int sigNumDisables = 0;

/*---------------------------------------------------------
 * While we can conveniently run Ctrl-C interrupts from
 * the terminal at any time, we can't do this from the
 * window in Tcl/Tk because the GUI event handler is not
 * a separate process.  So we run a timer process during
 * WindUpdate() which periodically processes Tk events
 * in the window.
 *
 * This timer can be set for "secs" second intervals.  If
 * "secs" is zero, it defaults to a 1/4 second interval.
 *---------------------------------------------------------
 */

void
SigSetTimer(int secs)
{
#ifdef __EMSCRIPTEN__
    (void)secs;
    return;
#else
    struct itimerval subsecond;	/* one-quarter second interval */

    /*
    if (GrDisplayStatus == DISPLAY_IDLE)
	fprintf(stderr, "Timer start\n");
    else
	fprintf(stderr, "Timer repeat\n");
    fflush(stderr);
    */

    subsecond.it_interval.tv_sec = 0;
    subsecond.it_interval.tv_usec = 0;
    subsecond.it_value.tv_sec = secs;
    subsecond.it_value.tv_usec = (secs == 0) ? 250000 : 0;

    setitimer(ITIMER_REAL, &subsecond, NULL);
#endif
}

/*---------------------------*/
/* Remove any existing timer */
/*---------------------------*/

void
SigRemoveTimer()
{
#ifdef __EMSCRIPTEN__
    return;
#else
    struct itimerval zero;	/* zero time to stop the timer */

    /* fprintf(stderr, "Timer stop\n"); fflush(stderr); */

    zero.it_value.tv_sec = 0;
    zero.it_value.tv_usec = 0;
    zero.it_interval.tv_sec = 0;
    zero.it_interval.tv_usec = 0;

    setitimer(ITIMER_REAL, &zero, NULL);
#endif
}

sigRetVal
sigOnAlarm(int signo)
{
    if (GrDisplayStatus == DISPLAY_IN_PROGRESS)
	GrDisplayStatus = DISPLAY_BREAK_PENDING;

    sigReturn;
}

/*-----------------------------------------*/
/* Set timer to act as a display interrupt */
/*-----------------------------------------*/

void
SigTimerDisplay()
{
    sigSetAction(SIGALRM,  sigOnAlarm);
}

/*------------------------------------------*/
/* Set timer to act like a Ctrl-C interrupt */
/*------------------------------------------*/

void
SigTimerInterrupts()
{
    sigSetAction(SIGALRM,  sigOnInterrupt);
}

/*---------------------------------------------------------
 * sigOnStop:
 *	This procedure handles stop signals.
 *
 * Results:
 *	sigReturn result (see signals.h)
 *
 * Side Effects:
 *	The text display is reset, and we stop
 *---------------------------------------------------------
 */

sigRetVal
sigOnStop(int signo)
{
    /* fix things up */
    TxResetTerminal(TRUE);
    GrStop();

    /* restore the default action and resend the signal */

    sigSetAction(SIGTSTP, SIG_DFL);
    kill(getpid(),
#ifdef linux
    	SIGSTOP
#else
    	SIGTSTP
#endif
	);

    /* -- we stop here -- */

    /* NOTE:  The following code really belongs in a routine that is
     * called in response to a SIGCONT signal, but it doesn't seem to
     * work that way.  Maybe there is a Unix bug with this???
     */

    GrResume();
    TxSetTerminal();
    TxReprint();

    /* catch future stops now that we have finished resuming */

    sigSetAction(SIGTSTP, sigOnStop);
    sigReturn;
}

/*
 *---------------------------------------------------------
 * SigCheckProcess ---
 *
 *	Check if a process exists by sending it a signal.
 *	Perhaps there are better ways to do this?
 *
 * Results:
 *	TRUE if the process exists, FALSE if not.
 *
 * Side effects:
 *	Whatever happens when the process gets SIGCONT;
 *	hopefully nothing.
 *---------------------------------------------------------
 */

bool
SigCheckProcess(pid)
    int pid;
{
#ifdef __EMSCRIPTEN__
    (void)pid;
    return FALSE;
#else
    int result;
    result = kill((pid_t)pid, SIGCONT);

    if (result == 0) return TRUE;
    else return FALSE;
#endif
}

/*---------------------------------------------------------
 * sigEnableInterrupts:
 *	This procedure reenables our handling of interrupts.
 *
 * Results:	None.
 *
 * Side Effects:
 *	None.
 *---------------------------------------------------------
 */

void
SigEnableInterrupts()
{
    /*sigNumDisables--;*/
    if (sigNumDisables == 1)
    {
	SigInterruptPending = sigInterruptReceived;
	sigInterruptReceived = FALSE;
    }
    sigNumDisables--;
}


/*---------------------------------------------------------
 * sigDisableInterrupts:
 *	This procedure disables our handling of interrupts.
 *
 * Results:	None.
 *
 * Side Effects:
 *	None.
 *---------------------------------------------------------
 */

void
SigDisableInterrupts()
{
    sigNumDisables++;
    if (sigNumDisables == 1)
    {
	sigInterruptReceived = SigInterruptPending;
	SigInterruptPending = FALSE;
    }
    /*sigNumDisables++;*/
}


/*
 * ----------------------------------------------------------------------------
 * SigWatchFile --
 *
 *	Take interrupts on a given IO stream.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	SigIOReady will be set when the IO stream becomes ready.  It is
 *	the responsibility of the client to clear that flag when needed.
 * ----------------------------------------------------------------------------
 */

void
SigWatchFile(filenum, filename)
    int filenum;		/* A file descriptor number */
    char *filename;		/* Used to recognize special files that
				 * don't support a full range of fcntl
				 * calls (such as windows: /dev/winXX).
				 */
{
#ifdef __EMSCRIPTEN__
    (void)filenum;
    (void)filename;
    return;
#else
    int flags;
    bool iswindow;

    iswindow = (filename && (strncmp(filename, "/dev/win", 8) == 0));

    flags = fcntl(filenum, F_GETFL, 0);
    if (flags == -1)
    {
	perror("(Magic) SigWatchFile1");
	return;
    }

    if (!mainDebug)
    {
	/* turn on FASYNC */
#ifndef SYSV
#ifdef F_SETOWN
	if (!iswindow)
	{
	    if (fcntl(filenum, F_SETOWN, -getpid()) == -1)
		perror("(Magic) SigWatchFile2");
	}
#endif
#endif /* SYSV */
#ifdef FASYNC
	if (fcntl(filenum, F_SETFL, flags | FASYNC) == -1)
	    perror("(Magic) SigWatchFile3");
#else
# ifdef FIOASYNC
	flags = 1;
	if (ioctl(filenum, FIOASYNC, &flags) == -1)
	    perror("(Magic) SigWatchFile3a");
# endif
#endif
    }
    else
    {
#ifdef FASYNC
	/* turn off FASYNC */
	if (fcntl(filenum, F_SETFL, flags & (~FASYNC)) == -1)
	    perror("(Magic) SigWatchFile4");
#else
# ifdef FIOASYNC
	flags = 0;
	if (ioctl(filenum, FIOASYNC, &flags) == -1)
	    perror("(Magic) SigWatchFile3b");
# endif
#endif
    }
#endif
}


/*
 * ----------------------------------------------------------------------------
 * SigUnWatchFile --
 *
 *	Do not take interrupts on a given IO stream.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	SigIOReady will be not set when the IO stream becomes ready.
 * ----------------------------------------------------------------------------
 */
 /*ARGSUSED*/

void
SigUnWatchFile(filenum, filename)
    int filenum;		/* A file descriptor number */
    char *filename;		/* Used to recognize special files that
				 * don't support a full range of fcntl
				 * calls (such as windows: /dev/winXX).
				 */
{
#ifdef __EMSCRIPTEN__
    (void)filenum;
    (void)filename;
    return;
#else
    int flags;

    flags = fcntl(filenum, F_GETFL, 0);
    if (flags == -1)
    {
	perror("(Magic) SigUnWatchFile1");
	return;
    }

#ifdef FASYNC
    /* turn off FASYNC */
    if (fcntl(filenum, F_SETFL, flags & (~FASYNC)) == -1)
	perror("(Magic) SigUnWatchFile4");
#else
# ifdef FIOASYNC
    flags = 0;
    if (ioctl(filenum, FIOASYNC, &flags) == -1)
	perror("(Magic) SigWatchFile3");
# endif
#endif
#endif
}


/*---------------------------------------------------------
 * sigOnInterrupt:
 *	This procedure handles interupt signals.
 *
 * Results:
 *	sigReturn result (see signals.h)
 *
 * Side Effects:
 *    A global flag is set
 *---------------------------------------------------------
 */

sigRetVal
sigOnInterrupt(int signo)
{
    if (sigNumDisables != 0)
	sigInterruptReceived = TRUE;
    else
	SigInterruptPending = TRUE;
    sigReturn;
}


/*
 * ----------------------------------------------------------------------------
 * sigOnTerm:
 *
 *	Catch the terminate (SIGTERM) signal.
 *	Force all modified cells to be written to disk (in new files,
 *	of course).
 *
 * Results:
 *	Function does not return.
 *
 * Side effects:
 *	Writes cells out to disk (by calling DBWriteBackup()).
 *	Exits.
 * ----------------------------------------------------------------------------
 */

sigRetVal
sigOnTerm(int signo)
{
    DBWriteBackup(NULL, FALSE, FALSE);
    exit (1);
}



/*
 * ----------------------------------------------------------------------------
 * sigOnWinch --
 *
 *	A window has changed size or otherwise needs attention.
 *
 * Results:
 *	sigReturn result (see signals.h)
 *
 * Side effects:
 *	Sets a global flag.
 * ----------------------------------------------------------------------------
 */

sigRetVal
sigOnWinch(int signo)
{
    SigGotSigWinch = TRUE;
    sigReturn;
}

/*
 * ----------------------------------------------------------------------------
 * sigIO --
 *
 *	Some IO device is ready (probably the keyboard or the mouse).
 *
 * Results:
 *	sigReturn result (see signals.h)
 *
 * Side effects:
 *	Sets a global flag.
 * ----------------------------------------------------------------------------
 */

sigRetVal
sigIO(int signo)
{
    SigIOReady = TRUE;
    if (SigInterruptOnSigIO == 1) sigOnInterrupt(0);
    sigReturn;
}

/*
 * ----------------------------------------------------------------------------
 *
 * sigCrash --
 *
 *	Something when wrong, reset the terminal and die.
 *
 * Results:
 *	Function never returns.
 *
 * Side Effects:
 *	We die.
 *
 * ----------------------------------------------------------------------------
 */

sigRetVal
sigCrash(signum)
    int signum;
{
    static int magicNumber = 1239987;
    char *msg;
    extern bool AbortFatal;

#if !defined(linux) && !defined(EMSCRIPTEN)
    if (magicNumber == 1239987) {
	/* Things aren't screwed up that badly, try to reset the terminal */
	magicNumber = 0;
	switch (signum) {
	    case SIGILL: {msg = "Illegal Instruction"; break;};
	    case SIGTRAP: {msg = "Instruction Trap"; break;};
	    case SIGIOT: {msg = "IO Trap"; break;};
	    case SIGEMT: {msg = "EMT Trap"; break;};
	    case SIGFPE: {msg = "Floating Point Exception"; break;};
	    case SIGBUS: {msg = "Bus Error"; break;};
	    case SIGSEGV: {msg = "Segmentation Violation"; break;};
	    case SIGSYS: {msg = "Bad System Call"; break;};
	    default: {msg = "Unknown signal"; break;};
	};
	strcpy(AbortMessage, msg);
	AbortFatal = TRUE;
	niceabort();
	TxResetTerminal(TRUE);
    }
#else
	if (magicNumber == 1239987) {
	  magicNumber = 0;
	  /* Don't use switch statement.  These values aren't mutually exclusive
	  * under Linux.
	  */
	  if (signum == SIGILL) msg = "Illegal Instruction";
	  else if (signum == SIGTRAP) msg = "Instruction Trap";
	  else if (signum == SIGIOT) msg = "IO Trap";
	  else if (signum == SIGEMT) msg = "EMT Trap";
	  else if (signum == SIGFPE) msg = "Floating Point Exception";
	  else if (signum == SIGBUS) msg = "Bus Error";
	  else if (signum == SIGSEGV) msg = "Segmentation Violation";
	  else if (signum == SIGSYS) msg = "Bad System Call";
	  else msg = "Unknown signal";
	  strcpy(AbortMessage, msg);
	  AbortFatal = TRUE;
	  niceabort();
	  TxResetTerminal(TRUE);
	}
#endif

    /* Crash & burn */
    magicNumber = 0;
    exit(12);
}


/*
 * ----------------------------------------------------------------------------
 * SigInit:
 *
 *	Set up signal handling for all signals.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Signal handling is set up.
 * ----------------------------------------------------------------------------
 */

void
SigInit(batchmode)
    int batchmode;
{
#ifdef __EMSCRIPTEN__
    SigInterruptOnSigIO = (batchmode) ? -1 : 0;
    SigInterruptPending = FALSE;
    SigIOReady = FALSE;
    SigGotSigWinch = FALSE;
    return;
#else
    /* fprintf(stderr, "Establishing signal handlers.\n"); fflush(stderr); */

    if (batchmode)
    {
	SigInterruptOnSigIO = -1;
    }
    else
    {
	SigInterruptOnSigIO = 0;
	sigSetAction(SIGINT,  sigOnInterrupt);
	sigSetAction(SIGTERM, sigOnTerm);
    }

    /* Under Tcl, sigOnStop just causes Tcl to hang forever.  So don't	*/
    /* set any new actions.						*/

#ifndef MAGIC_WRAPPER
#ifdef SIGTSTP
    sigSetAction(SIGTSTP, sigOnStop);
#endif

#ifdef SIGWINCH
    sigSetAction(SIGWINCH, sigOnWinch);
#endif

#ifdef SIGWINDOW
    sigSetAction(SIGWINDOW, sigOnWinch);
#endif
#endif	/* MAGIC_WRAPPER */

    if (!mainDebug )
    {
	sigSetAction(SIGIO, sigIO);
#ifdef MAGIC_WRAPPER
	if (batchmode == 0)
	    SigTimerDisplay();
	else
	    sigSetAction(SIGALRM, SIG_IGN);
#else
	sigSetAction(SIGALRM, SIG_IGN);
#endif

	sigSetAction(SIGPIPE, SIG_IGN);

#ifdef SIGPOLL
	if (SIGIO != SIGPOLL)
	{
	    sigSetAction(SIGPOLL, SIG_IGN);
	}
#endif
    }

#if !defined(SYSV) && !defined(CYGWIN) && !defined(EMSCRIPTEN)
    sigsetmask(0);
#endif
#endif
}

void
sigSetAction(int signo, sigRetVal (*handler)(int))
{
#if defined(SYSV) || defined(CYGWIN) || defined(__NetBSD__) || defined(EMSCRIPTEN)
    struct sigaction sa;

    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(signo, &sa, (struct sigaction *)NULL);
#else
    struct sigvec sv;

    sv.sv_handler = handler;
    sv.sv_mask    = 0;
    sv.sv_flags   = 0;
    sigvec(signo, &sv, (struct sigvec *)NULL);
#endif
}
