/* Signal related system calls and processing
   Copyright (C) 1991 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

The GNU Hurd is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

#include <mach/mach_traps.h>/* must be first because of mach_task_self macro */
#include "mach.h"
#include <mach/error.h>
#include <mach/notify.h>
#include <mig_errors.h>
#include <cthreads.h>
#include <gnu/types.h>
#include <gnu/param.h>
#include <gnu/errno.h>
#include <gnu/signal.h>
#include <gnu/posix_errors.h>
#include <gnu/wait.h>
#include <hurd/hurd_types.h>
#include <lib/libhurd.h>

#include "process.h"

#include "alix.h"
#include <machine/alix_machdep.h>
#include "alix_proc.h"

/* Missing:
   return_sig_post, write_corefile, sigpending, sigaltstack,
*/

enum action_type
{
  stop, term, core, handle, ignore,
};

static struct mutex siglock;	/* only locks these two... */
static int stopped;
static thread_t desig_thread;

static enum action_type get_sigact (int, struct sigstate *, int);
static void return_sig_post (int);

/* Signals that cannot be blocked */
#define CANT_BLOCK (sigmask (SIGKILL) | sigmask (SIGSTOP))

/* Signals that cannot be ignored or caught */
#define CANT_IGNORE (sigmask (SIGKILL) | sigmask (SIGSTOP))
      
/* SYSCALL: Change the current signal vector for a signal.  */
int
#ifdef COMPAT_43
sigaction1 (void *ap, int *ret1, int *ret2, int compat_43)
#else
sigaction (void *ap, int *ret1, int *ret2)
#endif
{
  struct 
    {
      int signo;
      struct sigaction *sap;
      struct sigaction *osap;
    }
  *args = ap;
  struct sigstate *ss;
  struct sigaction sa;
  int err;

  if (args->signo < 0 || args->signo >= NSIG)
    return EINVAL;

  if (err = copyin (args->sap, &sa, sizeof (struct sigaction)))
    return err;
  
#ifdef COMPAT_43
  if (compat_43)
    sa.sa_flags ^= SA_RESTART;
#endif  

  if ((sigmask (args->signo) & CANT_IGNORE) && sa.sa_handler != SIG_DFL)
    {
      mutex_unlock (&siglock);
      return EINVAL;
    }

  ss = thread_to_sigstate (mach_thread_self ());

  /* Can't do this until we have guaranteed the syscall will succeed.  */
  if (args->osap)
    {
      struct sigaction *osap;
#ifdef COMPAT_43
      if (compat_43)
	{
	  osap = alloca (sizeof (struct sigaction));
	  bcopy (&ss->actions[args->signo], osap, sizeof (struct sigaction));
	  osap->sa_flags ^= SA_RESTART;
	}
      else
	osap = &ss->actions[args->signo];
#else
      osap = &ss->actions[args->signo];
#endif      
      if (err = copyout (osap, args->osap, sizeof (struct sigaction)))
	{
	  mutex_unlock (&ss->lock);
	  return err;
	}
    }

  mutex_lock (&siglock);
  if (args->signo == SIGCHLD && mach_thread_self () == desig_thread)
    {
      /* Ick -- this doesn't belong here...  */
      if (sa.sa_flags & SA_NOCLDSTOP)
	proc_mark_nostopchild (procserver, 1);
      else
	proc_mark_nostopchild (procserver, 0);
    }
  mutex_unlock (&siglock);

  /* Clear ignored signals from the pending set.  */
  if (((args->signo == SIGCONT || args->signo == SIGIO || args->signo == SIGURG
	|| args->signo == SIGCHLD || args->signo == SIGWINCH) 
       && sa.sa_handler == SIG_DFL)
      || sa.sa_handler == SIG_IGN)
    ss->pending &= ~sigmask (args->signo);
  
  ss->actions[args->signo] = sa;

  ss->actions[args->signo].sa_mask &= ~CANT_BLOCK;

  mutex_unlock (&ss->lock);
  return 0;
}


#ifdef COMPAT_43
int
sigvec_compat43 (void *ap, int *ret1, int *ret2)
{
  return sigaction1 (ap, ret1, ret2, 1);
}

int
sigaction (void *ap, int *ret1, int *ret2)
{
  return sigaction1 (ap, ret1, ret2, 0);
}
#endif


