root/usr/src/uts/common/io/tty_pty.c
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2015, Joyent, Inc.
 */

/*
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved. The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

/*
 * PTY - Stream "pseudo-terminal" device.  For each "manager" side it connects
 * to a "subsidiary" side.
 */


#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>
#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_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 */
        nulldev,                /* identify */
        nulldev,                /* probe */
        ptc_attach,             /* attach */
        nodev,                  /* detach */
        nodev,                  /* reset */
        &ptc_cb_ops,            /* driver operations */
        (struct bus_ops *)0,    /* bus operations */
        NULL,                   /* power */
        ddi_quiesce_not_supported,      /* devo_quiesce */
};

#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,
        "tty pseudo driver control 'ptc'",
        &ptc_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 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, DDI_PSEUDO, 0) == 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);
        }
}

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

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);   /* manager 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 subsidiary, 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);
}

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 subsidiary, since "carrier" is dropping.
                 */
                (void) putctl(q, M_HANGUP);
        }

        /*
         * Clear out all the manager-side state.  This also
         * clears PF_CARR_ON, which is correct because the
         * "carrier" is dropping since the manager 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;

        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 subsidiary is open, and there's
                 * something to read; but if we lost the subsidiary 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_SUBSIDGONE) {
                        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;

        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 subsidiary to open.
                 */
                if (pty->pt_flags & PF_SUBSIDGONE) {
                        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_readq, &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 subsidiary's read queue to unclog.
                                 */
                                if (pty->pt_flags & PF_SUBSIDGONE) {
                                        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;
                } 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 subsidiary 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;

        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 manager-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
                 * subsidiary write queue, which is manager 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;

        /*
         * XXX These should not be here.  The only reason why an
         * "ioctl" on the manager side should get the
         * subsidiary side's process group is so that the process on
         * the manager side can send a signal to the subsidiary
         * side's process group; however, this is better done
         * with TIOCSIGNAL, both because it doesn't require us
         * to know about the subsidiary side's process group and because
         * the manager 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:
        case TIOCSPGRP:
        /*
         * 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 manager side, it really applies to the subsidiary-side
         * stream.  It should NEVER have been possible to do ANY sort
         * of tty operations on the manager 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;

        if (polllock(php, &pty->ptc_lock) != 0) {
                *reventsp = POLLNVAL;
                return (0);
        }

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

        *reventsp = 0;
        if (pty->pt_flags & PF_SUBSIDGONE) {
                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;
        mblk_t *bp = NULL;

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

        *mpp = NULL;

        /*
         * Create data part of message, if any.
         */
        if (count >= 0) {
                if ((bp = allocb(count, pri)) == NULL)
                        return (ENOSR);

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