File:  [MW Coherent from dump] / coherent / b / kernel / coh.386 / sig.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Wed May 29 04:56:37 2019 UTC (7 years ago) by root
Branches: MarkWilliams, MAIN
CVS tags: relic, HEAD
coherent

/* (lgl-
 *	The information contained herein is a trade secret of Mark Williams
 *	Company, and  is confidential information.  It is provided  under a
 *	license agreement,  and may be  copied or disclosed  only under the
 *	terms of  that agreement.  Any  reproduction or disclosure  of this
 *	material without the express written authorization of Mark Williams
 *	Company or persuant to the license agreement is unlawful.
 *
 *	COHERENT Version 5.0
 *	Copyright (c) 1982, 1993.
 *	An unpublished work by Mark Williams Company, Chicago.
 *	All rights reserved.
 -lgl) */
/*
 * File:	coh.386/sig.c
 *
 * Purpose:	signal handling
 *
 * Revised: Tue May  4 11:59:15 1993 CDT
 */

/*
 * ----------------------------------------------------------------------
 * Includes.
 */
#include <sys/coherent.h>
#include <errno.h>
#include <sys/ino.h>
#include <sys/inode.h>
#include <sys/io.h>
#include <sys/proc.h>
#include <sys/ptrace.h>
#include <sys/sched.h>
#include <sys/seg.h>
#include <signal.h>
#include <sys/core.h>

/*
 * ----------------------------------------------------------------------
 * Definitions.
 *	Constants.
 *	Macros with argument lists.
 *	Typedefs.
 *	Enums.
 */
typedef void (*VFPTR)();	/* pointer to void function */

/*
 * ----------------------------------------------------------------------
 * Functions.
 *	Import Functions.
 *	Export Functions.
 *	Local Functions.
 */
int	actvsig();
int	nondsig();
int	ptret();
int	ptset();
void	sendsig();
void	sigDefault();
void	sigHold();
void	sigIgnore();
void	sigPause();
void	sigRelease();
int	sigSet();
int	sigdump();
int	usigsys();

static struct _fpstate * empack();

/*
 * ----------------------------------------------------------------------
 * Global Data.
 *	Import Variables.
 *	Export Variables.
 *	Local Variables.
 */
/*
 * Patchable variables.
 *
 * Patch DUMP_TEXT nonzero to make text segment show up in core files.
 * Patch DUMP_LIM set the upper limit in bytes of how much of a
 * segment is written to a core file.
 *
 * Patch CATCH_SEGV nonzero if you are trying to run software that was
 * written in blatant defiance of the SVID 2 caution that handling SIGSEGV
 * is nonportable and should not be assumed valid on all systems.
 */
int	DUMP_TEXT = 0;
int	DUMP_LIM=512*1024;
int	CATCH_SEGV = 0;

/*
 * ----------------------------------------------------------------------
 * Code.
 */

/*
 * Given 1-based signal number, ask whether a signal handler was
 * attached to the current process using sigset().  This allows
 * the kernel to process sigset() differently from signal(), as
 * required.
 *
 * return 1 if sigset(), 0 if not.
 */
int
sigSet(signal)
int signal;
{
	return (SELF->p_dsig & SIG_BIT(signal)) ? 1 : 0;
}

/*
 * Given 1-based signal number, ignore that signal in the current process.
 */
void
sigIgnore(signal)
int signal;
{
	int sigbit = SIG_BIT(signal);

	SELF->p_dfsig &= ~sigbit;	/* No longer defaulted.  */
	SELF->p_isig |= sigbit;		/* Mark signal as ignored.  */
	u.u_sfunc[signal - 1] = SIG_IGN;
}

/*
 * Given 1-based signal number, restore default handling for the current
 * process.
 *
 * There is some duplication of work done in sigAttach(), but sigDefault()
 * is also called from msig.c
 */
void
sigDefault(signal)
int signal;
{
	int sigbit = SIG_BIT(signal);

	SELF->p_dfsig |= sigbit;
	SELF->p_isig &= ~sigbit;
	u.u_sfunc[signal - 1] = SIG_DFL;
}

/*
 * Given 1-based signal number, hold that signal for the current process.
 */
void
sigHold(signal)
int signal;
{
	SELF->p_hsig |= SIG_BIT(signal);
}

