/*      $NetBSD: tty.c,v 1.313 2024/07/14 05:10:40 kre Exp $    */

/*-
* Copyright (c) 2008, 2020 The NetBSD Foundation, Inc.
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/

/*-
* Copyright (c) 1982, 1986, 1990, 1991, 1993
*      The Regents of the University of California.  All rights reserved.
* (c) UNIX System Laboratories, Inc.
* All or some portions of this file are derived from material licensed
* to the University of California by American Telephone and Telegraph
* Co. or Unix System Laboratories, Inc. and are reproduced herein with
* the permission of UNIX System Laboratories, Inc.
*
* 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.c       8.13 (Berkeley) 1/9/95
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tty.c,v 1.313 2024/07/14 05:10:40 kre Exp $");

#ifdef _KERNEL_OPT
#include "opt_compat_netbsd.h"
#endif

#define TTY_ALLOW_PRIVATE

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#define TTYDEFCHARS
#include <sys/tty.h>
#undef  TTYDEFCHARS
#include <sys/file.h>
#include <sys/conf.h>
#include <sys/cpu.h>
#include <sys/dkstat.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/syslog.h>
#include <sys/kmem.h>
#include <sys/signalvar.h>
#include <sys/resourcevar.h>
#include <sys/poll.h>
#include <sys/kprintf.h>
#include <sys/namei.h>
#include <sys/sysctl.h>
#include <sys/kauth.h>
#include <sys/intr.h>
#include <sys/ioctl_compat.h>
#include <sys/module.h>
#include <sys/bitops.h>
#include <sys/compat_stub.h>
#include <sys/atomic.h>
#include <sys/condvar.h>
#include <sys/pserialize.h>

static int      ttnread(struct tty *);
static void     ttyblock(struct tty *);
static void     ttyecho(int, struct tty *);
static void     ttyrubo(struct tty *, int);
static void     ttyprintf_nolock(struct tty *, const char *fmt, ...)
   __printflike(2, 3);
static int      proc_compare_wrapper(struct proc *, struct proc *);
static void     ttysigintr(void *);

/* Symbolic sleep message strings. */
const char      ttclos[] = "ttycls";
const char      ttopen[] = "ttyopn";
const char      ttybg[] = "ttybg";
const char      ttyin[] = "ttyin";
const char      ttyout[] = "ttyout";

/*
* Used to determine whether we still have a connection.  This is true in
* one of 3 cases:
* 1) We have carrier.
* 2) It's a locally attached terminal, and we are therefore ignoring carrier.
* 3) We're using a flow control mechanism that overloads the carrier signal.
*/
#define CONNECTED(tp)   (ISSET(tp->t_state, TS_CARR_ON) ||      \
                        ISSET(tp->t_cflag, CLOCAL | MDMBUF))

/*
* Table with character classes and parity. The 8th bit indicates parity,
* the 7th bit indicates the character is an alphameric or underscore (for
* ALTWERASE), and the low 6 bits indicate delay type.  If the low 6 bits
* are 0 then the character needs no special processing on output; classes
* other than 0 might be translated or (not currently) require delays.
*/
#define E       0x00    /* Even parity. */
#define O       0x80    /* Odd parity. */
#define PARITY(c)       (char_type[c] & O)

#define ALPHA   0x40    /* Alpha or underscore. */
#define ISALPHA(c)      (char_type[(c) & TTY_CHARMASK] & ALPHA)

#define CCLASSMASK      0x3f
#define CCLASS(c)       (char_type[c] & CCLASSMASK)

#define BS      BACKSPACE
#define CC      CONTROL
#define CR      RETURN
#define NA      ORDINARY | ALPHA
#define NL      NEWLINE
#define NO      ORDINARY
#define TB      TAB
#define VT      VTAB

unsigned char const char_type[] = {
       E|CC, O|CC, O|CC, E|CC, O|CC, E|CC, E|CC, O|CC, /* nul - bel */
       O|BS, E|TB, E|NL, O|CC, E|VT, O|CR, O|CC, E|CC, /* bs - si */
       O|CC, E|CC, E|CC, O|CC, E|CC, O|CC, O|CC, E|CC, /* dle - etb */
       E|CC, O|CC, O|CC, E|CC, O|CC, E|CC, E|CC, O|CC, /* can - us */
       O|NO, E|NO, E|NO, O|NO, E|NO, O|NO, O|NO, E|NO, /* sp - ' */
       E|NO, O|NO, O|NO, E|NO, O|NO, E|NO, E|NO, O|NO, /* ( - / */
       E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* 0 - 7 */
       O|NA, E|NA, E|NO, O|NO, E|NO, O|NO, O|NO, E|NO, /* 8 - ? */
       O|NO, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* @ - G */
       E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* H - O */
       E|NA, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* P - W */
       O|NA, E|NA, E|NA, O|NO, E|NO, O|NO, O|NO, O|NA, /* X - _ */
       E|NO, O|NA, O|NA, E|NA, O|NA, E|NA, E|NA, O|NA, /* ` - g */
       O|NA, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* h - o */
       O|NA, E|NA, E|NA, O|NA, E|NA, O|NA, O|NA, E|NA, /* p - w */
       E|NA, O|NA, O|NA, E|NO, O|NO, E|NO, E|NO, O|CC, /* x - del */
       /*
        * Meta chars; should be settable per character set;
        * for now, treat them all as normal characters.
        */
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
       NA,   NA,   NA,   NA,   NA,   NA,   NA,   NA,
};
#undef  BS
#undef  CC
#undef  CR
#undef  NA
#undef  NL
#undef  NO
#undef  TB
#undef  VT

static struct ttylist_head tty_sigqueue = TAILQ_HEAD_INITIALIZER(tty_sigqueue);
static void *tty_sigsih;

struct ttylist_head ttylist = TAILQ_HEAD_INITIALIZER(ttylist);
int tty_count;
kmutex_t tty_lock;
kmutex_t constty_lock;
static struct pserialize *constty_psz;
static kcondvar_t ttyref_cv;

struct ptm_pty *ptm = NULL;

uint64_t tk_cancc;
uint64_t tk_nin;
uint64_t tk_nout;
uint64_t tk_rawcc;

static kauth_listener_t tty_listener;

#define TTY_MINQSIZE    0x00400
#define TTY_MAXQSIZE    0x10000
int tty_qsize = TTY_MINQSIZE;

static int
tty_get_qsize(int *qsize, int newsize)
{
       if (newsize <= 0)
               return EINVAL;

       newsize = 1 << ilog2(newsize);  /* Make it a power of two */

       if (newsize < TTY_MINQSIZE || newsize > TTY_MAXQSIZE)
               return EINVAL;

       *qsize = newsize;
       return 0;
}

static int
tty_set_qsize(struct tty *tp, int newsize)
{
       struct clist rawq, canq, outq;
       struct clist orawq, ocanq, ooutq;

       clalloc(&rawq, newsize, 1);
       clalloc(&canq, newsize, 1);
       clalloc(&outq, newsize, 0);

       mutex_spin_enter(&tty_lock);

       if (tp->t_outq.c_cc != 0) {
               mutex_spin_exit(&tty_lock);
               clfree(&rawq);
               clfree(&canq);
               clfree(&outq);
               return EBUSY;
       }

       orawq = tp->t_rawq;
       ocanq = tp->t_canq;
       ooutq = tp->t_outq;

       tp->t_qsize = newsize;
       tp->t_rawq = rawq;
       tp->t_canq = canq;
       tp->t_outq = outq;

       ttsetwater(tp);

       mutex_spin_exit(&tty_lock);

       clfree(&orawq);
       clfree(&ocanq);
       clfree(&ooutq);

       return 0;
}

static int
sysctl_kern_tty_qsize(SYSCTLFN_ARGS)
{
       int newsize;
       int error;
       struct sysctlnode node;
       node = *rnode;
       node.sysctl_data = &newsize;

       newsize = tty_qsize;
       error = sysctl_lookup(SYSCTLFN_CALL(&node));
       if (error || newp == NULL)
               return error;


       return tty_get_qsize(&tty_qsize, newsize);
}

static void
sysctl_kern_tty_setup(void)
{
       const struct sysctlnode *rnode, *cnode;

       sysctl_createv(NULL, 0, NULL, NULL,
                      CTLFLAG_PERMANENT,
                      CTLTYPE_NODE, "tkstat",
                      SYSCTL_DESCR("Number of characters sent and received "
                                   "on ttys"),
                      NULL, 0, NULL, 0,
                      CTL_KERN, KERN_TKSTAT, CTL_EOL);

       sysctl_createv(NULL, 0, NULL, NULL,
                      CTLFLAG_PERMANENT,
                      CTLTYPE_QUAD, "nin",
                      SYSCTL_DESCR("Total number of tty input characters"),
                      NULL, 0, &tk_nin, 0,
                      CTL_KERN, KERN_TKSTAT, KERN_TKSTAT_NIN, CTL_EOL);
       sysctl_createv(NULL, 0, NULL, NULL,
                      CTLFLAG_PERMANENT,
                      CTLTYPE_QUAD, "nout",
                      SYSCTL_DESCR("Total number of tty output characters"),
                      NULL, 0, &tk_nout, 0,
                      CTL_KERN, KERN_TKSTAT, KERN_TKSTAT_NOUT, CTL_EOL);
       sysctl_createv(NULL, 0, NULL, NULL,
                      CTLFLAG_PERMANENT,
                      CTLTYPE_QUAD, "cancc",
                      SYSCTL_DESCR("Number of canonical tty input characters"),
                      NULL, 0, &tk_cancc, 0,
                      CTL_KERN, KERN_TKSTAT, KERN_TKSTAT_CANCC, CTL_EOL);
       sysctl_createv(NULL, 0, NULL, NULL,
                      CTLFLAG_PERMANENT,
                      CTLTYPE_QUAD, "rawcc",
                      SYSCTL_DESCR("Number of raw tty input characters"),
                      NULL, 0, &tk_rawcc, 0,
                      CTL_KERN, KERN_TKSTAT, KERN_TKSTAT_RAWCC, CTL_EOL);

       sysctl_createv(NULL, 0, NULL, &rnode,
                      CTLFLAG_PERMANENT,
                      CTLTYPE_NODE, "tty", NULL,
                      NULL, 0, NULL, 0,
                      CTL_KERN, CTL_CREATE, CTL_EOL);
       sysctl_createv(NULL, 0, &rnode, &cnode,
                      CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
                      CTLTYPE_INT, "qsize",
                      SYSCTL_DESCR("TTY input and output queue size"),
                      sysctl_kern_tty_qsize, 0, &tty_qsize, 0,
                      CTL_CREATE, CTL_EOL);
}

/*
* ttylock(tp), ttyunlock(tp), ttylocked(tp)
*
*      Exclusive lock on tty.  Currently a single global lock.
*
*      ttylocked is for positive DIAGNOSTIC assertions only.
*/
void
ttylock(struct tty *tp)
{

       mutex_spin_enter(&tty_lock);
}

void
ttyunlock(struct tty *tp)
{

       mutex_spin_exit(&tty_lock);
}

bool
ttylocked(struct tty *tp)
{

       return mutex_owned(&tty_lock);
}

int
ttyopen(struct tty *tp, int dialout, int nonblock)
{
       int     error;

       error = 0;

       mutex_spin_enter(&tty_lock);

       if (dialout) {
               /*
                * If the device is already open for non-dialout, fail.
                * Otherwise, set TS_DIALOUT to block any pending non-dialout
                * opens.
                */
               if (ISSET(tp->t_state, TS_ISOPEN) &&
                   !ISSET(tp->t_state, TS_DIALOUT)) {
                       error = EBUSY;
                       goto out;
               }
               SET(tp->t_state, TS_DIALOUT);
       } else {
               if (!nonblock) {
                       /*
                        * Wait for carrier.  Also wait for any dialout
                        * processes to close the tty first.
                        */
                       while (ISSET(tp->t_state, TS_DIALOUT) ||
                              !CONNECTED(tp)) {
                               tp->t_wopen++;
                               error = ttysleep(tp, &tp->t_rawcv, true, 0);
                               tp->t_wopen--;
                               if (error)
                                       goto out;
                       }
               } else {
                       /*
                        * Don't allow a non-blocking non-dialout open if the
                        * device is already open for dialout.
                        */
                       if (ISSET(tp->t_state, TS_DIALOUT)) {
                               error = EBUSY;
                               goto out;
                       }
               }
       }

out:
       mutex_spin_exit(&tty_lock);
       return (error);
}

