head	1.3;
access;
symbols
	version39-41:1.2;
locks;
comment	@ *  @;


1.3
date	92.08.09.20.57.07;	author amiga;	state Exp;
branches;
next	1.2;

1.2
date	92.07.04.19.19.51;	author mwild;	state Exp;
branches;
next	1.1;

1.1
date	92.05.14.19.55.40;	author mwild;	state Exp;
branches;
next	;


desc
@higher level signal management code
@


1.3
log
@import sysbase
@
text
@/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Id: kern_sig.c,v 1.2 1992/07/04 19:19:51 mwild Exp $
 *
 *  $Log: kern_sig.c,v $
 *  Revision 1.2  1992/07/04  19:19:51  mwild
 *  change to new ix_sleep() semantics
 *
 * Revision 1.1  1992/05/14  19:55:40  mwild
 * Initial revision
 *
 *
 *  Since the code originated from Berkeley, the following copyright
 *  header applies as well. The code has been changed, it's not the
 *  original Berkeley code!
 */

/*
 * Copyright (c) 1982, 1986, 1989 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution is only permitted until one year after the first shipment
 * of 4.4BSD by the Regents.  Otherwise, redistribution and use in source and
 * binary forms are permitted provided that: (1) source distributions retain
 * this entire copyright notice and comment, and (2) distributions including
 * binaries display the following acknowledgement:  This product includes
 * software developed by the University of California, Berkeley and its
 * contributors'' in the documentation or other materials provided with the
 * distribution and in all advertising materials mentioning features or use
 * of this software.  Neither the name of the University nor the names of
 * its contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	@@(#)kern_sig.c	7.23 (Berkeley) 6/28/90
 */

#define KERNEL
#include "ixemul.h"

#ifdef DEBUG
#define DP(a) kprintf a
#else
#define DP(a)
#endif

extern struct ExecBase *SysBase;

#include <wait.h>

#define	ttystopsigmask	(sigmask(SIGTSTP)|sigmask(SIGTTIN)|sigmask(SIGTTOU))
#define	stopsigmask	(sigmask(SIGSTOP)|ttystopsigmask)
#define defaultignmask	(sigmask(SIGCONT)|sigmask(SIGIO)|sigmask(SIGURG)| \
			sigmask(SIGCHLD)|sigmask(SIGWINCH)|sigmask(SIGINFO)|sigmask(SIGMSG))

void setsigvec (int sig, struct sigaction *sa);
void _psignal (struct Task *t, int sig);
void sig_exit (int code);

/*
 * Can process p send the signal signo to process q?
 */
#define CANSIGNAL(p, q, signo) (1)

int
sigaction (int sig, const struct sigaction *nsa, struct sigaction *osa)
{
  struct sigaction vec;
  register struct sigaction *sa;
  int bit, error;

  if (sig <= 0 || sig >= NSIG || sig == SIGKILL || sig == SIGSTOP)
    {
      errno = EINVAL;
      return -1;
    }

  sa = &vec;
  if (osa)
    {
      sa->sa_handler = u.u_signal[sig];
      sa->sa_mask = u.u_sigmask[sig];
      bit = sigmask(sig);
      sa->sa_flags = 0;
      if ((u.u_sigonstack & bit) != 0)
	sa->sa_flags |= SA_ONSTACK;

      if ((u.u_sigintr & bit) == 0)
	sa->sa_flags |= SA_RESTART;

      if (u.p_flag & SNOCLDSTOP)
	sa->sa_flags |= SA_NOCLDSTOP;

      *osa = *sa;
    }

  if (nsa)
    {
      *sa = *nsa;
      setsigvec(sig, sa);
    }
  
  return (0);
}

