/*
 * Copyright (c) 1996-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

/*
 * PTY - Stream "pseudo-tty" device.  For each "controller" side
 * it connects to a "slave" side.
 */

#pragma ident	"@(#)tty_pty.c	1.51	99/11/05 SMI"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/filio.h>
#include <sys/ioccom.h>
#include <sys/termios.h>
#include <sys/termio.h>
#include <sys/ttold.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/tty.h>
#include <sys/user.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/vnode.h>	/* 1/0 on the vomit meter */
#include <sys/proc.h>
#include <sys/uio.h>
#include <sys/errno.h>
#include <sys/strsubr.h>
#include <sys/poll.h>
#include <sys/sysmacros.h>
#include <sys/debug.h>
#include <sys/procset.h>
#include <sys/cred.h>
#include <sys/ptyvar.h>
#include <sys/suntty.h>
#include <sys/stat.h>

#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

extern int npty;	/* number of pseudo-ttys configured in */
extern struct pty *pty_softc;
extern struct pollhead	ptcph;	/* poll head for ptcpoll() use */

int ptcopen(dev_t *, int, int, struct cred *);
int ptcclose(dev_t, int, int, struct cred *);
int ptcwrite(dev_t, struct uio *, struct cred *);
int ptcread(dev_t, struct uio *, struct cred *);
int ptcioctl(dev_t, int, intptr_t, int, struct cred *, int *);
int ptcpoll(dev_t, short, int, short *, struct pollhead **);

static int ptc_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int ptc_identify(dev_info_t *);
static int ptc_attach(dev_info_t *, ddi_attach_cmd_t);
static dev_info_t *ptc_dip;	/* for dev-to-dip conversions */

static void ptc_init(void), ptc_uninit(void);

static int makemsg(ssize_t count, struct uio *uiop,
    struct pty *pty, mblk_t **mpp);

struct cb_ops	ptc_cb_ops = {
	ptcopen,		/* open */
	ptcclose,		/* close */
	nodev,			/* strategy */
	nodev,			/* print */
	nodev,			/* dump */
	ptcread,		/* read */
	ptcwrite,		/* write */
	ptcioctl, 		/* ioctl */
	nodev,			/* devmap */
	nodev,			/* mmap */
	nodev,			/* segmap */
	ptcpoll,		/* poll */
	ddi_prop_op,		/* prop_op */
	0,			/* streamtab */
	D_NEW | D_MP		/* Driver compatibility flag */
};

struct dev_ops	ptc_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* refcnt */
	ptc_info,		/* info */
	ptc_identify,		/* identify */
	nulldev,		/* probe */
	ptc_attach,		/* attach */
	nodev,			/* detach */
	nodev,			/* reset */
	&ptc_cb_ops,		/* driver operations */
	(struct bus_ops *)0	/* bus operations */
};

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/modctl.h>

extern int dseekneg_flag;
extern struct mod_ops mod_driverops;
extern struct dev_ops ptc_ops;

/*
 * Module linkage information for the kernel.
 */

static struct modldrv modldrv = {
	&mod_driverops, /* Type of module.  This one is a pseudo driver */
	"tty pseudo driver control 'ptc'",
	&ptc_ops,	/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1,
	&modldrv,
	NULL
};

int
_init()
{
	int rc;

	if ((rc = mod_install(&modlinkage)) == 0)
		ptc_init();
	return (rc);
}


int
_fini()
{
	int rc;

	if ((rc = mod_remove(&modlinkage)) == 0)
		ptc_uninit();
	return (rc);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

static int
ptc_identify(dev_info_t *devi)
{
	if (strcmp(ddi_get_name(devi), "ptc") == 0)
		return (DDI_IDENTIFIED);
	else
		return (DDI_NOT_IDENTIFIED);
}

static char	*pty_banks = PTY_BANKS;
static char	*pty_digits = PTY_DIGITS;

/* ARGSUSED */
static int
ptc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	char	name[8];
	int	pty_num;
	char	*pty_digit = pty_digits;
	char	*pty_bank = pty_banks;

	for (pty_num = 0; pty_num < npty; pty_num++) {
		(void) sprintf(name, "pty%c%c", *pty_bank, *pty_digit);
		if (ddi_create_minor_node(devi, name, S_IFCHR,
			pty_num, NULL, NULL) == DDI_FAILURE) {
			ddi_remove_minor_node(devi, NULL);
			return (-1);
		}
		if (*(++pty_digit) == '\0') {
			pty_digit = pty_digits;
			if (*(++pty_bank) == '\0')
				break;
		}
	}
	ptc_dip = devi;
	return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
ptc_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	int error;

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if (ptc_dip == NULL) {
			*result = (void *)NULL;
			error = DDI_FAILURE;
		} else {
			*result = (void *) ptc_dip;
			error = DDI_SUCCESS;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)0;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}

