/*
 *  linux/ibcs/signal.c
 *
 *  This module does not go through the normal processing routines for
 *  ibcs. The reason for this is that for most events, the return is a
 *  procedure address for the previous setting. This procedure address
 *  may be negative which is not an error. Therefore, the return processing
 *  for standard functions is skipped by declaring this routine as a "special"
 *  module for the decoder and dealing with the register settings directly.
 *
 *  Please consider this closely if you plan on changing this mode.
 *  -- Al Longyear
 *
 * $Id: signal.c,v 1.1.1.3 1994/02/25 17:20:54 mike Exp $
 * $Source: /var/CVS/ibcs/signal.c,v $
 */

#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/segment.h>
#include <linux/ptrace.h>
#include <linux/config.h>
#include <linux/fcntl.h>
#include <linux/personality.h>

#include <asm/segment.h>
#include <asm/system.h>
#include <linux/fs.h>
#include <linux/sys.h>

#include <ibcs/ibcs.h>
#include <ibcs/xnx.h>
#include <ibcs/abi4.h>

#include <signal.h>
#define SIG_HOLD	((__sighandler_t)2)	/* hold signal */

#ifdef IBCS_TRACE
#include <ibcs/trace.h>
#endif

#ifdef __cplusplus
extern "C"
#endif


typedef void (*pfn) (void);     /* Completion function */

/*
 *  Parameters to the signal functions have a common stack frame. This
 *  defines the stack frame.
 */

#define SIGNAL_NUMBER    get_fs_long (&((unsigned long *) regs->esp) [1])
#define HIDDEN_PARAM     (SIGNAL_NUMBER & ~0xFF)
#define SECOND_PARAM     get_fs_long (&((unsigned long *) regs->esp) [2])
#define THIRD_PARAM      ((unsigned long) regs->edx)

/* Return a mask that includes SIG only.  */
#define __sigmask(sig)	(1 << ((sig) - 1))

/*
 *  Defines for the other signal processing routines
 */

#define __sigemptyset(set)	((*(set) = 0L), 0)
#define __sigfillset(set)       ((*(set) = -1L), 0)
#define __sigaddset(set, sig)   ((*(set) |= __sigmask (sig)), 0)
#define __sigdelset(set, sig)   ((*(set) &= ~__sigmask (sig)), 0)
#define __sigismember(set, sig) ((*(set) & __sigmask (sig)) ? 1 : 0)

#define sigaddset	__sigaddset
#define sigdelset	__sigdelset
#define sigismember	__sigismember
#define sigemptyset	__sigemptyset
#define sigfillset	__sigfillset

#define TO_KERNEL(save)      \
	save = get_fs ();    \
	set_fs (get_ds ())

#define FROM_KERNEL(save)    \
	set_fs (save)

/* These are the flags for the SVr4 sigaction struct */
#define ABI_SA_ONSTACK   1
#define ABI_SA_RESETHAND 2
#define ABI_SA_RESTART   4
#define ABI_SA_SIGINFO   8
#define ABI_SA_NODEFER  16
#define ABI_SA_NOCLDWAIT 0x10000
#define ABI_SA_NOCLDSTOP 0x20000

/* These are the error numbers for the SVr4 signal handling */
#define  IBCS_SIGHUP       1
#define  IBCS_SIGINT       2
#define  IBCS_SIGQUIT      3
#define  IBCS_SIGILL       4
#define  IBCS_SIGTRAP      5
#define  IBCS_SIGIOT       6
#define  IBCS_SIGABRT      6
#define  IBCS_SIGEMT       7
#define  IBCS_SIGFPE       8
#define  IBCS_SIGKILL      9
#define  IBCS_SIGBUS      10
#define  IBCS_SIGSEGV     11
#define  IBCS_SIGSYS      12
#define  IBCS_SIGPIPE     13
#define  IBCS_SIGALRM     14
#define  IBCS_SIGTERM	  15
#define  IBCS_SIGUSR1     16
#define  IBCS_SIGUSR2     17
#define  IBCS_SIGCLD      18
#define  IBCS_SIGCHLD     18
#define  IBCS_SIGPWR      19
#define  IBCS_SIGWINCH    20
#define  IBCS_SIGURG	  21
#define  IBCS_SIGPOLL     22
#define  IBCS_SIGIO 	  22
#define  IBCS_SIGSTOP	  23
#define  IBCS_SIGTSTP	  24
#define  IBCS_SIGCONT	  25
#define  IBCS_SIGTTIN	  26
#define  IBCS_SIGTTOU	  27
#define  IBCS_SIGVTALRM	  28
#define  IBCS_SIGPROF	  29
#define  IBCS_SIGGXCPU	  30
#define  IBCS_SIGGXFSZ	  31