/*
* Initial open of tty, or (re)entry to standard tty line discipline.
*/
int
ttylopen(dev_t device, struct tty *tp)
{

       mutex_spin_enter(&tty_lock);
       tp->t_dev = device;
       if (!ISSET(tp->t_state, TS_ISOPEN)) {
               SET(tp->t_state, TS_ISOPEN);
               memset(&tp->t_winsize, 0, sizeof(tp->t_winsize));
               tp->t_flags = 0;
       }
       mutex_spin_exit(&tty_lock);
       if (tp->t_qsize != tty_qsize)
               tty_set_qsize(tp, tty_qsize);
       return (0);
}

/*
* Interrupt any pending I/O and make it fail.  Used before close to
* interrupt pending open/read/write/&c. and make it fail promptly.
*/
void
ttycancel(struct tty *tp)
{

       mutex_spin_enter(&tty_lock);
       tp->t_state |= TS_CANCEL;
       cv_broadcast(&tp->t_outcv);
       cv_broadcast(&tp->t_rawcv);
       mutex_spin_exit(&tty_lock);
}

/*
* Handle close() on a tty line: flush and set to initial state,
* bumping generation number so that pending read/write calls
* can detect recycling of the tty.
*/
int
ttyclose(struct tty *tp)
{
       struct session *sess;

       /*
        * Make sure this is not the constty.  Without constty_lock it
        * is always allowed to transition from nonnull to null.
        */
       (void)atomic_cas_ptr(&constty, tp, NULL);

       /*
        * We don't know if this has _ever_ been the constty: another
        * thread may have kicked it out as constty before we started
        * to close.
        *
        * So we wait for all users that might be acquiring references
        * to finish doing so -- after that, no more references can be
        * made, at which point we can safely flush the tty, wait for
        * the existing references to drain, and finally free or reuse
        * the tty.
        */
       pserialize_perform(constty_psz);

       mutex_spin_enter(&tty_lock);

       ttyflush(tp, FREAD | FWRITE);

       tp->t_gen++;
       tp->t_pgrp = NULL;
       tp->t_state = 0;
       sess = tp->t_session;
       tp->t_session = NULL;

       while (tp->t_refcnt)
               cv_wait(&ttyref_cv, &tty_lock);

       mutex_spin_exit(&tty_lock);

       if (sess != NULL) {
               mutex_enter(&proc_lock);
               /* Releases proc_lock. */
               proc_sessrele(sess);
       }
       return (0);
}

#define FLUSHQ(q) {                                                     \
       if ((q)->c_cc)                                                  \
               ndflush(q, (q)->c_cc);                                  \
}

/*
* tty_acquire(tp), tty_release(tp)
*
*      Acquire a reference to tp that prevents it from being closed
*      until released.  Caller must guarantee tp has not yet been
*      closed, e.g. by obtaining tp from constty during a pserialize
*      read section.  Caller must not hold tty_lock.
*/
void
tty_acquire(struct tty *tp)
{
       unsigned refcnt __diagused;

       refcnt = atomic_inc_uint_nv(&tp->t_refcnt);
       KASSERT(refcnt < UINT_MAX);
}

void
tty_release(struct tty *tp)
{
       unsigned old, new;

       KDASSERT(mutex_ownable(&tty_lock));

       do {
               old = atomic_load_relaxed(&tp->t_refcnt);
               if (old == 1) {
                       mutex_spin_enter(&tty_lock);
                       if (atomic_dec_uint_nv(&tp->t_refcnt) == 0)
                               cv_broadcast(&ttyref_cv);
                       mutex_spin_exit(&tty_lock);
                       return;
               }
               KASSERT(old != 0);
               new = old - 1;
       } while (atomic_cas_uint(&tp->t_refcnt, old, new) != old);
}

/*
* This macro is used in canonical mode input processing, where a read
* request shall not return unless a 'line delimiter' ('\n') or 'break'
* (EOF, EOL, EOL2) character (or a signal) has been received. As EOL2
* is an extension to the POSIX.1 defined set of special characters,
* recognize it only if IEXTEN is set in the set of local flags.
*/
#define TTBREAKC(c, lflg)                                               \
       ((c) == '\n' || (((c) == cc[VEOF] || (c) == cc[VEOL] ||         \
       ((c) == cc[VEOL2] && ISSET(lflg, IEXTEN))) && (c) != _POSIX_VDISABLE))



/*
* ttyinput() helper.
* Call with the tty lock held.
*/
/* XXX static */ int
ttyinput_wlock(int c, struct tty *tp)
{
       int     iflag, lflag, i, error;
       u_char  *cc;

       KASSERT(mutex_owned(&tty_lock));

       /*
        * If input is pending take it first.
        */
       lflag = tp->t_lflag;
       if (ISSET(lflag, PENDIN))
               ttypend(tp);
       /*
        * Gather stats.
        */
       if (ISSET(lflag, ICANON)) {
               ++tk_cancc;
               ++tp->t_cancc;
       } else {
               ++tk_rawcc;
               ++tp->t_rawcc;
       }
       ++tk_nin;

       cc = tp->t_cc;

       /*
        * Handle exceptional conditions (break, parity, framing).
        */
       iflag = tp->t_iflag;
       if ((error = (ISSET(c, TTY_ERRORMASK))) != 0) {
               CLR(c, TTY_ERRORMASK);
               if (ISSET(error, TTY_FE) && c == 0) {           /* Break. */
                       if (ISSET(iflag, IGNBRK))
                               return (0);
                       else if (ISSET(iflag, BRKINT)) {
                               ttyflush(tp, FREAD | FWRITE);
                               ttysig(tp, TTYSIG_PG1, SIGINT);
                               return (0);
                       } else if (ISSET(iflag, PARMRK))
                               goto parmrk;
               } else if ((ISSET(error, TTY_PE) && ISSET(iflag, INPCK)) ||
                   ISSET(error, TTY_FE)) {
                       if (ISSET(iflag, IGNPAR))
                               return (0);
                       else if (ISSET(iflag, PARMRK)) {
parmrk:                        (void)putc(0377 | TTY_QUOTE, &tp->t_rawq);
                               (void)putc(0    | TTY_QUOTE, &tp->t_rawq);
                               (void)putc(c    | TTY_QUOTE, &tp->t_rawq);
                               return (0);
                       } else
                               c = 0;
               }
       } else if (c == 0377 &&
           ISSET(iflag, ISTRIP|IGNPAR|INPCK|PARMRK) == (INPCK|PARMRK)) {
               /* "Escape" a valid character of '\377'. */
               (void)putc(0377 | TTY_QUOTE, &tp->t_rawq);
               (void)putc(0377 | TTY_QUOTE, &tp->t_rawq);
               goto endcase;
       }

       /*
        * In tandem mode, check high water mark.
        */
       if (ISSET(iflag, IXOFF) || ISSET(tp->t_cflag, CHWFLOW))
               ttyblock(tp);
       if (!ISSET(tp->t_state, TS_TYPEN) && ISSET(iflag, ISTRIP))
               CLR(c, 0x80);
       if (!ISSET(lflag, EXTPROC)) {
               /*
                * Check for literal nexting very first
                */
               if (ISSET(tp->t_state, TS_LNCH)) {
                       SET(c, TTY_QUOTE);
                       CLR(tp->t_state, TS_LNCH);
               }
               /*
                * Scan for special characters.  This code
                * is really just a big case statement with
                * non-constant cases.  The bottom of the
                * case statement is labeled ``endcase'', so goto
                * it after a case match, or similar.
                */

               /*
                * Control chars which aren't controlled
                * by ICANON, ISIG, or IXON.
                */
               if (ISSET(lflag, IEXTEN)) {
                       if (CCEQ(cc[VLNEXT], c)) {
                               if (ISSET(lflag, ECHO)) {
                                       if (ISSET(lflag, ECHOE)) {
                                               (void)ttyoutput('^', tp);
                                               (void)ttyoutput('\b', tp);
                                       } else
                                               ttyecho(c, tp);
                               }
                               SET(tp->t_state, TS_LNCH);
                               goto endcase;
                       }
                       if (CCEQ(cc[VDISCARD], c)) {
                               if (ISSET(lflag, FLUSHO))
                                       CLR(tp->t_lflag, FLUSHO);
                               else {
                                       ttyflush(tp, FWRITE);
                                       ttyecho(c, tp);
                                       if (tp->t_rawq.c_cc + tp->t_canq.c_cc)
                                               ttyretype(tp);
                                       SET(tp->t_lflag, FLUSHO);
                               }
                               goto startoutput;
                       }
               }
               /*
                * Signals.
                */
               if (ISSET(lflag, ISIG)) {
                       if (CCEQ(cc[VINTR], c) || CCEQ(cc[VQUIT], c)) {
                               if (!ISSET(lflag, NOFLSH))
                                       ttyflush(tp, FREAD | FWRITE);
                               ttyecho(c, tp);
                               ttysig(tp, TTYSIG_PG1, CCEQ(cc[VINTR], c) ?
                                   SIGINT : SIGQUIT);
                               goto endcase;
                       }
                       if (CCEQ(cc[VSUSP], c)) {
                               if (!ISSET(lflag, NOFLSH))
                                       ttyflush(tp, FREAD);
                               ttyecho(c, tp);
                               ttysig(tp, TTYSIG_PG1, SIGTSTP);
                               goto endcase;
                       }
               }
               /*
                * Handle start/stop characters.
                */
               if (ISSET(iflag, IXON)) {
                       if (CCEQ(cc[VSTOP], c)) {
                               if (!ISSET(tp->t_state, TS_TTSTOP)) {
                                       SET(tp->t_state, TS_TTSTOP);
                                       cdev_stop(tp, 0);
                                       return (0);
                               }
                               if (!CCEQ(cc[VSTART], c))
                                       return (0);
                               /*
                                * if VSTART == VSTOP then toggle
                                */
                               goto endcase;
                       }
                       if (CCEQ(cc[VSTART], c))
                               goto restartoutput;
               }
               /*
                * IGNCR, ICRNL, & INLCR
                */
               if (c == '\r') {
                       if (ISSET(iflag, IGNCR))
                               goto endcase;
                       else if (ISSET(iflag, ICRNL))
                               c = '\n';
               } else if (c == '\n' && ISSET(iflag, INLCR))
                       c = '\r';
       }
       if (!ISSET(lflag, EXTPROC) && ISSET(lflag, ICANON)) {
               /*
                * From here on down canonical mode character
                * processing takes place.
                */
               /*
                * erase (^H / ^?)
                */
               if (CCEQ(cc[VERASE], c)) {
                       if (tp->t_rawq.c_cc)
                               ttyrub(unputc(&tp->t_rawq), tp);
                       goto endcase;
               }
               /*
                * kill (^U)
                */
               if (CCEQ(cc[VKILL], c)) {
                       if (ISSET(lflag, ECHOKE) &&
                           tp->t_rawq.c_cc == tp->t_rocount &&
                           !ISSET(lflag, ECHOPRT))
                               while (tp->t_rawq.c_cc)
                                       ttyrub(unputc(&tp->t_rawq), tp);
                       else {
                               ttyecho(c, tp);
                               if (ISSET(lflag, ECHOK) ||
                                   ISSET(lflag, ECHOKE))
                                       ttyecho('\n', tp);
                               FLUSHQ(&tp->t_rawq);
                               tp->t_rocount = 0;
                       }
                       CLR(tp->t_state, TS_LOCAL);
                       goto endcase;
               }
               /*
                * Extensions to the POSIX.1 GTI set of functions.
                */
               if (ISSET(lflag, IEXTEN)) {
                       /*
                        * word erase (^W)
                        */
                       if (CCEQ(cc[VWERASE], c)) {
                               int alt = ISSET(lflag, ALTWERASE);
                               int ctype;

                               /*
                                * erase whitespace
                                */
                               while ((c = unputc(&tp->t_rawq)) == ' ' ||
                                   c == '\t')
                                       ttyrub(c, tp);
                               if (c == -1)
                                       goto endcase;
                               /*
                                * erase last char of word and remember the
                                * next chars type (for ALTWERASE)
                                */
                               ttyrub(c, tp);
                               c = unputc(&tp->t_rawq);
                               if (c == -1)
                                       goto endcase;
                               if (c == ' ' || c == '\t') {
                                       (void)putc(c, &tp->t_rawq);
                                       goto endcase;
                               }
                               ctype = ISALPHA(c);
                               /*
                                * erase rest of word
                                */
                               do {
                                       ttyrub(c, tp);
                                       c = unputc(&tp->t_rawq);
                                       if (c == -1)
                                               goto endcase;
                               } while (c != ' ' && c != '\t' &&
                                   (alt == 0 || ISALPHA(c) == ctype));
                               (void)putc(c, &tp->t_rawq);
                               goto endcase;
                       }
                       /*
                        * reprint line (^R)
                        */
                       if (CCEQ(cc[VREPRINT], c)) {
                               ttyretype(tp);
                               goto endcase;
                       }
                       /*
                        * ^T - kernel info and generate SIGINFO
                        */
                       if (CCEQ(cc[VSTATUS], c)) {
                               ttysig(tp, TTYSIG_PG1, SIGINFO);
                               goto endcase;
                       }
               }
       }
       /*
        * Check for input buffer overflow
        */
       if (tp->t_rawq.c_cc + tp->t_canq.c_cc >= TTYHOG) {
               if (ISSET(iflag, IMAXBEL)) {
                       if (tp->t_outq.c_cc < tp->t_hiwat)
                               (void)ttyoutput(CTRL('g'), tp);
               } else
                       ttyflush(tp, FREAD | FWRITE);
               goto endcase;
       }
       /*
        * Put data char in q for user and
        * wakeup on seeing a line delimiter.
        */
       if (putc(c, &tp->t_rawq) >= 0) {
               if (!ISSET(lflag, ICANON)) {
                       ttwakeup(tp);
                       ttyecho(c, tp);
                       goto endcase;
               }
               if (TTBREAKC(c, lflag)) {
                       tp->t_rocount = 0;
                       catq(&tp->t_rawq, &tp->t_canq);
                       ttwakeup(tp);
               } else if (tp->t_rocount++ == 0)
                       tp->t_rocol = tp->t_column;
               if (ISSET(tp->t_state, TS_ERASE)) {
                       /*
                        * end of prterase \.../
                        */
                       CLR(tp->t_state, TS_ERASE);
                       (void)ttyoutput('/', tp);
               }
               i = tp->t_column;
               ttyecho(c, tp);
               if (CCEQ(cc[VEOF], c) && ISSET(lflag, ECHO)) {
                       /*
                        * Place the cursor over the '^' of the ^D.
                        */
                       i = uimin(2, tp->t_column - i);
                       while (i > 0) {
                               (void)ttyoutput('\b', tp);
                               i--;
                       }
               }
       }
endcase:
       /*
        * IXANY means allow any character to restart output.
        */
       if (ISSET(tp->t_state, TS_TTSTOP) &&
           !ISSET(iflag, IXANY) && cc[VSTART] != cc[VSTOP]) {
               return (0);
       }
restartoutput:
       CLR(tp->t_lflag, FLUSHO);
       CLR(tp->t_state, TS_TTSTOP);
startoutput:
       return (ttstart(tp));
}