/*
 * Given 1-based signal number, pause for that signal for the current process.
 */
void
sigPause(signal)
int signal;
{
	SELF->p_hsig &= ~SIG_BIT(signal);

	/*
	 * Like upause(), do a sleep on an event which never gets a wakeup.
	 * The sleep returns immediately if a signal was already holding.
	 */
	x_sleep((char *)&u, prilo, slpriSigCatch, "sigpause");
	actvsig();
}

/*
 * Given 1-based signal number, release that signal for the current process.
 */
void
sigRelease(signal)
int signal;
{
	SELF->p_hsig &= ~SIG_BIT(signal);
	if (nondsig()) {
		actvsig();
	}
}

/*
 * Given 1-based signal number, a pointer to a signal-handling function,
 * and a flag, attach the signal handler to the current process.
 *
 * Function pointer "func" may take on special values SIG_DFL, SIG_IGN,
 * and, if "how" is SIGSET, SIG_HOLD.
 *
 * The flag "how" is 0 if attachment is via signal(), SIGSET if attachment
 * is via sigset().
 *
 * Return the previously attached signal handler, or SIG_HOLD if signals
 * were previously held.
 */
VFPTR
sigAttach(signal, func, how)
int signal;
VFPTR func;
int how;
{
	VFPTR retval;
	int sigbit = SIG_BIT(signal);

	/*
	 * Set up the return value, which says what was previously
	 * done with the given signal.
	 */
	if (SELF->p_isig & sigbit)
		retval = (VFPTR)SIG_IGN;
	else if (SELF->p_hsig & sigbit)
		retval = (VFPTR)SIG_HOLD;
	else
		retval = u.u_sfunc[signal - 1];

	u.u_sigreturn = (VFPTR)(u.u_regl[EDX]);
	u.u_sfunc[signal - 1] = func;

	/*
	 * Remove previous default, ignore, or hold status.
	 */
	SELF->p_dfsig &= ~sigbit;
	SELF->p_isig &= ~sigbit;
	SELF->p_hsig &= ~sigbit;

	/*
	 * Any pending signal is lost.
	 */
	SELF->p_ssig &= ~sigbit;

	/*
	 * Special cases for handler.
	 */
	switch ((int)func) {
	case (int)SIG_DFL:
		sigDefault(signal);
		break;
	case (int)SIG_IGN:
		sigIgnore(signal);
		break;
	case (int)SIG_HOLD:
		sigHold(signal);
		break;
	}

	/*
	 * Remember whether handler was attached with sigset() vs signal().
	 */
	if (how == SIGSET)
		SELF->p_dsig |= sigbit;
	else
		SELF->p_dsig &= ~sigbit;

	return retval;
}

/*
 * Set up the action to be taken on a signal.
 */
int
usigsys(signal, func)
int	signal;
VFPTR func;
{
	int	sigtype;
	int	retval = 0;

	sigtype = signal & ~0xFF;
	signal &= 0xFF;

	T_HAL(8, if (signal == SIGINT)
	  printf("[%d]sigint(%x, %x) ", SELF->p_pid, sigtype, func));

	/* Range check on 1-based signal number. */
	if (signal <= 0 || signal > NSIG) {
		u.u_error = EINVAL;
		return;
	}

	/*
	 * Don't allow setting/holding/releasing some signals.
	 *
	 * NOTICE:  Ignoring SIGSEGV causes runaway user faults.
	 * SVID Issue 2 says *don't* do signal(SIGSEGV,...)!!!
	 */
	if (signal == SIGKILL) {
		u.u_error = EINVAL;
		return;
	}

	if (signal == SIGSEGV && CATCH_SEGV == 0) {
		u.u_error = EINVAL;
		return;
	}
	 
	switch (sigtype) {
	case SIGHOLD:
		sigHold(signal);
		break;
	case SIGRELSE:
		sigRelease(signal);
		break;
	case SIGIGNORE:
		sigIgnore(signal);
		break;
	case 0:				/* old system entry */
		retval = (int)sigAttach(signal, func, 0);
		break;
	case SIGSET:			/* new system entry */
		retval = (int)sigAttach(signal, func, SIGSET);
		break;
	case SIGPAUSE:
		sigPause(signal);
		break;
	default:
		u.u_error = SIGSYS;
		break;
	}
	return retval;
}