static void
ptc_init(void)
{
	minor_t dev;

	for (dev = 0; dev < npty; dev++) {
		cv_init(&pty_softc[dev].pt_cv_flags, NULL, CV_DEFAULT, NULL);
		cv_init(&pty_softc[dev].pt_cv_readq, NULL, CV_DEFAULT, NULL);
		cv_init(&pty_softc[dev].pt_cv_writeq, NULL, CV_DEFAULT, NULL);
		mutex_init(&pty_softc[dev].ptc_lock, NULL, MUTEX_DEFAULT, NULL);
	}
}

static void
ptc_uninit(void)
{
	minor_t dev;

	for (dev = 0; dev < npty; dev++) {
		cv_destroy(&pty_softc[dev].pt_cv_flags);
		cv_destroy(&pty_softc[dev].pt_cv_readq);
		cv_destroy(&pty_softc[dev].pt_cv_writeq);
		mutex_destroy(&pty_softc[dev].ptc_lock);
	}
}

/*
 * Controller side.  This is not, alas, a streams device; there are too
 * many old features that we must support and that don't work well
 * with streams.
 */

/*ARGSUSED*/
int
ptcopen(dev_t *devp, int flag, int otyp, struct cred *cred)
{
	dev_t dev = *devp;
	struct pty *pty;
	queue_t *q;

	if (getminor(dev) >= npty) {
		return (ENXIO);
	}
	pty = &pty_softc[getminor(dev)];
	mutex_enter(&pty->ptc_lock);
	if (pty->pt_flags & PF_CARR_ON) {
		mutex_exit(&pty->ptc_lock);
		return (EIO);	/* controller is exclusive use */
				/* XXX - should be EBUSY! */
	}
	if (pty->pt_flags & PF_WOPEN) {
		pty->pt_flags &= ~PF_WOPEN;
		cv_broadcast(&pty->pt_cv_flags);
	}

	if ((q = pty->pt_ttycommon.t_readq) != NULL) {
		/*
		 * Send an un-hangup to the slave, since "carrier" is
		 * coming back up.  Make sure we're doing canonicalization.
		 */
		(void) putctl(q, M_UNHANGUP);
		(void) putctl1(q, M_CTL, MC_DOCANON);
	}
	pty->pt_flags |= PF_CARR_ON;
	pty->pt_send = 0;
	pty->pt_ucntl = 0;

	mutex_exit(&pty->ptc_lock);
	return (0);
}

/*ARGSUSED1*/
int
ptcclose(dev_t dev, int flag, int otyp, struct cred *cred)
{
	struct pty *pty;
	mblk_t *bp;
	queue_t *q;

	pty = &pty_softc[getminor(dev)];

	mutex_enter(&pty->ptc_lock);
	if ((q = pty->pt_ttycommon.t_readq) != NULL) {
		/*
		 * Send a hangup to the slave, since "carrier" is dropping.
		 */
		(void) putctl(q, M_HANGUP);
	}

	/*
	 * Clear out all the controller-side state.  This also
	 * clears PF_CARR_ON, which is correct because the
	 * "carrier" is dropping since the controller process
	 * is going away.
	 */
	pty->pt_flags &= (PF_WOPEN|PF_STOPPED|PF_NOSTOP);
	while ((bp = pty->pt_stuffqfirst) != NULL) {
		if ((pty->pt_stuffqfirst = bp->b_next) == NULL)
			pty->pt_stuffqlast = NULL;
		else
			pty->pt_stuffqfirst->b_prev = NULL;
		pty->pt_stuffqlen--;
		bp->b_next = bp->b_prev = NULL;
		freemsg(bp);
	}
	mutex_exit(&pty->ptc_lock);
	return (0);
}