/*
* Process input of a single character received on a tty.
*
* XXX - this is a hack, all drivers must changed to acquire the
*       lock before calling linesw->l_rint()
*/
int
ttyinput(int c, struct tty *tp)
{
       int error;

       /*
        * Unless the receiver is enabled, drop incoming data.
        */
       if (!ISSET(tp->t_cflag, CREAD))
               return (0);

       mutex_spin_enter(&tty_lock);
       error = ttyinput_wlock(c, tp);
       mutex_spin_exit(&tty_lock);

       return (error);
}

/*
* Output a single character on a tty, doing output processing
* as needed (expanding tabs, newline processing, etc.).
* Returns < 0 if succeeds, otherwise returns char to resend.
* Must be recursive.
*
* Call with tty lock held.
*/
int
ttyoutput(int c, struct tty *tp)
{
       long    oflag;
       int     col, notout;

       KASSERT(mutex_owned(&tty_lock));

       oflag = tp->t_oflag;
       if (!ISSET(oflag, OPOST)) {
               tk_nout++;
               tp->t_outcc++;
               if (!ISSET(tp->t_lflag, FLUSHO) && putc(c, &tp->t_outq))
                       return (c);
               return (-1);
       }
       /*
        * Do tab expansion if OXTABS is set.  Special case if we do external
        * processing, we don't do the tab expansion because we'll probably
        * get it wrong.  If tab expansion needs to be done, let it happen
        * externally.
        */
       CLR(c, ~TTY_CHARMASK);
       if (c == '\t' &&
           ISSET(oflag, OXTABS) && !ISSET(tp->t_lflag, EXTPROC)) {
               c = 8 - (tp->t_column & 7);
               if (ISSET(tp->t_lflag, FLUSHO)) {
                       notout = 0;
               } else {
                       notout = b_to_q("        ", c, &tp->t_outq);
                       c -= notout;
                       tk_nout += c;
                       tp->t_outcc += c;
               }
               tp->t_column += c;
               return (notout ? '\t' : -1);
       }
       if (c == CEOT && ISSET(oflag, ONOEOT))
               return (-1);

       /*
        * Newline translation: if ONLCR is set,
        * translate newline into "\r\n".
        */
       if (c == '\n' && ISSET(tp->t_oflag, ONLCR)) {
               tk_nout++;
               tp->t_outcc++;
               if (!ISSET(tp->t_lflag, FLUSHO) && putc('\r', &tp->t_outq))
                       return (c);
       }
       /* If OCRNL is set, translate "\r" into "\n". */
       else if (c == '\r' && ISSET(tp->t_oflag, OCRNL))
               c = '\n';
       /* If ONOCR is set, don't transmit CRs when on column 0. */
       else if (c == '\r' && ISSET(tp->t_oflag, ONOCR) && tp->t_column == 0)
               return (-1);

       tk_nout++;
       tp->t_outcc++;
       if (!ISSET(tp->t_lflag, FLUSHO) && putc(c, &tp->t_outq))
               return (c);

       col = tp->t_column;
       switch (CCLASS(c)) {
       case BACKSPACE:
               if (col > 0)
                       --col;
               break;
       case CONTROL:
               break;
       case NEWLINE:
               if (ISSET(tp->t_oflag, ONLCR | ONLRET))
                       col = 0;
               break;
       case RETURN:
               col = 0;
               break;
       case ORDINARY:
               ++col;
               break;
       case TAB:
               col = (col + 8) & ~7;
               break;
       }
       tp->t_column = col;
       return (-1);
}

