root/sys/kern/tty_tty.c
/*-
 * (MPSAFE)
 *
 * Copyright (c) 1982, 1986, 1991, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      @(#)tty_tty.c   8.2 (Berkeley) 9/23/93
 * $FreeBSD: src/sys/kern/tty_tty.c,v 1.30 1999/09/25 18:24:24 phk Exp $
 */

/*
 * Indirect driver for controlling tty.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/lock.h>
#include <sys/fcntl.h>
#include <sys/proc.h>
#include <sys/ttycom.h>
#include <sys/vnode.h>
#include <sys/kernel.h>
#include <sys/poll.h> /* XXX: poll args used in KQ filters */
#include <sys/event.h>

static  d_open_t        cttyopen;
static  d_close_t       cttyclose;
static  d_read_t        cttyread;
static  d_write_t       cttywrite;
static  d_ioctl_t       cttyioctl;
static  d_kqfilter_t    cttykqfilter;

static void cttyfilt_detach(struct knote *);
static int cttyfilt_read(struct knote *, long);
static int cttyfilt_write(struct knote *, long);

#define CDEV_MAJOR      1
static struct dev_ops ctty_ops = {
        { "ctty", 0, D_TTY | D_MPSAFE },
        .d_open =       cttyopen,
        .d_close =      cttyclose,
        .d_read =       cttyread,
        .d_write =      cttywrite,
        .d_ioctl =      cttyioctl,
        .d_kqfilter =   cttykqfilter
};

#define cttyvp(p) (((p)->p_flags & P_CONTROLT) ? \
                        (p)->p_session->s_ttyvp : NULL)

/*
 * This opens /dev/tty.  Because multiple opens of /dev/tty only
 * generate a single open to the actual tty, the file modes are
 * locked to FREAD|FWRITE.
 */
static  int
cttyopen(struct dev_open_args *ap)
{
        struct proc *p = curproc;
        struct vnode *ttyvp;
        int error;

        KKASSERT(p);
retry:
        if ((ttyvp = cttyvp(p)) == NULL)
                return (ENXIO);
        if (ttyvp->v_flag & VCTTYISOPEN)
                return (0);

        /*
         * Messy interlock, don't let the vnode go away while we try to
         * lock it and check for race after we might have blocked.
         *
         * WARNING! The device open (devfs_spec_open()) temporarily
         *          releases the vnode lock on ttyvp when issuing the
         *          dev_dopen(), which means that the VCTTYISOPEn flag
         *          can race during the VOP_OPEN().
         *
         *          If something does race we have to undo our potentially
         *          extra open.
         */
        vhold(ttyvp);
        vn_lock(ttyvp, LK_EXCLUSIVE | LK_RETRY);
        if (ttyvp != cttyvp(p) || (ttyvp->v_flag & VCTTYISOPEN)) {
                vn_unlock(ttyvp);
                vdrop(ttyvp);
                goto retry;
        }
        error = VOP_OPEN(ttyvp, FREAD|FWRITE, ap->a_cred, NULL);

        /*
         * Race against ctty close or change.  This case has been validated
         * and occurs every so often during synth builds.
         */
        if (ttyvp != cttyvp(p) || (ttyvp->v_flag & VCTTYISOPEN)) {
                if (error == 0)
                        VOP_CLOSE(ttyvp, FREAD|FWRITE, NULL);
                vn_unlock(ttyvp);
                vdrop(ttyvp);
                goto retry;
        }
        if (error == 0)
                vsetflags(ttyvp, VCTTYISOPEN);
        vn_unlock(ttyvp);
        vdrop(ttyvp);
        return(error);
}

/*
 * This closes /dev/tty.  Because multiple opens of /dev/tty only
 * generate a single open to the actual tty, the file modes are
 * locked to FREAD|FWRITE.
 */
static int
cttyclose(struct dev_close_args *ap)
{
        struct proc *p = curproc;
        struct vnode *ttyvp;
        int error;

        KKASSERT(p);
retry:
        /*
         * The tty may have been TIOCNOTTY'd, don't return an
         * error on close.  We just have nothing to do.
         */
        if ((ttyvp = cttyvp(p)) == NULL)
                return(0);
        if (ttyvp->v_flag & VCTTYISOPEN) {
                /*
                 * Avoid a nasty race if we block while getting the lock.
                 */
                vref(ttyvp);
                error = vn_lock(ttyvp, LK_EXCLUSIVE | LK_RETRY |
                                       LK_FAILRECLAIM);
                if (error) {
                        vrele(ttyvp);
                        goto retry;
                }
                if (ttyvp != cttyvp(p) || (ttyvp->v_flag & VCTTYISOPEN) == 0) {
                        kprintf("Warning: cttyclose: race avoided\n");
                        vn_unlock(ttyvp);
                        vrele(ttyvp);
                        goto retry;
                }
                vclrflags(ttyvp, VCTTYISOPEN);
                error = VOP_CLOSE(ttyvp, FREAD|FWRITE, NULL);
                vn_unlock(ttyvp);
                vrele(ttyvp);
        } else {
                error = 0;
        }
        return(error);
}

