diff options
Diffstat (limited to 'sys/kern')
-rw-r--r-- | sys/kern/init_main.c | 2 | ||||
-rw-r--r-- | sys/kern/init_sysent.c | 3 | ||||
-rw-r--r-- | sys/kern/kern_acct.c | 6 | ||||
-rw-r--r-- | sys/kern/kern_conf.c | 11 | ||||
-rw-r--r-- | sys/kern/kern_descrip.c | 12 | ||||
-rw-r--r-- | sys/kern/kern_exit.c | 62 | ||||
-rw-r--r-- | sys/kern/kern_proc.c | 59 | ||||
-rw-r--r-- | sys/kern/kern_resource.c | 25 | ||||
-rw-r--r-- | sys/kern/subr_prf.c | 24 | ||||
-rw-r--r-- | sys/kern/syscalls.c | 3 | ||||
-rw-r--r-- | sys/kern/syscalls.master | 1 | ||||
-rw-r--r-- | sys/kern/systrace_args.c | 27 | ||||
-rw-r--r-- | sys/kern/tty.c | 4182 | ||||
-rw-r--r-- | sys/kern/tty_compat.c | 55 | ||||
-rw-r--r-- | sys/kern/tty_conf.c | 205 | ||||
-rw-r--r-- | sys/kern/tty_cons.c | 15 | ||||
-rw-r--r-- | sys/kern/tty_info.c | 14 | ||||
-rw-r--r-- | sys/kern/tty_inq.c | 502 | ||||
-rw-r--r-- | sys/kern/tty_outq.c | 365 | ||||
-rw-r--r-- | sys/kern/tty_pts.c | 1303 | ||||
-rw-r--r-- | sys/kern/tty_pty.c | 810 | ||||
-rw-r--r-- | sys/kern/tty_ttydisc.c | 1131 |
22 files changed, 4121 insertions, 4696 deletions
diff --git a/sys/kern/init_main.c b/sys/kern/init_main.c index f726d27..3a51967 100644 --- a/sys/kern/init_main.c +++ b/sys/kern/init_main.c @@ -415,7 +415,7 @@ proc0_init(void *dummy __unused) pgrp0.pg_session = &session0; mtx_init(&session0.s_mtx, "session", NULL, MTX_DEF); - session0.s_count = 1; + refcount_init(&session0.s_count, 1); session0.s_leader = p; p->p_sysent = &null_sysvec; diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c index 22db8a8..1dcfbab 100644 --- a/sys/kern/init_sysent.c +++ b/sys/kern/init_sysent.c @@ -3,7 +3,7 @@ * * DO NOT EDIT-- this file is automatically generated. * $FreeBSD$ - * created from FreeBSD: src/sys/kern/syscalls.master,v 1.242 2008/03/31 12:06:55 kib Exp + * created from FreeBSD: head/sys/kern/syscalls.master 178888 2008-05-09 23:03:00Z julian */ #include "opt_compat.h" @@ -532,4 +532,5 @@ struct sysent sysent[] = { { AS(renameat_args), (sy_call_t *)renameat, AUE_RENAMEAT, NULL, 0, 0 }, /* 501 = renameat */ { AS(symlinkat_args), (sy_call_t *)symlinkat, AUE_SYMLINKAT, NULL, 0, 0 }, /* 502 = symlinkat */ { AS(unlinkat_args), (sy_call_t *)unlinkat, AUE_UNLINKAT, NULL, 0, 0 }, /* 503 = unlinkat */ + { AS(posix_openpt_args), (sy_call_t *)posix_openpt, AUE_POSIXOPENPT, NULL, 0, 0 }, /* 504 = posix_openpt */ }; diff --git a/sys/kern/kern_acct.c b/sys/kern/kern_acct.c index 76d4c75..e505f62 100644 --- a/sys/kern/kern_acct.c +++ b/sys/kern/kern_acct.c @@ -402,12 +402,12 @@ acct_process(struct thread *td) acct.ac_gid = p->p_ucred->cr_rgid; /* (7) The terminal from which the process was started */ - SESS_LOCK(p->p_session); + sx_slock(&proctree_lock); if ((p->p_flag & P_CONTROLT) && p->p_pgrp->pg_session->s_ttyp) - acct.ac_tty = dev2udev(p->p_pgrp->pg_session->s_ttyp->t_dev); + acct.ac_tty = tty_udev(p->p_pgrp->pg_session->s_ttyp); else acct.ac_tty = NODEV; - SESS_UNLOCK(p->p_session); + sx_sunlock(&proctree_lock); /* (8) The boolean flags that tell how the process terminated, etc. */ acct.ac_flagx = p->p_acflag; diff --git a/sys/kern/kern_conf.c b/sys/kern/kern_conf.c index 4c10871..409652f 100644 --- a/sys/kern/kern_conf.c +++ b/sys/kern/kern_conf.c @@ -42,7 +42,6 @@ __FBSDID("$FreeBSD$"); #include <sys/poll.h> #include <sys/sx.h> #include <sys/ctype.h> -#include <sys/tty.h> #include <sys/ucred.h> #include <sys/taskqueue.h> #include <machine/stdarg.h> @@ -611,14 +610,6 @@ prep_cdevsw(struct cdevsw *devsw) devsw->d_kqfilter = dead_kqfilter; } - if (devsw->d_flags & D_TTY) { - if (devsw->d_ioctl == NULL) devsw->d_ioctl = ttyioctl; - if (devsw->d_read == NULL) devsw->d_read = ttyread; - if (devsw->d_write == NULL) devsw->d_write = ttywrite; - if (devsw->d_kqfilter == NULL) devsw->d_kqfilter = ttykqfilter; - if (devsw->d_poll == NULL) devsw->d_poll = ttypoll; - } - if (devsw->d_flags & D_NEEDGIANT) { if (devsw->d_gianttrick == NULL) { memcpy(dsw2, devsw, sizeof *dsw2); @@ -692,11 +683,9 @@ make_dev_credv(int flags, struct cdevsw *devsw, int minornr, } dev->si_flags |= SI_NAMED; -#ifdef MAC if (cr != NULL) dev->si_cred = crhold(cr); else -#endif dev->si_cred = NULL; dev->si_uid = uid; dev->si_gid = gid; diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c index b7a486b..3c0858c 100644 --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -70,6 +70,7 @@ __FBSDID("$FreeBSD$"); #include <sys/syscallsubr.h> #include <sys/sysctl.h> #include <sys/sysproto.h> +#include <sys/tty.h> #include <sys/unistd.h> #include <sys/user.h> #include <sys/vnode.h> @@ -2564,6 +2565,7 @@ sysctl_kern_proc_filedesc(SYSCTL_HANDLER_ARGS) struct vnode *vp; struct file *fp; struct proc *p; + struct tty *tp; int vfslocked; name = (int *)arg1; @@ -2595,6 +2597,7 @@ sysctl_kern_proc_filedesc(SYSCTL_HANDLER_ARGS) kif->kf_structsize = sizeof(*kif); vp = NULL; so = NULL; + tp = NULL; kif->kf_fd = i; switch (fp->f_type) { case DTYPE_VNODE: @@ -2637,6 +2640,11 @@ sysctl_kern_proc_filedesc(SYSCTL_HANDLER_ARGS) kif->kf_type = KF_TYPE_SEM; break; + case DTYPE_PTS: + kif->kf_type = KF_TYPE_PTS; + tp = fp->f_data; + break; + default: kif->kf_type = KF_TYPE_UNKNOWN; break; @@ -2730,6 +2738,10 @@ sysctl_kern_proc_filedesc(SYSCTL_HANDLER_ARGS) kif->kf_sock_type = so->so_type; kif->kf_sock_protocol = so->so_proto->pr_protocol; } + if (tp != NULL) { + strlcpy(kif->kf_path, tty_devname(tp), + sizeof(kif->kf_path)); + } error = SYSCTL_OUT(req, kif, sizeof(*kif)); if (error) break; diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c index 9e53316..f0866ed 100644 --- a/sys/kern/kern_exit.c +++ b/sys/kern/kern_exit.c @@ -119,9 +119,8 @@ void exit1(struct thread *td, int rv) { struct proc *p, *nq, *q; - struct tty *tp; - struct vnode *ttyvp; struct vnode *vtmp; + struct vnode *ttyvp = NULL; #ifdef KTRACE struct vnode *tracevp; struct ucred *tracecred; @@ -298,56 +297,48 @@ exit1(struct thread *td, int rv) vmspace_exit(td); - mtx_lock(&Giant); /* XXX TTY */ sx_xlock(&proctree_lock); if (SESS_LEADER(p)) { struct session *sp; sp = p->p_session; - if (sp->s_ttyvp) { + + SESS_LOCK(sp); + ttyvp = sp->s_ttyvp; + sp->s_ttyvp = NULL; + SESS_UNLOCK(sp); + + if (ttyvp != NULL) { /* * Controlling process. - * Signal foreground pgrp, - * drain controlling terminal - * and revoke access to controlling terminal. + * Signal foreground pgrp and revoke access to + * controlling terminal. + * + * There is no need to drain the terminal here, + * because this will be done on revocation. */ - if (sp->s_ttyp && (sp->s_ttyp->t_session == sp)) { - tp = sp->s_ttyp; - if (sp->s_ttyp->t_pgrp) { - PGRP_LOCK(sp->s_ttyp->t_pgrp); - pgsignal(sp->s_ttyp->t_pgrp, SIGHUP, 1); - PGRP_UNLOCK(sp->s_ttyp->t_pgrp); - } - /* XXX tp should be locked. */ - sx_xunlock(&proctree_lock); - (void) ttywait(tp); - sx_xlock(&proctree_lock); + if (sp->s_ttyp != NULL) { + struct tty *tp = sp->s_ttyp; + + tty_lock(tp); + tty_signal_pgrp(tp, SIGHUP); + tty_unlock(tp); + /* * The tty could have been revoked * if we blocked. */ - if (sp->s_ttyvp) { - ttyvp = sp->s_ttyvp; - SESS_LOCK(p->p_session); - sp->s_ttyvp = NULL; - SESS_UNLOCK(p->p_session); + if (ttyvp->v_type != VBAD) { sx_xunlock(&proctree_lock); VOP_LOCK(ttyvp, LK_EXCLUSIVE); VOP_REVOKE(ttyvp, REVOKEALL); - vput(ttyvp); + VOP_UNLOCK(ttyvp, 0); sx_xlock(&proctree_lock); } } - if (sp->s_ttyvp) { - ttyvp = sp->s_ttyvp; - SESS_LOCK(p->p_session); - sp->s_ttyvp = NULL; - SESS_UNLOCK(p->p_session); - vrele(ttyvp); - } /* - * s_ttyp is not zero'd; we use this to indicate - * that the session once had a controlling terminal. + * s_ttyp is not zero'd; we use this to indicate that + * the session once had a controlling terminal. * (for logging and informational purposes) */ } @@ -358,7 +349,10 @@ exit1(struct thread *td, int rv) fixjobc(p, p->p_pgrp, 0); sx_xunlock(&proctree_lock); (void)acct_process(td); - mtx_unlock(&Giant); + + /* Release the TTY now we've unlocked everything. */ + if (ttyvp != NULL) + vrele(ttyvp); #ifdef KTRACE /* * Disable tracing, then drain any pending records and release diff --git a/sys/kern/kern_proc.c b/sys/kern/kern_proc.c index 34ffaf1..25820ea 100644 --- a/sys/kern/kern_proc.c +++ b/sys/kern/kern_proc.c @@ -352,14 +352,13 @@ enterpgrp(p, pgid, pgrp, sess) * new session */ mtx_init(&sess->s_mtx, "session", NULL, MTX_DEF); - mtx_lock(&Giant); /* XXX TTY */ PROC_LOCK(p); p->p_flag &= ~P_CONTROLT; PROC_UNLOCK(p); PGRP_LOCK(pgrp); sess->s_leader = p; sess->s_sid = p->p_pid; - sess->s_count = 1; + refcount_init(&sess->s_count, 1); sess->s_ttyvp = NULL; sess->s_ttyp = NULL; bcopy(p->p_session->s_login, sess->s_login, @@ -368,11 +367,8 @@ enterpgrp(p, pgid, pgrp, sess) KASSERT(p == curproc, ("enterpgrp: mksession and p != curproc")); } else { - mtx_lock(&Giant); /* XXX TTY */ pgrp->pg_session = p->p_session; - SESS_LOCK(pgrp->pg_session); - pgrp->pg_session->s_count++; - SESS_UNLOCK(pgrp->pg_session); + sess_hold(pgrp->pg_session); PGRP_LOCK(pgrp); } pgrp->pg_id = pgid; @@ -386,7 +382,6 @@ enterpgrp(p, pgid, pgrp, sess) pgrp->pg_jobc = 0; SLIST_INIT(&pgrp->pg_sigiolst); PGRP_UNLOCK(pgrp); - mtx_unlock(&Giant); /* XXX TTY */ doenterpgrp(p, pgrp); @@ -446,7 +441,6 @@ doenterpgrp(p, pgrp) fixjobc(p, pgrp, 1); fixjobc(p, p->p_pgrp, 0); - mtx_lock(&Giant); /* XXX TTY */ PGRP_LOCK(pgrp); PGRP_LOCK(savepgrp); PROC_LOCK(p); @@ -456,7 +450,6 @@ doenterpgrp(p, pgrp) LIST_INSERT_HEAD(&pgrp->pg_members, p, p_pglist); PGRP_UNLOCK(savepgrp); PGRP_UNLOCK(pgrp); - mtx_unlock(&Giant); /* XXX TTY */ if (LIST_EMPTY(&savepgrp->pg_members)) pgdelete(savepgrp); } @@ -472,14 +465,12 @@ leavepgrp(p) sx_assert(&proctree_lock, SX_XLOCKED); savepgrp = p->p_pgrp; - mtx_lock(&Giant); /* XXX TTY */ PGRP_LOCK(savepgrp); PROC_LOCK(p); LIST_REMOVE(p, p_pglist); p->p_pgrp = NULL; PROC_UNLOCK(p); PGRP_UNLOCK(savepgrp); - mtx_unlock(&Giant); /* XXX TTY */ if (LIST_EMPTY(&savepgrp->pg_members)) pgdelete(savepgrp); return (0); @@ -493,6 +484,7 @@ pgdelete(pgrp) register struct pgrp *pgrp; { struct session *savesess; + struct tty *tp; sx_assert(&proctree_lock, SX_XLOCKED); PGRP_LOCK_ASSERT(pgrp, MA_NOTOWNED); @@ -504,18 +496,22 @@ pgdelete(pgrp) */ funsetownlst(&pgrp->pg_sigiolst); - mtx_lock(&Giant); /* XXX TTY */ PGRP_LOCK(pgrp); - if (pgrp->pg_session->s_ttyp != NULL && - pgrp->pg_session->s_ttyp->t_pgrp == pgrp) - pgrp->pg_session->s_ttyp->t_pgrp = NULL; + tp = pgrp->pg_session->s_ttyp; LIST_REMOVE(pgrp, pg_hash); savesess = pgrp->pg_session; - SESSRELE(savesess); PGRP_UNLOCK(pgrp); + + /* Remove the reference to the pgrp before deallocating it. */ + if (tp != NULL) { + tty_lock(tp); + tty_rel_pgrp(tp, pgrp); + tty_unlock(tp); + } + mtx_destroy(&pgrp->pg_mtx); FREE(pgrp, M_PGRP); - mtx_unlock(&Giant); /* XXX TTY */ + sess_release(savesess); } static void @@ -618,16 +614,21 @@ orphanpg(pg) } void -sessrele(struct session *s) +sess_hold(struct session *s) { - int i; - - SESS_LOCK(s); - i = --s->s_count; - SESS_UNLOCK(s); - if (i == 0) { - if (s->s_ttyp != NULL) - ttyrel(s->s_ttyp); + + refcount_acquire(&s->s_count); +} + +void +sess_release(struct session *s) +{ + + if (refcount_release(&s->s_count)) { + if (s->s_ttyp != NULL) { + tty_lock(s->s_ttyp); + tty_rel_sess(s->s_ttyp, s); + } mtx_destroy(&s->s_mtx); FREE(s, M_SESSION); } @@ -779,12 +780,13 @@ fill_kinfo_proc_only(struct proc *p, struct kinfo_proc *kp) kp->ki_kiflag |= KI_CTTY; if (SESS_LEADER(p)) kp->ki_kiflag |= KI_SLEADER; + /* XXX proctree_lock */ tp = sp->s_ttyp; SESS_UNLOCK(sp); } } if ((p->p_flag & P_CONTROLT) && tp != NULL) { - kp->ki_tdev = dev2udev(tp->t_dev); + kp->ki_tdev = tty_udev(tp); kp->ki_tpgid = tp->t_pgrp ? tp->t_pgrp->pg_id : NO_PID; if (tp->t_session) kp->ki_tsid = tp->t_session->s_sid; @@ -1122,9 +1124,10 @@ sysctl_kern_proc(SYSCTL_HANDLER_ARGS) PROC_UNLOCK(p); continue; } + /* XXX proctree_lock */ SESS_LOCK(p->p_session); if (p->p_session->s_ttyp == NULL || - dev2udev(p->p_session->s_ttyp->t_dev) != + tty_udev(p->p_session->s_ttyp) != (dev_t)name[0]) { SESS_UNLOCK(p->p_session); PROC_UNLOCK(p); diff --git a/sys/kern/kern_resource.c b/sys/kern/kern_resource.c index 8267c29..9a15319 100644 --- a/sys/kern/kern_resource.c +++ b/sys/kern/kern_resource.c @@ -1329,3 +1329,28 @@ chgsbsize(uip, hiwat, to, max) *hiwat = to; return (1); } + +/* + * Change the count associated with number of pseudo-terminals + * a given user is using. When 'max' is 0, don't enforce a limit + */ +int +chgptscnt(uip, diff, max) + struct uidinfo *uip; + int diff; + rlim_t max; +{ + + /* Don't allow them to exceed max, but allow subtraction. */ + if (diff > 0 && max != 0) { + if (atomic_fetchadd_long(&uip->ui_ptscnt, (long)diff) + diff > max) { + atomic_subtract_long(&uip->ui_ptscnt, (long)diff); + return (0); + } + } else { + atomic_add_long(&uip->ui_ptscnt, (long)diff); + if (uip->ui_ptscnt < 0) + printf("negative ptscnt for uid = %d\n", uip->ui_uid); + } + return (1); +} diff --git a/sys/kern/subr_prf.c b/sys/kern/subr_prf.c index a0605ab..dea4034 100644 --- a/sys/kern/subr_prf.c +++ b/sys/kern/subr_prf.c @@ -136,7 +136,7 @@ uprintf(const char *fmt, ...) if (td == NULL || TD_IS_IDLETHREAD(td)) return (0); - mtx_lock(&Giant); + sx_slock(&proctree_lock); p = td->td_proc; PROC_LOCK(p); if ((p->p_flag & P_CONTROLT) == 0) { @@ -154,10 +154,12 @@ uprintf(const char *fmt, ...) } pca.flags = TOTTY; va_start(ap, fmt); + tty_lock(pca.tty); retval = kvprintf(fmt, putchar, &pca, 10, ap); + tty_unlock(pca.tty); va_end(ap); out: - mtx_unlock(&Giant); + sx_sunlock(&proctree_lock); return (retval); } @@ -174,19 +176,17 @@ tprintf(struct proc *p, int pri, const char *fmt, ...) struct putchar_arg pca; struct session *sess = NULL; - mtx_lock(&Giant); + sx_slock(&proctree_lock); if (pri != -1) flags |= TOLOG; if (p != NULL) { PROC_LOCK(p); if (p->p_flag & P_CONTROLT && p->p_session->s_ttyvp) { sess = p->p_session; - SESS_LOCK(sess); + sess_hold(sess); PROC_UNLOCK(p); - SESSHOLD(sess); tp = sess->s_ttyp; - SESS_UNLOCK(sess); - if (ttycheckoutq(tp, 0)) + if (tp != NULL && tty_checkoutq(tp)) flags |= TOTTY; else tp = NULL; @@ -197,12 +197,16 @@ tprintf(struct proc *p, int pri, const char *fmt, ...) pca.tty = tp; pca.flags = flags; va_start(ap, fmt); + if (pca.tty != NULL) + tty_lock(pca.tty); kvprintf(fmt, putchar, &pca, 10, ap); + if (pca.tty != NULL) + tty_unlock(pca.tty); va_end(ap); if (sess != NULL) - SESSRELE(sess); + sess_release(sess); msgbuftrigger = 1; - mtx_unlock(&Giant); + sx_sunlock(&proctree_lock); } /* @@ -413,7 +417,7 @@ putchar(int c, void *arg) putcons(c, ap); } else { if ((flags & TOTTY) && tp != NULL) - tputchar(c, tp); + tty_putchar(tp, c); if (flags & TOCONS) { if (constty != NULL) msgbuf_addchar(&consmsgbuf, c); diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c index 8fb0127..0a057ce 100644 --- a/sys/kern/syscalls.c +++ b/sys/kern/syscalls.c @@ -3,7 +3,7 @@ * * DO NOT EDIT-- this file is automatically generated. * $FreeBSD$ - * created from FreeBSD: src/sys/kern/syscalls.master,v 1.242 2008/03/31 12:06:55 kib Exp + * created from FreeBSD: head/sys/kern/syscalls.master 178888 2008-05-09 23:03:00Z julian */ const char *syscallnames[] = { @@ -511,4 +511,5 @@ const char *syscallnames[] = { "renameat", /* 501 = renameat */ "symlinkat", /* 502 = symlinkat */ "unlinkat", /* 503 = unlinkat */ + "posix_openpt", /* 504 = posix_openpt */ }; diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master index 4cb55fa..8ed20d4 100644 --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -886,5 +886,6 @@ 502 AUE_SYMLINKAT STD { int symlinkat(char *path1, int fd, \ char *path2); } 503 AUE_UNLINKAT STD { int unlinkat(int fd, char *path, int flag); } +504 AUE_POSIXOPENPT STD { int posix_openpt(int flags); } ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master diff --git a/sys/kern/systrace_args.c b/sys/kern/systrace_args.c index 98558cc..097bf21 100644 --- a/sys/kern/systrace_args.c +++ b/sys/kern/systrace_args.c @@ -3054,6 +3054,13 @@ systrace_args(int sysnum, void *params, u_int64_t *uarg, int *n_args) *n_args = 3; break; } + /* posix_openpt */ + case 504: { + struct posix_openpt_args *p = params; + iarg[0] = p->flags; /* int */ + *n_args = 1; + break; + } default: *n_args = 0; break; @@ -4607,6 +4614,16 @@ systrace_setargdesc(int sysnum, int ndx, char *desc, size_t descsz) break; }; break; + /* setfib */ + case 175: + switch(ndx) { + case 0: + p = "int"; + break; + default: + break; + }; + break; /* ntp_adjtime */ case 176: switch(ndx) { @@ -8093,6 +8110,16 @@ systrace_setargdesc(int sysnum, int ndx, char *desc, size_t descsz) break; }; break; + /* posix_openpt */ + case 504: + switch(ndx) { + case 0: + p = "int"; + break; + default: + break; + }; + break; default: break; }; diff --git a/sys/kern/tty.c b/sys/kern/tty.c index e7818b8..af12da3 100644 --- a/sys/kern/tty.c +++ b/sys/kern/tty.c @@ -1,19 +1,9 @@ /*- - * 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. - * - * Copyright (c) 2002 Networks Associates Technologies, Inc. + * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> * All rights reserved. * - * Portions of this software were developed for the FreeBSD Project by - * ThinkSec AS and NAI Labs, the Security Research Division of Network - * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 - * ("CBOSS"), as part of the DARPA CHATS research program. + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -23,14 +13,11 @@ * 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. - * 4. 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 + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 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) @@ -38,798 +25,455 @@ * 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.8 (Berkeley) 1/21/94 - */ - -/*- - * TODO: - * o Fix races for sending the start char in ttyflush(). - * o Handle inter-byte timeout for "MIN > 0, TIME > 0" in ttyselect(). - * With luck, there will be MIN chars before select() returns(). - * o Handle CLOCAL consistently for ptys. Perhaps disallow setting it. - * o Don't allow input in TS_ZOMBIE case. It would be visible through - * FIONREAD. - * o Do the new sio locking stuff here and use it to avoid special - * case for EXTPROC? - * o Lock PENDIN too? - * o Move EXTPROC and/or PENDIN to t_state? - * o Wrap most of ttioctl in spltty/splx. - * o Implement TIOCNOTTY or remove it from <sys/ioctl.h>. - * o Send STOP if IXOFF is toggled off while TS_TBLOCK is set. - * o Don't allow certain termios flags to affect disciplines other - * than TTYDISC. Cancel their effects before switch disciplines - * and ignore them if they are set while we are in another - * discipline. - * o Now that historical speed conversions are handled here, don't - * do them in drivers. - * o Check for TS_CARR_ON being set while everything is closed and not - * waiting for carrier. TS_CARR_ON isn't cleared if nothing is open, - * so it would live until the next open even if carrier drops. - * o Restore TS_WOPEN since it is useful in pstat. It must be cleared - * only when _all_ openers leave open(). */ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); #include "opt_compat.h" -#include "opt_tty.h" #include <sys/param.h> -#include <sys/systm.h> +#include <sys/conf.h> #include <sys/cons.h> +#include <sys/fcntl.h> #include <sys/filio.h> -#include <sys/lock.h> -#include <sys/mutex.h> -#include <sys/namei.h> -#include <sys/sx.h> -#if defined(COMPAT_43TTY) +#ifdef COMPAT_43TTY #include <sys/ioctl_compat.h> -#endif +#endif /* COMPAT_43TTY */ +#include <sys/kernel.h> +#include <sys/limits.h> +#include <sys/malloc.h> +#include <sys/mount.h> +#include <sys/namei.h> +#include <sys/poll.h> #include <sys/priv.h> #include <sys/proc.h> -#define TTYDEFCHARS -#include <sys/tty.h> -#undef TTYDEFCHARS -#include <sys/fcntl.h> -#include <sys/conf.h> -#include <sys/poll.h> -#include <sys/kernel.h> -#include <sys/vnode.h> #include <sys/serial.h> -#include <sys/signalvar.h> -#include <sys/malloc.h> -#include <sys/filedesc.h> +#include <sys/signal.h> +#include <sys/stat.h> +#include <sys/sx.h> #include <sys/sysctl.h> -#include <sys/timepps.h> +#include <sys/systm.h> +#include <sys/tty.h> +#include <sys/ttycom.h> +#define TTYDEFCHARS +#include <sys/ttydefaults.h> +#undef TTYDEFCHARS +#include <sys/ucred.h> +#include <sys/vnode.h> #include <machine/stdarg.h> -MALLOC_DEFINE(M_TTYS, "ttys", "tty data structures"); - -long tk_cancc; -long tk_nin; -long tk_nout; -long tk_rawcc; - -static d_open_t ttysopen; -static d_close_t ttysclose; -static d_read_t ttysrdwr; -static d_ioctl_t ttysioctl; -static d_purge_t ttypurge; - -/* Default cdevsw for common tty devices */ -static struct cdevsw tty_cdevsw = { - .d_version = D_VERSION, - .d_open = ttyopen, - .d_close = ttyclose, - .d_ioctl = ttyioctl, - .d_purge = ttypurge, - .d_name = "ttydrv", - .d_flags = D_TTY | D_NEEDGIANT, -}; +static MALLOC_DEFINE(M_TTY, "tty", "tty device"); -/* Cdevsw for slave tty devices */ -static struct cdevsw ttys_cdevsw = { - .d_version = D_VERSION, - .d_open = ttysopen, - .d_close = ttysclose, - .d_read = ttysrdwr, - .d_write = ttysrdwr, - .d_ioctl = ttysioctl, - .d_name = "TTYS", - .d_flags = D_TTY | D_NEEDGIANT, -}; +static void tty_rel_free(struct tty *tp); -static int ttnread(struct tty *tp); -static void ttyecho(int c, struct tty *tp); -static int ttyoutput(int c, struct tty *tp); -static void ttypend(struct tty *tp); -static void ttyretype(struct tty *tp); -static void ttyrub(int c, struct tty *tp); -static void ttyrubo(struct tty *tp, int cnt); -static void ttyunblock(struct tty *tp); -static int ttywflush(struct tty *tp); -static int filt_ttyread(struct knote *kn, long hint); -static void filt_ttyrdetach(struct knote *kn); -static int filt_ttywrite(struct knote *kn, long hint); -static void filt_ttywdetach(struct knote *kn); +static TAILQ_HEAD(, tty) tty_list = TAILQ_HEAD_INITIALIZER(tty_list); +static struct sx tty_list_sx; +SX_SYSINIT(tty_list, &tty_list_sx, "tty list"); +static unsigned int tty_list_count = 0; /* - * 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. + * Flags that are supported and stored by this implementation. */ -#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 - -static u_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 - -/* Macros to clear/set/test flags. */ -#define SET(t, f) (t) |= (f) -#define CLR(t, f) (t) &= ~(f) -#define ISSET(t, f) ((t) & (f)) - -#undef MAX_INPUT /* XXX wrong in <sys/syslimits.h> */ -#define MAX_INPUT TTYHOG /* XXX limit is usually larger for !ICANON */ +#define TTYSUP_IFLAG (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|\ + INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY|IMAXBEL) +#define TTYSUP_OFLAG (OPOST|ONLCR|TAB3|ONOEOT|OCRNL|ONOCR|ONLRET) +#define TTYSUP_LFLAG (ECHOKE|ECHOE|ECHOK|ECHO|ECHONL|ECHOPRT|\ + ECHOCTL|ISIG|ICANON|ALTWERASE|IEXTEN|TOSTOP|\ + FLUSHO|NOKERNINFO|NOFLSH) +#define TTYSUP_CFLAG (CIGNORE|CSIZE|CSTOPB|CREAD|PARENB|PARODD|\ + HUPCL|CLOCAL|CCTS_OFLOW|CRTS_IFLOW|CDTR_IFLOW|\ + CDSR_OFLOW|CCAR_OFLOW) + +#define TTY_CALLOUT(tp,d) ((tp)->t_dev != (d)) /* - * list of struct tty where pstat(8) can pick it up with sysctl - * - * The lock order is to grab the list mutex before the tty mutex. - * Together with additions going on the tail of the list, this allows - * the sysctl to avoid doing retries. + * Set TTY buffer sizes. */ -static TAILQ_HEAD(, tty) tty_list = TAILQ_HEAD_INITIALIZER(tty_list); -static struct mtx tty_list_mutex; -MTX_SYSINIT(tty_list, &tty_list_mutex, "ttylist", MTX_DEF); -static struct unrhdr *tty_unit; +static void +tty_watermarks(struct tty *tp) +{ + speed_t sp; + + /* Provide an input buffer for 0.2 seconds of data. */ + sp = MAX(tp->t_termios.c_ispeed, 0); + ttyinq_setsize(&tp->t_inq, tp, sp / 5); + + /* Set low watermark at 10% (when 90% is available). */ + tp->t_inlow = (ttyinq_getsize(&tp->t_inq) * 9) / 10; -static int drainwait = 5*60; -SYSCTL_INT(_kern, OID_AUTO, drainwait, CTLFLAG_RW, &drainwait, - 0, "Output drain timeout in seconds"); + /* Provide an ouput buffer for 0.2 seconds of data. */ + sp = MAX(tp->t_termios.c_ospeed, 0); + ttyoutq_setsize(&tp->t_outq, tp, sp / 5); -static struct tty * -tty_gettp(struct cdev *dev) + /* Set low watermark at 10% (when 90% is available). */ + tp->t_outlow = (ttyoutq_getsize(&tp->t_outq) * 9) / 10; +} + +static void +tty_freebuffers(struct tty *tp) { - struct tty *tp; - struct cdevsw *csw; - - csw = dev_refthread(dev); - if (csw == NULL) - return (NULL); - KASSERT(csw->d_flags & D_TTY, - ("non D_TTY (%s) in tty code", devtoname(dev))); - tp = dev->si_tty; - dev_relthread(dev); - KASSERT(tp != NULL, - ("no tty pointer on (%s) in tty code", devtoname(dev))); - return (tp); + + /* Destroy input buffers. */ + ttyinq_flush(&tp->t_inq); + ttyinq_setsize(&tp->t_inq, NULL, 0); + MPASS(ttyinq_getsize(&tp->t_inq) == 0); + tp->t_inlow = 0; + + /* Destroy output buffers. */ + ttyoutq_flush(&tp->t_outq); + ttyoutq_setsize(&tp->t_outq, NULL, 0); + MPASS(ttyoutq_getsize(&tp->t_outq) == 0); + tp->t_outlow = 0; } -/* - * Initial open of tty, or (re)entry to standard tty line discipline. - */ -int -tty_open(struct cdev *device, struct tty *tp) +static int +tty_drain(struct tty *tp) { - int s; - - s = spltty(); - tp->t_dev = device; - tp->t_hotchar = 0; - if (!ISSET(tp->t_state, TS_ISOPEN)) { - ttyref(tp); - SET(tp->t_state, TS_ISOPEN); - if (ISSET(tp->t_cflag, CLOCAL)) - SET(tp->t_state, TS_CONNECTED); - bzero(&tp->t_winsize, sizeof(tp->t_winsize)); + int error; + + while (ttyoutq_bytesused(&tp->t_outq) > 0) { + ttydevsw_outwakeup(tp); + /* Could be handled synchronously. */ + if (ttyoutq_bytesused(&tp->t_outq) == 0) + return (0); + + /* Wait for data to be drained. */ + error = tty_wait(tp, &tp->t_outwait); + if (error) + return (error); } - /* XXX don't hang forever on output */ - if (tp->t_timeout < 0) - tp->t_timeout = drainwait*hz; - ttsetwater(tp); - splx(s); + return (0); } /* - * 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. - * XXX our caller should have done `spltty(); l_close(); tty_close();' - * and l_close() should have flushed, but we repeat the spltty() and - * the flush in case there are buggy callers. + * Because the revoke() call already calls d_close() without making sure + * all threads are purged from the TTY, we can only destroy the buffers + * and such when the last thread leaves the TTY. ttydev_enter() and + * ttydev_leave() are called from within the cdev functions, to make + * sure we can garbage collect the TTY. */ -int -tty_close(struct tty *tp) + +static __inline int +ttydev_enter(struct tty *tp) { - int ostate, s; + tty_lock(tp); + if (tty_gone(tp) || !tty_opened(tp)) { + /* Device is already gone. */ + tty_unlock(tp); + return (ENXIO); + } + + return (0); +} + +static void +ttydev_leave(struct tty *tp) +{ + tty_lock_assert(tp, MA_OWNED); + + if (tty_opened(tp) || tp->t_flags & TF_OPENCLOSE) { + /* Device is still opened somewhere. */ + tty_unlock(tp); + return; + } + + tp->t_flags |= TF_OPENCLOSE; + + /* Stop asynchronous I/O. */ funsetown(&tp->t_sigio); - s = spltty(); + + /* Remove console TTY. */ if (constty == tp) constty_clear(); - ttyflush(tp, FREAD | FWRITE); - clist_free_cblocks(&tp->t_canq); - clist_free_cblocks(&tp->t_outq); - clist_free_cblocks(&tp->t_rawq); - - tp->t_gen++; - tp->t_line = TTYDISC; - tp->t_hotchar = 0; - tp->t_pgrp = NULL; - tp->t_session = NULL; - ostate = tp->t_state; - tp->t_state = 0; - knlist_clear(&tp->t_rsel.si_note, 0); - knlist_clear(&tp->t_wsel.si_note, 0); - /* - * Both final close and revocation close might end up calling - * this method. Only the thread clearing TS_ISOPEN should - * release the reference to the tty. - */ - if (ISSET(ostate, TS_ISOPEN)) - ttyrel(tp); - splx(s); - return (0); -} + /* Drain any output. */ + MPASS((tp->t_flags & TF_STOPPED) == 0); + if (!tty_gone(tp)) + tty_drain(tp); -#define FLUSHQ(q) { \ - if ((q)->c_cc) \ - ndflush(q, (q)->c_cc); \ -} + ttydisc_close(tp); + + /* Destroy associated buffers already. */ + tty_freebuffers(tp); + + knlist_clear(&tp->t_inpoll.si_note, 1); + knlist_clear(&tp->t_outpoll.si_note, 1); -/* Is 'c' a line delimiter ("break" character)? */ -#define TTBREAKC(c, lflag) \ - ((c) == '\n' || (((c) == cc[VEOF] || \ - (c) == cc[VEOL] || ((c) == cc[VEOL2] && lflag & IEXTEN)) && \ - (c) != _POSIX_VDISABLE)) + if (!tty_gone(tp)) + ttydevsw_close(tp); + + tp->t_flags &= ~TF_OPENCLOSE; + tty_rel_free(tp); +} /* - * Process input of a single character received on a tty. + * Operations that are exposed through the character device in /dev. */ -int -ttyinput(int c, struct tty *tp) +static int +ttydev_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { - tcflag_t iflag, lflag; - cc_t *cc; - int i, err; + struct tty *tp = dev->si_drv1; + int error; + /* Disallow access when the TTY belongs to a different prison. */ + if (dev->si_cred != NULL && + dev->si_cred->cr_prison != td->td_ucred->cr_prison && + priv_check(td, PRIV_TTY_PRISON)) { + return (EPERM); + } + + tty_lock(tp); + if (tty_gone(tp)) { + /* Device is already gone. */ + tty_unlock(tp); + return (ENXIO); + } /* - * If input is pending take it first. - */ - lflag = tp->t_lflag; - if (ISSET(lflag, PENDIN)) - ttypend(tp); - /* - * Gather stats. + * Prevent the TTY from being opened when being torn down or + * built up by unrelated processes. */ - if (ISSET(lflag, ICANON)) { - ++tk_cancc; - ++tp->t_cancc; - } else { - ++tk_rawcc; - ++tp->t_rawcc; + if (tp->t_flags & TF_OPENCLOSE) { + tty_unlock(tp); + return (EBUSY); } - ++tk_nin; + tp->t_flags |= TF_OPENCLOSE; /* - * Block further input iff: - * current input > threshold AND input is available to user program - * AND input flow control is enabled and not yet invoked. - * The 3 is slop for PARMRK. + * Make sure the "tty" and "cua" device cannot be opened at the + * same time. */ - iflag = tp->t_iflag; - if (tp->t_rawq.c_cc + tp->t_canq.c_cc > tp->t_ihiwat - 3 && - (!ISSET(lflag, ICANON) || tp->t_canq.c_cc != 0) && - (ISSET(tp->t_cflag, CRTS_IFLOW) || ISSET(iflag, IXOFF)) && - !ISSET(tp->t_state, TS_TBLOCK)) - ttyblock(tp); - - /* Handle exceptional conditions (break, parity, framing). */ - cc = tp->t_cc; - err = (ISSET(c, TTY_ERRORMASK)); - if (err) { - CLR(c, TTY_ERRORMASK); - if (ISSET(err, TTY_BI)) { - if (ISSET(iflag, IGNBRK)) - return (0); - if (ISSET(iflag, BRKINT)) { - ttyflush(tp, FREAD | FWRITE); - if (tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, SIGINT, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - goto endcase; - } - if (ISSET(iflag, PARMRK)) - goto parmrk; - } else if ((ISSET(err, TTY_PE) && ISSET(iflag, INPCK)) - || ISSET(err, TTY_FE)) { - if (ISSET(iflag, IGNPAR)) - return (0); - else if (ISSET(iflag, PARMRK)) { -parmrk: - if (tp->t_rawq.c_cc + tp->t_canq.c_cc > - MAX_INPUT - 3) - goto input_overflow; - (void)putc(0377 | TTY_QUOTE, &tp->t_rawq); - (void)putc(0 | TTY_QUOTE, &tp->t_rawq); - (void)putc(c | TTY_QUOTE, &tp->t_rawq); - goto endcase; - } else - c = 0; + if (TTY_CALLOUT(tp, dev)) { + if (tp->t_flags & TF_OPENED_IN) { + error = EBUSY; + goto done; + } + } else { + if (tp->t_flags & TF_OPENED_OUT) { + error = EBUSY; + goto done; } } - 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. - */ + if (tp->t_flags & TF_EXCLUDE && priv_check(td, PRIV_TTY_EXCLUSIVE)) { + error = EBUSY; + goto done; + } - /* - * 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; - } + if (!tty_opened(tp)) { + /* Set proper termios flags. */ + if (TTY_CALLOUT(tp, dev)) { + tp->t_termios = tp->t_termios_init_out; + } else { + tp->t_termios = tp->t_termios_init_in; } - /* - * 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); - if (tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, - CCEQ(cc[VINTR], c) ? SIGINT : SIGQUIT, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - goto endcase; - } - if (CCEQ(cc[VSUSP], c)) { - if (!ISSET(lflag, NOFLSH)) - ttyflush(tp, FREAD); - ttyecho(c, tp); - if (tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, SIGTSTP, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - 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); - tt_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)) - return (0); - else if (ISSET(iflag, ICRNL)) - c = '\n'; - } else if (c == '\n' && ISSET(iflag, INLCR)) - c = '\r'; + ttydevsw_param(tp, &tp->t_termios); + + ttydevsw_modem(tp, SER_DTR|SER_RTS, 0); + + error = ttydevsw_open(tp); + if (error != 0) + goto done; + + ttydisc_open(tp); + tty_watermarks(tp); } - if (!ISSET(tp->t_lflag, EXTPROC) && ISSET(lflag, ICANON)) { - /* - * From here on down canonical mode character - * processing takes place. - */ - /* - * erase or erase2 (^H / ^?) - */ - if (CCEQ(cc[VERASE], c) || CCEQ(cc[VERASE2], 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; - } - /* - * word erase (^W) - */ - if (CCEQ(cc[VWERASE], c) && ISSET(lflag, IEXTEN)) { - 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' && - (!ISSET(lflag, ALTWERASE) || ISALPHA(c) == ctype)); - (void)putc(c, &tp->t_rawq); - goto endcase; - } - /* - * reprint line (^R) - */ - if (CCEQ(cc[VREPRINT], c) && ISSET(lflag, IEXTEN)) { - ttyretype(tp); - goto endcase; - } - /* - * ^T - kernel info and generate SIGINFO - */ - if (CCEQ(cc[VSTATUS], c) && ISSET(lflag, IEXTEN)) { - if (ISSET(lflag, ISIG) && tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, SIGINFO, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - if (!ISSET(lflag, NOKERNINFO)) - ttyinfo(tp); - goto endcase; + /* Wait for Carrier Detect. */ + if (!TTY_CALLOUT(tp, dev) && (oflags & O_NONBLOCK) == 0 && + (tp->t_termios.c_cflag & CLOCAL) == 0) { + while ((ttydevsw_modem(tp, 0, 0) & SER_DCD) == 0) { + error = tty_wait(tp, &tp->t_dcdwait); + if (error != 0) + goto done; } } - /* - * Check for input buffer overflow - */ - if (tp->t_rawq.c_cc + tp->t_canq.c_cc >= MAX_INPUT) { -input_overflow: - if (ISSET(iflag, IMAXBEL)) { - if (tp->t_outq.c_cc < tp->t_ohiwat) - (void)ttyoutput(CTRL('g'), tp); - } - goto endcase; + + if (TTY_CALLOUT(tp, dev)) { + tp->t_flags |= TF_OPENED_OUT; + } else { + tp->t_flags |= TF_OPENED_IN; } - if ( c == 0377 && ISSET(iflag, PARMRK) && !ISSET(iflag, ISTRIP) - && ISSET(iflag, IGNBRK|IGNPAR) != (IGNBRK|IGNPAR)) - (void)putc(0377 | TTY_QUOTE, &tp->t_rawq); +done: tp->t_flags &= ~TF_OPENCLOSE; + ttydev_leave(tp); + return (error); +} + +static int +ttydev_close(struct cdev *dev, int fflag, int devtype, struct thread *td) +{ + struct tty *tp = dev->si_drv1; + + tty_lock(tp); /* - * Put data char in q for user and - * wakeup on seeing a line delimiter. + * This can only be called once. The callin and the callout + * devices cannot be opened at the same time. */ - 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 = imin(2, tp->t_column - i); - while (i > 0) { - (void)ttyoutput('\b', tp); - i--; - } + MPASS((tp->t_flags & TF_OPENED) != TF_OPENED); + tp->t_flags &= ~(TF_OPENED|TF_EXCLUDE|TF_STOPPED); + + /* Properly wake up threads that are stuck - revoke(). */ + tp->t_revokecnt++; + tty_wakeup(tp, FREAD|FWRITE); + cv_broadcast(&tp->t_bgwait); + + ttydev_leave(tp); + + return (0); +} + +static __inline int +tty_is_ctty(struct tty *tp, struct proc *p) +{ + tty_lock_assert(tp, MA_OWNED); + + return (p->p_session == tp->t_session && p->p_flag & P_CONTROLT); +} + +static int +tty_wait_background(struct tty *tp, struct thread *td, int sig) +{ + struct proc *p = td->td_proc; + struct pgrp *pg; + int error; + + MPASS(sig == SIGTTIN || sig == SIGTTOU); + tty_lock_assert(tp, MA_OWNED); + + for (;;) { + PROC_LOCK(p); + /* + * The process should only sleep, when: + * - This terminal is the controling terminal + * - Its process group is not the foreground process + * group + * - The parent process isn't waiting for the child to + * exit + * - the signal to send to the process isn't masked + */ + if (!tty_is_ctty(tp, p) || + p->p_pgrp == tp->t_pgrp || p->p_flag & P_PPWAIT || + SIGISMEMBER(p->p_sigacts->ps_sigignore, sig) || + SIGISMEMBER(td->td_sigmask, sig)) { + /* Allow the action to happen. */ + PROC_UNLOCK(p); + return (0); } + + /* + * Send the signal and sleep until we're the new + * foreground process group. + */ + pg = p->p_pgrp; + PROC_UNLOCK(p); + if (pg->pg_jobc == 0) + return (EIO); + PGRP_LOCK(pg); + pgsignal(pg, sig, 1); + PGRP_UNLOCK(pg); + + error = tty_wait(tp, &tp->t_bgwait); + if (error) + return (error); } -endcase: +} + +static int +ttydev_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct tty *tp = dev->si_drv1; + int error; + + error = ttydev_enter(tp); + if (error) + return (0); + + error = tty_wait_background(tp, curthread, SIGTTIN); + if (error) + goto done; + + error = ttydisc_read(tp, uio, ioflag); +done: ttydev_leave(tp); + /* - * IXANY means allow any character to restart output. + * The read() and write() calls should not throw an error when + * the device is ripped offline. */ - if (ISSET(tp->t_state, TS_TTSTOP) && - !ISSET(iflag, IXANY) && cc[VSTART] != cc[VSTOP]) + if (error == ENXIO) return (0); -restartoutput: - CLR(tp->t_lflag, FLUSHO); - CLR(tp->t_state, TS_TTSTOP); -startoutput: - return (ttstart(tp)); + + 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. - */ static int -ttyoutput(int c, struct tty *tp) +ttydev_write(struct cdev *dev, struct uio *uio, int ioflag) { - tcflag_t oflag; - int col, s; - - oflag = tp->t_oflag; - if (!ISSET(oflag, OPOST)) { - if (ISSET(tp->t_lflag, FLUSHO)) - return (-1); - if (putc(c, &tp->t_outq)) - return (c); - tk_nout++; - tp->t_outcc++; - return (-1); - } - /* - * Do tab expansion if OXTABS is set. Special case if we 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)) { - s = spltty(); /* Don't interrupt tabs. */ - c -= b_to_q(" ", c, &tp->t_outq); - tk_nout += c; - tp->t_outcc += c; - splx(s); - } - tp->t_column += c; - return (c ? -1 : '\t'); + struct tty *tp = dev->si_drv1; + int error; + + error = ttydev_enter(tp); + if (error) + return (0); + + if (tp->t_termios.c_lflag & TOSTOP) { + error = tty_wait_background(tp, curthread, SIGTTOU); + if (error) + goto done; } - if (c == CEOT && ISSET(oflag, ONOEOT)) - return (-1); + + error = ttydisc_write(tp, uio, ioflag); +done: ttydev_leave(tp); /* - * Newline translation: if ONLCR is set, - * translate newline into "\r\n". + * The read() and write() calls should not throw an error when + * the device is ripped offline. */ - 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); + if (error == ENXIO) + return (0); - 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); + return (error); } -/* - * 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) +static int +ttydev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, + struct thread *td) { - struct proc *p; - struct thread *td; - struct pgrp *pgrp; - int s, error, bits, sig, sig2; + struct tty *tp = dev->si_drv1; + int error; - td = curthread; /* XXX */ - p = td->td_proc; + error = ttydev_enter(tp); + if (error) + return (error); - /* If the ioctl involves modification, hang if in the background. */ switch (cmd) { - case TIOCCBRK: - case TIOCCONS: - case TIOCDRAIN: - case TIOCEXCL: - case TIOCFLUSH: -#ifdef TIOCHPCL - case TIOCHPCL: + case TIOCCBRK: + case TIOCCONS: + case TIOCDRAIN: + case TIOCEXCL: + case TIOCFLUSH: + case TIOCNXCL: + case TIOCSBRK: + case TIOCSCTTY: + case TIOCSETA: + case TIOCSETAF: + case TIOCSETAW: + case TIOCSPGRP: + case TIOCSTART: + case TIOCSTAT: + case TIOCSTOP: + case TIOCSWINSZ: +#if 0 + case TIOCSDRAINWAIT: + case TIOCSETD: + case TIOCSTI: #endif - case TIOCNXCL: - case TIOCSBRK: - case TIOCSCTTY: - case TIOCSDRAINWAIT: - case TIOCSETA: - case TIOCSETAF: - case TIOCSETAW: - case TIOCSETD: - case TIOCSPGRP: - case TIOCSTART: - case TIOCSTAT: - case TIOCSTI: - case TIOCSTOP: - case TIOCSWINSZ: -#if defined(COMPAT_43TTY) +#ifdef COMPAT_43TTY case TIOCLBIC: case TIOCLBIS: case TIOCLSET: @@ -838,2390 +482,1268 @@ ttioctl(struct tty *tp, u_long cmd, void *data, int flag) case TIOCSETN: case TIOCSETP: case TIOCSLTC: -#endif - sx_slock(&proctree_lock); - PROC_LOCK(p); - while (isbackground(p, tp) && !(p->p_flag & P_PPWAIT) && - !SIGISMEMBER(p->p_sigacts->ps_sigignore, SIGTTOU) && - !SIGISMEMBER(td->td_sigmask, SIGTTOU)) { - pgrp = p->p_pgrp; - PROC_UNLOCK(p); - if (pgrp->pg_jobc == 0) { - sx_sunlock(&proctree_lock); - return (EIO); - } - PGRP_LOCK(pgrp); - sx_sunlock(&proctree_lock); - pgsignal(pgrp, SIGTTOU, 1); - PGRP_UNLOCK(pgrp); - error = ttysleep(tp, &lbolt, TTOPRI | PCATCH, "ttybg1", - 0); - if (error) - return (error); - sx_slock(&proctree_lock); - PROC_LOCK(p); - } - PROC_UNLOCK(p); - sx_sunlock(&proctree_lock); - break; - } - - - if (tp->t_modem != NULL) { - switch (cmd) { - case TIOCSDTR: - tt_modem(tp, SER_DTR, 0); - return (0); - case TIOCCDTR: - tt_modem(tp, 0, SER_DTR); - return (0); - case TIOCMSET: - bits = *(int *)data; - sig = (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1; - sig2 = ((~bits) & (TIOCM_DTR | TIOCM_RTS)) >> 1; - tt_modem(tp, sig, sig2); - return (0); - case TIOCMBIS: - bits = *(int *)data; - sig = (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1; - tt_modem(tp, sig, 0); - return (0); - case TIOCMBIC: - bits = *(int *)data; - sig = (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1; - tt_modem(tp, 0, sig); - return (0); - case TIOCMGET: - sig = tt_modem(tp, 0, 0); - /* See <sys/serial.h. for the "<< 1" stuff */ - bits = TIOCM_LE + (sig << 1); - *(int *)data = bits; - return (0); - default: - break; - } - } - - if (tp->t_pps != NULL) { - error = pps_ioctl(cmd, data, tp->t_pps); - if (error != ENOIOCTL) - return (error); - } - - switch (cmd) { /* Process the ioctl. */ - case FIOASYNC: /* set/clear async i/o */ - s = spltty(); - if (*(int *)data) - SET(tp->t_state, TS_ASYNC); - else - CLR(tp->t_state, TS_ASYNC); - splx(s); - break; - case FIONBIO: /* set/clear non-blocking i/o */ - break; /* XXX: delete. */ - case FIONREAD: /* get # bytes to read */ - s = spltty(); - *(int *)data = ttnread(tp); - splx(s); - break; - - case FIOSETOWN: +#endif /* COMPAT_43TTY */ /* - * Policy -- Don't allow FIOSETOWN on someone else's - * controlling tty + * If the ioctl() causes the TTY to be modified, let it + * wait in the background. */ - if (tp->t_session != NULL && !isctty(p, tp)) - return (ENOTTY); - - error = fsetown(*(int *)data, &tp->t_sigio); + error = tty_wait_background(tp, curthread, SIGTTOU); if (error) - return (error); - break; - case FIOGETOWN: - if (tp->t_session != NULL && !isctty(p, tp)) - return (ENOTTY); - *(int *)data = fgetown(&tp->t_sigio); - break; - - case TIOCEXCL: /* set exclusive use of tty */ - s = spltty(); - SET(tp->t_state, TS_XCLUDE); - splx(s); - break; - case TIOCFLUSH: { /* flush buffers */ - int flags = *(int *)data; - - if (flags == 0) - flags = FREAD | FWRITE; - else - flags &= FREAD | FWRITE; - ttyflush(tp, flags); - break; + goto done; } - case TIOCCONS: /* become virtual console */ - if (*(int *)data) { - struct nameidata nid; - if (constty && constty != tp && - ISSET(constty->t_state, TS_CONNECTED)) - return (EBUSY); - - /* Ensure user can open the real console. */ - NDINIT(&nid, LOOKUP, LOCKLEAF | FOLLOW, UIO_SYSSPACE, - "/dev/console", td); - if ((error = namei(&nid)) != 0) - return (error); - NDFREE(&nid, NDF_ONLY_PNBUF); - error = VOP_ACCESS(nid.ni_vp, VREAD, td->td_ucred, td); - vput(nid.ni_vp); - if (error) - return (error); - - constty_set(tp); - } else if (tp == constty) - constty_clear(); - break; - case TIOCDRAIN: /* wait till output drained */ - error = ttywait(tp); - if (error) - return (error); - break; - case TIOCGETA: { /* get termios struct */ - struct termios *t = (struct termios *)data; + error = tty_ioctl(tp, cmd, data, td); +done: ttydev_leave(tp); - bcopy(&tp->t_termios, t, sizeof(struct termios)); - break; - } - case TIOCGETD: /* get line discipline */ - *(int *)data = tp->t_line; - break; - case TIOCGWINSZ: /* get window size */ - *(struct winsize *)data = tp->t_winsize; - break; - case TIOCGPGRP: /* get pgrp of tty */ - if (!isctty(p, tp)) - return (ENOTTY); - *(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : NO_PID; - break; - case TIOCGSID: /* get sid of tty */ - if (!isctty(p, tp)) - return (ENOTTY); - *(int *)data = tp->t_session->s_sid; - break; -#ifdef TIOCHPCL - case TIOCHPCL: /* hang up on last close */ - s = spltty(); - SET(tp->t_cflag, HUPCL); - splx(s); - break; -#endif - case TIOCMGDTRWAIT: - *(int *)data = tp->t_dtr_wait * 100 / hz; - break; - case TIOCMSDTRWAIT: - /* must be root since the wait applies to following logins */ - error = priv_check(td, PRIV_TTY_DTRWAIT); - if (error) - return (error); - tp->t_dtr_wait = *(int *)data * hz / 100; - break; - case TIOCNXCL: /* reset exclusive use of tty */ - s = spltty(); - CLR(tp->t_state, TS_XCLUDE); - splx(s); - 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; + return (error); +} - if (t->c_ispeed == 0) - t->c_ispeed = t->c_ospeed; - if (t->c_ispeed == 0) - t->c_ispeed = tp->t_ospeed; - if (t->c_ispeed == 0) - return (EINVAL); - s = spltty(); - if (cmd == TIOCSETAW || cmd == TIOCSETAF) { - error = ttywait(tp); - if (error) { - splx(s); - return (error); - } - if (cmd == TIOCSETAF) - ttyflush(tp, FREAD); - } - if (!ISSET(t->c_cflag, CIGNORE)) { - /* - * Set device hardware. - */ - error = tt_param(tp, t); - if (error) { - splx(s); - return (error); - } - if (ISSET(t->c_cflag, CLOCAL) && - !ISSET(tp->t_cflag, CLOCAL)) { - /* - * XXX disconnections would be too hard to - * get rid of without this kludge. The only - * way to get rid of controlling terminals - * is to exit from the session leader. - */ - CLR(tp->t_state, TS_ZOMBIE); - - wakeup(TSA_CARR_ON(tp)); - ttwakeup(tp); - ttwwakeup(tp); - } - if ((ISSET(tp->t_state, TS_CARR_ON) || - ISSET(t->c_cflag, CLOCAL)) && - !ISSET(tp->t_state, TS_ZOMBIE)) - SET(tp->t_state, TS_CONNECTED); - else - CLR(tp->t_state, TS_CONNECTED); - tp->t_cflag = t->c_cflag; - tp->t_ispeed = t->c_ispeed; - if (t->c_ospeed != 0) - tp->t_ospeed = t->c_ospeed; - ttsetwater(tp); - } - if (ISSET(t->c_lflag, ICANON) != ISSET(tp->t_lflag, ICANON) && - cmd != TIOCSETAF) { - if (ISSET(t->c_lflag, ICANON)) - SET(tp->t_lflag, PENDIN); - else { - /* - * XXX we really shouldn't allow toggling - * ICANON while we're in a non-termios line - * discipline. Now we have to worry about - * panicing for a null queue. - */ - if (tp->t_canq.c_cbreserved > 0 && - tp->t_rawq.c_cbreserved > 0) { - catq(&tp->t_rawq, &tp->t_canq); - /* - * XXX the queue limits may be - * different, so the old queue - * swapping method no longer works. - */ - catq(&tp->t_canq, &tp->t_rawq); - } - CLR(tp->t_lflag, PENDIN); - } - ttwakeup(tp); - } - 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); - if (t->c_cc[VMIN] != tp->t_cc[VMIN] || - t->c_cc[VTIME] != tp->t_cc[VTIME]) - ttwakeup(tp); - bcopy(t->c_cc, tp->t_cc, sizeof(t->c_cc)); - splx(s); - break; +static int +ttydev_poll(struct cdev *dev, int events, struct thread *td) +{ + struct tty *tp = dev->si_drv1; + int error, revents = 0; + + error = ttydev_enter(tp); + if (error) { + /* Don't return the error here, but the event mask. */ + return (events & + (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); } - case TIOCSETD: { /* set line discipline */ - int t = *(int *)data; - if ((u_int)t >= nlinesw) - return (ENXIO); - if (t == tp->t_line) - return (0); - s = spltty(); - ttyld_close(tp, flag); - tp->t_line = t; - /* XXX: we should use the correct cdev here */ - error = ttyld_open(tp, tp->t_dev); - if (error) { - /* - * If we fail to switch line discipline we cannot - * fall back to the previous, because we can not - * trust that ldisc to open successfully either. - * Fall back to the default ldisc which we know - * will allways succeed. - */ - tp->t_line = TTYDISC; - (void)ttyld_open(tp, tp->t_dev); - } - splx(s); - return (error); - break; + if (events & (POLLIN|POLLRDNORM)) { + /* See if we can read something. */ + if (ttydisc_read_poll(tp) > 0) + revents |= events & (POLLIN|POLLRDNORM); } - case TIOCSTART: /* start output, like ^Q */ - s = spltty(); - 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); - } - splx(s); - break; - case TIOCSTI: /* simulate terminal input */ - if ((flag & FREAD) == 0 && priv_check(td, PRIV_TTY_STI)) - return (EPERM); - if (!isctty(p, tp) && priv_check(td, PRIV_TTY_STI)) - return (EACCES); - s = spltty(); - ttyld_rint(tp, *(u_char *)data); - splx(s); - break; - case TIOCSTOP: /* stop output, like ^S */ - s = spltty(); - if (!ISSET(tp->t_state, TS_TTSTOP)) { - SET(tp->t_state, TS_TTSTOP); - tt_stop(tp, 0); - } - splx(s); - break; - case TIOCSCTTY: /* become controlling tty */ - /* Session ctty vnode pointer set in vnode layer. */ - sx_slock(&proctree_lock); - if (!SESS_LEADER(p) || - ((p->p_session->s_ttyvp || tp->t_session) && - (tp->t_session != p->p_session))) { - sx_sunlock(&proctree_lock); - return (EPERM); - } - tp->t_session = p->p_session; - tp->t_pgrp = p->p_pgrp; - SESS_LOCK(p->p_session); - ttyref(tp); /* ttyrel(): kern_proc.c:pgdelete() */ - p->p_session->s_ttyp = tp; - SESS_UNLOCK(p->p_session); - PROC_LOCK(p); - p->p_flag |= P_CONTROLT; - PROC_UNLOCK(p); - sx_sunlock(&proctree_lock); - break; - case TIOCSPGRP: { /* set pgrp of tty */ - sx_slock(&proctree_lock); - pgrp = pgfind(*(int *)data); - if (!isctty(p, tp)) { - if (pgrp != NULL) - PGRP_UNLOCK(pgrp); - sx_sunlock(&proctree_lock); - return (ENOTTY); - } - if (pgrp == NULL) { - sx_sunlock(&proctree_lock); - return (EPERM); - } - PGRP_UNLOCK(pgrp); - if (pgrp->pg_session != p->p_session) { - sx_sunlock(&proctree_lock); - return (EPERM); - } - sx_sunlock(&proctree_lock); - tp->t_pgrp = pgrp; - break; + if (events & (POLLOUT|POLLWRNORM)) { + /* See if we can write something. */ + if (ttydisc_write_poll(tp) > 0) + revents |= events & (POLLOUT|POLLWRNORM); } - case TIOCSTAT: /* simulate control-T */ - s = spltty(); - ttyinfo(tp); - splx(s); - break; - case TIOCSWINSZ: /* set window size */ - if (bcmp((caddr_t)&tp->t_winsize, data, - sizeof (struct winsize))) { - tp->t_winsize = *(struct winsize *)data; - if (tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, SIGWINCH, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - } - break; - case TIOCSDRAINWAIT: - error = priv_check(td, PRIV_TTY_DRAINWAIT); - if (error) - return (error); - tp->t_timeout = *(int *)data * hz; - wakeup(TSA_OCOMPLETE(tp)); - wakeup(TSA_OLOWAT(tp)); - break; - case TIOCGDRAINWAIT: - *(int *)data = tp->t_timeout / hz; - break; - case TIOCSBRK: - return (tt_break(tp, 1)); - case TIOCCBRK: - return (tt_break(tp, 0)); - default: -#if defined(COMPAT_43TTY) - return (ttcompat(tp, cmd, data, flag)); -#else - return (ENOIOCTL); -#endif + if (tp->t_flags & TF_ZOMBIE) + /* Hangup flag on zombie state. */ + revents |= events & POLLHUP; + + if (revents == 0) { + if (events & (POLLIN|POLLRDNORM)) + selrecord(td, &tp->t_inpoll); + if (events & (POLLOUT|POLLWRNORM)) + selrecord(td, &tp->t_outpoll); } - return (0); -} -int -ttypoll(struct cdev *dev, int events, struct thread *td) -{ - int s; - int revents = 0; - struct tty *tp; - - tp = tty_gettp(dev); - - if (tp == NULL) /* XXX used to return ENXIO, but that means true! */ - return ((events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)) - | POLLHUP); + ttydev_leave(tp); - s = spltty(); - if (events & (POLLIN | POLLRDNORM)) { - if (ISSET(tp->t_state, TS_ZOMBIE)) - revents |= (events & (POLLIN | POLLRDNORM)) | - POLLHUP; - else if (ttnread(tp) > 0) - revents |= events & (POLLIN | POLLRDNORM); - else - selrecord(td, &tp->t_rsel); - } - if (events & POLLOUT) { - if (ISSET(tp->t_state, TS_ZOMBIE)) - revents |= POLLHUP; - else if (tp->t_outq.c_cc <= tp->t_olowat && - ISSET(tp->t_state, TS_CONNECTED)) - revents |= events & POLLOUT; - else - selrecord(td, &tp->t_wsel); - } - splx(s); return (revents); } -static struct filterops ttyread_filtops = - { 1, NULL, filt_ttyrdetach, filt_ttyread }; -static struct filterops ttywrite_filtops = - { 1, NULL, filt_ttywdetach, filt_ttywrite }; - -int -ttykqfilter(struct cdev *dev, struct knote *kn) +static int +ttydev_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) { - struct tty *tp; - struct knlist *klist; - int s; - - tp = tty_gettp(dev); - if (tp == NULL || (tp->t_state & TS_GONE)) - return (ENODEV); - - switch (kn->kn_filter) { - case EVFILT_READ: - klist = &tp->t_rsel.si_note; - kn->kn_fop = &ttyread_filtops; - break; - case EVFILT_WRITE: - klist = &tp->t_wsel.si_note; - kn->kn_fop = &ttywrite_filtops; - break; - default: - return (EINVAL); - } + struct tty *tp = dev->si_drv1; + int error; - kn->kn_hook = (caddr_t)tp; + /* Handle mmap() through the driver. */ - s = spltty(); - knlist_add(klist, kn, 0); - splx(s); + error = ttydev_enter(tp); + if (error) + return (-1); + error = ttydevsw_mmap(tp, offset, paddr, nprot); + ttydev_leave(tp); - return (0); + return (error); } +/* + * kqueue support. + */ + static void -filt_ttyrdetach(struct knote *kn) +tty_kqops_read_detach(struct knote *kn) { - struct tty *tp = (struct tty *)kn->kn_hook; - int s = spltty(); + struct tty *tp = kn->kn_hook; - knlist_remove(&tp->t_rsel.si_note, kn, 0); - splx(s); + knlist_remove(&tp->t_inpoll.si_note, kn, 0); } static int -filt_ttyread(struct knote *kn, long hint) +tty_kqops_read_event(struct knote *kn, long hint) { - struct tty *tp = (struct tty *)kn->kn_hook; + struct tty *tp = kn->kn_hook; + + tty_lock_assert(tp, MA_OWNED); - kn->kn_data = ttnread(tp); - if ((tp->t_state & TS_GONE) || ISSET(tp->t_state, TS_ZOMBIE)) { + if (tty_gone(tp) || tp->t_flags & TF_ZOMBIE) { kn->kn_flags |= EV_EOF; return (1); + } else { + kn->kn_data = ttydisc_read_poll(tp); + return (kn->kn_data > 0); } - return (kn->kn_data > 0); } static void -filt_ttywdetach(struct knote *kn) +tty_kqops_write_detach(struct knote *kn) { - struct tty *tp = (struct tty *)kn->kn_hook; - int s = spltty(); + struct tty *tp = kn->kn_hook; - knlist_remove(&tp->t_wsel.si_note, kn, 0); - splx(s); + knlist_remove(&tp->t_outpoll.si_note, kn, 0); } static int -filt_ttywrite(struct knote *kn, long hint) +tty_kqops_write_event(struct knote *kn, long hint) { - struct tty *tp = (struct tty *)kn->kn_hook; + struct tty *tp = kn->kn_hook; + + tty_lock_assert(tp, MA_OWNED); - kn->kn_data = tp->t_outq.c_cc; - if ((tp->t_state & TS_GONE) || ISSET(tp->t_state, TS_ZOMBIE)) + if (tty_gone(tp)) { + kn->kn_flags |= EV_EOF; return (1); - return (kn->kn_data <= tp->t_olowat && - ISSET(tp->t_state, TS_CONNECTED)); + } else { + kn->kn_data = ttydisc_write_poll(tp); + return (kn->kn_data > 0); + } } -/* - * Must be called at spltty(). - */ +static struct filterops tty_kqops_read = + { 1, NULL, tty_kqops_read_detach, tty_kqops_read_event }; +static struct filterops tty_kqops_write = + { 1, NULL, tty_kqops_write_detach, tty_kqops_write_event }; + static int -ttnread(struct tty *tp) +ttydev_kqfilter(struct cdev *dev, struct knote *kn) { - int nread; - - 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] == 0) - nread = 0; + struct tty *tp = dev->si_drv1; + int error; + + error = ttydev_enter(tp); + if (error) + return (error); + + switch (kn->kn_filter) { + case EVFILT_READ: + kn->kn_hook = tp; + kn->kn_fop = &tty_kqops_read; + knlist_add(&tp->t_inpoll.si_note, kn, 1); + break; + case EVFILT_WRITE: + kn->kn_hook = tp; + kn->kn_fop = &tty_kqops_write; + knlist_add(&tp->t_outpoll.si_note, kn, 1); + break; + default: + error = EINVAL; + break; } - return (nread); + + ttydev_leave(tp); + return (error); } +static struct cdevsw ttydev_cdevsw = { + .d_version = D_VERSION, + .d_open = ttydev_open, + .d_close = ttydev_close, + .d_read = ttydev_read, + .d_write = ttydev_write, + .d_ioctl = ttydev_ioctl, + .d_kqfilter = ttydev_kqfilter, + .d_poll = ttydev_poll, + .d_mmap = ttydev_mmap, + .d_name = "ttydev", + .d_flags = D_TTY, +}; + /* - * Wait for output to drain. + * Init/lock-state devices */ -int -ttywait(struct tty *tp) + +static int +ttyil_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { - int error, s; - - error = 0; - s = spltty(); - while ((tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)) && - ISSET(tp->t_state, TS_CONNECTED) && tp->t_oproc) { - tt_oproc(tp); - if ((tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY)) && - ISSET(tp->t_state, TS_CONNECTED)) { - SET(tp->t_state, TS_SO_OCOMPLETE); - error = ttysleep(tp, TSA_OCOMPLETE(tp), - TTOPRI | PCATCH, "ttywai", - tp->t_timeout); - if (error) { - if (error == EWOULDBLOCK) - error = EIO; - break; - } - } else - break; - } - if (!error && (tp->t_outq.c_cc || ISSET(tp->t_state, TS_BUSY))) - error = EIO; - splx(s); + struct tty *tp = dev->si_drv1; + int error = 0; + + tty_lock(tp); + if (tty_gone(tp)) + error = ENODEV; + tty_unlock(tp); + return (error); } -/* - * Flush if successfully wait. - */ static int -ttywflush(struct tty *tp) +ttyil_close(struct cdev *dev, int flag, int mode, struct thread *td) { - int error; + return (0); +} - if ((error = ttywait(tp)) == 0) - ttyflush(tp, FREAD); - return (error); +static int +ttyil_rdwr(struct cdev *dev, struct uio *uio, int ioflag) +{ + return (ENODEV); } -/* - * Flush tty read and/or write queues, notifying anyone waiting. - */ -void -ttyflush(struct tty *tp, int rw) +static int +ttyil_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, + struct thread *td) { - int s; + struct tty *tp = dev->si_drv1; + int error = 0; - s = spltty(); -#if 0 -again: -#endif - if (rw & FWRITE) { - FLUSHQ(&tp->t_outq); - CLR(tp->t_state, TS_TTSTOP); + tty_lock(tp); + if (tty_gone(tp)) { + error = ENODEV; + goto done; } - tt_stop(tp, rw); - if (rw & FREAD) { - FLUSHQ(&tp->t_canq); - FLUSHQ(&tp->t_rawq); - CLR(tp->t_lflag, PENDIN); - tp->t_rocount = 0; - tp->t_rocol = 0; - CLR(tp->t_state, TS_LOCAL); - ttwakeup(tp); - if (ISSET(tp->t_state, TS_TBLOCK)) { - if (rw & FWRITE) - FLUSHQ(&tp->t_outq); - ttyunblock(tp); - /* - * Don't let leave any state that might clobber the - * next line discipline (although we should do more - * to send the START char). Not clearing the state - * may have caused the "putc to a clist with no - * reserved cblocks" panic/printf. - */ - CLR(tp->t_state, TS_TBLOCK); - -#if 0 /* forget it, sleeping isn't always safe and we don't know when it is */ - if (ISSET(tp->t_iflag, IXOFF)) { - /* - * XXX wait a bit in the hope that the stop - * character (if any) will go out. Waiting - * isn't good since it allows races. This - * will be fixed when the stop character is - * put in a special queue. Don't bother with - * the checks in ttywait() since the timeout - * will save us. - */ - SET(tp->t_state, TS_SO_OCOMPLETE); - ttysleep(tp, TSA_OCOMPLETE(tp), TTOPRI, - "ttyfls", hz / 10); - /* - * Don't try sending the stop character again. - */ - CLR(tp->t_state, TS_TBLOCK); - goto again; - } -#endif - } - } - if (rw & FWRITE) { - FLUSHQ(&tp->t_outq); - ttwwakeup(tp); + switch (cmd) { + case TIOCGETA: + /* Obtain terminal flags through tcgetattr(). */ + bcopy(dev->si_drv2, data, sizeof(struct termios)); + break; + case TIOCSETA: + /* Set terminal flags through tcsetattr(). */ + error = priv_check(td, PRIV_TTY_SETA); + if (error) + break; + bcopy(data, dev->si_drv2, sizeof(struct termios)); + return (0); + break; + case TIOCGETD: + *(int *)data = TTYDISC; + break; + case TIOCGWINSZ: + bzero(data, sizeof(struct winsize)); + break; + default: + error = ENOTTY; } - splx(s); + +done: tty_unlock(tp); + return (error); } -/* - * Copy in the default termios characters. - */ -void -termioschars(struct termios *t) +static struct cdevsw ttyil_cdevsw = { + .d_version = D_VERSION, + .d_open = ttyil_open, + .d_close = ttyil_close, + .d_read = ttyil_rdwr, + .d_write = ttyil_rdwr, + .d_ioctl = ttyil_ioctl, + .d_name = "ttyil", + .d_flags = D_TTY, +}; + +static void +tty_init_termios(struct tty *tp) { + struct termios *t = &tp->t_termios_init_in; - bcopy(ttydefchars, t->c_cc, sizeof t->c_cc); + t->c_cflag = TTYDEF_CFLAG; + t->c_iflag = TTYDEF_IFLAG; + t->c_lflag = TTYDEF_LFLAG; + t->c_oflag = TTYDEF_OFLAG; + t->c_ispeed = TTYDEF_SPEED; + t->c_ospeed = TTYDEF_SPEED; + bcopy(ttydefchars, &t->c_cc, sizeof ttydefchars); + + tp->t_termios_init_out = *t; } -/* - * Old interface. - */ void -ttychars(struct tty *tp) +tty_init_console(struct tty *tp, speed_t s) { + struct termios *ti = &tp->t_termios_init_in; + struct termios *to = &tp->t_termios_init_out; + + if (s != 0) { + ti->c_ispeed = ti->c_ospeed = s; + to->c_ispeed = to->c_ospeed = s; + } - termioschars(&tp->t_termios); + ti->c_cflag |= CLOCAL; + to->c_cflag |= CLOCAL; } /* - * Handle input high water. Send stop character for the IXOFF case. Turn - * on our input flow control bit and propagate the changes to the driver. - * XXX the stop character should be put in a special high priority queue. + * Standard device routine implementations, mostly meant for + * pseudo-terminal device drivers. When a driver creates a new terminal + * device class, missing routines are patched. */ -void -ttyblock(struct tty *tp) + +static int +ttydevsw_defopen(struct tty *tp) { - SET(tp->t_state, TS_TBLOCK); - if (ISSET(tp->t_iflag, IXOFF) && tp->t_cc[VSTOP] != _POSIX_VDISABLE && - putc(tp->t_cc[VSTOP], &tp->t_outq) != 0) - CLR(tp->t_state, TS_TBLOCK); /* try again later */ - ttstart(tp); + return (0); } -/* - * Handle input low water. Send start character for the IXOFF case. Turn - * off our input flow control bit and propagate the changes to the driver. - * XXX the start character should be put in a special high priority queue. - */ static void -ttyunblock(struct tty *tp) +ttydevsw_defclose(struct tty *tp) { - - CLR(tp->t_state, TS_TBLOCK); - if (ISSET(tp->t_iflag, IXOFF) && tp->t_cc[VSTART] != _POSIX_VDISABLE && - putc(tp->t_cc[VSTART], &tp->t_outq) != 0) - SET(tp->t_state, TS_TBLOCK); /* try again later */ - ttstart(tp); } -#ifdef notyet -/* Not used by any current (i386) drivers. */ -/* - * Restart after an inter-char delay. - */ -void -ttrstrt(void *tp_arg) +static void +ttydevsw_defoutwakeup(struct tty *tp) { - struct tty *tp; - int s; - KASSERT(tp_arg != NULL, ("ttrstrt")); + panic("Terminal device has output, while not implemented"); +} - tp = tp_arg; - s = spltty(); +static void +ttydevsw_definwakeup(struct tty *tp) +{ +} - CLR(tp->t_state, TS_TIMEOUT); - ttstart(tp); +static int +ttydevsw_defioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) +{ - splx(s); + return (ENOIOCTL); } -#endif -int -ttstart(struct tty *tp) +static int +ttydevsw_defparam(struct tty *tp, struct termios *t) { - tt_oproc(tp); + /* Use a fake baud rate, we're not a real device. */ + t->c_ispeed = t->c_ospeed = TTYDEF_SPEED_PSEUDO; + return (0); } -/* - * "close" a line discipline - */ -int -ttylclose(struct tty *tp, int flag) +static int +ttydevsw_defmodem(struct tty *tp, int sigon, int sigoff) { - if (flag & FNONBLOCK || ttywflush(tp)) - ttyflush(tp, FREAD | FWRITE); - return (0); + /* Simulate a carrier to make the TTY layer happy. */ + return (SER_DCD); } -/* - * 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) +static int +ttydevsw_defmmap(struct tty *tp, vm_offset_t offset, vm_paddr_t *paddr, + int nprot) { - if (ISSET(tp->t_state, TS_CARR_ON) && ISSET(tp->t_cflag, MDMBUF)) { - /* - * MDMBUF: do flow control according to carrier flag - * XXX TS_CAR_OFLOW doesn't do anything yet. TS_TTSTOP - * works if IXON and IXANY are clear. - */ - if (flag) { - CLR(tp->t_state, TS_CAR_OFLOW); - CLR(tp->t_state, TS_TTSTOP); - ttstart(tp); - } else if (!ISSET(tp->t_state, TS_CAR_OFLOW)) { - SET(tp->t_state, TS_CAR_OFLOW); - SET(tp->t_state, TS_TTSTOP); - tt_stop(tp, 0); - } - } else if (flag == 0) { - /* - * Lost carrier. - */ - CLR(tp->t_state, TS_CARR_ON); - if (ISSET(tp->t_state, TS_ISOPEN) && - !ISSET(tp->t_cflag, CLOCAL)) { - SET(tp->t_state, TS_ZOMBIE); - CLR(tp->t_state, TS_CONNECTED); - if (tp->t_session) { - sx_slock(&proctree_lock); - if (tp->t_session && tp->t_session->s_leader) { - struct proc *p; - - p = tp->t_session->s_leader; - PROC_LOCK(p); - psignal(p, SIGHUP); - PROC_UNLOCK(p); - } - sx_sunlock(&proctree_lock); - } - ttyflush(tp, FREAD | FWRITE); - return (0); - } - } else { - /* - * Carrier now on. - */ - SET(tp->t_state, TS_CARR_ON); - if (!ISSET(tp->t_state, TS_ZOMBIE)) - SET(tp->t_state, TS_CONNECTED); - wakeup(TSA_CARR_ON(tp)); - ttwakeup(tp); - ttwwakeup(tp); - } - return (1); + return (-1); } -/* - * Reinput pending characters after state switch - * call at spltty(). - */ static void -ttypend(struct tty *tp) +ttydevsw_deffree(void *softc) { - struct clist tq; - int c; - CLR(tp->t_lflag, PENDIN); - SET(tp->t_state, TS_TYPEN); - /* - * XXX this assumes too much about clist internals. It may even - * fail if the cblock slush pool is empty. We can't allocate more - * cblocks here because we are called from an interrupt handler - * and clist_alloc_cblocks() can wait. - */ - tq = tp->t_rawq; - bzero(&tp->t_rawq, sizeof tp->t_rawq); - tp->t_rawq.c_cbmax = tq.c_cbmax; - tp->t_rawq.c_cbreserved = tq.c_cbreserved; - while ((c = getc(&tq)) >= 0) - ttyinput(c, tp); - CLR(tp->t_state, TS_TYPEN); + panic("Terminal device freed without a free-handler"); } /* - * Process a read call on a tty device. + * TTY allocation and deallocation. TTY devices can be deallocated when + * the driver doesn't use it anymore, when the TTY isn't a session's + * controlling TTY and when the device node isn't opened through devfs. */ -int -ttread(struct tty *tp, struct uio *uio, int flag) + +struct tty * +tty_alloc(struct ttydevsw *tsw, void *sc, struct mtx *mutex) { - struct clist *qp; - int c; - tcflag_t lflag; - cc_t *cc = tp->t_cc; - struct thread *td; - struct proc *p; - int s, first, error = 0; - int has_stime = 0, last_cc = 0; - long slp = 0; /* XXX this should be renamed `timo'. */ - struct timeval stime = { 0, 0 }; - struct pgrp *pg; + struct tty *tp; - td = curthread; - p = td->td_proc; -loop: - s = spltty(); - lflag = tp->t_lflag; - /* - * take pending input first - */ - if (ISSET(lflag, PENDIN)) { - ttypend(tp); - splx(s); /* reduce latency */ - s = spltty(); - lflag = tp->t_lflag; /* XXX ttypend() clobbers it */ + /* Make sure the driver defines all routines. */ +#define PATCH_FUNC(x) do { \ + if (tsw->tsw_ ## x == NULL) \ + tsw->tsw_ ## x = ttydevsw_def ## x; \ +} while (0) + PATCH_FUNC(open); + PATCH_FUNC(close); + PATCH_FUNC(outwakeup); + PATCH_FUNC(inwakeup); + PATCH_FUNC(ioctl); + PATCH_FUNC(param); + PATCH_FUNC(modem); + PATCH_FUNC(mmap); + PATCH_FUNC(free); +#undef PATCH_FUNC + + tp = malloc(sizeof(struct tty), M_TTY, M_WAITOK|M_ZERO); + tp->t_devsw = tsw; + tp->t_softc = sc; + tp->t_flags = tsw->tsw_flags; + + tty_init_termios(tp); + + cv_init(&tp->t_inwait, "tty input"); + cv_init(&tp->t_outwait, "tty output"); + cv_init(&tp->t_bgwait, "tty background"); + cv_init(&tp->t_dcdwait, "tty dcd"); + + TAILQ_INIT(&tp->t_inq.ti_list); + STAILQ_INIT(&tp->t_outq.to_list); + + /* Allow drivers to use a custom mutex to lock the TTY. */ + if (mutex != NULL) { + tp->t_mtx = mutex; + } else { + tp->t_mtx = &tp->t_mtxobj; + mtx_init(&tp->t_mtxobj, "tty lock", NULL, MTX_DEF); } - /* - * Hang process if it's in the background. - */ - if (isbackground(p, tp)) { - splx(s); - sx_slock(&proctree_lock); - PROC_LOCK(p); - if (SIGISMEMBER(p->p_sigacts->ps_sigignore, SIGTTIN) || - SIGISMEMBER(td->td_sigmask, SIGTTIN) || - (p->p_flag & P_PPWAIT) || p->p_pgrp->pg_jobc == 0) { - PROC_UNLOCK(p); - sx_sunlock(&proctree_lock); - return (EIO); - } - pg = p->p_pgrp; - PROC_UNLOCK(p); - PGRP_LOCK(pg); - sx_sunlock(&proctree_lock); - pgsignal(pg, SIGTTIN, 1); - PGRP_UNLOCK(pg); - error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, "ttybg2", 0); - if (error) - return (error); - goto loop; - } + knlist_init(&tp->t_inpoll.si_note, tp->t_mtx, NULL, NULL, NULL); + knlist_init(&tp->t_outpoll.si_note, tp->t_mtx, NULL, NULL, NULL); - if (ISSET(tp->t_state, TS_ZOMBIE)) { - splx(s); - return (0); /* EOF */ - } + sx_xlock(&tty_list_sx); + TAILQ_INSERT_TAIL(&tty_list, tp, t_list); + tty_list_count++; + sx_xunlock(&tty_list_sx); - /* - * If canonical, use the canonical queue, - * else use the raw queue. - * - * (should get rid of clists...) - */ - qp = ISSET(lflag, ICANON) ? &tp->t_canq : &tp->t_rawq; + return (tp); +} - if (flag & IO_NDELAY) { - if (qp->c_cc > 0) - goto read; - if (!ISSET(lflag, ICANON) && cc[VMIN] == 0) { - splx(s); - return (0); - } - splx(s); - return (EWOULDBLOCK); - } - if (!ISSET(lflag, ICANON)) { - int m = cc[VMIN]; - long t = cc[VTIME]; - struct timeval timecopy; +static void +tty_dealloc(void *arg) +{ + struct tty *tp = arg; - /* - * 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; - if (qp->c_cc > 0) - goto read; - - /* m, t and qp->c_cc are all 0. 0 is enough input. */ - splx(s); - return (0); - } - t *= 100000; /* time in us */ -#define diff(t1, t2) (((t1).tv_sec - (t2).tv_sec) * 1000000 + \ - ((t1).tv_usec - (t2).tv_usec)) - if (m > 0) { - if (qp->c_cc <= 0) - goto sleep; - if (qp->c_cc >= m) - goto read; - getmicrotime(&timecopy); - if (!has_stime) { - /* first character, start timer */ - has_stime = 1; - stime = timecopy; - slp = t; - } else if (qp->c_cc > last_cc) { - /* got a character, restart timer */ - stime = timecopy; - slp = t; - } else { - /* nothing, check expiration */ - slp = t - diff(timecopy, stime); - if (slp <= 0) - goto read; - } - last_cc = qp->c_cc; - } else { /* m == 0 */ - if (qp->c_cc > 0) - goto read; - getmicrotime(&timecopy); - if (!has_stime) { - has_stime = 1; - stime = timecopy; - slp = t; - } else { - slp = t - diff(timecopy, stime); - if (slp <= 0) { - /* Timed out, but 0 is enough input. */ - splx(s); - return (0); - } - } - } -#undef diff - if (slp != 0) { - struct timeval tv; /* XXX style bug. */ + sx_xlock(&tty_list_sx); + TAILQ_REMOVE(&tty_list, tp, t_list); + tty_list_count--; + sx_xunlock(&tty_list_sx); - tv.tv_sec = slp / 1000000; - tv.tv_usec = slp % 1000000; - slp = tvtohz(&tv); - /* - * XXX bad variable names. slp was the timeout in - * usec. Now it is the timeout in ticks. - */ - } - goto sleep; - } - if (qp->c_cc <= 0) { -sleep: - /* - * There is no input, or not enough input and we can block. - */ - error = ttysleep(tp, TSA_HUP_OR_INPUT(tp), TTIPRI | PCATCH, - ISSET(tp->t_state, TS_CONNECTED) ? - "ttyin" : "ttyhup", (int)slp); - splx(s); - if (error == EWOULDBLOCK) - error = 0; - else if (error) - return (error); - /* - * XXX what happens if another process eats some input - * while we are asleep (not just here)? It would be - * safest to detect changes and reset our state variables - * (has_stime and last_cc). - */ - slp = 0; - goto loop; - } -read: - splx(s); - /* - * Input present, check for input mapping and processing. - */ - first = 1; - if (ISSET(lflag, ICANON | ISIG)) - goto slowcase; - for (;;) { - char ibuf[IBUFSIZ]; - int icc; - - icc = imin(uio->uio_resid, IBUFSIZ); - icc = q_to_b(qp, ibuf, icc); - if (icc <= 0) { - if (first) - goto loop; - break; - } - error = uiomove(ibuf, icc, uio); - /* - * XXX if there was an error then we should ungetc() the - * unmoved chars and reduce icc here. - */ - if (error) - break; - if (uio->uio_resid == 0) - break; - first = 0; - } - goto out; -slowcase: - for (;;) { - c = getc(qp); - if (c < 0) { - if (first) - goto loop; - break; - } - /* - * delayed suspend (^Y) - */ - if (CCEQ(cc[VDSUSP], c) && - ISSET(lflag, IEXTEN | ISIG) == (IEXTEN | ISIG)) { - if (tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, SIGTSTP, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - if (first) { - error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, - "ttybg3", 0); - if (error) - break; - goto loop; - } - break; - } - /* - * Interpret EOF only in canonical mode. - */ - if (CCEQ(cc[VEOF], c) && ISSET(lflag, ICANON)) - break; - /* - * Give user character. - */ - error = ureadc(c, uio); - if (error) - /* XXX should ungetc(c, qp). */ - 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; - } + knlist_destroy(&tp->t_inpoll.si_note); + knlist_destroy(&tp->t_outpoll.si_note); -out: - /* - * Look to unblock input now that (presumably) - * the input queue has gone down. - */ - s = spltty(); - if (ISSET(tp->t_state, TS_TBLOCK) && - tp->t_rawq.c_cc + tp->t_canq.c_cc <= tp->t_ilowat) - ttyunblock(tp); - splx(s); + cv_destroy(&tp->t_inwait); + cv_destroy(&tp->t_outwait); + cv_destroy(&tp->t_bgwait); + cv_destroy(&tp->t_dcdwait); - return (error); + if (tp->t_mtx == &tp->t_mtxobj) + mtx_destroy(&tp->t_mtxobj); + ttydevsw_free(tp); + free(tp, M_TTY); } -/* - * 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. - */ -int -ttycheckoutq(struct tty *tp, int wait) +static void +tty_rel_free(struct tty *tp) { - int hiwat, s; - sigset_t oldmask; - struct thread *td; - struct proc *p; + struct cdev *dev; - td = curthread; - p = td->td_proc; - hiwat = tp->t_ohiwat; - SIGEMPTYSET(oldmask); - s = spltty(); - if (wait) { - PROC_LOCK(p); - oldmask = td->td_siglist; - PROC_UNLOCK(p); - } - if (tp->t_outq.c_cc > hiwat + OBUFSIZ + 100) - while (tp->t_outq.c_cc > hiwat) { - ttstart(tp); - if (tp->t_outq.c_cc <= hiwat) - break; - if (!wait) { - splx(s); - return (0); - } - PROC_LOCK(p); - if (!SIGSETEQ(td->td_siglist, oldmask)) { - PROC_UNLOCK(p); - splx(s); - return (0); - } - PROC_UNLOCK(p); - SET(tp->t_state, TS_SO_OLOWAT); - tsleep(TSA_OLOWAT(tp), PZERO - 1, "ttoutq", hz); - } - splx(s); - return (1); -} + tty_lock_assert(tp, MA_OWNED); -/* - * Process a write call on a tty device. - */ -int -ttwrite(struct tty *tp, struct uio *uio, int flag) -{ - char *cp = NULL; - int cc, ce; - struct thread *td; - struct proc *p; - int i, hiwat, cnt, error, s; - char obuf[OBUFSIZ]; - - hiwat = tp->t_ohiwat; - cnt = uio->uio_resid; - error = 0; - cc = 0; - td = curthread; - p = td->td_proc; -loop: - s = spltty(); - if (ISSET(tp->t_state, TS_ZOMBIE)) { - splx(s); - if (uio->uio_resid == cnt) - error = EIO; - goto out; - } - if (!ISSET(tp->t_state, TS_CONNECTED)) { - if (flag & IO_NDELAY) { - splx(s); - error = EWOULDBLOCK; - goto out; - } - error = ttysleep(tp, TSA_CARR_ON(tp), TTIPRI | PCATCH, - "ttywdcd", 0); - splx(s); - if (error) - goto out; - goto loop; - } - splx(s); - /* - * Hang the process if it's in the background. - */ - sx_slock(&proctree_lock); - PROC_LOCK(p); - if (isbackground(p, tp) && - ISSET(tp->t_lflag, TOSTOP) && !(p->p_flag & P_PPWAIT) && - !SIGISMEMBER(p->p_sigacts->ps_sigignore, SIGTTOU) && - !SIGISMEMBER(td->td_sigmask, SIGTTOU)) { - if (p->p_pgrp->pg_jobc == 0) { - PROC_UNLOCK(p); - sx_sunlock(&proctree_lock); - error = EIO; - goto out; - } - PROC_UNLOCK(p); - PGRP_LOCK(p->p_pgrp); - sx_sunlock(&proctree_lock); - pgsignal(p->p_pgrp, SIGTTOU, 1); - PGRP_UNLOCK(p->p_pgrp); - error = ttysleep(tp, &lbolt, TTIPRI | PCATCH, "ttybg4", 0); - if (error) - goto out; - goto loop; - } else { - PROC_UNLOCK(p); - sx_sunlock(&proctree_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) { - cc = imin(uio->uio_resid, OBUFSIZ); - cp = obuf; - error = uiomove(cp, cc, uio); - if (error) { - cc = 0; - break; - } - } - /* - * 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. - */ - while (cc > 0) { - if (!ISSET(tp->t_oflag, OPOST)) - ce = cc; - else { - ce = cc - scanc((u_int)cc, (u_char *)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) { - /* No Clists, wait a bit. */ - ttstart(tp); - if (flag & IO_NDELAY) { - error = EWOULDBLOCK; - goto out; - } - error = ttysleep(tp, &lbolt, - TTOPRI|PCATCH, - "ttybf1", 0); - if (error) - goto out; - goto loop; - } - cp++; - cc--; - if (ISSET(tp->t_lflag, FLUSHO) || - tp->t_outq.c_cc > hiwat) - 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) { - /* No Clists, wait a bit. */ - ttstart(tp); - if (flag & IO_NDELAY) { - error = EWOULDBLOCK; - goto out; - } - error = ttysleep(tp, &lbolt, TTOPRI | PCATCH, - "ttybf2", 0); - if (error) - goto out; - goto loop; - } - if (ISSET(tp->t_lflag, FLUSHO) || - tp->t_outq.c_cc > hiwat) - break; - } - ttstart(tp); + if (tp->t_sessioncnt != 0 || + (tp->t_flags & (TF_GONE|TF_OPENED)) != TF_GONE) { + /* TTY is still in use. */ + tty_unlock(tp); + return; } -out: - /* - * If cc is nonzero, we leave the uio structure inconsistent, as the - * offset and iov pointers have moved forward, but it doesn't matter - * (the call will either return short or restart with a new uio). - */ - uio->uio_resid += cc; - return (error); -ovhiwat: - ttstart(tp); - s = spltty(); - /* - * 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) { - splx(s); - goto loop; - } - if (flag & IO_NDELAY) { - splx(s); - uio->uio_resid += cc; - return (uio->uio_resid == cnt ? EWOULDBLOCK : 0); - } - SET(tp->t_state, TS_SO_OLOWAT); - error = ttysleep(tp, TSA_OLOWAT(tp), TTOPRI | PCATCH, "ttywri", - tp->t_timeout); - splx(s); - if (error == EWOULDBLOCK) - error = EIO; - if (error) - goto out; - goto loop; + tty_freebuffers(tp); + + /* TTY can be deallocated. */ + dev = tp->t_dev; + tp->t_dev = NULL; + tty_unlock(tp); + + destroy_dev_sched_cb(dev, tty_dealloc, tp); } -/* - * Rubout one character from the rawq of tp - * as cleanly as possible. - */ -static void -ttyrub(int c, struct tty *tp) +void +tty_rel_pgrp(struct tty *tp, struct pgrp *pg) { - char *cp; - int savecol; - int tabc, s; + tty_lock_assert(tp, MA_OWNED); - 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; - } - s = spltty(); - savecol = tp->t_column; - SET(tp->t_state, TS_CNTTB); - SET(tp->t_lflag, FLUSHO); - tp->t_column = tp->t_rocol; - cp = tp->t_rawq.c_cf; - if (cp) - tabc = *cp; /* XXX FIX NEXTC */ - for (; cp; cp = nextc(&tp->t_rawq, cp, &tabc)) - ttyecho(tabc, tp); - CLR(tp->t_lflag, FLUSHO); - CLR(tp->t_state, TS_CNTTB); - splx(s); - - /* 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 */ -#define PANICSTR "ttyrub: would panic c = %d, val = %d\n" - (void)printf(PANICSTR, c, CCLASS(c)); -#ifdef notdef - panic(PANICSTR, c, CCLASS(c)); -#endif - } - } - } 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); - /* - * This code may be executed not only when an ERASE key - * is pressed, but also when ^U (KILL) or ^W (WERASE) are. - * So, I didn't think it was worthwhile to pass the extra - * information (which would need an extra parameter, - * changing every call) needed to distinguish the ERASE2 - * case from the ERASE. - */ - } - --tp->t_rocount; + if (tp->t_pgrp == pg) + tp->t_pgrp = NULL; } -/* - * Back over cnt characters, erasing them. - */ -static void -ttyrubo(struct tty *tp, int cnt) +void +tty_rel_sess(struct tty *tp, struct session *sess) { + MPASS(tp->t_sessioncnt > 0); - while (cnt-- > 0) { - (void)ttyoutput('\b', tp); - (void)ttyoutput(' ', tp); - (void)ttyoutput('\b', tp); + /* Current session has left. */ + if (tp->t_session == sess) { + tp->t_session = NULL; + MPASS(tp->t_pgrp == NULL); } + tp->t_sessioncnt--; + tty_rel_free(tp); } -/* - * ttyretype -- - * Reprint the rawq line. Note, it is assumed that c_cc has already - * been checked. - */ -static void -ttyretype(struct tty *tp) +void +tty_rel_gone(struct tty *tp) { - char *cp; - int s, c; + MPASS(!tty_gone(tp)); - /* Echo the reprint character. */ - if (tp->t_cc[VREPRINT] != _POSIX_VDISABLE) - ttyecho(tp->t_cc[VREPRINT], tp); + /* Simulate carrier removal. */ + ttydisc_modem(tp, 0); - (void)ttyoutput('\n', tp); + /* Wake up misc. blocked threads. */ + cv_broadcast(&tp->t_bgwait); + cv_broadcast(&tp->t_dcdwait); - /* - * XXX - * FIX: NEXTC IS BROKEN - DOESN'T CHECK QUOTE - * BIT OF FIRST CHAR. - */ - s = spltty(); - for (cp = tp->t_canq.c_cf, c = (cp != NULL ? *cp : 0); - cp != NULL; cp = nextc(&tp->t_canq, cp, &c)) - ttyecho(c, tp); - for (cp = tp->t_rawq.c_cf, c = (cp != NULL ? *cp : 0); - cp != NULL; cp = nextc(&tp->t_rawq, cp, &c)) - ttyecho(c, tp); - CLR(tp->t_state, TS_ERASE); - splx(s); - - tp->t_rocount = tp->t_rawq.c_cc; - tp->t_rocol = 0; + tp->t_flags |= TF_GONE; + tty_rel_free(tp); } /* - * Echo a typed character to the terminal. + * Exposing information about current TTY's through sysctl */ + static void -ttyecho(int c, struct tty *tp) +tty_to_xtty(struct tty *tp, struct xtty *xt) { - - if (!ISSET(tp->t_state, TS_CNTTB)) - CLR(tp->t_lflag, FLUSHO); - if ((!ISSET(tp->t_lflag, ECHO) && - (c != '\n' || !ISSET(tp->t_lflag, ECHONL))) || - 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); + tty_lock_assert(tp, MA_OWNED); + + xt->xt_size = sizeof(struct xtty); + xt->xt_insize = ttyinq_getsize(&tp->t_inq); + xt->xt_incc = ttyinq_bytescanonicalized(&tp->t_inq); + xt->xt_inlc = ttyinq_bytesline(&tp->t_inq); + xt->xt_inlow = tp->t_inlow; + xt->xt_outsize = ttyoutq_getsize(&tp->t_outq); + xt->xt_outcc = ttyoutq_bytesused(&tp->t_outq); + xt->xt_outlow = tp->t_outlow; + xt->xt_column = tp->t_column; + xt->xt_pgid = tp->t_pgrp ? tp->t_pgrp->pg_id : 0; + xt->xt_sid = tp->t_session ? tp->t_session->s_sid : 0; + xt->xt_flags = tp->t_flags; + xt->xt_dev = tp->t_dev ? dev2udev(tp->t_dev) : NODEV; } -/* - * Wake up any readers on a tty. - */ -void -ttwakeup(struct tty *tp) +static int +sysctl_kern_ttys(SYSCTL_HANDLER_ARGS) { + unsigned long lsize; + struct xtty *xtlist, *xt; + struct tty *tp; + int error; - if (SEL_WAITING(&tp->t_rsel)) - selwakeuppri(&tp->t_rsel, TTIPRI); - if (ISSET(tp->t_state, TS_ASYNC) && tp->t_sigio != NULL) - pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); - wakeup(TSA_HUP_OR_INPUT(tp)); - KNOTE_UNLOCKED(&tp->t_rsel.si_note, 0); + sx_slock(&tty_list_sx); + lsize = tty_list_count * sizeof(struct xtty); + if (lsize == 0) { + sx_sunlock(&tty_list_sx); + return (0); + } + + xtlist = xt = malloc(lsize, M_TEMP, M_WAITOK); + + TAILQ_FOREACH(tp, &tty_list, t_list) { + tty_lock(tp); + tty_to_xtty(tp, xt); + tty_unlock(tp); + xt++; + } + sx_sunlock(&tty_list_sx); + + error = SYSCTL_OUT(req, xtlist, lsize); + free(xtlist, M_TEMP); + return (error); } +SYSCTL_PROC(_kern, OID_AUTO, ttys, CTLTYPE_OPAQUE|CTLFLAG_RD, + 0, 0, sysctl_kern_ttys, "S,xtty", "List of TTYs"); + /* - * Wake up any writers on a tty. + * Device node creation. Device has been set up, now we can expose it to + * the user. */ + void -ttwwakeup(struct tty *tp) +tty_makedev(struct tty *tp, struct ucred *cred, const char *fmt, ...) { + va_list ap; + struct cdev *dev; + const char *prefix = "tty"; + char name[SPECNAMELEN - 3]; /* for "tty" and "cua". */ + uid_t uid; + gid_t gid; + mode_t mode; - if (SEL_WAITING(&tp->t_wsel) && tp->t_outq.c_cc <= tp->t_olowat) - selwakeuppri(&tp->t_wsel, TTOPRI); - if (ISSET(tp->t_state, TS_ASYNC) && tp->t_sigio != NULL) - pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); - if (ISSET(tp->t_state, TS_BUSY | TS_SO_OCOMPLETE) == - TS_SO_OCOMPLETE && tp->t_outq.c_cc == 0) { - CLR(tp->t_state, TS_SO_OCOMPLETE); - wakeup(TSA_OCOMPLETE(tp)); + /* Remove "tty" prefix from devices like PTY's. */ + if (tp->t_flags & TF_NOPREFIX) + prefix = ""; + + va_start(ap, fmt); + vsnrprintf(name, sizeof name, 32, fmt, ap); + va_end(ap); + + if (cred == NULL) { + /* System device. */ + uid = UID_ROOT; + gid = GID_WHEEL; + mode = S_IRUSR|S_IWUSR; + } else { + /* User device. */ + uid = cred->cr_ruid; + gid = GID_TTY; + mode = S_IRUSR|S_IWUSR|S_IWGRP; } - if (ISSET(tp->t_state, TS_SO_OLOWAT) && - tp->t_outq.c_cc <= tp->t_olowat) { - CLR(tp->t_state, TS_SO_OLOWAT); - wakeup(TSA_OLOWAT(tp)); + + /* Master call-in device. */ + dev = make_dev_cred(&ttydev_cdevsw, 0, cred, + uid, gid, mode, "%s%s", prefix, name); + dev->si_drv1 = tp; + tp->t_dev = dev; + + /* Slave call-in devices. */ + if (tp->t_flags & TF_INITLOCK) { + dev = make_dev_cred(&ttyil_cdevsw, 0, cred, + uid, gid, mode, "%s%s.init", prefix, name); + dev_depends(tp->t_dev, dev); + dev->si_drv1 = tp; + dev->si_drv2 = &tp->t_termios_init_in; + + dev = make_dev_cred(&ttyil_cdevsw, 0, cred, + uid, gid, mode, "%s%s.lock", prefix, name); + dev_depends(tp->t_dev, dev); + dev->si_drv1 = tp; + dev->si_drv2 = &tp->t_termios_lock_in; + } + + /* Call-out devices. */ + if (tp->t_flags & TF_CALLOUT) { + dev = make_dev_cred(&ttydev_cdevsw, 0, cred, + UID_UUCP, GID_DIALER, 0660, "cua%s", name); + dev_depends(tp->t_dev, dev); + dev->si_drv1 = tp; + + /* Slave call-out devices. */ + if (tp->t_flags & TF_INITLOCK) { + dev = make_dev_cred(&ttyil_cdevsw, 0, cred, + UID_UUCP, GID_DIALER, 0660, "cua%s.init", name); + dev_depends(tp->t_dev, dev); + dev->si_drv1 = tp; + dev->si_drv2 = &tp->t_termios_init_out; + + dev = make_dev_cred(&ttyil_cdevsw, 0, cred, + UID_UUCP, GID_DIALER, 0660, "cua%s.lock", name); + dev_depends(tp->t_dev, dev); + dev->si_drv1 = tp; + dev->si_drv2 = &tp->t_termios_lock_out; + } } - KNOTE_UNLOCKED(&tp->t_wsel.si_note, 0); } /* - * Look up a code for a specified speed in a conversion table; - * used by drivers to map software speed values to hardware parameters. + * Signalling processes. */ -int -ttspeedtab(int speed, struct speedtab *table) + +void +tty_signal_sessleader(struct tty *tp, int sig) { + struct proc *p; - for ( ; table->sp_speed != -1; table++) - if (table->sp_speed == speed) - return (table->sp_code); - return (-1); + tty_lock_assert(tp, MA_OWNED); + MPASS(sig >= 1 && sig < NSIG); + + /* Make signals start output again. */ + tp->t_flags &= ~TF_STOPPED; + + if (tp->t_session != NULL && tp->t_session->s_leader != NULL) { + p = tp->t_session->s_leader; + PROC_LOCK(p); + psignal(p, sig); + PROC_UNLOCK(p); + } } -/* - * Set input and output watermarks and buffer sizes. For input, the - * high watermark is about one second's worth of input above empty, the - * low watermark is slightly below high water, and the buffer size is a - * driver-dependent amount above high water. For output, the watermarks - * are near the ends of the buffer, with about 1 second's worth of input - * between them. All this only applies to the standard line discipline. - */ void -ttsetwater(struct tty *tp) +tty_signal_pgrp(struct tty *tp, int sig) { - int cps, ttmaxhiwat, x; - - /* Input. */ - clist_alloc_cblocks(&tp->t_canq, TTYHOG, 512); - switch (tp->t_ispeedwat) { - case (speed_t)-1: - cps = tp->t_ispeed / 10; - break; - case 0: - /* - * This case is for old drivers that don't know about - * t_ispeedwat. Arrange for them to get the old buffer - * sizes and watermarks. - */ - cps = TTYHOG - 2 * 256; - tp->t_ififosize = 2 * 256; - break; - default: - cps = tp->t_ispeedwat / 10; - break; + tty_lock_assert(tp, MA_OWNED); + MPASS(sig >= 1 && sig < NSIG); + + /* Make signals start output again. */ + tp->t_flags &= ~TF_STOPPED; + + if (sig == SIGINFO && !(tp->t_termios.c_lflag & NOKERNINFO)) + tty_info(tp); + if (tp->t_pgrp != NULL) { + PGRP_LOCK(tp->t_pgrp); + pgsignal(tp->t_pgrp, sig, 1); + PGRP_UNLOCK(tp->t_pgrp); } - tp->t_ihiwat = cps; - tp->t_ilowat = 7 * cps / 8; - x = cps + tp->t_ififosize; - clist_alloc_cblocks(&tp->t_rawq, x, x); - - /* Output. */ - switch (tp->t_ospeedwat) { - case (speed_t)-1: - cps = tp->t_ospeed / 10; - ttmaxhiwat = 2 * TTMAXHIWAT; - break; - case 0: - cps = tp->t_ospeed / 10; - ttmaxhiwat = TTMAXHIWAT; - break; - default: - cps = tp->t_ospeedwat / 10; - ttmaxhiwat = 8 * TTMAXHIWAT; - break; - } -#define CLAMP(x, h, l) ((x) > h ? h : ((x) < l) ? l : (x)) - tp->t_olowat = x = CLAMP(cps / 2, TTMAXLOWAT, TTMINLOWAT); - x += cps; - x = CLAMP(x, ttmaxhiwat, TTMINHIWAT); /* XXX clamps are too magic */ - tp->t_ohiwat = roundup(x, CBSIZE); /* XXX for compat */ - x = imax(tp->t_ohiwat, TTMAXHIWAT); /* XXX for compat/safety */ - x += OBUFSIZ + 100; - clist_alloc_cblocks(&tp->t_outq, x, x); -#undef CLAMP } -/* - * Output char to tty; console putchar style. - */ -int -tputchar(int c, struct tty *tp) +void +tty_wakeup(struct tty *tp, int flags) { - int s; + if (tp->t_flags & TF_ASYNC && tp->t_sigio != NULL) + pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); - s = spltty(); - if (!ISSET(tp->t_state, TS_CONNECTED)) { - splx(s); - return (-1); + if (flags & FWRITE) { + cv_broadcast(&tp->t_outwait); + selwakeup(&tp->t_outpoll); + KNOTE_LOCKED(&tp->t_outpoll.si_note, 0); + } + if (flags & FREAD) { + cv_broadcast(&tp->t_inwait); + selwakeup(&tp->t_inpoll); + KNOTE_LOCKED(&tp->t_inpoll.si_note, 0); } - if (c == '\n') - (void)ttyoutput('\r', tp); - (void)ttyoutput(c, tp); - ttstart(tp); - splx(s); - return (0); } -/* - * Sleep on chan, returning ERESTART if tty changed while we napped and - * returning any errors (e.g. EINTR/EWOULDBLOCK) reported by tsleep. If - * the tty is revoked, restarting a pending call will redo validation done - * at the start of the call. - */ int -ttysleep(struct tty *tp, void *chan, int pri, char *wmesg, int timo) +tty_wait(struct tty *tp, struct cv *cv) { int error; - int gen; + int revokecnt = tp->t_revokecnt; + +#if 0 + /* XXX: /dev/console also picks up Giant. */ + tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); +#endif + tty_lock_assert(tp, MA_OWNED); + + error = cv_wait_sig(cv, tp->t_mtx); - gen = tp->t_gen; - error = tsleep(chan, pri, wmesg, timo); - if (tp->t_state & TS_GONE) + /* Restart the system call when we may have been revoked. */ + if (tp->t_revokecnt != revokecnt) + return (ERESTART); + + /* Bail out when the device slipped away. */ + if (tty_gone(tp)) return (ENXIO); - if (error) - return (error); - return (tp->t_gen == gen ? 0 : ERESTART); + + return (error); } -/* - * Gain a reference to a TTY - */ int -ttyref(struct tty *tp) +tty_timedwait(struct tty *tp, struct cv *cv, int hz) { - int i; + int error; + int revokecnt = tp->t_revokecnt; + +#if 0 + /* XXX: /dev/console also picks up Giant. */ + tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); +#endif + tty_lock_assert(tp, MA_OWNED); + + error = cv_timedwait_sig(cv, tp->t_mtx, hz); + + /* Restart the system call when we may have been revoked. */ + if (tp->t_revokecnt != revokecnt) + return (ERESTART); - mtx_lock(&tp->t_mtx); - KASSERT(tp->t_refcnt > 0, - ("ttyref(): tty refcnt is %d (%s)", - tp->t_refcnt, tp->t_dev != NULL ? devtoname(tp->t_dev) : "??")); - i = ++tp->t_refcnt; - mtx_unlock(&tp->t_mtx); - return (i); + /* Bail out when the device slipped away. */ + if (tty_gone(tp)) + return (ENXIO); + + return (error); } -/* - * Drop a reference to a TTY. - * When reference count drops to zero, we free it. - */ -int -ttyrel(struct tty *tp) +void +tty_flush(struct tty *tp, int flags) { - int i; - - mtx_lock(&tty_list_mutex); - mtx_lock(&tp->t_mtx); - KASSERT(tp->t_refcnt > 0, - ("ttyrel(): tty refcnt is %d (%s)", - tp->t_refcnt, tp->t_dev != NULL ? devtoname(tp->t_dev) : "??")); - i = --tp->t_refcnt; - if (i != 0) { - mtx_unlock(&tp->t_mtx); - mtx_unlock(&tty_list_mutex); - return (i); + if (flags & FWRITE) { + tp->t_flags &= ~TF_HIWAT_OUT; + ttyoutq_flush(&tp->t_outq); + tty_wakeup(tp, FWRITE); + } + if (flags & FREAD) { + tty_hiwat_in_unblock(tp); + ttyinq_flush(&tp->t_inq); + ttydevsw_inwakeup(tp); } - TAILQ_REMOVE(&tty_list, tp, t_list); - mtx_unlock(&tp->t_mtx); - mtx_unlock(&tty_list_mutex); - knlist_destroy(&tp->t_rsel.si_note); - knlist_destroy(&tp->t_wsel.si_note); - mtx_destroy(&tp->t_mtx); - free(tp, M_TTYS); - return (i); } -/* - * Allocate a tty struct. Clists in the struct will be allocated by - * tty_open(). - */ -struct tty * -ttyalloc() +static int +tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, struct thread *td) { - struct tty *tp; - - tp = malloc(sizeof *tp, M_TTYS, M_WAITOK | M_ZERO); - mtx_init(&tp->t_mtx, "tty", NULL, MTX_DEF); + int error; + switch (cmd) { /* - * Set up the initial state + * Modem commands. + * The SER_* and TIOCM_* flags are the same, but one bit + * shifted. I don't know why. */ - tp->t_refcnt = 1; - tp->t_timeout = -1; - tp->t_dtr_wait = 3 * hz; - - ttyinitmode(tp, 0, 0); - bcopy(ttydefchars, tp->t_init_in.c_cc, sizeof tp->t_init_in.c_cc); + case TIOCSDTR: + ttydevsw_modem(tp, SER_DTR, 0); + return (0); + case TIOCCDTR: + ttydevsw_modem(tp, 0, SER_DTR); + return (0); + case TIOCMSET: { + int bits = *(int *)data; + ttydevsw_modem(tp, + (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, + ((~bits) & (TIOCM_DTR | TIOCM_RTS)) >> 1); + return (0); + } + case TIOCMBIS: { + int bits = *(int *)data; + ttydevsw_modem(tp, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, 0); + return (0); + } + case TIOCMBIC: { + int bits = *(int *)data; + ttydevsw_modem(tp, 0, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1); + return (0); + } + case TIOCMGET: + *(int *)data = TIOCM_LE + (ttydevsw_modem(tp, 0, 0) << 1); + return (0); - /* Make callout the same as callin */ - tp->t_init_out = tp->t_init_in; + case FIOASYNC: + if (*(int *)data) + tp->t_flags |= TF_ASYNC; + else + tp->t_flags &= ~TF_ASYNC; + return (0); + case FIONBIO: + /* This device supports non-blocking operation. */ + return (0); + case FIONREAD: + *(int *)data = ttyinq_bytescanonicalized(&tp->t_inq); + return (0); + case FIOSETOWN: + if (tp->t_session != NULL && !tty_is_ctty(tp, td->td_proc)) + /* Not allowed to set ownership. */ + return (ENOTTY); - mtx_lock(&tty_list_mutex); - TAILQ_INSERT_TAIL(&tty_list, tp, t_list); - mtx_unlock(&tty_list_mutex); - knlist_init(&tp->t_rsel.si_note, &tp->t_mtx, NULL, NULL, NULL); - knlist_init(&tp->t_wsel.si_note, &tp->t_mtx, NULL, NULL, NULL); - return (tp); -} + /* Temporarily unlock the TTY to set ownership. */ + tty_unlock(tp); + error = fsetown(*(int *)data, &tp->t_sigio); + tty_lock(tp); + return (error); + case FIOGETOWN: + if (tp->t_session != NULL && !tty_is_ctty(tp, td->td_proc)) + /* Not allowed to set ownership. */ + return (ENOTTY); -static void -ttypurge(struct cdev *dev) -{ + /* Get ownership. */ + *(int *)data = fgetown(&tp->t_sigio); + return (0); + case TIOCGETA: + /* Obtain terminal flags through tcgetattr(). */ + bcopy(&tp->t_termios, data, sizeof(struct termios)); + return (0); + case TIOCSETA: + case TIOCSETAW: + case TIOCSETAF: { + struct termios *t = data; - if (dev->si_tty == NULL) - return; - ttygone(dev->si_tty); -} + /* + * Who makes up these funny rules? According to POSIX, + * input baud rate is set equal to the output baud rate + * when zero. + */ + if (t->c_ispeed == 0) + t->c_ispeed = t->c_ospeed; -/* - * ttycreate() - * - * Create the device entries for this tty thereby opening it for business. - * - * The flags argument controls if "cua" units are created. - * - * The t_sc filed is copied to si_drv1 in the created cdevs. This - * is particularly important for ->t_cioctl() users. - * - * XXX: implement the init and lock devices by cloning. - */ + /* Don't allow invalid flags to be set. */ + if ((t->c_iflag & ~TTYSUP_IFLAG) != 0 || + (t->c_oflag & ~TTYSUP_OFLAG) != 0 || + (t->c_lflag & ~TTYSUP_LFLAG) != 0 || + (t->c_cflag & ~TTYSUP_CFLAG) != 0) + return (EINVAL); -int -ttycreate(struct tty *tp, int flags, const char *fmt, ...) -{ - char namebuf[SPECNAMELEN - 3]; /* XXX space for "tty" */ - struct cdevsw *csw = NULL; - int unit = 0; - va_list ap; - struct cdev *cp; - int i, minor, sminor, sunit; + /* Set terminal flags through tcsetattr(). */ + if (cmd == TIOCSETAW || cmd == TIOCSETAF) { + error = tty_drain(tp); + if (error) + return (error); + if (cmd == TIOCSETAF) + tty_flush(tp, FREAD); + } - mtx_assert(&Giant, MA_OWNED); + /* + * Only call param() when the flags really change. + */ + if ((t->c_cflag & CIGNORE) == 0 && + (tp->t_termios.c_cflag != t->c_cflag || + tp->t_termios.c_ispeed != t->c_ispeed || + tp->t_termios.c_ospeed != t->c_ospeed)) { + error = ttydevsw_param(tp, t); + if (error) + return (error); - if (tty_unit == NULL) - tty_unit = new_unrhdr(0, 0xffff, NULL); + /* XXX: CLOCAL? */ + + tp->t_termios.c_cflag = t->c_cflag; + tp->t_termios.c_ispeed = t->c_ispeed; + tp->t_termios.c_ospeed = t->c_ospeed; - sunit = alloc_unr(tty_unit); - tp->t_devunit = sunit; + /* Baud rate has changed - update watermarks. */ + tty_watermarks(tp); + } - if (csw == NULL) { - csw = &tty_cdevsw; - unit = sunit; - } - KASSERT(csw->d_purge == NULL || csw->d_purge == ttypurge, - ("tty should not have d_purge")); + /* Copy new non-device driver parameters. */ + tp->t_termios.c_iflag = t->c_iflag; + tp->t_termios.c_oflag = t->c_oflag; + tp->t_termios.c_lflag = t->c_lflag; + bcopy(t->c_cc, &tp->t_termios.c_cc, sizeof(t->c_cc)); - csw->d_purge = ttypurge; + ttydisc_optimize(tp); - minor = unit2minor(unit); - sminor = unit2minor(sunit); - va_start(ap, fmt); - i = vsnrprintf(namebuf, sizeof namebuf, 32, fmt, ap); - va_end(ap); - KASSERT(i < sizeof namebuf, ("Too long tty name (%s)", namebuf)); - - cp = make_dev(csw, minor, - UID_ROOT, GID_WHEEL, 0600, "tty%s", namebuf); - tp->t_dev = cp; - tp->t_mdev = cp; - cp->si_tty = tp; - cp->si_drv1 = tp->t_sc; - - cp = make_dev(&ttys_cdevsw, sminor | MINOR_INIT, - UID_ROOT, GID_WHEEL, 0600, "tty%s.init", namebuf); - dev_depends(tp->t_dev, cp); - cp->si_drv1 = tp->t_sc; - cp->si_drv2 = &tp->t_init_in; - cp->si_tty = tp; - - cp = make_dev(&ttys_cdevsw, sminor | MINOR_LOCK, - UID_ROOT, GID_WHEEL, 0600, "tty%s.lock", namebuf); - dev_depends(tp->t_dev, cp); - cp->si_drv1 = tp->t_sc; - cp->si_drv2 = &tp->t_lock_in; - cp->si_tty = tp; - - if (flags & TS_CALLOUT) { - cp = make_dev(csw, minor | MINOR_CALLOUT, - UID_UUCP, GID_DIALER, 0660, "cua%s", namebuf); - dev_depends(tp->t_dev, cp); - cp->si_drv1 = tp->t_sc; - cp->si_tty = tp; - - cp = make_dev(&ttys_cdevsw, sminor | MINOR_CALLOUT | MINOR_INIT, - UID_UUCP, GID_DIALER, 0660, "cua%s.init", namebuf); - dev_depends(tp->t_dev, cp); - cp->si_drv1 = tp->t_sc; - cp->si_drv2 = &tp->t_init_out; - cp->si_tty = tp; - - cp = make_dev(&ttys_cdevsw, sminor | MINOR_CALLOUT | MINOR_LOCK, - UID_UUCP, GID_DIALER, 0660, "cua%s.lock", namebuf); - dev_depends(tp->t_dev, cp); - cp->si_drv1 = tp->t_sc; - cp->si_drv2 = &tp->t_lock_out; - cp->si_tty = tp; + if ((t->c_lflag & ICANON) == 0) { + /* + * When in non-canonical mode, wake up all + * readers. Canonicalize any partial input. VMIN + * and VTIME could also be adjusted. + */ + ttyinq_canonicalize(&tp->t_inq); + tty_wakeup(tp, FREAD); + } + return (0); } + case TIOCGETD: + /* For compatibility - we only support TTYDISC. */ + *(int *)data = TTYDISC; + return (0); + case TIOCGPGRP: + if (!tty_is_ctty(tp, td->td_proc)) + return (ENOTTY); - return (0); -} - -/* - * This function is called when the hardware disappears. We set a flag - * and wake up stuff so all sleeping threads will notice. - */ -void -ttygone(struct tty *tp) -{ - - tp->t_state |= TS_GONE; - if (SEL_WAITING(&tp->t_rsel)) - selwakeuppri(&tp->t_rsel, TTIPRI); - if (SEL_WAITING(&tp->t_wsel)) - selwakeuppri(&tp->t_wsel, TTOPRI); - if (ISSET(tp->t_state, TS_ASYNC) && tp->t_sigio != NULL) - pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); - wakeup(&tp->t_dtr_wait); - wakeup(TSA_CARR_ON(tp)); - wakeup(TSA_HUP_OR_INPUT(tp)); - wakeup(TSA_OCOMPLETE(tp)); - wakeup(TSA_OLOWAT(tp)); - KNOTE_UNLOCKED(&tp->t_rsel.si_note, 0); - KNOTE_UNLOCKED(&tp->t_wsel.si_note, 0); - tt_purge(tp); -} - -/* - * ttyfree() - * - * Called when the driver is ready to free the tty structure. - * - * XXX: This shall sleep until all threads have left the driver. - */ -void -ttyfree(struct tty *tp) -{ - struct cdev *dev; - u_int unit; - - mtx_assert(&Giant, MA_OWNED); - ttygone(tp); - unit = tp->t_devunit; - dev = tp->t_mdev; - dev->si_tty = NULL; - tp->t_dev = NULL; - destroy_dev(dev); - ttyrel(tp); - free_unr(tty_unit, unit); -} + if (tp->t_pgrp != NULL) + *(int *)data = tp->t_pgrp->pg_id; + else + *(int *)data = NO_PID; + return (0); + case TIOCGSID: + if (!tty_is_ctty(tp, td->td_proc)) + return (ENOTTY); -static int -sysctl_kern_ttys(SYSCTL_HANDLER_ARGS) -{ - struct tty *tp, *tp2; - struct xtty xt; - int error; + MPASS(tp->t_session); + *(int *)data = tp->t_session->s_sid; + return (0); + case TIOCSCTTY: { + struct proc *p = td->td_proc; - error = 0; - mtx_lock(&tty_list_mutex); - tp = TAILQ_FIRST(&tty_list); - if (tp != NULL) - ttyref(tp); - while (tp != NULL) { - if (tp->t_state & TS_GONE) - goto nexttp; - bzero(&xt, sizeof xt); - xt.xt_size = sizeof xt; -#define XT_COPY(field) xt.xt_##field = tp->t_##field - xt.xt_rawcc = tp->t_rawq.c_cc; - xt.xt_cancc = tp->t_canq.c_cc; - xt.xt_outcc = tp->t_outq.c_cc; - XT_COPY(line); + /* XXX: This looks awful. */ + tty_unlock(tp); + sx_xlock(&proctree_lock); + tty_lock(tp); - /* - * XXX: We hold the tty list lock while doing this to - * work around a race with pty/pts tty destruction. - * They set t_dev to NULL and then call ttyrel() to - * free the structure which will block on the list - * lock before they call destroy_dev() on the cdev - * backing t_dev. - * - * XXX: ttyfree() now does the same since it has been - * fixed to not leak ttys. - */ - if (tp->t_dev != NULL) - xt.xt_dev = dev2udev(tp->t_dev); - XT_COPY(state); - XT_COPY(flags); - XT_COPY(timeout); - if (tp->t_pgrp != NULL) - xt.xt_pgid = tp->t_pgrp->pg_id; - if (tp->t_session != NULL) - xt.xt_sid = tp->t_session->s_sid; - XT_COPY(termios); - XT_COPY(winsize); - XT_COPY(column); - XT_COPY(rocount); - XT_COPY(rocol); - XT_COPY(ififosize); - XT_COPY(ihiwat); - XT_COPY(ilowat); - XT_COPY(ispeedwat); - XT_COPY(ohiwat); - XT_COPY(olowat); - XT_COPY(ospeedwat); -#undef XT_COPY - mtx_unlock(&tty_list_mutex); - error = SYSCTL_OUT(req, &xt, sizeof xt); - if (error != 0) { - ttyrel(tp); - return (error); + if (!SESS_LEADER(p)) { + /* Only the session leader may do this. */ + sx_xunlock(&proctree_lock); + return (EPERM); } - mtx_lock(&tty_list_mutex); -nexttp: tp2 = TAILQ_NEXT(tp, t_list); - if (tp2 != NULL) - ttyref(tp2); - mtx_unlock(&tty_list_mutex); - ttyrel(tp); - tp = tp2; - mtx_lock(&tty_list_mutex); - } - mtx_unlock(&tty_list_mutex); - return (0); -} -SYSCTL_PROC(_kern, OID_AUTO, ttys, CTLTYPE_OPAQUE|CTLFLAG_RD, - 0, 0, sysctl_kern_ttys, "S,xtty", "All ttys"); -SYSCTL_LONG(_kern, OID_AUTO, tty_nin, CTLFLAG_RD, - &tk_nin, 0, "Total TTY in characters"); -SYSCTL_LONG(_kern, OID_AUTO, tty_nout, CTLFLAG_RD, - &tk_nout, 0, "Total TTY out characters"); + if (tp->t_session != NULL && tp->t_session == p->p_session) { + /* This is already our controlling TTY. */ + sx_xunlock(&proctree_lock); + return (0); + } -void -nottystop(struct tty *tp, int rw) -{ + if (!SESS_LEADER(p) || p->p_session->s_ttyvp != NULL || + (tp->t_session != NULL && tp->t_session->s_ttyvp != NULL)) { + /* + * There is already a relation between a TTY and + * a session, or the caller is not the session + * leader. + * + * Allow the TTY to be stolen when the vnode is + * NULL, but the reference to the TTY is still + * active. + */ + sx_xunlock(&proctree_lock); + return (EPERM); + } - return; -} + /* Connect the session to the TTY. */ + tp->t_session = p->p_session; + tp->t_session->s_ttyp = tp; + tp->t_sessioncnt++; + sx_xunlock(&proctree_lock); -int -ttyopen(struct cdev *dev, int flag, int mode, struct thread *td) -{ - int error; - int s; - struct tty *tp; + /* Assign foreground process group. */ + tp->t_pgrp = p->p_pgrp; + PROC_LOCK(p); + p->p_flag |= P_CONTROLT; + PROC_UNLOCK(p); - tp = dev->si_tty; + return (0); + } + case TIOCSPGRP: { + struct pgrp *pg; - s = spltty(); - /* - * We jump to this label after all non-interrupted sleeps to pick - * up any changes of the device state. - */ -open_top: - if (tp->t_state & TS_GONE) - return (ENXIO); - error = ttydtrwaitsleep(tp); - if (error) - goto out; - if (tp->t_state & TS_ISOPEN) { /* - * The device is open, so everything has been initialized. - * Handle conflicts. + * XXX: Temporarily unlock the TTY to locate the process + * group. This code would be lot nicer if we would ever + * decompose proctree_lock. */ - if (ISCALLOUT(dev) && !tp->t_actout) - return (EBUSY); - if (tp->t_actout && !ISCALLOUT(dev)) { - if (flag & O_NONBLOCK) - return (EBUSY); - error = tsleep(&tp->t_actout, - TTIPRI | PCATCH, "ttybi", 0); - if (error != 0 || (tp->t_state & TS_GONE)) - goto out; - goto open_top; + tty_unlock(tp); + sx_slock(&proctree_lock); + pg = pgfind(*(int *)data); + if (pg != NULL) + PGRP_UNLOCK(pg); + if (pg == NULL || pg->pg_session != td->td_proc->p_session) { + sx_sunlock(&proctree_lock); + tty_lock(tp); + return (EPERM); } - if (tp->t_state & TS_XCLUDE && priv_check(td, - PRIV_TTY_EXCLUSIVE)) - return (EBUSY); - } else { + tty_lock(tp); + /* - * The device isn't open, so there are no conflicts. - * Initialize it. Initialization is done twice in many - * cases: to preempt sleeping callin opens if we are - * callout, and to complete a callin open after DCD rises. + * Determine if this TTY is the controlling TTY after + * relocking the TTY. */ - tp->t_termios = ISCALLOUT(dev) ? tp->t_init_out : tp->t_init_in; - tp->t_cflag = tp->t_termios.c_cflag; - if (tp->t_modem != NULL) - tt_modem(tp, SER_DTR | SER_RTS, 0); - ++tp->t_wopeners; - error = tt_param(tp, &tp->t_termios); - --tp->t_wopeners; - if (error == 0) - error = tt_open(tp, dev); - if (error != 0) - goto out; - if (ISCALLOUT(dev) || (tt_modem(tp, 0, 0) & SER_DCD)) - ttyld_modem(tp, 1); - } - /* - * Wait for DCD if necessary. - */ - if (!(tp->t_state & TS_CARR_ON) && !ISCALLOUT(dev) - && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { - ++tp->t_wopeners; - error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "ttydcd", 0); - --tp->t_wopeners; - if (error != 0 || (tp->t_state & TS_GONE)) - goto out; - goto open_top; - } - error = ttyld_open(tp, dev); - ttyldoptim(tp); - if (tp->t_state & TS_ISOPEN && ISCALLOUT(dev)) - tp->t_actout = TRUE; -out: - splx(s); - if (!(tp->t_state & TS_ISOPEN) && tp->t_wopeners == 0) - tt_close(tp); - return (error); -} + if (!tty_is_ctty(tp, td->td_proc)) { + sx_sunlock(&proctree_lock); + return (ENOTTY); + } + tp->t_pgrp = pg; + sx_sunlock(&proctree_lock); -int -ttyclose(struct cdev *dev, int flag, int mode, struct thread *td) -{ - struct tty *tp; + /* Wake up the background process groups. */ + cv_broadcast(&tp->t_bgwait); + return (0); + } + case TIOCFLUSH: { + int flags = *(int *)data; - tp = dev->si_tty; - ttyld_close(tp, flag); - ttyldoptim(tp); - tt_close(tp); - tp->t_do_timestamp = 0; - if (tp->t_pps != NULL) - tp->t_pps->ppsparam.mode = 0; - tty_close(tp); - return (0); -} + if (flags == 0) + flags = (FREAD|FWRITE); + else + flags &= (FREAD|FWRITE); + tty_flush(tp, flags); + return (0); + } + case TIOCDRAIN: + /* Drain TTY output. */ + return tty_drain(tp); + case TIOCCONS: + /* Set terminal as console TTY. */ + if (*(int *)data) { + struct nameidata nd; + int vfslocked; -int -ttyread(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp; + /* + * XXX: TTY won't slip away, but constty would + * really need to be locked! + */ + tty_unlock(tp); - tp = tty_gettp(dev); + if (constty == tp) { + tty_lock(tp); + return (0); + } + if (constty != NULL) { + tty_lock(tp); + return (EBUSY); + } + /* XXX: allow disconnected constty's to be stolen! */ - if (tp == NULL || (tp->t_state & TS_GONE)) - return (ENODEV); - return (ttyld_read(tp, uio, flag)); -} + /* + * Only allow this to work when the user can + * open /dev/console. + */ + NDINIT(&nd, LOOKUP, FOLLOW|LOCKLEAF|MPSAFE, + UIO_SYSSPACE, "/dev/console", td); + if ((error = namei(&nd)) != 0) { + tty_lock(tp); + return (error); + } + vfslocked = NDHASGIANT(&nd); + NDFREE(&nd, NDF_ONLY_PNBUF); -int -ttywrite(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp; + error = VOP_ACCESS(nd.ni_vp, VREAD, td->td_ucred, td); + vput(nd.ni_vp); + VFS_UNLOCK_GIANT(vfslocked); + if (error) { + tty_lock(tp); + return (error); + } - tp = tty_gettp(dev); + constty_set(tp); + tty_lock(tp); + } else if (constty == tp) { + constty_clear(); + } + return (0); + case TIOCGWINSZ: + /* Obtain window size. */ + bcopy(&tp->t_winsize, data, sizeof(struct winsize)); + return (0); + case TIOCSWINSZ: + /* Set window size. */ + if (bcmp(&tp->t_winsize, data, sizeof(struct winsize)) == 0) + return (0); + bcopy(data, &tp->t_winsize, sizeof(struct winsize)); + tty_signal_pgrp(tp, SIGWINCH); + return (0); + case TIOCEXCL: + tp->t_flags |= TF_EXCLUDE; + return (0); + case TIOCNXCL: + tp->t_flags &= ~TF_EXCLUDE; + return (0); + case TIOCOUTQ: + *(unsigned int *)data = ttyoutq_bytesused(&tp->t_outq); + return (0); + case TIOCSTOP: + tp->t_flags |= TF_STOPPED; + return (0); + case TIOCSTART: + tp->t_flags &= ~TF_STOPPED; + ttydevsw_outwakeup(tp); + return (0); + case TIOCSTAT: + tty_info(tp); + return (0); + } - if (tp == NULL || (tp->t_state & TS_GONE)) - return (ENODEV); - return (ttyld_write(tp, uio, flag)); +#ifdef COMPAT_43TTY + return tty_ioctl_compat(tp, cmd, data, td); +#else /* !COMPAT_43TTY */ + return (ENOIOCTL); +#endif /* COMPAT_43TTY */ } int -ttyioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) +tty_ioctl(struct tty *tp, u_long cmd, void *data, struct thread *td) { - struct tty *tp; - int error; - - tp = dev->si_tty; - - if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { - int cc; - struct termios *dt = (struct termios *)data; - struct termios *lt = - ISCALLOUT(dev) ? &tp->t_lock_out : &tp->t_lock_in; - - dt->c_iflag = (tp->t_iflag & lt->c_iflag) - | (dt->c_iflag & ~lt->c_iflag); - dt->c_oflag = (tp->t_oflag & lt->c_oflag) - | (dt->c_oflag & ~lt->c_oflag); - dt->c_cflag = (tp->t_cflag & lt->c_cflag) - | (dt->c_cflag & ~lt->c_cflag); - dt->c_lflag = (tp->t_lflag & lt->c_lflag) - | (dt->c_lflag & ~lt->c_lflag); - for (cc = 0; cc < NCCS; ++cc) - if (lt->c_cc[cc] != 0) - dt->c_cc[cc] = tp->t_cc[cc]; - if (lt->c_ispeed != 0) - dt->c_ispeed = tp->t_ispeed; - if (lt->c_ospeed != 0) - dt->c_ospeed = tp->t_ospeed; - } + int error; - error = ttyld_ioctl(tp, cmd, data, flag, td); + tty_lock_assert(tp, MA_OWNED); + + if (tty_gone(tp)) + return (ENXIO); + + error = ttydevsw_ioctl(tp, cmd, data, td); if (error == ENOIOCTL) - error = ttioctl(tp, cmd, data, flag); - ttyldoptim(tp); - if (error != ENOIOCTL) - return (error); - return (ENOTTY); -} + error = tty_generic_ioctl(tp, cmd, data, td); -void -ttyldoptim(struct tty *tp) -{ - struct termios *t; - - t = &tp->t_termios; - if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) - && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) - && (!(t->c_iflag & PARMRK) - || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) - && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) - && linesw[tp->t_line]->l_rint == ttyinput) - tp->t_state |= TS_CAN_BYPASS_L_RINT; - else - tp->t_state &= ~TS_CAN_BYPASS_L_RINT; + return (error); } -static void -ttydtrwaitwakeup(void *arg) +dev_t +tty_udev(struct tty *tp) { - struct tty *tp; - - tp = arg; - tp->t_state &= ~TS_DTR_WAIT; - wakeup(&tp->t_dtr_wait); + if (tp->t_dev) + return dev2udev(tp->t_dev); + else + return NODEV; } - -void -ttydtrwaitstart(struct tty *tp) +int +tty_checkoutq(struct tty *tp) { - if (tp->t_dtr_wait == 0) - return; - if (tp->t_state & TS_DTR_WAIT) - return; - timeout(ttydtrwaitwakeup, tp, tp->t_dtr_wait); - tp->t_state |= TS_DTR_WAIT; + /* 256 bytes should be enough to print a log message. */ + return (ttyoutq_bytesleft(&tp->t_outq) >= 256); } -int -ttydtrwaitsleep(struct tty *tp) +void +tty_hiwat_in_block(struct tty *tp) { - int error; - error = 0; - while (error == 0) { - if (tp->t_state & TS_GONE) - error = ENXIO; - else if (!(tp->t_state & TS_DTR_WAIT)) - break; - else - error = tsleep(&tp->t_dtr_wait, TTIPRI | PCATCH, - "dtrwait", 0); + if ((tp->t_flags & TF_HIWAT_IN) == 0 && + tp->t_termios.c_iflag & IXOFF && + tp->t_termios.c_cc[VSTOP] != _POSIX_VDISABLE) { + /* + * Input flow control. Only enter the high watermark when we + * can successfully store the VSTOP character. + */ + if (ttyoutq_write_nofrag(&tp->t_outq, + &tp->t_termios.c_cc[VSTOP], 1) == 0) + tp->t_flags |= TF_HIWAT_IN; + } else { + /* No input flow control. */ + tp->t_flags |= TF_HIWAT_IN; } - return (error); } -static int -ttysopen(struct cdev *dev, int flag, int mode, struct thread *td) +void +tty_hiwat_in_unblock(struct tty *tp) { - struct tty *tp; - - tp = dev->si_tty; - KASSERT(tp != NULL, - ("ttysopen(): no tty pointer on device (%s)", devtoname(dev))); - if (tp->t_state & TS_GONE) - return (ENODEV); - return (0); -} -static int -ttysclose(struct cdev *dev, int flag, int mode, struct thread *td) -{ + if ((tp->t_flags & TF_HIWAT_IN) == 0 && + tp->t_termios.c_iflag & IXOFF && + tp->t_termios.c_cc[VSTART] != _POSIX_VDISABLE) { + /* + * Input flow control. Only leave the high watermark when we + * can successfully store the VSTART character. + */ + if (ttyoutq_write_nofrag(&tp->t_outq, + &tp->t_termios.c_cc[VSTART], 1) == 0) + tp->t_flags &= ~TF_HIWAT_IN; + } else { + /* No input flow control. */ + tp->t_flags &= ~TF_HIWAT_IN; + } - return (0); + if (!tty_gone(tp)) + ttydevsw_inwakeup(tp); } -static int -ttysrdwr(struct cdev *dev, struct uio *uio, int flag) -{ +#include "opt_ddb.h" +#ifdef DDB +#include <ddb/ddb.h> - return (ENODEV); -} +static struct { + int flag; + char val; +} ttystates[] = { +#if 0 + { TF_NOPREFIX, 'N' }, +#endif + { TF_INITLOCK, 'I' }, + { TF_CALLOUT, 'C' }, + + /* Keep these together -> 'Oi' and 'Oo'. */ + { TF_OPENED, 'O' }, + { TF_OPENED_IN, 'i' }, + { TF_OPENED_OUT,'o' }, + + { TF_GONE, 'G' }, + { TF_OPENCLOSE, 'B' }, + { TF_ASYNC, 'Y' }, + { TF_LITERAL, 'L' }, + + /* Keep these together -> 'Hi' and 'Ho'. */ + { TF_HIWAT, 'H' }, + { TF_HIWAT_IN, 'i' }, + { TF_HIWAT_OUT, 'o' }, + + { TF_STOPPED, 'S' }, + { TF_EXCLUDE, 'X' }, + { TF_BYPASS, 'l' }, + { TF_ZOMBIE, 'Z' }, + + { 0, '\0' }, +}; -static int -ttysioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) +/* DDB command to show TTY statistics. */ +DB_SHOW_COMMAND(ttys, db_show_ttys) { - struct tty *tp; - int error; - struct termios *ct; - - tp = dev->si_tty; - KASSERT(tp != NULL, - ("ttysopen(): no tty pointer on device (%s)", devtoname(dev))); - if (tp->t_state & TS_GONE) - return (ENODEV); - ct = dev->si_drv2; - switch (cmd) { - case TIOCSETA: - error = priv_check(td, PRIV_TTY_SETA); - if (error != 0) - return (error); - *ct = *(struct termios *)data; - return (0); - case TIOCGETA: - *(struct termios *)data = *ct; - return (0); - case TIOCGETD: - *(int *)data = TTYDISC; - return (0); - case TIOCGWINSZ: - bzero(data, sizeof(struct winsize)); - return (0); - default: - if (tp->t_cioctl != NULL) - return(tp->t_cioctl(dev, cmd, data, flag, td)); - return (ENOTTY); + struct tty *tp; + size_t isiz, osiz; + int i, j; + + /* Make the output look like `pstat -t'. */ + db_printf(" LINE INQ CAN LIN LOW OUTQ USE LOW " + "COL SESS PGID STATE\n"); + + TAILQ_FOREACH(tp, &tty_list, t_list) { + isiz = tp->t_inq.ti_nblocks * TTYINQ_DATASIZE; + osiz = tp->t_outq.to_nblocks * TTYOUTQ_DATASIZE; + + db_printf("%10s %5zu %4u %4u %4zu %5zu %4u %4zu %5u %5d %5d ", + tty_devname(tp), + isiz, + tp->t_inq.ti_linestart - tp->t_inq.ti_begin, + tp->t_inq.ti_end - tp->t_inq.ti_linestart, + isiz - tp->t_inlow, + osiz, + tp->t_outq.to_end - tp->t_outq.to_begin, + osiz - tp->t_outlow, + tp->t_column, + tp->t_session ? tp->t_session->s_sid : 0, + tp->t_pgrp ? tp->t_pgrp->pg_id : 0); + + /* Flag bits. */ + for (i = j = 0; ttystates[i].flag; i++) + if (tp->t_flags & ttystates[i].flag) { + db_printf("%c", ttystates[i].val); + j++; + } + if (j == 0) + db_printf("-"); + db_printf("\n"); } } - -/* - * Initialize a tty to sane modes. - */ -void -ttyinitmode(struct tty *tp, int echo, int speed) -{ - - if (speed == 0) - speed = TTYDEF_SPEED; - tp->t_init_in.c_iflag = TTYDEF_IFLAG; - tp->t_init_in.c_oflag = TTYDEF_OFLAG; - tp->t_init_in.c_cflag = TTYDEF_CFLAG; - if (echo) - tp->t_init_in.c_lflag = TTYDEF_LFLAG_ECHO; - else - tp->t_init_in.c_lflag = TTYDEF_LFLAG_NOECHO; - - tp->t_init_in.c_ispeed = tp->t_init_in.c_ospeed = speed; - termioschars(&tp->t_init_in); - tp->t_init_out = tp->t_init_in; - tp->t_termios = tp->t_init_in; -} - -/* - * Use more "normal" termios paramters for consoles. - */ -void -ttyconsolemode(struct tty *tp, int speed) -{ - - if (speed == 0) - speed = TTYDEF_SPEED; - ttyinitmode(tp, 1, speed); - tp->t_init_in.c_cflag |= CLOCAL; - tp->t_lock_out.c_cflag = tp->t_lock_in.c_cflag = CLOCAL; - tp->t_lock_out.c_ispeed = tp->t_lock_out.c_ospeed = - tp->t_lock_in.c_ispeed = tp->t_lock_in.c_ospeed = speed; - tp->t_init_out = tp->t_init_in; - tp->t_termios = tp->t_init_in; - ttsetwater(tp); -} - -/* - * Record the relationship between the serial ports notion of modem control - * signals and the one used in certain ioctls in a way the compiler can enforce - * XXX: We should define TIOCM_* in terms of SER_ if we can limit the - * XXX: consequences of the #include work that would take. - */ -CTASSERT(SER_DTR == TIOCM_DTR / 2); -CTASSERT(SER_RTS == TIOCM_RTS / 2); -CTASSERT(SER_STX == TIOCM_ST / 2); -CTASSERT(SER_SRX == TIOCM_SR / 2); -CTASSERT(SER_CTS == TIOCM_CTS / 2); -CTASSERT(SER_DCD == TIOCM_DCD / 2); -CTASSERT(SER_RI == TIOCM_RI / 2); -CTASSERT(SER_DSR == TIOCM_DSR / 2); - +#endif /* DDB */ diff --git a/sys/kern/tty_compat.c b/sys/kern/tty_compat.c index 4e2c16f..6736cfb 100644 --- a/sys/kern/tty_compat.c +++ b/sys/kern/tty_compat.c @@ -45,6 +45,11 @@ __FBSDID("$FreeBSD$"); #include <sys/kernel.h> #include <sys/sysctl.h> +struct speedtab { + int sp_speed; /* Speed. */ + int sp_code; /* Code. */ +}; + static int ttcompatgetflags(struct tty *tp); static void ttcompatsetflags(struct tty *tp, struct termios *t); static void ttcompatsetlflags(struct tty *tp, struct termios *t); @@ -102,16 +107,18 @@ ttsetcompat(struct tty *tp, u_long *com, caddr_t data, struct termios *term) if ((speed = sg->sg_ispeed) > MAX_SPEED || speed < 0) return(EINVAL); - else if (speed != ttcompatspeedtab(tp->t_ispeed, compatspeeds)) + else if (speed != ttcompatspeedtab(tp->t_termios.c_ispeed, + compatspeeds)) term->c_ispeed = compatspcodes[speed]; else - term->c_ispeed = tp->t_ispeed; + term->c_ispeed = tp->t_termios.c_ispeed; if ((speed = sg->sg_ospeed) > MAX_SPEED || speed < 0) return(EINVAL); - else if (speed != ttcompatspeedtab(tp->t_ospeed, compatspeeds)) + else if (speed != ttcompatspeedtab(tp->t_termios.c_ospeed, + compatspeeds)) term->c_ospeed = compatspcodes[speed]; else - term->c_ospeed = tp->t_ospeed; + term->c_ospeed = tp->t_termios.c_ospeed; term->c_cc[VERASE] = sg->sg_erase; term->c_cc[VKILL] = sg->sg_kill; tp->t_flags = (tp->t_flags&0xffff0000) | (sg->sg_flags&0xffff); @@ -171,7 +178,7 @@ ttsetcompat(struct tty *tp, u_long *com, caddr_t data, struct termios *term) /*ARGSUSED*/ int -ttcompat(struct tty *tp, u_long com, caddr_t data, int flag) +tty_ioctl_compat(struct tty *tp, u_long com, caddr_t data, struct thread *td) { switch (com) { case TIOCSETP: @@ -187,17 +194,19 @@ ttcompat(struct tty *tp, u_long com, caddr_t data, int flag) term = tp->t_termios; if ((error = ttsetcompat(tp, &com, data, &term)) != 0) return error; - return ttioctl(tp, com, &term, flag); + return tty_ioctl(tp, com, &term, td); } case TIOCGETP: { struct sgttyb *sg = (struct sgttyb *)data; - cc_t *cc = tp->t_cc; + cc_t *cc = tp->t_termios.c_cc; - sg->sg_ospeed = ttcompatspeedtab(tp->t_ospeed, compatspeeds); - if (tp->t_ispeed == 0) + sg->sg_ospeed = ttcompatspeedtab(tp->t_termios.c_ospeed, + compatspeeds); + if (tp->t_termios.c_ispeed == 0) sg->sg_ispeed = sg->sg_ospeed; else - sg->sg_ispeed = ttcompatspeedtab(tp->t_ispeed, compatspeeds); + sg->sg_ispeed = ttcompatspeedtab(tp->t_termios.c_ispeed, + compatspeeds); sg->sg_erase = cc[VERASE]; sg->sg_kill = cc[VKILL]; sg->sg_flags = tp->t_flags = ttcompatgetflags(tp); @@ -205,7 +214,7 @@ ttcompat(struct tty *tp, u_long com, caddr_t data, int flag) } case TIOCGETC: { struct tchars *tc = (struct tchars *)data; - cc_t *cc = tp->t_cc; + cc_t *cc = tp->t_termios.c_cc; tc->t_intrc = cc[VINTR]; tc->t_quitc = cc[VQUIT]; @@ -217,7 +226,7 @@ ttcompat(struct tty *tp, u_long com, caddr_t data, int flag) } case TIOCGLTC: { struct ltchars *ltc = (struct ltchars *)data; - cc_t *cc = tp->t_cc; + cc_t *cc = tp->t_termios.c_cc; ltc->t_suspc = cc[VSUSP]; ltc->t_dsuspc = cc[VDSUSP]; @@ -237,19 +246,19 @@ ttcompat(struct tty *tp, u_long com, caddr_t data, int flag) break; case OTIOCGETD: - *(int *)data = tp->t_line ? tp->t_line : 2; + *(int *)data = 2; break; case OTIOCSETD: { int ldisczero = 0; - return (ttioctl(tp, TIOCSETD, - *(int *)data == 2 ? (caddr_t)&ldisczero : data, flag)); + return (tty_ioctl(tp, TIOCSETD, + *(int *)data == 2 ? (caddr_t)&ldisczero : data, td)); } case OTIOCCONS: *(int *)data = 1; - return (ttioctl(tp, TIOCCONS, data, flag)); + return (tty_ioctl(tp, TIOCCONS, data, td)); default: return (ENOIOCTL); @@ -260,10 +269,10 @@ ttcompat(struct tty *tp, u_long com, caddr_t data, int flag) static int ttcompatgetflags(struct tty *tp) { - tcflag_t iflag = tp->t_iflag; - tcflag_t lflag = tp->t_lflag; - tcflag_t oflag = tp->t_oflag; - tcflag_t cflag = tp->t_cflag; + tcflag_t iflag = tp->t_termios.c_iflag; + tcflag_t lflag = tp->t_termios.c_lflag; + tcflag_t oflag = tp->t_termios.c_oflag; + tcflag_t cflag = tp->t_termios.c_cflag; int flags = 0; if (iflag&IXOFF) @@ -299,7 +308,7 @@ ttcompatgetflags(struct tty *tp) flags |= MDMBUF; if ((cflag&HUPCL) == 0) flags |= NOHANG; - if (oflag&OXTABS) + if (oflag&TAB3) flags |= XTABS; if (lflag&ECHOE) flags |= CRTERA|CRTBS; @@ -334,9 +343,9 @@ ttcompatsetflags(struct tty *tp, struct termios *t) iflag |= BRKINT|IXON|IMAXBEL; lflag |= ISIG|IEXTEN|ECHOCTL; /* XXX was echoctl on ? */ if (flags & XTABS) - oflag |= OXTABS; + oflag |= TAB3; else - oflag &= ~OXTABS; + oflag &= ~TAB3; if (flags & CBREAK) lflag &= ~ICANON; else diff --git a/sys/kern/tty_conf.c b/sys/kern/tty_conf.c deleted file mode 100644 index 77a9593..0000000 --- a/sys/kern/tty_conf.c +++ /dev/null @@ -1,205 +0,0 @@ -/*- - * Copyright (c) 2004 Poul-Henning Kamp. All rights reserved. - * Copyright (c) 1982, 1986, 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. - * 4. 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_conf.c 8.4 (Berkeley) 1/21/94 - */ - -#include <sys/cdefs.h> -__FBSDID("$FreeBSD$"); - -#include "opt_compat.h" - -#include <sys/param.h> -#include <sys/systm.h> -#include <sys/tty.h> -#include <sys/conf.h> - -#ifndef MAXLDISC -#define MAXLDISC 9 -#endif - -static l_open_t l_noopen; -static l_close_t l_noclose; -static l_rint_t l_norint; -static l_start_t l_nostart; - -/* - * XXX it probably doesn't matter what the entries other than the l_open - * entry are here. The l_nullioctl and ttymodem entries still look fishy. - * Reconsider the removal of nullmodem anyway. It was too much like - * ttymodem, but a completely null version might be useful. - */ - -static struct linesw nodisc = { - .l_open = l_noopen, - .l_close = l_noclose, - .l_read = l_noread, - .l_write = l_nowrite, - .l_ioctl = l_nullioctl, - .l_rint = l_norint, - .l_start = l_nostart, - .l_modem = ttymodem -}; - -static struct linesw termios_disc = { - .l_open = tty_open, - .l_close = ttylclose, - .l_read = ttread, - .l_write = ttwrite, - .l_ioctl = l_nullioctl, - .l_rint = ttyinput, - .l_start = ttstart, - .l_modem = ttymodem -}; - -#ifdef COMPAT_43 -# define ntty_disc termios_disc -#else -# define ntty_disc nodisc -#endif - -struct linesw *linesw[MAXLDISC] = { - &termios_disc, /* 0 - termios */ - &nodisc, /* 1 - defunct */ - &ntty_disc, /* 2 - NTTYDISC */ - &nodisc, /* 3 - loadable */ - &nodisc, /* 4 - SLIPDISC */ - &nodisc, /* 5 - PPPDISC */ - &nodisc, /* 6 - NETGRAPHDISC */ - &nodisc, /* 7 - loadable */ - &nodisc, /* 8 - loadable */ -}; - -int nlinesw = sizeof (linesw) / sizeof (linesw[0]); - -#define LOADABLE_LDISC 7 - -/* - * ldisc_register: Register a line discipline. - * - * discipline: Index for discipline to load, or LDISC_LOAD for us to choose. - * linesw_p: Pointer to linesw_p. - * - * Returns: Index used or -1 on failure. - */ - -int -ldisc_register(int discipline, struct linesw *linesw_p) -{ - int slot = -1; - - if (discipline == LDISC_LOAD) { - int i; - for (i = LOADABLE_LDISC; i < MAXLDISC; i++) - if (linesw[i] == &nodisc) { - slot = i; - break; - } - } else if (discipline >= 0 && discipline < MAXLDISC) { - slot = discipline; - } - - if (slot != -1 && linesw_p) - linesw[slot] = linesw_p; - - return slot; -} - -/* - * ldisc_deregister: Deregister a line discipline obtained with - * ldisc_register. - * - * discipline: Index for discipline to unload. - */ - -void -ldisc_deregister(int discipline) -{ - - if (discipline < MAXLDISC) - linesw[discipline] = &nodisc; -} - -/* - * "no" and "null" versions of line discipline functions - */ - -static int -l_noopen(struct cdev *dev, struct tty *tp) -{ - - return (ENODEV); -} - -static int -l_noclose(struct tty *tp, int flag) -{ - - return (ENODEV); -} - -int -l_noread(struct tty *tp, struct uio *uio, int flag) -{ - - return (ENODEV); -} - -int -l_nowrite(struct tty *tp, struct uio *uio, int flag) -{ - - return (ENODEV); -} - -static int -l_norint(int c, struct tty *tp) -{ - - return (ENODEV); -} - -static int -l_nostart(struct tty *tp) -{ - - return (ENODEV); -} - -int -l_nullioctl(struct tty *tp, u_long cmd, char *data, int flags, struct thread *td) -{ - - return (ENOIOCTL); -} diff --git a/sys/kern/tty_cons.c b/sys/kern/tty_cons.c index ff57d82..81e1708 100644 --- a/sys/kern/tty_cons.c +++ b/sys/kern/tty_cons.c @@ -711,9 +711,18 @@ constty_timeout(void *arg) { int c; - while (constty != NULL && (c = msgbuf_getchar(&consmsgbuf)) != -1) { - if (tputchar(c, constty) < 0) - constty = NULL; + if (constty != NULL) { + tty_lock(constty); + while ((c = msgbuf_getchar(&consmsgbuf)) != -1) { + if (tty_putchar(constty, c) < 0) { + tty_unlock(constty); + constty = NULL; + break; + } + } + + if (constty != NULL) + tty_unlock(constty); } if (constty != NULL) { callout_reset(&conscallout, hz / constty_wakeups_per_second, diff --git a/sys/kern/tty_info.c b/sys/kern/tty_info.c index 7da932d..de06775 100644 --- a/sys/kern/tty_info.c +++ b/sys/kern/tty_info.c @@ -211,7 +211,7 @@ proc_compare(struct proc *p1, struct proc *p2) * Report on state of foreground process group. */ void -ttyinfo(struct tty *tp) +tty_info(struct tty *tp) { struct timeval utime, stime; struct proc *p, *pick; @@ -223,32 +223,27 @@ ttyinfo(struct tty *tp) char comm[MAXCOMLEN + 1]; struct rusage ru; - if (ttycheckoutq(tp,0) == 0) + tty_lock_assert(tp, MA_OWNED); + + if (tty_checkoutq(tp) == 0) return; /* Print load average. */ load = (averunnable.ldavg[0] * 100 + FSCALE / 2) >> FSHIFT; ttyprintf(tp, "load: %d.%02d ", load / 100, load % 100); - /* - * On return following a ttyprintf(), we set tp->t_rocount to 0 so - * that pending input will be retyped on BS. - */ if (tp->t_session == NULL) { ttyprintf(tp, "not a controlling terminal\n"); - tp->t_rocount = 0; return; } if (tp->t_pgrp == NULL) { ttyprintf(tp, "no foreground process group\n"); - tp->t_rocount = 0; return; } PGRP_LOCK(tp->t_pgrp); if (LIST_EMPTY(&tp->t_pgrp->pg_members)) { PGRP_UNLOCK(tp->t_pgrp); ttyprintf(tp, "empty foreground process group\n"); - tp->t_rocount = 0; return; } @@ -313,5 +308,4 @@ ttyinfo(struct tty *tp) (long)utime.tv_sec, utime.tv_usec / 10000, (long)stime.tv_sec, stime.tv_usec / 10000, pctcpu / 100, rss); - tp->t_rocount = 0; } diff --git a/sys/kern/tty_inq.c b/sys/kern/tty_inq.c new file mode 100644 index 0000000..b93dde6 --- /dev/null +++ b/sys/kern/tty_inq.c @@ -0,0 +1,502 @@ +/*- + * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/queue.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/tty.h> +#include <sys/uio.h> + +#include <vm/uma.h> + +/* + * TTY input queue buffering. + * + * Unlike the output queue, the input queue has more features that are + * needed to properly implement various features offered by the TTY + * interface: + * + * - Data can be removed from the tail of the queue, which is used to + * implement backspace. + * - Once in a while, input has to be `canonicalized'. When ICANON is + * turned on, this will be done after a CR has been inserted. + * Otherwise, it should be done after any character has been inserted. + * - The input queue can store one bit per byte, called the quoting bit. + * This bit is used by TTYDISC to make backspace work on quoted + * characters. + * + * In most cases, there is probably less input than output, so unlike + * the outq, we'll stick to 128 byte blocks here. + */ + +/* Statistics. */ +static long ttyinq_nfast = 0; +SYSCTL_LONG(_kern, OID_AUTO, tty_inq_nfast, CTLFLAG_RD, + &ttyinq_nfast, 0, "Unbuffered reads to userspace on input"); +static long ttyinq_nslow = 0; +SYSCTL_LONG(_kern, OID_AUTO, tty_inq_nslow, CTLFLAG_RD, + &ttyinq_nslow, 0, "Buffered reads to userspace on input"); + +#define TTYINQ_QUOTESIZE (TTYINQ_DATASIZE / BMSIZE) +#define BMSIZE 32 +#define GETBIT(tib,boff) \ + ((tib)->tib_quotes[(boff) / BMSIZE] & (1 << ((boff) % BMSIZE))) +#define SETBIT(tib,boff) \ + ((tib)->tib_quotes[(boff) / BMSIZE] |= (1 << ((boff) % BMSIZE))) +#define CLRBIT(tib,boff) \ + ((tib)->tib_quotes[(boff) / BMSIZE] &= ~(1 << ((boff) % BMSIZE))) + +struct ttyinq_block { + TAILQ_ENTRY(ttyinq_block) tib_list; + uint32_t tib_quotes[TTYINQ_QUOTESIZE]; + char tib_data[TTYINQ_DATASIZE]; +}; + +static uma_zone_t ttyinq_zone; + +void +ttyinq_setsize(struct ttyinq *ti, struct tty *tp, size_t size) +{ + unsigned int nblocks; + struct ttyinq_block *tib; + + nblocks = howmany(size, TTYINQ_DATASIZE); + + while (nblocks > ti->ti_nblocks) { + /* + * List is getting bigger. + * Add new blocks to the tail of the list. + * + * We must unlock the TTY temporarily, because we need + * to allocate memory. This won't be a problem, because + * in the worst case, another thread ends up here, which + * may cause us to allocate too many blocks, but this + * will be caught by the loop below. + */ + tty_unlock(tp); + tib = uma_zalloc(ttyinq_zone, M_WAITOK); + tty_lock(tp); + + if (tty_gone(tp)) + return; + + TAILQ_INSERT_TAIL(&ti->ti_list, tib, tib_list); + ti->ti_nblocks++; + } + + while (nblocks < ti->ti_nblocks) { + /* + * List is getting smaller. Remove unused blocks at the + * end. This means we cannot guarantee this routine + * shrinks buffers properly, when we need to reclaim + * more space than there is available. + * + * XXX TODO: Two solutions here: + * - Throw data away + * - Temporarily hit the watermark until enough data has + * been flushed, so we can remove the blocks. + */ + + if (ti->ti_end == 0) + tib = TAILQ_FIRST(&ti->ti_list); + else + tib = TAILQ_NEXT(ti->ti_lastblock, tib_list); + if (tib == NULL) + break; + TAILQ_REMOVE(&ti->ti_list, tib, tib_list); + uma_zfree(ttyinq_zone, tib); + ti->ti_nblocks--; + } +} + +int +ttyinq_read_uio(struct ttyinq *ti, struct tty *tp, struct uio *uio, + size_t rlen, size_t flen) +{ + + MPASS(rlen <= uio->uio_resid); + + while (rlen > 0) { + int error; + struct ttyinq_block *tib; + size_t cbegin, cend, clen; + + /* See if there still is data. */ + if (ti->ti_begin == ti->ti_linestart) + return (0); + tib = TAILQ_FIRST(&ti->ti_list); + if (tib == NULL) + return (0); + + /* + * The end address should be the lowest of these three: + * - The write pointer + * - The blocksize - we can't read beyond the block + * - The end address if we could perform the full read + */ + cbegin = ti->ti_begin; + cend = MIN(MIN(ti->ti_linestart, ti->ti_begin + rlen), + TTYINQ_DATASIZE); + clen = cend - cbegin; + MPASS(clen >= flen); + rlen -= clen; + + /* + * We can prevent buffering in some cases: + * - We need to read the block until the end. + * - We don't need to read the block until the end, but + * there is no data beyond it, which allows us to move + * the write pointer to a new block. + */ + if (cend == TTYINQ_DATASIZE || cend == ti->ti_end) { + atomic_add_long(&ttyinq_nfast, 1); + + /* + * Fast path: zero copy. Remove the first block, + * so we can unlock the TTY temporarily. + */ + TAILQ_REMOVE(&ti->ti_list, tib, tib_list); + ti->ti_nblocks--; + ti->ti_begin = 0; + + /* + * Because we remove the first block, we must + * fix up the block offsets. + */ +#define CORRECT_BLOCK(t) do { \ + if (t <= TTYINQ_DATASIZE) { \ + t = 0; \ + } else { \ + t -= TTYINQ_DATASIZE; \ + } \ +} while (0) + CORRECT_BLOCK(ti->ti_linestart); + CORRECT_BLOCK(ti->ti_reprint); + CORRECT_BLOCK(ti->ti_end); +#undef CORRECT_BLOCK + + /* + * Temporary unlock and copy the data to + * userspace. We may need to flush trailing + * bytes, like EOF characters. + */ + tty_unlock(tp); + error = uiomove(tib->tib_data + cbegin, + clen - flen, uio); + tty_lock(tp); + + if (tty_gone(tp)) { + /* Something went bad - discard this block. */ + uma_zfree(ttyinq_zone, tib); + return (ENXIO); + } + /* Block can now be readded to the list. */ + /* + * XXX: we could remove the blocks here when the + * queue was shrunk, but still in use. See + * ttyinq_setsize(). + */ + TAILQ_INSERT_TAIL(&ti->ti_list, tib, tib_list); + ti->ti_nblocks++; + if (error != 0) + return (error); + } else { + char ob[TTYINQ_DATASIZE - 1]; + atomic_add_long(&ttyinq_nslow, 1); + + /* + * Slow path: store data in a temporary buffer. + */ + memcpy(ob, tib->tib_data + cbegin, clen - flen); + ti->ti_begin += clen; + MPASS(ti->ti_begin < TTYINQ_DATASIZE); + + /* Temporary unlock and copy the data to userspace. */ + tty_unlock(tp); + error = uiomove(ob, clen - flen, uio); + tty_lock(tp); + + if (error != 0) + return (error); + if (tty_gone(tp)) + return (ENXIO); + } + } + + return (0); +} + +static __inline void +ttyinq_set_quotes(struct ttyinq_block *tib, size_t offset, + size_t length, int value) +{ + + if (value) { + /* Set the bits. */ + for (; length > 0; length--, offset++) + SETBIT(tib, offset); + } else { + /* Unset the bits. */ + for (; length > 0; length--, offset++) + CLRBIT(tib, offset); + } +} + +size_t +ttyinq_write(struct ttyinq *ti, const void *buf, size_t nbytes, int quote) +{ + const char *cbuf = buf; + struct ttyinq_block *tib; + unsigned int boff; + size_t l; + + while (nbytes > 0) { + tib = ti->ti_lastblock; + boff = ti->ti_end % TTYINQ_DATASIZE; + + if (ti->ti_end == 0) { + /* First time we're being used or drained. */ + MPASS(ti->ti_begin == 0); + tib = ti->ti_lastblock = TAILQ_FIRST(&ti->ti_list); + if (tib == NULL) { + /* Queue has no blocks. */ + break; + } + } else if (boff == 0) { + /* We reached the end of this block on last write. */ + tib = TAILQ_NEXT(tib, tib_list); + if (tib == NULL) { + /* We've reached the watermark. */ + break; + } + ti->ti_lastblock = tib; + } + + /* Don't copy more than was requested. */ + l = MIN(nbytes, TTYINQ_DATASIZE - boff); + MPASS(l > 0); + memcpy(tib->tib_data + boff, cbuf, l); + + /* Set the quoting bits for the proper region. */ + ttyinq_set_quotes(tib, boff, l, quote); + + cbuf += l; + nbytes -= l; + ti->ti_end += l; + } + + return (cbuf - (const char *)buf); +} + +int +ttyinq_write_nofrag(struct ttyinq *ti, const void *buf, size_t nbytes, int quote) +{ + size_t ret; + + if (ttyinq_bytesleft(ti) < nbytes) + return (-1); + + /* We should always be able to write it back. */ + ret = ttyinq_write(ti, buf, nbytes, quote); + MPASS(ret == nbytes); + + return (0); +} + +void +ttyinq_canonicalize(struct ttyinq *ti) +{ + + ti->ti_linestart = ti->ti_reprint = ti->ti_end; + ti->ti_startblock = ti->ti_reprintblock = ti->ti_lastblock; +} + +size_t +ttyinq_findchar(struct ttyinq *ti, const char *breakc, size_t maxlen, + char *lastc) +{ + struct ttyinq_block *tib = TAILQ_FIRST(&ti->ti_list); + unsigned int boff = ti->ti_begin; + unsigned int bend = MIN(MIN(TTYINQ_DATASIZE, ti->ti_linestart), + ti->ti_begin + maxlen); + + MPASS(maxlen > 0); + + if (tib == NULL) + return (0); + + while (boff < bend) { + if (index(breakc, tib->tib_data[boff]) && !GETBIT(tib, boff)) { + *lastc = tib->tib_data[boff]; + return (boff - ti->ti_begin + 1); + } + boff++; + } + + /* Not found - just process the entire block. */ + return (bend - ti->ti_begin); +} + +void +ttyinq_flush(struct ttyinq *ti) +{ + + ti->ti_begin = 0; + ti->ti_linestart = 0; + ti->ti_reprint = 0; + ti->ti_end = 0; +} + +#if 0 +void +ttyinq_flush_safe(struct ttyinq *ti) +{ + struct ttyinq_block *tib; + + ttyinq_flush(ti); + + /* Zero all data in the input queue to make it more safe */ + TAILQ_FOREACH(tib, &ti->ti_list, tib_list) { + bzero(&tib->tib_quotes, sizeof tib->tib_quotes); + bzero(&tib->tib_data, sizeof tib->tib_data); + } +} +#endif + +int +ttyinq_peekchar(struct ttyinq *ti, char *c, int *quote) +{ + unsigned int boff; + struct ttyinq_block *tib = ti->ti_lastblock; + + if (ti->ti_linestart == ti->ti_end) + return (-1); + + MPASS(ti->ti_end > 0); + boff = (ti->ti_end - 1) % TTYINQ_DATASIZE; + + *c = tib->tib_data[boff]; + *quote = GETBIT(tib, boff); + + return (0); +} + +void +ttyinq_unputchar(struct ttyinq *ti) +{ + + MPASS(ti->ti_linestart < ti->ti_end); + + if (--ti->ti_end % TTYINQ_DATASIZE == 0) { + /* Roll back to the previous block. */ + ti->ti_lastblock = TAILQ_PREV(ti->ti_lastblock, + ttyinq_bhead, tib_list); + /* + * This can only fail if we are unputchar()'ing the + * first character in the queue. + */ + MPASS((ti->ti_lastblock == NULL) == (ti->ti_end == 0)); + } +} + +void +ttyinq_reprintpos_set(struct ttyinq *ti) +{ + + ti->ti_reprint = ti->ti_end; + ti->ti_reprintblock = ti->ti_lastblock; +} + +void +ttyinq_reprintpos_reset(struct ttyinq *ti) +{ + + ti->ti_reprint = ti->ti_linestart; + ti->ti_reprintblock = ti->ti_startblock; +} + +static void +ttyinq_line_iterate(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, void *data, + unsigned int offset, struct ttyinq_block *tib) +{ + unsigned int boff; + + /* Use the proper block when we're at the queue head. */ + if (offset == 0) + tib = TAILQ_FIRST(&ti->ti_list); + + /* Iterate all characters and call the iterator function. */ + for (; offset < ti->ti_end; offset++) { + boff = offset % TTYINQ_DATASIZE; + MPASS(tib != NULL); + + /* Call back the iterator function. */ + iterator(data, tib->tib_data[boff], GETBIT(tib, boff)); + + /* Last byte iterated - go to the next block. */ + if (boff == TTYINQ_DATASIZE - 1) + tib = TAILQ_NEXT(tib, tib_list); + MPASS(tib != NULL); + } +} + +void +ttyinq_line_iterate_from_linestart(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, void *data) +{ + + ttyinq_line_iterate(ti, iterator, data, + ti->ti_linestart, ti->ti_startblock); +} + +void +ttyinq_line_iterate_from_reprintpos(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, void *data) +{ + + ttyinq_line_iterate(ti, iterator, data, + ti->ti_reprint, ti->ti_reprintblock); +} + +static void +ttyinq_startup(void *dummy) +{ + + ttyinq_zone = uma_zcreate("ttyinq", sizeof(struct ttyinq_block), + NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); +} + +SYSINIT(ttyinq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyinq_startup, NULL); diff --git a/sys/kern/tty_outq.c b/sys/kern/tty_outq.c new file mode 100644 index 0000000..e945cca --- /dev/null +++ b/sys/kern/tty_outq.c @@ -0,0 +1,365 @@ +/*- + * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/queue.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/tty.h> +#include <sys/uio.h> + +#include <vm/uma.h> + +/* + * TTY output queue buffering. + * + * The previous design of the TTY layer offered the so-called clists. + * These clists were used for both the input queues and the output + * queue. We don't use certain features on the output side, like quoting + * bits for parity marking and such. This mechanism is similar to the + * old clists, but only contains the features we need to buffer the + * output. + */ + +/* Statistics. */ +static long ttyoutq_nfast = 0; +SYSCTL_LONG(_kern, OID_AUTO, tty_outq_nfast, CTLFLAG_RD, + &ttyoutq_nfast, 0, "Unbuffered reads to userspace on output"); +static long ttyoutq_nslow = 0; +SYSCTL_LONG(_kern, OID_AUTO, tty_outq_nslow, CTLFLAG_RD, + &ttyoutq_nslow, 0, "Buffered reads to userspace on output"); + +struct ttyoutq_block { + STAILQ_ENTRY(ttyoutq_block) tob_list; + char tob_data[TTYOUTQ_DATASIZE]; +}; + +static uma_zone_t ttyoutq_zone; + +void +ttyoutq_flush(struct ttyoutq *to) +{ + + to->to_begin = 0; + to->to_end = 0; +} + +void +ttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t size) +{ + unsigned int nblocks; + struct ttyoutq_block *tob; + + nblocks = howmany(size, TTYOUTQ_DATASIZE); + + while (nblocks > to->to_nblocks) { + /* + * List is getting bigger. + * Add new blocks to the tail of the list. + * + * We must unlock the TTY temporarily, because we need + * to allocate memory. This won't be a problem, because + * in the worst case, another thread ends up here, which + * may cause us to allocate too many blocks, but this + * will be caught by the loop below. + */ + tty_unlock(tp); + tob = uma_zalloc(ttyoutq_zone, M_WAITOK); + tty_lock(tp); + + if (tty_gone(tp)) + return; + + STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list); + to->to_nblocks++; + } + + while (nblocks < to->to_nblocks) { + /* + * List is getting smaller. Remove unused blocks at the + * end. This means we cannot guarantee this routine + * shrinks buffers properly, when we need to reclaim + * more space than there is available. + * + * XXX TODO: Two solutions here: + * - Throw data away + * - Temporarily hit the watermark until enough data has + * been flushed, so we can remove the blocks. + */ + + if (to->to_end == 0) { + tob = STAILQ_FIRST(&to->to_list); + if (tob == NULL) + break; + STAILQ_REMOVE_HEAD(&to->to_list, tob_list); + } else { + tob = STAILQ_NEXT(to->to_lastblock, tob_list); + if (tob == NULL) + break; + STAILQ_REMOVE_NEXT(&to->to_list, to->to_lastblock, tob_list); + } + uma_zfree(ttyoutq_zone, tob); + to->to_nblocks--; + } +} + +size_t +ttyoutq_read(struct ttyoutq *to, void *buf, size_t len) +{ + char *cbuf = buf; + + while (len > 0) { + struct ttyoutq_block *tob; + size_t cbegin, cend, clen; + + /* See if there still is data. */ + if (to->to_begin == to->to_end) + break; + tob = STAILQ_FIRST(&to->to_list); + if (tob == NULL) + break; + + /* + * The end address should be the lowest of these three: + * - The write pointer + * - The blocksize - we can't read beyond the block + * - The end address if we could perform the full read + */ + cbegin = to->to_begin; + cend = MIN(MIN(to->to_end, to->to_begin + len), + TTYOUTQ_DATASIZE); + clen = cend - cbegin; + + if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) { + /* Read the block until the end. */ + STAILQ_REMOVE_HEAD(&to->to_list, tob_list); + STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list); + to->to_begin = 0; + if (to->to_end <= TTYOUTQ_DATASIZE) { + to->to_end = 0; + } else { + to->to_end -= TTYOUTQ_DATASIZE; + } + } else { + /* Read the block partially. */ + to->to_begin += clen; + } + + /* Copy the data out of the buffers. */ + memcpy(cbuf, tob->tob_data + cbegin, clen); + cbuf += clen; + len -= clen; + } + + return (cbuf - (char *)buf); +} + +/* + * An optimized version of ttyoutq_read() which can be used in pseudo + * TTY drivers to directly copy data from the outq to userspace, instead + * of buffering it. + * + * We can only copy data directly if we need to read the entire block + * back to the user, because we temporarily remove the block from the + * queue. Otherwise we need to copy it to a temporary buffer first, to + * make sure data remains in the correct order. + */ +int +ttyoutq_read_uio(struct ttyoutq *to, struct tty *tp, struct uio *uio) +{ + + while (uio->uio_resid > 0) { + int error; + struct ttyoutq_block *tob; + size_t cbegin, cend, clen; + + /* See if there still is data. */ + if (to->to_begin == to->to_end) + return (0); + tob = STAILQ_FIRST(&to->to_list); + if (tob == NULL) + return (0); + + /* + * The end address should be the lowest of these three: + * - The write pointer + * - The blocksize - we can't read beyond the block + * - The end address if we could perform the full read + */ + cbegin = to->to_begin; + cend = MIN(MIN(to->to_end, to->to_begin + uio->uio_resid), + TTYOUTQ_DATASIZE); + clen = cend - cbegin; + + /* + * We can prevent buffering in some cases: + * - We need to read the block until the end. + * - We don't need to read the block until the end, but + * there is no data beyond it, which allows us to move + * the write pointer to a new block. + */ + if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) { + atomic_add_long(&ttyoutq_nfast, 1); + + /* + * Fast path: zero copy. Remove the first block, + * so we can unlock the TTY temporarily. + */ + STAILQ_REMOVE_HEAD(&to->to_list, tob_list); + to->to_nblocks--; + to->to_begin = 0; + if (to->to_end <= TTYOUTQ_DATASIZE) { + to->to_end = 0; + } else { + to->to_end -= TTYOUTQ_DATASIZE; + } + + /* Temporary unlock and copy the data to userspace. */ + tty_unlock(tp); + error = uiomove(tob->tob_data + cbegin, clen, uio); + tty_lock(tp); + + if (tty_gone(tp)) { + /* We lost the discipline. */ + uma_zfree(ttyoutq_zone, tob); + return (ENXIO); + } + + /* Block can now be readded to the list. */ + /* + * XXX: we could remove the blocks here when the + * queue was shrunk, but still in use. See + * ttyoutq_setsize(). + */ + STAILQ_INSERT_TAIL(&to->to_list, tob, tob_list); + to->to_nblocks++; + if (error != 0) + return (error); + } else { + char ob[TTYOUTQ_DATASIZE - 1]; + atomic_add_long(&ttyoutq_nslow, 1); + + /* + * Slow path: store data in a temporary buffer. + */ + memcpy(ob, tob->tob_data + cbegin, clen); + to->to_begin += clen; + MPASS(to->to_begin < TTYOUTQ_DATASIZE); + + /* Temporary unlock and copy the data to userspace. */ + tty_unlock(tp); + error = uiomove(ob, clen, uio); + tty_lock(tp); + + if (tty_gone(tp)) { + /* We lost the discipline. */ + return (ENXIO); + } + + if (error != 0) + return (error); + } + } + + return (0); +} + +size_t +ttyoutq_write(struct ttyoutq *to, const void *buf, size_t nbytes) +{ + const char *cbuf = buf; + struct ttyoutq_block *tob; + unsigned int boff; + size_t l; + + while (nbytes > 0) { + /* Offset in current block. */ + tob = to->to_lastblock; + boff = to->to_end % TTYOUTQ_DATASIZE; + + if (to->to_end == 0) { + /* First time we're being used or drained. */ + MPASS(to->to_begin == 0); + tob = to->to_lastblock = STAILQ_FIRST(&to->to_list); + if (tob == NULL) { + /* Queue has no blocks. */ + break; + } + } else if (boff == 0) { + /* We reached the end of this block on last write. */ + tob = STAILQ_NEXT(tob, tob_list); + if (tob == NULL) { + /* We've reached the watermark. */ + break; + } + to->to_lastblock = tob; + } + + /* Don't copy more than was requested. */ + l = MIN(nbytes, TTYOUTQ_DATASIZE - boff); + MPASS(l > 0); + memcpy(tob->tob_data + boff, cbuf, l); + + cbuf += l; + nbytes -= l; + to->to_end += l; + } + + return (cbuf - (const char *)buf); +} + +int +ttyoutq_write_nofrag(struct ttyoutq *to, const void *buf, size_t nbytes) +{ + size_t ret; + + if (ttyoutq_bytesleft(to) < nbytes) + return (-1); + + /* We should always be able to write it back. */ + ret = ttyoutq_write(to, buf, nbytes); + MPASS(ret == nbytes); + + return (0); +} + +static void +ttyoutq_startup(void *dummy) +{ + + ttyoutq_zone = uma_zcreate("ttyoutq", sizeof(struct ttyoutq_block), + NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); +} + +SYSINIT(ttyoutq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyoutq_startup, NULL); diff --git a/sys/kern/tty_pts.c b/sys/kern/tty_pts.c index ff8838e..8a51448 100644 --- a/sys/kern/tty_pts.c +++ b/sys/kern/tty_pts.c @@ -1,16 +1,9 @@ -/* - * Copyright (c) 2003 Networks Associates Technology, Inc. - * Copyright (c) 2006 Robert N. M. Watson - * Copyright (c) 2006 Olivier Houchard +/*- + * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> * All rights reserved. * - * This software was developed for the FreeBSD Project in part by Network - * Associates Laboratories, the Security Research Division of Network - * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), - * as part of the DARPA CHATS research program. - * - * Copyright (c) 1982, 1986, 1989, 1993 - * The Regents of the University of California. All rights reserved. + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -20,14 +13,11 @@ * 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. - * 4. 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 + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 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) @@ -35,889 +25,632 @@ * 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_pty.c 8.4 (Berkeley) 2/20/95 */ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -/* - * Pseudo-teletype Driver - * (Actually two drivers, requiring two entries in 'cdevsw') - */ -#include "opt_compat.h" #include "opt_tty.h" + +/* Add compatibility bits for FreeBSD. */ +#define PTS_COMPAT +#ifdef DEV_PTY +/* Add /dev/ptyXX compat bits. */ +#define PTS_EXTERNAL +#endif /* DEV_PTY */ +/* Add bits to make Linux binaries work. */ +#define PTS_LINUX + #include <sys/param.h> -#include <sys/systm.h> #include <sys/lock.h> -#include <sys/mutex.h> -#include <sys/sx.h> -#if defined(COMPAT_43TTY) -#include <sys/ioctl_compat.h> -#endif -#include <sys/priv.h> -#include <sys/proc.h> -#include <sys/queue.h> -#include <sys/tty.h> +#include <sys/condvar.h> +#include <sys/conf.h> #include <sys/fcntl.h> -#include <sys/poll.h> +#include <sys/file.h> +#include <sys/filedesc.h> +#include <sys/filio.h> #include <sys/kernel.h> -#include <sys/vnode.h> -#include <sys/signalvar.h> #include <sys/malloc.h> -#include <sys/conf.h> -#include <sys/sysctl.h> -#include <sys/filio.h> - -static MALLOC_DEFINE(M_PTY, "ptys", "pty data structures"); - -static void ptsstart(struct tty *tp); -static void ptsstop(struct tty *tp, int rw); -static void ptcwakeup(struct tty *tp, int flag); - -static d_open_t ptsopen; -static d_close_t ptsclose; -static d_read_t ptsread; -static d_write_t ptswrite; -static d_ioctl_t ptsioctl; -static d_ioctl_t ptcioctl; -static d_open_t ptcopen; -static d_close_t ptcclose; -static d_read_t ptcread; -static d_write_t ptcwrite; -static d_poll_t ptcpoll; - -static struct cdevsw pts_cdevsw = { - .d_version = D_VERSION, - .d_open = ptsopen, - .d_close = ptsclose, - .d_read = ptsread, - .d_write = ptswrite, - .d_ioctl = ptsioctl, - .d_poll = ttypoll, - .d_name = "pts", - .d_flags = D_TTY | D_NEEDGIANT, - .d_kqfilter = ttykqfilter, -}; +#include <sys/poll.h> +#include <sys/proc.h> +#include <sys/resourcevar.h> +#include <sys/serial.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/syscallsubr.h> +#include <sys/sysent.h> +#include <sys/sysproto.h> +#include <sys/systm.h> +#include <sys/tty.h> +#include <sys/ttycom.h> -static struct cdevsw ptc_cdevsw = { - .d_version = D_VERSION, - .d_open = ptcopen, - .d_close = ptcclose, - .d_read = ptcread, - .d_write = ptcwrite, - .d_ioctl = ptcioctl, - .d_poll = ptcpoll, - .d_name = "ptc", - .d_flags = D_TTY | D_NEEDGIANT, - .d_kqfilter = ttykqfilter, -}; +#include <machine/stdarg.h> -#define BUFSIZ 100 /* Chunk size iomoved to/from user */ +static struct unrhdr *pts_pool; +#define MAXPTSDEVS 999 -#define TSA_PTC_READ(tp) ((void *)&(tp)->t_outq.c_cf) -#define TSA_PTC_WRITE(tp) ((void *)&(tp)->t_rawq.c_cl) -#define TSA_PTS_READ(tp) ((void *)&(tp)->t_canq) +static MALLOC_DEFINE(M_PTS, "pts", "pseudo tty device"); -#define NUM_TO_MINOR(c) ((c & 0xff) | ((c & ~0xff) << 16)) -/*- - * Once a tty is allocated, it cannot (currently) be freed. As such, - * we keep a global list of ptys that have been used so we can recycle - * them. Another list is provided for released pts, which are - * not currently allocated, permitting reuse. pt_flags holds state - * associated with a particular session, so isn't overloaded for this. - * When a pty descriptor is unused, its number is set to -1 giving - * more consistent and traditional allocation orders to pty numbers. +/* + * Per-PTS structure. * - * Locking: (p) indicates that the field is locked by the global pt_mtx. - * (c) indicates the value is constant after allocation. Other fields - * await tty locking generally, and are protected by Giant. + * List of locks + * (t) locked by tty_lock() + * (c) const until freeing */ -struct pt_desc { - int pt_num; /* (c) pty number */ - LIST_ENTRY(pt_desc) pt_list; /* (p) global pty list */ - - int pt_flags; - struct selinfo pt_selr, pt_selw; - u_char pt_send; - u_char pt_ucntl; - struct tty *pt_tty; - struct cdev *pt_devs, *pt_devc; - int pt_pts_open, pt_ptc_open; - struct prison *pt_prison; -}; +struct pts_softc { + int pts_unit; /* (c) Device unit number. */ + unsigned int pts_flags; /* (t) Device flags. */ +#define PTS_PKT 0x1 /* Packet mode. */ -static struct mtx pt_mtx; -static LIST_HEAD(,pt_desc) pt_list; -static LIST_HEAD(,pt_desc) pt_free_list; + struct cv pts_inwait; /* (t) Blocking write() on master. */ + struct selinfo pts_inpoll; /* (t) Select queue for write(). */ + struct cv pts_outwait; /* (t) Blocking read() on master. */ + struct selinfo pts_outpoll; /* (t) Select queue for read(). */ -#define PF_PKT 0x008 /* packet mode */ -#define PF_STOPPED 0x010 /* user told stopped */ -#define PF_NOSTOP 0x040 -#define PF_UCNTL 0x080 /* user control mode */ +#ifdef PTS_EXTERNAL + struct cdev *pts_cdev; /* (c) Master device node. */ +#endif /* PTS_EXTERNAL */ -static unsigned int next_avail_nb; - -static int use_pts = 0; + struct uidinfo *pts_uidinfo; /* (c) Resource limit. */ +}; -static unsigned int max_pts = 1000; +/* + * Controller-side file operations. + */ -static unsigned int nb_allocated; +static int +ptsdev_read(struct file *fp, struct uio *uio, struct ucred *active_cred, + int flags, struct thread *td) +{ + struct tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + int error, oresid; -TUNABLE_INT("kern.pts.enable", &use_pts); + if (uio->uio_resid == 0) + return (0); + + /* + * Implement packet mode. When packet mode is turned on, the + * first byte contains a bitmask of events that occured (start, + * stop, flush, window size, etc). + */ -SYSCTL_NODE(_kern, OID_AUTO, pts, CTLFLAG_RD, 0, "pts"); + if (psc->pts_flags & PTS_PKT) { + /* XXX: return proper bits. */ + error = ureadc(0, uio); + if (error != 0) + return (error); + if (uio->uio_resid == 0) + return (0); + } -SYSCTL_INT(_kern_pts, OID_AUTO, enable, CTLFLAG_RW, &use_pts, 0, - "enable pts"); + oresid = uio->uio_resid; -SYSCTL_INT(_kern_pts, OID_AUTO, max, CTLFLAG_RW, &max_pts, 0, "max pts"); + tty_lock(tp); + for (;;) { + error = ttydisc_getc_uio(tp, uio); + /* We've got data (or an error). */ + if (error != 0 || uio->uio_resid != oresid) + break; -/* - * If there's a free pty descriptor in the pty descriptor list, retrieve it. - * Otherwise, allocate a new one, initialize it, and hook it up. If there's - * not a tty number, reject. - */ -static struct pt_desc * -pty_new(void) -{ - struct pt_desc *pt; - int nb; + /* Maybe the device isn't used anyway. */ + if (tty_opened(tp) == 0) { + error = ENXIO; + break; + } - mtx_lock(&pt_mtx); - if (nb_allocated >= max_pts || nb_allocated == 0xffffff) { - mtx_unlock(&pt_mtx); - return (NULL); - } - nb_allocated++; - pt = LIST_FIRST(&pt_free_list); - if (pt) { - LIST_REMOVE(pt, pt_list); - } else { - nb = next_avail_nb++; - mtx_unlock(&pt_mtx); - pt = malloc(sizeof(*pt), M_PTY, M_WAITOK | M_ZERO); - pt->pt_tty = ttyalloc(); - mtx_lock(&pt_mtx); - pt->pt_num = nb; + /* Wait for more data. */ + if (fp->f_flag & O_NONBLOCK) { + error = EWOULDBLOCK; + break; + } + error = cv_wait_sig(&psc->pts_outwait, tp->t_mtx); + if (error != 0) + break; } - LIST_INSERT_HEAD(&pt_list, pt, pt_list); - mtx_unlock(&pt_mtx); - return (pt); -} - -/* - * Release a pty descriptor back to the pool for reuse. The pty number - * remains allocated. - */ -static void -pty_release(void *v) -{ - struct pt_desc *pt = (struct pt_desc *)v; - - mtx_lock(&pt_mtx); - KASSERT(pt->pt_ptc_open == 0 && pt->pt_pts_open == 0, - ("pty_release: pts/%d freed while open\n", pt->pt_num)); - KASSERT(pt->pt_devs == NULL && pt->pt_devc == NULL, - ("pty_release: pts/%d freed whith non-null struct cdev\n", pt->pt_num)); - nb_allocated--; - LIST_REMOVE(pt, pt_list); - LIST_INSERT_HEAD(&pt_free_list, pt, pt_list); - mtx_unlock(&pt_mtx); -} + tty_unlock(tp); -/* - * Given a pty descriptor, if both endpoints are closed, release all - * resources and destroy the device nodes to flush file system level - * state for the tty (owner, avoid races, etc). - */ -static void -pty_maybecleanup(struct pt_desc *pt) -{ - struct cdev *pt_devs, *pt_devc; - - if (pt->pt_ptc_open || pt->pt_pts_open) - return; - - if (pt->pt_tty->t_refcnt > 1) - return; - - if (bootverbose) - printf("destroying pty %d\n", pt->pt_num); - - pt_devs = pt->pt_devs; - pt_devc = pt->pt_devc; - pt->pt_devs = pt->pt_devc = NULL; - pt->pt_tty->t_dev = NULL; - pt_devc->si_drv1 = NULL; - ttyrel(pt->pt_tty); - pt->pt_tty = NULL; - destroy_dev_sched(pt_devs); - destroy_dev_sched_cb(pt_devc, pty_release, pt); + return (error); } -/*ARGSUSED*/ static int -ptsopen(struct cdev *dev, int flag, int devtype, struct thread *td) +ptsdev_write(struct file *fp, struct uio *uio, struct ucred *active_cred, + int flags, struct thread *td) { - struct tty *tp; - int error; - struct pt_desc *pt; - - pt = dev->si_drv1; - tp = dev->si_tty; - if ((tp->t_state & TS_ISOPEN) == 0) - ttyinitmode(tp, 1, 0); - else if (tp->t_state & TS_XCLUDE && priv_check(td, - PRIV_TTY_EXCLUSIVE)) { - return (EBUSY); - } else if (pt->pt_prison != td->td_ucred->cr_prison && - priv_check(td, PRIV_TTY_PRISON)) { - return (EBUSY); - } - if (tp->t_oproc) /* Ctrlr still around. */ - ttyld_modem(tp, 1); - while ((tp->t_state & TS_CARR_ON) == 0) { - if (flag & FNONBLOCK) - break; - error = ttysleep(tp, TSA_CARR_ON(tp), TTIPRI | PCATCH, - "ptsopn", 0); - if (error) - return (error); - } - error = ttyld_open(tp, dev); - if (error == 0) { - ptcwakeup(tp, FREAD|FWRITE); - pt->pt_pts_open = 1; + struct tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + char ib[256], *ibstart; + size_t iblen, rintlen; + int error = 0; + + tty_lock(tp); + + while (uio->uio_resid > 0) { + /* Temporarily unlock to buffer new characters. */ + tty_unlock(tp); + ibstart = ib; + iblen = MIN(uio->uio_resid, sizeof ib); + error = uiomove(ib, iblen, uio); + tty_lock(tp); + if (error != 0) + goto done; + + /* + * When possible, avoid the slow path. rint_bypass() + * copies all input to the input queue at once. + */ + while (iblen > 0) { + if (ttydisc_can_bypass(tp)) { + /* Store data at once. */ + rintlen = ttydisc_rint_bypass(tp, + ibstart, iblen); + ibstart += rintlen; + iblen -= rintlen; + + if (iblen == 0) { + /* All data written. */ + continue; + } + } else { + error = ttydisc_rint(tp, *ibstart, 0); + if (error == 0) { + /* Character stored successfully. */ + ibstart++; + iblen--; + continue; + } + } + + /* Maybe the device isn't used anyway. */ + if (tty_opened(tp) == 0) { + error = ENXIO; + goto done; + } + + /* Wait for more data. */ + if (fp->f_flag & O_NONBLOCK) { + error = EWOULDBLOCK; + goto done; + } + + /* Wake up users on the slave side. */ + ttydisc_rint_done(tp); + error = cv_wait_sig(&psc->pts_inwait, tp->t_mtx); + if (error != 0) + goto done; + } } + +done: ttydisc_rint_done(tp); + tty_unlock(tp); return (error); } static int -ptsclose(struct cdev *dev, int flag, int mode, struct thread *td) +ptsdev_ioctl(struct file *fp, u_long cmd, void *data, + struct ucred *active_cred, struct thread *td) { - struct pt_desc *pt = dev->si_drv1; - struct tty *tp; - int err; - - tp = dev->si_tty; - err = ttyld_close(tp, flag); - ptsstop(tp, FREAD|FWRITE); - (void) tty_close(tp); - pt->pt_pts_open = 0; - pty_maybecleanup(pt); - return (err); -} + struct tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + int error = 0, sig; -static int -ptsread(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp = dev->si_tty; - int error = 0; + switch (cmd) { + case FIONBIO: + /* This device supports non-blocking operation. */ + return (0); + case FIODGNAME: { + struct fiodgname_arg *fgn; + const char *p; + int i; + + /* Reverse device name lookups, for ptsname() and ttyname(). */ + fgn = data; +#ifdef PTS_EXTERNAL + if (psc->pts_cdev != NULL) + p = devtoname(psc->pts_cdev); + else +#endif /* PTS_EXTERNAL */ + p = tty_devname(tp); + i = strlen(p) + 1; + if (i > fgn->len) + return (EINVAL); + return copyout(p, fgn->buf, i); + } + + /* + * We need to implement TIOCGPGRP and TIOCGSID here again. When + * called on the pseudo-terminal master, it should not check if + * the terminal is the foreground terminal of the calling + * process. + * + * TIOCGETA is also implemented here. Various Linux PTY routines + * often call isatty(), which is implemented by tcgetattr(). + */ +#ifdef PTS_LINUX + case TIOCGETA: + /* Obtain terminal flags through tcgetattr(). */ + tty_lock(tp); + bcopy(&tp->t_termios, data, sizeof(struct termios)); + tty_unlock(tp); + return (0); +#endif /* PTS_LINUX */ + case TIOCSETAF: + case TIOCSETAW: + /* + * We must make sure we turn tcsetattr() calls of TCSAFLUSH and + * TCSADRAIN into something different. If an application would + * call TCSAFLUSH or TCSADRAIN on the master descriptor, it may + * deadlock waiting for all data to be read. + */ + cmd = TIOCSETA; + break; +#if defined(PTS_COMPAT) || defined(PTS_LINUX) + case TIOCGPTN: + /* + * Get the device unit number. + */ + if (psc->pts_unit < 0) + return (ENOTTY); + *(unsigned int *)data = psc->pts_unit; + return (0); +#endif /* PTS_COMPAT || PTS_LINUX */ + case TIOCGPGRP: + /* Get the foreground process group ID. */ + tty_lock(tp); + if (tp->t_pgrp != NULL) + *(int *)data = tp->t_pgrp->pg_id; + else + *(int *)data = NO_PID; + tty_unlock(tp); + return (0); + case TIOCGSID: + /* Get the session leader process ID. */ + tty_lock(tp); + if (tp->t_session == NULL) + error = ENOTTY; + else + *(int *)data = tp->t_session->s_sid; + tty_unlock(tp); + return (error); + case TIOCPTMASTER: + /* Yes, we are a pseudo-terminal master. */ + return (0); + case TIOCSIG: + /* Signal the foreground process group. */ + sig = *(int *)data; + if (sig < 1 || sig >= NSIG) + return (EINVAL); + + tty_lock(tp); + tty_signal_pgrp(tp, sig); + tty_unlock(tp); + return (0); + case TIOCPKT: + /* Enable/disable packet mode. */ + tty_lock(tp); + if (*(int *)data) + psc->pts_flags |= PTS_PKT; + else + psc->pts_flags &= ~PTS_PKT; + tty_unlock(tp); + return (0); + } + + /* Just redirect this ioctl to the slave device. */ + tty_lock(tp); + error = tty_ioctl(tp, cmd, data, td); + tty_unlock(tp); - if (tp->t_oproc) - error = ttyld_read(tp, uio, flag); - ptcwakeup(tp, FWRITE); return (error); } -/* - * Write to pseudo-tty. - * Wakeups of controlling tty will happen - * indirectly, when tty driver calls ptsstart. - */ static int -ptswrite(struct cdev *dev, struct uio *uio, int flag) +ptsdev_poll(struct file *fp, int events, struct ucred *active_cred, + struct thread *td) { - struct tty *tp; + struct tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + int revents = 0; - tp = dev->si_tty; - if (tp->t_oproc == 0) - return (EIO); - return (ttyld_write(tp, uio, flag)); -} + tty_lock(tp); -/* - * Start output on pseudo-tty. - * Wake up process selecting or sleeping for input from controlling tty. - */ -static void -ptsstart(struct tty *tp) -{ - struct pt_desc *pt = tp->t_dev->si_drv1; + if (tty_opened(tp) == 0) { + /* Slave device is not opened. */ + tty_unlock(tp); + return (events & + (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); + } - if (tp->t_state & TS_TTSTOP) - return; - if (pt->pt_flags & PF_STOPPED) { - pt->pt_flags &= ~PF_STOPPED; - pt->pt_send = TIOCPKT_START; + if (events & (POLLIN|POLLRDNORM)) { + /* See if we can getc something. */ + if (ttydisc_getc_poll(tp)) + revents |= events & (POLLIN|POLLRDNORM); + } + if (events & (POLLOUT|POLLWRNORM)) { + /* See if we can rint something. */ + if (ttydisc_rint_poll(tp)) + revents |= events & (POLLOUT|POLLWRNORM); } - ptcwakeup(tp, FREAD); -} -static void -ptcwakeup(struct tty *tp, int flag) -{ - struct pt_desc *pt = tp->t_dev->si_drv1; + /* + * No need to check for POLLHUP here. This device cannot be used + * as a callout device, which means we always have a carrier, + * because the master is. + */ - if (flag & FREAD) { - selwakeup(&pt->pt_selr); - wakeup(TSA_PTC_READ(tp)); - } - if (flag & FWRITE) { - selwakeup(&pt->pt_selw); - wakeup(TSA_PTC_WRITE(tp)); + if (revents == 0) { + /* + * This code might look misleading, but the naming of + * poll events on this side is the opposite of the slave + * device. + */ + if (events & (POLLIN|POLLRDNORM)) + selrecord(td, &psc->pts_outpoll); + if (events & (POLLOUT|POLLWRNORM)) + selrecord(td, &psc->pts_inpoll); } + + tty_unlock(tp); + + return (revents); } -/* - * ptcopen implementes exclusive access to the master/control device - * as well as creating the slave device based on the credential of the - * process opening the master. By creating the slave here, we avoid - * a race to access the master in terms of having a process with access - * to an incorrectly owned slave, but it does create the possibility - * that a racing process can cause a ptmx user to get EIO if it gets - * there first. Consumers of ptmx must look for EIO and retry if it - * happens. VFS locking may actually prevent this from occurring due - * to the lookup into devfs holding the vnode lock through open, but - * it's better to be careful. - */ static int -ptcopen(struct cdev *dev, int flag, int devtype, struct thread *td) +ptsdev_stat(struct file *fp, struct stat *sb, struct ucred *active_cred, + struct thread *td) { - struct pt_desc *pt; - struct tty *tp; - struct cdev *devs; + struct tty *tp = fp->f_data; +#ifdef PTS_EXTERNAL + struct pts_softc *psc = tty_softc(tp); +#endif /* PTS_EXTERNAL */ - pt = dev->si_drv1; - if (pt == NULL) - return (EIO); /* - * In case we have destroyed the struct tty at the last connect time, - * we need to recreate it. + * According to POSIX, we must implement an fstat(). This also + * makes this implementation compatible with Linux binaries, + * because Linux calls fstat() on the pseudo-terminal master to + * obtain st_rdev. + * + * XXX: POSIX also mentions we must fill in st_dev, st_atime, + * st_ctime and st_mtime, but how? */ - if (pt->pt_tty == NULL) { - tp = ttyalloc(); - mtx_lock(&pt_mtx); - if (pt->pt_tty == NULL) { - pt->pt_tty = tp; - dev->si_tty = pt->pt_tty; - mtx_unlock(&pt_mtx); - } else { - mtx_unlock(&pt_mtx); - ttyrel(tp); - } - } - tp = dev->si_tty; - if (tp->t_oproc) - return (EIO); - /* - * XXX: Might want to make the ownership/permissions here more - * configurable. - */ - if (pt->pt_devs) - devs = pt->pt_devs; + bzero(sb, sizeof *sb); +#ifdef PTS_EXTERNAL + if (psc->pts_cdev != NULL) + sb->st_ino = sb->st_rdev = dev2udev(psc->pts_cdev); else - pt->pt_devs = devs = make_dev_cred(&pts_cdevsw, - NUM_TO_MINOR(pt->pt_num), - td->td_ucred, UID_ROOT, GID_WHEEL, 0666, "pts/%d", - pt->pt_num); - devs->si_drv1 = pt; - devs->si_tty = pt->pt_tty; - pt->pt_tty->t_dev = devs; - - tp->t_timeout = -1; - tp->t_oproc = ptsstart; - tp->t_stop = ptsstop; - ttyld_modem(tp, 1); - tp->t_lflag &= ~EXTPROC; - pt = dev->si_drv1; - pt->pt_prison = td->td_ucred->cr_prison; - pt->pt_flags = 0; - pt->pt_send = 0; - pt->pt_ucntl = 0; - pt->pt_ptc_open = 1; +#endif /* PTS_EXTERNAL */ + sb->st_ino = sb->st_rdev = tty_udev(tp); + sb->st_mode = S_IFCHR; + sb->st_uid = tp->t_dev->si_cred->cr_ruid; + sb->st_gid = GID_TTY; + return (0); } static int -ptcclose(struct cdev *dev, int flags, int fmt, struct thread *td) +ptsdev_close(struct file *fp, struct thread *td) { - struct pt_desc *pt = dev->si_drv1; - struct tty *tp; + struct tty *tp = fp->f_data; - tp = dev->si_tty; - ttyld_modem(tp, 0); + /* Deallocate TTY device. */ + tty_lock(tp); + tty_rel_gone(tp); - /* - * XXX MDMBUF makes no sense for ptys but would inhibit the above - * l_modem(). CLOCAL makes sense but isn't supported. Special - * l_modem()s that ignore carrier drop make no sense for ptys but - * may be in use because other parts of the line discipline make - * sense for ptys. Recover by doing everything that a normal - * ttymodem() would have done except for sending a SIGHUP. - */ - if (tp->t_state & TS_ISOPEN) { - tp->t_state &= ~(TS_CARR_ON | TS_CONNECTED); - tp->t_state |= TS_ZOMBIE; - ttyflush(tp, FREAD | FWRITE); - } - - tp->t_oproc = 0; /* mark closed */ - pt->pt_ptc_open = 0; - pty_maybecleanup(pt); return (0); } -static int -ptcread(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp = dev->si_tty; - struct pt_desc *pt = dev->si_drv1; - char buf[BUFSIZ]; - int error = 0, cc; +static struct fileops ptsdev_ops = { + .fo_read = ptsdev_read, + .fo_write = ptsdev_write, + .fo_ioctl = ptsdev_ioctl, + .fo_poll = ptsdev_poll, + .fo_stat = ptsdev_stat, + .fo_close = ptsdev_close, + .fo_flags = DFLAG_PASSABLE, +}; - /* - * We want to block until the slave - * is open, and there's something to read; - * but if we lost the slave or we're NBIO, - * then return the appropriate error instead. - */ - for (;;) { - if (tp->t_state&TS_ISOPEN) { - if (pt->pt_flags&PF_PKT && pt->pt_send) { - error = ureadc((int)pt->pt_send, uio); - if (error) - return (error); - if (pt->pt_send & TIOCPKT_IOCTL) { - cc = min(uio->uio_resid, - sizeof(tp->t_termios)); - uiomove(&tp->t_termios, cc, uio); - } - pt->pt_send = 0; - return (0); - } - if (pt->pt_flags&PF_UCNTL && pt->pt_ucntl) { - error = ureadc((int)pt->pt_ucntl, uio); - if (error) - return (error); - pt->pt_ucntl = 0; - return (0); - } - if (tp->t_outq.c_cc && (tp->t_state&TS_TTSTOP) == 0) - break; - } - if ((tp->t_state & TS_CONNECTED) == 0) - return (0); /* EOF */ - if (flag & O_NONBLOCK) - return (EWOULDBLOCK); - error = tsleep(TSA_PTC_READ(tp), TTIPRI | PCATCH, "ptcin", 0); - if (error) - return (error); - } - if (pt->pt_flags & (PF_PKT|PF_UCNTL)) - error = ureadc(0, uio); - while (uio->uio_resid > 0 && error == 0) { - cc = q_to_b(&tp->t_outq, buf, min(uio->uio_resid, BUFSIZ)); - if (cc <= 0) - break; - error = uiomove(buf, cc, uio); - } - ttwwakeup(tp); - return (error); -} +/* + * Driver-side hooks. + */ static void -ptsstop(struct tty *tp, int flush) +ptsdrv_outwakeup(struct tty *tp) { - struct pt_desc *pt = tp->t_dev->si_drv1; - int flag; - - /* note: FLUSHREAD and FLUSHWRITE already ok */ - if (flush == 0) { - flush = TIOCPKT_STOP; - pt->pt_flags |= PF_STOPPED; - } else - pt->pt_flags &= ~PF_STOPPED; - pt->pt_send |= flush; - /* change of perspective */ - flag = 0; - if (flush & FREAD) - flag |= FWRITE; - if (flush & FWRITE) - flag |= FREAD; - ptcwakeup(tp, flag); + struct pts_softc *psc = tty_softc(tp); + + cv_broadcast(&psc->pts_outwait); + selwakeup(&psc->pts_outpoll); } -static int -ptcpoll(struct cdev *dev, int events, struct thread *td) +static void +ptsdrv_inwakeup(struct tty *tp) { - struct tty *tp = dev->si_tty; - struct pt_desc *pt = dev->si_drv1; - int revents = 0; - int s; + struct pts_softc *psc = tty_softc(tp); - if ((tp->t_state & TS_CONNECTED) == 0) - return (events & - (POLLHUP | POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)); + cv_broadcast(&psc->pts_inwait); + selwakeup(&psc->pts_inpoll); +} - /* - * Need to block timeouts (ttrstart). - */ - s = spltty(); +static void +ptsdrv_close(struct tty *tp) +{ - if (events & (POLLIN | POLLRDNORM)) - if ((tp->t_state & TS_ISOPEN) && - ((tp->t_outq.c_cc && (tp->t_state & TS_TTSTOP) == 0) || - ((pt->pt_flags & PF_PKT) && pt->pt_send) || - ((pt->pt_flags & PF_UCNTL) && pt->pt_ucntl))) - revents |= events & (POLLIN | POLLRDNORM); + /* Wake up any blocked readers/writers. */ + ptsdrv_outwakeup(tp); + ptsdrv_inwakeup(tp); +} - if (events & (POLLOUT | POLLWRNORM)) - if (tp->t_state & TS_ISOPEN && - (((tp->t_rawq.c_cc + tp->t_canq.c_cc < TTYHOG - 2) || - (tp->t_canq.c_cc == 0 && (tp->t_lflag & ICANON))))) - revents |= events & (POLLOUT | POLLWRNORM); +static void +ptsdrv_free(void *softc) +{ + struct pts_softc *psc = softc; - if (events & POLLHUP) - if ((tp->t_state & TS_CARR_ON) == 0) - revents |= POLLHUP; + /* Make device number available again. */ + if (psc->pts_unit >= 0) + free_unr(pts_pool, psc->pts_unit); - if (revents == 0) { - if (events & (POLLIN | POLLRDNORM)) - selrecord(td, &pt->pt_selr); + chgptscnt(psc->pts_uidinfo, -1, 0); + uifree(psc->pts_uidinfo); - if (events & (POLLOUT | POLLWRNORM)) - selrecord(td, &pt->pt_selw); - } - splx(s); +#ifdef PTS_EXTERNAL + /* Destroy master device as well. */ + if (psc->pts_cdev != NULL) + destroy_dev_sched(psc->pts_cdev); +#endif /* PTS_EXTERNAL */ - return (revents); + free(psc, M_PTS); } +static struct ttydevsw pts_class = { + .tsw_flags = TF_NOPREFIX, + .tsw_outwakeup = ptsdrv_outwakeup, + .tsw_inwakeup = ptsdrv_inwakeup, + .tsw_close = ptsdrv_close, + .tsw_free = ptsdrv_free, +}; + static int -ptcwrite(struct cdev *dev, struct uio *uio, int flag) +pts_alloc(int fflags, struct thread *td, struct file *fp) { - struct tty *tp = dev->si_tty; - u_char *cp = 0; - int cc = 0; - u_char locbuf[BUFSIZ]; - int cnt = 0; - int error = 0; - -again: - if ((tp->t_state&TS_ISOPEN) == 0) - goto block; - while (uio->uio_resid > 0 || cc > 0) { - if (cc == 0) { - cc = min(uio->uio_resid, BUFSIZ); - cp = locbuf; - error = uiomove(cp, cc, uio); - if (error) - return (error); - /* check again for safety */ - if ((tp->t_state & TS_ISOPEN) == 0) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; - return (EIO); - } - } - while (cc > 0) { - if ((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= TTYHOG - 2 && - (tp->t_canq.c_cc > 0 || !(tp->t_lflag&ICANON))) { - wakeup(TSA_HUP_OR_INPUT(tp)); - goto block; - } - ttyld_rint(tp, *cp++); - cnt++; - cc--; - } - cc = 0; + int unit, ok; + struct tty *tp; + struct pts_softc *psc; + struct proc *p = td->td_proc; + struct uidinfo *uid = td->td_ucred->cr_ruidinfo; + + /* Resource limiting. */ + PROC_LOCK(p); + ok = chgptscnt(uid, 1, lim_cur(p, RLIMIT_NPTS)); + PROC_UNLOCK(p); + if (!ok) + return (EAGAIN); + + /* Try to allocate a new pts unit number. */ + unit = alloc_unr(pts_pool); + if (unit < 0) { + chgptscnt(uid, -1, 0); + return (EAGAIN); } + + /* Allocate TTY and softc. */ + psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO); + cv_init(&psc->pts_inwait, "pts inwait"); + cv_init(&psc->pts_outwait, "pts outwait"); + + psc->pts_unit = unit; + psc->pts_uidinfo = uid; + uihold(uid); + + tp = tty_alloc(&pts_class, psc, NULL); + + /* Expose the slave device as well. */ + tty_makedev(tp, td->td_ucred, "pts/%u", psc->pts_unit); + + finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); + return (0); -block: - /* - * Come here to wait for slave to open, for space - * in outq, or space in rawq, or an empty canq. - */ - if ((tp->t_state & TS_CONNECTED) == 0) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; - return (EIO); - } - if (flag & IO_NDELAY) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; - if (cnt == 0) - return (EWOULDBLOCK); - return (0); - } - error = tsleep(TSA_PTC_WRITE(tp), TTOPRI | PCATCH, "ptcout", 0); - if (error) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; - return (error); - } - goto again; } -static int -ptcioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) +#ifdef PTS_EXTERNAL +int +pts_alloc_external(int fflags, struct thread *td, struct file *fp, + struct cdev *dev, const char *name) { - struct tty *tp = dev->si_tty; - struct pt_desc *pt = dev->si_drv1; -#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ - defined(COMPAT_FREEBSD4) || defined(COMPAT_43) - int ival; -#endif + int ok; + struct tty *tp; + struct pts_softc *psc; + struct proc *p = td->td_proc; + struct uidinfo *uid = td->td_ucred->cr_ruidinfo; - switch (cmd) { - - case TIOCGPGRP: - /* - * We avoid calling ttioctl on the controller since, - * in that case, tp must be the controlling terminal. - */ - *(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : 0; - return (0); - - case TIOCPKT: - if (*(int *)data) { - if (pt->pt_flags & PF_UCNTL) - return (EINVAL); - pt->pt_flags |= PF_PKT; - } else - pt->pt_flags &= ~PF_PKT; - return (0); - - case TIOCUCNTL: - if (*(int *)data) { - if (pt->pt_flags & PF_PKT) - return (EINVAL); - pt->pt_flags |= PF_UCNTL; - } else - pt->pt_flags &= ~PF_UCNTL; - return (0); - case TIOCGPTN: - *(unsigned int *)data = pt->pt_num; - return (0); - } - - /* - * The rest of the ioctls shouldn't be called until - * the slave is open. - */ - if ((tp->t_state & TS_ISOPEN) == 0) { - if (cmd == TIOCGETA) { - /* - * TIOCGETA is used by isatty() to make sure it's - * a tty. Linux openpty() calls isatty() very early, - * before the slave is opened, so don't actually - * fill the struct termios, but just let isatty() - * know it's a tty. - */ - return (0); - } - if (cmd != FIONBIO && cmd != FIOASYNC) - return (EAGAIN); - } - - switch (cmd) { -#ifdef COMPAT_43TTY - case TIOCSETP: - case TIOCSETN: -#endif - case TIOCSETD: - case TIOCSETA: - case TIOCSETAW: - case TIOCSETAF: - /* - * IF CONTROLLER STTY THEN MUST FLUSH TO PREVENT A HANG. - * ttywflush(tp) will hang if there are characters in - * the outq. - */ - ndflush(&tp->t_outq, tp->t_outq.c_cc); - break; - -#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ - defined(COMPAT_FREEBSD4) || defined(COMPAT_43) - case _IO('t', 95): - ival = IOCPARM_IVAL(data); - data = (caddr_t)&ival; - /* FALLTHROUGH */ -#endif - case TIOCSIG: - if (*(unsigned int *)data >= NSIG || - *(unsigned int *)data == 0) - return(EINVAL); - if ((tp->t_lflag&NOFLSH) == 0) - ttyflush(tp, FREAD|FWRITE); - if (tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, *(unsigned int *)data, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - if ((*(unsigned int *)data == SIGINFO) && - ((tp->t_lflag&NOKERNINFO) == 0)) - ttyinfo(tp); - return(0); - } - return (ptsioctl(dev, cmd, data, flag, td)); -} -/*ARGSUSED*/ -static int -ptsioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) -{ - struct tty *tp = dev->si_tty; - struct pt_desc *pt = dev->si_drv1; - u_char *cc = tp->t_cc; - int stop, error; + /* Resource limiting. */ + PROC_LOCK(p); + ok = chgptscnt(uid, 1, lim_cur(p, RLIMIT_NPTS)); + PROC_UNLOCK(p); + if (!ok) + return (EAGAIN); - if (cmd == TIOCEXT) { - /* - * When the EXTPROC bit is being toggled, we need - * to send an TIOCPKT_IOCTL if the packet driver - * is turned on. - */ - if (*(int *)data) { - if (pt->pt_flags & PF_PKT) { - pt->pt_send |= TIOCPKT_IOCTL; - ptcwakeup(tp, FREAD); - } - tp->t_lflag |= EXTPROC; - } else { - if ((tp->t_lflag & EXTPROC) && - (pt->pt_flags & PF_PKT)) { - pt->pt_send |= TIOCPKT_IOCTL; - ptcwakeup(tp, FREAD); - } - tp->t_lflag &= ~EXTPROC; - } - return(0); - } - error = ttioctl(tp, cmd, data, flag); - if (error == ENOTTY) { - if (pt->pt_flags & PF_UCNTL && - (cmd & ~0xff) == UIOCCMD(0)) { - if (cmd & 0xff) { - pt->pt_ucntl = (u_char)cmd; - ptcwakeup(tp, FREAD); - } - return (0); - } - error = ENOTTY; - } - /* - * If external processing and packet mode send ioctl packet. - */ - if ((tp->t_lflag&EXTPROC) && (pt->pt_flags & PF_PKT)) { - switch(cmd) { - case TIOCSETA: - case TIOCSETAW: - case TIOCSETAF: -#ifdef COMPAT_43TTY - case TIOCSETP: - case TIOCSETN: - case TIOCSETC: - case TIOCSLTC: - case TIOCLBIS: - case TIOCLBIC: - case TIOCLSET: -#endif - pt->pt_send |= TIOCPKT_IOCTL; - ptcwakeup(tp, FREAD); - break; - default: - break; - } - } - stop = (tp->t_iflag & IXON) && CCEQ(cc[VSTOP], CTRL('s')) - && CCEQ(cc[VSTART], CTRL('q')); - if (pt->pt_flags & PF_NOSTOP) { - if (stop) { - pt->pt_send &= ~TIOCPKT_NOSTOP; - pt->pt_send |= TIOCPKT_DOSTOP; - pt->pt_flags &= ~PF_NOSTOP; - ptcwakeup(tp, FREAD); - } - } else { - if (!stop) { - pt->pt_send &= ~TIOCPKT_DOSTOP; - pt->pt_send |= TIOCPKT_NOSTOP; - pt->pt_flags |= PF_NOSTOP; - ptcwakeup(tp, FREAD); - } - } - return (error); -} + /* Allocate TTY and softc. */ + psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO); + cv_init(&psc->pts_inwait, "pts inwait"); + cv_init(&psc->pts_outwait, "pts outwait"); -/* - * Match lookups on /dev/ptmx, find the next free pty (if any), set up - * the pty descriptor, register it, and return a reference to the master. - * - * pts == /dev/pts/xxx (oldstyle: ttyp...) - * ptc == /dev/pty/xxx (oldstyle: ptyp...) - */ -static void -pty_clone(void *arg, struct ucred *cred, char *name, int namelen, - struct cdev **dev) -{ - struct pt_desc *pt; - struct cdev *devc; + psc->pts_unit = -1; + psc->pts_cdev = dev; + psc->pts_uidinfo = uid; + uihold(uid); - if (!use_pts) - return; + tp = tty_alloc(&pts_class, psc, NULL); - if (*dev != NULL) - return; + /* Expose the slave device as well. */ + tty_makedev(tp, td->td_ucred, "%s", name); - if (strcmp(name, "ptmx") != 0) - return; + finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); - mtx_lock(&Giant); - pt = pty_new(); - if (pt == NULL) { - mtx_unlock(&Giant); - return; - } + return (0); +} +#endif /* PTS_EXTERNAL */ + +int +posix_openpt(struct thread *td, struct posix_openpt_args *uap) +{ + int error, fd; + struct file *fp; /* - * XXX: Lack of locking here considered worrying. We expose the - * pts/pty device nodes before they are fully initialized, although - * Giant likely protects us (unless make_dev blocks...?). - * - * XXX: If a process performs a lookup on /dev/ptmx but never an - * open, we won't GC the device node. We should have a callout - * sometime later that GC's device instances that were never - * opened, or some way to tell devfs that "this had better be for - * an open() or we won't create a device". + * POSIX states it's unspecified when other flags are passed. We + * don't allow this. */ - pt->pt_devc = devc = make_dev_credf(MAKEDEV_REF, &ptc_cdevsw, - NUM_TO_MINOR(pt->pt_num), cred, UID_ROOT, GID_WHEEL, 0666, - "pty/%d", pt->pt_num); + if (uap->flags & ~(O_RDWR|O_NOCTTY)) + return (EINVAL); + + error = falloc(td, &fp, &fd); + if (error) + return (error); + + /* Allocate the actual pseudo-TTY. */ + error = pts_alloc(FFLAGS(uap->flags & O_ACCMODE), td, fp); + if (error != 0) { + fdclose(td->td_proc->p_fd, fp, fd, td); + return (error); + } + + /* Pass it back to userspace. */ + td->td_retval[0] = fd; + fdrop(fp, td); + + return (0); +} - devc->si_drv1 = pt; - devc->si_tty = pt->pt_tty; - *dev = devc; - mtx_unlock(&Giant); +#if defined(PTS_COMPAT) || defined(PTS_LINUX) +static int +ptmx_fdopen(struct cdev *dev, int fflags, struct thread *td, struct file *fp) +{ + int error; - if (bootverbose) - printf("pty_clone: allocated pty %d to uid %d\n", pt->pt_num, - cred->cr_ruid); + error = pts_alloc(fflags & (FREAD|FWRITE), td, fp); + if (error != 0) + return (error); - return; + return (0); } +static struct cdevsw ptmx_cdevsw = { + .d_version = D_VERSION, + .d_fdopen = ptmx_fdopen, + .d_name = "ptmx", +}; +#endif /* PTS_COMPAT || PTS_LINUX */ + static void -pty_drvinit(void *unused) +pts_init(void *unused) { - mtx_init(&pt_mtx, "pt_mtx", NULL, MTX_DEF); - LIST_INIT(&pt_list); - LIST_INIT(&pt_free_list); - EVENTHANDLER_REGISTER(dev_clone, pty_clone, 0, 1000); + pts_pool = new_unrhdr(0, MAXPTSDEVS, NULL); +#if defined(PTS_COMPAT) || defined(PTS_LINUX) + make_dev(&ptmx_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, "ptmx"); +#endif /* PTS_COMPAT || PTS_LINUX */ } -SYSINIT(ptydev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE,pty_drvinit,NULL); +SYSINIT(pts, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, pts_init, NULL); diff --git a/sys/kern/tty_pty.c b/sys/kern/tty_pty.c index 3827e82..e2edd21 100644 --- a/sys/kern/tty_pty.c +++ b/sys/kern/tty_pty.c @@ -1,6 +1,9 @@ /*- - * Copyright (c) 1982, 1986, 1989, 1993 - * The Regents of the University of California. All rights reserved. + * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,14 +13,11 @@ * 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. - * 4. 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 + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 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) @@ -25,793 +25,101 @@ * 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_pty.c 8.4 (Berkeley) 2/20/95 */ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -/* - * Pseudo-teletype Driver - * (Actually two drivers, requiring two entries in 'cdevsw') - */ -#include "opt_compat.h" -#include "opt_tty.h" #include <sys/param.h> -#include <sys/systm.h> -#include <sys/libkern.h> -#include <sys/lock.h> -#include <sys/mutex.h> -#include <sys/sx.h> -#if defined(COMPAT_43TTY) -#include <sys/ioctl_compat.h> -#endif -#include <sys/priv.h> -#include <sys/proc.h> -#include <sys/tty.h> #include <sys/conf.h> +#include <sys/eventhandler.h> #include <sys/fcntl.h> -#include <sys/poll.h> #include <sys/kernel.h> -#include <sys/uio.h> -#include <sys/signalvar.h> -#include <sys/malloc.h> - -static MALLOC_DEFINE(M_PTY, "ptys", "pty data structures"); - -static void ptsstart(struct tty *tp); -static void ptsstop(struct tty *tp, int rw); -static void ptcwakeup(struct tty *tp, int flag); -static struct cdev *ptyinit(struct cdev *cdev, struct thread *td); - -static d_open_t ptsopen; -static d_close_t ptsclose; -static d_read_t ptsread; -static d_write_t ptswrite; -static d_ioctl_t ptsioctl; -static d_open_t ptcopen; -static d_close_t ptcclose; -static d_read_t ptcread; -static d_ioctl_t ptcioctl; -static d_write_t ptcwrite; -static d_poll_t ptcpoll; - -static struct cdevsw pts_cdevsw = { - .d_version = D_VERSION, - .d_open = ptsopen, - .d_close = ptsclose, - .d_read = ptsread, - .d_write = ptswrite, - .d_ioctl = ptsioctl, - .d_name = "pts", - .d_flags = D_TTY | D_NEEDGIANT, -}; - -static struct cdevsw ptc_cdevsw = { - .d_version = D_VERSION, - .d_open = ptcopen, - .d_close = ptcclose, - .d_read = ptcread, - .d_write = ptcwrite, - .d_ioctl = ptcioctl, - .d_poll = ptcpoll, - .d_name = "ptc", - .d_flags = D_TTY | D_NEEDGIANT, -}; - -static struct mtx ptyinit_lock; -MTX_SYSINIT(ptyinit_lock, &ptyinit_lock, "ptyinit", MTX_DEF); - -#define BUFSIZ 100 /* Chunk size iomoved to/from user */ - -struct ptsc { - int pt_flags; - struct selinfo pt_selr, pt_selw; - u_char pt_send; - u_char pt_ucntl; - struct tty *pt_tty; - struct cdev *devs, *devc; - int pt_devs_open, pt_devc_open; - struct prison *pt_prison; -}; - -#define PF_PKT 0x08 /* packet mode */ -#define PF_STOPPED 0x10 /* user told stopped */ -#define PF_NOSTOP 0x40 -#define PF_UCNTL 0x80 /* user control mode */ - -#define TSA_PTC_READ(tp) ((void *)&(tp)->t_outq.c_cf) -#define TSA_PTC_WRITE(tp) ((void *)&(tp)->t_rawq.c_cl) -#define TSA_PTS_READ(tp) ((void *)&(tp)->t_canq) +#include <sys/proc.h> +#include <sys/systm.h> +#include <sys/tty.h> -static const char names[] = "pqrsPQRSlmnoLMNO"; /* - * This function creates and initializes a pts/ptc pair - * - * pts == /dev/tty[pqrsPQRSlmnoLMNO][0123456789abcdefghijklmnopqrstuv] - * ptc == /dev/pty[pqrsPQRSlmnoLMNO][0123456789abcdefghijklmnopqrstuv] + * This driver implements a BSD-style compatibility naming scheme for + * the pts(4) driver. We just call into pts(4) to create the actual PTY. + * To make sure we don't use the same PTY multiple times, we abuse + * si_drv1 inside the cdev to mark whether the PTY is in use. */ -static struct cdev * -ptyinit(struct cdev *devc, struct thread *td) -{ - struct ptsc *pt; - int n; - n = minor2unit(minor(devc)); +static int pty_warningcnt = 10; - /* We only allow for up to 32 ptys per char in "names". */ - if (n >= 32 * (sizeof(names) - 1)) - return (NULL); - - devc->si_flags &= ~SI_CHEAPCLONE; - - /* - * Initially do not create a slave endpoint. - */ - pt = malloc(sizeof(*pt), M_PTY, M_WAITOK | M_ZERO); - pt->devc = devc; - - pt->pt_tty = ttyalloc(); - pt->pt_tty->t_sc = pt; - mtx_lock(&ptyinit_lock); - if (devc->si_drv1 == NULL) { - devc->si_drv1 = pt; - devc->si_tty = pt->pt_tty; - mtx_unlock(&ptyinit_lock); - } else { - mtx_unlock(&ptyinit_lock); - ttyrel(pt->pt_tty); - free(pt, M_PTY); - } - return (devc); -} - -static void -pty_create_slave(struct ucred *cred, struct ptsc *pt, int m) +static int +ptydev_fdopen(struct cdev *dev, int fflags, struct thread *td, struct file *fp) { - int n; + int u, error; + char name[] = "ttyXX"; - n = minor2unit(m); - KASSERT(n >= 0 && n / 32 < sizeof(names), - ("pty_create_slave: n %d ptsc %p", n, pt)); - pt->devs = make_dev_cred(&pts_cdevsw, m, cred, UID_ROOT, GID_WHEEL, - 0666, "tty%c%r", names[n / 32], n % 32); - pt->devs->si_drv1 = pt; - pt->devs->si_tty = pt->pt_tty; - pt->pt_tty->t_dev = pt->devs; -} - -static void -pty_destroy_slave(struct ptsc *pt) -{ - - if (pt->pt_tty->t_refcnt > 1) - return; - pt->pt_tty->t_dev = NULL; - ttyrel(pt->pt_tty); - pt->pt_tty = NULL; - destroy_dev(pt->devs); - pt->devs = NULL; -} - -static void -pty_maybe_destroy_slave(struct ptsc *pt) -{ - - /* - * vfs bugs and complications near revoke() make - * it currently impossible to destroy struct cdev - */ - if (0 && pt->pt_devc_open == 0 && pt->pt_devs_open == 0) - pty_destroy_slave(pt); -} - -/*ARGSUSED*/ -static int -ptsopen(struct cdev *dev, int flag, int devtype, struct thread *td) -{ - struct tty *tp; - int error; - struct ptsc *pt; - - if (!dev->si_drv1) - return(ENXIO); - pt = dev->si_drv1; - tp = dev->si_tty; - - if ((tp->t_state & TS_ISOPEN) == 0) { - ttyinitmode(tp, 1, 0); - } else if (tp->t_state & TS_XCLUDE && priv_check(td, - PRIV_TTY_EXCLUSIVE)) - return (EBUSY); - else if (pt->pt_prison != td->td_ucred->cr_prison && - priv_check(td, PRIV_TTY_PRISON)) + if (!atomic_cmpset_ptr((uintptr_t *)&dev->si_drv1, 0, 1)) return (EBUSY); - if (tp->t_oproc) /* Ctrlr still around. */ - (void)ttyld_modem(tp, 1); - while ((tp->t_state & TS_CARR_ON) == 0) { - if (flag&FNONBLOCK) - break; - error = ttysleep(tp, TSA_CARR_ON(tp), TTIPRI | PCATCH, - "ptsopn", 0); - if (error) - return (error); - } - error = ttyld_open(tp, dev); - if (error == 0) { - ptcwakeup(tp, FREAD|FWRITE); - pt->pt_devs_open = 1; - } else - pty_maybe_destroy_slave(pt); - return (error); -} - -static int -ptsclose(struct cdev *dev, int flag, int mode, struct thread *td) -{ - struct ptsc *pti; - struct tty *tp; - int err; - - tp = dev->si_tty; - pti = dev->si_drv1; - - KASSERT(dev == pti->devs, ("ptsclose: dev != pti->devs")); - - err = ttyld_close(tp, flag); - (void) tty_close(tp); - - pti->pt_devs_open = 0; - pty_maybe_destroy_slave(pti); - - return (err); -} - -static int -ptsread(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp = dev->si_tty; - int error = 0; - - if (tp->t_oproc) - error = ttyld_read(tp, uio, flag); - ptcwakeup(tp, FWRITE); - return (error); -} - -/* - * Write to pseudo-tty. - * Wakeups of controlling tty will happen - * indirectly, when tty driver calls ptsstart. - */ -static int -ptswrite(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp; - - tp = dev->si_tty; - if (tp->t_oproc == 0) - return (EIO); - return (ttyld_write(tp, uio, flag)); -} - -/* - * Start output on pseudo-tty. - * Wake up process selecting or sleeping for input from controlling tty. - */ -static void -ptsstart(struct tty *tp) -{ - struct ptsc *pt = tp->t_sc; - - if (tp->t_state & TS_TTSTOP) - return; - if (pt->pt_flags & PF_STOPPED) { - pt->pt_flags &= ~PF_STOPPED; - pt->pt_send = TIOCPKT_START; - } - ptcwakeup(tp, FREAD); -} - -static void -ptcwakeup(struct tty *tp, int flag) -{ - struct ptsc *pt = tp->t_sc; - - if (flag & FREAD) { - selwakeuppri(&pt->pt_selr, TTIPRI); - wakeup(TSA_PTC_READ(tp)); - } - if (flag & FWRITE) { - selwakeuppri(&pt->pt_selw, TTOPRI); - wakeup(TSA_PTC_WRITE(tp)); - } -} - -static int -ptcopen(struct cdev *dev, int flag, int devtype, struct thread *td) -{ - struct tty *tp; - struct ptsc *pt; - - if (!dev->si_drv1) - ptyinit(dev, td); - if (!dev->si_drv1) - return(ENXIO); - - pt = dev->si_drv1; - /* - * In case we have destroyed the struct tty at the last connect time, - * we need to recreate it. - */ - if (pt->pt_tty == NULL) { - tp = ttyalloc(); - mtx_lock(&ptyinit_lock); - if (pt->pt_tty == NULL) { - pt->pt_tty = tp; - pt->pt_tty->t_sc = pt; - dev->si_tty = pt->pt_tty; - mtx_unlock(&ptyinit_lock); - } else { - mtx_unlock(&ptyinit_lock); - ttyrel(tp); - } - } - tp = dev->si_tty; - - if (tp->t_oproc) - return (EIO); - tp->t_timeout = -1; - tp->t_oproc = ptsstart; - tp->t_stop = ptsstop; - (void)ttyld_modem(tp, 1); - tp->t_lflag &= ~EXTPROC; - pt->pt_prison = td->td_ucred->cr_prison; - pt->pt_flags = 0; - pt->pt_send = 0; - pt->pt_ucntl = 0; - - if (!pt->devs) - pty_create_slave(td->td_ucred, pt, minor(dev)); - pt->pt_devc_open = 1; - - return (0); -} - -static int -ptcclose(struct cdev *dev, int flags, int fmt, struct thread *td) -{ - struct ptsc *pti = dev->si_drv1; - struct tty *tp; - - tp = dev->si_tty; - (void)ttyld_modem(tp, 0); - - /* - * XXX MDMBUF makes no sense for ptys but would inhibit the above - * l_modem(). CLOCAL makes sense but isn't supported. Special - * l_modem()s that ignore carrier drop make no sense for ptys but - * may be in use because other parts of the line discipline make - * sense for ptys. Recover by doing everything that a normal - * ttymodem() would have done except for sending a SIGHUP. - */ - if (tp->t_state & TS_ISOPEN) { - tp->t_state &= ~(TS_CARR_ON | TS_CONNECTED); - tp->t_state |= TS_ZOMBIE; - ttyflush(tp, FREAD | FWRITE); - } - - tp->t_oproc = 0; /* mark closed */ - pti->pt_devc_open = 0; - pty_maybe_destroy_slave(pti); - return (0); -} - -static int -ptcread(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp = dev->si_tty; - struct ptsc *pt = dev->si_drv1; - char buf[BUFSIZ]; - int error = 0, cc; - - /* - * We want to block until the slave - * is open, and there's something to read; - * but if we lost the slave or we're NBIO, - * then return the appropriate error instead. - */ - for (;;) { - if (tp->t_state&TS_ISOPEN) { - if (pt->pt_flags&PF_PKT && pt->pt_send) { - error = ureadc((int)pt->pt_send, uio); - if (error) - return (error); - if (pt->pt_send & TIOCPKT_IOCTL) { - cc = min(uio->uio_resid, - sizeof(tp->t_termios)); - uiomove(&tp->t_termios, cc, uio); - } - pt->pt_send = 0; - return (0); - } - if (pt->pt_flags&PF_UCNTL && pt->pt_ucntl) { - error = ureadc((int)pt->pt_ucntl, uio); - if (error) - return (error); - pt->pt_ucntl = 0; - return (0); - } - if (tp->t_outq.c_cc && (tp->t_state&TS_TTSTOP) == 0) - break; - } - if ((tp->t_state & TS_CONNECTED) == 0) - return (0); /* EOF */ - if (flag & O_NONBLOCK) - return (EWOULDBLOCK); - error = tsleep(TSA_PTC_READ(tp), TTIPRI | PCATCH, "ptcin", 0); - if (error) - return (error); - } - if (pt->pt_flags & (PF_PKT|PF_UCNTL)) - error = ureadc(0, uio); - while (uio->uio_resid > 0 && error == 0) { - cc = q_to_b(&tp->t_outq, buf, min(uio->uio_resid, BUFSIZ)); - if (cc <= 0) - break; - error = uiomove(buf, cc, uio); - } - ttwwakeup(tp); - return (error); -} - -static void -ptsstop(struct tty *tp, int flush) -{ - struct ptsc *pt = tp->t_sc; - int flag; - - /* note: FLUSHREAD and FLUSHWRITE already ok */ - if (flush == 0) { - flush = TIOCPKT_STOP; - pt->pt_flags |= PF_STOPPED; - } else - pt->pt_flags &= ~PF_STOPPED; - pt->pt_send |= flush; - /* change of perspective */ - flag = 0; - if (flush & FREAD) - flag |= FWRITE; - if (flush & FWRITE) - flag |= FREAD; - ptcwakeup(tp, flag); -} - -static int -ptcpoll(struct cdev *dev, int events, struct thread *td) -{ - struct tty *tp = dev->si_tty; - struct ptsc *pt = dev->si_drv1; - int revents = 0; - int s; - - if ((tp->t_state & TS_CONNECTED) == 0) - return (events & - (POLLHUP | POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)); - /* - * Need to block timeouts (ttrstart). - */ - s = spltty(); + /* Generate device name and create PTY. */ + u = dev2unit(dev); + name[3] = u >> 8; + name[4] = u; - if (events & (POLLIN | POLLRDNORM)) - if ((tp->t_state & TS_ISOPEN) && - ((tp->t_outq.c_cc && (tp->t_state & TS_TTSTOP) == 0) || - ((pt->pt_flags & PF_PKT) && pt->pt_send) || - ((pt->pt_flags & PF_UCNTL) && pt->pt_ucntl))) - revents |= events & (POLLIN | POLLRDNORM); - - if (events & (POLLOUT | POLLWRNORM)) - if (tp->t_state & TS_ISOPEN && - (((tp->t_rawq.c_cc + tp->t_canq.c_cc < TTYHOG - 2) || - (tp->t_canq.c_cc == 0 && (tp->t_lflag & ICANON))))) - revents |= events & (POLLOUT | POLLWRNORM); - - if (events & POLLHUP) - if ((tp->t_state & TS_CARR_ON) == 0) - revents |= POLLHUP; - - if (revents == 0) { - if (events & (POLLIN | POLLRDNORM)) - selrecord(td, &pt->pt_selr); - - if (events & (POLLOUT | POLLWRNORM)) - selrecord(td, &pt->pt_selw); - } - splx(s); - - return (revents); -} - -static int -ptcwrite(struct cdev *dev, struct uio *uio, int flag) -{ - struct tty *tp = dev->si_tty; - u_char *cp = 0; - int cc = 0; - u_char locbuf[BUFSIZ]; - int cnt = 0; - int error = 0; - -again: - if ((tp->t_state&TS_ISOPEN) == 0) - goto block; - while (uio->uio_resid > 0 || cc > 0) { - if (cc == 0) { - cc = min(uio->uio_resid, BUFSIZ); - cp = locbuf; - error = uiomove(cp, cc, uio); - if (error) - return (error); - /* check again for safety */ - if ((tp->t_state & TS_ISOPEN) == 0) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; - return (EIO); - } - } - while (cc > 0) { - if ((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= TTYHOG - 2 && - (tp->t_canq.c_cc > 0 || !(tp->t_lflag&ICANON))) { - wakeup(TSA_HUP_OR_INPUT(tp)); - goto block; - } - ttyld_rint(tp, *cp++); - cnt++; - cc--; - } - cc = 0; - } - return (0); -block: - /* - * Come here to wait for slave to open, for space - * in outq, or space in rawq, or an empty canq. - */ - if ((tp->t_state & TS_CONNECTED) == 0) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; - return (EIO); - } - if (flag & O_NONBLOCK) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; - if (cnt == 0) - return (EWOULDBLOCK); - return (0); - } - error = tsleep(TSA_PTC_WRITE(tp), TTOPRI | PCATCH, "ptcout", 0); - if (error) { - /* adjust for data copied in but not written */ - uio->uio_resid += cc; + error = pts_alloc_external(fflags & (FREAD|FWRITE), td, fp, dev, name); + if (error != 0) { + destroy_dev_sched(dev); return (error); } - goto again; -} - -/*ARGSUSED*/ -static int -ptcioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) -{ - struct tty *tp = dev->si_tty; - struct ptsc *pt = dev->si_drv1; -#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ - defined(COMPAT_FREEBSD4) || defined(COMPAT_43) - int ival; -#endif - - switch (cmd) { - - case TIOCGPGRP: - /* - * We avoid calling ttioctl on the controller since, - * in that case, tp must be the controlling terminal. - */ - *(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : 0; - return (0); - - case TIOCPKT: - if (*(int *)data) { - if (pt->pt_flags & PF_UCNTL) - return (EINVAL); - pt->pt_flags |= PF_PKT; - } else - pt->pt_flags &= ~PF_PKT; - return (0); - - case TIOCUCNTL: - if (*(int *)data) { - if (pt->pt_flags & PF_PKT) - return (EINVAL); - pt->pt_flags |= PF_UCNTL; - } else - pt->pt_flags &= ~PF_UCNTL; - return (0); - } - - /* - * The rest of the ioctls shouldn't be called until - * the slave is open. - */ - if ((tp->t_state & TS_ISOPEN) == 0) - return (EAGAIN); - - switch (cmd) { -#ifdef COMPAT_43TTY - case TIOCSETP: - case TIOCSETN: -#endif - case TIOCSETD: - case TIOCSETA: - case TIOCSETAW: - case TIOCSETAF: - /* - * IF CONTROLLER STTY THEN MUST FLUSH TO PREVENT A HANG. - * ttywflush(tp) will hang if there are characters in - * the outq. - */ - ndflush(&tp->t_outq, tp->t_outq.c_cc); - break; -#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ - defined(COMPAT_FREEBSD4) || defined(COMPAT_43) - case _IO('t', 95): - ival = IOCPARM_IVAL(data); - data = (caddr_t)&ival; - /* FALLTHROUGH */ -#endif - case TIOCSIG: - if (*(unsigned int *)data >= NSIG || - *(unsigned int *)data == 0) - return(EINVAL); - if ((tp->t_lflag&NOFLSH) == 0) - ttyflush(tp, FREAD|FWRITE); - if (tp->t_pgrp != NULL) { - PGRP_LOCK(tp->t_pgrp); - pgsignal(tp->t_pgrp, *(unsigned int *)data, 1); - PGRP_UNLOCK(tp->t_pgrp); - } - if ((*(unsigned int *)data == SIGINFO) && - ((tp->t_lflag&NOKERNINFO) == 0)) - ttyinfo(tp); - return(0); + /* Raise a warning when a legacy PTY has been allocated. */ + if (pty_warningcnt > 0) { + pty_warningcnt--; + printf("pid %d (%s) is using legacy pty devices%s\n", + td->td_proc->p_pid, td->td_name, + pty_warningcnt ? "" : " - not logging anymore"); } - return (ptsioctl(dev, cmd, data, flag, td)); + return (0); } -/*ARGSUSED*/ -static int -ptsioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) -{ - struct tty *tp = dev->si_tty; - struct ptsc *pt = dev->si_drv1; - u_char *cc = tp->t_cc; - int stop, error; - - if (cmd == TIOCEXT) { - /* - * When the EXTPROC bit is being toggled, we need - * to send an TIOCPKT_IOCTL if the packet driver - * is turned on. - */ - if (*(int *)data) { - if (pt->pt_flags & PF_PKT) { - pt->pt_send |= TIOCPKT_IOCTL; - ptcwakeup(tp, FREAD); - } - tp->t_lflag |= EXTPROC; - } else { - if ((tp->t_lflag & EXTPROC) && - (pt->pt_flags & PF_PKT)) { - pt->pt_send |= TIOCPKT_IOCTL; - ptcwakeup(tp, FREAD); - } - tp->t_lflag &= ~EXTPROC; - } - return(0); - } - error = ttyioctl(dev, cmd, data, flag, td); - if (error == ENOTTY) { - if (pt->pt_flags & PF_UCNTL && - (cmd & ~0xff) == UIOCCMD(0)) { - if (cmd & 0xff) { - pt->pt_ucntl = (u_char)cmd; - ptcwakeup(tp, FREAD); - } - return (0); - } - error = ENOTTY; - } - /* - * If external processing and packet mode send ioctl packet. - */ - if ((tp->t_lflag&EXTPROC) && (pt->pt_flags & PF_PKT)) { - switch(cmd) { - case TIOCSETA: - case TIOCSETAW: - case TIOCSETAF: -#ifdef COMPAT_43TTY - case TIOCSETP: - case TIOCSETN: - case TIOCSETC: - case TIOCSLTC: - case TIOCLBIS: - case TIOCLBIC: - case TIOCLSET: -#endif - pt->pt_send |= TIOCPKT_IOCTL; - ptcwakeup(tp, FREAD); - break; - default: - break; - } - } - stop = (tp->t_iflag & IXON) && CCEQ(cc[VSTOP], CTRL('s')) - && CCEQ(cc[VSTART], CTRL('q')); - if (pt->pt_flags & PF_NOSTOP) { - if (stop) { - pt->pt_send &= ~TIOCPKT_NOSTOP; - pt->pt_send |= TIOCPKT_DOSTOP; - pt->pt_flags &= ~PF_NOSTOP; - ptcwakeup(tp, FREAD); - } - } else { - if (!stop) { - pt->pt_send &= ~TIOCPKT_DOSTOP; - pt->pt_send |= TIOCPKT_NOSTOP; - pt->pt_flags |= PF_NOSTOP; - ptcwakeup(tp, FREAD); - } - } - return (error); -} +static struct cdevsw ptydev_cdevsw = { + .d_version = D_VERSION, + .d_fdopen = ptydev_fdopen, + .d_name = "ptydev", +}; static void pty_clone(void *arg, struct ucred *cr, char *name, int namelen, struct cdev **dev) { - char *cp; int u; + /* Cloning is already satisfied. */ if (*dev != NULL) return; - if (bcmp(name, "pty", 3) != 0) - return; - if (name[5] != '\0' || name[3] == '\0') + + /* Only catch /dev/ptyXX. */ + if (namelen != 5 || bcmp(name, "pty", 3) != 0) return; - cp = index(names, name[3]); - if (cp == NULL) + + /* Only catch /dev/pty[l-sL-S]X. */ + if (!(name[3] >= 'l' && name[3] <= 's') && + !(name[3] >= 'L' && name[3] <= 'S')) return; - u = (cp - names) * 32; - if (name[4] >= '0' && name[4] <= '9') - u += name[4] - '0'; - else if (name[4] >= 'a' && name[4] <= 'v') - u += name[4] - 'a' + 10; - else + + /* Only catch /dev/pty[l-sL-S][0-9a-v]. */ + if (!(name[4] >= '0' && name[4] <= '9') && + !(name[4] >= 'a' && name[4] <= 'v')) return; - *dev = make_dev_credf(MAKEDEV_REF, &ptc_cdevsw, unit2minor(u), cr, - UID_ROOT, GID_WHEEL, 0666, "pty%c%r", names[u / 32], u % 32); - (*dev)->si_flags |= SI_CHEAPCLONE; - return; + + /* Create the controller device node. */ + u = (unsigned int)name[3] << 8 | name[4]; + *dev = make_dev_credf(MAKEDEV_REF, &ptydev_cdevsw, u, + NULL, UID_ROOT, GID_WHEEL, 0666, name); } static void -ptc_drvinit(void *unused) +pty_init(void *unused) { EVENTHANDLER_REGISTER(dev_clone, pty_clone, 0, 1000); } -SYSINIT(ptcdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE,ptc_drvinit,NULL); +SYSINIT(pty, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, pty_init, NULL); diff --git a/sys/kern/tty_ttydisc.c b/sys/kern/tty_ttydisc.c new file mode 100644 index 0000000..b52fc18 --- /dev/null +++ b/sys/kern/tty_ttydisc.c @@ -0,0 +1,1131 @@ +/*- + * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/fcntl.h> +#include <sys/filio.h> +#include <sys/kernel.h> +#include <sys/signal.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/tty.h> +#include <sys/ttycom.h> +#include <sys/ttydefaults.h> +#include <sys/uio.h> +#include <sys/vnode.h> + +/* + * Standard TTYDISC `termios' line discipline. + */ + +/* Statistics. */ +static long tty_nin = 0; +SYSCTL_LONG(_kern, OID_AUTO, tty_nin, CTLFLAG_RD, + &tty_nin, 0, "Total amount of bytes received"); +static long tty_nout = 0; +SYSCTL_LONG(_kern, OID_AUTO, tty_nout, CTLFLAG_RD, + &tty_nout, 0, "Total amount of bytes transmitted"); + +/* termios comparison macro's. */ +#define CMP_CC(v,c) (tp->t_termios.c_cc[v] != _POSIX_VDISABLE && \ + tp->t_termios.c_cc[v] == (c)) +#define CMP_FLAG(field,opt) (tp->t_termios.c_ ## field ## flag & (opt)) + +/* Characters that cannot be modified through c_cc. */ +#define CTAB '\t' +#define CNL '\n' +#define CCR '\r' + +/* Character is a control character. */ +#define CTL_VALID(c) ((c) == 0x7f || (unsigned char)(c) < 0x20) +/* Control character should be processed on echo. */ +#define CTL_ECHO(c,q) (!(q) && ((c) == CERASE2 || (c) == CTAB || \ + (c) == CNL || (c) == CCR)) +/* Control character should be printed using ^X notation. */ +#define CTL_PRINT(c,q) ((c) == 0x7f || ((unsigned char)(c) < 0x20 && \ + ((q) || ((c) != CTAB && (c) != CNL)))) +/* Character is whitespace. */ +#define CTL_WHITE(c) ((c) == ' ' || (c) == CTAB) +/* Character is alphanumeric. */ +#define CTL_ALNUM(c) (((c) >= '0' && (c) <= '9') || \ + ((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) + +void +ttydisc_open(struct tty *tp) +{ + ttydisc_optimize(tp); +} + +void +ttydisc_close(struct tty *tp) +{ + + /* Clean up our flags when leaving the discipline. */ + tp->t_flags &= ~(TF_STOPPED|TF_HIWAT|TF_ZOMBIE); + + /* POSIX states we should flush when close() is called. */ + ttyinq_flush(&tp->t_inq); + ttyoutq_flush(&tp->t_outq); + + if (!tty_gone(tp)) { + ttydevsw_inwakeup(tp); + ttydevsw_outwakeup(tp); + } +} + +static int +ttydisc_read_canonical(struct tty *tp, struct uio *uio, int ioflag) +{ + char breakc[4] = { CNL }; /* enough to hold \n, VEOF and VEOL. */ + int error; + size_t clen, flen = 0, n = 1; + unsigned char lastc = _POSIX_VDISABLE; + +#define BREAK_ADD(c) do { \ + if (tp->t_termios.c_cc[c] != _POSIX_VDISABLE) \ + breakc[n++] = tp->t_termios.c_cc[c]; \ +} while (0) + /* Determine which characters we should trigger on. */ + BREAK_ADD(VEOF); + BREAK_ADD(VEOL); +#undef BREAK_ADD + breakc[n] = '\0'; + + do { + /* + * Quite a tricky case: unlike the old TTY + * implementation, this implementation copies data back + * to userspace in large chunks. Unfortunately, we can't + * calculate the line length on beforehand if it crosses + * ttyinq_block boundaries, because multiple reads could + * then make this code read beyond the newline. + * + * This is why we limit the read to: + * - The size the user has requested + * - The blocksize (done in tty_inq.c) + * - The amount of bytes until the newline + * + * This causes the line length to be recalculated after + * each block has been copied to userspace. This will + * cause the TTY layer to return data in chunks using + * the blocksize (except the first and last blocks). + */ + clen = ttyinq_findchar(&tp->t_inq, breakc, uio->uio_resid, + &lastc); + + /* No more data. */ + if (clen == 0) { + if (ioflag & IO_NDELAY) + return (EWOULDBLOCK); + else if (tp->t_flags & TF_ZOMBIE) + return (0); + + error = tty_wait(tp, &tp->t_inwait); + if (error) + return (error); + continue; + } + + /* Don't send the EOF char back to userspace. */ + if (CMP_CC(VEOF, lastc)) + flen = 1; + + MPASS(flen <= clen); + + /* Read and throw away the EOF character. */ + error = ttyinq_read_uio(&tp->t_inq, tp, uio, clen, flen); + if (error) + return (error); + + } while (uio->uio_resid > 0 && lastc == _POSIX_VDISABLE); + + return (0); +} + +static int +ttydisc_read_raw_no_timer(struct tty *tp, struct uio *uio, int ioflag) +{ + size_t vmin = tp->t_termios.c_cc[VMIN]; + int oresid = uio->uio_resid; + int error; + + MPASS(tp->t_termios.c_cc[VTIME] == 0); + + /* + * This routine implements the easy cases of read()s while in + * non-canonical mode, namely case B and D, where we don't have + * any timers at all. + */ + + for (;;) { + error = ttyinq_read_uio(&tp->t_inq, tp, uio, + uio->uio_resid, 0); + if (error) + return (error); + if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) + return (0); + + /* We have to wait for more. */ + if (ioflag & IO_NDELAY) + return (EWOULDBLOCK); + else if (tp->t_flags & TF_ZOMBIE) + return (0); + + error = tty_wait(tp, &tp->t_inwait); + if (error) + return (error); + } +} + +static int +ttydisc_read_raw_read_timer(struct tty *tp, struct uio *uio, int ioflag, + int oresid) +{ + size_t vmin = MAX(tp->t_termios.c_cc[VMIN], 1); + unsigned int vtime = tp->t_termios.c_cc[VTIME]; + struct timeval end, now, left; + int error, hz; + + MPASS(tp->t_termios.c_cc[VTIME] != 0); + + /* Determine when the read should be expired. */ + end.tv_sec = vtime / 10; + end.tv_usec = (vtime % 10) * 100000; + getmicrotime(&now); + timevaladd(&end, &now); + + for (;;) { + error = ttyinq_read_uio(&tp->t_inq, tp, uio, + uio->uio_resid, 0); + if (error) + return (error); + if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) + return (0); + + /* Calculate how long we should wait. */ + getmicrotime(&now); + if (timevalcmp(&now, &end, >)) + return (0); + left = end; + timevalsub(&left, &now); + hz = tvtohz(&left); + + /* + * We have to wait for more. If the timer expires, we + * should return a 0-byte read. + */ + if (ioflag & IO_NDELAY) + return (EWOULDBLOCK); + else if (tp->t_flags & TF_ZOMBIE) + return (0); + + error = tty_timedwait(tp, &tp->t_inwait, hz); + if (error) + return (error == EWOULDBLOCK ? 0 : error); + } + + return (0); +} + +static int +ttydisc_read_raw_interbyte_timer(struct tty *tp, struct uio *uio, int ioflag) +{ + size_t vmin = tp->t_termios.c_cc[VMIN]; + int oresid = uio->uio_resid; + int error; + + MPASS(tp->t_termios.c_cc[VMIN] != 0); + MPASS(tp->t_termios.c_cc[VTIME] != 0); + + /* + * When using the interbyte timer, the timer should be started + * after the first byte has been received. We just call into the + * generic read timer code after we've received the first byte. + */ + + for (;;) { + error = ttyinq_read_uio(&tp->t_inq, tp, uio, + uio->uio_resid, 0); + if (error) + return (error); + if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) + return (0); + + /* + * Not enough data, but we did receive some, which means + * we'll now start using the interbyte timer. + */ + if (oresid != uio->uio_resid) + break; + + /* We have to wait for more. */ + if (ioflag & IO_NDELAY) + return (EWOULDBLOCK); + else if (tp->t_flags & TF_ZOMBIE) + return (0); + + error = tty_wait(tp, &tp->t_inwait); + if (error) + return (error); + } + + return ttydisc_read_raw_read_timer(tp, uio, ioflag, oresid); +} + +int +ttydisc_read(struct tty *tp, struct uio *uio, int ioflag) +{ + int error; + size_t c; + + tty_lock_assert(tp, MA_OWNED); + + if (uio->uio_resid == 0) + return (0); + + if (CMP_FLAG(l, ICANON)) + error = ttydisc_read_canonical(tp, uio, ioflag); + else if (tp->t_termios.c_cc[VTIME] == 0) + error = ttydisc_read_raw_no_timer(tp, uio, ioflag); + else if (tp->t_termios.c_cc[VMIN] == 0) + error = ttydisc_read_raw_read_timer(tp, uio, ioflag, + uio->uio_resid); + else + error = ttydisc_read_raw_interbyte_timer(tp, uio, ioflag); + + c = ttyinq_bytesleft(&tp->t_inq); + if (c >= tp->t_inlow) { + /* Unset the input watermark when we've got enough space. */ + tty_hiwat_in_unblock(tp); + } + + return (error); +} + +static __inline unsigned int +ttydisc_findchar(const char *obstart, unsigned int oblen) +{ + const char *c = obstart; + + while (oblen--) { + if (CTL_VALID(*c)) + break; + c++; + } + + return (c - obstart); +} + +static int +ttydisc_write_oproc(struct tty *tp, char c) +{ + unsigned int scnt, error; + + MPASS(CMP_FLAG(o, OPOST)); + MPASS(CTL_VALID(c)); + +#define PRINT_NORMAL() ttyoutq_write_nofrag(&tp->t_outq, &c, 1) + switch (c) { + case CEOF: + /* End-of-text dropping. */ + if (CMP_FLAG(o, ONOEOT)) + return (0); + return PRINT_NORMAL(); + + case CERASE2: + /* Handle backspace to fix tab expansion. */ + if (PRINT_NORMAL() != 0) + return (-1); + if (tp->t_column > 0) + tp->t_column--; + return (0); + + case CTAB: + /* Tab expansion. */ + scnt = 8 - (tp->t_column & 7); + if (CMP_FLAG(o, TAB3)) { + error = ttyoutq_write_nofrag(&tp->t_outq, + " ", scnt); + } else { + error = PRINT_NORMAL(); + } + if (error) + return (-1); + + tp->t_column += scnt; + MPASS((tp->t_column % 8) == 0); + return (0); + + case CNL: + /* Newline conversion. */ + if (CMP_FLAG(o, ONLCR)) { + /* Convert \n to \r\n. */ + error = ttyoutq_write_nofrag(&tp->t_outq, "\r\n", 2); + } else { + error = PRINT_NORMAL(); + } + if (error) + return (-1); + + if (CMP_FLAG(o, ONLCR|ONLRET)) { + tp->t_column = tp->t_writepos = 0; + ttyinq_reprintpos_set(&tp->t_inq); + } + return (0); + + case CCR: + /* Carriage return to newline conversion. */ + if (CMP_FLAG(o, OCRNL)) + c = CNL; + /* Omit carriage returns on column 0. */ + if (CMP_FLAG(o, ONOCR) && tp->t_column == 0) + return (0); + if (PRINT_NORMAL() != 0) + return (-1); + + tp->t_column = tp->t_writepos = 0; + ttyinq_reprintpos_set(&tp->t_inq); + return (0); + } + + /* + * Invisible control character. Print it, but don't + * increase the column count. + */ + return PRINT_NORMAL(); +#undef PRINT_NORMAL +} + +/* + * Just like the old TTY implementation, we need to copy data in chunks + * into a temporary buffer. One of the reasons why we need to do this, + * is because output processing (only TAB3 though) may allow the buffer + * to grow eight times. + */ +int +ttydisc_write(struct tty *tp, struct uio *uio, int ioflag) +{ + char ob[256]; + char *obstart; + int error = 0; + unsigned int oblen = 0; + + tty_lock_assert(tp, MA_OWNED); + + if (tp->t_flags & TF_ZOMBIE) + return (EIO); + + /* + * We don't need to check whether the process is the foreground + * process group or if we have a carrier. This is already done + * in ttydev_write(). + */ + + while (uio->uio_resid > 0) { + unsigned int nlen; + + MPASS(oblen == 0); + + /* Step 1: read data. */ + + tty_unlock(tp); + + obstart = ob; + nlen = MIN(uio->uio_resid, sizeof ob); + error = uiomove(ob, nlen, uio); + if (error != 0) + break; + oblen = nlen; + + tty_lock(tp); + if (tty_gone(tp)) { + error = ENXIO; + break; + } + + MPASS(oblen > 0); + + /* Step 2: process data. */ + do { + unsigned int plen, wlen; + + /* Search for special characters for post processing. */ + if (CMP_FLAG(o, OPOST)) { + plen = ttydisc_findchar(obstart, oblen); + } else { + plen = oblen; + } + + if (plen == 0) { + /* + * We're going to process a character + * that needs processing + */ + if (ttydisc_write_oproc(tp, *obstart) == 0) { + obstart++; + oblen--; + + tp->t_writepos = tp->t_column; + ttyinq_reprintpos_set(&tp->t_inq); + continue; + } + } else { + /* We're going to write regular data. */ + wlen = ttyoutq_write(&tp->t_outq, obstart, plen); + obstart += wlen; + oblen -= wlen; + tp->t_column += wlen; + + tp->t_writepos = tp->t_column; + ttyinq_reprintpos_set(&tp->t_inq); + + if (wlen == plen) + continue; + } + + /* Watermark reached. Try to sleep. */ + tp->t_flags |= TF_HIWAT_OUT; + + if (ioflag & IO_NDELAY) { + error = EWOULDBLOCK; + goto done; + } + + /* + * The driver may write back the data + * synchronously. Be sure to check the high + * water mark before going to sleep. + */ + ttydevsw_outwakeup(tp); + if ((tp->t_flags & TF_HIWAT_OUT) == 0) + continue; + + error = tty_wait(tp, &tp->t_outwait); + if (error) + goto done; + + if (tp->t_flags & TF_ZOMBIE) { + error = EIO; + goto done; + } + } while (oblen > 0); + } + +done: + ttydevsw_outwakeup(tp); + + /* + * Add the amount of bytes that we didn't process back to the + * uio counters. We need to do this to make sure write() doesn't + * count the bytes we didn't store in the queue. + */ + uio->uio_resid += oblen; + return (error); +} + +void +ttydisc_optimize(struct tty *tp) +{ + tty_lock_assert(tp, MA_OWNED); + + if (!CMP_FLAG(i, ICRNL|IGNCR|IMAXBEL|INLCR|ISTRIP|IXON) && + (!CMP_FLAG(i, BRKINT) || CMP_FLAG(i, IGNBRK)) && + (!CMP_FLAG(i, PARMRK) || + CMP_FLAG(i, IGNPAR|IGNBRK) == (IGNPAR|IGNBRK)) && + !CMP_FLAG(l, ECHO|ICANON|IEXTEN|ISIG|PENDIN)) { + tp->t_flags |= TF_BYPASS; + } else { + tp->t_flags &= ~TF_BYPASS; + } +} + +void +ttydisc_modem(struct tty *tp, int open) +{ + + tty_lock_assert(tp, MA_OWNED); + + if (open) + cv_broadcast(&tp->t_dcdwait); + + /* + * Ignore modem status lines when CLOCAL is turned on, but don't + * enter the zombie state when the TTY isn't opened, because + * that would cause the TTY to be in zombie state after being + * opened. + */ + if (!tty_opened(tp) || CMP_FLAG(c, CLOCAL)) + return; + + if (open == 0) { + /* + * Lost carrier. + */ + tp->t_flags |= TF_ZOMBIE; + + tty_signal_sessleader(tp, SIGHUP); + tty_flush(tp, FREAD|FWRITE); + } else { + /* + * Carrier is back again. + */ + + /* XXX: what should we do here? */ + } +} + +static int +ttydisc_echo_force(struct tty *tp, char c, int quote) +{ + + if (CMP_FLAG(o, OPOST) && CTL_ECHO(c, quote)) { + /* + * Only perform postprocessing when OPOST is turned on + * and the character is an unquoted BS/TB/NL/CR. + */ + return ttydisc_write_oproc(tp, c); + } else if (CMP_FLAG(l, ECHOCTL) && CTL_PRINT(c, quote)) { + /* + * Only use ^X notation when ECHOCTL is turned on and + * we've got an quoted control character. + */ + char ob[2] = { '^', '?' }; + + /* Print ^X notation. */ + if (c != 0x7f) + ob[1] = c + 'A' - 1; + + tp->t_column += 2; + return ttyoutq_write_nofrag(&tp->t_outq, ob, 2); + } else { + /* Can just be printed. */ + tp->t_column++; + return ttyoutq_write_nofrag(&tp->t_outq, &c, 1); + } +} + +static int +ttydisc_echo(struct tty *tp, char c, int quote) +{ + + /* + * Only echo characters when ECHO is turned on, or ECHONL when + * the character is an unquoted newline. + */ + if (!CMP_FLAG(l, ECHO) && + (!CMP_FLAG(l, ECHONL) || c != CNL || quote)) + return (0); + + return ttydisc_echo_force(tp, c, quote); +} + + +static void +ttydisc_reprint_char(void *d, char c, int quote) +{ + struct tty *tp = d; + + ttydisc_echo(tp, c, quote); +} + +static void +ttydisc_reprint(struct tty *tp) +{ + cc_t c; + + /* Print ^R\n, followed by the line. */ + c = tp->t_termios.c_cc[VREPRINT]; + if (c != _POSIX_VDISABLE) + ttydisc_echo(tp, c, 0); + ttydisc_echo(tp, CNL, 0); + ttyinq_reprintpos_reset(&tp->t_inq); + + ttyinq_line_iterate_from_linestart(&tp->t_inq, ttydisc_reprint_char, tp); +} + +struct ttydisc_recalc_length { + struct tty *tp; + unsigned int curlen; +}; + +static void +ttydisc_recalc_charlength(void *d, char c, int quote) +{ + struct ttydisc_recalc_length *data = d; + struct tty *tp = data->tp; + + if (CTL_PRINT(c, quote)) { + if (CMP_FLAG(l, ECHOCTL)) + data->curlen += 2; + } else if (c == CTAB) { + data->curlen += 8 - (data->curlen & 7); + } else { + data->curlen++; + } +} + +static unsigned int +ttydisc_recalc_linelength(struct tty *tp) +{ + struct ttydisc_recalc_length data = { tp, tp->t_writepos }; + + ttyinq_line_iterate_from_reprintpos(&tp->t_inq, + ttydisc_recalc_charlength, &data); + return (data.curlen); +} + +static int +ttydisc_rubchar(struct tty *tp) +{ + char c; + int quote; + unsigned int prevpos, tablen; + + if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) + return (-1); + ttyinq_unputchar(&tp->t_inq); + + if (CMP_FLAG(l, ECHO)) { + /* + * Remove the character from the screen. This is even + * safe for characters that span multiple characters + * (tabs, quoted, etc). + */ + if (tp->t_writepos >= tp->t_column) { + /* Retype the sentence. */ + ttydisc_reprint(tp); + } else if (CMP_FLAG(l, ECHOE)) { + if (CTL_PRINT(c, quote)) { + /* Remove ^X formatted chars. */ + if (CMP_FLAG(l, ECHOCTL)) { + tp->t_column -= 2; + ttyoutq_write_nofrag(&tp->t_outq, + "\b\b \b\b", 6); + } + } else if (c == ' ') { + /* Space character needs no rubbing. */ + tp->t_column -= 1; + ttyoutq_write_nofrag(&tp->t_outq, "\b", 1); + } else if (c == CTAB) { + /* + * Making backspace work with tabs is + * quite hard. Recalculate the length of + * this character and remove it. + * + * Because terminal settings could be + * changed while the line is being + * inserted, the calculations don't have + * to be correct. Make sure we keep the + * tab length within proper bounds. + */ + prevpos = ttydisc_recalc_linelength(tp); + if (prevpos >= tp->t_column) + tablen = 1; + else + tablen = tp->t_column - prevpos; + if (tablen > 8) + tablen = 8; + + tp->t_column = prevpos; + ttyoutq_write_nofrag(&tp->t_outq, + "\b\b\b\b\b\b\b\b", tablen); + return (0); + } else { + /* + * Remove a regular character by + * punching a space over it. + */ + tp->t_column -= 1; + ttyoutq_write_nofrag(&tp->t_outq, "\b \b", 3); + } + } else { + /* Don't print spaces. */ + ttydisc_echo(tp, tp->t_termios.c_cc[VERASE], 0); + } + } + + return (0); +} + +static void +ttydisc_rubword(struct tty *tp) +{ + char c; + int quote, alnum; + + /* Strip whitespace first. */ + for (;;) { + if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) + return; + if (!CTL_WHITE(c)) + break; + ttydisc_rubchar(tp); + } + + /* + * Record whether the last character from the previous iteration + * was alphanumeric or not. We need this to implement ALTWERASE. + */ + alnum = CTL_ALNUM(c); + for (;;) { + ttydisc_rubchar(tp); + + if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) + return; + if (CTL_WHITE(c)) + return; + if (CMP_FLAG(l, ALTWERASE) && CTL_ALNUM(c) != alnum) + return; + } +} + +int +ttydisc_rint(struct tty *tp, char c, int flags) +{ + int signal, quote = 0; + char ob[3] = { 0xff, 0x00 }; + size_t ol; + + tty_lock_assert(tp, MA_OWNED); + + atomic_add_long(&tty_nin, 1); + + if (tp->t_flags & TF_BYPASS) + goto processed; + + if (flags) { + if (flags & TRE_BREAK) { + if (CMP_FLAG(i, IGNBRK)) { + /* Ignore break characters. */ + return (0); + } else if (CMP_FLAG(i, BRKINT)) { + /* Generate SIGINT on break. */ + tty_flush(tp, FREAD|FWRITE); + tty_signal_pgrp(tp, SIGINT); + return (0); + } else { + /* Just print it. */ + goto parmrk; + } + } else if (flags & TRE_FRAMING || + (flags & TRE_PARITY && CMP_FLAG(i, INPCK))) { + if (CMP_FLAG(i, IGNPAR)) { + /* Ignore bad characters. */ + return (0); + } else { + /* Just print it. */ + goto parmrk; + } + } + } + + /* Allow any character to perform a wakeup. */ + if (CMP_FLAG(i, IXANY)) + tp->t_flags &= ~TF_STOPPED; + + /* Remove the top bit. */ + if (CMP_FLAG(i, ISTRIP)) + c &= ~0x80; + + /* Skip input processing when we want to print it literally. */ + if (tp->t_flags & TF_LITERAL) { + tp->t_flags &= ~TF_LITERAL; + quote = 1; + goto processed; + } + + /* Special control characters that are implementation dependent. */ + if (CMP_FLAG(l, IEXTEN)) { + /* Accept the next character as literal. */ + if (CMP_CC(VLNEXT, c)) { + if (CMP_FLAG(l, ECHO)) { + if (CMP_FLAG(l, ECHOE)) + ttyoutq_write_nofrag(&tp->t_outq, "^\b", 2); + else + ttydisc_echo(tp, c, 0); + } + tp->t_flags |= TF_LITERAL; + return (0); + } + } + + /* + * Handle signal processing. + */ + if (CMP_FLAG(l, ISIG)) { + if (CMP_FLAG(l, ICANON|IEXTEN) == (ICANON|IEXTEN)) { + if (CMP_CC(VSTATUS, c)) { + tty_signal_pgrp(tp, SIGINFO); + return (0); + } + } + + /* + * When compared to the old implementation, this + * implementation also flushes the output queue. POSIX + * is really brief about this, but does makes us assume + * we have to do so. + */ + signal = 0; + if (CMP_CC(VINTR, c)) { + signal = SIGINT; + } else if (CMP_CC(VQUIT, c)) { + signal = SIGQUIT; + } else if (CMP_CC(VSUSP, c)) { + signal = SIGTSTP; + } + + if (signal != 0) { + /* + * Echo the character before signalling the + * processes. + */ + if (!CMP_FLAG(l, NOFLSH)) + tty_flush(tp, FREAD|FWRITE); + ttydisc_echo(tp, c, 0); + tty_signal_pgrp(tp, signal); + return (0); + } + } + + /* + * Handle start/stop characters. + */ + if (CMP_FLAG(i, IXON)) { + if (CMP_CC(VSTOP, c)) { + /* Stop it if we aren't stopped yet. */ + if ((tp->t_flags & TF_STOPPED) == 0) { + tp->t_flags |= TF_STOPPED; + return (0); + } + /* + * Fallthrough: + * When VSTART == VSTOP, we should make this key + * toggle it. + */ + if (!CMP_CC(VSTART, c)) + return (0); + } + if (CMP_CC(VSTART, c)) { + tp->t_flags &= ~TF_STOPPED; + return (0); + } + } + + /* Conversion of CR and NL. */ + switch (c) { + case CCR: + if (CMP_FLAG(i, IGNCR)) + return (0); + if (CMP_FLAG(i, ICRNL)) + c = CNL; + break; + case CNL: + if (CMP_FLAG(i, INLCR)) + c = CCR; + break; + } + + /* Canonical line editing. */ + if (CMP_FLAG(l, ICANON)) { + if (CMP_CC(VERASE, c) || CMP_CC(VERASE2, c)) { + ttydisc_rubchar(tp); + return (0); + } else if (CMP_CC(VKILL, c)) { + while (ttydisc_rubchar(tp) == 0); + return (0); + } else if (CMP_FLAG(l, IEXTEN)) { + if (CMP_CC(VWERASE, c)) { + ttydisc_rubword(tp); + return (0); + } else if (CMP_CC(VREPRINT, c)) { + ttydisc_reprint(tp); + return (0); + } + } + } + +processed: + if (CMP_FLAG(i, PARMRK) && (unsigned char)c == 0xff) { + /* Print 0xff 0xff. */ + ob[1] = 0xff; + ol = 2; + quote = 1; + } else { + ob[0] = c; + ol = 1; + } + + goto print; + +parmrk: + if (CMP_FLAG(i, PARMRK)) { + /* Prepend 0xff 0x00 0x.. */ + ob[2] = c; + ol = 3; + quote = 1; + } else { + ob[0] = c; + ol = 1; + } + +print: + /* See if we can store this on the input queue. */ + if (ttyinq_write_nofrag(&tp->t_inq, ob, ol, quote) != 0) { + /* We cannot. Enable the input watermark. */ + tty_hiwat_in_block(tp); + return (-1); + } + + /* + * In raw mode, we canonicalize after receiving a single + * character. Otherwise, we canonicalize when we receive a + * newline, VEOL or VEOF, but only when it isn't quoted. + */ + if (!CMP_FLAG(l, ICANON) || + (!quote && (c == CNL || CMP_CC(VEOL, c) || CMP_CC(VEOF, c)))) { + ttyinq_canonicalize(&tp->t_inq); + } + + ttydisc_echo(tp, c, quote); + + return (0); +} + +size_t +ttydisc_rint_bypass(struct tty *tp, char *buf, size_t len) +{ + size_t ret; + + tty_lock_assert(tp, MA_OWNED); + + MPASS(tp->t_flags & TF_BYPASS); + + atomic_add_long(&tty_nin, len); + + ret = ttyinq_write(&tp->t_inq, buf, len, 0); + ttyinq_canonicalize(&tp->t_inq); + + return (ret); +} + +void +ttydisc_rint_done(struct tty *tp) +{ + + tty_lock_assert(tp, MA_OWNED); + + /* Wake up readers. */ + tty_wakeup(tp, FREAD); + /* Wake up driver for echo. */ + ttydevsw_outwakeup(tp); +} + +static void +ttydisc_wakeup_watermark(struct tty *tp) +{ + size_t c; + + c = ttyoutq_bytesleft(&tp->t_outq); + if (tp->t_flags & TF_HIWAT_OUT) { + /* Only allow us to run when we're below the watermark. */ + if (c < tp->t_outlow) + return; + + /* Reset the watermark. */ + tp->t_flags &= ~TF_HIWAT_OUT; + } else { + /* Only run when we have data at all. */ + if (c == 0) + return; + } + tty_wakeup(tp, FWRITE); +} + +size_t +ttydisc_getc(struct tty *tp, void *buf, size_t len) +{ + int ret; + + tty_lock_assert(tp, MA_OWNED); + + if (tp->t_flags & TF_STOPPED) + return (0); + + ret = ttyoutq_read(&tp->t_outq, buf, len); + ttydisc_wakeup_watermark(tp); + + atomic_add_long(&tty_nout, ret); + + return (ret); +} + +int +ttydisc_getc_uio(struct tty *tp, struct uio *uio) +{ + int error; + int obytes = uio->uio_resid; + + tty_lock_assert(tp, MA_OWNED); + + if (tp->t_flags & TF_STOPPED) + return (0); + + error = ttyoutq_read_uio(&tp->t_outq, tp, uio); + ttydisc_wakeup_watermark(tp); + + atomic_add_long(&tty_nout, obytes - uio->uio_resid); + + return (error); +} + +/* + * XXX: not really related to the TTYDISC, but we'd better put + * tty_putchar() here, because we need to perform proper output + * processing. + */ + +int +tty_putchar(struct tty *tp, char c) +{ + tty_lock_assert(tp, MA_OWNED); + + if (tty_gone(tp)) + return (-1); + + ttydisc_echo_force(tp, c, 0); + tp->t_writepos = tp->t_column; + ttyinq_reprintpos_set(&tp->t_inq); + + ttydevsw_outwakeup(tp); + return (0); +} |