/*
* Ioctls for all tty devices.  Called after line-discipline specific ioctl
* has been called to do discipline-specific functions and/or reject any
* of these ioctl commands.
*/
/* ARGSUSED */
int
ttioctl(struct tty *tp, u_long cmd, void *data, int flag, struct lwp *l)
{
       struct proc *p;
       struct linesw   *lp;
       int             s, error;
       struct pathbuf *pb;
       struct nameidata nd;
       char            infobuf[200];

       KASSERT(l != NULL);
       p = l->l_proc;

       /* If the ioctl involves modification, hang if in the background. */
       switch (cmd) {
       case  TIOCFLUSH:
       case  TIOCDRAIN:
       case  TIOCSBRK:
       case  TIOCCBRK:
       case  TIOCSTART:
       case  TIOCSETA:
       case  TIOCSETD:
       case  TIOCSLINED:
       case  TIOCSETAF:
       case  TIOCSETAW:
#ifdef notdef
       case  TIOCSPGRP:
       case  FIOSETOWN:
#endif
       case  TIOCSTAT:
       case  TIOCSTI:
       case  TIOCSWINSZ:
       case  TIOCSQSIZE:
       case  TIOCLBIC:
       case  TIOCLBIS:
       case  TIOCLSET:
       case  TIOCSETC:
       case OTIOCSETD:
       case  TIOCSETN:
       case  TIOCSETP:
       case  TIOCSLTC:
               mutex_spin_enter(&tty_lock);
               while (isbackground(curproc, tp) &&
                   p->p_pgrp->pg_jobc && (p->p_lflag & PL_PPWAIT) == 0 &&
                   !sigismasked(l, SIGTTOU)) {
                       mutex_spin_exit(&tty_lock);

                       mutex_enter(&proc_lock);
                       pgsignal(p->p_pgrp, SIGTTOU, 1);
                       mutex_exit(&proc_lock);

                       mutex_spin_enter(&tty_lock);
                       error = ttypause(tp, hz);
                       if (error) {
                               mutex_spin_exit(&tty_lock);
                               return (error);
                       }
               }
               mutex_spin_exit(&tty_lock);
               break;
       }

       switch (cmd) {                  /* Process the ioctl. */
       case FIOASYNC:                  /* set/clear async i/o */
               mutex_spin_enter(&tty_lock);
               if (*(int *)data)
                       SET(tp->t_state, TS_ASYNC);
               else
                       CLR(tp->t_state, TS_ASYNC);
               mutex_spin_exit(&tty_lock);
               break;
       case FIONBIO:                   /* set/clear non-blocking i/o */
               break;                  /* XXX: delete. */
       case FIONREAD:                  /* get # bytes to read */
               mutex_spin_enter(&tty_lock);
               *(int *)data = ttnread(tp);
               mutex_spin_exit(&tty_lock);
               break;
       case FIONWRITE:                 /* get # bytes to written & unsent */
               mutex_spin_enter(&tty_lock);
               *(int *)data = tp->t_outq.c_cc;
               mutex_spin_exit(&tty_lock);
               break;
       case FIONSPACE:                 /* get # bytes to written & unsent */
               mutex_spin_enter(&tty_lock);
               *(int *)data = tp->t_outq.c_cn - tp->t_outq.c_cc;
               mutex_spin_exit(&tty_lock);
               break;
       case TIOCEXCL:                  /* set exclusive use of tty */
               mutex_spin_enter(&tty_lock);
               SET(tp->t_state, TS_XCLUDE);
               mutex_spin_exit(&tty_lock);
               break;
       case TIOCFLUSH: {               /* flush buffers */
               int flags = *(int *)data;

               if (flags == 0)
                       flags = FREAD | FWRITE;
               else
                       flags &= FREAD | FWRITE;
               mutex_spin_enter(&tty_lock);
               ttyflush(tp, flags);
               mutex_spin_exit(&tty_lock);
               break;
       }
       case TIOCCONS: {                /* become virtual console */
               struct tty *ctp;

               mutex_enter(&constty_lock);
               error = 0;
               ctp = atomic_load_relaxed(&constty);
               if (*(int *)data) {
                       if (ctp != NULL && ctp != tp &&
                           ISSET(ctp->t_state, TS_CARR_ON | TS_ISOPEN) ==
                           (TS_CARR_ON | TS_ISOPEN)) {
                               error = EBUSY;
                               goto unlock_constty;
                       }

                       pb = pathbuf_create("/dev/console");
                       if (pb == NULL) {
                               error = ENOMEM;
                               goto unlock_constty;
                       }
                       NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, pb);
                       if ((error = namei(&nd)) != 0) {
                               pathbuf_destroy(pb);
                               goto unlock_constty;
                       }
                       error = VOP_ACCESS(nd.ni_vp, VREAD, l->l_cred);
                       vput(nd.ni_vp);
                       pathbuf_destroy(pb);
                       if (error)
                               goto unlock_constty;

                       KASSERT(atomic_load_relaxed(&constty) == ctp ||
                           atomic_load_relaxed(&constty) == NULL);
                       atomic_store_release(&constty, tp);
               } else if (tp == ctp) {
                       atomic_store_relaxed(&constty, NULL);
               }
unlock_constty: mutex_exit(&constty_lock);
               if (error)
                       return error;
               break;
       }
       case TIOCDRAIN:                 /* wait till output drained */
               if ((error = ttywait(tp)) != 0)
                       return (error);
               break;
       case TIOCGETA: {                /* get termios struct */
               struct termios *t = (struct termios *)data;

               memcpy(t, &tp->t_termios, sizeof(struct termios));
               break;
       }
       case TIOCGETD:                  /* get line discipline (old) */
               *(int *)data = tp->t_linesw->l_no;
               break;
       case TIOCGLINED:                /* get line discipline (new) */
               (void)strncpy((char *)data, tp->t_linesw->l_name,
                   TTLINEDNAMELEN - 1);
               break;
       case TIOCGWINSZ:                /* get window size */
               *(struct winsize *)data = tp->t_winsize;
               break;
       case TIOCGQSIZE:
               *(int *)data = tp->t_qsize;
               break;
       case FIOGETOWN:
               mutex_enter(&proc_lock);
               if (tp->t_session != NULL && !isctty(p, tp)) {
                       mutex_exit(&proc_lock);
                       return (ENOTTY);
               }
               *(int *)data = tp->t_pgrp ? -tp->t_pgrp->pg_id : 0;
               mutex_exit(&proc_lock);
               break;
       case TIOCGPGRP:                 /* get pgrp of tty */
               mutex_enter(&proc_lock);
               if (!isctty(p, tp)) {
                       mutex_exit(&proc_lock);
                       return (ENOTTY);
               }
               *(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : NO_PGID;
               mutex_exit(&proc_lock);
               break;
       case TIOCGSID:                  /* get sid of tty */
               mutex_enter(&proc_lock);
               if (!isctty(p, tp)) {
                       mutex_exit(&proc_lock);
                       return (ENOTTY);
               }
               *(int *)data = tp->t_session->s_sid;
               mutex_exit(&proc_lock);
               break;
#ifdef TIOCHPCL
       case TIOCHPCL:                  /* hang up on last close */
               mutex_spin_enter(&tty_lock);
               SET(tp->t_cflag, HUPCL);
               mutex_spin_exit(&tty_lock);
               break;
#endif
       case TIOCNXCL:                  /* reset exclusive use of tty */
               mutex_spin_enter(&tty_lock);
               CLR(tp->t_state, TS_XCLUDE);
               mutex_spin_exit(&tty_lock);
               break;
       case TIOCOUTQ:                  /* output queue size */
               *(int *)data = tp->t_outq.c_cc;
               break;
       case TIOCSETA:                  /* set termios struct */
       case TIOCSETAW:                 /* drain output, set */
       case TIOCSETAF: {               /* drn out, fls in, set */
               struct termios *t = (struct termios *)data;

               if (cmd == TIOCSETAW || cmd == TIOCSETAF) {
                       if ((error = ttywait(tp)) != 0)
                               return (error);

                       if (cmd == TIOCSETAF) {
                               mutex_spin_enter(&tty_lock);
                               ttyflush(tp, FREAD);
                               mutex_spin_exit(&tty_lock);
                       }
               }

               s = spltty();
               /*
                * XXXSMP - some drivers call back on us from t_param(), so
                *          don't take the tty spin lock here.
                *          require t_param() to unlock upon callback?
                */
               /* wanted here: mutex_spin_enter(&tty_lock); */
               if (!ISSET(t->c_cflag, CIGNORE)) {
                       /*
                        * Set device hardware.
                        */
                       if (tp->t_param && (error = (*tp->t_param)(tp, t))) {
                               /* wanted here: mutex_spin_exit(&tty_lock); */
                               splx(s);
                               return (error);
                       } else {
                               tp->t_cflag = t->c_cflag;
                               tp->t_ispeed = t->c_ispeed;
                               tp->t_ospeed = t->c_ospeed;
                               if (t->c_ospeed == 0)
                                       ttysig(tp, TTYSIG_LEADER, SIGHUP);
                       }
                       ttsetwater(tp);
               }

               /* delayed lock acquiring */
               mutex_spin_enter(&tty_lock);
               if (cmd != TIOCSETAF) {
                       if (ISSET(t->c_lflag, ICANON) !=
                           ISSET(tp->t_lflag, ICANON)) {
                               if (ISSET(t->c_lflag, ICANON)) {
                                       SET(tp->t_lflag, PENDIN);
                                       ttwakeup(tp);
                               } else {
                                       struct clist tq;

                                       catq(&tp->t_rawq, &tp->t_canq);
                                       tq = tp->t_rawq;
                                       tp->t_rawq = tp->t_canq;
                                       tp->t_canq = tq;
                                       CLR(tp->t_lflag, PENDIN);
                               }
                       }
               }
               tp->t_iflag = t->c_iflag;
               tp->t_oflag = t->c_oflag;
               /*
                * Make the EXTPROC bit read only.
                */
               if (ISSET(tp->t_lflag, EXTPROC))
                       SET(t->c_lflag, EXTPROC);
               else
                       CLR(t->c_lflag, EXTPROC);
               tp->t_lflag = t->c_lflag | ISSET(tp->t_lflag, PENDIN);
               memcpy(tp->t_cc, t->c_cc, sizeof(t->c_cc));
               mutex_spin_exit(&tty_lock);
               splx(s);
               break;
       }
       case TIOCSETD:                  /* set line discipline (old) */
               lp = ttyldisc_lookup_bynum(*(int *)data);
               goto setldisc;

       case TIOCSLINED: {              /* set line discipline (new) */
               char *name = (char *)data;
               dev_t device;

               /* Null terminate to prevent buffer overflow */
               name[TTLINEDNAMELEN - 1] = '\0';
               lp = ttyldisc_lookup(name);
setldisc:
               if (lp == NULL)
                       return (ENXIO);

               if (lp != tp->t_linesw) {
                       device = tp->t_dev;
                       s = spltty();
                       (*tp->t_linesw->l_close)(tp, flag);
                       error = (*lp->l_open)(device, tp);
                       if (error) {
                               (void)(*tp->t_linesw->l_open)(device, tp);
                               splx(s);
                               ttyldisc_release(lp);
                               return (error);
                       }
                       ttyldisc_release(tp->t_linesw);
                       tp->t_linesw = lp;
                       splx(s);
               } else {
                       /* Drop extra reference. */
                       ttyldisc_release(lp);
               }
               break;
       }
       case TIOCSTART:                 /* start output, like ^Q */
               mutex_spin_enter(&tty_lock);
               if (ISSET(tp->t_state, TS_TTSTOP) ||
                   ISSET(tp->t_lflag, FLUSHO)) {
                       CLR(tp->t_lflag, FLUSHO);
                       CLR(tp->t_state, TS_TTSTOP);
                       ttstart(tp);
               }
               mutex_spin_exit(&tty_lock);
               break;
       case TIOCSTI:                   /* simulate terminal input */
               if ((error = kauth_authorize_device_tty(l->l_cred,
                   KAUTH_DEVICE_TTY_STI, tp)) != 0) {
                       if (!ISSET(flag, FREAD))
                               return EPERM;
                       if (!isctty(p, tp))
                               return EACCES;
                       if (tp->t_session->s_leader->p_cred != p->p_cred)
                               return error;
               }
               (*tp->t_linesw->l_rint)(*(u_char *)data, tp);
               break;
       case TIOCSTOP:                  /* stop output, like ^S */
       {
               mutex_spin_enter(&tty_lock);
               if (!ISSET(tp->t_state, TS_TTSTOP)) {
                       SET(tp->t_state, TS_TTSTOP);
                       cdev_stop(tp, 0);
               }
               mutex_spin_exit(&tty_lock);
               break;
       }
       case TIOCSCTTY:                 /* become controlling tty */
               mutex_enter(&proc_lock);
               mutex_spin_enter(&tty_lock);

               /* Session ctty vnode pointer set in vnode layer. */
               if (!SESS_LEADER(p) ||
                   ((p->p_session->s_ttyvp || tp->t_session) &&
                   (tp->t_session != p->p_session))) {
                       mutex_spin_exit(&tty_lock);
                       mutex_exit(&proc_lock);
                       return (EPERM);
               }

               /*
                * `p_session' acquires a reference.
                * But note that if `t_session' is set at this point,
                * it must equal `p_session', in which case the session
                * already has the correct reference count.
                */
               if (tp->t_session == NULL) {
                       proc_sesshold(p->p_session);
               }
               tp->t_session = p->p_session;
               tp->t_pgrp = p->p_pgrp;
               p->p_session->s_ttyp = tp;
               p->p_lflag |= PL_CONTROLT;
               mutex_spin_exit(&tty_lock);
               mutex_exit(&proc_lock);
               break;
       case FIOSETOWN: {               /* set pgrp of tty */
               pid_t pgid = *(pid_t *)data;
               struct pgrp *pgrp;

               mutex_enter(&proc_lock);
               if (tp->t_session != NULL && !isctty(p, tp)) {
                       mutex_exit(&proc_lock);
                       return (ENOTTY);
               }

               if (pgid < 0) {
                       if (pgid <= INT_MIN) {
                               mutex_exit(&proc_lock);
                               return (EINVAL);
                       }
                       pgrp = pgrp_find(-pgid);
                       if (pgrp == NULL) {
                               mutex_exit(&proc_lock);
                               return (EINVAL);
                       }
               } else {
                       struct proc *p1;
                       p1 = proc_find(pgid);
                       if (!p1) {
                               mutex_exit(&proc_lock);
                               return (ESRCH);
                       }
                       pgrp = p1->p_pgrp;
               }

               if (pgrp->pg_session != p->p_session) {
                       mutex_exit(&proc_lock);
                       return (EPERM);
               }
               mutex_spin_enter(&tty_lock);
               tp->t_pgrp = pgrp;
               mutex_spin_exit(&tty_lock);
               mutex_exit(&proc_lock);
               break;
       }
       case TIOCSPGRP: {               /* set pgrp of tty */
               struct pgrp *pgrp;
               pid_t pgid = *(pid_t *)data;

               if (pgid == NO_PGID)
                       return EINVAL;

               mutex_enter(&proc_lock);
               if (!isctty(p, tp)) {
                       mutex_exit(&proc_lock);
                       return (ENOTTY);
               }
               pgrp = pgrp_find(pgid);
               if (pgrp == NULL || pgrp->pg_session != p->p_session) {
                       mutex_exit(&proc_lock);
                       return (EPERM);
               }
               mutex_spin_enter(&tty_lock);
               tp->t_pgrp = pgrp;
               mutex_spin_exit(&tty_lock);
               mutex_exit(&proc_lock);
               break;
       }
       case TIOCSTAT:                  /* get load avg stats */
               mutex_enter(&proc_lock);
               ttygetinfo(tp, 0, infobuf, sizeof(infobuf));
               mutex_exit(&proc_lock);

               mutex_spin_enter(&tty_lock);
               ttyputinfo(tp, infobuf);
               mutex_spin_exit(&tty_lock);
               break;
       case TIOCSWINSZ:                /* set window size */
               mutex_spin_enter(&tty_lock);
               if (memcmp((void *)&tp->t_winsize, data,
                   sizeof(struct winsize))) {
                       tp->t_winsize = *(struct winsize *)data;
                       ttysig(tp, TTYSIG_PG1, SIGWINCH);
               }
               mutex_spin_exit(&tty_lock);
               break;
       case TIOCSQSIZE:
               if ((error = tty_get_qsize(&s, *(int *)data)) == 0 &&
                   s != tp->t_qsize)
                       error = tty_set_qsize(tp, s);
               return error;

       case TIOCSBRK:
       case TIOCCBRK:
       case TIOCSDTR:
       case TIOCCDTR:
       case TIOCSFLAGS:
       case TIOCGFLAGS:
       case TIOCMSET:
       case TIOCMGET:
       case TIOCMBIS:
       case TIOCMBIC:
               /* Handled by the driver layer */
               return EPASSTHROUGH;

       case TIOCEXT:
       case TIOCPTSNAME:
       case TIOCGRANTPT:
       case TIOCPKT:
       case TIOCUCNTL:
       case TIOCREMOTE:
       case TIOCSIG:
               /* for ptys */
               return EPASSTHROUGH;

       default:
               /* Pass through various console ioctls */
               switch (IOCGROUP(cmd)) {
               case 'c':       /* syscons console */
               case 'v':       /* usl console, video - where one letter */
               case 'K':       /* usl console, keyboard - aint enough */
               case 'V':       /* pcvt compat */
               case 'W':       /* wscons console */
                       return EPASSTHROUGH;
               default:
                       break;
               }

               /* We may have to load the compat_60 module for this. */
               (void)module_autoload("compat_60", MODULE_CLASS_EXEC);
               MODULE_HOOK_CALL(tty_ttioctl_60_hook,
                   (tp, cmd, data, flag, l), enosys(), error);
               if (error != EPASSTHROUGH)
                       return error;

               /* We may have to load the compat_43 module for this. */
               (void)module_autoload("compat_43", MODULE_CLASS_EXEC);
               MODULE_HOOK_CALL(tty_ttioctl_43_hook,
                   (tp, cmd, data, flag, l), enosys(), error);
               return error;
       }
       return (0);
}

int
ttpoll(struct tty *tp, int events, struct lwp *l)
{
       int     revents;

       revents = 0;
       mutex_spin_enter(&tty_lock);
       if (events & (POLLIN | POLLRDNORM))
               if (ttnread(tp) > 0)
                       revents |= events & (POLLIN | POLLRDNORM);

       if (events & (POLLOUT | POLLWRNORM))
               if (tp->t_outq.c_cc <= tp->t_lowat)
                       revents |= events & (POLLOUT | POLLWRNORM);

       if (events & POLLHUP)
               if (!CONNECTED(tp))
                       revents |= POLLHUP;

       if (revents == 0) {
               if (events & (POLLIN | POLLHUP | POLLRDNORM))
                       selrecord(l, &tp->t_rsel);

               if (events & (POLLOUT | POLLWRNORM))
                       selrecord(l, &tp->t_wsel);
       }

       mutex_spin_exit(&tty_lock);

       return (revents);
}