/* This array is used to convert from linux signals to ibcs2 signals.
   You should index the array with the linux signal number, and you
   get the equivalent ibcs2 signal number out the other side */

	static int lsignals [] = {
	  -1,
	  IBCS_SIGHUP,
	  IBCS_SIGINT,
	  IBCS_SIGQUIT,
	  IBCS_SIGILL,
	  IBCS_SIGTRAP,
	  IBCS_SIGABRT,
	  -1,
	  IBCS_SIGFPE,
	  IBCS_SIGKILL,
	  IBCS_SIGUSR1,
	  IBCS_SIGSEGV,
	  IBCS_SIGUSR2,
	  IBCS_SIGPIPE,
	  IBCS_SIGALRM,
	  IBCS_SIGTERM,
	  -1,
	  IBCS_SIGCHLD,
	  IBCS_SIGCONT,
	  IBCS_SIGSTOP,
	  IBCS_SIGTSTP,
	  IBCS_SIGTTIN,
	  IBCS_SIGTTOU,
	  IBCS_SIGIO,
	  IBCS_SIGGXCPU,
	  IBCS_SIGGXFSZ,
	  IBCS_SIGVTALRM,
	  IBCS_SIGPROF,
	  IBCS_SIGWINCH,
	  -1,
	  IBCS_SIGPWR
	};

/* This array is used to convert from iBCS2 signals to linux signals.
   You should index the array with the iBCS2 signal number, and you
   get the equivalent linux signal number out the other side */

	static int signals [] = {
		-1,
		SIGHUP,
		SIGINT,
		SIGQUIT,
		SIGILL,
		SIGTRAP,
		SIGIOT,
		SIGUNUSED,       /* SIGEMT */
		SIGFPE,
		SIGKILL,
		SIGUNUSED,       /* SIGSYS  */
		SIGSEGV,
		SIGUNUSED,
		SIGPIPE,
		SIGALRM,
		SIGTERM,
		SIGUSR1,
		SIGUSR2,
		SIGCHLD,
		SIGPWR,
		SIGWINCH,
		-1,
		SIGPOLL,
		SIGSTOP,
		SIGTSTP,
		SIGCONT,
		SIGTTIN,
		SIGTTOU,
		SIGVTALRM,
		SIGPROF,
		SIGXCPU,
		SIGXFSZ
	};
/*
 *  Translate the signal number to the corresponding item for Linux.
 */
 int ibcs_mapsig(int sig) {

	if ((unsigned int) sig >= sizeof(signals)/sizeof(signals[0])) {
		return -1;
	}
	if(sig > 22 && current->personality != PER_SVR4) return -1;
	return signals[sig];
}

/* For a given linux signal number, find the equivalent iBCS2 signal number */
int ibcs_lmapsig(int sig) {

	if ((unsigned int) sig >= sizeof(lsignals)/sizeof(lsignals[0])) {
		return -1;
	}
	return lsignals[sig];
}

inline int ibcs_signo (struct pt_regs * regs, int *sig) {
	int    value = ibcs_mapsig(SIGNAL_NUMBER & 0xFF);

	if (value == -1) {
		regs->eax     = iABI_errors (EINVAL);
		regs->eflags |= 1;
		return 0;
	}

	*sig = value;
	return 1;
}

/* Suspend the process.  The set of blocked signals is in linux signal
   numbers, not ibcs2 signal numbers */

#define _S(nr) (1<<((nr)-1))

#define _BLOCKABLE (~(_S(IBCS_SIGKILL) | _S(IBCS_SIGSTOP)))
#define _LBLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

static int ibcs_sigsuspend(struct pt_regs * regs, unsigned int newset)
{
  unsigned long mask;

  mask = current->blocked;
  current->blocked = newset & _LBLOCKABLE;
  regs->eax = -EINTR;
  while (1) {
    current->state = TASK_INTERRUPTIBLE;
    schedule();
    if (do_signal(mask,regs))
      return -EINTR;
  }
}

/*
 *  Process the signal() function from iBCS
 *
 *  This version appeared in "Advanced Programming in the Unix Environment"
 *  by W. Richard Stevens, page 298.
 */

