/*
 * DANG_BEGIN_MODULE
 *
 * Initializes the signals for Linux.
 *
 * DANG_END_MODULE
 *
 * DANG_BEGIN_CHANGELOG
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 *
 * DANG_END_CHANGELOG
 */

#include "dosemu.h"
#include "base/signals.h"
#include "base/detach.h"
#include "arch/signals.h"
#include "dosext/dpmi.h"
#include "env/video.h"
#include "env/setup.h"

#include <signal.h>
#include <syscall.h>

/* for use by cli() and sti() */
static sigset_t oldset;

/* 
 * DANG_BEGIN_FUNCTION signal_block
 *
 * description:
 *  Stop additional signals from interrupting DOSEMU.
 *
 * DANG_END_FUNCTION
 */
void signal_block(void)
{
  sigset_t blockset;

  sigfillset(&blockset);
  DOS_SYSCALL(sigprocmask(SIG_SETMASK, &blockset, &oldset));
}

/* 
 * DANG_BEGIN_FUNCTION signal_unblock
 *
 * description:
 *  Allow all signals to interrupt DOSEMU.
 *
 * DANG_END_FUNCTION
 */
void signal_unblock(void)
{
  sigset_t blockset;

  DOS_SYSCALL(sigprocmask(SIG_SETMASK, &oldset, &blockset));
}

/*
 * Similar to the sigaction function in libc, except it leaves alone the
 * restorer field -- stolen from the wine-project
 */
int dosemu_sigaction(int sig, struct sigaction *new, struct sigaction *old)
{
  __asm__("int $0x80":"=a"(sig)
	  :"0"(SYS_sigaction), "b"(sig), "c"(new), "d"(old));
  if (sig >= 0)
    return 0;
  errno = -sig;
  return -1;
}

void newsetqsig(int sig,__sighandler_t fun)
{
   struct sigaction sa;
   sa.sa_handler = fun;
   /* Point to the top of the stack minus 4 (just in case) and align it  */
   sa.sa_restorer = 
     (void (*)(void)) (((unsigned int)(cstack) + sizeof(cstack) - 4) & ~3);
   sa.sa_flags = SA_RESTART ;
   sigemptyset(&sa.sa_mask);
   sigaddset(&sa.sa_mask, SIGIO);
   sigaddset(&sa.sa_mask, SIG_TIME);
   sigaddset(&sa.sa_mask, SIG_RELEASE);
   sigaddset(&sa.sa_mask, SIG_ACQUIRE);
   dosemu_sigaction(sig, &sa, NULL);
}

void setsig(int sig,__sighandler_t fun) 
{
   struct sigaction sa;
   sa.sa_handler = fun;
   sa.sa_flags = SA_RESTART;
   sigemptyset(&sa.sa_mask);
   sigaddset(&sa.sa_mask, SIG_TIME);
   sigaction(sig, &sa, NULL);
}

void newsetsig(int sig,__sighandler_t fun) 
{
   struct sigaction sa;
   sa.sa_handler = fun;
   /* Point to the top of the stack minus 4 (just in case) and align it  */
   sa.sa_restorer =
      (void (*)(void)) (((unsigned int)(cstack) + sizeof(cstack) - 4) & ~3);
   sa.sa_flags = SA_RESTART;
   sigemptyset(&sa.sa_mask);
   sigaddset(&sa.sa_mask, SIG_TIME);
   dosemu_sigaction(sig, &sa, NULL);
}

static void sigio(int sig, struct sigcontext_struct context)
{
  if (in_dpmi && !in_vm86)
    dpmi_sigio(&context);
  signal_queue(signal_sigio);
}

static void sigalrm(int sig, struct sigcontext_struct context)
{
  if (in_dpmi && !in_vm86)
    dpmi_sigio(&context);
  signal_queue(signal_timer);
}

static void sigquit(int sig)
{
  signal_queue(signal_quit);
}


void linux_signal_init(void)
{
  sigset_t trashset;

  /* block no additional signals (i.e. get the current signal mask) */
  sigemptyset(&trashset);
  sigprocmask(SIG_BLOCK, &trashset, &oldset);

  g_printf("Initialized all signals to NOT-BLOCK\n");

  /* init signal handlers */
  NEWSETSIG(SIGILL, dosemu_fault);
  NEWSETQSIG(SIG_TIME, sigalrm);
  NEWSETSIG(SIGFPE, dosemu_fault);
  NEWSETSIG(SIGTRAP, dosemu_fault);

  SETSIG(SIGHUP, leavedos);	/* for "graceful" shutdown */
  SETSIG(SIGTERM, leavedos);
  SETSIG(SIGQUIT, sigquit);
  SETSIG(SIGWINCH, gettermcap); /* Adjust window sizes in DOS */
  NEWSETQSIG(SIGIO, sigio);
  NEWSETSIG(SIGSEGV, dosemu_fault);
}
