/* $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.
 */

#ident "$Header: winsiz.c 1.10 89/09/21 $"

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

#include "sys/param.h"
#include "sys/sysmacros.h"
#include "sys/signal.h"
#include "sys/types.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/timer.h"
#include "sys/intctl.h"
#include "sys/file.h"
#include "sys/dir.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/errno.h"
#include "sys/debug.h"
#include "sys/cmn_err.h"
#include "sys/strid.h"
#include "sys/termio.h"

#ifdef DEBUG
/*
 * Flavours
 */
#define DPRINTF1 if (str_debug(STRID_LD0, 1))printf
#define DPRINTF2 if (str_debug(STRID_LD0, 2))printf
#define DPRINTF3 if (str_debug(STRID_LD0, 3))printf
#define DPRINTF4 if (str_debug(STRID_LD0, 4))printf

#define WSZDEBUG DPRINTF2
#endif


/*
 * Process TIOC[GS]WINSZ ioctls.
 *
 *
 * This routine is called by both the ld0 module and the pty drivers.
 *
 *
 * Call for either M_IOCTL, M_PCIOCTL, or M_IOCDATA after verifying that
 * ioc_cmd is TIOCGWINSZ or TIOCSWINSZ.  
 *
 * In the case of ld0, this is called for M_IOCNAK messages for these 
 * ioctls received in the read queue.  This allows ld0 to process these 
 * ioctls only if the lower modules/driver don't.  Because of this,
 * this routine never generates an M_IOCNAK to fail an ioctl but uses
 * M_IOCACK instead to distinguish from the case where the ioctl is being
 * failed because it is not supported.  This prevents the scenario where 
 * a driver calls this routine to process the ioctls, this routine fails 
 * the ioctl, and ld0 sees the NAK message and jumps in itself.  The 
 * principle is that once a lower module/driver begins processing the 
 * ioctl via calling this routine, ld0 should not intervene.
 *
 * A pointer to the caller's copy of the (struct winsiz) parameters is
 * passed.  This is read or written as appropriate.
 *
 * A pointer to a lock_t may optionally be passed.  It will be v_lock'd
 * using the passed spl before returning (after any read or write with 
 * wdold is done).
 *
 * reply_q is the queue to reply to during ioctl servicing.  This is where
 * M_IOCACK, M_COPYOUT, M_COPYIN messages are putnext()'d.
 *
 * sig_q is used to putnextctl1(sig_q, M_SIG, SIGWINCH) in the event that
 * a TIOCSWINSZ alters wdold to new values; it is typically the same as
 * reply_q but may be different (e.g., in the case of ptys).
 *
 * This routine returns in *ioc_idp either ioc_id (from the iocblk in mp)
 * if a transparent ioctl is in progress or zero if no ioctl is in progress.
 * *ioc_idp is updated before releasing the passed in lock_t.
 * This allows the caller to test BEFORE CALLING THIS ROUTINE whether
 * a received M_IOCDATA message is for the ioctl in progress of being 
 * processed by this routine or not.
 *
 * If STREAMS buffer allocation fails, the message is ack'd with
 * errno = EAGAIN.  Using bufcall() might be more appropriate but
 * was harder to do in the context of a general interface which
 * could be called from different STREAMS drivers.
 */


/* 
 * To enable debug printfs, do
 * 	#define WSZDEBUG printf
 * or your favorite variant.
 */
#ifndef WSZDEBUG

#ifdef lint
/*
 * Even though the optimizer tosses null-effect expressions,
 * lint doesn't like them.  So to shut up lint...
 */
#define WSZDEBUG printf
#else
#define WSZDEBUG (void)
#endif

#endif


