/* $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: str_supp.c 1.67 90/10/18 $"

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


/* 
 * STREAM SYSTEM CALL LAYER SUPPORT ROUTINES
 *
 * This file contains support routines needed by the system call
 * layer for streams.
 *
 * The low-level streams utilities are defined in various other files:
 *   streamio.c => syscall layer
 *   strq.c     => streams queue mgt routines
 *   strmm.c    => msg mgt routines
 *   stream.c   => scheduler and other misc 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/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"


/* id value used to distinguish between different ioctl messages */
static long ioc_id;

extern lock_t	tbl_mutex;		/* synchronizes stream table */
extern lock_t	svc_mutex;		/* synchronizes scheduler */
extern lock_t	id_mutex;		/* synchronizes ioc_ids */
extern lock_t	link_mutex;		/* synchronizes link table */
extern lock_t	poll_mutex;		/* synchronizes poll  */
extern sema_t	poll_sema;		/* used by poll for waiting  */
extern struct qinit strdata;		/* for head queue-pair init */
extern struct qinit stwdata;		/* for head queue-pair init */

extern mblk_t *l_getq();
extern int l_canput();
extern uint bufcall();

#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 str2debug = 0;
#define STR2PRINTF(arg)	if (str2debug) printf(arg); else
#else
#define STR2PRINTF(arg)
#endif


/*
 * Converts file-pointer to a stream pointer.  Returns NULL if not a valid fp.
 */

struct snode *
getspstr(fp)
	register struct file *fp;
{
	register struct vnode *vp;
	register struct snode *sp;
	extern struct vnodeops spec_vnodeops;

	if (!(vp = fp->f_vnode)) {
		return(NULL);
	}
	if (vp->v_op != &spec_vnodeops) {
		/* vp does not point to a specfs device snode */
		return(NULL);
        }
	sp = VTOS(vp);
	if ((vp->v_type != VCHR) || !(sp->s_sptr)) {
		return(NULL);
	}
	return(sp);
}


/* 
 * Attach a stream device or module.  qp is a read queue; the new
 * queue goes in so its next read ptr is qp, and the write queue
 * corresponding to the qp points to this new queue.  Return the new
 * read pointer on success, NULL on failure.
 *
 * Assumes stream is locked.
 */

queue_t *
qattach(qinfo, qp)
	register struct streamtab *qinfo;
	register queue_t *qp;
{
	register queue_t *rq;

	if (!(rq = allocq())) {
		return(NULL);
	}
	rq->q_next = qp;
	rq->q_prev = qp->q_prev;
	WR(rq)->q_prev = WR(qp);
	WR(rq)->q_next = WR(qp)->q_next;
	qp->q_prev = rq;
	WR(qp)->q_next = WR(rq);
	if (rq->q_prev) {
		(rq->q_prev)->q_next = rq;
		WR(rq->q_prev)->q_prev = WR(rq);
	}
	setq(rq, qinfo->st_rdinit, qinfo->st_wrinit);
 	rq->q_flag |= QWANTR;
	WR(rq)->q_flag |= QWANTR;
	return(rq);
}



/*
 * Close and detach a stream module or device.
 * The tricky part of the logic is the synchronization with ongoing activity.
 *
 * Assumes stream is unlocked.
 */

qdetach(qp, flag)
	register queue_t *qp;	/* read queue */
	register flag;		/* meant to passed on to driver's close */
{
	/* 
	 * Run any enqueued service procedures before they are forcibly
	 * removed from the svc_q (this is just a heuristic measure).  This
	 * call must be outside locks.
	 */
		
	runqueues();
	
	/* 
	 * The following close should be called outside sd_mutex since it may 
	 * sleep.  Note that this allows the possibility of the q's
	 * put/service procedure being called AFTER its close has been called.
	 * As part of the close processing, a call to "disable_procs" should 
	 * be made - this disables the puts/service procs from then on.
	 */

	(*qp->q_qinfo->qi_qclose)(qp, (qp->q_next ? 0 : flag));

	/*
	 * Check if procs have been disabled; if not then do so.  This
	 * is for backward compatiblity only.  The driver close is
	 * expected to have done so.  No locks are necessary for the
	 * following logic.
	 */

	if (qp->q_flag & QPROCENAB) {
		disable_procs(qp);
	}
	freeq(qp);
}



/* 
 * Should be called during the first open of a module being pushed.  It
 * enables the various put and service procedures of the queue-pair.  Prior to
 * this call, the put procedures will be logically equivalent to "putnext",
 * i.e.  the module will be bypassed by all traffic.  Hence no flow-control
 * flags need to be fussed.  However, for safety reasons, the queue-pair is
 * frozen before the switch is done.
 *
 * Warning: Driver opens should not call this - only module opens.
 * Assumes that stream is unlocked.
 */
enable_procs(qp)
	register queue_t *qp;	/* read queue */
{
	register spl_t s;
	register struct stdata *stp;

	stp = qp->q_str;
	s = p_lock(&stp->sd_mutex, SPLSTR);
	if (!(qp->q_flag & QPROCENAB)) {
		/* freeze all activity on the queue-pair */
		freeze_procs(qp);

		/* switch from alternate q_qinfo to the real one */
		qp->q_qinfo = (struct qinit *)&qp->q_qinfo->_qinit[-1];

		/* mark the fact that procs are now enabled */
		qp->q_flag |= QPROCENAB;
	}
	v_lock(&stp->sd_mutex, s);
}

/*
 * Should be called during the close processing of drivers/modules.
 *
 * It waits until all currently active put and service procedures
 * finish, and disables all further activation of these procedures (by
 * unlinking the module from the stream).  Does not free the
 * queue-pair.
 *
 * Assumes: No locks should be held by the caller since this routine
 * 	    may sleep before it finishes.
 */