void
setsigvec (int sig, struct sigaction *sa)
{
  register int bit;

  bit = sigmask(sig);
  /*
   * Change setting atomically.
   */
  Disable();

  u.u_signal[sig] = sa->sa_handler;
  u.u_sigmask[sig] = sa->sa_mask &~ sigcantmask;

  if ((sa->sa_flags & SA_RESTART) == 0)
    u.u_sigintr |= bit;
  else
    u.u_sigintr &= ~bit;

  if (sa->sa_flags & SA_ONSTACK)
    u.u_sigonstack |= bit;
  else
    u.u_sigonstack &= ~bit;

  if (sig == SIGCHLD) 
    {
      if (sa->sa_flags & SA_NOCLDSTOP)
	u.p_flag |= SNOCLDSTOP;
      else
	u.p_flag &= ~SNOCLDSTOP;
    }

  /*
   * Set bit in p_sigignore for signals that are set to SIG_IGN,
   * and for signals set to SIG_DFL where the default is to ignore.
   * However, don't put SIGCONT in p_sigignore,
   * as we have to restart the process.
   */
  if (sa->sa_handler == SIG_IGN ||
      (bit & defaultignmask && sa->sa_handler == SIG_DFL)) 
    {
      u.p_sig &= ~bit;		/* never to be seen again */
      if (sig != SIGCONT)
	u.p_sigignore |= bit;	/* easier in _psignal */
      u.p_sigcatch &= ~bit;
    }
  else 
    {
      u.p_sigignore &= ~bit;
      if (sa->sa_handler == SIG_DFL)
	u.p_sigcatch &= ~bit;
      else
	u.p_sigcatch |= bit;
    }

  Enable();
}


/*
 * Manipulate signal mask.
 */

int
sigprocmask (int how, const sigset_t *mask, sigset_t *omask)
{
  if (omask)
    *omask = u.p_sigmask;

  if (mask)
    {
      Disable();

      switch (how) 
        {
        case SIG_BLOCK:
	  u.p_sigmask |= *mask &~ sigcantmask;
	  break;

        case SIG_UNBLOCK:
	  u.p_sigmask &= ~*mask;
	  break;

        case SIG_SETMASK:
	  u.p_sigmask = *mask &~ sigcantmask;
	  break;
	
        default:
	  errno = EINVAL;
	  goto err_ret;
        }

      Enable();
    }

  if (CURSIG (&u))
    setrun (FindTask (0));

  return 0;

err_ret:
  Enable ();
  return -1;
}

int
sigpending (sigset_t *sigs)
{
  *sigs = u.p_sig;
  return 0;
}

/*
 * Generalized interface signal handler, 4.3-compatible.
 * (included in amiga version, because I want to reduce the static part of the
 *  library to a minimum)
 */
/* ARGSUSED */
int
sigvec(int sig, const struct sigvec *nsv, struct sigvec *osv)
{
  struct sigvec vec;
  register struct sigvec *sv;
  struct user *p = &u;
  int bit, error;

  if (sig <= 0 || sig >= NSIG || sig == SIGKILL || sig == SIGSTOP)
    {
      *p->u_errno = EINVAL;
      return -1;
    }

  sv = &vec;
  if (osv) 
    {
      *(sig_t *)&sv->sv_handler = p->u_signal[sig];
      sv->sv_mask = p->u_sigmask[sig];
      bit = sigmask(sig);
      sv->sv_flags = 0;
      if ((p->u_sigonstack & bit) != 0)
	sv->sv_flags |= SV_ONSTACK;
      if ((p->u_sigintr & bit) != 0)
	sv->sv_flags |= SV_INTERRUPT;
      if (p->p_flag & SNOCLDSTOP)
	sv->sv_flags |= SA_NOCLDSTOP;
      *osv = *sv;
    }

  if (nsv) 
    {
      *sv = *nsv;
      sv->sv_flags ^= SA_RESTART;	/* opposite of SV_INTERRUPT */
      setsigvec(sig, (struct sigaction *)sv);
    }

  return (0);
}

sigset_t
sigblock (sigset_t mask)
{
  sigset_t result;

  Disable();
  result = u.p_sigmask;
  u.p_sigmask |= mask &~ sigcantmask;
  (void) Enable();

  errno = 0;
  return result;
}

sigset_t
sigsetmask(sigset_t mask)
{
  sigset_t result;
  struct user *p = &u;

  Disable();
  result = u.p_sigmask;
  u.p_sigmask = mask &~ sigcantmask;
  Enable();

  if (CURSIG (p))
    setrun (FindTask (0));

  errno = 0;
  return result;
}

