/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: streamio.c 1.126 90/08/08 $"

/* $Log:	streamio.c,v $
 */


/* 
 * STREAM SYSTEM CALL LAYER
 *
 * This file contains the streams implementation of the file system
 * and other stream-specific system calls.  Only the "top level"
 * routines invoked directly from the syscall layer are in this file;
 * all the internal ones are in the following:
 *
 *   strsupp.c => misc support routines needed by streamio.c
 *   strq.c    => streams queue mgt routines
 *   strmm.c   => msg mgt routines
 *   stream.c  => scheduler & initialization related routines
 */


#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/mutex.h"
#include "sys/intctl.h"
#include "sys/file.h"
#include "sys/sysmacros.h"
#include "sys/param.h"
#include "sys/errno.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/conf.h"
#include "sys/debug.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/strstat.h"
#include "sys/buf.h"
#include "sys/vfs.h"
#include "sys/vnode.h"
#include "sys/timer.h"
#include "sys/session.h"
#include "sys/proc.h"
#include "sys/uio.h"
#include "sys/cmap.h"
#include "sys/snode.h"
#include "sys/ufsfilsys.h"
#include "sys/var.h"
#include "sys/poll.h"
#include "sys/termio.h"
#include "sys/ttold.h"
#include "sys/systm.h"
#include "sys/callo.h"
#include "sys/gate.h"
#include "sys/cmn_err.h"

#ifdef DEBUG
extern	int	jc_debug;
extern	int	fvhang_up;
#endif

/*
 *  Qinit structure and Module_info structures
 *        for stream head read and write queues
 */
int	strrput(), strwsrv();
struct 	module_info strm_info = {0, "strrhead", 0, INFPSZ, STRHIGH, STRLOW};
struct  module_info stwm_info = {0, "strwhead", 0, 0, 0, 0 };
struct	qinit strdata = {strrput, NULL, NULL, NULL, NULL, &strm_info, NULL};
struct	qinit stwdata = {NULL, strwsrv, NULL, NULL, NULL, &stwm_info, NULL};

extern	sema_t	poll_sema;		/* polling process wait on this */
extern	lock_t	poll_mutex;		/* synchronizes poll */
extern lock_t	tbl_mutex;		/* synchronizes stream table */
extern lock_t	svc_mutex;		/* synchronizes scheduler */

extern mblk_t *l_getq();
extern int l_canput();
extern queue_t *qattach();
extern int strtime();
extern int p_plumb_v_lock();
extern mblk_t *strmakemsg();
extern mblk_t *getrdbuf();
extern int strwaitbuf();
extern struct file *getf();
extern struct snode *getspstr();
extern int str_push_mod();
extern int str_find_mod();


#define ncopyin(A, B, C, D)	copyin(A, B, C)		/* temporary */
#define ncopyout(A, B, C, D)	copyout(A, B, C)	/* temporary */


#ifdef DEBUG
int str1debug = 0;
#define STR1PRINTF(arg)	if (str1debug) printf(arg); else
#else
#define STR1PRINTF(arg)
#endif

/*
 * Takes a stream out of the freelist and attaches it into the 
 * hash-bucket's list of streams.
 * Assumes tbl_lock is held.
 */
struct stdata *
allocstr(bucket)
	register struct stdatahdr *bucket;
{
	register struct stdata *stp;

	stp = strfree->sd_forw;
	if ((struct stdatahdr *)stp == strfree) {
		strst.stream.fail++;
		return(NULL);
	}
	/* remove from freelist */
	stp->sd_back->sd_forw = stp->sd_forw;
	stp->sd_forw->sd_back = stp->sd_back;

	stp->sd_flag = STRINUSE;

	/* insert into bucket list */
	stp->sd_forw = bucket->sd_forw;
	stp->sd_back = (struct stdata *)bucket;
	bucket->sd_forw->sd_back = stp;
	bucket->sd_forw = stp;
	BUMPUP(strst.stream);
	return(stp);
}

/* 
 * Detached stream from its hash-bucket list and attaches it
 * in the freelist.
 * Assumes tbl_lock is held.
 */

freestr(stp)
	register struct stdata *stp;
{
	/* remove from bucket list */
	stp->sd_back->sd_forw = stp->sd_forw;
	stp->sd_forw->sd_back = stp->sd_back;

	stp->sd_flag = 0;

	/* insert in freelist */
	stp->sd_forw = strfree->sd_forw;
	stp->sd_back = (struct stdata *)strfree;
	strfree->sd_forw->sd_back = stp;
	strfree->sd_forw = stp;
	strst.stream.use--;
}

/*
 * Changes the stream head to a different hash bucket.  Called 
 * when the minor number of the stream changes due to cloning.
 * Assumes tbl_lock is held.
 */
rehashstr(stp)
	register struct stdata *stp;
{
	register struct stdatahdr *bucket;

	bucket = STRHASH(stp->sd_rdev);

	/* first remove from old bucket */
	stp->sd_back->sd_forw = stp->sd_forw;
	stp->sd_forw->sd_back = stp->sd_back;

	/* insert into new bucket list */
	stp->sd_forw = bucket->sd_forw;
	stp->sd_back = (struct stdata *)bucket;
	bucket->sd_forw->sd_back = stp;
	bucket->sd_forw = stp;
}


/*
 * Opens a stream device.
 * 
 * The snode is assumed unlocked.
 *
 * CALLERS NOTE:  If the snode represents the clone device and the open
 * succeeds, the supplied snode will be deallocated and a new one allocated 
 * in its place.  Callers must not examine/modify the supplied snode after
 * a successful call.
 */