disable_procs(qp)
	register queue_t *qp;	/* read queue */
{
	register spl_t s, s_str;
	register struct stdata *stp;

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

	/* dequeue q-pair if in the svc_q */
	
	s_str = p_lock(&svc_mutex, SPLMM);
	if (qp->q_svcflag & QENAB) {
		svc_dequeue(qp);
		qp->q_svcflag &= ~QENAB;	
	}
	if (WR(qp)->q_svcflag & QENAB) {
		svc_dequeue(WR(qp));
		WR(qp)->q_svcflag &= ~QENAB;	
	}
	v_lock(&svc_mutex, s_str);

	/* the stream is locked at this point */

	/* freeze all activity on the queue-pair */
	freeze_procs(qp);

	/* flush queue-pair of all messages */
	l_flushq(qp, FLUSHALL);
	l_flushq(WR(qp), FLUSHALL);

	/* detach the queue-pair from the stream */
	qdetach1(qp, 0);

	/* mark the fact that procs are now disabled */
	qp->q_flag &= ~QPROCENAB;

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

/*
 * Finishes closing processing for a stream.  At this point only the
 * stream head queues are assumed remaining.  Waits until all
 * currently active put and service procedures finish, and disables
 * all further activation of these procedures.
 *
 * Assumes: stream is unlocked.
 */
release_stream(stp, sp)
	register struct stdata *stp;
	register struct snode *sp;
{
	register spl_t s, s_str;
	register mblk_t *mp, *tmp;
	register queue_t *qp;	/* stream head read queue */

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

	/* dequeue q-pair if in the svc_q */
	s_str = p_lock(&svc_mutex, SPLMM);
	if (qp->q_svcflag & QENAB) {
		svc_dequeue(qp);
		qp->q_svcflag &= ~QENAB;	
	}
	if (WR(qp)->q_svcflag & QENAB) {
		svc_dequeue(WR(qp));
		WR(qp)->q_svcflag &= ~QENAB;	
	}
	v_lock(&svc_mutex, s_str);

	/* the stream is locked at this point */

	/* freeze all activity on the queue-pair */
	freeze_procs(qp);

	/* flush write side queue of all messages */
	l_flushq(WR(qp), FLUSHALL);
	/* 
	 * Flush read queue. Must be done carefully since it 
	 * might have some file descriptors that need closing.
	 */
	mp = qp->q_first;
	qp->q_first = qp->q_last = NULL;
	qp->q_count = 0;

	/* free stream head queue pair */
	freeq(qp);

	if (stp->sd_iocblk) {
		freemsg(stp->sd_iocblk);
		stp->sd_iocblk = NULL;
	}
	v_lock(&stp->sd_mutex, s);

	/* free all msgs which were enqueued at the head */
	while (mp) {
		tmp = mp->b_next;
		if (mp->b_datap->db_type == M_PASSFP) {
			/* this fp is still open, close it */
			closef(((struct strrecvfd *)mp->b_rptr)->f.fp);
		}
		freemsg(mp);
		mp = tmp;
	}

	/* 
	 * Acquire the tbl lock, reacquire the stream lock and break the
	 * stream <-> device binding.  Note that the stream lock is always
	 * acquired after the tbl_lock.  When finished, unblock any racing
	 * opens blocked on the sd_open semaphore, and free the stream head.
	 */

	s = p_lock(&tbl_mutex, SPLSTR);
	s_str = p_lock(&stp->sd_mutex, SPLSTR);
	/* wakeup processes blocked on sd_open due to WOPEN flag */
	vall_sema(&stp->sd_open);
	vall_sema(&stp->sd_vhangup);
	sp->s_sptr = NULL;
	v_lock(&stp->sd_mutex, s_str);
	freestr(stp);
	v_lock(&tbl_mutex, s);
}

/*
 * Freeze the state of the queue-pair with respect to ongoing activity.
 * The activities are handled in the following manner:
 * - Put procedures:
 *	puts already running => loop-and-waits until they finish
 *	puts yet to come     => locks these out with sd_mutex
 * - Service procedure:
 *	svc procedure running   => waits until it finishes
 *	svc procedure enqueued  => waits until it runs and finishes
 *
 * Assumes stream is locked.
 */
freeze_procs(qp)
	register queue_t *qp;	/* read queue */
{
	register spl_t s;
	register queue_t *qp1;
	register struct stdata *stp;

	qp1 = WR(qp);
	stp = qp->q_str;

	/* 
	 * Mark both queues freezing.  This causes subsequently finishing put
	 * and service procedures to cv_sema the sd_freeze semaphore.
	 */

	qp->q_flag |= QFREEZE;
	qp1->q_flag |= QFREEZE;

	/* 
	 * Wait until all ongoing queue-pair activity is finished.
	 *
	 * The ongoing activities may be:
	 *	put procedures running (indicated by putcnt > 0)
	 *	service procedure running (indicated by QSVCBUSY flag)
	 *	service procedure enqueued (indicated by QENAB flag)
	 *
	 * Note that all subsequent qenables will not enqueue the queue
	 * until de-freeze occurs.  Instead, they set the QTOENAB flag
	 * which may then be used by the caller to re-qenable after the
	 * freeze is over.
	 */

 retry:
	if ((qp->q_putcnt == 0) && (qp1->q_putcnt == 0)) {
		/* puts are not active, check for service procs now */
		s = p_lock(&svc_mutex, SPLMM);
		if (((qp->q_svcflag & (QSVCBUSY | QENAB)) == 0) && 
		    ((qp1->q_svcflag & (QSVCBUSY | QENAB)) == 0)) {
			/* service procs are also not active: q-pair is frozen */
			v_lock(&svc_mutex, s);
			qp->q_flag &= ~QFREEZE;
			qp1->q_flag &= ~QFREEZE;
			return;
		}
		v_lock(&svc_mutex, s);
	} 
	
	/* 
	 * Wait for current activity to end.  The following is
	 * non-interruptible.
	 */
	(void) p_sema_v_lock(&stp->sd_freeze, PZERO - 1, &stp->sd_mutex);
	(void) p_lock(&stp->sd_mutex, SPLSTR);  
	goto retry;
}


/*
 * Detach a stream module or device.
 * Assumes stream is locked.
 */

qdetach1(qp, flag)
	register queue_t *qp;	/* read queue */
	register flag;		/* whether qp should be freed also */
{
	if (qp->q_prev) {
		(qp->q_prev)->q_next = qp->q_next;
		WR(qp->q_prev)->q_prev = WR(qp)->q_prev;
	}
	if (qp->q_next) {
		(qp->q_next)->q_prev = qp->q_prev;
		WR(qp->q_next)->q_next = WR(qp)->q_next;
	}
	if (flag) {
		freeq(qp);
	}
}



/* 
 * Given a queue ptr, follow the chain of q_next pointers until you reach the
 * last queue on the chain and return it.
 *
 * Assumes stream locking is taken care of.
 */

queue_t *
getendq(q)
	register queue_t *q;
{
	while (q->q_next) {
		q = q->q_next;
	}
	return(q);
}


/* 
 * Allocate a linkblk table entry for the triple:
 * 	write queue of bottom module of top stream, 
 *	write queue of stream head of bottom stream, 
 *	file table index number.
 *
 * linkblk table entries are freed by nulling the l_qtop field.
 * Returns NULL if unable to allocate link table entry.
 *
 * Assumes stream locking is already taken care of.
 */

struct linkblk *
alloclink(qup, qdown, index)
	queue_t *qup, *qdown;
	int index;
{
	register struct linkblk *linkblkp;
	register spl_t s;

	s = p_lock(&link_mutex, SPLSTR);
	for (linkblkp = &linkblk[0]; linkblkp < &linkblk[nmuxlink]; linkblkp++)
		if (!linkblkp->l_qtop) {
			linkblkp->l_qtop = qup;
			linkblkp->l_qbot = qdown;
			linkblkp->l_index = index;
			v_lock(&link_mutex, s);
			return(linkblkp);
		}

	v_lock(&link_mutex, s);
	return(NULL);
}


/* 
 * Free the linkblk table entry by nulling the l_qtop field.
 * Assumes stream locking is already taken care of.
 */

freelink(linkblkp)
	register struct linkblk *linkblkp;
{
	register spl_t s;

	s = p_lock(&link_mutex, SPLSTR);
	linkblkp->l_qtop = NULL;
	linkblkp->l_qbot = NULL;
	linkblkp->l_index = 0;
	v_lock(&link_mutex, s);
}


/*
 * Same as linkcycle.
 * Assumes link table is locked.
 */

int
l_linkcycle(qup, qdown)
	register queue_t *qup, *qdown;
{
	register struct linkblk *linkblkp;

	for (linkblkp = &linkblk[0]; linkblkp < &linkblk[nmuxlink]; linkblkp++) {
		if (linkblkp->l_qtop == qdown) {
			if ((linkblkp->l_qbot == qup) ||
			    l_linkcycle(qup, linkblkp->l_qbot)) {
				return(1);
			}
		}
	}
	return(0);
}

/*
 * Check for a potential linking cycle.  Returns 1 if, indeed, a cycle.
 *
 * Qup is the upper queue in a multiplexor that is going to be linked.  Qdown
 * is initially the queue to be linked below the multiplexor.  Linkcycle() is
 * called to recursively scan the tree of links rooted at qdown to determine
 * if qup is contained in that tree.
 *
 * Assumes link table is unlocked.
 */

int
linkcycle(qup, qdown)
	register queue_t *qup, *qdown;
{
	register result;
	register spl_t s;

	s = p_lock(&link_mutex, SPLSTR);
	result = l_linkcycle(qup, qdown);
	v_lock(&link_mutex, s);
	return(result);
}




/* 
 * Finds linkblk table entry corresponding to triple.
 * A NULL parameter means any value matches that member of the triple.
 * Returns pointer to linkblk entry.
 *
 * Assumes link table is unlocked.
 */

struct linkblk *
findlinks(qup, qdown, index)
	register queue_t *qup, *qdown;
	register index;
{
	register spl_t s;
	register struct linkblk *linkblkp;

	ASSERT_DEBUG(qup || qdown || index, "findlinks: bad parameter(s)");

	s = p_lock(&link_mutex, SPLSTR);

	for (linkblkp = &linkblk[0]; linkblkp < &linkblk[nmuxlink]; linkblkp++) {
		if (linkblkp->l_qtop &&
		    (!qup || (qup == linkblkp->l_qtop))   &&
		    (!qdown || (qdown == linkblkp->l_qbot)) &&
		    (!index || (index == linkblkp->l_index))) {
			v_lock(&link_mutex, s);
			return(linkblkp);
		}
	}
	v_lock(&link_mutex, s);
	return(NULL);
}


/*
 * Set the interface values for a pair of queues (qinit structure,
 * packet sizes, water marks).
 *
 * The stream is assumed locked.
 */

setq(rq, rinit, winit)
	register queue_t *rq;	/* read queue */
	register struct qinit *rinit, *winit;
{
	register queue_t  *wq;

	wq = WR(rq);

	rq->q_qinfo = rinit;
	rq->q_hiwat = rinit->qi_minfo->mi_hiwat;
	rq->q_lowat = rinit->qi_minfo->mi_lowat;
	rq->q_minpsz = rinit->qi_minfo->mi_minpsz;
	rq->q_maxpsz = rinit->qi_minfo->mi_maxpsz;
	wq->q_qinfo = winit;
	wq->q_hiwat = winit->qi_minfo->mi_hiwat;
	wq->q_lowat = winit->qi_minfo->mi_lowat;
	wq->q_minpsz = winit->qi_minfo->mi_minpsz;
	wq->q_maxpsz = winit->qi_minfo->mi_maxpsz;
}



/*
 *  Tries allocating streams buffers upto three different (decreasing) 
 *  sizes, waiting if necessary.  Returns message if found otherwise
 *  returns NULL.
 *
 * Assumes stream is unlocked.
 */

static mblk_t *
getbp(size, pri, stp)
	register size;
	register uint pri;
	register struct stdata *stp;
{
	register class;
	register mblk_t *bp;

	for (;;) {
		if (size < QBSIZE) {
			if (bp = allocb(size, pri)) {
				return(bp);
			}
			if (strwaitbuf(size, (int) pri, 1, stp)) {
				return(NULL);
			}
		} else {
			if ((class = getclass(size)) == NCLASS) {
				class = NCLASS-1;
				size = rbsize[class];
			}
			if (bp = allocb((int) rbsize[class], pri)) {
				return(bp);
			}
			if (bp = allocb((int) rbsize[--class], pri)) {
				return(bp);
			}
			if (bp = allocb((int) rbsize[--class], pri)) {
				return(bp);
			}
			/*
			 * Have strwaitbuf() call bufcall up to 3
			 * times to mimic 3 allocb() tries above.
			 * This prevents failure if there are no
			 * buffers configured for 'size' bytes.
			 */
			if (strwaitbuf(size, (int) pri, 3, stp)) {
				return(NULL);
			}
		}
	}
}


/*
 * Make a protocol message given control and data buffers.
 * Assumes stream is unlocked (copies user data).
 */

mblk_t *
strmakemsg(mctl, mdata, wroff, flag, seg, stp)
	register struct strbuf *mctl;
	register struct strbuf *mdata;
	register wroff;
	register flag;
	register seg;
	register struct stdata *stp;
{
	register mblk_t *mp = NULL;
	register mblk_t *bp;
	register count;
	register uint pri;
	register msgtype;
	register offlg = 0;
	caddr_t base;

	if (flag & RS_HIPRI) {
		pri = BPRI_MED;
	} else {
		pri = BPRI_LO;
	}

	/* Create control part of message, if any. */

	if (mctl->len >= 0) {

		if (flag & RS_HIPRI) {
			msgtype = M_PCPROTO;
		} else {
			msgtype = M_PROTO;
		}

		count = mctl->len;
		base = mctl->buf;

		/*
		 * Range checking has already been done, simply try
		 * to allocate a message block for the ctl part.
		 */
		while (!(bp = allocb(count, pri))) {
			if (strwaitbuf(count, (int) pri, 1, stp)) {
				return(NULL);
			}
		}

		bp->b_datap->db_type = msgtype;
		switch (seg) {
		case UIOSEG_USER:
			if (copyin(base, (caddr_t) bp->b_wptr, (uint) count)) {
				u.u_error = EFAULT;
				freeb(bp);
				return(NULL);
			}
			break;
		case UIOSEG_KERNEL:
			bcopy(base, (caddr_t) bp->b_wptr, (uint) count);
			break;
		default:
			cmn_err(CE_PANIC, "strmakemsg: invalid segment type for message copy");
			/*
			 *+ strmakemsg was called with an invalid segment type
			 *+ to indicate source/destination for the message copy.
			 */
			break;
		}
		bp->b_wptr += count;
		mp = bp;
	}

	/* Create data part of message, if any. */
	if (mdata->len >= 0) {

		count = mdata->len;
		base = mdata->buf;
		do {
			register size;

			size = count + (offlg ? 0 : wroff);

			if ((bp = getbp(size, pri, stp)) == NULL) {
				freemsg(mp);
				return(NULL);
			}

			if (wroff && !offlg++ &&
			    (wroff < bp->b_datap->db_lim - bp->b_wptr)) {
				bp->b_rptr += wroff;
				bp->b_wptr += wroff;
			}

			if (size = min((uint) count, (uint) (bp->b_datap->db_lim - bp->b_wptr))) {
				switch (seg) {
				case UIOSEG_USER:
					if (copyin(base, (caddr_t) bp->b_wptr, (uint) size)) {
						u.u_error = EFAULT;
						freeb(bp);
						freemsg(mp);
						return(NULL);
					}
					break;
				case UIOSEG_KERNEL:
					bcopy(base, (caddr_t) bp->b_wptr, (uint) size);
					break;
				default:
					cmn_err(CE_PANIC, "strmakemsg: bad seg flag");
					/*
					 *+ strmakemsg was called with an invalid 
					 *+ source/destination segment type.
					 */
					break;
				}
				bp->b_wptr += size;
				base += size;
				count -= size;
			}
			if (!mp) {
				mp = bp;
			} else {
				linkb(mp, bp);
			}

		} while (count);
	}
	return(mp);
}


/*
 * Break up data indicated in uio structure into
 * streams messages.  Uses uiomove() so that the
 * uio structure is correctly updated also.
 *
 * Assumes stream is unlocked (copies user data).
 */

mblk_t *
uiomakemsg(uio, wroff, stp, count)
	struct uio *uio;
	register wroff;
	register struct stdata *stp;
	register count;		/*  total bytes to be copied */
{
	register mblk_t *mp = NULL;
	register mblk_t *bp;
	register size;
	register offlg = 0;

	do {
		size = count + (offlg ? 0 : wroff);
		
		if ((bp = getbp(size, BPRI_LO, stp)) == NULL) {
			freemsg(mp);
			return(NULL);
		}
		
		if (wroff && !offlg++ &&
		    (wroff < bp->b_datap->db_lim - bp->b_wptr)) {
			bp->b_rptr += wroff;
			bp->b_wptr += wroff;
		}
		
		if (size = min((uint)count, (uint) (bp->b_datap->db_lim - bp->b_wptr))) {
			if (uiomove((caddr_t)bp->b_wptr, size, UIO_WRITE, uio)) {
				freeb(bp);
				freemsg(mp);
				return(NULL);
			}
			bp->b_wptr += size;
			count -= size;
		}
		if (!mp) {
			mp = bp;
		} else {
			linkb(mp, bp);
		}
	} while (count);
	return(mp);
}


/*
 * Called via the bufcall mechanism to awaken a process waiting
 * for buffers.
 */
strwakebuf(stp)
	register struct stdata *stp;
{
	register spl_t s;

	s = p_lock(&stp->sd_mutex, SPLSTR);
	vall_sema(&stp->sd_waitbuf);
	v_lock(&stp->sd_mutex, s);
}



/*
 * Wait for a buffer to become available.  Return 1 if not able to wait,
 * 0 if buffer is probably there.  'ncalls' is # of bufcall() attempts.
 *
 * Assumes that the stream is unlocked.
 */
strwaitbuf(size, pri, ncalls, stp)
	register size, pri, ncalls;
	register struct stdata *stp;
{
	register uint token;
	register spl_t s;
	extern int strwakebuf();

	s = p_lock(&stp->sd_mutex, SPLSTR);
	if ((token = bufcall((uint)size, pri, strwakebuf, (long)stp)) == 0) {
		if (ncalls > 1) {
			int i, class;

			class = getclass(size);
			for (i = 1; i < ncalls; i++) {
				if ((class > 0) && (class <= NCLASS)) {
					--class;
				} else {
					v_lock(&stp->sd_mutex, s);
					cmn_err(CE_WARN, "strwaitbuf: invalid class 0x%x", class);
					/*
					 *+ The streams strwaitbuf() routine was invoked with
					 *+ parameters that imply an invalid buffer class.
					 */
					u.u_error = ENOSR;
					return(1);
				}
				if ((token = bufcall((uint)(size = rbsize[class]), 
						     pri, strwakebuf, (long)stp)) != 0) {
					goto bufpass;
				}
			}
		}
		v_lock(&stp->sd_mutex, s);
		u.u_error = ENOSR;
		return(1);
	}

bufpass:
	if (p_sema_v_lock(&stp->sd_waitbuf, STIPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
		unbufcall(token);
		u.u_error = EINTR;
		return(1);
	}
	unbufcall(token);
	return(0);
}


/*
 * Send SIGPOLL signal to all processes registered on the given signal
 * list that want a signal for the specified event.
 *
 * Assumes stream is locked.
 */

strsendsig(siglist, event)
	register struct strevent *siglist;
	register event;
{
	register struct strevent *sep;

	for (sep = siglist; sep; sep = sep->se_next) {
		if (sep->se_events & event) {
			psignal(sep->se_procp, SIGPOLL);
		}
	}
}


/*
 * str_push_mod()
 *	Do an I_PUSH STREAMS ioctl with arguments in kernel rather than
 *	user space.  This function is used by the base, and also by NFS.
 *
 * If an error occurred, the function return value is the non-zero errno.
 */

str_push_mod(mname, stp, sp)
	char *mname;			/* module name to look for */
	struct stdata *stp;
	struct snode *sp;
{
	register int i;

	if ((i = findmod(mname)) < 0) {
		return(EINVAL);
	}
	if ((i = str_push(i, stp, sp)) == 0) { 
		return(str_autop(stp, sp));
	}
	return(i);
}


/*
 * str_find_mod(mname, stp, found)
 *	Do an I_FIND STREAMS ioctl with arguments in kernel, rather than
 *	user space.  This function is used by the base and NFS.
 *
 * If an error occurred, the function return value is the non-zero errno.
 * If no error occurred, *found is set to non-zero if the module was found;
 * otherwise zero.
 */

str_find_mod(mname, stp, found)
	char *mname;			/* module name to look for */
	register struct stdata *stp;
	int *found;
{
	register queue_t *q;
	register int i;
	spl_t s;
	int error;
			
	error = 0;
	*found = 0;

	/* find module in fmodsw */
	if ((i = findmod(mname)) < 0) {
		return(EINVAL);
	}
			
	/* look downstream to see if module is there */
			
	s = p_lock(&stp->sd_mutex, SPLSTR);
			
	/* error checks */
	if (stp->sd_flag & (STPLEX | STRERR)) {
		if (stp->sd_flag & STPLEX) {
			error = EINVAL;
		} else {
			error = stp->sd_error;
		}
		v_lock(&stp->sd_mutex, s);
		return(error);
	}
			
	for (q = stp->sd_wrq->q_next; 
	     q && (fmodsw[i].f_str->st_wrinit != q->q_qinfo); 
	     q = q->q_next)
		;
			
	v_lock(&stp->sd_mutex, s);
			
	if (q) {
		*found = 1;
	}
	return(error);
}


/* 
 * Convert string to number, updating pointer.
 */
str2num(str) 
	register char **str;
{
	register int n;

	n = 0;
	for (; **str >= '0' && **str <= '9'; (*str)++) {
		n = 10 * n + **str - '0';
	}
	return(n);
}

/* 
 * Update canonical format pointer with bytes worth of data 
 */
static
adjfmtp(str, bp, bytes)
	register char **str;
	register mblk_t *bp;
	register bytes;
{
	register caddr_t addr;
	register caddr_t lim;
	register long num;

	addr = (caddr_t) bp->b_rptr;
	lim = addr + bytes;
	while (addr < lim) {
		switch (*(*str)++) {
		case 's':			/* short */
			addr = SALIGN(addr);
			addr = SNEXT(addr);
			break;
		case 'i':			/* integer */
			addr = IALIGN(addr);
			addr = INEXT(addr);
			break;
		case 'l':			/* long */
			addr = LALIGN(addr);
			addr = LNEXT(addr);
			break;
		case 'b':			/* byte */
			addr++;
			break;
		case 'c':			/* character */
			num = str2num(str);
			if (num == 0) {
				while (*addr++)
					;
			} else {
				addr += num;
			}
			break;
		case 0:
			return;
		default:
			break;
		}
	}
}


/*
 *  Put ioctl data from user land to ioctl buffers.  Return 0 for failure,
 *  1 for success.
 *
 *  Assumes stream is unlocked.
 */
static int
putiocd(bp, arg, copymode, flag, fmt, stp)
	register mblk_t *bp;
	caddr_t arg;
	int copymode;
	int flag;
	char *fmt;
	register struct stdata *stp;
{
	register mblk_t *tmp;
	register int count, n;

	switch (bp->b_datap->db_type) {
	case M_PCIOCTL:
	case M_IOCTL:
		count = ((struct iocblk *)bp->b_rptr)->ioc_count;
		break;
	case M_COPYIN:
		count = ((struct copyreq *)bp->b_rptr)->cq_size;
		break;
	default:
		cmn_err(CE_WARN,"putiocd: wrong msg type 0x%x", bp->b_datap->db_type);
		/*
		 *+ The streams ioctl mechanism deals only with some well-known
		 *+ message types.  The ioctl operation being performed has an
		 *+ unknown message type.
		 */
		u.u_error = EINVAL;
		return(0);
	}

	/*
	 * Strdoioctl validates ioc_count, so if this assert fails it
	 * cannot be due to user error.
	 */

	ASSERT_DEBUG((count >= 0), "str_supp: putiocd: count < 0");

	while (count) {
		n = min(MAXIOCBSZ, (uint) count);
		if (flag == SE_SLEEP) {
			while (!(tmp = allocb(n, BPRI_HI))) {
				if (strwaitbuf(n, BPRI_HI, 1, stp)) {
					freemsg(bp->b_cont);
					bp->b_cont = NULL;
					return(0);
				}
			}
		} else {
			if (!(tmp = allocb(n, BPRI_HI))) {
				freemsg(bp->b_cont);
				bp->b_cont = NULL;
				u.u_error = EAGAIN;
				return(0);
			}
		}
		switch (copymode) {
		case K_TO_K:
			bcopy(arg, (caddr_t) tmp->b_wptr, (uint) n);
			break;

		case U_TO_K:
			if (ncopyin(arg, (caddr_t) tmp->b_wptr, (uint) n, fmt)) {
				freemsg(bp->b_cont);
				bp->b_cont = NULL;
				u.u_error = EFAULT;
				return(0);
			}
			if (fmt && count > MAXIOCBSZ) {
				adjfmtp(&fmt, tmp, n);
			}
			break;

		default:
			cmn_err(CE_CONT, "putiocd: unknown streams message type\n");
			/*
			 *+ The streams ioctl mechanism deals only with some well-known
			 *+ message types.  An unknown type is being put as an ioctl message.
			 */

			freemsg(bp->b_cont);
			bp->b_cont = NULL;
			return(0);
		}
		arg += n;
		tmp->b_datap->db_type = M_DATA;
		tmp->b_wptr += n;
		count -= n;
		linkb(bp, tmp);
	}
	return(1);
}

/*
 * Copy ioctl data to user land.  Return 0 for failure, 1 for success.
 *
 * Assumes stream is unlocked.
 */
static int
getiocd(bp, arg, fmt)
	register mblk_t *bp;
	caddr_t arg;
	char *fmt;
{
	register int count, n;

	if (bp->b_datap->db_type == M_IOCACK) {
		count = ((struct iocblk *)bp->b_rptr)->ioc_count;
	} else {
		ASSERT_DEBUG(bp->b_datap->db_type == M_COPYOUT, "getiocd: wrong msg type");
		count = ((struct copyreq *)bp->b_rptr)->cq_size;
	}
	ASSERT_DEBUG((count >= 0), "str_supp: getiocd: count < 0");

	for (bp = bp->b_cont; bp && count; count -= n, bp = bp->b_cont, arg += n) {
		n = min((uint) count, (uint) (bp->b_wptr - bp->b_rptr));
		if (ncopyout((caddr_t) bp->b_rptr, arg, (uint) n, fmt)) {
			u.u_error = EFAULT;
			return(0);
		}
		if (fmt && bp->b_cont) {
			adjfmtp(&fmt, bp, n);
		}
	}
	ASSERT_DEBUG((count == 0), "str_supp: getiocd: count != 0");
	return(1);
}



/*
 * This function is placed in the callout table to wake up a process
 * waiting to close a stream that has not completely drained.
 *
 * Assumes stream is unlocked and that caller's spl is <= SPLSTR.
 */

strtime(stp)
	register struct stdata *stp;
{
	register spl_t s;

	s = p_lock(&stp->sd_mutex, SPLSTR);
	if (stp->sd_flag & STRTIME) {
		v_sema(&stp->sd_timer);
		stp->sd_flag &= ~STRTIME;
	}
	v_lock(&stp->sd_mutex, s);
}


/*
 * This function is placed in the callout table to wake up all processes
 * waiting to send an ioctl down a particular stream.  The process placing
 * this function in the callout table will remove it if he gets control of the
 * ioctl mechanism for the stream - this should only run if there is a
 * failure.
 *
 * Assumes stream is unlocked and that caller's spl is <= SPLSTR.
 */

static
str2time(stp)
	register struct stdata *stp;
{
	register spl_t s;

	s = p_lock(&stp->sd_mutex, SPLSTR);
	vall_sema(&stp->sd_timer2);
	v_lock(&stp->sd_mutex, s);
}

/*
 * This function is placed on the callout table to wake up the the process
 * that has an outstanding ioctl waiting acknowledgement on a stream.  It
 * should be removed from the callout table when the acknowledgement arrives.
 * If this function runs, it is the result of a failure.
 *
 * Assumes stream is unlocked and that caller's spl is <= SPLSTR.
 */

static
str3time(stp)
	struct stdata *stp;
{
	register spl_t s;

	s = p_lock(&stp->sd_mutex, SPLSTR);
	if (stp->sd_flag & STR3TIME) {
		stp->sd_flag &= ~STR3TIME;
		(void) cv_sema(&stp->sd_timer3);
	}
	v_lock(&stp->sd_mutex, s);
}

/*
 * Acquire the sd_plumb semaphore to exclude other plumbing ops.  Block for up
 * to STRTIMOUT sec if there is a outstanding plumbing operation for this
 * stream.
 *
 * All blocked processes will be awakened by a v_sema on sd_plumb
 * semaphore which is done as a result of an ACK or NAK being received for the
 * outstanding ioctl, or as a result of the timer expiring on the outstanding
 * ioctl (a failure), or as a result of any other process waiting on sd_timer2
 * timing out (also a failure).
 *
 * Returns 0 if successful and u_error if not.
 *
 * Assumes stream is locked, UNLOCKS it before leaving (its logically
 * equivalent to p_sema_v_lock with a timer).
 */

int
p_plumb_v_lock(stp)
	register struct stdata *stp;
{
	register id;
	register spl_t s = SPL0;
	timeval_t atv;	/* absolute time value after STR2TIMOUT secs */

	/* first see if sd_plumb is available, if so no need to set timer */
	if (cp_sema(&stp->sd_plumb)) {
		v_lock(&stp->sd_mutex, s);
		return(0);
	}

	/* set timeout on the wait for plumb */
	atv.tv_sec = STRTIMOUT;
	atv.tv_usec = 0;
	P_GATE(G_TIME);
	timevaladd(&atv, &time);
	id = ltimeout(str2time, (caddr_t) stp, hzto(&atv));
	V_GATE(G_TIME);

	while (!cp_sema(&stp->sd_plumb)) {
		if (p_sema_v_lock(&stp->sd_timer2, STIPRI | PCATCH, &stp->sd_mutex) == PCATCH) {
			untimeout(id);
			return(EINTR);
		}
		/* check if time already exceeded */
		s = p_lock(&stp->sd_mutex, SPLSTR);
		P_GATE(G_TIME);
		if (time.tv_sec >= atv.tv_sec) {
			V_GATE(G_TIME);
			v_lock(&stp->sd_mutex, s);
			untimeout(id);
			return(ETIME);
		}
		V_GATE(G_TIME);
		if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
			u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
			v_lock(&stp->sd_mutex, s);
			untimeout(id);
			return(u.u_error);
		}
	}

	/* 
	 * At this point sd_plumb has been acquired.
	 * Release stream lock and reset the timer mechanism.
	 */
	
	v_lock(&stp->sd_mutex, s);
	untimeout(id);
	return(0);
}

/*
 * Undoes the effects of p_plumb_v_lock by doing a v_sema on sd_plumb.
 * Wakes up all processes sleeping on sd_timer2.
 *
 * Assumes stream is unlocked.
 */

v_plumb(stp)
	register struct stdata *stp;
{
	register spl_t s;

	s = p_lock(&stp->sd_mutex, SPLSTR);
	v_sema(&stp->sd_plumb);
	vall_sema(&stp->sd_timer2);
	v_lock(&stp->sd_mutex, s);
}

/*
 * Send an ioctl message downstream and wait for acknowledgement.
 *
 * Assumes stream is unlocked and sd_plumb as already been acquired.
 */

strdoioctl(stp, strioc, copyflg, fmtp, mtype)
	register struct stdata *stp;
	struct strioctl *strioc;
	int copyflg;		/* K_TO_K, U_TO_K etc */
	char *fmtp;		/* canonical format string */
	unsigned char mtype;	/* M_PCIOCTL or M_IOCTL */
{
	register mblk_t *bp;
	register spl_t s, s_str;
	register queue_t *q;
	register id, ret;
	struct iocblk *iocbp;
	struct copyreq *reqp;
	struct copyresp *resp;
	mblk_t *fmtbp;
	int transparent = 0;
	int len = 0;
	caddr_t taddr;

	if (strioc->ic_len == TRANSPARENT) {	/* send arg in M_DATA block */
		transparent = 1;
		strioc->ic_len = sizeof(int);
	}

	if ((strioc->ic_len < 0) || (strioc->ic_len > strmsgsz)) {
		u.u_error = EINVAL;
		return;
	}

	/*
	 * Send down ioctl packet and wait for response.  
	 */

	while (!(bp = allocb((int)max(sizeof(struct iocblk), sizeof(struct copyreq)), BPRI_HI))) {
		if (strwaitbuf(sizeof(struct iocblk), BPRI_HI, 1, stp)) {
			return;
		}
	}

	iocbp = (struct iocblk *) bp->b_wptr;
	iocbp->ioc_count = strioc->ic_len;
	iocbp->ioc_cmd = strioc->ic_cmd;
	iocbp->ioc_uid = u.u_uid;
	iocbp->ioc_gid = u.u_gid;
	iocbp->ioc_error = 0;
	iocbp->ioc_rval = 0;
	bp->b_datap->db_type = mtype;  /* M_IOCTL or M_PCIOCTL */
	bp->b_wptr += sizeof(struct iocblk);

	/* If there is data to copy into ioctl block, do so */

	if (iocbp->ioc_count && !putiocd(bp, strioc->ic_dp, copyflg, SE_NOSLP, fmtp, stp)) {
		freemsg(bp);
		return;
	}

	if (transparent) {
		iocbp->ioc_count = TRANSPARENT;
	}

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

	if (stp->sd_iocblk) {
		freemsg(stp->sd_iocblk);
		stp->sd_iocblk = NULL;
	}

	/* assign sequence number within id_mutex lock */
	s_str = p_lock(&id_mutex, SPLSTR);
	iocbp->ioc_id = ++ioc_id;
	v_lock(&id_mutex, s_str);
	stp->sd_iocid = iocbp->ioc_id;

	/* the following is essentially an inline version of putnext */

	stp->sd_flag |= IOCWAIT;
	q = stp->sd_wrq->q_next;
	q->q_putcnt++;
	v_lock(&stp->sd_mutex, s);

	/* call module's put procedure - note that lock is not held */
	(*q->q_qinfo->qi_putp)(q, bp);

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

	/*
	 * Timed wait for acknowledgment.  The wait time is limited by the
	 * timeout value, which must be a positive integer (number of seconds
	 * to wait, or 0 (use default value of STRTIMOUT seconds), or -1 (wait
	 * forever).  This will be awakened either by an ACK/NAK message
	 * arriving, the timer expiring, or an error of some sort.
	 *
	 * The timer (if any) keeps ticking also over any sequence of copying
	 * due to the processing of transparent ioctls before the ioctl is
	 * ak'd or nak'd.
	 */
 waitioc:
	if (!(stp->sd_flag & STR3TIME) && (strioc->ic_timout >= 0)) {
		id = timeout(str3time, 
			     (caddr_t) stp, 
			     (strioc->ic_timout ? strioc->ic_timout: STRTIMOUT) * HZ);
	}

	stp->sd_flag |= STR3TIME;

	/*
	 * If the reply has already arrived, don't sleep.  If awakened from
	 * the sleep, fail only if the reply has not arrived by then.
	 * Otherwise, process the reply.
	 */

	while (!stp->sd_iocblk) {
		if (stp->sd_flag & (STPLEX | STRERR | STRHUP)) {
			u.u_error = ((stp->sd_flag & STPLEX) ? EINVAL : stp->sd_error);
			stp->sd_flag &= ~(STR3TIME | IOCWAIT);
			v_lock(&stp->sd_mutex, s);
			if (strioc->ic_timout >= 0) {
				untimeout(id);
			}
			return;
		}

		switch (p_sema_v_lock(&stp->sd_timer3, STIPRI | PCATCH, &stp->sd_mutex)) {
		case 0:		/* due to v_sema */
			s = p_lock(&stp->sd_mutex, SPLSTR);
			if (!(stp->sd_flag & STR3TIME)) {
				u.u_error = ETIME;
				break;
			}
			continue;	/* while loop */
		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);
			u.u_error = EINTR;
			break;
		}
		
		/* This is an error ret.  u_error has already been set */

		stp->sd_flag &= ~(STR3TIME | IOCWAIT);
		bp = NULL;
		/*
		 * A message could have come in after we were scheduled
		 * but before we were actually run.
		 */
		if (stp->sd_iocblk) {
			bp = stp->sd_iocblk;
			stp->sd_iocblk = NULL;
		}
		if (bp) {
			if ((bp->b_datap->db_type == M_COPYIN) ||
			    (bp->b_datap->db_type == M_COPYOUT)) {
				if (bp->b_cont) {
					freemsg(bp->b_cont);
					bp->b_cont = NULL;
				}
				bp->b_datap->db_type = M_IOCDATA;
				resp = (struct copyresp *)bp->b_rptr;
				resp->cp_rval = (caddr_t)1;	/* failure */
				q->q_putcnt++;
				v_lock(&stp->sd_mutex, s);
				(*q->q_qinfo->qi_putp)(q, bp);
				s = p_lock(&stp->sd_mutex, SPLSTR);
				q->q_putcnt--;
				if (q->q_flag & QFREEZE) {
					(void)cv_sema(&q->q_str->sd_freeze);
				}
			} else {
				freemsg(bp);
			}
		}
		v_lock(&stp->sd_mutex, s);
		if (strioc->ic_timout >= 0) {
			untimeout(id);
		}
		return;
	}
	ASSERT_DEBUG(stp->sd_iocblk, "str_supp: strdoioctl: NULL sd_iocblk");

	/* stream is locked at this point, prepare to unlock it */

	bp = stp->sd_iocblk;
	stp->sd_iocblk = NULL;
	if ((bp->b_datap->db_type == M_IOCACK) || (bp->b_datap->db_type == M_IOCNAK)) {
		stp->sd_flag &= ~(STR3TIME | IOCWAIT);
		v_lock(&stp->sd_mutex, s);
		if (strioc->ic_timout >= 0) {
			untimeout(id);
		}
	} else {
		v_lock(&stp->sd_mutex, s);
	}

	/* 
	 * Have recieved acknowlegment, process it.  
	 * Stream is unlocked from now on.
	 */

	switch (bp->b_datap->db_type) {

	case M_IOCACK:
		STR2PRINTF("strdoioctl: its an ACK\n");
		/* Positive ack */

		/* set error if indicated */
		iocbp = (struct iocblk *) bp->b_rptr;
		if (iocbp->ioc_error) {
			u.u_error = iocbp->ioc_error;
			break;
		}

		/* set return value */
		u.u_r.r_val1 = iocbp->ioc_rval;

		/*
		 * Data may have been returned in ACK message (ioc_count > 0).
		 * If so, copy it out to the user's buffer.
		 */
		if (iocbp->ioc_count && !transparent) {
			if (strioc->ic_cmd == TCGETA || 
			    strioc->ic_cmd == TIOCGETP ||
			    strioc->ic_cmd == LDGETT) {
				if (!getiocd(bp, strioc->ic_dp, fmtp)) {
					break;
				}
			} else {
				if (!getiocd(bp, strioc->ic_dp, NULL)) {
					break;
				}
			}
		}
		if (!transparent) {
			if (len) {		/* an M_COPYOUT was used with I_STR */
				strioc->ic_len = len;
			} else {
				strioc->ic_len = iocbp->ioc_count;
			}
		}
		break;

	case M_IOCNAK:
		/* Negative ack.
		 * The only thing to do is set error as specified in ack.
		 */
		iocbp = (struct iocblk *) bp->b_rptr;
		u.u_error = (iocbp->ioc_error ? iocbp->ioc_error : EINVAL);
		break;

	case M_COPYIN:
		/*
		 * driver or module has requested user ioctl data
		 */
		reqp = (struct copyreq *)bp->b_rptr;
		fmtbp = bp->b_cont;
		bp->b_cont = NULL;
		if (reqp->cq_flag & RECOPY) {
			/* redo I_STR copyin with canonical processing */
			ASSERT_DEBUG(fmtbp, "strdoioctl: null fmtbp");
			reqp->cq_size = strioc->ic_len;
			ret = putiocd(bp, strioc->ic_dp, copyflg, SE_SLEEP,
					(char *)(fmtbp ? fmtbp->b_rptr : NULL), stp);
		} else if (reqp->cq_flag & STRCANON) {
			/* copyin with canonical processing */
			ASSERT_DEBUG(fmtbp, "strdoioctl: null fmtbp");
			ret = putiocd(bp, reqp->cq_addr, U_TO_K, SE_SLEEP,
					(char *)(fmtbp ? fmtbp->b_rptr : NULL), stp);
		} else {
			/* copyin raw data (i.e. no canonical processing) */
			ret = putiocd(bp, reqp->cq_addr, U_TO_K, SE_SLEEP, NULL, stp);
		}
		if (fmtbp) {
			freemsg(fmtbp);
		}
		if (!ret && bp->b_cont) {
			freemsg(bp->b_cont);
			bp->b_cont = NULL;
		}

		bp->b_wptr = bp->b_rptr + sizeof(struct copyresp);
		bp->b_datap->db_type = M_IOCDATA;
		resp = (struct copyresp *)bp->b_rptr;
		resp->cp_rval = (caddr_t)!ret;

		/* inline version of putnext */
		s = p_lock(&stp->sd_mutex, SPLSTR);
		/* 
		 * No checks for error necessary since sd_plumb was held thruout 
		 * and the following reply must be made anyway.
		 */
		q->q_putcnt++;
		v_lock(&stp->sd_mutex, s);
		(*q->q_qinfo->qi_putp)(q, bp);
		s = p_lock(&stp->sd_mutex, SPLSTR);
		q->q_putcnt--;
		if (q->q_flag & QFREEZE) {
			(void)cv_sema(&q->q_str->sd_freeze);
		}

		if (!ret) {
			v_lock(&stp->sd_mutex, s);
			u.u_error = EFAULT;
			if (strioc->ic_timout >= 0)
				untimeout(id);
			return;
		}
		goto waitioc;	/* stream is locked */

	case M_COPYOUT:
		/*
		 * driver or module has ioctl data for a user
		 */
		reqp = (struct copyreq *)bp->b_rptr;
		ASSERT_DEBUG(bp->b_cont, "strdoioctl: invalid bp in M_COPYOUT");
		if (transparent) {
			taddr = reqp->cq_addr;
		} else {
			taddr = strioc->ic_dp;
			len = reqp->cq_size;
		}
		if (reqp->cq_flag & STRCANON) {
			/* copyout with canonical processing */
			if (fmtbp = bp->b_cont) {
				bp->b_cont = fmtbp->b_cont;
				fmtbp->b_cont = NULL;
			}
			ret = getiocd(bp, taddr,
				(char *)(fmtbp ? fmtbp->b_rptr : NULL));
			if (fmtbp) {
				freeb(fmtbp);
			}
		} else {
			/* copyout raw data (i.e. no canonical processing) */
			ret = getiocd(bp, taddr, NULL);
		}
		freemsg(bp->b_cont);
		bp->b_cont = NULL;

		bp->b_wptr = bp->b_rptr + sizeof(struct copyresp);
		bp->b_datap->db_type = M_IOCDATA;
		resp = (struct copyresp *)bp->b_rptr;
		resp->cp_rval = (caddr_t)!ret;

		/* inline version of putnext */
		s = p_lock(&stp->sd_mutex, SPLSTR);
		/* 
		 * No checks for error necessary since sd_plumb was held thruout 
		 * and the following reply must be made anyway.
		 */
		q->q_putcnt++;
		v_lock(&stp->sd_mutex, s);

		(*q->q_qinfo->qi_putp)(q, bp);

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

		if (!ret) {
			v_lock(&stp->sd_mutex, s);
			u.u_error = EFAULT;
			if (strioc->ic_timout >= 0)
				untimeout(id);
			return;
		}
		goto waitioc;	/* stream is locked */

	default:
		cmn_err(CE_CONT, "strdoioctl: unknown streams message type\n");
		/*
		 *+ The streams ioctl mechanism deals only with some well-known
		 *+ message types.  An unknown type has been received by the stream
		 *+ head as an acknowledgement to an ioctl message.
		 */
		break;
	}
	freemsg(bp);
}