static void
filt_ttyrdetach(struct knote *kn)
{
       struct tty      *tp;

       tp = kn->kn_hook;
       mutex_spin_enter(&tty_lock);
       selremove_knote(&tp->t_rsel, kn);
       mutex_spin_exit(&tty_lock);
}

static int
filt_ttyread(struct knote *kn, long hint)
{
       struct tty      *tp;
       int rv;

       tp = kn->kn_hook;
       if ((hint & NOTE_SUBMIT) == 0)
               mutex_spin_enter(&tty_lock);
       kn->kn_data = ttnread(tp);
       rv = kn->kn_data > 0;
       if ((hint & NOTE_SUBMIT) == 0)
               mutex_spin_exit(&tty_lock);
       return rv;
}

static void
filt_ttywdetach(struct knote *kn)
{
       struct tty      *tp;

       tp = kn->kn_hook;
       mutex_spin_enter(&tty_lock);
       selremove_knote(&tp->t_wsel, kn);
       mutex_spin_exit(&tty_lock);
}

static int
filt_ttywrite(struct knote *kn, long hint)
{
       struct tty      *tp;
       int             canwrite;

       tp = kn->kn_hook;
       if ((hint & NOTE_SUBMIT) == 0)
               mutex_spin_enter(&tty_lock);
       kn->kn_data = tp->t_outq.c_cn - tp->t_outq.c_cc;
       canwrite = (tp->t_outq.c_cc <= tp->t_lowat) && CONNECTED(tp);
       if ((hint & NOTE_SUBMIT) == 0)
               mutex_spin_exit(&tty_lock);
       return (canwrite);
}

static const struct filterops ttyread_filtops = {
       .f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
       .f_attach = NULL,
       .f_detach = filt_ttyrdetach,
       .f_event = filt_ttyread,
};

static const struct filterops ttywrite_filtops = {
       .f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
       .f_attach = NULL,
       .f_detach = filt_ttywdetach,
       .f_event = filt_ttywrite,
};

int
ttykqfilter(dev_t dev, struct knote *kn)
{
       struct tty      *tp;
       struct selinfo  *sip;

       if ((tp = cdev_tty(dev)) == NULL)
               return (ENXIO);

       switch (kn->kn_filter) {
       case EVFILT_READ:
               sip = &tp->t_rsel;
               kn->kn_fop = &ttyread_filtops;
               break;
       case EVFILT_WRITE:
               sip = &tp->t_wsel;
               kn->kn_fop = &ttywrite_filtops;
               break;
       default:
               return EINVAL;
       }

       kn->kn_hook = tp;

       mutex_spin_enter(&tty_lock);
       selrecord_knote(sip, kn);
       mutex_spin_exit(&tty_lock);

       return (0);
}

/*
* Find the number of chars ready to be read from this tty.
* Call with the tty lock held.
*/
static int
ttnread(struct tty *tp)
{
       int     nread;

       KASSERT(mutex_owned(&tty_lock));

       if (ISSET(tp->t_lflag, PENDIN))
               ttypend(tp);
       nread = tp->t_canq.c_cc;
       if (!ISSET(tp->t_lflag, ICANON)) {
               nread += tp->t_rawq.c_cc;
               if (nread < tp->t_cc[VMIN] && !tp->t_cc[VTIME])
                       nread = 0;
       }
       return (nread);
}

/*
* Wait for output to drain, or if this times out, flush it.
*/
static int
ttywait_timo(struct tty *tp, int timo)
{
       int     error;

       error = 0;

       mutex_spin_enter(&tty_lock);
       while ((tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)) &&
           CONNECTED(tp) && tp->t_oproc) {
               (*tp->t_oproc)(tp);
               error = ttysleep(tp, &tp->t_outcv, true, timo);
               if (error == EWOULDBLOCK)
                       ttyflush(tp, FWRITE);
               if (error)
                       break;
       }
       mutex_spin_exit(&tty_lock);

       return (error);
}

/*
* Wait for output to drain.
*/
int
ttywait(struct tty *tp)
{
       return ttywait_timo(tp, 0);
}

/*
* Flush if successfully wait.
*/
int
ttywflush(struct tty *tp)
{
       int     error;

       error = ttywait_timo(tp, 5 * hz);
       if (error == 0 || error == EWOULDBLOCK) {
               mutex_spin_enter(&tty_lock);
               ttyflush(tp, FREAD);
               mutex_spin_exit(&tty_lock);
       }
       return (error);
}

/*
* Flush tty read and/or write queues, notifying anyone waiting.
* Call with the tty lock held.
*/
void
ttyflush(struct tty *tp, int rw)
{

       KASSERT(mutex_owned(&tty_lock));

       if (rw & FREAD) {
               FLUSHQ(&tp->t_canq);
               FLUSHQ(&tp->t_rawq);
               tp->t_rocount = 0;
               tp->t_rocol = 0;
               CLR(tp->t_state, TS_LOCAL);
               ttwakeup(tp);
       }
       if (rw & FWRITE) {
               CLR(tp->t_state, TS_TTSTOP);
               cdev_stop(tp, rw);
               FLUSHQ(&tp->t_outq);
               cv_broadcast(&tp->t_outcv);
               selnotify(&tp->t_wsel, 0, NOTE_SUBMIT);
       }
}

/*
* Copy in the default termios characters.
*/
void
ttychars(struct tty *tp)
{

       memcpy(tp->t_cc, ttydefchars, sizeof(ttydefchars));
}

/*
* Send stop character on input overflow.
* Call with the tty lock held.
*/
static void
ttyblock(struct tty *tp)
{
       int     total;

       KASSERT(mutex_owned(&tty_lock));

       total = tp->t_rawq.c_cc + tp->t_canq.c_cc;
       if (tp->t_rawq.c_cc > TTYHOG) {
               ttyflush(tp, FREAD | FWRITE);
               CLR(tp->t_state, TS_TBLOCK);
       }
       /*
        * Block further input iff: current input > threshold
        * AND input is available to user program.
        */
       if (total >= TTYHOG / 2 &&
           !ISSET(tp->t_state, TS_TBLOCK) &&
           (!ISSET(tp->t_lflag, ICANON) || tp->t_canq.c_cc > 0)) {
               if (ISSET(tp->t_iflag, IXOFF) &&
                   tp->t_cc[VSTOP] != _POSIX_VDISABLE &&
                   putc(tp->t_cc[VSTOP], &tp->t_outq) == 0) {
                       SET(tp->t_state, TS_TBLOCK);
                       ttstart(tp);
               }
               /* Try to block remote output via hardware flow control. */
               if (ISSET(tp->t_cflag, CHWFLOW) && tp->t_hwiflow &&
                   (*tp->t_hwiflow)(tp, 1) != 0)
                       SET(tp->t_state, TS_TBLOCK);
       }
}

/*
* Delayed line discipline output
*/
void
ttrstrt(void *tp_arg)
{
       struct tty      *tp;

#ifdef DIAGNOSTIC
       if (tp_arg == NULL)
               panic("ttrstrt");
#endif
       tp = tp_arg;
       mutex_spin_enter(&tty_lock);

       CLR(tp->t_state, TS_TIMEOUT);
       ttstart(tp); /* XXX - Shouldn't this be tp->l_start(tp)? */

       mutex_spin_exit(&tty_lock);
}

/*
* start a line discipline
* Always call with tty lock held?
*/
int
ttstart(struct tty *tp)
{

       if (tp->t_oproc != NULL)        /* XXX: Kludge for pty. */
               (*tp->t_oproc)(tp);
       return (0);
}

/*
* "close" a line discipline
*/
int
ttylclose(struct tty *tp, int flag)
{

       if (flag & FNONBLOCK) {
               mutex_spin_enter(&tty_lock);
               ttyflush(tp, FREAD | FWRITE);
               mutex_spin_exit(&tty_lock);
       } else
               ttywflush(tp);
       return (0);
}

/*
* Handle modem control transition on a tty.
* Flag indicates new state of carrier.
* Returns 0 if the line should be turned off, otherwise 1.
*/
int
ttymodem(struct tty *tp, int flag)
{

       mutex_spin_enter(&tty_lock);
       if (flag == 0) {
               if (ISSET(tp->t_state, TS_CARR_ON)) {
                       /*
                        * Lost carrier.
                        */
                       CLR(tp->t_state, TS_CARR_ON);
                       if (ISSET(tp->t_state, TS_ISOPEN) && !CONNECTED(tp)) {
                               ttysig(tp, TTYSIG_LEADER, SIGHUP);
                               ttyflush(tp, FREAD | FWRITE);
                               mutex_spin_exit(&tty_lock);
                               return (0);
                       }
               }
       } else {
               if (!ISSET(tp->t_state, TS_CARR_ON)) {
                       /*
                        * Carrier now on.
                        */
                       SET(tp->t_state, TS_CARR_ON);
                       ttwakeup(tp);
               }
       }
       mutex_spin_exit(&tty_lock);

       return (1);
}

/*
* Default modem control routine (for other line disciplines).
* Return argument flag, to turn off device on carrier drop.
*/
int
nullmodem(struct tty *tp, int flag)
{

       mutex_spin_enter(&tty_lock);
       if (flag)
               SET(tp->t_state, TS_CARR_ON);
       else {
               CLR(tp->t_state, TS_CARR_ON);
               if (!CONNECTED(tp)) {
                       ttysig(tp, TTYSIG_LEADER, SIGHUP);
                       mutex_spin_exit(&tty_lock);
                       return (0);
               }
       }
       mutex_spin_exit(&tty_lock);

       return (1);
}

/*
* Reinput pending characters after state switch.
*/
void
ttypend(struct tty *tp)
{
       struct clist    tq;
       int             c;

       KASSERT(mutex_owned(&tty_lock));

       CLR(tp->t_lflag, PENDIN);
       SET(tp->t_state, TS_TYPEN);
       tq = tp->t_rawq;
       tp->t_rawq.c_cc = 0;
       tp->t_rawq.c_cf = tp->t_rawq.c_cl = 0;
       while ((c = getc(&tq)) >= 0)
               ttyinput_wlock(c, tp);
       CLR(tp->t_state, TS_TYPEN);
}

