/* This file handles signals, which are asynchronous events and are generally
 * a messy and unpleasant business.  Signals can be generated by the KILL
 * system call, or from the keyboard (SIGINT) or from the clock (SIGALRM).
 * In all cases control eventually passes to check_sig() to see which processes
 * can be signalled.  The actual signalling is done by sig_proc().
 *
 * The entry points into this file are:
 *   do_signal:	perform the SIGNAL system call
 *   do_kill:	perform the KILL system call
 *   do_ksig:	accept a signal originating in the kernel (e.g., SIGINT)
 *   sig_proc:	interrupt or terminate a signalled process
 *   do_alarm:	perform the ALARM system call by calling set_alarm()
 *   set_alarm:	tell the clock task to start or stop a timer
 *   do_pause:	perform the PAUSE system call
 *   unpause:	check to see if process is suspended on anything
 */

#include "../h/const.h"
#include "../h/type.h"
#include "../h/callnr.h"
#include "../h/com.h"
#include "../h/error.h"
#include "../h/signal.h"
#include "../h/stat.h"
#include "const.h"
#include "glo.h"
#include "mproc.h"
#include "param.h"

#define DUMP_SIZE	 256	/* buffer size for core dumps */
#define CORE_MODE	0777	/* mode to use on core image files */
#define DUMPED          0200	/* bit set in status when core dumped */

PRIVATE message m_sig;

/*===========================================================================*
 *				do_signal				     *
 *===========================================================================*/
PUBLIC int do_signal()
{
/* Perform the signal(sig, func) call by setting bits to indicate that a signal
 * is to be caught or ignored.
 */

  int mask;

  if (sig < 1 || sig > NR_SIGS) return(EINVAL);
  if (sig == SIGKILL) return(OK);	/* SIGKILL may not ignored/caught */
  mask = 1 << (sig - 1);	/* singleton set with 'sig' bit on */

  /* All this func does is set the bit maps for subsequent sig processing. */
  if (func == SIG_IGN) {
	mp->mp_ignore |= mask;
	mp->mp_catch &= ~mask;
  } else if (func == SIG_DFL) {
	mp->mp_ignore &= ~mask;
	mp->mp_catch &= ~mask;
  } else {
	mp->mp_ignore &= ~mask;
	mp->mp_catch |= mask;
	mp->mp_func = func;
  }
  return(OK);
}


/*===========================================================================*
 *				do_kill					     *
 *===========================================================================*/
PUBLIC int do_kill()
{
/* Perform the kill(pid, kill_sig) system call. */

  return check_sig(pid, kill_sig, mp->mp_effuid);
}


/*===========================================================================*
 *				do_ksig					     *
 *===========================================================================*/
PUBLIC int do_ksig()
{
/* Certain signals, such as segmentation violations and DEL, originate in the
 * kernel.  When the kernel detects such signals, it sets bits in a bit map.
 * As soon is MM is awaiting new work, the kernel sends MM a message containing
 * the process slot and bit map.  That message comes here.  The File System
 * also uses this mechanism to signal writing on broken pipes (SIGPIPE).
 */

  register struct mproc *rmp;
  int i, proc_id, proc_nr, id;
  unshort sig_map;		/* bits 0 - 15 for sigs 1 - 16 */

  /* Only kernel and FS may make this call. */
  if (who != HARDWARE && who != FS_PROC_NR) return(EPERM);

  proc_nr = mm_in.PROC1;
  rmp = &mproc[proc_nr];
  if ( (rmp->mp_flags & IN_USE) == 0 || (rmp->mp_flags & HANGING) ) return(OK);
  proc_id = rmp->mp_pid;
  sig_map = (unshort) mm_in.SIG_MAP;
  mp = &mproc[0];		/* pretend kernel signals are from MM */
  mp->mp_procgrp = rmp->mp_procgrp;	/* get process group right */

  /* Stack faults are passed from kernel to MM as pseudo-signal 16. */
  if (sig_map == 1 << (STACK_FAULT - 1)) {
	stack_fault(proc_nr);
	return(OK);
  }

  /* Check each bit in turn to see if a signal is to be sent.  Unlike
   * kill(), the kernel may collect several unrelated signals for a process
   * and pass them to MM in one blow.  Thus loop on the bit map. For SIGINT
   * and SIGQUIT, use proc_id 0, since multiple processes may have to signalled.
   */
  for (i = 0; i < NR_SIGS; i++) {
	id= (i+1 == SIGINT || i+1 == SIGQUIT ? 0 : proc_id);
	if (i + 1 == SIGKILL) id = -1;	/* simulate kill -1 9 */
	if ( (sig_map >> i) & 1) check_sig(id, i + 1, SUPER_USER);
  }

  dont_reply = TRUE;		/* don't reply to the kernel */
  return(OK);
}


/*===========================================================================*
 *				check_sig				     *
 *===========================================================================*/
