/*
 *  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.7 1994/05/26 13:26:20 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

#include "maps/signal.inc"


/* From linux/kernel/signal.c */
extern void check_pending(int);
extern int do_signal(unsigned long, struct pt_regs *);


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

/*
 *  Translate the signal number to the corresponding item for Linux.
 */
static  int ibcs_mapsig(int sig) {

	if ((unsigned int) sig >= NSIGNALS) {
		return -1;
	}
#ifdef INIT_MM
	return current->signal_map[sig];
#else
	return signal_map_to_linux[current->personality & PER_MASK][sig];
#endif
}

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

	if ((unsigned int) sig >= NSIGNALS) {
		return -1;
	}
	return signal_map_from_linux[current->personality & PER_MASK][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_DEBUG "%snal(%d,0x%08lx)";
		break;

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

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

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

	case 0x1000:
		ibcs_sigpause (regs);
		func = KERN_DEBUG "%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_DEBUG " == 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_DEBUG "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 = ibcs_mapsig(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;
#ifdef INIT_MM
		  for (i=0; i<NSIGNALS; i++)
		    if (current->signal_map[i] != -1
		    && (p->sa_mask & (1 << i)))
			newmask |= (1 << current->signal_invmap[i]);
#else
		  for(i=0; i<NSIGNALS; i++)
		    if(signal_map_to_linux[current->personality & PER_MASK][i] != -1
		    && (p->sa_mask & (1 << i)))
			newmask |= (1 << signal_map_from_linux[current->personality & PER_MASK][i]);
#endif
		  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;
#ifdef INIT_MM
		  for (i=0; i<NSIGNALS; i++)
		    if (current->signal_map[i] != -1
		    && (new_sa.sa_mask & (1 << i)))
			newmask |= (1 << current->signal_invmap[i]);
#else
		  for(i=0; i<NSIGNALS; i++)
		    if(signal_map_to_linux[current->personality & PER_MASK][i] != -1
		    && (new_sa.sa_mask & (1 << i)))
			newmask |= (1 << signal_map_from_linux[current->personality & PER_MASK][i]);
#endif
	  	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 for pending signals. */
		if (p->sa_handler == SIG_IGN) {
			if (signum == SIGCHLD)
				return 0;
			current->signal &= ~_S(signum);
			return 0;
		}
		if (p->sa_handler == SIG_DFL) {
			if (signum != SIGCONT
			&& signum != SIGCHLD
			&& signum != SIGWINCH)
				return 0;
			current->signal &= ~_S(signum);
			return 0;
		}	
	}
	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);

#ifdef INIT_MM
  for (i=0; i<NSIGNALS; i++)
    if (current->signal_map[i] != -1
    && (oldset & (1 << i)))
	newset |= (1 << current->signal_invmap[i]);
#else
  for(i=0; i<NSIGNALS; i++)
    if(signal_map_to_linux[current->personality & PER_MASK][i] != -1
    && (oldset & (1 << i)))
	newset |= (1 << signal_map_from_linux[current->personality & PER_MASK][i]);
#endif

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