int
ptcread(dev_t dev, struct uio *uio, struct cred *cred)
{
	struct pty *pty = &pty_softc[getminor(dev)];
	mblk_t *bp, *nbp;
	queue_t *q;
	unsigned char tmp;
	ssize_t cc;
	int error;
	off_t off;

#ifdef lint
	cred = cred;
#endif

	off = uio->uio_offset;

	mutex_enter(&pty->ptc_lock);

	for (;;) {
		while (pty->pt_flags & PF_READ) {
			pty->pt_flags |= PF_WREAD;
			cv_wait(&pty->pt_cv_flags, &pty->ptc_lock);
		}
		pty->pt_flags |= PF_READ;

		/*
		 * If there's a TIOCPKT packet waiting, pass it back.
		 */
		while (pty->pt_flags&(PF_PKT|PF_UCNTL) && pty->pt_send) {
			tmp = pty->pt_send;
			pty->pt_send = 0;
			mutex_exit(&pty->ptc_lock);
			error = ureadc((int)tmp, uio);
			uio->uio_offset = off;
			mutex_enter(&pty->ptc_lock);
			if (error) {
				pty->pt_send |= tmp;
				goto out;
			}
			if (pty->pt_send == 0)
				goto out;
		}

		/*
		 * If there's a user-control packet waiting, pass the
		 * "ioctl" code back.
		 */
		while ((pty->pt_flags & (PF_UCNTL|PF_43UCNTL)) &&
		    pty->pt_ucntl) {
			tmp = pty->pt_ucntl;
			pty->pt_ucntl = 0;
			mutex_exit(&pty->ptc_lock);
			error = ureadc((int)tmp, uio);
			uio->uio_offset = off;
			mutex_enter(&pty->ptc_lock);
			if (error) {
				if (pty->pt_ucntl == 0)
					pty->pt_ucntl = tmp;
				goto out;
			}
			if (pty->pt_ucntl == 0)
				goto out;
		}

		/*
		 * If there's any data waiting, pass it back.
		 */
		if ((q = pty->pt_ttycommon.t_writeq) != NULL &&
		    q->q_first != NULL &&
		    !(pty->pt_flags & PF_STOPPED)) {
			if (pty->pt_flags & (PF_PKT|PF_UCNTL|PF_43UCNTL)) {
				/*
				 * We're about to begin a move in packet or
				 * user-control mode; precede the data with a
				 * data header.
				 */
				mutex_exit(&pty->ptc_lock);
				error = ureadc(TIOCPKT_DATA, uio);
				uio->uio_offset = off;
				mutex_enter(&pty->ptc_lock);
				if (error != 0)
					goto out;
				if ((q = pty->pt_ttycommon.t_writeq) == NULL)
					goto out;
			}
			if ((bp = getq(q)) == NULL)
				goto out;
			while (uio->uio_resid > 0) {
				while ((cc = bp->b_wptr - bp->b_rptr) == 0) {
					nbp = bp->b_cont;
					freeb(bp);
					if ((bp = nbp) == NULL) {
						if ((q == NULL) ||
						    (bp = getq(q)) == NULL)
							goto out;
					}
				}
				cc = MIN(cc, uio->uio_resid);
				mutex_exit(&pty->ptc_lock);
				error = uiomove((caddr_t)bp->b_rptr,
				    cc, UIO_READ, uio);
				uio->uio_offset = off;
				mutex_enter(&pty->ptc_lock);
				if (error != 0) {
					freemsg(bp);
					goto out;
				}
				q = pty->pt_ttycommon.t_writeq;
				bp->b_rptr += cc;
			}
			/*
			 * Strip off zero-length blocks from the front of
			 * what we're putting back on the queue.
			 */
			while ((bp->b_wptr - bp->b_rptr) == 0) {
				nbp = bp->b_cont;
				freeb(bp);
				if ((bp = nbp) == NULL)
					goto out;	/* nothing left */
			}
			if (q != NULL)
				(void) putbq(q, bp);
			else
				freemsg(bp);
			goto out;
		}

		/*
		 * If there's any TIOCSTI-stuffed characters, pass
		 * them back.  (They currently arrive after all output;
		 * is this correct?)
		 */
		if (pty->pt_flags&PF_UCNTL && pty->pt_stuffqfirst != NULL) {
			mutex_exit(&pty->ptc_lock);
			error = ureadc(TIOCSTI&0xff, uio);
			mutex_enter(&pty->ptc_lock);
			while (error == 0 &&
			    (bp = pty->pt_stuffqfirst) != NULL &&
			    uio->uio_resid > 0) {
				pty->pt_stuffqlen--;
				if ((pty->pt_stuffqfirst = bp->b_next) == NULL)
					pty->pt_stuffqlast = NULL;
				else
					pty->pt_stuffqfirst->b_prev = NULL;
				mutex_exit(&pty->ptc_lock);
				error = ureadc((int)*bp->b_rptr, uio);
				bp->b_next = bp->b_prev = NULL;
				freemsg(bp);
				mutex_enter(&pty->ptc_lock);
			}
			uio->uio_offset = off;
			goto out;
		}

		/*
		 * There's no data available.
		 * We want to block until the slave is open, and there's
		 * something to read; but if we lost the slave or we're NBIO,
		 * then return the appropriate error instead.  POSIX-style
		 * non-block has top billing and gives -1 with errno = EAGAIN,
		 * BSD-style comes next and gives -1 with errno = EWOULDBLOCK,
		 * SVID-style comes last and gives 0.
		 */
		if (pty->pt_flags & PF_SLAVEGONE) {
			error = EIO;
			goto out;
		}
		if (uio->uio_fmode & FNONBLOCK) {
			error = EAGAIN;
			goto out;
		}
		if (pty->pt_flags & PF_NBIO) {
			error = EWOULDBLOCK;
			goto out;
		}
		if (uio->uio_fmode & FNDELAY)
			goto out;

		if (pty->pt_flags & PF_WREAD)
			cv_broadcast(&pty->pt_cv_flags);

		pty->pt_flags &= ~(PF_READ | PF_WREAD);


		if (!cv_wait_sig(&pty->pt_cv_writeq, &pty->ptc_lock)) {
			mutex_exit(&pty->ptc_lock);
			return (EINTR);
		}
	}

out:
	if (pty->pt_flags & PF_WREAD)
		cv_broadcast(&pty->pt_cv_flags);

	pty->pt_flags &= ~(PF_READ | PF_WREAD);

	mutex_exit(&pty->ptc_lock);
	return (error);
}