/* Special sync guarantees for kill of self ??? XXX */
/* SYSCALL: Send a signal to a process or process group using proc_kill. */
int
kill (void *ap, int *ret1, int *ret2)
{
  struct
    {
      pid_t pid;
      int signo;
    }
  *args = ap;
  
  return h2ae (proc_kill (procserver, args->pid, args->signo));
}

#ifdef COMPAT_43
/* SYSCALL: Send a signal to a process group using proc_kill.  */
int
killpg_compat43 (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      pid_t pgrp;
      int signo;
    }
  *args = ap;

  if (args->pgrp <= 0)
    return EINVAL;
  
  return h2ae (proc_kill (procserver, -args->pgrp, args->signo));
}
#endif

#ifdef COMPAT_43
/* SYSCALL: Add signals to the current set of blocked signals.  */
int
sigblock_compat43 (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int mask;
    }
  *args = ap;
  struct sigstate *ss;
  
  ss = thread_to_sigstate (mach_thread_self ());

  *ret1 = ss->mask;
  ss->mask |= (args->mask & ~CANT_BLOCK);

  mutex_unlock (&ss->lock);
  return 0;
}

/* SYSCALL: Change the current signal mask.  */
int
sigsetmask_compat43 (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int mask;
    }
  *args = ap;
  struct sigstate *ss;

  ss = thread_to_sigstate (mach_thread_self ());
  *ret1 = ss->mask;
  ss->mask = (args->mask & ~CANT_BLOCK);
  mutex_unlock (&ss->lock);
  return 0;
}
#endif

/* SYSCALL: Set/change the signal mask. */
int
sigprocmask (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      int how;
      int bits;
    }
  *args = ap;
  struct sigstate *ss;
  
  ss = thread_to_sigstate (mach_thread_self ());
  *ret1 = ss->mask;
  switch (args->how)
    {
    case SIG_BLOCK:
      ss->mask |= args->bits;
      break;
    case SIG_UNBLOCK:
      ss->mask &= ~args->bits;
      break;
    case SIG_SETMASK:
      ss->mask = args->bits;
      break;
    default:
      mutex_unlock (&ss->lock);
      return EINVAL;
    }
  ss->mask &= ~CANT_BLOCK;
  mutex_unlock (&ss->lock);
  return 0;
}

/* SYSCALL: Change the current signal stack.  */
int
sigstack (void *ap, int *ret1, int *ret2)
{
  struct
    {
      struct sigstack *ssp;
      struct sigstack *oss;
    }
  *args = ap;
  struct sigstack sstk;
  struct sigstate *ss;
  int err;

  if (err = copyin (args->ssp, &sstk, sizeof (struct sigstack)))
    return err;

  ss = thread_to_sigstate (mach_thread_self ());
  if (args->oss)
    if (err = copyout (&ss->sigstack, args->oss, sizeof (struct sigstack)))
      {
	mutex_unlock (&ss->lock);
	return err;
      }
  ss->sigstack = sstk;
  mutex_unlock (&ss->lock);
  return 0;
}


/* Abort all the emulator threads' rpcs.  All threads but ourselves
   are stopped.  */
void
abort_rpcs (int restart)
{
  int state[STATE_SIZE];
  thread_t ourthread = mach_thread_self ();
  int i;
  thread_t *list;
  u_int nthreads;
  
  task_threads (mach_task_self (), &list, &nthreads);
  for (i = 0; i < nthreads; i++)
    {
      if (list[i] != ourthread)
	{
	  fetch_state (list[i], state);
	  if (thread_in_emulator (state))
	    abort_thread_rpc (list[i], state, restart);
	}
      mach_port_deallocate (mach_task_self (), list[i]);
    }
}

/* SYSCALL: Suspend the process until a signal arrives, atomically 
   setting and unsetting the signal mask.  */