PRIVATE int check_sig(proc_id, sig_nr, send_uid)
int proc_id;			/* pid of process to signal, or 0 or -1 */
int sig_nr;			/* which signal to send (1-16) */
uid send_uid;			/* identity of process sending the signal */
{
/* Check to see if it is possible to send a signal.  The signal may have to be
 * sent to a group of processes.  This routine is invoked by the KILL system
 * call, and also when the kernel catches a DEL or other signal. SIGALRM too.
 */

  register struct mproc *rmp;
  int count, send_sig;
  unshort mask;
  extern unshort core_bits;

  if (sig_nr < 1 || sig_nr > NR_SIGS) return(EINVAL);
  count = 0;			/* count # of signals sent */
  mask = 1 << (sig_nr - 1);

  /* Search the proc table for processes to signal.  Several tests are made:
   * 	- if proc's uid != sender's, and sender is not superuser, don't signal
   *	- if specific process requested (i.e., 'procpid' > 0, check for match
   *	- if a process has already exited, it can't receive signals
   *	- if 'proc_id' is 0 signal everyone in same process group except caller
   */
  for (rmp = &mproc[INIT_PROC_NR + 1]; rmp < &mproc[NR_PROCS]; rmp++ ) {
	if ( (rmp->mp_flags & IN_USE) == 0) continue;
	send_sig = TRUE;	/* if it's FALSE at end of loop, don't signal */
	if (send_uid != rmp->mp_effuid && send_uid != SUPER_USER)send_sig=FALSE;
	if (proc_id > 0 && proc_id != rmp->mp_pid) send_sig = FALSE;
	if (rmp->mp_flags & HANGING) send_sig = FALSE;   /*don't wake the dead*/
	if (proc_id == 0 && mp->mp_procgrp != rmp->mp_procgrp) send_sig = FALSE;
	if (send_uid == SUPER_USER && proc_id == -1) send_sig = TRUE;

	/* SIGALARM is a little special.  When a process exits, a clock signal
	 * can arrive just as the timer is being turned off.  Also, turn off
	 * ALARM_ON bit when timer goes off to keep it accurate.
	 */
	if (sig_nr == SIGALRM) {
		if ( (rmp->mp_flags & ALARM_ON) == 0) continue;
		rmp->mp_flags &= ~ALARM_ON;
	}

	if (send_sig == FALSE || rmp->mp_ignore & mask) continue;
	count++;

	/* Send the signal or kill the process, possibly with core dump. */
	sig_proc(rmp, sig_nr);

	/* If process is hanging on PAUSE, WAIT, tty, pipe, etc. release it. */
	unpause(rmp - mproc);	/* check to see if process is paused */
	if (proc_id > 0) break;	/* only one process being signalled */
  }

  /* If the calling process has killed itself, don't reply. */
  if ((mp->mp_flags & IN_USE) == 0 || (mp->mp_flags & HANGING))dont_reply =TRUE;
  return(count > 0 ? OK : ESRCH);
}


/*===========================================================================*
 *				sig_proc				     *
 *===========================================================================*/
PUBLIC sig_proc(rmp, sig_nr)
register struct mproc *rmp;	/* pointer to the process to be signalled */
int sig_nr;			/* signal to send to process (1-16) */
{
/* Send a signal to a process.  Check to see if the signal is to be caught.
 * If so, the pc, psw, and signal number are to be pushed onto the process'
 * stack.  If the stack cannot grow or the signal is not to be caught, kill
 * the process.
 */

  unshort mask;
  int core_file;
  vir_bytes new_sp;
  extern unshort core_bits;

  if ( (rmp->mp_flags & IN_USE) == 0) return;	/* if already dead forget it */
  mask = 1 << (sig_nr - 1);
  if (rmp->mp_catch & mask) {
	/* Signal should be caught. */
	rmp->mp_catch &= ~mask;		/* disable further signals */
	sys_getsp(rmp - mproc, &new_sp);
	new_sp -= SIG_PUSH_BYTES;
	if (adjust(rmp, rmp->mp_seg[D].mem_len, new_sp) == OK) {
		sys_sig(rmp - mproc, sig_nr, rmp->mp_func);
		return;		/* successful signal */
	}
  }

  /* Signal should not or cannot be caught.  Take default action. */
  core_file = ( core_bits >> (sig_nr - 1 )) & 1;
  rmp->mp_sigstatus = (char) sig_nr;
  if (core_file) dump_core(rmp); /* dump core */
  mm_exit(rmp, 0);		/* terminate process */
}


/*===========================================================================*
 *				do_alarm				     *
 *===========================================================================*/
PUBLIC int do_alarm()
{
/* Perform the alarm(seconds) system call. */

  register int r;
  unsigned sec;

  sec = (unsigned) seconds;
  r = set_alarm(who, sec);
  return(r);
}


/*===========================================================================*
 *				set_alarm				     *
 *===========================================================================*/