int
ptcwrite(dev_t dev, struct uio *uio, struct cred *cred)
{
	struct pty *pty = &pty_softc[getminor(dev)];
	queue_t *q;
	int written;
	mblk_t *mp;
	int fmode = 0;
	int error = 0;

	off_t off;
	off = uio->uio_offset;

#ifdef lint
	cred = cred;
#endif


	mutex_enter(&pty->ptc_lock);

again:
	while (pty->pt_flags & PF_WRITE) {
		pty->pt_flags |= PF_WWRITE;
		cv_wait(&pty->pt_cv_flags, &pty->ptc_lock);
	}

	pty->pt_flags |= PF_WRITE;

	if ((q = pty->pt_ttycommon.t_readq) == NULL) {

		/*
		 * Wait for slave to open.
		 */
		if (pty->pt_flags & PF_SLAVEGONE) {
			error = EIO;
			goto out;
		}
		if (uio->uio_fmode & FNONBLOCK) {
			error = EAGAIN;
			goto out;
		}
		if (pty->pt_flags & PF_NBIO) {
			error = EWOULDBLOCK;
			goto out;
		}
		if (uio->uio_fmode & FNDELAY)
			goto out;

		if (pty->pt_flags & PF_WWRITE)
			cv_broadcast(&pty->pt_cv_flags);

		pty->pt_flags &= ~(PF_WRITE | PF_WWRITE);

		if (!cv_wait_sig(&pty->pt_cv_writeq, &pty->ptc_lock)) {
			mutex_exit(&pty->ptc_lock);
			return (EINTR);
		}

		goto again;
	}

	/*
	 * If in remote mode, even zero-length writes generate messages.
	 */
	written = 0;
	if ((pty->pt_flags & PF_REMOTE) || uio->uio_resid > 0) {
		do {
			while (!canput(q)) {
				/*
				 * Wait for slave's read queue to unclog.
				 */
				if (pty->pt_flags & PF_SLAVEGONE) {
					error = EIO;
					goto out;
				}
				if (uio->uio_fmode & FNONBLOCK) {
					if (!written)
						error = EAGAIN;
					goto out;
				}
				if (pty->pt_flags & PF_NBIO) {
					if (!written)
						error = EWOULDBLOCK;
					goto out;
				}
				if (uio->uio_fmode & FNDELAY)
					goto out;

				if (pty->pt_flags & PF_WWRITE)
					cv_broadcast(&pty->pt_cv_flags);

				pty->pt_flags &= ~(PF_WRITE | PF_WWRITE);

				if (!cv_wait_sig(&pty->pt_cv_readq,
				    &pty->ptc_lock)) {
					mutex_exit(&pty->ptc_lock);
					return (EINTR);
				}

				while (pty->pt_flags & PF_WRITE) {
					pty->pt_flags |= PF_WWRITE;
					cv_wait(&pty->pt_cv_flags,
					    &pty->ptc_lock);
				}

				pty->pt_flags |= PF_WRITE;
			}

			if ((pty->pt_flags & PF_NBIO) &&
					uio->uio_fmode != FNONBLOCK) {
				fmode = uio->uio_fmode;
				uio->uio_fmode |= FNONBLOCK;
			}

			error = makemsg(uio->uio_resid, uio, pty, &mp);
			uio->uio_offset = off;
			if (fmode)
				uio->uio_fmode = fmode;
			if (error != 0) {
				if (error != EAGAIN && error != EWOULDBLOCK)
					goto out;
				if (uio->uio_fmode & FNONBLOCK) {
					if (!written)
						error = EAGAIN;
					goto out;
				}
				if (pty->pt_flags & PF_NBIO) {
					if (!written)
						error = EWOULDBLOCK;
					goto out;
				}
				if (uio->uio_fmode & FNDELAY)
					goto out;
				cmn_err(CE_PANIC,
				    "ptcwrite: non null return from"
					" makemsg");
			}

			/*
			 * Check again for safety; since "uiomove" can take a
			 * page fault, there's no guarantee that "pt_flags"
			 * didn't change while it was happening.
			 */
			if ((q = pty->pt_ttycommon.t_readq) == NULL) {
				if (mp)
					freemsg(mp);
				error = EIO;
				goto out;
			}
			if (mp)
				(void) putq(q, mp);
			written = 1;
			if (qready()) {
				mutex_exit(&pty->ptc_lock);
				runqueues();
				mutex_enter(&pty->ptc_lock);
				if ((q = pty->pt_ttycommon.t_readq) == NULL) {
					error = EIO;
					goto out;
				}
			}
		} while (uio->uio_resid > 0);
	}
out:
	if (pty->pt_flags & PF_WWRITE)
		cv_broadcast(&pty->pt_cv_flags);

	pty->pt_flags &= ~(PF_WRITE | PF_WWRITE);

	mutex_exit(&pty->ptc_lock);
	return (error);
}