int
sigsuspend (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int newmask;
    }
  *args = ap;
  struct sigstate *ss;
  int omask;
  int sigcheck;
  int signo;
  
  ss = thread_to_sigstate (mach_thread_self ());
  omask = ss->mask;
  ss->mask = args->newmask & ~CANT_BLOCK;

  for (;;)
    {
      sigcheck = ss->pending & ~ss->mask;
      if (!sigcheck)
	{
	  condition_wait (&ss->arrived, &ss->lock);
	  continue;
	}
      signo = ffs (sigcheck);
      ss->pending &= ~sigmask (signo);

      switch (get_sigact (ffs (sigcheck), ss, orphaned))
	{
	case core:
	case term:
	case handle:
	  /* Take the signal */
	  ss->nextsig = signo;
	  ss->omask = omask;
	  ss->useomask = 1;
	  mutex_unlock (&ss->lock);
	  return EINTR;

	case ignore:
	  break;
	  
	case stop:
	  /* Stop right here */
	  proc_dostop (procserver, mach_thread_self ());
	  thread_resume (alix_sigthread);
	  stopped = 1;
	  abort_rpcs (1);
	  proc_markstop (procserver, signo);
	  mutex_unlock (&ss->lock);
	  thread_suspend (mach_thread_self ());
	  mutex_lock (&ss->lock);
	  break;
	}
    }      
}
  
/* SYSCALL: Specify the thread to take externally generated signals.  */
int
sigsetthread (void *ap, int *ret1, int *ret2)
{
  struct
    {
      thread_t thread;
    }
  *args = ap;

  mach_port_mod_refs (mach_task_self (), args->thread,
		      MACH_PORT_RIGHT_SEND, 1);
  mutex_lock (&siglock);
  mach_port_deallocate (mach_task_self (), desig_thread);
  desig_thread = args->thread;
  mutex_unlock (&siglock);
  return 0;
}

/* Find out what to do when a signal has arrived.  Take appropriate
   miscellaneous actions at this time.  */
static enum action_type
get_sigact (int signo,
	    struct sigstate *ss,
	    int isorphaned)
{
  enum action_type act;
  
  switch ((int)ss->actions[signo].sa_handler)
    {
    case SIG_DFL:
      switch (signo)
	{
	case SIGTTIN: case SIGTTOU:
	case SIGSTOP: case SIGTSTP:
	  ss->pending &= ~sigmask (SIGCONT);
	  act = stop;
	  break;

	case SIGCONT:
	  ss->pending &= ~(sigmask (SIGSTOP) | sigmask (SIGTSTP)
			     | sigmask (SIGTTIN) | sigmask (SIGTTOU));
	  /* fall through */
	case SIGIO: case SIGURG: case SIGCHLD: case SIGWINCH:
	  act = ignore;
	  break;

	case SIGQUIT: case SIGILL: case SIGTRAP: case SIGIOT: case SIGEMT:
	case SIGFPE: case SIGBUS: case SIGSEGV: case SIGSYS:
	  act = core;
	  break;

	default:
	  act = term;
	  break;
	}
      break;
    case SIG_IGN:
      act = ignore;
      break;
    default:
      act = handle;
      break;
    }
  return act;
}
  

/* Called by the proc server when a signal has arrived and we should
   deal with it.  */
error_t
sig_post (sigthread_t st,
	  int signo,
	  int isorphaned,
	  int cttykill,
	  int *willstop)
{
  int oldmask, exitstatus;
  int state[STATE_SIZE];
  struct sigstate *ss;
  enum action_type act;

  ss = thread_to_sigstate (desig_thread);
  
  act = get_sigact (signo, ss, isorphaned);
  if (orphaned && (signo == SIGTTIN || signo == SIGTTOU) && act == stop)
    {
      signo = SIGKILL;
      act = term;
    }
      
  /* Handle receipt of a blocked signal */
  if (((sigmask (signo) & ss->mask) && act != ignore)
      || (signo != SIGKILL && stopped))
    {
      if (cttykill)
	{
	  *willstop = IGNBLK;
	  return POSIX_SUCCESS;
	}
      ss->pending |= sigmask (signo);
      act = ignore;
    }

  switch (act)
    {
    default:
      panic ("sig_post");
    case stop:
      return_sig_post (WILLSTOP);
      proc_dostop (procserver, mach_thread_self ());
      abort_rpcs (1);
      proc_markstop (procserver, signo);
      mutex_unlock (&ss->lock);
      return MIG_NO_REPLY;
      
    case ignore:
      mutex_unlock (&ss->lock);
      *willstop = IGNBLK;
      return POSIX_SUCCESS;
      
    case core:
    case term:
      return_sig_post (CATCH);
      exitstatus = W_EXITCODE(0, signo);
      proc_dostop (procserver, mach_thread_self ());
      abort_rpcs (0);
      if (act == core && write_corefile ())
	exitstatus |= WCOREFLAG;
      mutex_unlock (&ss->lock);
      proc_exit (procserver, exitstatus);
      task_terminate (mach_task_self ());
      return MIG_NO_REPLY;	/* hee hee */

    case handle:
      return_sig_post (CATCH);
      thread_suspend (desig_thread);
      fetch_state (desig_thread, state);
      
      if (thread_in_emulator (state))
	{
	  abort_thread_rpc (desig_thread, state,
			    ss->actions[signo].sa_flags & SA_RESTART);
	      
	  /* The signal will be picked up by the thread later.  */
	  ss->pending |= sigmask (signo);
	  /* Notify any waiting sigsuspend */
	  condition_broadcast (&ss->arrived);
	}
      else
	{
	  oldmask = ss->mask;
	  ss->mask = ss->actions[signo].sa_mask;
	  zap_thread_sighandler (desig_thread, state, signo,
				 (int)ss->actions[signo].sa_handler, 
				 ss->actions[signo].sa_flags, oldmask,
				 &ss->sigstack);
	}
      thread_resume (desig_thread);
      mutex_unlock (&ss->lock);
      return MIG_NO_REPLY;
    }
}