/*
 * Suspend process until signal, providing mask to be set
 * in the meantime. 
 */
/* ARGSUSED */
int
sigsuspend (const sigset_t *mask)
{
  struct user *p = &u;
  int rc;

#if 0
  /* clear the signal, which might already be set... */
  SetSignal (0, 1 << p->u_sleep_sig);
#endif

#ifdef DEBUG
  {
    if (SysBase->TDNestCnt >= 0)
      {
        DP(("PANIC!! sigsuspend called inside Forbid!!\n"));
        while (1);
      }
    if (SysBase->IDNestCnt >= 0)
      {
        DP(("PANIC!! sigsuspend called inside Disable!!\n"));
	while (1);
      }
  }
#endif

  /*
   * When returning from sigpause, we want
   * the old mask to be restored after the
   * signal handler has finished.  Thus, we
   * save it here and mark the proc structure
   * to indicate this (should be in u.).
   */

  Disable ();
  p->u_oldmask = p->p_sigmask;
  p->p_flag |= SOMASK;
  p->p_sigmask = *mask &~ sigcantmask;

/*  (void) tsleep((caddr_t)&u, PPAUSE | PCATCH, "pause", 0); */

  /* NOTE: we have to specify SIGBREAKF_CTRL_C here, as the OS doesn't seem
   *       to reschedule our task, if it receives a signal it isn't waiting
   *       for. If SIGINT is ignored, then this will jump back into the Wait,
   *	   if not, we're leaving correctly, since we waited for a signal
   *	   that now occured (lucky we, the OS tests the Recvd-field before
   *	   tc_Launch has a chance to reset it ;-))
   */

#if 0
  if (CURSIG (p))
    setrun (FindTask (0));
  else
    Wait ((1 << p->u_sleep_sig) | SIGBREAKF_CTRL_C);
#else
DP(("sigsuspend: going to sleep\n"));
  while (ix_sleep (p, "sigsuspend") == 0)
    DP(("  ignoring masked out SIGINT\n"));
#endif
  Enable ();

  DP(("sigsuspend-setrun: state = %ld, sig = $%lx, mask = $%lx, i = %ld\n",
      p->p_stat, p->p_sig, p->p_sigmask, CURSIG(p)));
  setrun (FindTask (0));

  p->p_sigmask = p->u_oldmask;

  if (CURSIG (p))
    setrun (FindTask (0));

  /* always return EINTR rather than ERESTART... */
  errno = EINTR;
  return -1;
}


int
sigpause (sigset_t mask)
{
  return sigsuspend (&mask);
}


/* ARGSUSED */
int
sigstack(const struct sigstack *nss, struct sigstack *oss)
{
  if (oss) *oss = u.u_sigstack;
  if (nss) u.u_sigstack = *nss;

  return 0;
}


/*
 * Initialize signal state for process 0;
 * set to ignore signals that are ignored by default.
 */
void
siginit(struct user *p)
{
  p->p_sigignore = defaultignmask &~ sigmask(SIGCONT);
}

/*
 * This looks for the process p, validates it, and checks, whether the process
 * is currently using our signal mechanism (checks magic cookie in struct user)
 */
static inline struct Task *
pfind (pid_t p)
{
  struct Task *t;
  
  if (p && !(p & 1))
    {
      t = (struct Task *) p;
      if (t->tc_Node.ln_Type == NT_TASK ||
          t->tc_Node.ln_Type == NT_PROCESS)
        {
          struct user *tu = (struct user *) t->tc_TrapData;
          if (tu && !((int)t->tc_TrapData & 1) && tu->u_ixbase == u.u_ixbase)
	    return t;
	}
    }
  else if (! p)
    return FindTask (0);

  return 0;
}