void ibcs_sig_handler (struct pt_regs * regs, int sig, __sighandler_t handler)
{
	struct sigaction act, oact;
	int	      answer;
	int	      old_fs;

	sigemptyset (&act.sa_mask);
	act.sa_handler = handler;
	act.sa_flags   = 0;

#if 0
	/* THis seems wrong to me.  How about the ONESHOT|NOMASK? ERY */
	if (sig != SIGALRM) {
		act.sa_flags |= SA_RESTART;
	}
#else
	act.sa_flags = SA_ONESHOT | SA_NOMASK;
#endif


	TO_KERNEL (old_fs);
	answer = sys_sigaction (sig, &act, &oact);
	FROM_KERNEL (old_fs);

	if (answer < 0) {
		regs->eax     = iABI_errors (-answer);
		regs->eflags |= 1;
	} else
		regs->eax     = (int) oact.sa_handler;
}

/*
 *  Process the signal() function from iBCS
 */
int ibcs_signal (struct pt_regs * regs)
{
	__sighandler_t   vec;
	int	      sig;

	if (ibcs_signo (regs, &sig)) {
		vec = (__sighandler_t) SECOND_PARAM;
		ibcs_sig_handler (regs, sig, vec);
	}
	return 0;
}

/*
 *      Process the iBCS sigset function.
 *
 *      This is basically the same as the signal() routine with the exception
 *      that it will accept a SIG_HOLD parameter.
 *
 *      A SIG_HOLD will defer the processing of the signal until a sigrelse()
 *      function is called.
 */
int ibcs_sigset (struct pt_regs * regs)
{
	sigset_t	 newmask, oldmask;
	__sighandler_t   vec;
	int	      sig, answer;
	int	      old_fs;

	if (ibcs_signo (regs, &sig)) {
		vec = (__sighandler_t) SECOND_PARAM;
		if (vec != SIG_HOLD) {
			ibcs_sig_handler (regs, sig, vec);
		} else {
/*
 *      Process the hold function
 */
			sigemptyset (&newmask);
			sigaddset  (&newmask, sig);

			TO_KERNEL (old_fs);
			answer = sys_sigprocmask (SIG_BLOCK,
						  &newmask,
						  &oldmask);
			FROM_KERNEL (old_fs);

			if (answer < 0) {
				regs->eax     = iABI_errors (-answer);
				regs->eflags |= 1;
			}
		}
	}
	return 0;
}

/*
 *      Process the iBCS sighold function.
 *
 *      Suspend the signal from future recognition.
 */
void ibcs_sighold (struct pt_regs * regs)
{
	sigset_t   newmask, oldmask;
	int	sig, answer;
	int	old_fs;

	if (!ibcs_signo (regs, &sig))
		return;

	sigemptyset (&newmask);
	sigaddset  (&newmask, sig);

	TO_KERNEL (old_fs);
	answer = sys_sigprocmask (SIG_BLOCK, &newmask, &oldmask);
	FROM_KERNEL (old_fs);

	if (answer < 0) {
		regs->eax     = iABI_errors (-answer);
		regs->eflags |= 1;
	}
}

/*
 *      Process the iBCS sigrelse.
 *
 *      Re-enable the signal processing from a previously suspended
 *      signal. This may have been done by calling the sighold() function
 *      or a longjmp() during the signal processing routine. If you do a
 *      longjmp() function then it is expected that you will call sigrelse
 *      before going on with the program.
 */
void ibcs_sigrelse (struct pt_regs * regs)
{
	sigset_t   newmask, oldmask;
	int	sig, answer;
	int	old_fs;

	if (!ibcs_signo (regs, &sig))
		return;

	sigemptyset (&newmask);
	sigaddset   (&newmask, sig);

	TO_KERNEL (old_fs);
	answer = sys_sigprocmask (SIG_UNBLOCK, &newmask, &oldmask);
	FROM_KERNEL (old_fs);

	if (answer < 0) {
		regs->eax     = iABI_errors (-answer);
		regs->eflags |= 1;
	}
}

/*
 *      Process the iBCS sigignore
 *
 *      This is basically a signal (...,SIG_IGN) call.
 */

void ibcs_sigignore (struct pt_regs * regs)
{
	struct sigaction act, oact;
	int	      sig, answer;
	int	      old_fs;

	if (!ibcs_signo (regs, &sig))
		return;

	sigemptyset (&act.sa_mask);

	act.sa_handler = SIG_IGN;
	act.sa_flags   = 0;

	TO_KERNEL (old_fs);
	answer = sys_sigaction (sig, &act, &oact);
	FROM_KERNEL (old_fs);

	if (answer < 0) {
		regs->eax     = iABI_errors (-answer);
		regs->eflags |= 1;
	}
}