PUBLIC int set_alarm(proc_nr, sec)
int proc_nr;			/* process that wants the alarm */
unsigned sec;			/* how many seconds delay before the signal */
{
/* This routine is used by do_alarm() to set the alarm timer.  It is also
 * to turn the timer off when a process exits with the timer still on.
 */

  int remaining;

  m_sig.m_type = SET_ALARM;
  m_sig.CLOCK_PROC_NR = proc_nr;
  m_sig.DELTA_TICKS = HZ * sec;
  if (sec != 0)
	mproc[proc_nr].mp_flags |= ALARM_ON;	/* turn ALARM_ON bit on */
  else
	mproc[proc_nr].mp_flags &= ~ALARM_ON;	/* turn ALARM_ON bit off */

  /* Tell the clock task to provide a signal message when the time comes. */
  if (sendrec(CLOCK, &m_sig) != OK) panic("alarm er", NO_NUM);
  remaining = (int) m_sig.SECONDS_LEFT;
  return(remaining);
}


/*===========================================================================*
 *				do_pause				     *
 *===========================================================================*/
PUBLIC int do_pause()
{
/* Perform the pause() system call. */

  mp->mp_flags |= PAUSED;	/* turn on PAUSE bit */
  dont_reply = TRUE;
  return(OK);
}


/*===========================================================================*
 *				unpause					     *
 *===========================================================================*/
PUBLIC unpause(pro)
int pro;			/* which process number */
{
/* A signal is to be sent to a process.  It that process is hanging on a
 * system call, the system call must be terminated with EINTR.  Possible
 * calls are PAUSE, WAIT, READ and WRITE, the latter two for pipes and ttys.
 * First check if the process is hanging on PAUSE or WAIT.  If not, tell FS,
 * so it can check for READs and WRITEs from pipes, ttys and the like.
 */

  register struct mproc *rmp;

  rmp = &mproc[pro];

  /* Check to see if process is hanging on PAUSE call. */
  if ( (rmp->mp_flags & PAUSED) && (rmp->mp_flags & HANGING) == 0) {
	rmp->mp_flags &= ~PAUSED;	/* turn off PAUSED bit */
	reply(pro, EINTR, 0, NIL_PTR);
	return;
  }

  /* Check to see if process is hanging on a WAIT call. */
  if ( (rmp->mp_flags & WAITING) && (rmp->mp_flags & HANGING) == 0) {
	rmp->mp_flags &= ~ WAITING;	/* turn off WAITING bit */
	reply(pro, EINTR, 0, NIL_PTR);
	return;
  }

  /* Process is not hanging on an MM call.  Ask FS to take a look. */
  tell_fs(UNPAUSE, pro, 0, 0);

  return;
}


/*===========================================================================*
 *				dump_core				     *
 *===========================================================================*/
PRIVATE dump_core(rmp)
register struct mproc *rmp;	/* whose core is to be dumped */
{
/* Make a core dump on the file "core", if possible. */

  struct stat s_buf, d_buf;
  char buf[DUMP_SIZE];
  int i, r, s, er1, er2, slot;
  vir_bytes v_buf;
  long len, a, c, ct, dest;
  struct mproc *xmp;
  extern char core_name[];


  /* Change to working directory of dumpee. */
  slot = rmp - mproc;
  tell_fs(CHDIR, slot, 0, 0);

  /* Can core file be written? */
  if (rmp->mp_realuid != rmp->mp_effuid) {
	tell_fs(CHDIR, 0, 1, 0);	/* go back to MM's directory */
	return;
  }
  xmp = mp;			/* allowed() looks at 'mp' */
  mp = rmp;
  r = allowed(core_name, &s_buf, W_BIT);	/* is core_file writable */
  s = allowed(".", &d_buf, W_BIT);	/* is directory writable? */
  mp = xmp;
  if (r >= 0) close(r);
  if (s >= 0) close(s);
  if (rmp->mp_effuid == SUPER_USER) r = 0;	/* su can always dump core */

  if (s >= 0 && (r >= 0 || r == ENOENT)) {
	/* Either file is writable or it doesn't exist & dir is writable */
	r = creat(core_name, CORE_MODE);
	tell_fs(CHDIR, 0, 1, 0);	/* go back to MM's own dir */
	if (r < 0) return;
	rmp->mp_sigstatus |= DUMPED;

	/* First loop through segments and write each length on core file. */
	for (i = 0; i < NR_SEGS; i++) {
		len = rmp->mp_seg[i].mem_len << CLICK_SHIFT;
		if (write(r, (char *) &len, sizeof len) < 0) {
			close(r);
			return;
		}
	}

	/* Now loop through segments and write the segments themselves out. */
	v_buf = (vir_bytes) buf;
	dest = (long) v_buf;
	for (i = 0; i < NR_SEGS; i++) {
		a = (phys_bytes) rmp->mp_seg[i].mem_vir << CLICK_SHIFT;
		c = (phys_bytes) rmp->mp_seg[i].mem_len << CLICK_SHIFT;

		/* Loop through a segment, dumping it. */
		while (c > 0) {
			ct = MIN(c, DUMP_SIZE);
			er1 = mem_copy(slot, i, a, MM_PROC_NR, D, dest, ct);
			er2 = write(r, buf, (int) ct);
			if (er1 < 0 || er2 < 0) {
				close(r);
				return;
			}
			a += ct;
			c -= ct;
		}
	}
  } else {
	tell_fs(CHDIR, 0, 1, 0);	/* go back to MM's own dir */
	close(r);
	return;
  }

  close(r);
}