/* Called by the exception handler to send a signal.  */
void
internal_sig_post (thread_t faultthread,
		   int signo)
{
  int oldmask;
  int state[STATE_SIZE];
  struct sigstate *ss;
  int exitstatus;
  enum action_type act;
  
  ss = thread_to_sigstate (faultthread);
  
  act = get_sigact (signo, ss, orphaned);
  if (((sigmask (signo) & ss->mask) && act != ignore)
      || (signo != SIGKILL && stopped))
    {
      ss->pending |= sigmask (signo);
      act = ignore;
    }

  switch (act)
    {
    default:
      panic ("internal_sig_post");
    case stop:
      stopped = 1;
      proc_dostop (procserver, mach_thread_self ());
      abort_rpcs (1);
      proc_markstop (procserver, signo);
      mutex_unlock (&ss->lock);
      return;
      
    case ignore:
      mutex_unlock (&ss->lock);
      return;
      
    case core:
    case term:
      exitstatus = W_EXITCODE (0, signo);
      proc_dostop (procserver, mach_thread_self ());
      abort_rpcs (0);
      if (act == core && write_corefile ())
	exitstatus |= WCOREFLAG;
      mutex_unlock (&ss->lock);
      proc_exit (procserver, exitstatus);
      task_terminate (mach_task_self ());
      return;			/* hee hee */

    case handle:
      fetch_state (faultthread, state);
      if (thread_in_emulator (state))
	emulator_fault(signo);
      else
	{
	  oldmask = ss->mask;
	  ss->mask = ss->actions[signo].sa_mask;
	  thread_suspend (faultthread);
	  zap_thread_sighandler (faultthread, state, signo,
				 (int)ss->actions[signo].sa_handler,
				 ss->actions[signo].sa_flags, oldmask,
				 &ss->sigstack);
	  thread_resume (faultthread);
	  mutex_unlock (&ss->lock);
	  return;
	}
    }
}

/* Called by syscall dispatch to cause a SIGSYS to be sent.  */
void
post_sigsys ()
{
  struct sigstate *ss;
  ss = thread_to_sigstate (mach_thread_self ());
  ss->pending |= sigmask (SIGSYS);
  mutex_unlock (&ss->lock);
}

/* Called by the syscall handler when we leave a syscall.  */
void
check_sigs (int *regs)
{
  int sigcheck;
  int signo;
  int exitstatus;
  int oldmask;
  struct sigstate *ss;
  enum action_type act;

  ss = thread_to_sigstate (mach_thread_self ());
  
  if (ss->nextsig)
    {
      signo = ss->nextsig;
      ss->nextsig = 0;
    }
  else
    {  
      sigcheck = ss->pending & ~ss->mask;
      if (!sigcheck)
	{
	  mutex_unlock (&ss->lock);
	  return;
	}

      signo = ffs (sigcheck);
      
      ss->pending &= ~(sigmask (signo));
    }
  
  if (!pidvalid)
    fetchpids ();

  switch (act = get_sigact (signo, ss, orphaned))
    {
    case stop:
      stopped = 1;
      proc_dostop (procserver, mach_thread_self ());
      thread_resume (alix_sigthread);
      abort_rpcs (1);
      proc_markstop (procserver, signo);
      mutex_unlock (&ss->lock);
      thread_suspend (mach_thread_self ());

      /* And now we can just return the syscall normally.  */
      return;

    case ignore:
      mutex_unlock (&ss->lock);
      return;
      
    case core:
    case term:
      exitstatus = W_EXITCODE (0, signo);
      proc_dostop (procserver, mach_thread_self ());
      abort_rpcs (0);
      if (act == core && write_corefile ())
	exitstatus |= WCOREFLAG;
      mutex_unlock (&ss->lock);
      proc_exit (procserver, exitstatus);
      task_terminate (mach_task_self ());
      return;			/* yeah, right */
      
    case handle:
      if (ss->useomask)
	oldmask = ss->omask;
      else
	oldmask = ss->mask;
      ss->useomask = 0;
      ss->mask |= sigmask (signo) | ss->actions[signo].sa_mask;
      set_sig_handler (regs, signo, (int)ss->actions[signo].sa_handler,
		       ss->actions[signo].sa_flags, oldmask,
		       &ss->sigstack);
      mutex_unlock (&ss->lock);
      return;
    }
}