/* ARGSUSED */
int
kill(pid_t pid, int signo)
{
  register struct Task *t;

  if ((unsigned) signo >= NSIG)
    {
      errno = EINVAL;
      return -1;
    }

  if (pid >= 0)
    {
      /* kill single process */
      t = pfind(pid);
      if (t == 0)
        {
	  /* there is a small chance, if pid == 0, that we may send the signal
	   * as well. If signo == SIGINT, and pid refers to a valid Task, we send
	   * it a SIGBREAKF_CTRL_C */
	  if ((signo == SIGINT) && pid && !(pid & 1))
	    {
	      t = (struct Task *) pid;
	      if (t->tc_Node.ln_Type == NT_TASK ||
	          t->tc_Node.ln_Type == NT_PROCESS)
	        {
		  Signal (t, SIGBREAKF_CTRL_C);
		  return 0;
		}
	    }

          errno = ESRCH;
          return -1;
        }
	
      if (signo)
	_psignal(t, signo);
	
      return (0);
    }

  /* signalling process groups is not (yet) implemented */  
  errno = ESRCH;
  return -1;
}

/* ARGSUSED */
int
killpg(int pgid, int signo)
{
  if ((unsigned) signo >= NSIG)
    errno = EINVAL;
  else
    errno = ESRCH;

  /* signalling process groups is not (yet) implemented */  
  return -1;
}

/*
 * Send a signal caused by a trap to the current process.
 * If it will be caught immediately, deliver it with correct code.
 * Otherwise, post it normally.
 */
int
trapsignal(struct Task *t, int sig, unsigned code, void *addr)
{
  int mask;

  mask = sigmask(sig);
  if ((u.p_flag & STRC) == 0 && (u.p_sigcatch & mask) != 0 &&
      (u.p_sigmask & mask) == 0)
    {
      u.u_ru.ru_nsignals++;
      sendsig(t->tc_TrapData, u.u_signal[sig], sig, u.p_sigmask, code, addr);
      u.p_sigmask |= u.u_sigmask[sig] | mask;
      setrun (t);
    }
  else
    {
      u.u_code = code;	/* XXX for core dump/debugger */
      _psignal(t, sig);
    }
}

/*
 * Send the specified signal to the specified process.
 * Most signals do not do anything directly to a process;
 * they set a flag that asks the process to do something to itself.
 * Exceptions:
 *   o When a stop signal is sent to a sleeping process that takes the default
 *     action, the process is stopped without awakening it.
 *   o SIGCONT restarts stopped processes (or puts them back to sleep)
 *     regardless of the signal action (eg, blocked or ignored).
 * Other ignored signals are discarded immediately.
 */
void
_psignal(struct Task *t, int sig)	/* MAY be called in Supervisor/Interrupt  !*/
{
  register int s;
  register sig_t action;
  /* may be another process, so don't use u. here ! */
  struct user *p = (struct user *)t->tc_TrapData;
  int mask;

#if 0
  if ((unsigned)sig >= NSIG || sig == 0)
    ix_panic("psignal sig");
#endif

  mask = sigmask(sig);

  /*
   * If proc is traced, always give parent a chance.
   */
  if (p->p_flag & STRC)
    action = SIG_DFL;
  else 
    {

      /* NOTE AMIGA !
       * I can't allow for trap signals to be either ignored or masked out.
       * This would cause the trap to reoccur immediately again, resulting
       * in a deadly loop. So if such a signal gets here, it is converted
       * in a SIGILL, masked in, not ignored, not caught, that's it.
       */
      if (((mask & p->p_sigignore) || (mask & p->p_sigmask))
	  && (sig == SIGILL  || sig == SIGBUS || sig == SIGFPE || 
	      sig == SIGTRAP || sig == SIGEMT))
	{
	  sig = SIGILL;
	  mask = sigmask (sig);
	  p->p_sigignore &= ~mask;
	  p->p_sigmask   &= ~mask;
	  p->p_sigcatch  &= ~mask;
	  /* that's it, SIGILL is now reset to SIG_DFL, which will exit() */
	}

      /*
       * If the signal is being ignored,
       * then we forget about it immediately.
       * (Note: we don't set SIGCONT in p_sigignore,
       * and if it is set to SIG_IGN,
       * action will be SIG_DFL here.)
       */
     if (p->p_sigignore & mask)
	return;

     if (p->p_sigmask & mask)
	action = SIG_HOLD;
     else if (p->p_sigcatch & mask)
	action = SIG_CATCH;
     else
	action = SIG_DFL;
    }

  switch (sig) 
    {
    case SIGTERM:
      if ((p->p_flag&STRC) || action != SIG_DFL)
	break;
	/* FALLTHROUGH */

    case SIGKILL:
      break;

    case SIGCONT:
      p->p_sig &= ~stopsigmask;
      break;

    case SIGTSTP:
    case SIGTTIN:
    case SIGTTOU:
    case SIGSTOP:
      p->p_sig &= ~sigmask(SIGCONT);
      break;
    }
  p->p_sig |= mask;

  /*
   * Defer further processing for signals which are held,
   * except that stopped processes must be continued by SIGCONT.
   */
  if (action == SIG_HOLD && (sig != SIGCONT || p->p_stat != SSTOP))
    return;

  setrun(t);
out:
  ;
}