/*
 * Send a signal to the process `pp'.
 * Return 1 if signal was sent.
 * Return 0 if signal was ignored.
 * The return value is of use to the trap handler.
 */
void
sendsig(sig, pp)
register unsigned sig;
register PROC *pp;
{
	register sig_t f;
	register int s;

	T_HAL(8, if (sig == SIGINT) printf("[%d]gets int ", pp->p_pid));

	/*
	 * Convert the signal to a bit position.
	 */
	f = SIG_BIT(sig);

	/*
	 * If the signal is ignored, and is not SIGCLD, do nothing.
	 */
	if ((pp->p_isig & f) && sig != SIGCLD) {
		goto sendSigDone;
	}

	/*
	 * No further processing for delayed or held signals.
	 */
	if ((pp->p_ssig & f) && (pp->p_hsig|pp->p_dsig) & f)
		goto sendSigDone;
	
	/*
	 * Actually send the signal by flagging the needed bit.
	 */
	pp->p_ssig |= f;

	/*
	 * If the process is sleeping, wake it up so that
	 * it can process this signal.
	 */
	if (pp->p_state == PSSLSIG) {
		s = sphi();
		pp->p_lback->p_lforw = pp->p_lforw;
		pp->p_lforw->p_lback = pp->p_lback;
#ifndef _I386
		addu(pp->p_cval, (utimer-pp->p_lctim)*CVCLOCK);
#endif
		setrun(pp);
		spl(s);
	}
sendSigDone:
	return;
}

/*
 * Return signal number if we have a non ignored or delayed signal, else zero.
 */
int
nondsig()
{
	register PROC *pp;
	register sig_t mask;
	register int signo;

	pp = SELF;
	signo = 0;

	/*
	 * Turn off all ignored signals except SIGCLD.
	 */
	pp->p_ssig &= ~(pp->p_isig & ~SIG_BIT(SIGCLD));

	/*
	 * If any signals have arrived, but which are not held,
	 * figure out what they are.
	 */
	if (pp->p_ssig&~pp->p_hsig) {
		/*
		 * There is at least one signal.  Extract its number
		 * from the signal bits.
		 */
		mask = (sig_t) 1;
		signo += 1;
		while (((pp->p_ssig&~pp->p_hsig) & mask) == 0) {
			mask <<= 1;
			signo += 1;
		}
	}
	return signo;
}

/*
 * If we have a signal that isn't ignored, activate it.
 */
int
actvsig()
{
	register int signum;
	register PROC *pp;
	register int (*func)();
	int ptval;

	/*
	 * Fetch an unprocessed signal.
	 * Return if there are none.
	 * The while() structure is only for traced processes.
	 */
	while (signum = nondsig()) {

		pp = SELF;

		/*
		 * Reset the signal to indicate that it has been processed.
		 * Bit table p_ssig uses 0-based signals, while signal.h
		 * lists 1-based signals.
		 */
		pp->p_ssig &= ~SIG_BIT(signum);

		/*
		 * Fetch the user function that goes with this signal.
		 * Function table u_sfunc uses 0-based signals, while signal.h
		 * lists 1-based signals.
		 */
		func = u.u_sfunc[signum-1];

		/*
		 * SIGCLD causes no work here if defaulted or ignored.
		 */
		if (signum == SIGCLD && (func == SIG_DFL || func == SIG_IGN))
			return;

		/*
		 * Store the (1-based) signal number in the u area.
		 * This is how a core dump records the death signal.
		 */
		u.u_signo = signum;

		/*
		 * If the signal is not defaulted, go run the requested
		 * function.
		 */
		if (func != SIG_DFL) {
			if (XMODE_286)
				oldsigstart(signum, func);
			else {
				msigstart(signum, func);
			}
			return;
		}

		/*
		 * ASSERTION:  the signal being processed is SIG_DFL'd.
		 */

		/*
		 * msysgen() is a nop for COHERENT 4.0.  The comment in the
		 * assembly code is "Nothing useful to save".
		 */
		msysgen(u.u_sysgen);

		/*
		 * When a traced process is signaled, it may need to exchange
		 * data with its parent (via ptret).
		 */
		if (pp->p_flags&PFTRAC) {
			pp->p_flags |= PFWAIT;
			ptval = ptret();
			T_HAL(0x10000, printf("ptret()=%x ", ptval));
			pp->p_flags &= ~(PFWAIT|PFSTOP);
			if (ptval == 0)
				/* see if another signal came in */
				continue;
			else
				signum = ptval;
		}

		/*
		 * Some signals cause a core file to be written.
		 */
		switch(signum) {
		case SIGQUIT:
		case SIGILL:
		case SIGTRAP:
		case SIGABRT:
		case SIGFPE:
		case SIGSEGV:
		case SIGSYS:
			if (sigdump())
				signum |= 0x80;
			break;
		}
		pexit(signum);
	}
}