#define	copy_in(data, d_arg) \
	if (copyin((caddr_t)data, &d_arg, sizeof (int)) != 0) \
		return (EFAULT)

#define	copy_out(d_arg, data) \
	if (copyout(&d_arg, (caddr_t)data, sizeof (int)) != 0) \
		return (EFAULT)

int
ptcioctl(dev_t dev, int cmd, intptr_t data, int flag, struct cred *cred,
    int *rvalp)
{
	struct pty *pty = &pty_softc[getminor(dev)];
	queue_t *q;
	struct ttysize tty_arg;
	struct winsize win_arg;
	int d_arg;
	int err;

	switch (cmd) {

	case TIOCPKT:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if (d_arg) {
			if (pty->pt_flags & (PF_UCNTL|PF_43UCNTL)) {
				mutex_exit(&pty->ptc_lock);
				return (EINVAL);
			}
			pty->pt_flags |= PF_PKT;
		} else
			pty->pt_flags &= ~PF_PKT;
		mutex_exit(&pty->ptc_lock);
		break;

	case TIOCUCNTL:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if (d_arg) {
			if (pty->pt_flags & (PF_PKT|PF_UCNTL)) {
				mutex_exit(&pty->ptc_lock);
				return (EINVAL);
			}
			pty->pt_flags |= PF_43UCNTL;
		} else
			pty->pt_flags &= ~PF_43UCNTL;
		mutex_exit(&pty->ptc_lock);
		break;

	case TIOCTCNTL:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if (d_arg) {
			if (pty->pt_flags & PF_PKT) {
				mutex_exit(&pty->ptc_lock);
				return (EINVAL);
			}
			pty->pt_flags |= PF_UCNTL;
		} else
			pty->pt_flags &= ~PF_UCNTL;
		mutex_exit(&pty->ptc_lock);
		break;

	case TIOCREMOTE:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if (d_arg) {
			if ((q = pty->pt_ttycommon.t_readq) != NULL)
				(void) putctl1(q, M_CTL, MC_NOCANON);
			pty->pt_flags |= PF_REMOTE;
		} else {
			if ((q = pty->pt_ttycommon.t_readq) != NULL)
				(void) putctl1(q, M_CTL, MC_DOCANON);
			pty->pt_flags &= ~PF_REMOTE;
		}
		mutex_exit(&pty->ptc_lock);
		break;

	case TIOCSIGNAL:
		/*
		 * Blast a M_PCSIG message up the slave stream; the
		 * signal number is the argument to the "ioctl".
		 */
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if ((q = pty->pt_ttycommon.t_readq) != NULL)
			(void) putctl1(q, M_PCSIG, (int)d_arg);
		mutex_exit(&pty->ptc_lock);
		break;

	/*
	 * XXX These should not be here.  The only reason why an
	 * "ioctl" on the controller side should get the
	 * slave side's process group is so that the process on
	 * the controller side can send a signal to the slave
	 * side's process group; however, this is better done
	 * with TIOCSIGNAL, both because it doesn't require us
	 * to know about the slave side's process group and because
	 * the controller side process may not have permission to
	 * send that signal to the entire process group.
	 *
	 * However, since vanilla 4BSD doesn't provide TIOCSIGNAL,
	 * we can't just get rid of them.
	 */
	case TIOCGPGRP:
		mutex_enter(&pty->ptc_lock);
		if (pty->pt_vnode == NULL ||
		    ((d_arg = strgetpgrp(pty->pt_vnode)) == -1)) {
			mutex_exit(&pty->ptc_lock);
			return (EIO);
		}
		mutex_exit(&pty->ptc_lock);
		copy_out(d_arg, data);
		break;

	case TIOCSPGRP:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if (pty->pt_vnode == NULL ||
		    (strsetpgrp(pty->pt_vnode, (pid_t)d_arg) == -1)) {
			mutex_exit(&pty->ptc_lock);
			return (EIO);
		}
		mutex_exit(&pty->ptc_lock);
		break;

	case FIONBIO:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if (d_arg)
			pty->pt_flags |= PF_NBIO;
		else
			pty->pt_flags &= ~PF_NBIO;
		mutex_exit(&pty->ptc_lock);
		break;

	case FIOASYNC:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		if (d_arg)
			pty->pt_flags |= PF_ASYNC;
		else
			pty->pt_flags &= ~PF_ASYNC;
		mutex_exit(&pty->ptc_lock);
		break;

	/*
	 * These, at least, can work on the controller-side process
	 * group.
	 */
	case FIOGETOWN:
		mutex_enter(&pty->ptc_lock);
		d_arg = -pty->pt_pgrp;
		mutex_exit(&pty->ptc_lock);
		copy_out(d_arg, data);
		break;

	case FIOSETOWN:
		copy_in(data, d_arg);
		mutex_enter(&pty->ptc_lock);
		pty->pt_pgrp = (short)(-d_arg);
		mutex_exit(&pty->ptc_lock);
		break;

	case FIONREAD: {
		/*
		 * Return the total number of bytes of data in all messages
		 * in slave write queue, which is master read queue, unless a
		 * special message would be read.
		 */
		mblk_t *mp;
		size_t count = 0;

		mutex_enter(&pty->ptc_lock);
		if (pty->pt_flags&(PF_PKT|PF_UCNTL) && pty->pt_send)
			count = 1;	/* will return 1 byte */
		else if ((pty->pt_flags & (PF_UCNTL|PF_43UCNTL)) &&
		    pty->pt_ucntl)
			count = 1;	/* will return 1 byte */
		else if ((q = pty->pt_ttycommon.t_writeq) != NULL &&
		    q->q_first != NULL && !(pty->pt_flags & PF_STOPPED)) {
			/*
			 * Will return whatever data is queued up.
			 */
			for (mp = q->q_first; mp != NULL; mp = mp->b_next)
				count += msgdsize(mp);
		} else if ((pty->pt_flags & PF_UCNTL) &&
		    pty->pt_stuffqfirst != NULL) {
			/*
			 * Will return STI'ed data.
			 */
			count = pty->pt_stuffqlen + 1;
		}

		/*
		 * Under LP64 we could have more than INT_MAX bytes to report,
		 * but the interface is defined in terms of int, so we cap it.
		 */
		d_arg = MIN(count, INT_MAX);
		mutex_exit(&pty->ptc_lock);
		copy_out(d_arg, data);
		break;
	}

	case TIOCSWINSZ:
		/*
		 * Unfortunately, TIOCSWINSZ and the old TIOCSSIZE "ioctl"s
		 * share the same code.  If the upper 16 bits of the number
		 * of lines is non-zero, it was probably a TIOCSWINSZ,
		 * with both "ws_row" and "ws_col" non-zero.
		 */
		if (copyin((caddr_t)data,
		    &tty_arg, sizeof (struct ttysize)) != 0)
			return (EFAULT);

		if ((tty_arg.ts_lines & 0xffff0000) != 0) {
			/*
			 * It's a TIOCSWINSZ.
			 */
			win_arg = *(struct winsize *)&tty_arg;

			mutex_enter(&pty->ptc_lock);
			/*
			 * If the window size changed, send a SIGWINCH.
			 */
			if (bcmp(&pty->pt_ttycommon.t_size,
			    &win_arg, sizeof (struct winsize))) {
				pty->pt_ttycommon.t_size = win_arg;
				if ((q = pty->pt_ttycommon.t_readq) != NULL)
					(void) putctl1(q, M_PCSIG, SIGWINCH);
			}
			mutex_exit(&pty->ptc_lock);
			break;
		}
		/* FALLTHROUGH */

	case TIOCSSIZE:
		if (copyin((caddr_t)data,
		    &tty_arg, sizeof (struct ttysize)) != 0)
			return (EFAULT);
		mutex_enter(&pty->ptc_lock);
		pty->pt_ttycommon.t_size.ws_row = (ushort_t)tty_arg.ts_lines;
		pty->pt_ttycommon.t_size.ws_col = (ushort_t)tty_arg.ts_cols;
		pty->pt_ttycommon.t_size.ws_xpixel = 0;
		pty->pt_ttycommon.t_size.ws_ypixel = 0;
		mutex_exit(&pty->ptc_lock);
		break;

	case TIOCGWINSZ:
		mutex_enter(&pty->ptc_lock);
		win_arg = pty->pt_ttycommon.t_size;
		mutex_exit(&pty->ptc_lock);
		if (copyout(&win_arg, (caddr_t)data,
		    sizeof (struct winsize)) != 0)
			return (EFAULT);
		break;

	case TIOCGSIZE:
		mutex_enter(&pty->ptc_lock);
		tty_arg.ts_lines = pty->pt_ttycommon.t_size.ws_row;
		tty_arg.ts_cols = pty->pt_ttycommon.t_size.ws_col;
		mutex_exit(&pty->ptc_lock);
		if (copyout(&tty_arg, (caddr_t)data,
		    sizeof (struct ttysize)) != 0)
			return (EFAULT);
		break;

	/*
	 * This is amazingly disgusting, but the stupid semantics of
	 * 4BSD pseudo-ttys makes us do it.  If we do one of these guys
	 * on the controller side, it really applies to the slave-side
	 * stream.  It should NEVER have been possible to do ANY sort
	 * of tty operations on the controller side, but it's too late
	 * to fix that now.  However, we won't waste our time implementing
	 * anything that the original pseudo-tty driver didn't handle.
	 */
	case TIOCGETP:
	case TIOCSETP:
	case TIOCSETN:
	case TIOCGETC:
	case TIOCSETC:
	case TIOCGLTC:
	case TIOCSLTC:
	case TIOCLGET:
	case TIOCLSET:
	case TIOCLBIS:
	case TIOCLBIC:
		mutex_enter(&pty->ptc_lock);
		if (pty->pt_vnode == NULL) {
			mutex_exit(&pty->ptc_lock);
			return (EIO);
		}
		pty->pt_flags |= PF_IOCTL;
		mutex_exit(&pty->ptc_lock);
		err = strioctl(pty->pt_vnode, cmd, data, flag,
		    U_TO_K, cred, rvalp);
		mutex_enter(&pty->ptc_lock);
		if (pty->pt_flags & PF_WAIT)
			cv_signal(&pty->pt_cv_flags);
		pty->pt_flags &= ~(PF_IOCTL|PF_WAIT);
		mutex_exit(&pty->ptc_lock);
		return (err);

	default:
		return (ENOTTY);
	}

	return (0);
}