/*
 * If the current process has a signal to process (should be caught
 * or cause termination, should interrupt current syscall),
 * return the signal number.  Stop signals with default action
 * are processed immediately, then cleared; they aren't returned.
 * This is asked at least once each time a process enters the
 * system (though this can usually be done without actually
 * calling issig by checking the pending signal masks.)
 */
int
issig(struct user *p)	/* called in SUPERVISOR */
{
  register int sig, mask;

  for (;;)
    {
      mask = p->p_sig &~ p->p_sigmask;
      if (p->p_flag&SVFORK)
	mask &= ~stopsigmask;

      if (mask == 0)	 	/* no signal to send */
	return 0;

      sig = ffs((long)mask);
      mask = sigmask(sig);
      /*
       * We should see pending but ignored signals
       * only if STRC was on when they were posted.
       */
      if (mask & p->p_sigignore && (p->p_flag&STRC) == 0) 
        {
          p->p_sig &= ~mask;
	  continue;
	}

#if notyet
      if (p->p_flag&STRC && (p->p_flag&SVFORK) == 0) 
        {
	  /*
	   * If traced, always stop, and stay
	   * stopped until released by the parent.
	   */
	  p->p_xstat = sig;
	  _psignal(p->p_pptr, SIGCHLD);
	  do 
            {
	      stop(p);
	      swtch();
	    } 
          while (!procxmt(p) && p->p_flag&STRC);

	  /*
	   * If the traced bit got turned off,
	   * go back up to the top to rescan signals.
	   * This ensures that p_sig* and u_signal are consistent.
	   */
	  if ((p->p_flag&STRC) == 0)
	    continue;

	  /*
	   * If parent wants us to take the signal,
	   * then it will leave it in p->p_xstat;
	   * otherwise we just look for signals again.
	   */
	  p->p_sig &= ~mask;	/* clear the old signal */
	  sig = p->p_xstat;
	  if (sig == 0)
	    continue;

	  /*
	   * Put the new signal into p_sig.
	   * If signal is being masked,
	   * look for other signals.
	   */
	  mask = sigmask(sig);
	  p->p_sig |= mask;
	  if (p->p_sigmask & mask)
	    continue;
	}
#endif

      /*
       * Decide whether the signal should be returned.
       * Return the signal's number, or fall through
       * to clear it from the pending mask.
       */
      switch ((int)p->u_signal[sig]) 
        {
	case SIG_DFL:
#if notyet
	  /*
	   * Don't take default actions on system processes.
	   */
	  if (p->p_ppid == 0)
	    break;		/* == ignore */
#endif
	  /*
	   * If there is a pending stop signal to process
	   * with default action, stop here,
	   * then clear the signal.  However,
	   * if process is member of an orphaned
	   * process group, ignore tty stop signals.
	   */
	  if (mask & stopsigmask) 
	    {
#if notyet
	      if (p->p_flag&STRC ||
		  (p->p_pgru.pg_jobc == 0 && mask & ttystopsigmask))
		break;	/* == ignore */
	      u.p_xstat = sig;
	      stop(p);
	      if ((u.p_pptr->p_flag & SNOCLDSTOP) == 0)
		_psignal(u.p_pptr, SIGCHLD);
	      swtch();
#endif
	      break;
	    } 
          else if (mask & defaultignmask)
	    {
	      /*
	       * Except for SIGCONT, shouldn't get here.
	       * Default action is to ignore; drop it.
	       */
	      break;		/* == ignore */
	    }
	  else
	    return (sig);
	  /*NOTREACHED*/

	case SIG_IGN:
	  /*
	   * Masking above should prevent us ever trying
	   * to take action on an ignored signal other
	   * than SIGCONT, unless process is traced.
	   */
#if 0
	  if (sig != SIGCONT && (u.p_flag&STRC) == 0)
	    printf("issig\n");
#endif
	  break;		/* == ignore */

	default:
	  /*
	   * This signal has an action, let
	   * psig process it.
	   */
	  return (sig);
	}
      u.p_sig &= ~mask;		/* take the signal! */
    }
  /* NOTREACHED */
}