/*
 * Read from the controlling terminal (/dev/tty).  The tty is refed as
 * of the cttyvp(), but the ref can get ripped out from under us if
 * the controlling terminal is revoked while we are blocked on the lock,
 * so use vget() instead of vn_lock().
 */
static  int
cttyread(struct dev_read_args *ap)
{
        struct proc *p = curproc;
        struct vnode *ttyvp;
        int error;

        KKASSERT(p);
        ttyvp = cttyvp(p);
        if (ttyvp == NULL)
                return (EIO);
        if ((error = vget(ttyvp, LK_EXCLUSIVE | LK_RETRY)) == 0) {
                error = VOP_READ(ttyvp, ap->a_uio, ap->a_ioflag, NOCRED);
                vput(ttyvp);
        }
        return (error);
}

/*
 * Read from the controlling terminal (/dev/tty).  The tty is refed as
 * of the cttyvp(), but the ref can get ripped out from under us if
 * the controlling terminal is revoked while we are blocked on the lock,
 * so use vget() instead of vn_lock().
 */
static  int
cttywrite(struct dev_write_args *ap)
{
        struct proc *p = curproc;
        struct vnode *ttyvp;
        int error;

        KKASSERT(p);
        ttyvp = cttyvp(p);
        if (ttyvp == NULL)
                return (EIO);
        if ((error = vget(ttyvp, LK_EXCLUSIVE | LK_RETRY)) == 0) {
                error = VOP_WRITE(ttyvp, ap->a_uio, ap->a_ioflag, NOCRED);
                vput(ttyvp);
        }
        return (error);
}

/*ARGSUSED*/
static  int
cttyioctl(struct dev_ioctl_args *ap)
{
        struct vnode *ttyvp;
        struct proc *p = curproc;

        KKASSERT(p);
        lwkt_gettoken(&p->p_token);
        ttyvp = cttyvp(p);
        if (ttyvp == NULL) {
                lwkt_reltoken(&p->p_token);
                return (EIO);
        }
        /*
         * Don't allow controlling tty to be set to the controlling tty
         * (infinite recursion).
         */
        if (ap->a_cmd == TIOCSCTTY) {
                lwkt_reltoken(&p->p_token);
                return EINVAL;
        }
        if (ap->a_cmd == TIOCNOTTY) {
                if (!SESS_LEADER(p)) {
                        p->p_flags &= ~P_CONTROLT;
                        lwkt_reltoken(&p->p_token);
                        return (0);
                } else {
                        lwkt_reltoken(&p->p_token);
                        return (EINVAL);
                }
        }
        lwkt_reltoken(&p->p_token);

        return (VOP_IOCTL(ttyvp, ap->a_cmd, ap->a_data, ap->a_fflag,
                          ap->a_cred, ap->a_sysmsg));
}

static struct filterops cttyfiltops_read =
        { FILTEROP_ISFD | FILTEROP_MPSAFE, NULL,
          cttyfilt_detach, cttyfilt_read };
static struct filterops cttyfiltops_write =
        { FILTEROP_ISFD | FILTEROP_MPSAFE, NULL,
          cttyfilt_detach, cttyfilt_write };

static int
cttykqfilter(struct dev_kqfilter_args *ap)
{
        cdev_t dev = ap->a_head.a_dev;
        struct proc *p = curproc;
        struct knote *kn = ap->a_kn;
        struct vnode *ttyvp;

        KKASSERT(p);
        ttyvp = cttyvp(p);

        if (ttyvp != NULL)
                return (VOP_KQFILTER(ttyvp, kn));

        ap->a_result = 0;

        switch (kn->kn_filter) {
        case EVFILT_READ:
                kn->kn_fop = &cttyfiltops_read;
                kn->kn_hook = (caddr_t)dev;
                break;
        case EVFILT_WRITE:
                kn->kn_fop = &cttyfiltops_write;
                kn->kn_hook = (caddr_t)dev;
                break;
        default:
                ap->a_result = EOPNOTSUPP;
                return (0);
        }

        return (0);
}

static void
cttyfilt_detach(struct knote *kn) {}

static int
cttyfilt_read(struct knote *kn, long hint)
{
        cdev_t dev = (cdev_t)kn->kn_hook;

        if (seltrue(dev, POLLIN | POLLRDNORM))
                return (1);

        return (0);
}

static int
cttyfilt_write(struct knote *kn, long hint)
{
        cdev_t dev = (cdev_t)kn->kn_hook;

        if (seltrue(dev, POLLOUT | POLLWRNORM))
                return (1);

        return (0);
}

static void
ctty_drvinit(void *unused __unused)
{
        make_dev(&ctty_ops, 0, 0, 0, 0666, "tty");
}

SYSINIT(cttydev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE + CDEV_MAJOR,
        ctty_drvinit, NULL);