/*
 * Create a dump of ourselves onto the file `core'.
 */
int
sigdump()
{
	register INODE *ip;
	register SR *srp;
	register SEG * sp;
	register int n;
	register paddr_t ssize;
	extern	int	DUMP_LIM;
	struct ch_info chInfo;

	if (SELF->p_flags&PFNDMP)
		return (0);
	u.u_io.io_seg  = IOSYS;
	u.u_io.io_flag = 0;
	/* Make the core with the real owners */
	schizo();
	if (ftoi("core", 'c')) {
		schizo();
		return (0);
	}
	if ((ip=u.u_cdiri) == NULL) {
		if ((ip=imake(IFREG|0644, 0)) == NULL) {
			schizo();
			return (0);
		}
	} else {
		if ((ip->i_mode&IFMT)!=IFREG
		 || iaccess(ip, IPW)==0
		 || getment(ip->i_dev, 1)==NULL) {
			idetach(ip);
			schizo();
			return (0);
		}
		iclear(ip);
	}
	schizo();
	u.u_error = 0;
	u.u_io.io_seek = 0;

	/* Write core file header */
	chInfo.ch_magic = CORE_MAGIC;
	chInfo.ch_info_len = sizeof(chInfo);
	chInfo.ch_uproc_offset = U_OFFSET;

	u.u_io.io_seg = IOSYS;
	u.u_io.io.vbase = &chInfo;
	u.u_io.io_ioc = sizeof(chInfo);
	u.u_io.io_flag = 0;

	sp->s_lrefc++;
	iwrite(ip, &u.u_io);
	sp->s_lrefc--;

	/*
	 * Added to aid in kernel debugging - if DUMP_TEXT is nonzero,
	 * dump the text segment (to see if it was corrupted) and set
	 * the dump flag so that postmortem utilities will know that
	 * text is present in the core file.
	 */
	if (DUMP_TEXT)
		u.u_segl[SISTEXT].sr_flag |= SRFDUMP;

	for (srp=u.u_segl; u.u_error==0 && srp<&u.u_segl[NUSEG]; srp++) {

		if ((srp->sr_flag & SRFDUMP)==0)
			continue;

		/* Don't try to dump empty segments. */
		if ((sp = srp->sr_segp)==NULL) {
			srp->sr_flag &= ~SRFDUMP;
			continue;
		}

		/* Don't dump segments too big to dump. */
		if (sp->s_size > DUMP_LIM)
			srp->sr_flag &= ~SRFDUMP;
	}

	for (srp=u.u_segl; u.u_error==0 && srp<&u.u_segl[NUSEG]; srp++) {

		/* Only dump segments flagged for dumping. */
		if ((srp->sr_flag & SRFDUMP)==0)
			continue;

		ssize = sp->s_size;
		u.u_io.io_seg = IOPHY;
		u.u_io.io.pbase = MAPIO(sp->s_vmem, 0);
		u.u_io.io_flag = 0;
		sp->s_lrefc++;
		while (u.u_error == 0 && ssize != 0) {
			n = ssize > SCHUNK ? SCHUNK : ssize;
			u.u_io.io_ioc = n;
			iwrite(ip, &u.u_io);
			u.u_io.io.pbase += n;
			ssize -= (paddr_t)n;
		}
		sp->s_lrefc--;
	}
	idetach(ip);
	return (u.u_error==0);
}

/*
 * Send a ptrace command to the child.
 *
 * "pid" is child pid.
 */