int
ptcpoll(dev_t dev,
	short events,
	int anyyet,
	short *reventsp,
	struct pollhead **phpp)
{
	struct pty *pty = &pty_softc[getminor(dev)];
	pollhead_t *php = &ptcph;
	queue_t *q;
	int pos = 0;

#ifdef lint
	anyyet = anyyet;
#endif
	polllock(php, &pty->ptc_lock);

	ASSERT(MUTEX_HELD(&pty->ptc_lock));

	*reventsp = 0;
	if (pty->pt_flags & PF_SLAVEGONE) {
		if (events & (POLLIN|POLLRDNORM))
			*reventsp |= (events & (POLLIN|POLLRDNORM));
		if (events & (POLLOUT|POLLWRNORM))
			*reventsp |= (events & (POLLOUT|POLLWRNORM));
		mutex_exit(&pty->ptc_lock);
		/*
		 * A non NULL pollhead pointer should be returned in case
		 * user polls for 0 events.
		 */
		*phpp = !anyyet && !*reventsp ? php : (struct pollhead *)NULL;
		return (0);
	}
	if (events & (POLLIN|POLLRDNORM)) {
		if ((q = pty->pt_ttycommon.t_writeq) != NULL &&
		    q->q_first != NULL && !(pty->pt_flags & PF_STOPPED)) {
			/*
			 * Regular data is available.
			 */
			*reventsp |= (events & (POLLIN|POLLRDNORM));
			pos++;
		}
		if (pty->pt_flags & (PF_PKT|PF_UCNTL) && pty->pt_send) {
			/*
			 * A control packet is available.
			 */
			*reventsp |= (events & (POLLIN|POLLRDNORM));
			pos++;
		}
		if ((pty->pt_flags & PF_UCNTL) &&
		    (pty->pt_ucntl || pty->pt_stuffqfirst != NULL)) {
			/*
			 * "ioctl" or TIOCSTI data is available.
			 */
			*reventsp |= (events & (POLLIN|POLLRDNORM));
			pos++;
		}
		if ((pty->pt_flags & PF_43UCNTL) && pty->pt_ucntl) {
			*reventsp |= (events & (POLLIN|POLLRDNORM));
			pos++;
		}
	}
	if (events & (POLLOUT|POLLWRNORM)) {
		if ((q = pty->pt_ttycommon.t_readq) != NULL &&
		    canput(q)) {
			*reventsp |= (events & (POLLOUT|POLLWRNORM));
			pos++;
		}
	}
	if (events & POLLERR) {
		*reventsp |= POLLERR;
		pos++;
	}
	if (events == 0) {	/* "exceptional conditions" */
		if (((pty->pt_flags & (PF_PKT|PF_UCNTL)) && pty->pt_send) ||
		    ((pty->pt_flags & PF_UCNTL) &&
		    (pty->pt_ucntl || pty->pt_stuffqfirst != NULL))) {
			pos++;
		}
		if ((pty->pt_flags & PF_43UCNTL) && pty->pt_ucntl) {
			pos++;
		}
	}