int
stropen(sp, flag)
	struct snode *sp;
	register flag;		/* FNDELAY and FNOCTTY flag */
{
	register struct stdata *stp;
	register struct stdatahdr *bucket;
	register queue_t *qp, *driver_q;
	register another_snode;
	register spl_t s, s_str;
	register proc_t	*p;

	p = u.u_procp;
	/* 
	 * Hash device number to a bucket in the stream table.
	 * The str->sd_wrq pointer is used as the "in-use" indication.
	 * If non-null the stream is in-use, otherwise its free.
	 */
	bucket = STRHASH(sp->s_dev);

	while (1) {
		another_snode = FALSE;
		s = p_lock(&tbl_mutex, SPLSTR);
		/* Check if current snode already points to valid stream */
		if (sp->s_sptr && (sp->s_sptr->sd_flag & STRINUSE) &&
		    (sp->s_dev == sp->s_sptr->sd_rdev)) {
			stp = sp->s_sptr;
		} else {
			/*
			 * This snode isn't streaming, but another
			 * snode may refer to same device, so search
			 * the stream table for another stream with
			 * the same device number to avoid building 2
			 * streams to the same device.
			 */
			for (stp = bucket->sd_forw; stp != (struct stdata *)bucket; stp = stp->sd_forw) {
				if (stp->sd_rdev == sp->s_dev) {
					another_snode = TRUE;
					break;
				}
			}
			if (!another_snode) {
				/* Not already streaming so allocate a stream. */
				if (!(qp = allocq())) {
					v_lock(&tbl_mutex, s);
					cmn_err(CE_CONT, "stropen: out of queues\n");
					/*
					 *+ A stream could not be opened due to exhaustion of
					 *+ stream queue resources.  The system may be under-
					 *+ configured in terms of queue resources (see the
					 *+ NQUEUE configuration parameter).
					 */
					return(ENOSR);
				}
				if (stp = allocstr(bucket)) {
					/* 
					 * Initialize the stream lock and a
					 * few other essentials before
					 * releasing the tbl_lock.  The rest
					 * of the of the initialization can be
					 * done later.  The device <-> stream
					 * binding must be done under the
					 * tbl_lock.
					 */
					sp->s_sptr = stp;
					stp->sd_rdev = sp->s_dev;
					init_lock(&stp->sd_mutex);
					s_str = p_lock(&stp->sd_mutex, SPLSTR);
					v_lock(&tbl_mutex, s_str);	/* note spl order */
					/* attach stream head */
					stp->sd_wrq = WR(qp);
					goto first_open;
				} else {
					/* no free stream found */
					v_lock(&tbl_mutex, s);
					cmn_err(CE_CONT, "stropen: out of streams\n");
					/*
					 *+ A stream could not be opened due to exhaustion
					 *+ of stream table entries.  The system may be under-
					 *+ configured in terms of streams resources (see the
					 *+ NSTREAM configuartion parameter).
					 */
					freeq(qp);
					return(ENOSR);
				}
			}
		}

		/*
		 * Not first_open.
		 * At this point a stream exists, so acquire its lock,
		 * validate, and release the table lock.
		 */
		s_str = p_lock(&stp->sd_mutex, SPLSTR);
			
		if (stp->sd_flag & (STRHUP | STRERR)) {
			v_lock(&stp->sd_mutex, s_str);
			v_lock(&tbl_mutex, s);
			return(EIO);
		}

		if (!(stp->sd_flag & STWOPEN)) {
			/* 
			 * No previous open/close in progress.  Before
			 * releasing tbl_lock, associate stp with snode.
			 */
			sp->s_sptr = stp;
			v_lock(&tbl_mutex, s_str);	/* note spl order */
			goto subsequent_open;
		}

		v_lock(&tbl_mutex, s_str);	/* note spl order */
		
		/*
		 * Must wait for previous open/close operation to finish.
		 * Sleep at a signallable priority.  Note that there is no
		 * check for NDELAY which implies that an open() call with
		 * NDELAY behind one with DELAY may block here.  This 
		 * is compatible with V.3.2 and any change in this should
		 * be very carefully considered.
		 */
		
		if (p_sema_v_lock(&stp->sd_open, STOPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
			return(EINTR);	
		}

		/*
		 * At this point, the previous open/close operation may have
		 * completed.  Since this could have been a clone open, the
		 * stream we are waiting on may have become reattached to a
		 * different snode!  Or the previous operation may have been a
		 * close which deallocates the stream.  Hence we must loop
		 * back and recheck the stream table etc.
		 */
	}

	/* The stream has been obtained and locked at this point */

 subsequent_open:

	/* STWOPEN flag stops subsequent opens till this one completes */
	stp->sd_flag |= STWOPEN;
	
	/* 
	 * Since this is a plumbing operation, it must serialize with
	 * other plumbers.
	 */

	while (1) {
		switch (p_sema_v_lock(&stp->sd_plumb, STOPRI | PCATCH, &stp->sd_mutex)) {
		case 0:		/* due to v_sema */
			break;
		case STOPPED:	/* job control */
			s = p_lock(&stp->sd_mutex, SPLSTR);
			continue;
		case PCATCH:	/* due to interrupt */
			/* Wake up any waiting processes */
			s = p_lock(&stp->sd_mutex, SPLSTR);
			stp->sd_flag &= ~STWOPEN;
			vall_sema(&stp->sd_open);	
			v_lock(&stp->sd_mutex, s);
			return(EINTR);		/* no locks held */
		}
		break;
	} 

	s = p_lock(&stp->sd_mutex, SPLSTR);
	
	if (stp->sd_flag & (STRHUP | STRERR)) {
		/* Wake up any waiting processes, unlock and return */
		stp->sd_flag &= ~STWOPEN;
		v_sema(&stp->sd_plumb);
		vall_sema(&stp->sd_timer2);
		vall_sema(&stp->sd_open);	
		v_lock(&stp->sd_mutex, s);
		return(EIO);
	}
	
	/* 
	 * Set up to test if open creates a controlling tty 
	 * Save open flags of device open for O_NOCTTY information.
	 */
	stp->sd_openflag = flag;
	if (!(flag & FNOCTTY) && (p->p_sid == p->p_pid) &&
		(!p->p_session->s_ctty) && (!stp->sd_ctty.c_session)) {
		/*
		 * We are session leader and
		 * there is no current controlling tty or
		 * its has been relinquished then permitt requests to
		 * establish a controlling tty.
		 *
		 * Since we are the only process that can change p_session
		 * we don't need to lock sess_list.
		 */
#ifdef DEBUG
		if (jc_debug)
			printf("stropen(): pid %d new ctty flag=0x%x?\n",
							p->p_pid, flag);
#endif
		stp->sd_flag |= CTTYFLG;
		u.u_ctty = 0;
	} else {
		stp->sd_flag &= ~CTTYFLG;
#ifdef DEBUG
		if ((flag & FNOCTTY) && (jc_debug))
			printf("stropen(): O_NOCTTY set\n");
#endif
	}
	
	/*
	 * Open all modules and devices down stream to notify
	 * that another user is streaming.  For modules, set
	 * the last argument to MODOPEN and do not pass any
	 * open flags.  The following open may block within
	 * the driver so the sd_mutex must be released first.
	 */
	
	qp = stp->sd_wrq;
	v_lock(&stp->sd_mutex, s);
	
	while (qp = qp->q_next) {
		if ((*RD(qp)->q_qinfo->qi_qopen)(RD(qp), sp->s_dev,
						 (qp->q_next ? 0 : flag),
						 (qp->q_next ? MODOPEN : 0)) == OPENFAIL) {
			if (!u.u_error)	{
				u.u_error = ENXIO;
			}
			break;
		}
	}
	
	s = p_lock(&stp->sd_mutex, SPLSTR);
	
	/* Increment refcnt & assign process group if controlling tty */
	if (!u.u_error) {
		stp->sd_refcnt++;
		if ((stp->sd_flag & CTTYFLG) && u.u_ctty) {
#ifdef DEBUG
			if (jc_debug)
				printf("stropen(): pid %d new_ctty for sid %d\n", p->p_pid, p->p_sid);
#endif
			new_ctty(p, STOV(sp), &stp->sd_ctty);
		}
	}
	
	/* Wake up any waiting processes, unlock and return */
	stp->sd_flag &= ~(STWOPEN | CTTYFLG);
	v_sema(&stp->sd_plumb);	
	vall_sema(&stp->sd_timer2);
	vall_sema(&stp->sd_open);	
	v_lock(&stp->sd_mutex, s);
	return(u.u_error);

 first_open:

	/* The stream is locked at this point. */

	stp->sd_ctty.c_session = NULL;
	stp->sd_ctty.c_sid = 0;
	stp->sd_ctty.c_pgrp = 0;
	stp->sd_refcnt = 0;
	stp->sd_error = 0;
	stp->sd_wroff = 0;
	stp->sd_iocwait = 0;
	stp->sd_iocblk = NULL;
	stp->sd_pushcnt = 0;
	stp->sd_siglist = NULL;
	stp->sd_pollist = NULL;
	stp->sd_sigflags = 0;
	stp->sd_pollflags = 0;
	setq(qp, &strdata, &stwdata);
	qp->q_ptr = WR(qp)->q_ptr = (caddr_t)stp;
	qp->q_str = WR(qp)->q_str = stp;
	stp->sd_strtab = DEV_STREAMTAB(sp->s_devp);
	init_sema(&stp->sd_freeze, 0);
	init_sema(&stp->sd_read, 0);
	init_sema(&stp->sd_write, 0);
	init_sema(&stp->sd_timer, 0);
	init_sema(&stp->sd_timer2, 0);
	init_sema(&stp->sd_timer3, 0);
	init_sema(&stp->sd_open, 0);
	init_sema(&stp->sd_plumb, 0);
	init_sema(&stp->sd_waitbuf, 0);
	init_sema(&stp->sd_vhangup, 0);

	/* Setup to find controlling tty */
	stp->sd_openflag = flag;
	if (!(flag & FNOCTTY) && (p->p_sid == p->p_pid) &&
			(!p->p_session->s_ctty) && (!stp->sd_ctty.c_session)) {
		/* 
		 * We are session leader and
		 * there is no current controlling tty or
		 * its has been relinquished then permitt requests to
		 * establish a controlling tty.
		 */
#ifdef DEBUG
		if (jc_debug)
			printf("stropen(): pid %d new ctty? flag=0x%x\n", 
					p->p_pid,flag);
#endif
		stp->sd_flag |= CTTYFLG;
		u.u_ctty = 0;
	} 

	/*
	 * Create a stream to the driver by attaching a queue pair.
	 * This routine will return NULL if unable to allocate queues. 
	 */

	driver_q = qattach(stp->sd_strtab, qp);

	/*
	 * Open the attached module or driver.  The open may sleep, but it
	 * must always return here.  Therefore, all sleeps must set PCATCH or
	 * ignore all signals to avoid a longjump if a signal arrives.
	 *
	 * The stream lock must be dropped since the open may block.  This is
	 * why STWOPEN flag is set: to block all subsequent opens.
	 */

	stp->sd_flag |= STWOPEN;

	if (driver_q) {
		driver_q->q_str = stp;
		WR(driver_q)->q_str = stp;
		v_lock(&stp->sd_mutex, s);
		if ((*driver_q->q_qinfo->qi_qopen)(driver_q, sp->s_dev, flag, 0) == OPENFAIL) {
			disable_procs(driver_q);
			freeq(driver_q);
			driver_q = NULL;
		} 
		(void) p_lock(&stp->sd_mutex, SPLSTR);
	}

	/* Do error unwind if open failed */

	if (!driver_q || (stp->sd_flag & (STRHUP | STRERR))) {
		stp->sd_flag |= STRHUP;
		v_lock(&stp->sd_mutex, s);

		/* If the device was successfully opened, close it now */
		if (driver_q) {
			driver_q->q_flag |= QPROCENAB;	/* for qdetach() */
			qdetach(driver_q, FNDELAY);
		}

		/* Close the stream head and free the stream */
		release_stream(stp, sp);
		if (!u.u_error) {
			u.u_error = ENXIO;
		}
		return(u.u_error);
	}

	/* Assign process group if controlling tty */

	if ((stp->sd_flag & CTTYFLG) && u.u_ctty) {
#ifdef DEBUG
		if (jc_debug)
			printf("stropen():pid %d new_ctty for sid %d\n",
					p->p_pid,p->p_sid);
#endif
		new_ctty(p, STOV(sp), &stp->sd_ctty);
	}

	stp->sd_flag &= ~(STWOPEN | CTTYFLG);
	stp->sd_refcnt = 1;
	driver_q->q_flag |= QPROCENAB;	/* for synch driver close */

	/* Wake up any waiting processes, unlock and return */
	v_sema(&stp->sd_plumb);	  /* enables subsequent plumbers */
	vall_sema(&stp->sd_open);
	v_lock(&stp->sd_mutex, s);
	if (u.u_error == 0) {
		if (str_autop(stp, sp))
			strclose(sp, flag);
	}
	return(u.u_error);
}




/*
 * Close a stream.  
 * This is called from closef() when the last reference to the fp is going
 * away.  Strclean() will already have removed the siglist and pollist
 * information, so all that remains is to remove all multiplexor links for the
 * stream, pop all the modules (and the driver), and free the stream
 * structure.
 *
 * Must be called at SPL0.
 * The snode assumed unlocked.
 */

strclose(sp, flag)
	struct snode *sp;
	register flag;		/* NDELAY flag */
{
	register spl_t spl0;
	register struct stdata *stp;
	register queue_t *wrq;
	register struct session *s;
	register int id;
	extern int strtime();

	stp = sp->s_sptr;
	ASSERT_DEBUG(stp, "streamio: strclose: null stream");
	/* 
	 * Decrement the refcnt and check if this is the last close.  If not,
	 * then there is nothing more to be done and this procedure returns.
	 */
	spl0 = p_lock(&stp->sd_mutex, SPLSTR);

 checkrefs:
	if (stp->sd_refcnt > 1) {
		--stp->sd_refcnt;
		v_lock(&stp->sd_mutex, spl0);
		return;
	}
	/* 
	 * Synchronize with any open-in-progress via the WOPEN flag.
	 * Since close must always succeed, interrupts are ignored.
	 */
	if (stp->sd_flag & STWOPEN) {
		(void) p_sema_v_lock(&stp->sd_open, PZERO, &stp->sd_mutex);
		(void) p_lock(&stp->sd_mutex, SPLSTR);
		goto checkrefs;
	} 

	/*
	 * At this point sd_refcnt = 1.
	 * This is the last close.  Thus, there are no previous
	 * references to this stream except this one.  However, there
	 * may be succeeding opens racing with this operation, so the
	 * STWOPEN flag is used to block these out (the stream lock
	 * must be released soon for other reasons and hence the need
	 * to set this.  Note that this is not unset explicitly in
	 * this routine, rather this happens implicitly when the
	 * stream is freed/reallocated.
	 */

	stp->sd_flag |= STWOPEN;

	/* 
	 * Relinquish the controlling tty association.
	 */
	if ((s = stp->sd_ctty.c_session) != NULL) {
		/*
		 *  Wakeup any remaining pollers polling via /dev/tty.
		 */
		strwakepoll(stp, POLLERR);	/* wake up pollers */
		if (s->s_ctty == &stp->sd_ctty) {
			(void)p_lock(&s->s_lock, SPLSESS);
			v_lock(&stp->sd_mutex, SPLSESS);	/* note spl */
			relinquish_ctty(s);	/* session unlock on return */
		} else
			v_lock(&stp->sd_mutex, spl0);
	} else
		v_lock(&stp->sd_mutex, spl0);

	/* Release any event cell still linked due to poll optimization */
	if (stp->sd_pollist) {
		ASSERT_DEBUG(stp->sd_pollist->se_next == NULL, 
			     "strclose: stream has extra cells");
		sefree(stp->sd_pollist);
	}

	/* STWOPEN protects us now; unlink all the muxed streams */
	munlinkall(stp, 1);

	/* 
	 * Close and detach each module on the stream.  Each module is allowed
	 * to drain for a while before calling close on it, unless NDELAY is
	 * set in which case the close is done immediately.
	 */
 
	wrq = stp->sd_wrq;
	while (wrq->q_next) {
		if (!(flag & FNDELAY)) {
			(void) p_lock(&stp->sd_mutex, SPLSTR);
			/* drain any data on the wrq */
			if (wrq->q_next->q_count) {
				stp->sd_flag |= STRTIME;
				id = timeout(strtime, (caddr_t) stp, (int) STRTIMOUT * HZ);
				while ((stp->sd_flag & STRTIME) && wrq->q_next->q_count) {
					/* ensure strwsrv gets enabled */
					wrq->q_next->q_flag |= QWANTW;
					/*
					 * Block until unblocked by
					 * strwsrv() or strtime() or
					 * interrupts. 
					 */
					switch (p_sema_v_lock(&stp->sd_timer, STOPRI | PCATCH, &stp->sd_mutex)) {
					case 0:		/* due to v_sema */
					case STOPPED:	/* due to job control */
						(void) p_lock(&stp->sd_mutex, SPLSTR);
						continue;
					case PCATCH:	/* due to interrupt */
						(void) p_lock(&stp->sd_mutex, SPLSTR);
						break;		/* out of the switch */
					}
					break;			/* out of the while */
				}
				stp->sd_flag &= ~STRTIME;
				v_lock(&stp->sd_mutex, spl0);
				untimeout(id);	/* must be outside lock */
			} else {
				v_lock(&stp->sd_mutex, spl0);
			}
		}
		/* call close on the next module and detach it */
		qdetach(RD(wrq->q_next), flag);
	}
	
	/* Close the stream head and free the stream */
	release_stream(stp, sp);
}



/*
 * Clean up after a process when it closes a stream.  This is called
 * from closef for all closes.  The pollist and siglist are scanned for
 * entries for the current process, and these are removed.
 *
 * The snode may be locked or unlocked.
 */

strclean(sp)
	struct snode *sp;
{
	register struct strevent *sep, *psep, *tsep;
	struct stdata *stp;
	register spl_t s;
	
	stp = sp->s_sptr;
	psep = NULL;

	s = p_lock(&stp->sd_mutex, SPLSTR);

	sep = stp->sd_siglist;
	while (sep) {
		if (sep->se_procp == u.u_procp) {
			tsep = sep->se_next;
			if (psep) {
				psep->se_next = tsep;
			} else {
				stp->sd_siglist = tsep;
			}
			sefree(sep);
			sep = tsep;
		} else {
			psep = sep;
			sep = sep->se_next;
		}
	}

	psep = NULL;
	sep = stp->sd_pollist;
	while (sep) {
		if (sep->se_procp == u.u_procp) {
			tsep = sep->se_next;
			if (psep) {
				psep->se_next = tsep;
			} else {
				stp->sd_pollist = tsep;
			}
			sefree(sep);
			sep = tsep;
		} else {
			psep = sep;
			sep = sep->se_next;
		}
	}
	v_lock(&stp->sd_mutex, s);
}



/*
 * Get the next message from the read queue.  If the message is priority,
 * STRPRI will have been set by strrput().  This flag should be reset only
 * when the entire message at the front of the queue as been consumed.
 *
 * Assumes snode is unlocked.
 */

int
strgetmsg(sp, mctl, mdata, flag, ioflag)
	register struct snode *sp;
	register struct strbuf *mctl;		/* user supplied param */
	register struct strbuf *mdata;		/* user supplied param */
	register flag;				/* user supplied param */
	register ioflag;	/* IO_NDELAY, IO_NOTOSTOP, and IO_GENTTY */
{
	register spl_t s;
	register struct stdata *stp;
	register mblk_t *bp, *nbp;
	register queue_t *rq;
	register mblk_t *savemp = NULL;
	register mblk_t *savemptail = NULL;
	int rflag;
	int flg = 0;
	register more = 0;
	register n;
	register bcnt;
	register char *ubuf;

	stp = sp->s_sptr;
	ASSERT_DEBUG(stp, "strgetmsg: null stream");

	if (ncopyin((char *) flag, (char *) &rflag, sizeof(int), STRINT)) {
		return(EFAULT);
	}
	if (rflag & ~RS_HIPRI) {
		return(EINVAL);
	}

	s = p_lock(&stp->sd_mutex, SPLSTR);

	if (stp->sd_flag & (STRERR | STPLEX)) {
		u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}

	/*
	 * If this stream is someone's ctty then check fgr/bgr permissions.
	 * IO_NOTOSTOP is used by /dev/console for daemon error logging.
	 * If sleeping sd_mutex is droppped.
	 */
	while (stp->sd_ctty.c_session && !(ioflag & IO_NOTOSTOP)) {
		switch (ctty_read(&stp->sd_ctty, &stp->sd_mutex, ioflag)) {
		case CTTY_ERR:		/* return error */
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		case CTTY_SIG:		/* signaled out */
			v_lock(&stp->sd_mutex, s);
			return(EINTR);
		case CTTY_SLEPT:
			if (stp->sd_flag & (STRERR | STPLEX)) {
				u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			continue;
		case CTTY_OK:		/* all ok */
			break;
		}
		break;
	}

	/* 
	 * If RS_HIPRI then PCPROTO msg is needed.  This is indicated by the
	 * STRPRI flag.  If not RS_HIPRI then a normal msg will do.  Wait
	 * until the appropriate one of these two conditions is satisfied.
	 */

	rq = RD(stp->sd_wrq);

	while (((rflag & RS_HIPRI) && !(stp->sd_flag & STRPRI)) ||
	       !(bp = l_getq(rq))) {

		/* If STRHUP, return 0 length control and data. */
		if (stp->sd_flag & STRHUP) {
			mctl->len = mdata->len = 0;
			v_lock(&stp->sd_mutex, s);
			u.u_r.r_val1 = 0;
			if (ncopyout((char *) &flg, (char *) flag, sizeof(int), STRINT)) {
				u.u_error = EFAULT;
			}
			return(u.u_error);
		} 
		if (ioflag & IO_NDELAY) {
			v_lock(&stp->sd_mutex, s);
			return(EAGAIN);
		}
		/*
		 * Since we have not released the stream lock (sd_mutex) since
		 * calling ctty_read(), it is not necessary to conduct new
		 * checks for the controlling tty.
		 *
		 * While we hold the stream lock, it is not possible for the
		 * controlling tty deallocation code to completely deallocate
		 * the tty (that code will be blocked on sd_mutex).  When it
		 * acquires sd_mutex, it will vall_sema() the sd_read semaphore
		 * and awaken us, causing us to call ctty_read() again, at
		 * which point we will see that the controlling terminal
		 * has been relinquished.
		 */
		if (p_sema_v_lock(&stp->sd_read, STIPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
			return(EINTR);
		}

		s = p_lock(&stp->sd_mutex, SPLSTR);

		/* Must recheck errors since lock was dropped */
		if (stp->sd_flag & (STRERR | STPLEX)) {
			u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		}
	}
	
	if (bp->b_datap->db_type == M_PASSFP) {
		l_putbq(rq, bp);
		v_lock(&stp->sd_mutex, s);
		return(EBADMSG);
	}

	/* Set HIPRI flag if message is priority. */
	if (stp->sd_flag & STRPRI) {
		flg |= RS_HIPRI;
	}
	v_lock(&stp->sd_mutex, s);

	/* First process PROTO or PCPROTO blocks, if any. */

	if (mctl->maxlen >= 0 && bp && bp->b_datap->db_type != M_DATA) {
		bcnt = mctl->maxlen;
		ubuf = mctl->buf;
		while (bp && bp->b_datap->db_type != M_DATA && bcnt >= 0) {
			if ((n = min((uint) bcnt, (uint) (bp->b_wptr - bp->b_rptr))) &&
			     copyout((char *) bp->b_rptr, ubuf, (uint) n)) {
				u.u_error = EFAULT;
				more = 0;
				freemsg(bp);
				s = p_lock(&stp->sd_mutex, SPLSTR);
				if (!(rq->q_first && (rq->q_first->b_datap->db_type == M_PCPROTO))) {
					stp->sd_flag &= ~STRPRI;
				}
				goto getmout;	/* unlocks and returns */
			}
			ubuf += n;
			bp->b_rptr += n;
			if (bp->b_rptr >= bp->b_wptr) {
				nbp = bp;
				bp = bp->b_cont;
				freeb(nbp);
			}
			if ((bcnt -= n) <= 0) {
				break;
			}
		}
		mctl->len = mctl->maxlen - bcnt;
	} else {	
		mctl->len = -1;
	}
		
	if (bp && bp->b_datap->db_type != M_DATA) {	
		/* More PROTO blocks in msg. */
		more |= MORECTL;
		savemp = bp;
		while (bp && bp->b_datap->db_type != M_DATA) {
			savemptail = bp;
			bp = bp->b_cont;
		}
		savemptail->b_cont = NULL;
	}

	/* Now process DATA blocks, if any. */

	if (mdata->maxlen >= 0 && bp) {
		bcnt = mdata->maxlen;
		ubuf = mdata->buf;
		while (bp && bcnt >= 0) {
			if ((n = min((uint) bcnt, (uint) (bp->b_wptr - bp->b_rptr))) &&
			     copyout((char *) bp->b_rptr, ubuf, (uint) n)) {
				u.u_error = EFAULT;
				more = 0;
				freemsg(bp);
				s = p_lock(&stp->sd_mutex, SPLSTR);
				if (!(rq->q_first && (rq->q_first->b_datap->db_type == M_PCPROTO))) {
					stp->sd_flag &= ~STRPRI;
				}
				goto getmout;	/* unlocks and returns */
			}
			ubuf += n;
			bp->b_rptr += n;
			if (bp->b_rptr >= bp->b_wptr) {
				nbp = bp;
				bp = bp->b_cont;
				freeb(nbp);
			}
			if ((bcnt -= n) <= 0) {
				break;
			}
		}
		mdata->len = mdata->maxlen - bcnt;
	} else {
		mdata->len = -1;
	}

	if (bp) {
		/* more data blocks in msg */
		more |= MOREDATA;
		if (savemp) {
			savemptail->b_cont = bp;
		} else {
			savemp = bp;
		}
	} 

	s = p_lock(&stp->sd_mutex, SPLSTR);

	if (savemp) {
		l_putbq(rq, savemp);
	} else {
		if (!(rq->q_first && (rq->q_first->b_datap->db_type == M_PCPROTO))) {
			stp->sd_flag &= ~STRPRI;
		}
	}

	/*
	 * Getmsg cleanup processing - if the state of the queue has changed
	 * some signals may need to be sent and/or poll awakened.
	 */

 getmout:	/* assumes stream is already locked at this point */

	/* Must recheck errors since lock was dropped */
	if (stp->sd_flag & (STRERR | STPLEX)) {
		u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}
	while ((bp = rq->q_first) && (bp->b_datap->db_type == M_SIG)) {
		bp = l_getq(rq);
		switch (*bp->b_rptr) {
		case SIGPOLL:
			if (stp->sd_sigflags & S_MSG) {
				strsendsig(stp->sd_siglist, S_MSG);
			}
			break;
		default:
			if (stp->sd_ctty.c_pgrp) {
				signal(stp->sd_ctty.c_pgrp, (int) (*bp->b_rptr));
			}
			break;
		}
		freemsg(bp);
	}

	/*
	 * If we have just received a high priority message and a regular
	 * message is now at the front of the queue, send signals in S_INPUT
	 * processes and wake up processes polling on POLLIN.
	 */

	if (rq->q_first && !(stp->sd_flag & STRPRI) && (flg & RS_HIPRI)) {
		if (stp->sd_sigflags & S_INPUT) {
			strsendsig(stp->sd_siglist, S_INPUT);
		}
		if (stp->sd_pollflags & POLLIN) {
			strwakepoll(stp, POLLIN);
		}
	}
	v_lock(&stp->sd_mutex, s);

	/* copy the flag argument back out */
	if (!u.u_error) {
		if (ncopyout((char *) &flg, (char *) flag, sizeof(int), STRINT)) {
			return(EFAULT);
		}
	}
	u.u_r.r_val1 = more;
	return(u.u_error);
}


/*
 * Read a stream according to the mode flags in sd_flag:
 *
 * (default mode)              - Byte stream, msg boundries are ignored
 * RMSGDIS (msg discard)       - Read on msg boundries and throw away 
 *                               any data remaining in msg
 * RMSGNODIS (msg non-discard) - Read on msg boundries and put back
 *		                 any remaining data on head of read queue
 *
 * Consume readable messages on the front of the queue until u.u_count
 * is satisfied, the readable messages are exhausted, or a message
 * boundary is reached in a message mode.  If no data was read and
 * the stream was not opened with the NDELAY flag, block until data arrives.
 * Otherwise return the data read and update the count.
 *
 * In default mode a 0 length message signifies end-of-file and terminates
 * a read in progress.  The 0 length message is removed from the queue
 * only if it is the only message read (no data is read).
 *
 * Attempts to read an M_PROTO or M_PCPROTO message results in an 
 * EBADMSG error return.
 *
 * Assumes snode is unlocked.
 */

int
strread(sp, uio, ioflag)
	struct snode *sp;
	struct uio *uio;	/* for copying data */
	register ioflag;	/* IO_NDELAY, IO_NOTOSTOP, and IO_GENTTY */
{
	register spl_t s;
	register struct stdata *stp;
	register mblk_t *bp, *nbp, *mp;
	register queue_t *rq;
	register n;
	long *rd_count;
	register long sav_resid;
 	register partial_read, got_bp, finish_tbd;
	register id = 0;
	extern int strvtime();

	stp = sp->s_sptr;
	ASSERT_DEBUG(stp, "streamio: strread: null stream");

	s = p_lock(&stp->sd_mutex, SPLSTR);

	if (stp->sd_flag & (STRERR | STPLEX)) {
		u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}
	rq = RD(stp->sd_wrq);
	v_lock(&stp->sd_mutex, s);

	/* 
	 * The following loop usually terminates when:
	 *	uio_resid == 0, OR when error cases occur.
	 * 	But in various read modes, it may terminate earlier.
	 */

	finish_tbd = partial_read = FALSE;
	bp = NULL;
	sav_resid = uio->uio_resid;

	while (1) {
		got_bp = FALSE;
		while (!got_bp) {
			s = p_lock(&stp->sd_mutex, SPLSTR);

			/* Cleanup previous iteration's leftover processing */
			if (finish_tbd) {
				/*
				 * The data may have been the leftover of a
				 * M_PCPROTO.  If done with it and if next
				 * message on the queue is not M_PCPROTO, unset
				 * the STRPRI flag.
				 */
				
				if (!bp) {
					if (!(rq->q_first && 
					      (rq->q_first->b_datap->db_type == M_PCPROTO))) {
						stp->sd_flag &= ~STRPRI;
					}
				} else {
					/* 
					 * Have remaining data in message.  Free
					 * msg if in discard mode.
					 */
					if (stp->sd_flag & RMSGDIS) {
						freemsg(bp);
						if (!(rq->q_first && 
						      (rq->q_first->b_datap->db_type == M_PCPROTO))) {
							stp->sd_flag &= ~STRPRI;
						}
					} else {
						l_putbq(rq, bp);
					}
				}
				
				/* Must recheck errors since lock was dropped */
				if (stp->sd_flag & (STRERR | STPLEX)) {
					u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
				
				/*
				 * Check for signal messages at the front of the read
				 * queue and generate the signal(s) if appropriate.  The
				 * only signal that can be on queue is M_SIG at this
				 * point.
				 */
				
				while (((bp = rq->q_first)) && (bp->b_datap->db_type == M_SIG)) {
					bp = l_getq(rq);
					switch (*bp->b_rptr) {
					case SIGPOLL:				
						if (stp->sd_sigflags & S_MSG) {
							strsendsig(stp->sd_siglist, S_MSG);
						}
						break;
					default:
						if (stp->sd_ctty.c_pgrp) {
							signal(stp->sd_ctty.c_pgrp, (int) (*bp->b_rptr));
						}
						break;
					}
					freemsg(bp);
				}

				/* 
				 * Check to see if read call is finished:
				 *	in byte-stream mode -> done if uio_resid = 0
				 *	in msg-discard mode -> done
				 *	in msg-non-discard  -> done
				 */

				if ((uio->uio_resid == 0) ||
				    (stp->sd_flag & (RMSGDIS | RMSGNODIS))) {
					/* we're done! */
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
				finish_tbd = FALSE;
			} else {
				/* Must recheck errors since lock was dropped */
				if (stp->sd_flag & (STRERR | STPLEX)) {
					u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
			}

			/*
			 * If this stream is someones ctty then check fgr/bgr
			 * permissions.  IO_NOTOSTOP is used by /dev/console
			 * for daemon logging.  If sleeping sd_mutex is dropped.
			 */
			while (stp->sd_ctty.c_session && !(ioflag & IO_NOTOSTOP)) {
				switch (ctty_read(&stp->sd_ctty, &stp->sd_mutex,
						  ioflag)) {
				case CTTY_ERR:		/* return error */
					v_lock(&stp->sd_mutex, s);
					STR1PRINTF("strread:ctty end 1\n");
					return(u.u_error);
				case CTTY_SIG:		/* signaled out */
					v_lock(&stp->sd_mutex, s);
					STR1PRINTF("strread:ctty end 2\n");
					return(EINTR);
				case CTTY_SLEPT:	/* all ok */
					if (stp->sd_flag & (STRERR | STPLEX)) {
						u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
						v_lock(&stp->sd_mutex, s);
						return(u.u_error);
					}
					continue;
				case CTTY_OK:		/* all ok */
					break;
				}
				break;
			}

			if (!(bp = l_getq(rq))) {
				if ((stp->sd_flag & STRHUP)) {
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
				if (ioflag & IO_NDELAY) {
					if (!partial_read) {
						u.u_error = ((stp->sd_flag & OLDNDELAY) ? 0 : EAGAIN);
					}
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				} else if (partial_read && !(stp->sd_flag & TTYVMODE)) {
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}

				/*
				 * If any module downstream has requested read notification
				 * by setting SNDMREAD flag using M_SETOPTS, send a message
				 * down stream.
				 */

				if (stp->sd_flag & SNDMREAD) {
					v_lock(&stp->sd_mutex, s);
					if ((mp = getrdbuf(stp)) == NULL) {
						(void) p_lock(&stp->sd_mutex, SPLSTR);
						if (partial_read || ((u.u_error == EAGAIN) && (stp->sd_flag & OLDNDELAY))) {
							u.u_error = 0;
						}
						v_lock(&stp->sd_mutex, s);
						return(u.u_error);
					} else {
						/* send the number of bytes requested by the
						 * read as the argument to M_READ
						 */
						mp->b_datap->db_type = M_READ;
						rd_count = (long *)mp->b_wptr;
						*rd_count = (long)uio->uio_resid;
						mp->b_wptr += sizeof(long);
						putnext(stp->sd_wrq, mp);
						if (rq->q_first != NULL) {
							/* don't sleep */
							continue;  
						}
					}
					(void) p_lock(&stp->sd_mutex, SPLSTR);
					/*
					 * Must recheck errors/hup/ctty since
					 * lock was dropped.
					 */
					if (stp->sd_flag & (STRERR | STPLEX)) {
						u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
						v_lock(&stp->sd_mutex, s);
						return(u.u_error);
					}
					if (stp->sd_flag & STRHUP) {
						v_lock(&stp->sd_mutex, s);
						return(u.u_error);
					}
					if ((ioflag & IO_GENTTY) &&
					    !u.u_procp->p_session->s_vp) {
						v_lock(&stp->sd_mutex, s);
						return(EIO);
					}
				}

				/* 
				 * At this point the read has to wait.  Must check for
				 * completion if VMIN/VTIME processing is on.  If not
				 * complete then trigger off vtimer if appropriate.  
				 *
				 *  if in VMODE:
				 *      if vmin = 0, vtime = 0 -> done.
				 * 	if vmin = 0, vtime > 0 -> if timer expired
				 *	if vmin > 0, vtime = 0 -> if vmin bytes received
				 *	if vmin > 0, vtime > 0 -> if vmin bytes received
				 *				  or if timer expired
				 */
				
				if (stp->sd_flag & TTYVMODE) {
					if (stp->sd_vmin) {
						if (((sav_resid - uio->uio_resid) >= (long)stp->sd_vmin) ||
						    (id && stp->sd_vtime && !(stp->sd_flag & STRVTIME))) {
							/* we're done! */
							v_lock(&stp->sd_mutex, s);
							return(u.u_error);
						}
					} else { /* vmin is 0 */
						if (!stp->sd_vtime ||
						    partial_read ||
						    (id && !(stp->sd_flag & STRVTIME))) {
							/* we're done! */
							v_lock(&stp->sd_mutex, s);
							return(u.u_error);
						}
					}
					/* 
					 * Set vtimer if vtime > 0 and:
					 *  if vmin == 0 or
					 *  at least one byte received
					 *  		(interval timer)
					 */
					if (stp->sd_vtime && (!stp->sd_vmin || partial_read)) {
						/* set timer to VTIME * 0.1 secs */
						stp->sd_flag |= STRVTIME;
						id = timeout(strvtime, (caddr_t)stp, (int)stp->sd_vtime * VHZ);
					}
				}

				/*
				 * Since we have not released the stream lock
				 * (sd_mutex) since calling ctty_read(), (or
				 * in the SNDMREAD case have verified upon
				 * reacquiring the stream lock that if we were
				 * called from /dev/tty, that we still have a
				 * controlling tty), it is not necessary to
				 * conduct new checks for the controlling tty.
				 *
				 * While we hold the stream lock, it is not
				 * possible for the controlling tty deallocation
				 * code to completely deallocate the tty (that
				 * code will be blocked on sd_mutex).  When it
				 * acquires sd_mutex, it will vall_sema() the
				 * sd_read semaphore and awaken us, causing us
				 * to call ctty_read() again, at which point
				 * we will see that the controlling terminal
				 * has been relinquished.
				 */
				if (p_sema_v_lock(&stp->sd_read, STIPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
					if (id && stp->sd_flag & STRVTIME) {
						untimeout(id);
					}
					/*
					 * POSIX 1003.1-1988 with FIPS 151
					 * requires that "If a read() is
					 * interrupted by a signal after it
					 * has successfully read some data,
					 * it shall return the number of
					 * bytes the system has read."
					 */
					if (partial_read &&
					              u.u_ptype != BCS_PROC)
						return(u.u_error);
					return(EINTR);
				}
				if (id && stp->sd_flag & STRVTIME) {
					untimeout(id);
				}
			} else {
				/* 
				 * bp is non-null and no freeze.
				 * Process the msg and prepare to unlock in order to do
				 * the copy.
				 */

				switch (bp->b_datap->db_type) {
					
				case M_DATA:
					if ((bp->b_wptr - bp->b_rptr) == 0) {
						/*
						 * If already read data put zero length
						 * message back on queue else free msg
						 * and return 0.
						 */
						if (partial_read) {
							l_putbq(rq, bp);
						} else {
							freemsg(bp);
						}
						v_lock(&stp->sd_mutex, s);
						return(u.u_error);
					}
					
					/* 
					 * Prepare to copy.  This must be done outside
					 * the stream lock.
					 */
					partial_read = TRUE;
					break;	/* out of switch */
				case M_PROTO:
				case M_PCPROTO:
				case M_PASSFP:
					/*
					 * Only data messages are readable.  Any others
					 * generate an error.
					 */
					l_putbq(rq, bp);
					v_lock(&stp->sd_mutex, s);
					return(EBADMSG);
				default:
					cmn_err(CE_NOTE, "strread: garbage on stream head queue\n");
					/*
					 *+ An unknown message type was found on the stream's
					 *+ read head queue.  The message has been discarded.
					 */
					freemsg(bp);
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
				v_lock(&stp->sd_mutex, s);
				got_bp = TRUE;
			}
		}

		/* Copy data to user space (must be done outside lock) */

		while (bp && (uio->uio_resid > 0)) {
			if (n = min((uint) uio->uio_resid, (uint) (bp->b_wptr - bp->b_rptr))) {
				if (uiomove((caddr_t) bp->b_rptr, n, UIO_READ, uio)) {
					freemsg(bp);
					return(EFAULT);
				}
			}
			bp->b_rptr += n;
			while (bp && (bp->b_rptr >= bp->b_wptr)) {
				nbp = bp;
				bp = bp->b_cont;
				freeb(nbp);
			} 
		}
		/* 
		 * The rest of the processing (e.g. re-queueing the remaining
		 * msg), must be done within the stream lock; so its done in the
		 * next iteration to avoid a lock round-trip.
		 */
		finish_tbd = TRUE;
	}
}




/*
 * Put a message downstream.
 *
 * Assumes snode is unlocked.
 */

int
strputmsg(sp, mctl, mdata, flag, ioflag)
	register struct snode *sp;
	register struct strbuf *mctl;		/* user supplied param */
	register struct strbuf *mdata;		/* user supplied param */
	register flag;				/* user supplied param */
	register ioflag;	/* IO_NDELAY, IO_NOTOSTOP, and IO_GENTTY */
{
	register struct stdata *stp;
	register spl_t s;
	register msgsize;
	register short rmin, rmax;
	register queue_t *q;
	register wroff, can_send;
	register mblk_t *mp;
	extern mblk_t *strmakemsg();

	stp = sp->s_sptr;
	ASSERT_DEBUG(stp, "strputmsg: null stream");

	/* Check for legal flag value */
	if ((flag & ~RS_HIPRI) || ((flag & RS_HIPRI) && (mctl->len < 0))) {
		return(EINVAL);
	}

	s = p_lock(&stp->sd_mutex, SPLSTR);

	if (stp->sd_flag & (STRHUP | STRERR | STPLEX)) {
		u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}

	/*
	 * Make sure ctl and data sizes together fall within the limits of the
	 * max and min receive packet sizes and do not exceed system limit.
	 */

	rmin = stp->sd_wrq->q_next->q_minpsz;
	rmax = stp->sd_wrq->q_next->q_maxpsz;
	wroff = stp->sd_wroff;

	/*
	 * If this stream is someones ctty then check fgr/bck permissions.
	 * IO_NOTOSTOP is used by /dev/console for daemon error logging.
	 * The lock is dropped if the process slept.
	 */
	while (stp->sd_ctty.c_session && !(ioflag & IO_NOTOSTOP)) {
		switch (ctty_write(&stp->sd_ctty,
				    stp->sd_tostop, &stp->sd_mutex, ioflag)) {
		case CTTY_ERR:		/* return error */
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		case CTTY_SIG:		/* signaled out */
			v_lock(&stp->sd_mutex, s);
			return(EINTR);
		case CTTY_SLEPT:	/* we stopped for a while */
			if (stp->sd_flag & (STRHUP | STRERR | STPLEX)) {
				u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			continue;
		case CTTY_OK:		/* all ok */
			break;
		}
		break;
	}

	if (rmax == INFPSZ) {
		rmax = strmsgsz;
	} else {
		rmax = min((uint) rmax, (uint) strmsgsz);
	}

	msgsize = mdata->len;
	if (msgsize < 0) {
		msgsize = 0;
		rmin = 0;	/* no range check for NULL data part */
	}
	if ((msgsize < rmin) || (msgsize > rmax) || (mctl->len > strctlsz)) {
		v_lock(&stp->sd_mutex, s);
		return(ERANGE);
	}

	v_lock(&stp->sd_mutex, s);

	if (!(mp = strmakemsg(mctl, mdata, wroff, flag, UIOSEG_USER, stp))) {
		return(u.u_error);
	}

	can_send = FALSE;
	while (!can_send) {
		s = p_lock(&stp->sd_mutex, SPLSTR);
		/* Must recheck errors since lock was dropped */
		if (stp->sd_flag & (STRERR | STRHUP | STPLEX)) {
			u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
			v_lock(&stp->sd_mutex, s);
			freemsg(mp);
			return(u.u_error);
		}
		
		/* Note q->next since this may change also after lock is dropped */
		q = stp->sd_wrq->q_next;
		
		/* 
		 * If the head wrq is freezing or if downstream flow-control
		 * is blocked (for normal pri case) then wait otherwise go
		 * ahead and prepare to send the message downstream.
		 */
		
		if ((stp->sd_wrq->q_flag & QFREEZE) || 
		    (!(flag & RS_HIPRI) && !l_canput(q))) {

			/* if normal msg and no delay then return error */
			if (!(flag & RS_HIPRI) && (ioflag & IO_NDELAY)) {
				v_lock(&stp->sd_mutex, s);
				freemsg(mp);
				return(EAGAIN);
			}
			
			/*
			 * Prior to sleeping on sd_write, we must check to see
			 * if our job-control checks raced with our controlling
			 * tty being relinquished.
			 *
			 * While we hold the stream lock, it is not possible
			 * for the controlling tty deallocation code to
			 * completely deallocate the tty (that code will be
			 * blocked on sd_mutex).  When it acquires sd_mutex,
			 * it will vall_sema() the sd_write semaphore and
			 * awaken us, causing us to cycle around again, at
			 * which point we will see that the controlling
			 * tty has been relinquished.
			 */
			if ((ioflag & IO_GENTTY) &&
			    (u.u_procp->p_session->s_vp == NULL)) {
				v_lock(&stp->sd_mutex, s);
				freemsg(mp);
				return(EIO);
			}
			if (p_sema_v_lock(&stp->sd_write, STOPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
				freemsg(mp);
				return(EINTR);
			}
		} else {
			/*
			 * Everything OK, can send now.  The following
			 * is basically an inline version of putnext.
			 * Remember that q is already pointing to
			 * next_q from stream head.
			 */
			q->q_putcnt++;
			v_lock(&stp->sd_mutex, s);
			can_send = TRUE;
		}
	}
	
	/* Put block downstream */
	(*q->q_qinfo->qi_putp)(q, mp);
	
	/* 
	 * The rest of the processing (cleaning up the putnext), must be done
	 * within stream lock.
	 */

	s = p_lock(&stp->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&stp->sd_mutex, s);
	return(u.u_error);
}



/*
 * Write attempts to break the write request into messages conforming
 * with the minimum and maximum packet sizes set downstream.  
 *
 * Write will always attempt to get the largest buffer it can to satisfy the
 * message size. If it can not, then it will try up to 2 classes down to try
 * to satisfy the write. Write will not block if IO_NDELAY is set, otherwise it
 * will block waiting for the queue to get room if downstream queue is full.
 * 
 * A write of zero bytes gets packaged into a zero length message and sent
 * downstream like any other message.
 *
 * If buffers of the requested sizes are not available, the write will
 * sleep until the buffers become available.
 *
 * Write (if specified) will supply a write offset in a message if it
 * makes sense. This can be specified by downstream modules as part of
 * a M_SETOPTS message. Write will not supply the write offset if it
 * cannot supply any data in a buffer. In other words, write will never
 * send down a empty packet due to a write offset.
 *
 * The code which copies data from user to kernel space assumes that there
 * there is only one iov per uio.
 *
 * The snode is assumed unlocked.
 */

int
strwrite(sp, uio, ioflag)
	struct snode *sp;
	struct uio *uio;	/* for copying data */
	register ioflag;	/* IO_NDELAY, IO_NOTOSTOP, and IO_GENTTY */
{
	register spl_t s;
	register struct stdata *stp;
	register queue_t *q;
	register mblk_t *mp;
	register short rmin, rmax;
	register wroff, tempflag;
	register partial_write;
	register can_send;
	register save_resid;
	register put_tbd = FALSE;
	extern mblk_t *uiomakemsg();

	stp = sp->s_sptr;
	ASSERT_DEBUG(stp, "streamio: strwrite: null stream");

	s = p_lock(&stp->sd_mutex, SPLSTR);

	if (stp->sd_flag & (STRERR | STRHUP | STPLEX)) {
		u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL :
			     ((stp->sd_flag & STRHUP) ? EIO : stp->sd_error));
		/* For POSIX compatibility, EIO instead of ENXIO in STRHUP case */
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}

	/*
	 * Check the min/max packet size constraints.  If min packet size is
	 * non-zero, the write cannot be split into multiple messages and
	 * still guarantee the size constraints.
	 */
	rmin = stp->sd_wrq->q_next->q_minpsz;
	rmax = stp->sd_wrq->q_next->q_maxpsz;
	wroff = stp->sd_wroff;

	/*
	 * If this stream is someones ctty then check fgr/bck permissions.
	 * IO_NOTOSTOP is used by /dev/console for daemon error logging.
	 * The lock is dropped if the process slept.
	 */
	while (stp->sd_ctty.c_session && !(ioflag & IO_NOTOSTOP)) {
		switch (ctty_write(&stp->sd_ctty,
				    stp->sd_tostop, &stp->sd_mutex, ioflag)) {
		case CTTY_ERR:		/* return error */
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		case CTTY_SIG:		/* signaled out */
			v_lock(&stp->sd_mutex, s);
			return(EINTR);
		case CTTY_SLEPT:	/* we stopped for a while */
			if (stp->sd_flag & (STRERR | STRHUP | STPLEX)) {
				u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL :
					     ((stp->sd_flag & STRHUP) ? EIO : stp->sd_error));
				/* For POSIX compatibility, EIO instead of ENXIO in STRHUP case */
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			continue;
		case CTTY_OK:		/* all ok */
			break;
		}
		break;
	}

	ASSERT_DEBUG(rmax, "streamio: strwrite: rmax = 0");
	if (rmax == 0) {
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}
	if (rmax == INFPSZ) {
		rmax = strmsgsz;
	} else {
		rmax = min((uint) strmsgsz, (uint) rmax);
	}

	if ((rmin > 0) && ((uio->uio_resid < rmin) || (uio->uio_resid > rmax))) {
		v_lock(&stp->sd_mutex, s);
		return(ERANGE);
	}
	v_lock(&stp->sd_mutex, s);

	if (stp->sd_flag & OLDNDELAY) {
		tempflag = ioflag & ~IO_NDELAY;
	} else {
		tempflag = ioflag;
	}

	/* Loop until count satisfied or error. */

	partial_write = FALSE;
	save_resid = uio->uio_resid;
	q = NULL;
	do {
		/*
		 * Break write into several messages based on max
		 * packet size.
		 */
		
		if (!(mp = uiomakemsg(uio, wroff, stp, imin((int)uio->uio_resid, (int)rmax)))) {
			s = p_lock(&stp->sd_mutex, SPLSTR);
			if (put_tbd) {
				q->q_putcnt--;
				if (q->q_flag & QFREEZE) {
					(void)cv_sema(&q->q_str->sd_freeze);
				}
			}
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		}

		can_send = FALSE;
		while (!can_send) {
			s = p_lock(&stp->sd_mutex, SPLSTR);

			/* cleanup previous putnext logic if appropriate */
			if (put_tbd) {
				q->q_putcnt--;
				if (q->q_flag & QFREEZE) {
					(void)cv_sema(&q->q_str->sd_freeze);
				}
				put_tbd = FALSE;
			}
			
			/* Must recheck errors since lock was dropped */
			
			if (stp->sd_flag & (STRERR | STRHUP | STPLEX)) {
				u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL :
					     ((stp->sd_flag & STRHUP) ? EIO : stp->sd_error));
				/* For POSIX compatibility, EIO instead of ENXIO in STRHUP case */
				v_lock(&stp->sd_mutex, s);
				freemsg(mp);
				return(u.u_error);
			}
			
			/* Note q->next since this may change also after lock is dropped */
			q = stp->sd_wrq->q_next;
			
			/* 
			 * If the head wrq is freezing or if downstream flow-control is blocked
			 * then wait otherwise go ahead and prepare to send the message downstream.
			 */
			
			if ((stp->sd_wrq->q_flag & QFREEZE) || !l_canput(q)) {
				if ((tempflag & IO_NDELAY) &&
				    u.u_ptype != BCS_PROC) {
					v_lock(&stp->sd_mutex, s);
					freemsg(mp);
					if (!partial_write) {
						u.u_error = EAGAIN;
					} else {
						/* 
						 * Restore resid to pre-
						 * uiomakemsg() call state.
						 */
						uio->uio_resid = save_resid;
					}
					return(u.u_error);
				}

				/*
				 * Prior to sleeping on sd_write, we must check
				 * to see if our job-control checks raced with
				 * our controlling tty being relinquished.
				 *
				 * While we hold the stream lock, it is not
				 * possible for the controlling tty dealloca-
				 * tion code to completely deallocate the tty
				 * (that code will be blocked on sd_mutex).
				 * When it acquires sd_mutex, it will
				 * vall_sema() the sd_write semaphore and
				 * awaken us, causing us to cycle around again,
				 * at which point we will see that the
				 * controlling tty has been relinquished.
				 */
				if ((ioflag & IO_GENTTY) &&
				    (u.u_procp->p_session->s_vp == NULL)) {
					v_lock(&stp->sd_mutex, s);
					freemsg(mp);
					if (!partial_write) {
						u.u_error = EIO;
					} else {
						/* 
						 * Restore resid to pre-
						 * uiomakemsg() call state.
						 */
						uio->uio_resid = save_resid;
					}
					return(u.u_error);
				}
				if (p_sema_v_lock(&stp->sd_write,STOPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
					freemsg(mp);
					if (!partial_write) {
						u.u_error = EINTR;
					} else {
						/* 
						 * Restore resid to pre-
						 * uiomakemsg() call state.
						 */
						uio->uio_resid = save_resid;
					}
					return(u.u_error);
				}
			} else {
				/*
				 * Everything OK, can send now.  The following
				 * is basically an inline version of putnext.
				 * Remember that q is already pointing to
				 * next_q from stream head.
				 */
				q->q_putcnt++;
				v_lock(&stp->sd_mutex, s);
				can_send = TRUE;
			}
		}

		/* Put block downstream */
		(*q->q_qinfo->qi_putp)(q, mp);
		
		/* 
		 * The rest of the processing (e.g. cleaning up the putnext),
		 * must be done within stream lock; so its done in the next
		 * iteration to avoid a lock round-trip.
		 */

		put_tbd = TRUE; 
		partial_write = TRUE;
		save_resid = uio->uio_resid;
	} while (uio->uio_resid > 0);

	/* Finish any putnext cleanup remaining */
	if (put_tbd) {
		s = p_lock(&stp->sd_mutex, SPLSTR);
		q->q_putcnt--;
		if (q->q_flag & QFREEZE) {
			(void)cv_sema(&q->q_str->sd_freeze);
		}
		v_lock(&stp->sd_mutex, s);
	}
	return(u.u_error);
}


/*
 * Ioctl for streams.
 * 
 * The plumbing operations block on sd_plumb semaphore with a non-interruptible
 * priority.  The assume that the previous plumbing operation will, in fact,
 * complete.  Since at the very bottom all driver and modules open/close
 * operations must be written in an interruptible way, this is a safe
 * assumption (i.e. in the event of a hang, the process which is actually hung
 * within the driver open/close should be interrupted; this will in turn
 * unlock all the processes blocked on sd_plumb semaphore behind it.
 *
 * Assumes snode is unlocked.
 */

int
strioctl(sp, cmd, arg, flag)
	struct snode *sp;	
	int cmd;		/* user supplied param */
	int arg;		/* user supplied param */
	int flag;		/* fp->f_flag, FNOTOSTOP, and FGENTTY */
{
	register struct stdata *stp;
	register struct strioctl strioc;
	register spl_t s;
	register proc_t *p;
	register ret;
	pid_t pid;
	extern proc_t *pgrp_find();

	stp = sp->s_sptr;
	ASSERT_DEBUG(stp != NULL, "streamio: strioctl: null stream");
	
	switch (cmd) {
		
	case I_SENDFD:
		{
			register queue_t *qp;
			register mblk_t *mp;
			register struct strrecvfd *srf;
			struct file *fp;

			s = p_lock(&stp->sd_mutex, SPLSTR);

			if (stp->sd_flag & STRHUP) {
				v_lock(&stp->sd_mutex, s);
				return(ENXIO);
			}

			/* Check to see if this is a stream pipe */
			for (qp = stp->sd_wrq; qp->q_next; qp = qp->q_next)
				;
			if (!is_strpipe(qp)) {
				v_lock(&stp->sd_mutex, s);
				return(EINVAL);
			}
			v_lock(&stp->sd_mutex, s);

			GETFP(fp, arg);
			if (fp == NULL) {
				return(EBADF);
			}

			if (!canputnext(stp->sd_wrq) || 
			    !(mp = allocb(sizeof(struct strrecvfd), BPRI_MED))) {
				return(EAGAIN);
			}
			srf = (struct strrecvfd *)mp->b_rptr;
			mp->b_wptr += sizeof(struct strrecvfd);
			mp->b_datap->db_type = M_PASSFP;
			srf->f.fp = fp;
			srf->uid = u.u_uid;
			srf->gid = u.u_gid;
			FDBUMP(fp);	/* locks appropriate fs lock */
			putnext(stp->sd_wrq, mp);
			return(u.u_error);
		}

	case I_RECVFD:
		{
			register mblk_t *mp;
			register struct strrecvfd *srf;
			register i;
			register struct file *fp;

			/* This ioctl basically behaves like a receive call.  */

			s = p_lock(&stp->sd_mutex, SPLSTR);

			if (stp->sd_flag & (STRERR | STPLEX)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			while (!(mp = l_getq(RD(stp->sd_wrq)))) {
				if (stp->sd_flag & STRHUP) {
					v_lock(&stp->sd_mutex, s);
					return(ENXIO);
				}

				if (flag & FNDELAY) {
					v_lock(&stp->sd_mutex, s);
					return(EAGAIN);
				}
				
				/*
				 * Prior to sleeping on sd_read, we must check
				 * to see if we were called via /dev/tty, in
				 * which case we must check to see if the
				 * controlling tty is being relinquished.
				 *
				 * While we hold the stream lock (sd_mutex),
				 * it is not possible for the controlling tty
				 * deallocation code to completely deallocate
				 * the tty (that code will be blocked on
				 * sd_mutex).  When it acquires sd_mutex, it
				 * will vall_sema() the sd_read semaphore
				 * and awaken us, causing us to again check
				 * that we still have access to a controlling
				 * tty, at which point we will see that the
				 * controlling tty has been relinquished.
				 */
				if ((flag & FGENTTY) &&
				    (u.u_procp->p_session->s_vp == NULL)) {
					v_lock(&stp->sd_mutex, s);
					return(EIO);
				}
				if (p_sema_v_lock(&stp->sd_read, STIPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
					return(EINTR);
				}
				
				/*
				 * Must recheck errors/hup/ctty since
				 * lock was dropped.
				 */
				s = p_lock(&stp->sd_mutex, SPLSTR);
				if (stp->sd_flag & (STRERR | STPLEX)) {
					if (stp->sd_flag & STPLEX) {
						u.u_error = EINVAL;
					} else {
						u.u_error = stp->sd_error;
					}
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
				if ((flag & FGENTTY) &&
				    (u.u_procp->p_session->s_vp == NULL)) {
					v_lock(&stp->sd_mutex, s);
					return(EIO);
				}
			}
			if (mp->b_datap->db_type != M_PASSFP) {
				l_putbq(RD(stp->sd_wrq), mp);
				v_lock(&stp->sd_mutex, s);
				return(EBADMSG);
			}
			v_lock(&stp->sd_mutex, s);

			srf = (struct strrecvfd *)mp->b_rptr;
			if ((i = ofile_alloc(u.u_ofile_tab, 0)) < 0) {
				putbq(RD(stp->sd_wrq), mp);
				return(u.u_error);
			}
			fp = srf->f.fp;
			/*
			 * Don't install the fp until after we know
			 * everything's OK.  Otherwise, we can end up
			 * racing with a dup/close, fail the ncopyout
			 * and have fp nuked before we can send it back
			 * to ourselves.
			 */
			srf->f.fd = i;
			if (ncopyout((caddr_t)srf, (caddr_t)arg, sizeof(struct strrecvfd), STRRECVFD)) {
				u.u_error = EFAULT;
				srf->f.fp = fp;
				putbq(RD(stp->sd_wrq), mp);
				ofile_install(u.u_ofile_tab, i, (struct file *)NULL);
				return(u.u_error);
			}
			ofile_install(u.u_ofile_tab, i, fp);
			freemsg(mp);
			u.u_r.r_val1 = 0;	/* reset value set by ufalloc() */
			return(u.u_error);
		}

	case I_FDINSERT:
		{
			struct strfdinsert strfdinsert;
			struct file *resftp;
			struct snode *ressp;
			struct stdata *resstp;
			queue_t *q, *qnext;
			mblk_t *mp;
			register msgsize;
			register wroff;
			short rmin, rmax;
			extern mblk_t *strmakemsg();

			/* 
			 * Since two streams have to be locked, deadlock
			 * must be prevented - hence the order of these
			 * checks is important.
			 */

			if (ncopyin((caddr_t)arg, (caddr_t)&strfdinsert, 
						sizeof(strfdinsert), STRFDINSERT)) {
				return(EFAULT);
			}
			if (strfdinsert.offset < 0 || 
			    (strfdinsert.offset % sizeof(queue_t *)) != 0 ) {
				return(EINVAL);
			}

			/* Get the res stream pointer and validate it */
			if (!(resftp = getf(strfdinsert.fildes)) ||
			    !(ressp = getspstr(resftp)) || 
			    !(resstp = ressp->s_sptr)) {
				return(EINVAL);
			}

			s = p_lock(&resstp->sd_mutex, SPLSTR);
			if (resstp->sd_flag & (STRERR | STRHUP | STPLEX)) {
				if (resstp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = resstp->sd_error;
				}
				v_lock(&resstp->sd_mutex, s);
				return(u.u_error);
			}
			/* get read queue of stream terminus */
			for (q = resstp->sd_wrq->q_next; q->q_next; q = q->q_next) ;
			q = RD(q);
			v_lock(&resstp->sd_mutex, s);

			if (strfdinsert.ctlbuf.len < (strfdinsert.offset + sizeof(queue_t *))) {
				return(EINVAL);
			}

			/* Check for legal flag value */
			if (strfdinsert.flags & ~RS_HIPRI) {
				return(EINVAL);
			}

			/*
			 * make sure ctl and data sizes together fall within 
			 * the limits of the max and min receive packet sizes 
			 * and do not exceed system limit.  A negative data length
			 * means that no data part is to be sent.
			 */
			rmin = stp->sd_wrq->q_next->q_minpsz;
			rmax = stp->sd_wrq->q_next->q_maxpsz;
			if (rmax == INFPSZ) {
				rmax = strmsgsz;
			} else {
				rmax = (short) min((uint)rmax, (uint)strmsgsz);
			}
			msgsize = strfdinsert.databuf.len;
			if (msgsize < 0) {
				msgsize = 0;
			}
			if ((msgsize < rmin) || (msgsize > rmax) ||
			    (strfdinsert.ctlbuf.len > strctlsz)) {
				return(ERANGE);
			}

			s = p_lock(&stp->sd_mutex, SPLSTR);
			if (stp->sd_flag & (STRERR | STRHUP | STPLEX)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}

			while ((stp->sd_wrq->q_flag & QFREEZE) || 
			       (!(strfdinsert.flags & RS_HIPRI) && !l_canput(q))) {

				if (flag & FNDELAY) {
					v_lock(&stp->sd_mutex, s);
					return(EAGAIN);
				}

				/*
				 * Prior to sleeping on sd_write, we must check
				 * to see if our job-control checks raced with
				 * our controlling tty being relinquished.
				 *
				 * While we hold the stream lock, it is not
				 * possible for the controlling tty dealloca-
				 * tion code to completely deallocate the tty
				 * (that code will be blocked on sd_mutex).
				 * When it acquires sd_mutex, it will
				 * vall_sema() the sd_write semaphore and
				 * awaken us, causing us to cycle around again,
				 * at which point we will see that the
				 * controlling tty has been relinquished.
				 */
				if ((flag & FGENTTY) &&
				    (u.u_procp->p_session->s_vp == NULL)) {
					v_lock(&stp->sd_mutex, s);
					return(EIO);
				}
				if (p_sema_v_lock(&stp->sd_write, STOPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
					u.u_error = EINTR;
					return(u.u_error);
				}
				/* Must recheck for errors */
				s = p_lock(&stp->sd_mutex, SPLSTR);
				if (stp->sd_flag & (STRERR | STRHUP | STPLEX)) {
					if (stp->sd_flag & STPLEX) {
						u.u_error = EINVAL;
					} else {
						u.u_error = stp->sd_error;
					}
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
			}

			/* inline version of putnext */
			wroff = (int) stp->sd_wroff;
			qnext = stp->sd_wrq->q_next;
			qnext->q_putcnt++;
			v_lock(&stp->sd_mutex, s);
			
			if (!(mp = strmakemsg(&strfdinsert.ctlbuf, 
					      &strfdinsert.databuf, wroff,
					      (int)strfdinsert.flags,
					      UIOSEG_USER, stp))) {
				return(u.u_error);
			}

			/*
			 * Place pointer to queue 'offset' bytes from the
			 * start of the control portion of the message.
			 */

			*((queue_t **)(mp->b_rptr + strfdinsert.offset)) = q;

			/* Put message downstream */
			(*qnext->q_qinfo->qi_putp)(qnext, mp);

			s = p_lock(&stp->sd_mutex, SPLSTR);
			qnext->q_putcnt--;
			if (qnext->q_flag & QFREEZE) {
				(void)cv_sema(&qnext->q_str->sd_freeze);
			}
			v_lock(&stp->sd_mutex, s);

			return(u.u_error);
		}

	case I_SETSIG:
		/*
		 * Register the calling proc to receive the SIGPOLL signal
		 * based on the events given in arg.  If arg is zero, remove
		 * the proc from register list.
		 */
		{
			register struct strevent *sep, *psep;
			
			if (arg && (arg & ~(S_INPUT | S_HIPRI | S_OUTPUT | S_MSG))) {
				return(EINVAL);
			}
			psep = NULL;

			s = p_lock(&stp->sd_mutex, SPLSTR);

			if (stp->sd_flag & (STPLEX | STRERR)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			/* Find any prev entry for this process */
			for (sep = stp->sd_siglist; 
			     sep && (sep->se_procp != u.u_procp);
			     psep = sep, sep = sep->se_next);
			
			if (arg) {
				/* If proc not already registered, add it */
				if (!sep) {
					if (!(sep = sealloc(SE_NOSLP))) {
						v_lock(&stp->sd_mutex, s);
						return(EAGAIN);
					}
					if (psep) {
						psep->se_next = sep;
					} else {
						stp->sd_siglist = sep;
					}
					sep->se_procp = u.u_procp;
				}
				
				/* set events */
				sep->se_events = arg;
				stp->sd_sigflags |= arg;
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			} 
			
			/* arg = 0 so remove proc from register list */
			
			if (sep) {
				if (psep) {
					psep->se_next = sep->se_next;
				} else {
					stp->sd_siglist = sep->se_next;
				}
				sefree(sep);
				/* Recalculate OR of sig events. */
				stp->sd_sigflags = 0;
				for (sep = stp->sd_siglist; sep; sep = sep->se_next) {
					stp->sd_sigflags |= sep->se_events;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			v_lock(&stp->sd_mutex, s);
			return(EINVAL);
		}
		
	case I_GETSIG:
		/*
		 * Return (in arg) the current registration of events
		 * for which the calling proc is to be signaled.
		 */
		{
			register struct strevent *sep;
			long karg;
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			/* error checks */
			if (stp->sd_flag & (STPLEX | STRERR)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			for (sep = stp->sd_siglist; sep; sep = sep->se_next) {
				if (sep->se_procp == u.u_procp) {
					karg = sep->se_events;
					v_lock(&stp->sd_mutex, s);
					if (ncopyout((caddr_t) &karg, (char *) arg, sizeof(int), STRINT)) {
						u.u_error = EFAULT;
					}
					return(u.u_error);
				}
			}
			v_lock(&stp->sd_mutex, s);
			return(EINVAL);
		}
		
	case I_NREAD:
		/*
		 * Return number of bytes of data in first message in queue in
		 * "arg" and return the number of messages in queue in return
		 * value.
		 */
		{
			register mblk_t *bp;
			int size = 0;
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			/* error checks */
			if (stp->sd_flag & (STPLEX | STRERR)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			if (bp = RD(stp->sd_wrq)->q_first) {
				size = msgdsize(bp);
			}
			u.u_r.r_val1 = l_qsize(RD(stp->sd_wrq));
			v_lock(&stp->sd_mutex, s);
			
			if (ncopyout((caddr_t) &size, (char *) arg, sizeof(size), STRINT)) {
				u.u_error = EFAULT;
			}
			return(u.u_error);
		}
		
	case I_FIND:
		/* Get module name. */
		{
			char mname[FMNAMESZ + 1];
			
			if (ncopyin((caddr_t) arg, mname, FMNAMESZ+1, STRNAME)) {
				return(EFAULT);
			}
			return(str_find_mod(mname, stp, &u.u_r.r_val1));
		}
		
	case I_LOOK:
		/*
		 * Get name of first module downstream.
		 * If no module (return error).
		 */
		{
			register i;
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			/* error checks */
			if (stp->sd_flag & (STPLEX | STRERR)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			for (i = 0; i < fmodcnt; i++) {
				if (fmodsw[i].f_str->st_wrinit == stp->sd_wrq->q_next->q_qinfo) {
					break;
				}
			}
			
			v_lock(&stp->sd_mutex, s);
			
			if (i < fmodcnt) {
				/* found it ! */
				if (ncopyout(fmodsw[i].f_name, (char *) arg, FMNAMESZ+1, STRNAME)) {
					u.u_error = EFAULT;
				}
				return(u.u_error);
			}
			/* did not find it */
			return(EINVAL);
		}
		
	case I_SRDOPT:
		/*
		 * Set read options:
		 *
		 * RNORM - default stream mode
		 * RMSGN - message no discard
		 * RMSGD - message discard
		 */
		
		s = p_lock(&stp->sd_mutex, SPLSTR);
		
		/* error checks */
		if (stp->sd_flag & (STPLEX | STRERR)) {
			if (stp->sd_flag & STPLEX) {
				u.u_error = EINVAL;
			} else {
				u.u_error = stp->sd_error;
			}
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		}
		
		switch (arg) {
		case RNORM: 
			stp->sd_flag &= ~(RMSGDIS | RMSGNODIS);
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		case RMSGD:
			stp->sd_flag = (stp->sd_flag & ~RMSGNODIS) | RMSGDIS;
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		case RMSGN:
			stp->sd_flag = (stp->sd_flag & ~RMSGDIS) | RMSGNODIS;
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		default:
			v_lock(&stp->sd_mutex, s);
			return(EINVAL);
		}
		
		
	case I_GRDOPT:
		/*
		 * Get read option and return the value
		 * to spot pointed to by arg.
		 */
		{
			int rdopt;
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			/* error checks */
			if (stp->sd_flag & (STPLEX | STRERR)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			if (stp->sd_flag & RMSGDIS) {
				rdopt = RMSGD;
			} else if  (stp->sd_flag & RMSGNODIS) {
				rdopt = RMSGN;
			} else {
				rdopt = RNORM;
			}
			v_lock(&stp->sd_mutex, s);
			
			if (ncopyout((caddr_t) &rdopt, (char *) arg, sizeof(rdopt), STRINT)) {
				u.u_error = EFAULT;
			}
			return(u.u_error);
		}
		
	case I_PUSH:
		/* Push a module. */
		{
			char mname[FMNAMESZ + 1];

			/* Copy in arguments etc and lookup modname. */
			
			if (ncopyin((caddr_t) arg, mname, FMNAMESZ+1, STRNAME)) {
				return(EFAULT);
			}
			return(str_push_mod(mname, stp, sp));
		}
		
		
	case I_POP:
		/* Pop module (if it exists). */
		{
			register queue_t *q;
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			if (stp->sd_pushcnt <= 0) {
				/*
				 * Normally, we would also return EINVAL if
				 * ((stp->sd_wrq->q_next->q_next->q_flag & 
				 *				QREADR) != 0).
				 * In other words, we should be sure not to pop
				 * from a streams-based pipe, eventhough we do
				 * not presently support them (this preserves
				 * system V semantics).
				 * However, two processes attempting to pop a 
				 * module from the same queue at the same time
				 * can cause trouble.  q_next linkage can be
				 * broken by one process calling qdetach()
				 * while another process is dereferencing
				 * q_next->q_next (since qdetach() is called
				 * outside the protection of sd_mutex).
				 * Therefore, we will verify the rest of this
				 * (i.e., dereference q_next->q_next) under the
				 * protection of sd_plumb below.
				 */
				v_lock(&stp->sd_mutex, s);
				return(EINVAL);
			}
			
			/* 
			 * Everything is okay, so go ahead and pop!  Since
			 * this is a plumbing operation, it must serialize
			 * with other plumbers.  The following sleep is
			 * signallable.
			 */
			
			while (1) {
				switch (p_sema_v_lock(&stp->sd_plumb, STOPRI | PCATCH, &stp->sd_mutex)) {
				case 0:		/* due to v_sema */
					break;		/* out of switch */
				case STOPPED:	/* job control */
					s = p_lock(&stp->sd_mutex, SPLSTR);
					continue;	/* with while loop */
				case PCATCH:	/* due to interrupt */
					return(EINTR);	
				}
				break;
			}
			
			
			/* 
			 * At this point, sd_plumb has been acquired.  Since
			 * the lock had been released, all the error checks
			 * must be remade.
			 */
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_sema(&stp->sd_plumb);
				vall_sema(&stp->sd_timer2);
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			if ((stp->sd_pushcnt <= 0) ||
			    (stp->sd_wrq->q_next->q_next->q_flag & QREADR)) {
				v_sema(&stp->sd_plumb);
				vall_sema(&stp->sd_timer2);
				v_lock(&stp->sd_mutex, s);
				return(EINVAL);
			}
			
			/* 
			 * Must release lock before calling qdetach.  Safe to
			 * do so because sd_plumb semaphore has been acquired
			 * which keeps the other plumbing operations out.  
			 *
			 * Note that puts (and hence service procedures) may
			 * get invoked *after* the close is actually complete
			 * since the lock must be dropped during close.  To
			 * guard against this race, the module's close should
			 * invoke "disable_procs" at a suitable time. 
			 *
			 * We also freeze the streams head so that it
			 * does not cause write side processing.
			 *
			 * (See also I_PUSH for similar considerations.)
			 */
			
			stp->sd_wrq->q_flag |= QFREEZE;
			RD(stp->sd_wrq)->q_flag |= QFREEZE;
			
			v_lock(&stp->sd_mutex, s);
			
			/* call close and detach the top module */
			qdetach(RD(stp->sd_wrq->q_next), 0);
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			/* 
			 * Since we had frozen stream-head, unfreeze it.  This
			 * involves enabling service procedures which may have
			 * been no-ops during the freeze period, and waking up
			 * all the readers and writers who may have been
			 * sleeping due to the freeze.
			 */
			
			stp->sd_wrq->q_flag &= ~QFREEZE;
			RD(stp->sd_wrq)->q_flag &= ~QFREEZE;
			
			/* Reset write side flow-control */
			if ((stp->sd_wrq->q_flag & QTOENAB) || l_canput(stp->sd_wrq)) {
				l_qenable(stp->sd_wrq);
			}
			/* Wakeup writers who may have been blocked */
			vall_sema(&stp->sd_write);
			
			/* Reset read side flow-control */
			for (q = l_backq(RD(stp->sd_wrq->q_next));
			     q && !q->q_qinfo->qi_srvp; q = l_backq(q));
			if (q) {
				l_qenable(q);
			}
			/* Wakeup readers who may have been blocked */
			vall_sema(&stp->sd_read);
			
			stp->sd_pushcnt--;
			v_sema(&stp->sd_plumb);	
			vall_sema(&stp->sd_timer2);
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
			
		}
		
	case I_PEEK:
		/*
		 * Reads first msg in stream head's read queue without
		 * taking the msg off the queue.  Because of the need
		 * to release lock before copying into user area, the
		 * msg is actually dup'ed into another msg within the 
		 * lock first, and then the copy to user space is done
		 * outside the lock.  This is simpler but assumes that
		 * this function is not performance critical.
		 */
		{
			register mblk_t *bp, *dupbp, *dupsave;
			register save_ucnt, n;
			struct strpeek strpeek;
			struct uio auio;
			struct iovec aiov;
			
			if (ncopyin((caddr_t) arg, (caddr_t) &strpeek, sizeof(strpeek), STRPEEK)) {
				return(EFAULT);
			}
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			if (stp->sd_flag & (STPLEX | STRERR)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			if (!(bp = RD(stp->sd_wrq)->q_first) ||
			    ((strpeek.flags & RS_HIPRI) && (queclass(bp) == QNORM))){
				u.u_r.r_val1 = 0;
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			if (bp->b_datap->db_type == M_PASSFP) {
				v_lock(&stp->sd_mutex, s);
				return(EBADMSG);
			}
			
			if (bp->b_datap->db_type == M_PCPROTO) {
				strpeek.flags = RS_HIPRI;
			} else {
				strpeek.flags = 0;
			}
			
			if (!(dupbp = dupmsg(bp))) {
				v_lock(&stp->sd_mutex, s);
				return(EAGAIN);
			}
			dupsave = dupbp;
			
			/* 
			 * At this point dupbp has the complete data in it, so
			 * we do not need the stream lock any more.
			 */
			v_lock(&stp->sd_mutex, s);
			
			/* First process PROTO blocks, if any. */
			
			aiov.iov_base = (caddr_t) strpeek.ctlbuf.buf;
			auio.uio_resid = aiov.iov_len = strpeek.ctlbuf.maxlen;
			auio.uio_iov = &aiov;
			auio.uio_iovcnt = 1;
			auio.uio_segflg = UIOSEG_USER;
			
			while (dupbp && dupbp->b_datap->db_type != M_DATA && auio.uio_resid >= 0) {
				if (n = min((uint) auio.uio_resid, (uint) (dupbp->b_wptr - dupbp->b_rptr))) {
					u.u_error = uiomove((caddr_t) dupbp->b_rptr, n, UIO_READ, &auio);
				}
				if (u.u_error) {
					freemsg(dupsave);
					return(u.u_error);
				}
				dupbp = dupbp->b_cont;
			}
			save_ucnt = auio.uio_resid;
			
			/* Now process DATA blocks, if any. */
			while (dupbp && dupbp->b_datap->db_type != M_DATA) {
				dupbp = dupbp->b_cont;
			}
			
			aiov.iov_base = (caddr_t) strpeek.databuf.buf;
			auio.uio_resid = aiov.iov_len = strpeek.databuf.maxlen;
			auio.uio_iov = &aiov;
			auio.uio_iovcnt = 1;
			auio.uio_segflg = UIOSEG_USER;
			
			while (dupbp && auio.uio_resid >= 0) {
				if (n = min((uint) auio.uio_resid, (uint) (dupbp->b_wptr - dupbp->b_rptr))) {
					u.u_error = uiomove((caddr_t) dupbp->b_rptr, n, UIO_READ, &auio);
				}
				if (u.u_error) {
					freemsg(dupsave);
					return(u.u_error);
				}
				dupbp = dupbp->b_cont;
			}
			
			strpeek.ctlbuf.len = strpeek.ctlbuf.maxlen - save_ucnt;
			strpeek.databuf.len = strpeek.databuf.maxlen - auio.uio_resid;
			if (ncopyout((caddr_t) &strpeek, (caddr_t) arg, sizeof(strpeek), STRPEEK)) {
				freemsg(dupsave);
				return(EFAULT);
			}
			u.u_r.r_val1 = 1;
			freemsg(dupsave);
			return(u.u_error);
		}
		
	case I_FLUSH:
		/*
		 * Send a flush message downstream.
		 * The flush message can indicate:
		 * 	FLUSHR - flush read queue
		 * 	FLUSHW - flush write queue
		 * 	FLUSHRW - flush read/write queue
		 *
		 * An improvement over the standard streams implementation is
		 * in the case read side also needs to be flushed.  This 
		 * operation waits until the read queue is actually flushed
		 * before returning (standard streams does not wait).  This
		 * is an optimization only since it does not guarantee that
		 * immediately after the flush, more data has not been enqueued
		 * in the stream, but for most practical cases, it should be
		 * sufficient.
		 */
		
		if (arg & ~FLUSHRW) {
			return(EINVAL);
		}
		
		s = p_lock(&stp->sd_mutex, SPLSTR);
		
		if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
			if (stp->sd_flag & STPLEX) {
				u.u_error = EINVAL;
			} else {
				u.u_error = stp->sd_error;
			}
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		}
		
		/* 
		 * Since flush is a plumbing operation, it must serialize with
		 * other plumbers.  The following sleep is signallable.
		 */
		
		while (1) {
			switch (p_sema_v_lock(&stp->sd_plumb, STOPRI | PCATCH, &stp->sd_mutex)) {
			case 0:		/* due to v_sema */
				break;		/* out of switch */
			case STOPPED:	/* job control */
				s = p_lock(&stp->sd_mutex, SPLSTR);
				continue;	/* with while loop */
			case PCATCH:	/* due to interrupt */
				return(EINTR);	
			}
			break;
		}
		
		
		/* 
		 * At this point, sd_plumb has been acquired.  Since
		 * the lock had been released, all the error checks
		 * must be remade.
		 */
		
		s = p_lock(&stp->sd_mutex, SPLSTR);
		
		if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
			if (stp->sd_flag & STPLEX) {
				u.u_error = EINVAL;
			} else {
				u.u_error = stp->sd_error;
			}
			v_sema(&stp->sd_plumb);
			vall_sema(&stp->sd_timer2);
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		}
		
		/*
		 * If the read queue is being flushed then we must 
		 * prepare to wait until the stream is flushed.
		 */
		if (arg & FLUSHR) {
			stp->sd_flag |= FLUSHWAIT;
		}
		v_lock(&stp->sd_mutex, s);
		/* Now send the message downstream */
		while (!putnextctl1(stp->sd_wrq, M_FLUSH, arg)) {
			if (strwaitbuf(1, BPRI_HI, 1, stp)) {
				s = p_lock(&stp->sd_mutex, SPLSTR);
				stp->sd_flag &= ~FLUSHWAIT;
				v_sema(&stp->sd_plumb);
				vall_sema(&stp->sd_timer2);
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
		}
		runqueues();

		/*
		 * If the read queue is being flushed then we must 
		 * wait until the strrput() procedure receives an
		 * M_FLUSH thus ensuring that the read side is indeed
		 * flushed.  We can use the sd_timer3 semaphore since
		 * this is exclusively for all ioctl operations which have
		 * acquired the sd_plumb semaphore (as we have).
		 */

		s = p_lock(&stp->sd_mutex, SPLSTR);

		if (arg & FLUSHR) {
			/* Will be awakened by strrput() */
			while (stp->sd_flag & FLUSHWAIT) {
				/*
				 * Prior to sleeping on sd_timer3, we must
				 * check to see if this operation is via
				 * /dev/tty, and if our controlling tty has
				 * been relinquished.
				 */
				if ((flag & FGENTTY) &&
				    (u.u_procp->p_session->s_vp == NULL)) {
					stp->sd_flag &= ~FLUSHWAIT;
					v_sema(&stp->sd_plumb);
					vall_sema(&stp->sd_timer2);
					v_lock(&stp->sd_mutex, s);
					return(EIO);
				}
				switch (p_sema_v_lock(&stp->sd_timer3, STIPRI | PCATCH, &stp->sd_mutex)) {
				case 0:		/* due to v_sema */
				case STOPPED:	/* job control */
					s = p_lock(&stp->sd_mutex, SPLSTR);
					continue;	/* while loop */
				case PCATCH:	/* due to interrupt */
					s = p_lock(&stp->sd_mutex, SPLSTR);
					stp->sd_flag &= ~FLUSHWAIT;
					v_sema(&stp->sd_plumb);
					vall_sema(&stp->sd_timer2);
					v_lock(&stp->sd_mutex, s);
					return(EINTR);
				}
			}
		}

		stp->sd_flag &= ~FLUSHWAIT;
		v_sema(&stp->sd_plumb);
		vall_sema(&stp->sd_timer2);
		v_lock(&stp->sd_mutex, s);
		
		return(u.u_error);
		

	case I_STR:
		/*
		 * Stream ioctl.  Read in an strioctl buffer from the user
		 * along with any data specified and send it downstream.
		 * Synchronize with plumbing operations by acquiring sd_plumb.
		 * strdoioctl() will allow only one ioctl message at a
		 * time, and waits for the acknowledgement.
		 */
		
		if (ncopyin((caddr_t) arg, (caddr_t) &strioc, sizeof(struct strioctl), STRIOCTL)) {
			return(EFAULT);
		}
		if (strioc.ic_len < 0 || strioc.ic_timout < -1) {
			return(EINVAL);
		}
		
		s = p_lock(&stp->sd_mutex, SPLSTR);
		
		if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
			if (stp->sd_flag & STPLEX) {
				u.u_error = EINVAL;
			} else {
				u.u_error = stp->sd_error;
			}
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		}
		
		if (ret = p_plumb_v_lock(stp)) {
			return(ret);
		}
		strdoioctl(stp, &strioc, U_TO_K, NULL, M_IOCTL);
		v_plumb(stp);
		
		if (ncopyout((caddr_t) &strioc, (caddr_t) arg, sizeof(struct strioctl), STRIOCTL)) {
			u.u_error = EFAULT;
		}
		
		return(u.u_error);
		
	case I_LINK:
		/* 
		 * Link a multiplexer.
		 *
		 * Since two streams are involved, the locking must be done with some
		 * care to avoid deadlock.
		 */
		{
			register struct file *fpdown;
			register struct snode *spdown;
			struct linkblk *linkblkp;
			register struct stdata *stpdown;
			register queue_t *rq, *q;
			extern struct linkblk *alloclink();
			
			/* Get the bottom stream pointer and validate it */
			
			GETFP(fpdown, arg);
			if (!fpdown) {
				return(EBADF);
			}
			if (!(spdown = getspstr(fpdown)) || 
			    !(stpdown = spdown->s_sptr) ||
			    (stpdown == stp)) {
				return(EINVAL);
			}
			
			/* Validate the control stream and acquire its sd_plumb */
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			if (!stp->sd_strtab->st_muxwinit) {
				v_lock(&stp->sd_mutex, s);
				return(EINVAL);
			}
			
			if (ret = p_plumb_v_lock(stp)) {
				return(ret);
			}
			
			/* Validate the bottom stream and acquire its sd_plumb */
			
			s = p_lock(&stpdown->sd_mutex, SPLSTR);
			
			if (stpdown->sd_flag & (STPLEX | STRHUP | STRERR)) {
				v_lock(&stpdown->sd_mutex, s);
				v_plumb(stp);
				return(EINVAL);
			}
			
			/* 
			 * To avoid deadlock, check if the bottom streams sd_plumb
			 * semaphore is available before trying to acquire it.  If
			 * unavailable, don't bother and return with error.
			 */

			if (!cp_sema(&stpdown->sd_plumb)) {
				v_lock(&stpdown->sd_mutex, s);
				v_plumb(stp);
				return(EINVAL);
			}
			v_lock(&stpdown->sd_mutex, s);
			
			/* 
			 * Bottom stream's sd_plumb acquired (hence safe from structural
			 * changes).  Both streams are unlocked at this point.
			 */
			
			/* Check for linking cycles and allocate link block */

			q = getendq(stp->sd_wrq);
			if (linkcycle(q, stpdown->sd_wrq)) {
				s = p_lock(&stpdown->sd_mutex, SPLSTR);
				v_sema(&stpdown->sd_plumb);
				vall_sema(&stpdown->sd_timer2);
				v_lock(&stpdown->sd_mutex, s);
				v_plumb(stp);
				return(EINVAL);
			}
			
			if (!(linkblkp = alloclink(q, stpdown->sd_wrq, fpdown - &file[0]))) {
				s = p_lock(&stpdown->sd_mutex, SPLSTR);
				v_sema(&stpdown->sd_plumb);
				vall_sema(&stpdown->sd_timer2);
				v_lock(&stpdown->sd_mutex, s);
				v_plumb(stp);
				return(EAGAIN);
			}
			
			/* linkblk allocated */
			
			strioc.ic_cmd = I_LINK;
			strioc.ic_timout = 0;
			strioc.ic_len = sizeof(struct linkblk);
			strioc.ic_dp = (char *) linkblkp;
			
			/* 
			 * Set up bottom stream's mux queues.
			 * First, freeze the head queue-pair.
			 */
			
			s = p_lock(&stpdown->sd_mutex, SPLSTR);
			
			rq = RD(stpdown->sd_wrq);
			freeze_procs(rq);
			
			/* Switch queues (head pair is frozen) */
			
			setq(rq, stp->sd_strtab->st_muxrinit, stp->sd_strtab->st_muxwinit);
			rq->q_ptr = WR(rq)->q_ptr = NULL;
			
			/* 
			 * Unfreeze head queue-pair.  The only thing to do is reset to
			 * read side flow-control since write side is not active on
			 * the bottom stream.
			 */
			
			rq->q_flag |= QWANTR;	
			WR(rq)->q_flag |= QWANTR;
			for (q = l_backq(RD(stpdown->sd_wrq->q_next));
			     q && !q->q_qinfo->qi_srvp; q = l_backq(q));
			if (q) {
				l_qenable(q);
			}
			
			v_lock(&stpdown->sd_mutex, s);
			
			/* Both streams unlocked at this point */
			
			/* Send ioctl msg down the upper stream. */
			
			strdoioctl(stp, &strioc, K_TO_K, STRLINK, M_IOCTL);
			
			/* Both streams unlocked at this point */
			
			if (u.u_error) {
				/* 
				 * Undo everything done on bottom stream.  The
				 * upper stream has already been taken care of
				 * in strdoioctl.
				 */
				
				freelink(linkblkp);
				s = p_lock(&stpdown->sd_mutex, SPLSTR);
				setq(rq, &strdata, &stwdata);
				rq->q_ptr = WR(rq)->q_ptr = (caddr_t) stpdown;
				v_sema(&stpdown->sd_plumb);
				vall_sema(&stpdown->sd_timer2);
				v_lock(&stpdown->sd_mutex, s);
				v_plumb(stp);
				return(u.u_error);
			}
			
			/* 
			 * Increase refcnt on the fp for bottom stream (otherwise a
			 * subsequent close may dismantle the stream!).
			 */
			
			FDBUMP(fpdown);
			
			/*
			 * Wake up any other processes that may have been waiting
			 * on the lower stream.  These will all error out.
			 */
			
			s = p_lock(&stpdown->sd_mutex, SPLSTR);
			stpdown->sd_flag |= STPLEX;
			v_sema(&stpdown->sd_plumb);
			vall_sema(&stpdown->sd_timer2);
			vall_sema(&stpdown->sd_write);
			vall_sema(&stpdown->sd_read);
			strwakepoll(stpdown, POLLNVAL);
			v_lock(&stpdown->sd_mutex, s);
			
			v_plumb(stp);
			u.u_r.r_val1 = fpdown - &file[0];
			return(u.u_error);
		}
		
		
	case I_UNLINK:
		/*
		 * Unlink a multiplexer.
		 * If arg is -1, unlink all links for which this is the
		 * controlling stream.  Otherwise, arg is a index number in
		 * the global file table for the link to be removed.
		 *
		 * Since two streams are involved simultaneously, locking must
		 * be done with care to avoid deadlocks.
		 *
		 * Note that as long as the STPLEX flag is set on the bottom
		 * stream, all operations from the user land will be refused.
		 * Thus we do not need to worry about acquiring its sd_plumb
		 * semaphore.
		 */
		{
			register struct file *fpdown;
			register struct snode *spdown;
			register struct stdata *stpdown;
			struct linkblk *linkblkp;
			
			/* Validate stream and acquire its sd_plumb sema */   
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			if (ret = p_plumb_v_lock(stp)) {
				return(ret);
			}
			
			/* stp unlocked at this point */
			
			if (arg == -1) {
				munlinkall(stp, 0);
			} else {
				fpdown = &file[arg];
				if ((fpdown < file) || 
				    (fpdown >= (struct file *) v.ve_file) ||
				    !(spdown = getspstr(fpdown)) ||
				    !(stpdown = spdown->s_sptr)) {
					/* invalid user supplied index number */
					v_plumb(stp);
					return(EINVAL);
				}
				s = p_lock(&stpdown->sd_mutex, SPLSTR);
				if (!(stpdown->sd_flag & STPLEX)) {
					v_lock(&stpdown->sd_mutex, s);
					v_plumb(stp);
					return(EINVAL);
				}
				v_lock(&stpdown->sd_mutex, s);
				if (!(linkblkp = findlinks(getendq(stp->sd_wrq), stpdown->sd_wrq, arg))) {
					/* invalid user supplied index number */
					v_plumb(stp);
					return(EINVAL);
				}
				/* Both streams unlocked at this point */
				(void) munlink(stp, fpdown, linkblkp, 0);
			}
			v_plumb(stp);
			return(u.u_error);
		}
		
		
	default:
		/* The ioctl could be a TTY related ioctl */

		if (((cmd & IOCTYPE) == LDIOC) || 
		    ((cmd & IOCTYPE) == tIOC) ||
		    ((cmd & IOCTYPE) == TIOC)) {

			/*
			 * Perform fgr/bgr job control checks on
			 * the ioctls which require them.
			 */
			switch (cmd) {
			case TCXONC:
			case TCFLSH:
			case TCSBRK:
			case TCDSET:
			case TCSETA:
			case TCSETAF:
			case TCSETAW:
			case TCSETP:
			case TCSETPW:
			case TCSETPF:
			case LDSETT:
			case TIOCSETP:
			case TIOCSPGRP:
				if (ret = ctty_ioctl(stp, flag)) {
					return(ret);
				}
				break;
			}

			/* First dispose of commands which do not need to send a msg */

			if (cmd == TIOCGPGRP) {
				p = u.u_procp;
#ifdef DEBUG
				if (jc_debug)
					printf("TIOGPGRP p_pid %d p_pgrp %d p_sid %d ->%d\n",
					   p->p_pid, p->p_pgrp, p->p_sid, 
					   stp->sd_ctty.c_pgrp);
#endif
				/*
				 * POSIX IEEE std 1003.1-1988 7.2.3.4
				 * [ENOTTY]: The calling process does not
				 * have a controlling terminal or the file
				 * is not a controlling terminal
				 *
				 * The session can only be changed by us via
				 * setsid() setpgr() or exit().
				 * If the sidp pointer does point to us then
				 * it will always point to us. If the value
				 * ever changes then it is already too late.
				 */
				s = p_lock(&stp->sd_mutex, SPLSTR);
				if ((p->p_session->s_ctty != &stp->sd_ctty) || 
				    (p->p_sid != stp->sd_ctty.c_sid)) {
					v_lock(&stp->sd_mutex, s);
					return(ENOTTY);
				}
				if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
					if (stp->sd_flag & STPLEX) {
						u.u_error = EINVAL;
					} else {
						u.u_error = stp->sd_error;
					}
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
				pid = stp->sd_ctty.c_pgrp;
				v_lock(&stp->sd_mutex, s);

				if (copyout((caddr_t)&pid, (caddr_t) arg, sizeof(pid_t))) {
					return(EFAULT);
				}
				return(u.u_error);
			}
			if (cmd == TIOCSPGRP) {	
				if (copyin((caddr_t) arg, (caddr_t)&pid, sizeof(pid_t))) {
					return(EFAULT);
				}
				/*
				 * POSIX IEEE std 1003.1-1988 7.2.4.4
				 * [EINVAL]: The value of the "pgrp_id"
				 * argument is a value not supported by
				 * the impelentation.
				 */
				p = u.u_procp;
#ifdef DEBUG
				if (jc_debug)
					printf("TIOCSGRP p_pid %d p_pgrp %d p_sid %d ->%d\n",
					   p->p_pid, p->p_pgrp, p->p_sid, pid);
#endif
				if (pid <0 || pid > MAXPID) {
					return(EINVAL);
				}
				/*
				 * POSIX IEEE std 1003.1-1988 7.2.4.4
				 * [ENOTTY]: The calling process does not
				 * have a controlling terminal or the file
				 * is not a controlling terminal
				 *
				 * lock the session since others may using
				 * or wanting to use the ctty pointer.
				 * if any other member of the controlling
				 * processes process group is attempting
				 * to set the value then it does matter who 
				 * wins.
				 */
				s = p_lock(&stp->sd_mutex, SPLSTR);
				(void)p_lock(&p->p_session->s_lock, SPLSESS);
				if ((p->p_session->s_ctty != &stp->sd_ctty) || 
					    (p->p_sid != stp->sd_ctty.c_sid)) {
#ifdef DEBUG
					if (jc_debug)
						printf("TIOCSGRP ENOTTY - s_ctty:0x%x &sd_ctty:0x%x p_sid:%d c_sid:%d\n",
					   	p->p_session->s_ctty,
					   	&stp->sd_ctty, 
						p->p_sid,
						stp->sd_ctty.c_sid);
#endif
					v_lock(&p->p_session->s_lock, SPLSTR);
					v_lock(&stp->sd_mutex, s);
					return(ENOTTY);
				}
				/*
				 * POSIX IEEE std 1003.1-1988 7.2.4.4
				 * [EPERM]: The value of "pgrp_id" is a
				 * value supported by the implementation
				 * but does not match the process group
				 * ID of a process group ID of a process
				 * in the same session as the calling
				 * process.
				 */
				if (!pgrp_find(pid)) {
					v_lock(&p->p_session->s_lock, SPLSTR);
					v_lock(&stp->sd_mutex, s);
					return(EPERM);
				}
				v_lock(&p->p_session->s_lock, SPLSTR);
				if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
					if (stp->sd_flag & STPLEX) {
						u.u_error = EINVAL;
					} else {
						u.u_error = stp->sd_error;
					}
					v_lock(&stp->sd_mutex, s);
					return(u.u_error);
				}
				stp->sd_ctty.c_pgrp = pid;
				v_lock(&stp->sd_mutex, s);
#ifdef DEBUG
				if (jc_debug)
					printf("TIOCSGRP &ctty:0x%x%d\n",
					   p->p_session->s_ctty,
					   p->p_session->s_ctty->c_pgrp); 
#endif
				return(u.u_error);
			}
			
			/*
			 * The ioctl is a tty ioctl - set up strioc buffer and
			 * call strdoioctl to do the actual work.  Synchronize
			 * with other plumbing operations by acquiring sd_plumb.
			 */
			
			s = p_lock(&stp->sd_mutex, SPLSTR);
			
			if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
				if (stp->sd_flag & STPLEX) {
					u.u_error = EINVAL;
				} else {
					u.u_error = stp->sd_error;
				}
				v_lock(&stp->sd_mutex, s);
				return(u.u_error);
			}
			
			if (ret = p_plumb_v_lock(stp)) {
				return(ret);
			}
			
			strioc.ic_cmd = cmd;
			strioc.ic_timout = INFTIM;
			
			switch (cmd) {
			case TCXONC:
			case TCFLSH:
				strioc.ic_len = sizeof(int);
				strioc.ic_dp = (char *) &arg;
				strdoioctl(stp, &strioc, K_TO_K, STRINT, M_PCIOCTL);
				v_plumb(stp);
				return(u.u_error);
			case TCSBRK:
			case TCDSET:
				strioc.ic_len = sizeof(int);
				strioc.ic_dp = (char *) &arg;
				strdoioctl(stp, &strioc, K_TO_K, STRINT, M_IOCTL);
				v_plumb(stp);
				return(u.u_error);
				
			case TCSETA:
				strioc.ic_len = sizeof(struct termio);
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMIO, M_PCIOCTL);
				v_plumb(stp);
				return(u.u_error);
			case TCSETAW:
			case TCSETAF:
				strioc.ic_len = sizeof(struct termio);
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMIO, M_IOCTL);
				v_plumb(stp);
				return(u.u_error);
				
			case TCSETP:
				strioc.ic_len = sizeof(struct termios);
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMIOS, M_PCIOCTL);
				v_plumb(stp);
				return(u.u_error);
			case TCSETPW:
			case TCSETPF:
				strioc.ic_len = sizeof(struct termios);
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMIOS, M_IOCTL);
				v_plumb(stp);
				return(u.u_error);
				
			case LDSETT:
				strioc.ic_len = sizeof(struct termcb);
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMCB, M_IOCTL);
				v_plumb(stp);
				return(u.u_error);
				
			case TIOCSETP:
				strioc.ic_len = sizeof(struct sgttyb);
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRSGTTYB, M_IOCTL);
				v_plumb(stp);
				return(u.u_error);
				
			case TCGETA:
				strioc.ic_len = 0;
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMIO, M_PCIOCTL);
				v_plumb(stp);
				return(u.u_error);
			case TCGETP:
				strioc.ic_len = 0;
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMIOS, M_PCIOCTL);
				v_plumb(stp);
				return(u.u_error);
			case LDGETT:
				strioc.ic_len = 0;
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRTERMCB, M_IOCTL);
				v_plumb(stp);
				return(u.u_error);
			case TIOCGETP:
				strioc.ic_len = 0;
				strioc.ic_dp = (char *) arg;
				strdoioctl(stp, &strioc, U_TO_K, STRSGTTYB, M_IOCTL);
				v_plumb(stp);
				return(u.u_error);

			default:
				v_plumb(stp);
				break;
			}
		}

		/*
		 * Unknown ioctl - treat it as a transparent ioctl.
		 */
		s = p_lock(&stp->sd_mutex, SPLSTR);
		if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
			if (stp->sd_flag & STPLEX) {
				u.u_error = EINVAL;
			} else {
				u.u_error = stp->sd_error;
			}
			v_lock(&stp->sd_mutex, s);
			return(u.u_error);
		}
		if (ret = p_plumb_v_lock(stp)) {
			return(ret);
		}
		strioc.ic_cmd = cmd;
		strioc.ic_timout = INFTIM;
		strioc.ic_len = TRANSPARENT;
		strioc.ic_dp = (char *)&arg;
		strdoioctl(stp, &strioc, K_TO_K, NULL, M_IOCTL);
		v_plumb(stp);
		return(u.u_error);
	}
	/*NOTREACHED*/
}


/*
 * This function is placed in the callout table to time out a process
 * waiting on poll.  If the poll completes, this function is removed
 * from the table.  It basically force_v_sema's the sleeping process off
 * the poll_sema semaphore.
 */

static
polltime(p)
	register struct proc *p;	/* process pointer */
{
	register spl_t s, s_str;

	/* 
	 * If the process is not sleeping any more (e.g. stopped due to job
	 * control) then there is no need to do anything since (ultimately,
	 * when continued) the polling process will re-iterate and notice that
	 * timeout has expired.
	 */

	s = p_lock(&poll_mutex, SPLSTR);
	s_str = p_lock(&p->p_state, SPLHI);
	if (p->p_wchan == &poll_sema) {
		force_v_sema(p);
	}
	v_lock(&p->p_state, s_str);
	v_lock(&poll_mutex, s);
}




/*
 * Removes all event cells that refer to the current process in the given
 * stream's poll list.  Recalculates poll flags for the stream.  As an
 * optimization, if the last cell is sometimes not removed but left around
 * so that the next strpoll() can make use of it.
 *
 * Assumes stream is locked.
 */

pollreset(stp)
	register struct stdata *stp;
{
	register struct strevent *psep, *sep, *tmp;

	sep = stp->sd_pollist;

	/* 
	 * Optimization for the common case: leave one cell around
	 * so that it can be used by the next poll system call.  This
	 * logic must be carefully coordinated with the corresponding
	 * pieces in strpoll(), strclose(), strwakepoll().
	 */
	if (sep && (sep->se_next == NULL) &&
	    (sep->se_procp == u.u_procp)) {
		/* mark cell free so that its reusable by next strpoll */
		sep->se_procp = NULL;
		sep->se_events = 0;
		stp->sd_pollflags = 0;
		return;
	}

	/* Not the common case: do the complete processing */

	psep = NULL;
	while (sep) {
		tmp = sep->se_next;
		if (sep->se_procp != u.u_procp) {
			psep = sep;
		} else {
			if (psep) {
				psep->se_next = tmp;
			} else {
				stp->sd_pollist = tmp;
			}
			sefree(sep);
		}
		sep = tmp;
	}

	/* recalculate pollflags */
	stp->sd_pollflags = 0;
	for (sep = stp->sd_pollist; sep; sep = sep->se_next) {
		stp->sd_pollflags |= sep->se_events;
	}
}


/*
 * Determines whether the necessary conditions are set on a stream
 * for it to be readable, writeable, or have exceptions.  The return
 * value is the retevents flag value (not u.u_error).
 *
 * Assumes stream is unlocked.
 */

int
strpoll(stp, events, anyyet)
	register struct stdata *stp;
	short events;		/* events if interest */
	register int anyyet;	/* whether some fd is ready */
{
	register retevents = 0;
	register spl_t s;
	register struct strevent *sep;

	s = p_lock(&stp->sd_mutex, SPLSTR);

	if (anyyet < 0) {		/* called only to remove previously */
		pollreset(stp);		/* established event cells */
		v_lock(&stp->sd_mutex, s);
		return(0);
	}
	if (stp->sd_flag & STPLEX) {
		v_lock(&stp->sd_mutex, s);
		return(POLLNVAL);
	}
	if (stp->sd_flag & STRERR) {
		v_lock(&stp->sd_mutex, s);
		return(POLLERR);
	}
	if (stp->sd_flag & STRHUP) {
		retevents |= POLLHUP;
	}
	if ((events & POLLOUT) &&
	    l_canput(stp->sd_wrq->q_next) &&
	    !(stp->sd_flag & STRHUP)) {
		retevents |= POLLOUT;
	}
	if ((events & POLLIN) &&
	    !(stp->sd_flag & STRPRI) &&
	    RD(stp->sd_wrq)->q_first) {
		retevents |= POLLIN;
	}
	if ((events & POLLPRI) &&
	    (stp->sd_flag & STRPRI)) {
		retevents |= POLLPRI;
	}

	ASSERT_DEBUG((retevents & (POLLPRI|POLLIN)) != (POLLPRI|POLLIN),
		     "strpoll: invalid retevents");

	if (retevents) {
		pollreset(stp);
		v_lock(&stp->sd_mutex, s);
		return(retevents);
	}

	/*
	 * If poll() has not found any events yet, set up event cell to wake
	 * up the poll if a requested event occurs on this stream.  This will
	 * sometimes result in adding an event cell in addition to an old one
	 * already there, but both will get cleaned up in the pollout.  Hence,
	 * it is not worth checking for here.
	 */

	if (!anyyet) {
		/* if a free cell is hanging around, use it, else alloc one */
		if ((stp->sd_pollist != NULL) && (stp->sd_pollflags == 0)) {
			/* free cell hanging around */
			sep = stp->sd_pollist;
		} else {
			if ((sep = sealloc(SE_NOSLP)) == NULL) {
				pollreset(stp);
				v_lock(&stp->sd_mutex, s);
				u.u_error = EAGAIN;
				return(0);
			}
			sep->se_next = stp->sd_pollist;
			stp->sd_pollist = sep;
		}
		sep->se_procp = u.u_procp;
		sep->se_events = events;
		stp->sd_pollflags |= events;
		v_lock(&stp->sd_mutex, s);
		return(0);
	}

	/*
	 * If we get here the poll has already found an event but there are no
	 * return events for this stream. Remove any event cell for this
	 * process from the pollist, and recalculate the pollflags.  A cell
	 * will exist if this stream was previously scanned in this poll().
	 */

	pollreset(stp);
	v_lock(&stp->sd_mutex, s);
	return(retevents);
}

/*
 * Wake up all processes sleeping on a poll for the given events on this
 * stream.  POLLERR and POLLHUP cause a wakeup of all processes regardless of
 * the events they were looking for.  Remove all of the event cells matching
 * the given events from the pollist.  As an optimization, the last cell
 * is sometimes not removed but left around to be reused by the next strpoll.
 *
 * Assumes stream is locked.
 */

strwakepoll(stp, events)
	struct stdata *stp;
{
	register struct strevent *sep, *psep;
	register spl_t s_str, s_ipl6;

	if (stp->sd_pollflags == 0) {
		/* 
		 * This ensures that a free cell hanging around
		 * (se_proc == NULL), does not mess things up.
		 */
		return;
	}
	sep = stp->sd_pollist;
	
	/* If single cell, take the optimzation */
	if (sep && (sep->se_next == NULL) && (sep->se_events & events)) {
		s_str = p_lock(&poll_mutex, SPL6);
		s_ipl6 = p_lock(&sep->se_procp->p_state, SPLHI);
		if (sep->se_procp->p_wchan == &poll_sema) {
			force_v_sema(sep->se_procp);
		} else {
			sep->se_procp->p_flag &= ~SPOLL;
		}
		v_lock(&sep->se_procp->p_state, s_ipl6);
		v_lock(&poll_mutex, s_str);
		
		/* mark cell reusable by next strpoll */
		sep->se_procp = NULL;
		sep->se_events = 0;
		stp->sd_pollflags = 0;
		return;
	}

	/* The optimizations do not apply: do the full logic */ 

	if (events & (POLLHUP | POLLERR | POLLNVAL))
		stp->sd_pollflags = 0;
	else
		stp->sd_pollflags &= ~events;
	psep = NULL;
	while (sep) {
		if ((sep->se_events & events) || 
		    (events & (POLLHUP | POLLERR | POLLNVAL))) {
			s_str = p_lock(&poll_mutex, SPL6);
			s_ipl6 = p_lock(&sep->se_procp->p_state, SPLHI);
			if (sep->se_procp->p_wchan == &poll_sema) {
				force_v_sema(sep->se_procp);
			} else {
				sep->se_procp->p_flag &= ~SPOLL;
			}
			v_lock(&sep->se_procp->p_state, s_ipl6);
			v_lock(&poll_mutex, s_str);

			if (psep) {
				psep->se_next = sep->se_next;
				sefree(sep);
				sep = psep->se_next;
			} else  {
				stp->sd_pollist = sep->se_next;
				sefree(sep);
				sep = stp->sd_pollist;
			}
		} else {
			psep = sep;
			sep = sep->se_next;
		}
	}
}

/*
 * Poll system call.
 */

poll()
{
	register struct uap  {
		struct	pollfd *fdp;
		unsigned long nfds;
		long	timo;
	} *uap = (struct uap *) u.u_ap;
	struct pollfd pollfd[NPOLLFILE];
	register caddr_t fdp;
	register fdcnt = 0;
	register int i, j;
	register size, time_id, mark;
	register spl_t s, s_str;
	register struct proc *p = u.u_procp;
	register struct file *fp;
	timeval_t atv;
	extern int polltime();

	if (uap->nfds > (unsigned long)OFILE_NOFILE(u.u_ofile_tab)) {
		u.u_error = EINVAL;
		return;
	}

	/* setup atv to contain absolute final time */
	if (uap->timo > 0) {
		atv.tv_sec = uap->timo / 1000;
		atv.tv_usec = (uap->timo % 1000) * 1000;
		P_GATE(G_TIME);
		timevaladd(&atv, &time);
		V_GATE(G_TIME);
	}

	if (uap->nfds == 0) {
		/*
		 * The syscall is being used as a timer.
		 */
		if (uap->timo == 0) {
			return;	/* immediately */
		}
		/*
		 * The only way to exit the following loop is when
		 * the time comparison fails, or if an interrupt occurs.
		 */
		while (1) {
			s = p_lock(&poll_mutex, SPLSTR);
			if (uap->timo > 0) {
				P_GATE(G_TIME);
				/* check if time already exceeded */
				if (time.tv_sec > atv.tv_sec || 
				    ((time.tv_sec == atv.tv_sec) && 
				     (time.tv_usec >= atv.tv_usec))) {
					V_GATE(G_TIME);
					v_lock(&poll_mutex, s);
					return;
				}
				time_id = ltimeout(polltime, (caddr_t) p, hzto(&atv));
				V_GATE(G_TIME);
			}
			
			switch (p_sema_v_lock(&poll_sema, (PZERO + 1) | PCATCH, &poll_mutex)) {
			case 0:		/* due to v_sema */
			case STOPPED:	/* due to job control */
				if (uap->timo > 0) {
					untimeout(time_id);
				}
				continue;
			case PCATCH:	/* due to interrupt */
				if (uap->timo > 0) {
					untimeout(time_id);
				}
				u.u_error = EINTR;
				return;
			}
		}
	}

	/*
	 * Retry scan of fds until an event is found or until the
	 * timeout is reached.
	 */

 retry:		

	/*
	 * Polling the fds is a relatively long process.  Set up the SPOLL
	 * flag so that we can see if something happened to an fd after we
	 * checked it but before we go to sleep.
	 */

	s = p_lock(&p->p_state, SPLHI);
	p->p_flag |= SPOLL;
	v_lock(&p->p_state, s);

	/*
	 * Check fd's for specified events.  
	 *
	 * Read in pollfd records in blocks of NPOLLFILE.  Test each fd in the
	 * block and store the result of the test in the event field of the
	 * in-core record.  After a block of fds is finished, write the result
	 * out to the user.  Note that if no event is found, the whole
	 * procedure will be repeated after awakenening from the sleep
	 * (subject to timeout).
	 */

	mark = uap->nfds;
	size = 0;
	fdp = (caddr_t) uap->fdp;
	for (i = 0; i < uap->nfds; i++) {
		j = i % NPOLLFILE;
		/*
		 * If we have looped back around to the base of pollfd, write
		 * out the results of the strpoll calls kept in pollfd to the
		 * user fdp.  Read in the next batch of fds to check.
		 */
		if (!j) {
			if (i > 0) {
				ASSERT_DEBUG((size == NPOLLFILE * sizeof(struct pollfd)), 
					     "poll: invalid size");
				if (copyout((caddr_t) pollfd, (caddr_t) fdp, (uint) size)) {
					u.u_error = EFAULT;
					return;
				}
				fdp += size;
			}
			size = min((uint) (uap->nfds - i), NPOLLFILE) * sizeof(struct pollfd);
			if (copyin((caddr_t) fdp, (caddr_t) pollfd, (uint) size)) {
				u.u_error = EFAULT;
				return;
			}
		}

		if (pollfd[j].fd < 0) {
			pollfd[j].revents = 0;
		} else {
			GETFP(fp, pollfd[j].fd);
			if (fp == NULL) {
				pollfd[j].revents = POLLNVAL;
			} else {
				pollfd[j].revents = VOP_STRPOLL(fp->f_vnode,
						      pollfd[j].events, fdcnt);
				if (u.u_error) {
					if (!fdcnt) {
						mark = i;
					}
					goto pollout;
				}
			}
		}
		if (pollfd[j].revents && !fdcnt++) {
			mark = i;
		}
		if (u.u_fpref != NULL) {
			deref_file(u.u_fpref);
			u.u_fpref = NULL;
		}
	}

	/*
	 * Poll of fds completed.  
	 * Copy out the last batch of events.  If the poll was successful,
	 * return fdcnt to user.
	 */
	u.u_r.r_val1 = fdcnt;
	if (copyout((caddr_t) pollfd, (caddr_t) fdp, (uint) size)) {
		u.u_error = EFAULT; 
		return;
	}
	if (fdcnt) {
		goto pollout;
	}

	/*
	 * If you get here, the poll of fds was unsuccessful.  First make sure
	 * your timeout hasn't been reached.  If not then sleep and wait until
	 * some fd becomes readable, writeable, or gets an exception.
	 */

	if (uap->timo == 0) {
		/* fail immediately */
		goto pollout;
	}

	/*
	 * If anything has happened on an fd since it was checked, it will
	 * have turned off SPOLL.  Check this and rescan if so.
	 *
	 * Locking poll_mutex prevents any poll wakeups or timeouts before the
	 * process sleeps on the poll_sema semaphore.
	 */
	(void) p_lock(&poll_mutex, SPLSTR);

	if (!(p->p_flag & SPOLL)) {
		v_lock(&poll_mutex, s);
		goto retry;
	}

	s_str = p_lock(&p->p_state, SPLHI);
	p->p_flag &= ~SPOLL;
	v_lock(&p->p_state, s_str);

	if (uap->timo > 0) {
		P_GATE(G_TIME);
		/* check if time already exceeded */
		if (time.tv_sec > atv.tv_sec || 
		    ((time.tv_sec == atv.tv_sec) && 
		    (time.tv_usec >= atv.tv_usec))) {
			V_GATE(G_TIME);
			v_lock(&poll_mutex, s);
			goto pollout;
		}
		time_id = ltimeout(polltime, (caddr_t) p, hzto(&atv));
		V_GATE(G_TIME);
	}

	/*
	 * The sleep will usually be awakened either by this poll's timeout or
	 * by the strwakepoll function called from a stream head.
	 */

	switch (p_sema_v_lock(&poll_sema, (PZERO + 1) | PCATCH, &poll_mutex)) {
	case 0:		/* due to v_sema */
	case STOPPED:	/* due to job control */
		break;
	case PCATCH:	/* due to interrupt */
		if (uap->timo > 0) {
			untimeout(time_id);
		}
		u.u_error = EINTR;
		goto pollout;
	}
	if (uap->timo > 0) {
		untimeout(time_id);
	}

	/*
	 * Go back and retry.  The only way to exit this retry loop is when
	 * the time comparison fails, or if an interrupt occurs, etc, or if
	 * a scan of fds actually succeeds in finding events.  In all these
	 * cases, we jump to pollout.
	 */

	goto retry;

 pollout:

	/*
	 * Poll general cleanup code. Go back to all of the streams before the
	 * mark and reset the wakeup mechanisms that were set up during the
	 * poll.  The streams after the mark do not have any cleanup to do
	 * since strpoll() does not set up any state in them.
	 */

	(void) p_lock(&p->p_state, SPLHI);
	p->p_flag &= ~SPOLL;
	v_lock(&p->p_state, s);

	fdp = (caddr_t) uap->fdp;
	for (i = 0; i < mark; i++) {
		j = i % NPOLLFILE;
		/*
		 * Read in next block of pollfds.  If the total number of pollfds
		 * is less than NPOLLFILE, don't bother because the pollfds of 
		 * interest are still in the pollfd[] array.
		 */
		if (!j && (uap->nfds > NPOLLFILE)) {
			size = min((uint) (uap->nfds - i), NPOLLFILE) * sizeof(struct pollfd);
			if (copyin((caddr_t) fdp, (caddr_t) pollfd, (uint) size)) {
				u.u_error = EFAULT;
				return;
			}
			fdp += size;
		}
		GETFP(fp, pollfd[j].fd);
		if (fp != NULL) 
			VOP_STRPOLL(fp->f_vnode, 0, -1);
		if (u.u_fpref != NULL) {
			deref_file(u.u_fpref);
			u.u_fpref = NULL;
		}
	}
}


/*
 * Getmsg system call.
 */
getmsg()
{
	register struct file *fp;
	register struct a {
		int		fdes;
		struct strbuf	*ctl;
		struct strbuf	*data;
		int		flags;
	} *uap = (struct a *)u.u_ap;
	struct strbuf msgctl, msgdata;

	GETFP(fp, uap->fdes);
	if (!fp ||
	    !(fp->f_flag & FREAD)) {
		u.u_error = EBADF;
		return;
	}

	/*
	 * If the line has been vhangup()ed then return as if a
	 * modem disconnect has occured.
	 */
	if (fp->f_flag & FHUPED) {
		/* No error; just return */
		return;
	}
	msgctl.maxlen = -1;
	if (uap->ctl) {
		if (copyin((caddr_t) uap->ctl,
			   (caddr_t) &msgctl, sizeof(struct strbuf))) {
			u.u_error = EFAULT;
			return;
		}
	}
	msgdata.maxlen = -1;
	if (uap->data) {
		if (copyin((caddr_t) uap->data,
			   (caddr_t) &msgdata, sizeof(struct strbuf))) {
			u.u_error = EFAULT;
			return;
		}
	}
	u.u_error = VOP_STRMSGIO(fp->f_vnode,
				 &msgctl, &msgdata, UIO_READ, uap->flags,
				 ((fp->f_flag & FNDELAY) ? IO_NDELAY : 0));

	/* sets return value also */

	if (u.u_error == 0) {
		if ((uap->ctl &&
		     copyout((caddr_t) &msgctl,
			     (caddr_t) uap->ctl, sizeof(struct strbuf))) ||
		    (uap->data &&
		     copyout((caddr_t) &msgdata,
			     (caddr_t) uap->data, sizeof(struct strbuf)))) {
			u.u_error = EFAULT;
		}
	}
}


/*
 * Putmsg system call.
 */
putmsg()
{
	register struct file *fp;
	register struct a {
		int		fdes;
		struct strbuf	*ctl;
		struct strbuf	*data;
		int		flags;
	} *uap = (struct a *)u.u_ap;
	struct strbuf msgctl, msgdata;

	GETFP(fp, uap->fdes);
	if (!fp ||
	    !(fp->f_flag & FWRITE)) {
		u.u_error = EBADF;
		return;
	}

	/*
	 * If the line has been vhangup()ed then return as if a
	 * modem disconnect has occured.
	 */
	if (fp->f_flag & FHUPED) {
		u.u_error = ENXIO;	/* FWRITE case returns ENXIO */
		return;
	}
	msgctl.len = -1;
	if (uap->ctl) {
		if (copyin((caddr_t) uap->ctl,
			   (caddr_t) &msgctl, sizeof(struct strbuf))) {
			u.u_error = EFAULT;
			return;
		}
	}
	msgdata.len = -1;
	if (uap->data) {
		if (copyin((caddr_t) uap->data,
			   (caddr_t) &msgdata, sizeof(struct strbuf))) {
			u.u_error = EFAULT;
			return;
		}
	}
	u.u_error = VOP_STRMSGIO(fp->f_vnode,
				 &msgctl, &msgdata, UIO_WRITE, uap->flags,
				 ((fp->f_flag & FNDELAY) ? IO_NDELAY : 0));
}


int
str_push(i, stp, sp)
	register i;
	register struct stdata *stp;
	register struct snode *sp;	
{
	register queue_t *pushed_q, *q;
	register spl_t s;
	register proc_t *p;

	s = p_lock(&stp->sd_mutex, SPLSTR);

	if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
		u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}
	if (stp->sd_pushcnt >= nstrpush) {
		v_lock(&stp->sd_mutex, s);
		return(EINVAL);
	}
	
	/* 
	 * Everything is okay, so go ahead and push!  Since
	 * this is a plumbing operation, it must serialize
	 * with other plumbers.  Note that the following sleep
	 * is signallable.
	 */
	
	while (1) {
		switch (p_sema_v_lock(&stp->sd_plumb, STOPRI | PCATCH, &stp->sd_mutex)) {
		case 0:
			break;
		case STOPPED:	
			s = p_lock(&stp->sd_mutex, SPLSTR);
			continue;	/* with while loop */
		case PCATCH:
			return(EINTR);
		}
		break;
	}
	
	/* 
	 * At this point, sd_plumb has been acquired.  Since
	 * the lock had been released, all the error checks
	 * must be remade.
	 */
	
	s = p_lock(&stp->sd_mutex, SPLSTR);
	
	if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
		u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
		v_sema(&stp->sd_plumb);
		vall_sema(&stp->sd_timer2);
		v_lock(&stp->sd_mutex, s);
		return(u.u_error);
	}
	
	if (stp->sd_pushcnt >= nstrpush) {
		v_sema(&stp->sd_plumb);
		vall_sema(&stp->sd_timer2);
		v_lock(&stp->sd_mutex, s);
		return(EINVAL);
	}
	
	/* 
	 * Set up to test if the push creates a controlling
	 * tty.
	 */
	p = u.u_procp;
	if (!(stp->sd_openflag & FNOCTTY) && (p->p_sid == p->p_pid) &&
			(!p->p_session->s_ctty) && (!stp->sd_ctty.c_session)) {
		/*
		 * There no current controlling tty or
		 * its has been relinquished.
		 */
#ifdef DEBUG
		if (jc_debug)
			printf("str_push(): new ctty ?\n");
#endif
		stp->sd_flag |= CTTYFLG;
		u.u_ctty = 0;
	}
	
	/*
	 * Push new module.  This routine will return NULL if
	 * unable to allocate queues.  The following does not
	 * call the open routine of the pushed queue.
	 */
	
	pushed_q = qattach(fmodsw[i].f_str, RD(stp->sd_wrq));
	
	/* 
	 * Open the attached module.  The open may sleep, but
	 * it must always return here.  Therefore, all sleeps
	 * must set PCATCH or ignore all signals to avoid a
	 * longjump if a signal arrives.
	 *
	 * The stream lock must be dropped/reacquired within
	 * the open call if it needs to block.  This is okay
	 * from the system call point of view since the
	 * sd_plumb sema will block all subsequent plumbing
	 * operations.  
	 *
	 * Note that puts (and hence service procedures may
	 * get invoked (*before* the open actually completes)
	 * since the lock must be dropped during open.  To
	 * protect against this problem, we substitute the
	 * putnext procedure in the module's put procedures
	 * initially when open is called.  This makes sure
	 * that if this put is invoked (say by putnext from
	 * the module/driver below it), it will behave as if
	 * the module is simply not there (which is going to
	 * be true very soon).
	 *
	 * As part of the module's open processing the procedure
	 * "enable_procs" may be called.  This will substitute the
	 * "real" procedures.
	 *
	 * This switching of put procedure is necessary only
	 * on the read side; we freeze the streams head so
	 * that it does not cause write side puts.
	 *
	 * (See also I_POP for similar considerations.)
	 */
	
	stp->sd_wrq->q_flag |= QFREEZE;
	RD(stp->sd_wrq)->q_flag |= QFREEZE;
	
	if (pushed_q) {
		pushed_q->q_str = stp;
		WR(pushed_q)->q_str = stp;
		/* Switch to alternate q_qinfo for the module open */
		pushed_q->q_qinfo = (struct qinit *)&pushed_q->q_qinfo->_qinit[1];
		v_lock(&stp->sd_mutex, s);
		if ((*pushed_q->q_qinfo->qi_qopen)(pushed_q, sp->s_dev, 0, MODOPEN) == OPENFAIL) {
			s = p_lock(&stp->sd_mutex, SPLSTR);
			qdetach1(pushed_q, 1);
			pushed_q = NULL;
		} else {
			/*
			 * Check to see if open did invoke
			 * "enable_procs".  If not, then do so
			 * here.  This is for backward
			 * compatibility only; it is expected
			 * that the open will call this
			 * procedure.  No locks are necessary.
			 */
			if (!(pushed_q->q_flag & QPROCENAB)) {
				enable_procs(pushed_q);
			}
			s = p_lock(&stp->sd_mutex, SPLSTR);
		}
	}
	
	/* The stream is locked here */
	
	if (!pushed_q) {
		if (!u.u_error)	{
			u.u_error =  ENXIO;
		}
	} else {
		/*
		 * If controlling tty established mark the 
		 * process group and tty vnode pointer.
		 */
		if ((stp->sd_flag & CTTYFLG) && u.u_ctty) {
#ifdef DEBUG
			if (jc_debug)
				printf("strpush(): new_ctty for sid %d\n",
						u.u_procp->p_sid);
#endif
			new_ctty(u.u_procp, STOV(sp), &stp->sd_ctty);
		}
		stp->sd_pushcnt++;
	}
	
	
	/* 
	 * Since we had frozen stream-head, unfreeze it.  This
	 * involves enabling service procedures which may have
	 * been no-ops during the freeze period, and waking up
	 * all the readers and writers who may have been
	 * sleeping due to the freeze.
	 */
	
	stp->sd_wrq->q_flag &= ~QFREEZE;
	RD(stp->sd_wrq)->q_flag &= ~QFREEZE;
	
	/* Reset write side flow-control */
	if ((stp->sd_wrq->q_flag & QTOENAB) || l_canput(stp->sd_wrq)) {
		l_qenable(stp->sd_wrq);
	}
	/* Wakeup writers who may have been blocked */
	vall_sema(&stp->sd_write);
	
	/* Reset read side flow-control */
	for (q = l_backq(RD(stp->sd_wrq->q_next));
	     q && !q->q_qinfo->qi_srvp; q = l_backq(q));
	if (q) {
		l_qenable(q);
	}
	
	/* Wakeup readers who may have been blocked */
	vall_sema(&stp->sd_read);
	
	stp->sd_flag &= ~CTTYFLG;
	
	v_sema(&stp->sd_plumb);	
	vall_sema(&stp->sd_timer2);
	v_lock(&stp->sd_mutex, s);
	return(u.u_error);
}

/*
 * is_strpipe()
 *
 * Checks to see if qp belongs to a valid stream pipe.
 * qp is the write queue of the driver (if its valid).
 * This routine returns 1 if valid pipe and 0 if invalid.
 *
 * Called with the stream locked at SPLSTR.
 */
int
is_strpipe(qp)
	register queue_t *qp;	/* points to pipe's write queue */
{
	/* Temporary stub: strpipe driver should replace this */
	return(qp != NULL);
}

/*
 * Check forground/background permission for controlling terminal.
 * Assumes no locks held.
 */
int
ctty_ioctl(stp, flag)
	register struct stdata *stp;
	int flag;		/* fp->f_flag */
{
	register spl_t spl;
	register int ioflag;

	ioflag = ((flag & FGENTTY) ? IO_GENTTY : 0);
	spl = p_lock(&stp->sd_mutex, SPLSTR);

	/*
	 * If this stream is someones ctty then check fgr/bck permissions.
	 * FNOTOSTOP is used by /dev/console for deamons error logging.
	 */
	while (stp->sd_ctty.c_session && !(flag & FNOTOSTOP)) {
		/*
		 * Note if slept then the lock is dropped.
		 */
		switch (ctty_write(&stp->sd_ctty, 1, &stp->sd_mutex, ioflag)) {
		case CTTY_ERR:
			v_lock(&stp->sd_mutex, spl);
			return(u.u_error);
		case CTTY_SIG:
			v_lock(&stp->sd_mutex, spl);
			return(EINTR);
		case CTTY_SLEPT:
			continue;
		case CTTY_OK:
			break;
		}
		break;
	}
	v_lock(&stp->sd_mutex, spl);
	return(0);
}


/*
 * strvhangup()
 *
 * Set the stream to STRHUP or STRERR in an attempt to force all current
 * io to complete. The file flags f->f_flags have already been set to prevent
 * any more I/O from taking place.
 *
 * Must be called at SPL0.
 */
static strvhangup_to = 4 * HZ;

strvhangup(sp)
	struct snode *sp;
{
	register spl_t 		spl0;
	register struct stdata *stp;
	int			tid;
	struct	session		*s;
	int			strvhangup_timeout();
#ifdef DEBUG
	extern int fvhang_up;
#endif

	STR1PRINTF("strvhangup: begin\n");

#ifdef DEBUG 
	if (fvhang_up)
		printf("strvhangup:\n");
#endif
	spl0 = p_lock(&tbl_mutex, SPLSTR);
	if (!sp || !(stp = sp->s_sptr)) {
		v_lock(&tbl_mutex, spl0);
#ifdef DEBUG
		if (fvhang_up)
			printf("strvhangup: stream gone\n");
#endif
		return;
	}
	(void) p_lock(&stp->sd_mutex, SPLSTR);
#ifdef DEBUG
	if (fvhang_up)
		printf("strvhangup: vhangup got stream\n");
#endif
	/*
	 * Set STRHUP and wake anyone sleeping.
	 */

	str_hangup(stp);

	/*
	 * now wait some time for all the sleepers to see STRHUP or STRERR and
	 * return to the user.
	 */

	tid = timeout((int (*)())strvhangup_timeout, sp, strvhangup_to);
	v_lock(&tbl_mutex, SPLSTR);		/* note spl order */
	(void)p_sema_v_lock(&stp->sd_vhangup, PZERO, &stp->sd_mutex);
	untimeout(tid);
#ifdef DEBUG
	if (fvhang_up)
		printf("strvhangup: woke up after %d seconds\n", strvhangup_to/HZ);
#endif
	spl0 = p_lock(&tbl_mutex, SPLSTR);
	if (stp != sp->s_sptr) {
		v_lock(&tbl_mutex, spl0);
#ifdef DEBUG
		if (fvhang_up)
			printf("strvhangup: stream gone\n");
#endif
		return;
	}
	(void) p_lock(&stp->sd_mutex, SPLSTR);

	sep_clean(&stp->sd_pollist, (proc_t *)NULL);
	sep_clean(&stp->sd_siglist, (proc_t *)NULL);

	/*
	 * now reset the error bits so that the next open succeeds.
	 */

	stp->sd_flag &= ~(STRHUP|STRERR);
	v_lock(&tbl_mutex, SPLSTR);		/* note spl order */

	/* 
	 * Relinquish the controlling tty association.
	 */
	if ((s = stp->sd_ctty.c_session) != NULL) {
#ifdef DEBUG
		if (fvhang_up)
			printf("strvhangup: relinquish ctty\n");
#endif
		if (s->s_ctty == &stp->sd_ctty) {
			(void)p_lock(&s->s_lock, SPLSESS);
			v_lock(&stp->sd_mutex, SPLSESS);	/* note spl */
			relinquish_ctty(s);	/* session unlock on return */
		} else
			v_lock(&stp->sd_mutex, spl0);
	} else
		v_lock(&stp->sd_mutex, spl0);
	
#ifdef DEBUG
	if (fvhang_up)
		printf("strvhangup: vhangup done\n");
#endif
}


/*
 * strrelctty()
 *
 * Finish disassociating the given stream (sp->s_sptr) from its session.
 * The session structure (stp->sd_ctty.c_session) is locked upon entry
 * to this function.
 *
 * Upon return, the session structure is unlocked,
 * and the spl level is set to SPL0.
 */
strrelctty(sp)
	struct snode *sp;
{
	register struct stdata *stp;
	void strwakeup();

	stp = sp->s_sptr;
	finish_relctty(stp->sd_ctty.c_session, &stp->sd_mutex, SPLSTR,
		       strwakeup, (long)stp);
}


/*
 * strwakeup()
 *
 * Wakeup all processes waiting for an I/O operation
 * to complete that was spawned via /dev/tty.
 *
 * This function exists solely for use in the:
 * strrelctty() -> finish_relctty() -> strwakeup()
 * sequence.
 *
 * The given stream must already be locked (sd_mutex held at SPLSTR).
 * Upon exit, the stream remains locked.
 */
void
strwakeup(param)
	long param;
{
	register struct stdata *stp;

	stp = (struct stdata *)param;
	vall_sema(&stp->sd_read);	/* wake up the readers */
	vall_sema(&stp->sd_write);	/* wake up the writers */
	(void)cv_sema(&stp->sd_timer3);	/* wake up ioctller */
}


/*
 * clean an event list (sep) for a process (or if NULL all processes) in 
 * the stream.
 * Assumes caller has stream locked.
 */

sep_clean(sephead, p)
struct strevent **sephead;
proc_t	*p;
{
	register struct strevent *sep,*psep, *tsep;

#ifdef DEBUG
	if (fvhang_up)
		printf("sep_clean(sephead:0x%x,p:0x%x)\n", sephead, p);
#endif
	psep = NULL;
	for (sep = *sephead; sep; ) {
		if ((p == NULL) || (sep->se_procp == p)) {
			tsep = sep->se_next;
			if (psep) {
				psep->se_next = tsep;
			} else {
				*sephead = tsep;
			}
			sefree(sep);
			sep = tsep;
		} else {
			psep = sep;
			sep = sep->se_next;
		}
	}
}

/*
 * strvhangup_timeout - Called via callout when the timeout that we
 * established in strvhangup() expires.
 */

int
strvhangup_timeout(sp)
register struct snode *sp;
{
	register struct stdata *stp;
	register spl_t s;

	s = p_lock(&tbl_mutex, SPLSTR);
	if ((stp = sp->s_sptr) != NULL) {
		/*
		 * We won the race with release_stream()  (but not 
		 * necessarily with stropen()!).  Therefore, the following
		 * cv_sema will wake the sd_vhangup sleeper.  If we had
 		 * lost the race, the vall_sema in release_stream()
		 * will have already caused all sd_hangup sleepers to 
		 * run.  Thus, no need to do it here.
		 *
		 * We are using a cv_sema (rather than a v_sema) since
		 * it is possible enter this block multiple times because
		 * of the following scenario:
		 *
		 * Process A:		     Process B:
		 *    In strvhangup()	     :
		 *    Blocks on sd_vhangup   :
		 *    :			     In release_stream(),last close
		 *    :			     vall_sema() on sd_vhangup
		 *    No longer blocked	     Clears sp->s_sptr
		 *      but may not run yet  Reopens same device & stream
		 *    :			     initializes sd_vhangup.s_count
		 *    :			     sp->s_sptr is no longer NULL
		 *    :
		 *    :		At interrupt level:
		 *    :	        sd_vhangup timer expires
		 *    :		strvhangup_timeout() sees non-null sp->s_sptr
		 *    :         invokes cv_sema(sd_vhangup)
 		 *    :		At this point sd_vhangup.s_count is zero.
		 *    :		Had we done a v_sema the count would have
		 *    :		incorrectly been 1.
		 *    :
		 *    calls untimeout()
		 */
		(void)p_lock(&stp->sd_mutex, SPLSTR);
		(void)cv_sema(&stp->sd_vhangup);
		v_lock(&stp->sd_mutex, SPLSTR);
	}
	v_lock(&tbl_mutex, s);
}