int
ptset(req, pid, addr, data)
unsigned req;
int *addr;
{
	register PROC *pp;

	lock(pnxgate);
	for (pp=procq.p_nforw; pp!=&procq; pp=pp->p_nforw)
		if (pp->p_pid == pid)
			break;
	unlock(pnxgate);
	if (pp==&procq || (pp->p_flags&PFSTOP)==0 || pp->p_ppid!=SELF->p_pid){
		u.u_error = ESRCH;
		return;
	}
	lock(pts.pt_gate);
	pts.pt_req = req;
	pts.pt_pid = pid;
	pts.pt_addr = addr;
	pts.pt_data = data;
	pts.pt_errs = 0;
	pts.pt_rval = 0;
	pts.pt_busy = 1;
	wakeup((char *)&pts.pt_req);
	while (pts.pt_busy) {
		x_sleep((char *)&pts.pt_busy, primed, slpriSigCatch, "ptrace");
		/* Send a ptrace command to the child.  */
	}
	u.u_error = pts.pt_errs;
	unlock(pts.pt_gate);
	return (pts.pt_rval);
}

/*
 * This routine is called when a child that is being traced receives a signal
 * that is not caught or ignored.  It follows up on any requests by the parent
 * and returns when done.
 *
 * After ptrace handling done in this routine, a real or simulated signal
 * may need to be sent to the traced process.
 * Return a signal number to be sent to the child process, or 0 if none.
 */
int
ptret()
{
	extern void (*ndpKfrstor)();
	register PROC *pp;
	register PROC *pp1;
	register int sign;
	unsigned off;
	int doEmUnpack = 0;

	struct _fpstate * fstp = empack();

	pp = SELF;
next:
	u.u_error = 0;
	if (pp->p_ppid == 1)
		return (SIGKILL);
	sign = -1;

	/* wake up parent if it is sleeping */
	lock(pnxgate);
	pp1 = &procq;
	for (;;) {
		if ((pp1=pp1->p_nforw) == &procq) {
			sign = SIGKILL;
			break;
		}
		if (pp1->p_pid != pp->p_ppid)
			continue;
		if (ASLEEP(pp1))
			wakeup((char *)pp1);
		break;
	}
	unlock(pnxgate);

	while (sign < 0) {
		/* If no pending ptrace transaction for this process, sleep. */
		if (pts.pt_busy==0 || pp->p_pid!=pts.pt_pid) {
			/* If a signal bit is set now, just exit - let
			 * actvsig() handle it next time through.
			 * Doing sleep and goto next will stick us in a loop */
			if (nondsig())
				return 0;
			x_sleep((char *)&pts.pt_req,
			  primed, slpriSigCatch, "ptret");
			goto next;
		}
		switch (pts.pt_req) {
		case PTRACE_RD_TXT:
			if (XMODE_286) {
				pts.pt_rval = getuwd(NBPS+pts.pt_addr);
				break;
			}
			/* Fall through for 386 mode processes. */
		case PTRACE_RD_DAT:
			pts.pt_rval = getuwd(pts.pt_addr);
			break;
		case PTRACE_RD_USR:
			/* See ptrace.h for valid offsets. */
			off = (unsigned)pts.pt_addr;
			if (off & 3)
				u.u_error = EINVAL;
			else if (off < PTRACE_FP_CW) {
				/* Reading CPU general register state */
				if (off == PTRACE_SIG)
					pts.pt_rval = u.u_signo;
				else
					pts.pt_rval = u.u_regl[off>>2];
			} else if (off < PTRACE_DR0) {
				/*
				 * Reading NDP state.
				 * If NDP state not already saved, save it.
				 * Fetch desired info.
				 * Restore NDP state in case we will resume.
				 */
				if (rdNdpUser()) {
					/* if using coprocessor */
					if (!rdNdpSaved()) {
						ndpSave(&u.u_ndpCon);
						wrNdpSaved(1);
					}
pts.pt_rval = ((int *)&u.u_ndpCon)[(off - PTRACE_FP_CW)>>2];
					ndpRestore(&u.u_ndpCon);
					wrNdpSaved(0);
				} else if (fstp) {
pts.pt_rval = getuwd(((int *)fstp) + ((off - PTRACE_FP_CW)>>2));
					/* if emulating */
				} else /* no ndp state to display */
					pts.pt_rval = 0;
			} else
				u.u_error = EINVAL;
			break;
		case PTRACE_WR_TXT:
			if (XMODE_286) {
				putuwd(NBPS+pts.pt_addr, pts.pt_data);
				break;
			}
			/* Fall through for 386 mode processes. */
		case PTRACE_WR_DAT:
			putuwd(pts.pt_addr, pts.pt_data);
			break;
		case PTRACE_WR_USR:
			/* See ptrace.h for valid offsets. */
			off = (unsigned)pts.pt_addr;

			if (off & 3)
				u.u_error = EINVAL;
			else if (off < PTRACE_FP_CW) {
				/* Writing CPU general register state */
				if (off == PTRACE_SIG)
					u.u_error = EINVAL;
				else
					u.u_regl[off>>2] = pts.pt_data;
			} else if (off < PTRACE_DR0) {
				if (rdNdpUser()) {
					/*
					 * Writing NDP state.
					 * If NDP state not already saved, save it.
					 * Store desired info.
					 * Restore NDP state in case we will resume.
					 */
					if (!rdNdpSaved()) {
						ndpSave(&u.u_ndpCon);
						wrNdpSaved(1);
					}
((int *)&u.u_ndpCon)[(off - PTRACE_FP_CW)>>2] = pts.pt_data;
					ndpRestore(&u.u_ndpCon);
					wrNdpSaved(0);
				} else if (fstp && ndpKfrstor) {
putuwd(((int *)fstp) + ((off - PTRACE_FP_CW)>>2), pts.pt_data);
					doEmUnpack = 1;
				}
			} else
				u.u_error = EINVAL;
			break;
		case PTRACE_RESUME:
			u.u_regl[EFL] &= ~MFTTB;
			goto sig;
		case PTRACE_TERM:
			sign = SIGKILL;
			break;
		case PTRACE_SSTEP:
			u.u_regl[EFL] |= MFTTB;
		sig:
			if (pts.pt_data<0 || pts.pt_data>NSIG) {
				u.u_error = EINVAL;
				break;
			}
			sign = pts.pt_data;
			if (pts.pt_addr != SIG_IGN) {
				u.u_regl[EIP] = (int)pts.pt_addr;
			}
			break;
		default:
			u.u_error = EINVAL;
		}
		if ((pts.pt_errs=u.u_error) == EFAULT)
			pts.pt_errs = EINVAL;
		pts.pt_busy = 0;
		wakeup((char *)&pts.pt_busy);
	}
	if (doEmUnpack)
		(*ndpKfrstor)(fstp, &u.u_ndpCon);
	return (sign);
}