/*
 *      Process the iBCS sigpause
 *
 *      Wait for the signal indicated to arrive before resuming the
 *      processing. I do not know if the signal is processed first using
 *      the normal event processing before the return. If someone can
 *      shed some light on this then please correct this code. I block
 *      the signal and look for it to show up in the pending list.
 */

void ibcs_sigpause (struct pt_regs * regs)
{
	sigset_t   newset, oldset, zeroset, pendingset;
	int	sig, answer;
	int	old_fs;

	if (!ibcs_signo (regs, &sig))
		return;

	sigemptyset (&zeroset);
	sigemptyset (&newset);
	sigaddset   (&newset, sig);

	TO_KERNEL (old_fs);
	answer    = sys_sigprocmask (SIG_BLOCK, &newset, &oldset);
	FROM_KERNEL (old_fs);

	if (answer >= 0) {
		TO_KERNEL (old_fs);
		answer = sys_sigpending (&pendingset);
		FROM_KERNEL (old_fs);

		while (answer >= 0) {
			if (sigismember (&pendingset, sig) != 0)
				break;

			answer = ibcs_sigsuspend (regs, zeroset);

			if (answer >= 0) {
				TO_KERNEL (old_fs);
				answer = sys_sigpending (&pendingset);
				FROM_KERNEL (old_fs);
			}
		}
		TO_KERNEL (old_fs);
		sys_sigprocmask (SIG_SETMASK, &oldset, NULL);
		FROM_KERNEL (old_fs);
	}

	if (answer < 0) {
		regs->eax     = iABI_errors (-answer);
		regs->eflags |= 1;
	}
}

/*
 *  This is the service routine for the syscall #48 (signal funcs).
 *
 *   Examine the request code and branch on the request to the appropriate
 *   function.
 */

int ibcs_sigfunc (struct pt_regs * regs)
{
        char *func;
	int sig_type = (int) HIDDEN_PARAM;

	regs->eflags &= ~1;
	regs->eax     = 0;

	switch (sig_type) {
	case 0x0000:
		ibcs_signal (regs);
		func = KERN_ERR "%snal(%d,0x%08lx)";
		break;

	case 0x0100:
		ibcs_sigset (regs);
		func = KERN_ERR "%sset(%d,0x%08lx";
		break;

	case 0x0200:
		ibcs_sighold (regs);
		func = KERN_ERR "%shold(%d)";
		break;
		
	case 0x0400:
		ibcs_sigrelse (regs);
		func = KERN_ERR "%srelse(%d)";
		break;

	case 0x0800:
		ibcs_sigignore (regs);
		func = KERN_ERR "%snore(%d)";
		break;

	case 0x1000:
		ibcs_sigpause (regs);
		func = KERN_ERR "%spause(%d)";
		break;

	default:
		regs->eax     = EINVAL;
		regs->eflags |= 1;

#ifdef IBCS_TRACE
		if ((ibcs_trace & (TRACE_SIGNAL | TRACE_SIGNAL_F))
		|| ibcs_func_p->trace)
		       printk (KERN_ERR "iBCS2 sigfunc(%x, %ld, %lx, %lx) unsupported\n",
			       sig_type,
			       SIGNAL_NUMBER,
			       SECOND_PARAM,
			       THIRD_PARAM);
#endif
		return 0;
	}

#ifdef IBCS_TRACE
	if ((ibcs_trace & (TRACE_SIGNAL | TRACE_SIGNAL_F))
	|| ibcs_func_p->trace) {
		printk (func,
			"iBCS2 sig",
			SIGNAL_NUMBER,
			SECOND_PARAM,
			THIRD_PARAM);
	         printk (KERN_ERR " == 0x%08lx\n", regs->eax);
	}
#endif
	return 0;
}

int ibcs_kill(int pid, int sig) {
	int outsig = ibcs_mapsig(sig & 0xFF);

#ifdef IBCS_TRACE
	if ((ibcs_trace & TRACE_SIGNAL) || ibcs_func_p->trace)
		printk (KERN_ERR "ibcs_kill:	insig (%d)	outsig(%d) \n"
			, sig & 0xFF, outsig);
#endif
	if (outsig < 0) {
		return -1;
	}
	return sys_kill (pid, outsig);
}


/* This function is used to handle the sigaction call from SVr4 binaries.
   If anyone else uses this, this function needs to be modified since the
   order and size of the ibcs_sigaction structure is different in ibcs
   and the SVr4 ABI */