/*
* Process a read call on a tty device.
*/
int
ttread(struct tty *tp, struct uio *uio, int flag)
{
       struct clist    *qp;
       u_char          *cc;
       struct proc     *p;
       int             c, first, error, has_stime, last_cc;
       long            lflag, slp;
       struct timeval  now, stime;

       if (uio->uio_resid == 0)
               return 0;

       stime.tv_usec = 0;      /* XXX gcc */
       stime.tv_sec = 0;       /* XXX gcc */

       cc = tp->t_cc;
       p = curproc;
       error = 0;
       has_stime = 0;
       last_cc = 0;
       slp = 0;

loop:
       mutex_spin_enter(&tty_lock);
       lflag = tp->t_lflag;
       /*
        * take pending input first
        */
       if (ISSET(lflag, PENDIN))
               ttypend(tp);

       /*
        * Hang process if it's in the background.
        */
       if (isbackground(p, tp)) {
               if (sigismasked(curlwp, SIGTTIN) ||
                   p->p_lflag & PL_PPWAIT || p->p_pgrp->pg_jobc == 0) {
                       mutex_spin_exit(&tty_lock);
                       return (EIO);
               }
               mutex_spin_exit(&tty_lock);

               mutex_enter(&proc_lock);
               pgsignal(p->p_pgrp, SIGTTIN, 1);
               mutex_exit(&proc_lock);

               mutex_spin_enter(&tty_lock);
               error = ttypause(tp, hz);
               mutex_spin_exit(&tty_lock);
               if (error)
                       return (error);
               goto loop;
       }

       if (!ISSET(lflag, ICANON)) {
               int m = cc[VMIN];
               long t = cc[VTIME];

               qp = &tp->t_rawq;
               /*
                * Check each of the four combinations.
                * (m > 0 && t == 0) is the normal read case.
                * It should be fairly efficient, so we check that and its
                * companion case (m == 0 && t == 0) first.
                * For the other two cases, we compute the target sleep time
                * into slp.
                */
               if (t == 0) {
                       if (qp->c_cc < m)
                               goto sleep;
                       goto read;
               }
               t *= hz;                /* time in deca-ticks */
/*
* Time difference in deca-ticks, split division to avoid numeric overflow.
* Ok for hz < ~200kHz
*/
#define diff(t1, t2) (((t1).tv_sec - (t2).tv_sec) * 10 * hz + \
                        ((t1).tv_usec - (t2).tv_usec) / 100 * hz / 1000)
               if (m > 0) {
                       if (qp->c_cc <= 0)
                               goto sleep;
                       if (qp->c_cc >= m)
                               goto read;
                       if (!has_stime) {
                               /* first character, start timer */
                               has_stime = 1;
                               getmicrotime(&stime);
                               slp = t;
                       } else if (qp->c_cc > last_cc) {
                               /* got a character, restart timer */
                               getmicrotime(&stime);
                               slp = t;
                       } else {
                               /* nothing, check expiration */
                               getmicrotime(&now);
                               slp = t - diff(now, stime);
                       }
               } else {        /* m == 0 */
                       if (qp->c_cc > 0)
                               goto read;
                       if (!has_stime) {
                               has_stime = 1;
                               getmicrotime(&stime);
                               slp = t;
                       } else {
                               getmicrotime(&now);
                               slp = t - diff(now, stime);
                       }
               }
               last_cc = qp->c_cc;
#undef diff
               if (slp > 0) {
                       /*
                        * Convert deca-ticks back to ticks.
                        * Rounding down may make us wake up just short
                        * of the target, so we round up.
                        * Maybe we should do 'slp/10 + 1' because the
                        * first tick maybe almost immediate.
                        * However it is more useful for a program that sets
                        * VTIME=10 to wakeup every second not every 1.01
                        * seconds (if hz=100).
                        */
                       slp = (slp + 9)/ 10;
                       goto sleep;
               }
       } else if ((qp = &tp->t_canq)->c_cc <= 0) {
               int     carrier;

sleep:
               /*
                * If there is no input, sleep on rawq
                * awaiting hardware receipt and notification.
                * If we have data, we don't need to check for carrier.
                */
               carrier = CONNECTED(tp);
               if (!carrier && ISSET(tp->t_state, TS_ISOPEN)) {
                       mutex_spin_exit(&tty_lock);
                       return (0);     /* EOF */
               }
               if (!has_stime || slp <= 0) {
                       if (flag & IO_NDELAY) {
                               mutex_spin_exit(&tty_lock);
                               return (EWOULDBLOCK);
                       }
               }
               error = ttysleep(tp, &tp->t_rawcv, true, slp);
               mutex_spin_exit(&tty_lock);
               /* VMIN == 0: any quantity read satisfies */
               if (cc[VMIN] == 0 && error == EWOULDBLOCK)
                       return (0);
               if (error && error != EWOULDBLOCK)
                       return (error);
               goto loop;
       }
read:

       /*
        * Input present, check for input mapping and processing.
        */
       first = 1;
       while ((c = getc(qp)) >= 0) {
               /*
                * delayed suspend (^Y)
                */
               if (CCEQ(cc[VDSUSP], c) &&
                   ISSET(lflag, IEXTEN|ISIG) == (IEXTEN|ISIG)) {
                       ttysig(tp, TTYSIG_PG1, SIGTSTP);
                       if (first) {
                               error = ttypause(tp, hz);
                               if (error)
                                       break;
                               mutex_spin_exit(&tty_lock);
                               goto loop;
                       }
                       break;
               }
               /*
                * Interpret EOF only in canonical mode.
                */
               if (CCEQ(cc[VEOF], c) && ISSET(lflag, ICANON))
                       break;
               /*
                * Give user character.
                */
               mutex_spin_exit(&tty_lock);
               error = ureadc(c, uio);
               mutex_spin_enter(&tty_lock);
               if (error)
                       break;
               if (uio->uio_resid == 0)
                       break;
               /*
                * In canonical mode check for a "break character"
                * marking the end of a "line of input".
                */
               if (ISSET(lflag, ICANON) && TTBREAKC(c, lflag))
                       break;
               first = 0;
       }

       /*
        * Look to unblock output now that (presumably)
        * the input queue has gone down.
        */
       if (ISSET(tp->t_state, TS_TBLOCK) && tp->t_rawq.c_cc < TTYHOG / 5) {
               if (ISSET(tp->t_iflag, IXOFF) &&
                   cc[VSTART] != _POSIX_VDISABLE &&
                   putc(cc[VSTART], &tp->t_outq) == 0) {
                       CLR(tp->t_state, TS_TBLOCK);
                       ttstart(tp);
               }
               /* Try to unblock remote output via hardware flow control. */
               if (ISSET(tp->t_cflag, CHWFLOW) && tp->t_hwiflow &&
                   (*tp->t_hwiflow)(tp, 0) != 0)
                       CLR(tp->t_state, TS_TBLOCK);
       }
       mutex_spin_exit(&tty_lock);

       return (error);
}

/*
* Check the output queue on tp for space for a kernel message (from uprintf
* or tprintf).  Allow some space over the normal hiwater mark so we don't
* lose messages due to normal flow control, but don't let the tty run amok.
* Sleeps here are not interruptible, but we return prematurely if new signals
* arrive.
* Call with tty lock held.
*/
static int
ttycheckoutq_wlock(struct tty *tp)
{
       int     hiwat;

       KASSERT(mutex_owned(&tty_lock));

       hiwat = tp->t_hiwat;
       if (tp->t_outq.c_cc > hiwat + 200)
               if (tp->t_outq.c_cc > hiwat) {
                       ttstart(tp);
                       return (0);
               }

       return (1);
}

int
ttycheckoutq(struct tty *tp)
{
       int     r;

       mutex_spin_enter(&tty_lock);
       r = ttycheckoutq_wlock(tp);
       mutex_spin_exit(&tty_lock);

       return (r);
}

/*
* Process a write call on a tty device.
*/
int
ttwrite(struct tty *tp, struct uio *uio, int flag)
{
       u_char          *cp;
       struct proc     *p;
       int             cc, cc0, ce, i, hiwat, error;
       u_char          obuf[OBUFSIZ];

       cp = NULL;
       hiwat = tp->t_hiwat;
       error = 0;
       cc0 = cc = 0;
loop:
       mutex_spin_enter(&tty_lock);
       if (!CONNECTED(tp)) {
               if (ISSET(tp->t_state, TS_ISOPEN)) {
                       mutex_spin_exit(&tty_lock);
                       return (EIO);
               } else if (flag & IO_NDELAY) {
                       mutex_spin_exit(&tty_lock);
                       error = EWOULDBLOCK;
                       goto out;
               } else {
                       /* Sleep awaiting carrier. */
                       error = ttysleep(tp, &tp->t_rawcv, true, 0);
                       mutex_spin_exit(&tty_lock);
                       if (error)
                               goto out;
                       goto loop;
               }
       }

       /*
        * Hang the process if it's in the background.
        */
       p = curproc;
       if (isbackground(p, tp) &&
           ISSET(tp->t_lflag, TOSTOP) && (p->p_lflag & PL_PPWAIT) == 0 &&
           !sigismasked(curlwp, SIGTTOU)) {
               if (p->p_pgrp->pg_jobc == 0) {
                       error = EIO;
                       mutex_spin_exit(&tty_lock);
                       goto out;
               }
               mutex_spin_exit(&tty_lock);

               mutex_enter(&proc_lock);
               pgsignal(p->p_pgrp, SIGTTOU, 1);
               mutex_exit(&proc_lock);

               mutex_spin_enter(&tty_lock);
               error = ttypause(tp, hz);
               mutex_spin_exit(&tty_lock);
               if (error)
                       goto out;
               goto loop;
       }
       mutex_spin_exit(&tty_lock);

       /*
        * Process the user's data in at most OBUFSIZ chunks.  Perform any
        * output translation.  Keep track of high water mark, sleep on
        * overflow awaiting device aid in acquiring new space.
        */
       while (uio->uio_resid > 0 || cc > 0) {
               if (ISSET(tp->t_lflag, FLUSHO)) {
                       uio->uio_resid = 0;
                       return (0);
               }
               if (tp->t_outq.c_cc > hiwat)
                       goto ovhiwat;
               /*
                * Grab a hunk of data from the user, unless we have some
                * leftover from last time.
                */
               if (cc == 0) {
                       uioskip(cc0, uio);
                       cc0 = cc = uimin(uio->uio_resid, OBUFSIZ);
                       cp = obuf;
                       error = uiopeek(cp, cc, uio);
                       if (error) {
                               cc = 0;
                               goto out;
                       }
               }
               /*
                * If nothing fancy need be done, grab those characters we
                * can handle without any of ttyoutput's processing and
                * just transfer them to the output q.  For those chars
                * which require special processing (as indicated by the
                * bits in char_type), call ttyoutput.  After processing
                * a hunk of data, look for FLUSHO so ^O's will take effect
                * immediately.
                */
               mutex_spin_enter(&tty_lock);
               while (cc > 0) {
                       if (!ISSET(tp->t_oflag, OPOST))
                               ce = cc;
                       else {
                               ce = cc - scanc((u_int)cc, cp, char_type,
                                   CCLASSMASK);
                               /*
                                * If ce is zero, then we're processing
                                * a special character through ttyoutput.
                                */
                               if (ce == 0) {
                                       tp->t_rocount = 0;
                                       if (ttyoutput(*cp, tp) >= 0) {
                                               /* out of space */
                                               mutex_spin_exit(&tty_lock);
                                               goto overfull;
                                       }
                                       cp++;
                                       cc--;
                                       if (ISSET(tp->t_lflag, FLUSHO) ||
                                           tp->t_outq.c_cc > hiwat) {
                                               mutex_spin_exit(&tty_lock);
                                               goto ovhiwat;
                                       }
                                       continue;
                               }
                       }
                       /*
                        * A bunch of normal characters have been found.
                        * Transfer them en masse to the output queue and
                        * continue processing at the top of the loop.
                        * If there are any further characters in this
                        * <= OBUFSIZ chunk, the first should be a character
                        * requiring special handling by ttyoutput.
                        */
                       tp->t_rocount = 0;
                       i = b_to_q(cp, ce, &tp->t_outq);
                       ce -= i;
                       tp->t_column += ce;
                       cp += ce, cc -= ce, tk_nout += ce;
                       tp->t_outcc += ce;
                       if (i > 0) {
                               /* out of space */
                               mutex_spin_exit(&tty_lock);
                               goto overfull;
                       }
                       if (ISSET(tp->t_lflag, FLUSHO) ||
                           tp->t_outq.c_cc > hiwat)
                               break;
               }
               ttstart(tp);
               mutex_spin_exit(&tty_lock);
       }

out:
       KASSERTMSG(error || cc == 0, "error=%d cc=%d", error, cc);
       KASSERTMSG(cc0 >= cc, "cc0=%d cc=%d", cc0, cc);
       uioskip(cc0 - cc, uio);
       return (error);

overfull:
       /*
        * Since we are using ring buffers, if we can't insert any more into
        * the output queue, we can assume the ring is full and that someone
        * forgot to set the high water mark correctly.  We set it and then
        * proceed as normal.
        */
       hiwat = tp->t_outq.c_cc - 1;

ovhiwat:
       mutex_spin_enter(&tty_lock);
       ttstart(tp);
       /*
        * This can only occur if FLUSHO is set in t_lflag,
        * or if ttstart/oproc is synchronous (or very fast).
        */
       if (tp->t_outq.c_cc <= hiwat) {
               mutex_spin_exit(&tty_lock);
               goto loop;
       }
       if (flag & IO_NDELAY) {
               mutex_spin_exit(&tty_lock);
               error = EWOULDBLOCK;
               goto out;
       }
       error = ttysleep(tp, &tp->t_outcv, true, 0);
       mutex_spin_exit(&tty_lock);
       if (error)
               goto out;
       goto loop;
}

/*
* Try to pull more output from the producer.  Return non-zero if
* there is output ready to be sent.
*/
bool
ttypull(struct tty *tp)
{

       /* XXXSMP not yet KASSERT(mutex_owned(&tty_lock)); */

       if (tp->t_outq.c_cc <= tp->t_lowat) {
               cv_broadcast(&tp->t_outcv);
               selnotify(&tp->t_wsel, 0, NOTE_SUBMIT);
       }
       return tp->t_outq.c_cc != 0;
}