/* Called by interruptible rpc stubs before they send an RPC. 
   Don't actually do a signal delivery, just report that one will
   have to happen so that the syscall can unwind.  */
int
rpc_check_sigs ()
{
  int sigcheck;
  int signo;
  struct sigstate *ss;

  ss = thread_to_sigstate (mach_thread_self ());
  
  if (ss->nextsig)
    signo = ss->nextsig;
  else
    {
      sigcheck = ss->pending & ~ss->mask;
      if (!sigcheck)
	{
	  mutex_unlock (&ss->lock);
	  return 0;
	}

      signo = ffs (sigcheck);
      ss->pending &= ~(sigmask (signo));
      ss->nextsig = signo;
    }

  switch (get_sigact (signo, ss, orphaned))
    {
    default:
      panic ("rpc_check_sigs");
    case core:
    case term:
    case stop:
      mutex_unlock (&ss->lock);
      return RESTART;

    case ignore:
      mutex_unlock (&ss->lock);
      return 0;

    case handle:
      if (ss->actions[signo].sa_flags & SA_RESTART)
	{
	  mutex_unlock (&ss->lock);
	  return RESTART;
	}
      else
	{
	  mutex_unlock (&ss->lock);
	  return POSIX_EINTR;
	}
    }
}


static struct mutex listlock;
struct sigstate *statelist;

struct sigstate *
thread_to_sigstate(thread_t thread)
{
  struct sigstate *ss;
  int i;
  int foo;
  
  mutex_lock (&listlock);
  
  for (ss = statelist; ss; ss = ss->next)
    if (ss->thread == thread)
      {  
	mutex_lock (&ss->lock);
	mutex_unlock (&listlock);
	return ss;
      }
  
  ss = malloc (sizeof (struct sigstate));
  mutex_init (&ss->lock);
  condition_init (&ss->arrived);
  mach_port_mod_refs (mach_task_self (), thread, MACH_PORT_RIGHT_SEND, 1);
  ss->thread = thread;
  ss->next = statelist;
  statelist = ss;
  mach_port_request_notification (mach_task_self (), thread,
				  MACH_NOTIFY_DEAD_NAME, 1, sigthread_port,
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &foo);
  mutex_lock (&ss->lock);
  mutex_unlock (&listlock);

  ss->mask = 0;
  ss->pending = 0;
  for (i = 0; i < NSIG; i++)
    ss->actions[i].sa_handler = SIG_DFL;
  ss->sigstack.ss_onstack = 0;
  ss->sigstack.ss_sp = 0;
  return ss;
}

/* Called when we get a nosenders notification on a thread. */

void
thread_died (thread_t thread)
{
  struct sigstate *ss, *prev;
  
  /* What if this is the designated thread??? */

  mutex_lock (&listlock);
  
  for (ss = statelist, prev = 0; ss; prev = ss, ss = ss->next)
    if (ss->thread == thread)
      {
	mutex_lock (&prev->lock);
	mutex_lock (&ss->lock);

	prev->next = ss->next;

	mutex_unlock (&listlock);
	mutex_unlock (&prev->lock);
	
	mach_port_mod_refs (mach_task_self (), thread, MACH_PORT_RIGHT_SEND,
			    -2);
	free (ss);
	mutex_unlock (&ss->lock);
      }
}

void
return_sig_post (int foo)
{
}

int
write_corefile ()
{
  return 0;
}