#if notyet
/*
 * Put the argument process into the stopped
 * state and notify the parent via wakeup.
 * Signals are handled elsewhere.
 * The process must not be on the run queue.
 */
void
stop(struct Task *t)
{
  struct user *p = (struct user *) t->tc_TrapData;

  p->p_stat = SSTOP;
  p->p_flag &= ~SWTED;
  wakeup((caddr_t)u.p_pptr);
}
#endif

/*
 * Perform the action specified by the current signal.
 * The usual sequence is:
 *	if (sig = CURSIG(p))
 *		psig(p, sig);
 */
void
psig(struct user *p, int sig)	/* called in SUPERVISOR */
{
  int mask, returnmask;
  register sig_t action; 

  do 
    {
#if 0
      if (sig == 0)
	ix_panic("psig");
#endif
      mask = sigmask(sig);
      p->p_sig &= ~mask;
      action = p->u_signal[sig];
      if (action != SIG_DFL) 
        {
	   /*
	    * Set the new mask value and also defer further
	    * occurences of this signal.
	    *
	    * Special case: user has done a sigpause.  Here the
	    * current mask is not of interest, but rather the
 	    * mask from before the sigpause is what we want
	    * restored after the signal processing is completed.
	    */
#if usermode
	  /* not needed here, no interrupt will play with the signalmask... */
	  (void) Disable();
#endif
	  if (p->p_flag & SOMASK)
	    {
	      returnmask = p->u_oldmask;
	      p->p_flag &= ~SOMASK;
	    }
	  else
	    returnmask = p->p_sigmask;
	  p->p_sigmask |= p->u_sigmask[sig] | mask;
#if usermode
	  (void) Enable();
#endif

	  p->u_ru.ru_nsignals++;
	  sendsig(p, action, sig, returnmask, 0, 0);
	  continue;
	}

#if whatdoesthisdo
      p->u_acflag |= AXSIG;
#endif

      switch (sig) 
        {
	case SIGILL:
	case SIGIOT:
	case SIGBUS:
	case SIGQUIT:
	case SIGTRAP:
	case SIGEMT:
	case SIGFPE:
	case SIGSEGV:
	case SIGSYS:
	  p->u_sig = sig;
	  if (core() == 0)
	    sig |= WCOREFLAG;
	}
      /* we can't call exit() when in supervisor mode, have to do this just like
       * it was a signal passed on its own frame */
      sendsig(p, sig_exit, sig, 0, 0, 0);
      /* NOTREACHED */
	
  } while (sig = CURSIG(p));
}

/*
 * Create a core image on the file "core".
 * It writes UPAGES block of the
 * user.h area followed by the entire
 * data+stack segments.
 */
int
core()
{
  return -1;
}
@


1.2
log
@change to new ix_sleep() semantics
@
text
@d19 1
a19 1
 *  $Id: kern_sig.c,v 1.1 1992/05/14 19:55:40 mwild Exp $
d22 3
d65 2
a331 1
    struct ExecBase *SysBase = *(void **)4;
@


1.1
log
@Initial revision
@
text
@d19 1
a19 1
 *  $Id$
d21 3
a23 1
 *  $Log$
d25 1
d371 1
a371 1
  while (ix_sleep (p) == 0)
@