/*
 * If using floating point emulator, make room on user stack and save
 * floating point context there.  Code elsewhere takes care of floating
 * point context if there is a coprocessor.
 *
 * Return the virtual address in user space of the context area, or
 * return NULL if not using FP emulation.
 */
static struct _fpstate *
empack()
{
	int uesp;
	int sphi, splo;
	SEG * segp;
	cseg_t * pp;
	struct _fpstate * ret = NULL;
	extern void (*ndpKfsave)();
	unsigned long sw_old;

	/* If not emulating, do nothing */
	if (rdNdpUser() || !rdEmTrapped() || !ndpKfsave)
		return NULL;

	/*
	 * Will copy at least u_sigreturn, _fpstackframe, and ndpFlags.
	 * If using ndp, need room for an _fpstate.
	 * If emulating, need room for an _fpemstate.
	 */
	uesp = u.u_regl[UESP] - sizeof(struct _fpstate);

	/* Add to user stack if necessary. */
	segp = u.u_segl[SISTACK].sr_segp;
	sphi = (XMODE_286) ? ISP_286 : ISP_386;
	splo = sphi - segp->s_size;

	if (splo > uesp) {
		pp = c_extend(segp->s_vmem, btoc(segp->s_size));
		if (pp==0) {
			printf("Empack failed.  cmd=%s  c_extend(%x,%x)=0 ",
			  u.u_comm, segp->s_vmem, btoc(segp->s_size));
			return NULL;
		}

		segp->s_vmem = pp;
		segp->s_size += NBPC;
		if (sproto(0)==0) {
			printf("Empack failed.  cmd=%s  sproto(0)=0 ",
			  u.u_comm);
			return NULL;
		}

		segload();
	}

	ret = (struct _fpstate *)uesp;
	(*ndpKfsave)(&u.u_ndpCon, uesp);
	sw_old = getuwd(&ret->sw);
	putuwd(&ret->status, sw_old);
	putuwd(&ret->sw, sw_old & 0x7f00);

	return ret;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.