	/*
	 * Arrange to have poll waken up when event occurs.
	 * if (!anyyet)
	 */
	if (!pos) {
		*phpp = php;
		*reventsp = 0;
	}

	mutex_exit(&pty->ptc_lock);
	return (0);
}

void
gsignal(int pid, int sig)
{
	procset_t set;
	sigsend_t v;

	bzero(&v, sizeof (v));
	v.sig = sig;
	v.perm = 0;
	v.checkperm = 1;
	v.value.sival_ptr = NULL;

	setprocset(&set, POP_AND, P_PGID, -pid, P_ALL, P_MYID);
	(void) sigsendset(&set, &v);
}

static int
makemsg(ssize_t count, struct uio *uiop, struct pty *pty, mblk_t **mpp)
{
	int pri = BPRI_LO;
	int error;
	bufcall_id_t id;
	mblk_t *bp = NULL;

	ASSERT(MUTEX_HELD(&pty->ptc_lock));

	*mpp = NULL;

	/*
	 * Create data part of message, if any.
	 */
	if (count >= 0) {
		while ((bp = allocb(count, pri)) == NULL) {
			if (uiop->uio_fmode & (FNDELAY|FNONBLOCK))
				return (EAGAIN);
			if ((id = bufcall(count, pri,
			    (void (*)(void *))cv_broadcast,
			    &ttoproc(curthread)->p_flag_cv)) == 0)
				return (ENOSR);
			if (!cv_wait_sig(&(ttoproc(curthread)->p_flag_cv),
			    &pty->ptc_lock)) {
				mutex_exit(&pty->ptc_lock);
				unbufcall(id);
				mutex_enter(&pty->ptc_lock);
				return (EINTR);
			}
			mutex_exit(&pty->ptc_lock);
			unbufcall(id);
			mutex_enter(&pty->ptc_lock);
		}
		mutex_exit(&pty->ptc_lock);
		error = uiomove((caddr_t)bp->b_wptr, count, UIO_WRITE, uiop);
		mutex_enter(&pty->ptc_lock);
		if (error) {
			freeb(bp);
			return (error);
		}

		bp->b_wptr += count;
	}

	*mpp = bp;
	return (0);
}