/*
* Rubout one character from the rawq of tp
* as cleanly as possible.
* Called with tty lock held.
*/
void
ttyrub(int c, struct tty *tp)
{
       u_char  *cp;
       int     savecol, tabc;

       KASSERT(mutex_owned(&tty_lock));

       if (!ISSET(tp->t_lflag, ECHO) || ISSET(tp->t_lflag, EXTPROC))
               return;
       CLR(tp->t_lflag, FLUSHO);
       if (ISSET(tp->t_lflag, ECHOE)) {
               if (tp->t_rocount == 0) {
                       /*
                        * Screwed by ttwrite; retype
                        */
                       ttyretype(tp);
                       return;
               }
               if (c == ('\t' | TTY_QUOTE) || c == ('\n' | TTY_QUOTE))
                       ttyrubo(tp, 2);
               else {
                       CLR(c, ~TTY_CHARMASK);
                       switch (CCLASS(c)) {
                       case ORDINARY:
                               ttyrubo(tp, 1);
                               break;
                       case BACKSPACE:
                       case CONTROL:
                       case NEWLINE:
                       case RETURN:
                       case VTAB:
                               if (ISSET(tp->t_lflag, ECHOCTL))
                                       ttyrubo(tp, 2);
                               break;
                       case TAB:
                               if (tp->t_rocount < tp->t_rawq.c_cc) {
                                       ttyretype(tp);
                                       return;
                               }
                               savecol = tp->t_column;
                               SET(tp->t_state, TS_CNTTB);
                               SET(tp->t_lflag, FLUSHO);
                               tp->t_column = tp->t_rocol;
                               for (cp = firstc(&tp->t_rawq, &tabc); cp;
                                   cp = nextc(&tp->t_rawq, cp, &tabc))
                                       ttyecho(tabc, tp);
                               CLR(tp->t_lflag, FLUSHO);
                               CLR(tp->t_state, TS_CNTTB);

                               /* savecol will now be length of the tab. */
                               savecol -= tp->t_column;
                               tp->t_column += savecol;
                               if (savecol > 8)
                                       savecol = 8;    /* overflow screw */
                               while (--savecol >= 0)
                                       (void)ttyoutput('\b', tp);
                               break;
                       default:                        /* XXX */
                               (void)printf("ttyrub: would panic c = %d, "
                                   "val = %d\n", c, CCLASS(c));
                       }
               }
       } else if (ISSET(tp->t_lflag, ECHOPRT)) {
               if (!ISSET(tp->t_state, TS_ERASE)) {
                       SET(tp->t_state, TS_ERASE);
                       (void)ttyoutput('\\', tp);
               }
               ttyecho(c, tp);
       } else
               ttyecho(tp->t_cc[VERASE], tp);
       --tp->t_rocount;
}

/*
* Back over cnt characters, erasing them.
* Called with tty lock held.
*/
static void
ttyrubo(struct tty *tp, int cnt)
{

       KASSERT(mutex_owned(&tty_lock));

       while (cnt-- > 0) {
               (void)ttyoutput('\b', tp);
               (void)ttyoutput(' ', tp);
               (void)ttyoutput('\b', tp);
       }
}

/*
* ttyretype --
*      Reprint the rawq line.  Note, it is assumed that c_cc has already
*      been checked.
*
* Called with tty lock held.
*/
void
ttyretype(struct tty *tp)
{
       u_char  *cp;
       int     c;

       KASSERT(mutex_owned(&tty_lock));

       /* Echo the reprint character. */
       if (tp->t_cc[VREPRINT] != _POSIX_VDISABLE)
               ttyecho(tp->t_cc[VREPRINT], tp);

       (void)ttyoutput('\n', tp);

       for (cp = firstc(&tp->t_canq, &c); cp; cp = nextc(&tp->t_canq, cp, &c))
               ttyecho(c, tp);
       for (cp = firstc(&tp->t_rawq, &c); cp; cp = nextc(&tp->t_rawq, cp, &c))
               ttyecho(c, tp);
       CLR(tp->t_state, TS_ERASE);

       tp->t_rocount = tp->t_rawq.c_cc;
       tp->t_rocol = 0;
}

/*
* Echo a typed character to the terminal.
* Called with tty lock held.
*/
static void
ttyecho(int c, struct tty *tp)
{

       KASSERT(mutex_owned(&tty_lock));

       if (!ISSET(tp->t_state, TS_CNTTB))
               CLR(tp->t_lflag, FLUSHO);
       if ((!ISSET(tp->t_lflag, ECHO) &&
           (!ISSET(tp->t_lflag, ECHONL) || c != '\n')) ||
           ISSET(tp->t_lflag, EXTPROC))
               return;
       if (((ISSET(tp->t_lflag, ECHOCTL) &&
           (ISSET(c, TTY_CHARMASK) <= 037 && c != '\t' && c != '\n')) ||
           ISSET(c, TTY_CHARMASK) == 0177)) {
               (void)ttyoutput('^', tp);
               CLR(c, ~TTY_CHARMASK);
               if (c == 0177)
                       c = '?';
               else
                       c += 'A' - 1;
       }
       (void)ttyoutput(c, tp);
}

/*
* Wake up any readers on a tty.
* Called with tty lock held.
*/
void
ttwakeup(struct tty *tp)
{

       KASSERT(mutex_owned(&tty_lock));

       selnotify(&tp->t_rsel, 0, NOTE_SUBMIT);
       if (ISSET(tp->t_state, TS_ASYNC))
               ttysig(tp, TTYSIG_PG2, SIGIO);
       cv_broadcast(&tp->t_rawcv);
}

/*
* Look up a code for a specified speed in a conversion table;
* used by drivers to map software speed values to hardware parameters.
*/
int
ttspeedtab(int speed, const struct speedtab *table)
{

       for (; table->sp_speed != -1; table++)
               if (table->sp_speed == speed)
                       return (table->sp_code);
       return (-1);
}

/*
* Set tty hi and low water marks.
*
* Try to arrange the dynamics so there's about one second
* from hi to low water.
*/
void
ttsetwater(struct tty *tp)
{
       int     cps, x;

       /* XXX not yet KASSERT(mutex_owned(&tty_lock)); */

#define CLAMP(x, h, l)  ((x) > h ? h : ((x) < l) ? l : (x))

       cps = tp->t_ospeed / 10;
       tp->t_lowat = x = CLAMP(cps / 2, TTMAXLOWAT, TTMINLOWAT);
       x += cps;
       x = CLAMP(x, TTMAXHIWAT, TTMINHIWAT);
       tp->t_hiwat = roundup(x, TTROUND);
#undef  CLAMP
}

/*
* Prepare report on state of foreground process group.
* Call with &proc_lock held.
*/
void
ttygetinfo(struct tty *tp, int fromsig, char *buf, size_t bufsz)
{
       struct lwp      *l;
       struct proc     *p, *pick = NULL;
       struct timeval  utime, stime;
       int             tmp;
       fixpt_t         pctcpu = 0;
       const char      *msg = NULL;
       char            lmsg[100];
       long            rss;
       bool            again = false;

       KASSERT(mutex_owned(&proc_lock));

       *buf = '\0';

retry:
       if (tp->t_session == NULL)
               msg = "not a controlling terminal\n";
       else if (tp->t_pgrp == NULL)
               msg = "no foreground process group\n";
       else if ((p = LIST_FIRST(&tp->t_pgrp->pg_members)) == NULL)
               msg = "empty foreground process group\n";
       else {
               /* Pick interesting process. */
               for (; p != NULL; p = LIST_NEXT(p, p_pglist)) {
                       struct proc *oldpick;

                       if (pick == NULL) {
                               pick = p;
                               continue;
                       }
                       if (pick->p_lock < p->p_lock) {
                               mutex_enter(pick->p_lock);
                               mutex_enter(p->p_lock);
                       } else if (pick->p_lock > p->p_lock) {
                               mutex_enter(p->p_lock);
                               mutex_enter(pick->p_lock);
                       } else
                               mutex_enter(p->p_lock);
                       oldpick = pick;
                       if (proc_compare_wrapper(pick, p))
                               pick = p;
                       mutex_exit(p->p_lock);
                       if (p->p_lock != oldpick->p_lock)
                               mutex_exit(oldpick->p_lock);
               }

               if (pick != NULL) {
                       mutex_enter(pick->p_lock);
                       if (P_ZOMBIE(pick)) {
                               mutex_exit(pick->p_lock);
                               pick = NULL;
                               if (!again) {
                                       again = true;
                                       goto retry;
                               }
                               msg = "found only zombie processes\n";
                       }
                       if (pick && fromsig &&
                           (SIGACTION_PS(pick->p_sigacts, SIGINFO).sa_flags &
                           SA_NOKERNINFO)) {
                               mutex_exit(pick->p_lock);
                               return;
                       }
               }
       }

       /* Print load average. */
       tmp = (averunnable.ldavg[0] * 100 + FSCALE / 2) >> FSHIFT;
       snprintf(lmsg, sizeof(lmsg), "load: %d.%02d ", tmp / 100, tmp % 100);
       strlcat(buf, lmsg, bufsz);

       if (pick == NULL) {
               strlcat(buf, msg, bufsz);
               return;
       }

       snprintf(lmsg, sizeof(lmsg), " cmd: %s %d [", pick->p_comm,
           pick->p_pid);
       strlcat(buf, lmsg, bufsz);

       KASSERT(mutex_owned(pick->p_lock));
       LIST_FOREACH(l, &pick->p_lwps, l_sibling) {
               const char *lp;
               lwp_lock(l);
#ifdef LWP_PC
#define FMT_RUN "%#"PRIxVADDR
#define VAL_RUNNING (vaddr_t)LWP_PC(l)
#define VAL_RUNNABLE (vaddr_t)LWP_PC(l)
#else
#define FMT_RUN "%s"
#define VAL_RUNNING "running"
#define VAL_RUNNABLE "runnable"
#endif
               switch (l->l_stat) {
               case LSONPROC:
                       snprintf(lmsg, sizeof(lmsg), FMT_RUN"/%d", VAL_RUNNING,
                           cpu_index(l->l_cpu));
                       lp = lmsg;
                       break;
               case LSRUN:
                       snprintf(lmsg, sizeof(lmsg), FMT_RUN, VAL_RUNNABLE);
                       lp = lmsg;
                       break;
               default:
                       lp = l->l_wchan ? l->l_wmesg : "iowait";
                       break;
               }
               strlcat(buf, lp, bufsz);
               strlcat(buf, LIST_NEXT(l, l_sibling) != NULL ? " " : "] ",
                   bufsz);
               pctcpu += l->l_pctcpu;
               lwp_unlock(l);
       }
       pctcpu += pick->p_pctcpu;
       calcru(pick, &utime, &stime, NULL, NULL);
       mutex_exit(pick->p_lock);

       /* Round up and print user+system time, %CPU and RSS. */
       utime.tv_usec += 5000;
       if (utime.tv_usec >= 1000000) {
               utime.tv_sec += 1;
               utime.tv_usec -= 1000000;
       }
       stime.tv_usec += 5000;
       if (stime.tv_usec >= 1000000) {
               stime.tv_sec += 1;
               stime.tv_usec -= 1000000;
       }
#define pgtok(a)        (((u_long) ((a) * PAGE_SIZE) / 1024))
       tmp = (pctcpu * 10000 + FSCALE / 2) >> FSHIFT;
       if (pick->p_stat == SIDL || P_ZOMBIE(pick))
               rss = 0;
       else
               rss = pgtok(vm_resident_count(pick->p_vmspace));

       snprintf(lmsg, sizeof(lmsg), "%ld.%02ldu %ld.%02lds %d%% %ldk",
           (long)utime.tv_sec, (long)utime.tv_usec / 10000,
           (long)stime.tv_sec, (long)stime.tv_usec / 10000,
           tmp / 100, rss);
       strlcat(buf, lmsg, bufsz);
}

/*
* Print report on state of foreground process group.
* Call with tty_lock held.
*/
void
ttyputinfo(struct tty *tp, char *buf)
{

       KASSERT(mutex_owned(&tty_lock));

       if (ttycheckoutq_wlock(tp) == 0)
               return;
       ttyprintf_nolock(tp, "%s\n", buf);
       tp->t_rocount = 0;      /* so pending input will be retyped if BS */
}

/*
* Returns 1 if p2 has a better chance being the active foreground process
* in a terminal instead of p1.
*/
static int
proc_compare_wrapper(struct proc *p1, struct proc *p2)
{
       lwp_t *l1, *l2;

       KASSERT(mutex_owned(p1->p_lock));
       KASSERT(mutex_owned(p2->p_lock));

       l1 = LIST_FIRST(&p1->p_lwps);
       l2 = LIST_FIRST(&p2->p_lwps);

       return proc_compare(p1, l1, p2, l2);
}