winsizioctl(reply_q, mp, wdold, sig_q, lockptr, spl, ioc_idp)
        queue_t *reply_q;  	/* read queue */
	mblk_t *mp;		/* M_IOCTL or M_IOCDATA TIOC[GS]WINSZ msg */
	struct winsize  *wdold;	/* ptr to current winsize settings */
        queue_t *sig_q;  	/* signal queue (typically same as reply_q) */
	lock_t *lockptr;	/* if non-zero, ptr to lock to v_lock */
	spl_t spl;		/* spl level for v_lock */
	uint	*ioc_idp;	/* ptr to "in process" ioc_id */
{
	struct iocblk 	*iocp;
	struct copyreq	*send_buf_p;
	struct winsize	*wb;
	mblk_t		*tmp;

	ASSERT_DEBUG((reply_q->q_flag & QREADR), "winsizioctl() wrong queue");
	ASSERT_DEBUG((sig_q->q_flag & QREADR), "winsizioctl() wrong queue");
	ASSERT_DEBUG(((mp->b_datap->db_type == M_IOCNAK) || (mp->b_datap->db_type == M_IOCTL) || (mp->b_datap->db_type == M_PCIOCTL)|| (mp->b_datap->db_type == M_IOCDATA)), "winsizioctl()");

	iocp = (struct iocblk *)mp->b_rptr;

        switch (iocp->ioc_cmd) {

	case TIOCGWINSZ:
		if (mp->b_datap->db_type == M_IOCDATA) {
			struct copyresp	*resp = (struct copyresp *)mp->b_rptr;

			if (resp->cp_rval) {

				/* Discard failed TIOCGWINSZ IOCDATA */

				WSZDEBUG("winsizioctl(): discard failed TIOCGWINSZ IOCDATA cp_rval=0x%x\n",
						resp->cp_rval);

				*ioc_idp = 0;	/* end ioctl processing */
				if (lockptr) v_lock(lockptr, spl);

				freemsg(mp);
				return;
			}

			/* Acknowledge that a previous M_COPYOUT is done */

			WSZDEBUG("winsizioctl(): TIOCGWINSZ ack M_IOCDATA\n");

			*ioc_idp = 0;	/* end ioctl processing */
			if (lockptr) v_lock(lockptr, spl);

			iocp = (struct iocblk *)mp->b_rptr;
			iocp->ioc_error = 0;
			iocp->ioc_count = 0;
			iocp->ioc_rval = 0;
			mp->b_datap->db_type = M_IOCACK;
			putnext(reply_q, mp);
			return;
		}
		/*
		 * If all zeros fail the ioctl for dumb terminals
		 */
		if ((wdold->ws_row    == 0) && (wdold->ws_col    == 0) && 
		    (wdold->ws_xpixel == 0) && (wdold->ws_ypixel == 0)) {

			WSZDEBUG("winsizioctl(): TIOCGWINSZ EINVAL all zeros\n");

			*ioc_idp = 0;	/* end ioctl processing */
			if (lockptr) v_lock(lockptr, spl);
			/* 
			 * Must use IOCACK instead of NAK; 
			 * see procedure header comment above
			 */
			mp->b_datap->db_type = M_IOCACK;
			iocp->ioc_count = 0;
			iocp->ioc_error = EINVAL;
			iocp->ioc_rval = -1;
			putnext(reply_q, mp);
			return;
		}

		if ((tmp = allocb(sizeof(struct winsize), BPRI_MED)) == NULL)  {

			WSZDEBUG("winsizioctl(): TIOCGWINSZ EAGAIN allocb failed\n");
			*ioc_idp = 0;	/* end ioctl processing */
			if (lockptr) v_lock(lockptr, spl);
			/* 
			 * Must use IOCACK instead of NAK; 
			 * see procedure header comment above
			 */
			mp->b_datap->db_type = M_IOCACK;
			iocp->ioc_count = 0;
			iocp->ioc_error = EAGAIN;
			iocp->ioc_rval = -1;
			putnext(reply_q, mp);
			return;
		}

		if (iocp->ioc_count == TRANSPARENT) {

			WSZDEBUG("winsizioctl(): TIOCGWINSZ TRANSPARENT\n");

			*ioc_idp = iocp->ioc_id; /* begin ioctl processing */

			send_buf_p = (struct copyreq *)mp->b_rptr;
			send_buf_p->cq_addr = 
				(caddr_t)(*(long *)(mp->b_cont->b_rptr));
			freemsg(mp->b_cont);
			mp->b_cont = tmp;
			tmp->b_wptr += sizeof(struct winsize);
			send_buf_p->cq_private = NULL;
			send_buf_p->cq_flag = 0;
			send_buf_p->cq_size = sizeof(struct winsize);
			mp->b_datap->db_type = M_COPYOUT;

		} else {

			WSZDEBUG("winsizioctl(): TIOCGWINSZ data\n");

			*ioc_idp = 0;	/* end ioctl processing */

			if (mp->b_cont)
				freemsg(mp->b_cont);
			mp->b_cont = tmp;
			tmp->b_wptr += sizeof(struct winsize);
			mp->b_datap->db_type = M_IOCACK;
			iocp->ioc_count = sizeof(struct winsize);
			iocp->ioc_error = 0;
			iocp->ioc_rval = 0;
		}

		wb = (struct winsize *)mp->b_cont->b_rptr;
		wb->ws_row = wdold->ws_row;
		wb->ws_col = wdold->ws_col;
		wb->ws_xpixel = wdold->ws_xpixel;
		wb->ws_ypixel = wdold->ws_ypixel;

		if (lockptr) v_lock(lockptr, spl);

		putnext(reply_q, mp);
		return;

	case TIOCSWINSZ:

		if (iocp->ioc_count == TRANSPARENT) {
			register struct copyreq *get_buf_p;

			WSZDEBUG("winsizioctl(): TIOCSWINSZ TRANSPARENT request M_COPYIN\n");

			*ioc_idp = iocp->ioc_id; /* begin ioctl processing */
			if (lockptr) v_lock(lockptr, spl);

			get_buf_p = ( struct copyreq *)mp->b_rptr;
			get_buf_p->cq_private = NULL;
			get_buf_p->cq_flag = 0;
			get_buf_p->cq_size = sizeof(struct winsize);
			get_buf_p->cq_addr = (caddr_t) (*(long *)(mp->b_cont->b_rptr));
			mp->b_datap->db_type = M_COPYIN;
			putnext(reply_q, mp);
			return;
		}
		WSZDEBUG("winsizioctl(): TIOCSWINSZ M_IOCDATA\n");
		ASSERT_DEBUG((mp->b_datap->db_type == M_IOCDATA), "winsizioctl()");
		{
			struct copyresp	*resp = (struct copyresp *)mp->b_rptr;

			if (resp->cp_rval) {

				/* Discard failed TIOCSWINSZ IOCDATA */

				WSZDEBUG("winsizioctl(): discard failed TIOCSWINSZ IOCDATA cp_rval=0x%x\n",
						resp->cp_rval);

				*ioc_idp = 0;	/* end ioctl processing */
				if (lockptr) v_lock(lockptr, spl);

				freemsg(mp);
				return;
			}
		}
		wb = (struct winsize *)mp->b_cont->b_rptr;

	        /*
		 * Send a SIGWINCH if the information is new
		 */
		if ((wdold->ws_row    != wb->ws_row) || 
		    (wdold->ws_col    != wb->ws_col) ||
		    (wdold->ws_xpixel != wb->ws_xpixel) ||
		    (wdold->ws_ypixel != wb->ws_ypixel))  {

		        /*
			 * Note that lockptr is still locked during 
			 * putnextclt1(). This is logically necessary to
			 * ensure that no one changes wdold.  It is 
			 * procedurally necessary for ptys to ensure that
			 * sig_q is valid.
			 */
			WSZDEBUG("winsizioctl(): sending SIGWINCH\n");

			if (!putnextctl1(sig_q, M_SIG, SIGWINCH)) {

				WSZDEBUG("winsizioctl(): SIGWINCH putnextctl FAILED\n");

				*ioc_idp = 0;	/* end ioctl processing */
				if (lockptr) v_lock(lockptr, spl);

				/* 
				 * Must use IOCACK instead of NAK; 
				 * see procedure header comment above
				 */
				mp->b_datap->db_type = M_IOCACK;
				iocp->ioc_count = 0;
				iocp->ioc_error = EAGAIN;
				iocp->ioc_rval = -1;
				putnext(reply_q, mp);
				return;
			}
		}

		wdold->ws_row = wb->ws_row;
		wdold->ws_col = wb->ws_col;
		wdold->ws_xpixel = wb->ws_xpixel;
		wdold->ws_ypixel = wb->ws_ypixel;

		*ioc_idp = 0;	/* end ioctl processing */
		if (lockptr) v_lock(lockptr, spl);

		iocp->ioc_count = 0;
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
		mp->b_datap->db_type = M_IOCACK;
		WSZDEBUG("winsizioctl(): M_IOCACK\n");
		putnext(reply_q, mp);
		return;

	default:
		/* caller passed in an inappropriate ioctl msg */

		if (lockptr) v_lock(lockptr, spl);
		cmn_err(CE_WARN,
			"winsizioctl(): illegal ioc_cmd = 0x%x, RD(q) = 0x%x\n",
			iocp->ioc_cmd, reply_q);
		/*
		 *+ A stream tty line discipline invoked the winsizioctl
		 *+ function to perform a window size ioctl, but the ioctl
		 *+ supplied to winsizioctl was not a window size ioctl.
		 *+ This indicates a kernel software problem.
		 */
		freemsg(mp);
		return;
	}
}