/*
 * Unlink a multiplexer link.  Stp is the controlling stream for the
 * link, fpdown is the file pointer for the lower stream, and linkblkp
 * points to the link's entry in the linkblk table.
 *
 * Assumes stream is unlocked and sd_plumb as already been acquired.
 */

int
munlink(stp, fpdown, linkblkp, cflag)
	register struct stdata *stp;
	register struct file *fpdown;
	register struct linkblk *linkblkp;
	register cflag;
{
	struct strioctl strioc;
	register struct stdata *stpdown;
	register struct snode *sp;
	register queue_t *rq, *q;
	register spl_t s;

	strioc.ic_cmd = I_UNLINK;
	strioc.ic_timout = 0;
	strioc.ic_len = sizeof(struct linkblk);
	strioc.ic_dp = (char *) linkblkp;
	
	strdoioctl(stp, &strioc, K_TO_K, STRLINK, M_IOCTL);

	/*
	 * If there was an error and this is not called via strclose,
	 * return to the user.  Otherwise, pretend there was no error
	 * and close the link.
	 */

	if (u.u_error) {
		if (!cflag) {
			return(-1);
		} else {
			/*
			 * The munlink() operation can fail because of the
			 * way that networking assembles and tears down
			 * its structures.  We just fake success as the
			 * desired effect has already been achieved.
			 */
			u.u_error = 0;	
			stp->sd_flag &= ~STRERR; 
		}
	}

	sp = getspstr(fpdown);
	ASSERT_DEBUG(sp, "munlink: bad link table entry");
	stpdown = sp->s_sptr;
	ASSERT_DEBUG(stpdown, "munlink: null stream");

	/* 
	 * Fixup the head queues of the bottom stream.  But the
	 * stream's head queue pair needs to be frozen first.
	 */

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

	rq = RD(stpdown->sd_wrq);
	freeze_procs(rq);

	/* Switch queues (head pair is frozen) */

	setq(rq, &strdata, &stwdata);
	rq->q_ptr = WR(rq)->q_ptr = (caddr_t) stpdown;
	
	/* 
	 * Unfreeze head queue-pair.  Reset the read side flow-control.  The
	 * write side reset is not needed since that is under the stream
	 * head's control.
	 */

	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);
	}

	/* Mark stream as unmuxed.  This allows further operations */
	stpdown->sd_flag &= ~STPLEX;
	/* Nobody sleeps on this condition, hence nobody to wake up */        

	v_lock(&stpdown->sd_mutex, s);

	freelink(linkblkp);
	closef(fpdown);
	return(0);
}