/*
* Output char to tty; console putchar style.
* Can be called with tty lock held through kprintf() machinery..
*/
int
tputchar(int c, int flags, struct tty *tp)
{
       int r = 0;

       if ((flags & NOLOCK) == 0)
               mutex_spin_enter(&tty_lock);
       if (!CONNECTED(tp)) {
               r = -1;
               goto out;
       }
       if (c == '\n')
               (void)ttyoutput('\r', tp);
       (void)ttyoutput(c, tp);
       ttstart(tp);
out:
       if ((flags & NOLOCK) == 0)
               mutex_spin_exit(&tty_lock);
       return (r);
}

/*
* Sleep on chan, returning ERESTART if tty changed while we napped and
* returning any errors (e.g. EINTR/EWOULDBLOCK) reported by
* cv_timedwait(_sig).
* If the tty is revoked, restarting a pending call will redo validation done
* at the start of the call.
*
* Must be called with the tty lock held.
*/
int
ttysleep(struct tty *tp, kcondvar_t *cv, bool catch_p, int timo)
{
       int     error;
       short   gen;

       KASSERT(mutex_owned(&tty_lock));

       gen = tp->t_gen;
       if (ISSET(tp->t_state, TS_CANCEL))
               error = ERESTART;
       else if (cv == NULL)
               error = kpause("ttypause", catch_p, timo, &tty_lock);
       else if (catch_p)
               error = cv_timedwait_sig(cv, &tty_lock, timo);
       else
               error = cv_timedwait(cv, &tty_lock, timo);
       if (error != 0)
               return (error);
       return (tp->t_gen == gen ? 0 : ERESTART);
}

int
ttypause(struct tty *tp, int timo)
{
       int error;

       error = ttysleep(tp, NULL, true, timo);
       if (error == EWOULDBLOCK)
               error = 0;
       return error;
}

/*
* Attach a tty to the tty list.
*
* This should be called ONLY once per real tty (including pty's).
* eg, on the sparc, the keyboard and mouse have struct tty's that are
* distinctly NOT usable as tty's, and thus should not be attached to
* the ttylist.  This is why this call is not done from tty_alloc().
*
* Device drivers should attach tty's at a similar time that they are
* allocated, or, for the case of statically allocated struct tty's
* either in the attach or (first) open routine.
*/
void
tty_attach(struct tty *tp)
{

       mutex_spin_enter(&tty_lock);
       TAILQ_INSERT_TAIL(&ttylist, tp, tty_link);
       ++tty_count;
       mutex_spin_exit(&tty_lock);
}

/*
* Remove a tty from the tty list.
*/
void
tty_detach(struct tty *tp)
{

       mutex_spin_enter(&tty_lock);
       --tty_count;
#ifdef DIAGNOSTIC
       if (tty_count < 0)
               panic("tty_detach: tty_count < 0");
#endif
       TAILQ_REMOVE(&ttylist, tp, tty_link);
       mutex_spin_exit(&tty_lock);
}

/*
* Allocate a tty structure and its associated buffers.
*/
struct tty *
tty_alloc(void)
{
       struct tty *tp;
       int i;

       tp = kmem_zalloc(sizeof(*tp), KM_SLEEP);
       callout_init(&tp->t_rstrt_ch, 0);
       callout_setfunc(&tp->t_rstrt_ch, ttrstrt, tp);
       tp->t_qsize = tty_qsize;
       clalloc(&tp->t_rawq, tp->t_qsize, 1);
       cv_init(&tp->t_rawcv, "ttyraw");
       cv_init(&tp->t_rawcvf, "ttyrawf");
       clalloc(&tp->t_canq, tp->t_qsize, 1);
       cv_init(&tp->t_cancv, "ttycan");
       cv_init(&tp->t_cancvf, "ttycanf");
       /* output queue doesn't need quoting */
       clalloc(&tp->t_outq, tp->t_qsize, 0);
       cv_init(&tp->t_outcv, "ttyout");
       cv_init(&tp->t_outcvf, "ttyoutf");
       /* Set default line discipline. */
       tp->t_linesw = ttyldisc_default();
       tp->t_dev = NODEV;
       selinit(&tp->t_rsel);
       selinit(&tp->t_wsel);
       for (i = 0; i < TTYSIG_COUNT; i++)  {
               sigemptyset(&tp->t_sigs[i]);
       }

       return tp;
}

/*
* Free a tty structure and its buffers.
*
* Be sure to call tty_detach() for any tty that has been
* tty_attach()ed.
*/
void
tty_free(struct tty *tp)
{
       int i;

       mutex_enter(&proc_lock);
       mutex_enter(&tty_lock);
       for (i = 0; i < TTYSIG_COUNT; i++)
               sigemptyset(&tp->t_sigs[i]);
       if (tp->t_sigcount != 0)
               TAILQ_REMOVE(&tty_sigqueue, tp, t_sigqueue);
       mutex_exit(&tty_lock);
       mutex_exit(&proc_lock);

       callout_halt(&tp->t_rstrt_ch, NULL);
       callout_destroy(&tp->t_rstrt_ch);
       ttyldisc_release(tp->t_linesw);
       clfree(&tp->t_rawq);
       clfree(&tp->t_canq);
       clfree(&tp->t_outq);
       cv_destroy(&tp->t_rawcv);
       cv_destroy(&tp->t_rawcvf);
       cv_destroy(&tp->t_cancv);
       cv_destroy(&tp->t_cancvf);
       cv_destroy(&tp->t_outcv);
       cv_destroy(&tp->t_outcvf);
       seldestroy(&tp->t_rsel);
       seldestroy(&tp->t_wsel);
       kmem_free(tp, sizeof(*tp));
}

/*
* tty_unit: map dev_t to tty unit number, as with TTUNIT
*
* => defined as function for use with struct cdevsw::d_devtounit
* => not for drivers with different unit numbering, e.g. TTUNIT(d) >> 4
*/
int
tty_unit(dev_t dev)
{
       return TTUNIT(dev);
}

/*
* ttyprintf_nolock: send a message to a specific tty, without locking.
*
* => should be used only by tty driver or anything that knows the
*    underlying tty will not be revoked(2)'d away.  [otherwise,
*    use tprintf]
*/
static void
ttyprintf_nolock(struct tty *tp, const char *fmt, ...)
{
       va_list ap;

       /* No mutex needed; going to process TTY. */
       va_start(ap, fmt);
       kprintf(fmt, TOTTY|NOLOCK, tp, NULL, ap);
       va_end(ap);
}

static int
tty_listener_cb(kauth_cred_t cred, kauth_action_t action, void *cookie,
   void *arg0, void *arg1, void *arg2, void *arg3)
{
       struct tty *tty;
       int result;

       result = KAUTH_RESULT_DEFER;

       if (action != KAUTH_DEVICE_TTY_OPEN)
               return result;

       tty = arg0;

       /* If it's not opened, we allow. */
       if ((tty->t_state & TS_ISOPEN) == 0)
               result = KAUTH_RESULT_ALLOW;
       else {
               /*
                * If it's opened, we can only allow if it's not exclusively
                * opened; otherwise, that's a privileged operation and we
                * let the secmodel handle it.
                */
               if ((tty->t_state & TS_XCLUDE) == 0)
                       result = KAUTH_RESULT_ALLOW;
       }

       return result;
}

/*
* Initialize the tty subsystem.
*/
void
tty_init(void)
{

       mutex_init(&tty_lock, MUTEX_DEFAULT, IPL_VM);
       mutex_init(&constty_lock, MUTEX_DEFAULT, IPL_NONE);
       constty_psz = pserialize_create();
       cv_init(&ttyref_cv, "ttyref");
       tty_sigsih = softint_establish(SOFTINT_CLOCK, ttysigintr, NULL);
       KASSERT(tty_sigsih != NULL);

       tty_listener = kauth_listen_scope(KAUTH_SCOPE_DEVICE,
           tty_listener_cb, NULL);

       sysctl_kern_tty_setup();
}

/*
* Send a signal from a tty to its process group or session leader.
* Handoff to the target is deferred to a soft interrupt.
*/
void
ttysig(struct tty *tp, enum ttysigtype st, int sig)
{
       sigset_t *sp;

       /* XXXSMP not yet KASSERT(mutex_owned(&tty_lock)); */

       sp = &tp->t_sigs[st];
       if (sigismember(sp, sig))
               return;
       sigaddset(sp, sig);
       if (tp->t_sigcount++ == 0)
               TAILQ_INSERT_TAIL(&tty_sigqueue, tp, t_sigqueue);
       softint_schedule(tty_sigsih);
}

/*
* Deliver deferred signals from ttys.  Note that the process groups
* and sessions associated with the ttys may have changed from when
* the signal was originally sent, but in practice it should not matter.
* For signals produced as a result of a syscall, the soft interrupt
* will fire before the syscall returns to the user.
*/
static void
ttysigintr(void *cookie)
{
       struct tty *tp;
       enum ttysigtype st;
       struct pgrp *pgrp;
       struct session *sess;
       int sig, lflag;
       char infobuf[200];

       mutex_enter(&proc_lock);
       mutex_spin_enter(&tty_lock);
       while ((tp = TAILQ_FIRST(&tty_sigqueue)) != NULL) {
               KASSERT(tp->t_sigcount > 0);
               for (st = TTYSIG_PG1; st < TTYSIG_COUNT; st++) {
                       if ((sig = firstsig(&tp->t_sigs[st])) != 0)
                               break;
               }
               KASSERT(st < TTYSIG_COUNT);
               sigdelset(&tp->t_sigs[st], sig);
               if (--tp->t_sigcount == 0)
                       TAILQ_REMOVE(&tty_sigqueue, tp, t_sigqueue);
               pgrp = tp->t_pgrp;
               sess = tp->t_session;
               lflag = tp->t_lflag;
               if (sig == SIGINFO) {
                       if (ISSET(tp->t_state, TS_SIGINFO)) {
                               /* Via ioctl: ignore tty option. */
                               tp->t_state &= ~TS_SIGINFO;
                               lflag |= ISIG;
                       }
                       if (!ISSET(lflag, NOKERNINFO)) {
                               mutex_spin_exit(&tty_lock);
                               ttygetinfo(tp, 1, infobuf, sizeof(infobuf));
                               mutex_spin_enter(&tty_lock);
                               ttyputinfo(tp, infobuf);
                       }
                       if (!ISSET(lflag, ISIG))
                               continue;
               }
               mutex_spin_exit(&tty_lock);
               KASSERT(sig != 0);
               switch (st) {
               case TTYSIG_PG1:
                       if (pgrp != NULL)
                               pgsignal(pgrp, sig, 1);
                       break;
               case TTYSIG_PG2:
                       if (pgrp != NULL)
                               pgsignal(pgrp, sig, sess != NULL);
                       break;
               case TTYSIG_LEADER:
                       if (sess != NULL && sess->s_leader != NULL)
                               psignal(sess->s_leader, sig);
                       break;
               default:
                       /* NOTREACHED */
                       break;
               }
               mutex_spin_enter(&tty_lock);
       }
       mutex_spin_exit(&tty_lock);
       mutex_exit(&proc_lock);
}

unsigned char
tty_getctrlchar(struct tty *tp, unsigned which)
{
       KASSERT(which < NCCS);
       return tp->t_cc[which];
}

void
tty_setctrlchar(struct tty *tp, unsigned which, unsigned char val)
{
       KASSERT(which < NCCS);
       tp->t_cc[which] = val;
}

int
tty_try_xonxoff(struct tty *tp, unsigned char c)
{
   const struct cdevsw *cdev;

   if (tp->t_iflag & IXON) {
       if (c == tp->t_cc[VSTOP] && tp->t_cc[VSTOP] != _POSIX_VDISABLE) {
           if ((tp->t_state & TS_TTSTOP) == 0) {
               tp->t_state |= TS_TTSTOP;
               cdev = cdevsw_lookup(tp->t_dev);
               if (cdev != NULL)
                       (*cdev->d_stop)(tp, 0);
           }
           return 0;
       }
       if (c == tp->t_cc[VSTART] && tp->t_cc[VSTART] != _POSIX_VDISABLE) {
           tp->t_state &= ~TS_TTSTOP;
           if (tp->t_oproc != NULL) {
               mutex_spin_enter(&tty_lock);    /* XXX */
               (*tp->t_oproc)(tp);
               mutex_spin_exit(&tty_lock);     /* XXX */
           }
           return 0;
       }
   }
   return EAGAIN;
}