summaryrefslogtreecommitdiffstats
path: root/sys/kern
diff options
context:
space:
mode:
authored <ed@FreeBSD.org>2008-08-20 08:31:58 +0000
committered <ed@FreeBSD.org>2008-08-20 08:31:58 +0000
commitcc3116a9380fe32a751b584f3d8083698ccfba15 (patch)
treebd0c08a66997254385160ce71ea32029b99f99f9 /sys/kern
parentb49301b5cd9ff43a7af0bd9054d9d1a328c0d212 (diff)
downloadFreeBSD-src-cc3116a9380fe32a751b584f3d8083698ccfba15.zip
FreeBSD-src-cc3116a9380fe32a751b584f3d8083698ccfba15.tar.gz
Integrate the new MPSAFE TTY layer to the FreeBSD operating system.
The last half year I've been working on a replacement TTY layer for the FreeBSD kernel. The new TTY layer was designed to improve the following: - Improved driver model: The old TTY layer has a driver model that is not abstract enough to make it friendly to use. A good example is the output path, where the device drivers directly access the output buffers. This means that an in-kernel PPP implementation must always convert network buffers into TTY buffers. If a PPP implementation would be built on top of the new TTY layer (still needs a hooks layer, though), it would allow the PPP implementation to directly hand the data to the TTY driver. - Improved hotplugging: With the old TTY layer, it isn't entirely safe to destroy TTY's from the system. This implementation has a two-step destructing design, where the driver first abandons the TTY. After all threads have left the TTY, the TTY layer calls a routine in the driver, which can be used to free resources (unit numbers, etc). The pts(4) driver also implements this feature, which means posix_openpt() will now return PTY's that are created on the fly. - Improved performance: One of the major improvements is the per-TTY mutex, which is expected to improve scalability when compared to the old Giant locking. Another change is the unbuffered copying to userspace, which is both used on TTY device nodes and PTY masters. Upgrading should be quite straightforward. Unlike previous versions, existing kernel configuration files do not need to be changed, except when they reference device drivers that are listed in UPDATING. Obtained from: //depot/projects/mpsafetty/... Approved by: philip (ex-mentor) Discussed: on the lists, at BSDCan, at the DevSummit Sponsored by: Snow B.V., the Netherlands dcons(4) fixed by: kan
Diffstat (limited to 'sys/kern')
-rw-r--r--sys/kern/init_main.c2
-rw-r--r--sys/kern/init_sysent.c3
-rw-r--r--sys/kern/kern_acct.c6
-rw-r--r--sys/kern/kern_conf.c11
-rw-r--r--sys/kern/kern_descrip.c12
-rw-r--r--sys/kern/kern_exit.c62
-rw-r--r--sys/kern/kern_proc.c59
-rw-r--r--sys/kern/kern_resource.c25
-rw-r--r--sys/kern/subr_prf.c24
-rw-r--r--sys/kern/syscalls.c3
-rw-r--r--sys/kern/syscalls.master1
-rw-r--r--sys/kern/systrace_args.c27
-rw-r--r--sys/kern/tty.c4182
-rw-r--r--sys/kern/tty_compat.c55
-rw-r--r--sys/kern/tty_conf.c205
-rw-r--r--sys/kern/tty_cons.c15
-rw-r--r--sys/kern/tty_info.c14
-rw-r--r--sys/kern/tty_inq.c502
-rw-r--r--sys/kern/tty_outq.c365
-rw-r--r--sys/kern/tty_pts.c1303
-rw-r--r--sys/kern/tty_pty.c810
-rw-r--r--sys/kern/tty_ttydisc.c1131
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, &quote) != 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, &quote) != 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, &quote) != 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);
+}
OpenPOWER on IntegriCloud