/*
 * Unlink all multiplexer links for which stp is the controlling
 * stream.
 *
 * Assumes stream is unlocked and sd_plumb as already been acquired.
 */

munlinkall(stp, cflag)
	register struct stdata *stp;
	register cflag;
{
	register struct file *fpdown;
	register struct linkblk *linkblkp;
	register queue_t *qup;

	qup = getendq(stp->sd_wrq);
	while (linkblkp = findlinks(qup, (queue_t *) NULL, NULL)) {
		fpdown = &file[linkblkp->l_index];
		ASSERT_DEBUG((fpdown >= file) && (fpdown < (struct file *)v.ve_file), 
			     "munlinkall: bad entry in link table");
		if (munlink(stp, fpdown, linkblkp, cflag) == -1) {
			cmn_err(CE_CONT, "munlink failed\n");
			/*
			 *+ The streams unlinking mechanism should not fail under normal
			 *+ circumstances, yet an unlink operation has failed.
			 */

			return;
		}
	}
	return;
}




/*
 * Stream read put procedure.  Called from downstream driver/module with
 * messages for the stream head.  Data, protocol, and in-stream signal
 * messages are placed on the queue, others are handled directly.
 *
 * Assumes stream is unlocked.
 */

strrput(q, bp)
	register queue_t *q;
	register mblk_t *bp;
{
	register struct stdata *stp;
	register struct iocblk *iocbp;
	register spl_t s;
	struct stroptions *sop;
	struct copyreq *reqp;
	struct copyresp *resp;

	stp = (struct stdata *)q->q_ptr;

	s = p_lock(&stp->sd_mutex, SPLSTR);
	ASSERT_DEBUG(!(stp->sd_flag & STPLEX), "streamio: strrput: invalid stream");

	switch (bp->b_datap->db_type) {

	case M_DATA:
	case M_PROTO:
	case M_PCPROTO:
	case M_PASSFP:
		if (bp->b_datap->db_type == M_PCPROTO) {
			/*
			 * Only one priority protocol message is allowed at
			 * the stream head at a time.
			 */
			if (stp->sd_flag & STRPRI) {
				freemsg(bp);
				v_lock(&stp->sd_mutex, s);
				return;
			}
			stp->sd_flag |= STRPRI;
			
			if (stp->sd_sigflags & S_HIPRI) {
				strsendsig(stp->sd_siglist, S_HIPRI);
			}

			if (stp->sd_pollflags & POLLPRI) {
				strwakepoll(stp, POLLPRI);
			}
		} else {
			if (!q->q_first) {
				if (stp->sd_sigflags & S_INPUT) {
					strsendsig(stp->sd_siglist, S_INPUT);
				}
				if (stp->sd_pollflags & POLLIN) {
					strwakepoll(stp, POLLIN);
				}
			}
		}

		/* 
		 * Enq msg and wake up sleeping read/getmsg.
		 */
		l_putq(q, bp);
		vall_sema(&stp->sd_read);
		v_lock(&stp->sd_mutex, s);
		return;

	case M_ERROR:
		/* 
		 * An error has occured downstream, the errno is in the first
		 * byte of the message.
		 */
		if (*bp->b_rptr != 0) {
			stp->sd_flag |= STRERR;
			stp->sd_error = *bp->b_rptr;

			vall_sema(&stp->sd_read);	/* wake up readers */
			vall_sema(&stp->sd_write);	/* wake up writers */
			(void)cv_sema(&stp->sd_timer3);	/* wake up ioctller */
			strwakepoll(stp, POLLERR);	/* wake up pollers */
			
			bp->b_datap->db_type = M_FLUSH;
			*bp->b_rptr = FLUSHRW;

			/* Must release lock before calling qreply */
			v_lock(&stp->sd_mutex, s);
			qreply(q, bp);
			return;
		}
		v_lock(&stp->sd_mutex, s);
		freemsg(bp);
		return;

	case M_HANGUP:
		freemsg(bp);
		str_hangup(stp);
		v_lock(&stp->sd_mutex, s);
		return;

	case M_SIG:
		/*
		 * Someone downstream wants to post a signal.  The signal to
		 * post is contained in the first byte of the message.  If
		 * the message would go on the front of the queue, send a
		 * signal to the process group (if not SIGPOLL) or to the
		 * siglist processes (SIGPOLL).  If something is already on
		 * the queue, just enqueue the message.
		 */

		if (q->q_first) {
			l_putq(q, bp);
			v_lock(&stp->sd_mutex, s);
			return;
		}
		/* flow through */

	case M_PCSIG:
		/*
		 * Don't enqueue, just post the signal.
		 */
		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;
		}
		v_lock(&stp->sd_mutex, s);
		freemsg(bp);
		return;

	case M_FLUSH:
		/*
		 * Flush queues.  The indication of which queues to flush is
		 * in the first byte of the message.  If the read queue is
		 * specified, then flush it.
		 */

		if (*bp->b_rptr & FLUSHR) {
			register queue_t *bq;
			register mblk_t *mp, *tmp;
			register int wantw;

			mp = q->q_first;
			q->q_first = q->q_last = NULL;
			q->q_count = 0;
			wantw = q->q_flag & QWANTW;
			q->q_flag &= ~(QFULL | QWANTW);
			stp->sd_flag &= ~STRPRI;

			/* Wake up any waiting ioctl(I_FLUSH) operation */
			if (stp->sd_flag & FLUSHWAIT) {
				stp->sd_flag &= ~FLUSHWAIT;
				(void)cv_sema(&stp->sd_timer3);
			}

			/* back-enable any queue blocked due to flow-control */
			if ((q->q_count <= q->q_lowat) && wantw) {
				/* find nearest back queue with service proc */
				for (bq = l_backq(q); bq && !bq->q_qinfo->qi_srvp; bq = l_backq(bq));
				
				/* enable it */
				if (bq) {
					l_qenable(bq);
				}
			}
			v_lock(&stp->sd_mutex, s);

			while (mp) {
				tmp = mp->b_next;
				if (mp->b_datap->db_type == M_PASSFP) {
					closef(((struct strrecvfd *)mp->b_rptr)->f.fp);
				}
				freemsg(mp);
				mp = tmp;
			}
		} else {
			/* Read queue is not be flushed */
			v_lock(&stp->sd_mutex, s);
		}

		/* stream unlocked here */

		if (*bp->b_rptr & FLUSHW) {
			*bp->b_rptr &= ~FLUSHR;
			qreply(q, bp);
			return;
		}
		freemsg(bp);
		return;

	case M_IOCACK:
	case M_IOCNAK:
		iocbp = (struct iocblk *) bp->b_rptr;

		/*
		 * If not waiting for ACK or NAK then just free msg.
		 * If incorrect id sequence number then just free msg.
		 * If already have ACK or NAK for user then just free msg.
		 */

		if ((stp->sd_flag & IOCWAIT) == 0 || 
		    stp->sd_iocblk || 
		    (stp->sd_iocid != iocbp->ioc_id)) {
			v_lock(&stp->sd_mutex, s);
			freemsg(bp);
			return;
		}

		/*
		 * Assign ACK or NAK to user and wake up.
		 */
		stp->sd_iocblk = bp;
		(void)cv_sema(&stp->sd_timer3);	/* wake up ioctller */
		v_lock(&stp->sd_mutex, s);
		return;

	case M_COPYIN:
	case M_COPYOUT:
		reqp = (struct copyreq *)bp->b_rptr;

		/*
		 * if not waiting for ACK or NAK then just fail request
		 * if already have ACK, NAK, or copy request, then just fail request
		 * if incorrect id sequence number then just fail request
		 */

		if ((stp->sd_flag & IOCWAIT) == 0 ||
		    stp->sd_iocblk || 
		    (stp->sd_iocid != reqp->cq_id)) {
			if (bp->b_cont) {
				freemsg(bp->b_cont);
				bp->b_cont = NULL;
			}
			bp->b_datap->db_type = M_IOCDATA;
			resp = (struct copyresp *)bp->b_rptr;
			resp->cp_rval = (caddr_t)1;	/* failure */
			v_lock(&stp->sd_mutex, s);
			qreply(q, bp);
			return;
		}

		/*
		 * Assign copy request to user and wake up
		 */
		stp->sd_iocblk = bp;
		(void)cv_sema(&stp->sd_timer3);	/* wake up ioctller */
		v_lock(&stp->sd_mutex, s);
		return;

	case M_SETOPTS:
	case M_PCSETOPTS:
		/*
		 * Set stream head options (read option, write offset,
		 * min/max packet size, and/or high/low water marks for the
		 * read side only).
		 */

		ASSERT_DEBUG((bp->b_wptr - bp->b_rptr) == sizeof(struct stroptions), 
		       "streamio: strrput: M_SETOPTS");

		sop = (struct stroptions *) bp->b_rptr;
		if (sop->so_flags & SO_READOPT) {
			switch (sop->so_readopt) {
			case RNORM: 
				stp->sd_flag &= ~(RMSGDIS | RMSGNODIS);
				break;
			case RMSGD:
				stp->sd_flag = (stp->sd_flag & ~RMSGNODIS) | RMSGDIS;
				break;
			case RMSGN:
				stp->sd_flag = (stp->sd_flag & ~RMSGDIS) | RMSGNODIS;
				break;
			}
		}
				
		if (sop->so_flags & SO_WROFF) {
			stp->sd_wroff = sop->so_wroff;
		}
		if (sop->so_flags & SO_MINPSZ) {
			q->q_minpsz = sop->so_minpsz;
		}
		if (sop->so_flags & SO_MAXPSZ) {
			q->q_maxpsz = sop->so_maxpsz;
		}
		if (sop->so_flags & SO_HIWAT) {
			q->q_hiwat = sop->so_hiwat;
		}
		if (sop->so_flags & SO_LOWAT) {
			q->q_lowat = sop->so_lowat;
		}
		if (sop->so_flags & SO_MREADON) {
			stp->sd_flag |= SNDMREAD;
		}
		if (sop->so_flags & SO_MREADOFF) {
			stp->sd_flag &= ~SNDMREAD;
		}
		if (sop->so_flags & SO_NDELON) {
			stp->sd_flag |= OLDNDELAY;
		}
		if (sop->so_flags & SO_NDELOFF) {
			stp->sd_flag &= ~OLDNDELAY;
		}
		if (sop->so_flags & SO_VMODEON) {
			stp->sd_flag |= TTYVMODE;
			stp->sd_vmin = sop->so_vmin;
			stp->sd_vtime = sop->so_vtime;
		}
		if (sop->so_flags & SO_VMODEOFF) {
			stp->sd_flag &= ~TTYVMODE;
		}
		if (sop->so_flags & SO_TOSTOPON) {
			stp->sd_tostop = sop->so_tostop;
		}
		if (sop->so_flags & SO_TOSTOPOFF) {
			stp->sd_tostop = 0;
		}

		/*
		 * Flow control may have been affected, so reset this
		 * mechanism.
		 */

		if ((q->q_count <= q->q_lowat) && (q->q_flag & QWANTW)) {
			q->q_flag &= ~QWANTW;
			for (q = l_backq(q); q && !q->q_qinfo->qi_srvp; q = l_backq(q));
			if (q) {
				l_qenable(q);
			}
		}

		v_lock(&stp->sd_mutex, s);
		freemsg(bp);
		return;

		/*
		 * The following set of cases deal with situations where two
		 * stream heads are connected to each other (twisted
		 * streams).  These messages should never originate at a
		 * driver or module.
		 */

	case M_BREAK:
	case M_CTL:
	case M_PCCTL:
	case M_DELAY:
	case M_START:
	case M_STOP:
	case M_IOCDATA:
		v_lock(&stp->sd_mutex, s);
		freemsg(bp);
		return;

	case M_IOCTL:
	case M_PCIOCTL:
		/*
		 * Always NAK this condition (makes no sense).
		 */
		bp->b_datap->db_type = M_IOCNAK;
		v_lock(&stp->sd_mutex, s);
		qreply(q, bp);
		return;

	default:
		cmn_err(CE_CONT, "putiocd: unknown streams message type\n");
		/*
		 *+ The stream head deals only with some well-known message types.
		 *+ An unknown type is being put at the stream head read queue.
		 */
		v_lock(&stp->sd_mutex, s);
		freemsg(bp);
		return;
	}
}