asmlinkage int abi_sigaction(int abi_signum, const struct abi_sigaction * action,
	struct abi_sigaction * oldaction)
{
	struct abi_sigaction new_sa, old_sa;
	int newflags, newmask, signum, i;
	struct sigaction *p;

	signum = signals[abi_signum];
	if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP)
		return -EINVAL;
	p = signum - 1 + current->sigaction;
	if (action) {
		memcpy_fromfs(&new_sa, action, sizeof(struct abi_sigaction));
		if (new_sa.sa_flags & ABI_SA_NODEFER)
			new_sa.sa_mask = 0;
		else {
			new_sa.sa_mask |= _S(abi_signum);
			new_sa.sa_mask &= _BLOCKABLE;
		}
		if (TASK_SIZE <= (unsigned long) new_sa.sa_handler)
			return -EFAULT;
	}
	if (oldaction) {
		if (!verify_area(VERIFY_WRITE,oldaction, sizeof(struct abi_sigaction))) {
		  old_sa.sa_handler = p->sa_handler;
		  newmask = 0;
		  for(i=0; i<sizeof(signals)/sizeof(signals[0]); i++)
		    if(signals[i] != -1 &&
		       (p->sa_mask & (1 << i))) newmask |= (1 << lsignals[i]);
		  old_sa.sa_mask = newmask;
		  newflags = 0;
		  if(p->sa_flags & SA_STACK) newflags |= ABI_SA_ONSTACK;
		  if(p->sa_flags & SA_RESTART) newflags |= ABI_SA_RESTART;
		  if(p->sa_flags & SA_NOMASK) newflags |= ABI_SA_NODEFER;
		  if(p->sa_flags & SA_ONESHOT) newflags |= ABI_SA_RESETHAND;
		  if(p->sa_flags & SA_NOCLDSTOP) newflags |= ABI_SA_NOCLDSTOP;
		  old_sa.sa_flags = newflags;
		  memcpy_tofs(oldaction, &old_sa, sizeof(struct abi_sigaction));
		      };
	}
	if (action) {
	        /* The internal format of the sigaction structure is
		   different, so we cannot simply copy the structure. */
	  	p->sa_handler = new_sa.sa_handler;
		newmask = 0;
		for(i=0; i<sizeof(signals)/sizeof(signals[0]); i++)
		  if(signals[i] != -1 &&
		     (new_sa.sa_mask & (1 << i))) newmask |= (1 << signals[i]);
	  	p->sa_mask = newmask;
		newflags = 0;
		if(new_sa.sa_flags & ABI_SA_ONSTACK) newflags |= SA_STACK;
		if(new_sa.sa_flags & ABI_SA_RESTART) newflags |= SA_RESTART;
		if(new_sa.sa_flags & ABI_SA_NODEFER) newflags |= SA_NOMASK;
		if(new_sa.sa_flags & ABI_SA_RESETHAND) newflags |= SA_ONESHOT;
		if(new_sa.sa_flags & ABI_SA_NOCLDSTOP) newflags |= SA_NOCLDSTOP;
	  	p->sa_flags = newflags;
		check_pending(signum);
	}
	return 0;
}

static short int howcnv[SIG_SETMASK, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK];

asmlinkage int abi_sigprocmask(int how, sigset_t *set, sigset_t *oset)
{
  int how1;
  if(how < 0 || how > 3) return -EINVAL;

  how1 = howcnv[how];
  return sys_sigprocmask(how1, set, oset);
}

/* Handle the sigsuspend syscall.  We cannot use sys_sigsuspend because
   this assumes that the first argument points to the argument stack
   for an int 0x80 handler */

int abi_sigsuspend(struct pt_regs * regs)
{
  sigset_t * set;
  unsigned long newset, oldset;
  int i, error;

  set = (sigset_t *) get_fs_long (&((unsigned long *) regs->esp) [1]);

  newset = 0;

  if ((error = verify_area(VERIFY_READ, set, sizeof(long))))
    return error;

  oldset = get_fs_long ((unsigned long *) set);

  for(i=0; i<sizeof(signals)/sizeof(signals[0]); i++)
    if(signals[i] != -1 &&
       (oldset & (1 << i))) newset |= (1 << signals[i]);

#ifdef IBCS_TRACE
  if ((ibcs_trace & TRACE_SIGNAL) || ibcs_func_p->trace)
    printk("iBCS: sigsuspend oldset, newset = %x %x\n", oldset, newset);
#endif
  return ibcs_sigsuspend(regs, newset);
}