/*
 * Stream head write service routine.
 * Its job is to wake up any sleeping writers when a queue downstream needs
 * data (part of the flow control in putq and getq).  It also must wake anyone
 * sleeping on a poll, and to signal anyone who has arranged to be signalled.
 *
 * Assume stream is unlocked.
 */

strwsrv(q)
	register queue_t *q;
{
	register struct stdata *stp;
	register spl_t s;

	stp = (struct stdata *)q->q_ptr;

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

	ASSERT_DEBUG(!(stp->sd_flag & STPLEX), "streamio: strwsrv: STPLEX");

	/* if strwq is freezing, just mark QTOENAB and return */

	if (q->q_flag & QFREEZE) {
		q->q_flag |= QTOENAB;
		v_lock(&stp->sd_mutex, s);
		return;
	}
	if (stp->sd_sigflags & S_OUTPUT) {
		strsendsig(stp->sd_siglist, S_OUTPUT);
	}
	if (stp->sd_pollflags & POLLOUT) {
		strwakepoll(stp, POLLOUT);
	}
	vall_sema(&stp->sd_write);	/* wakes up writers */
	v_lock(&stp->sd_mutex, s);
}


/*
 * Gets a buffer for the M_READ notification, waiting if necessary.
 */
mblk_t *
getrdbuf(stp)
	register struct stdata *stp;
{
	register mblk_t *mp;

	while ((mp = allocb(sizeof(long), BPRI_MED)) == NULL) {
		if (strwaitbuf(sizeof(long), (int)BPRI_MED, 1, stp)) {
			if (u.u_error == ENOSR) {
				u.u_error = EAGAIN;
			}
			return(NULL);
		}
	}
	return(mp);
}


/*
 * Called via the timeout mechanism to awaken a reader process waiting
 * for VTIME to expire.  For safety vall_sema is used, although the
 * VMIN/VTIME semantics become undefined if more than one reader processes
 * wait at the same time.
 */
strvtime(stp)
	struct stdata *stp;
{
	register spl_t s;

	s = p_lock(&stp->sd_mutex, SPLSTR);
	stp->sd_flag &= ~STRVTIME;
	vall_sema(&stp->sd_read);
	v_lock(&stp->sd_mutex, s);
}


/*
 * Set the STRHUP bit in the stream head and wake up any sleepers.
 * Assume stream is locked by caller.
 */
str_hangup(stp)
	register struct stdata *stp;
{
	stp->sd_error = ENXIO;
	stp->sd_flag |= STRHUP;

	/*
	 * Send signal if controlling tty.
	 */

	if (stp->sd_ctty.c_pgrp) {
		signal(stp->sd_ctty.c_pgrp, SIGHUP);
		signal(stp->sd_ctty.c_pgrp, SIGCONT);
	}

	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 */
	strwakepoll(stp, POLLHUP);	/* wake up pollers */

}
