summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/kern/tty.c90
-rw-r--r--sys/kern/tty_ttydisc.c101
-rw-r--r--sys/sys/tty.h10
-rw-r--r--sys/sys/ttydevsw.h4
-rw-r--r--sys/sys/ttydisc.h34
-rw-r--r--sys/sys/ttyhook.h147
-rw-r--r--usr.sbin/pstat/pstat.82
-rw-r--r--usr.sbin/pstat/pstat.c1
8 files changed, 341 insertions, 48 deletions
diff --git a/sys/kern/tty.c b/sys/kern/tty.c
index cd6a246..863b6ce 100644
--- a/sys/kern/tty.c
+++ b/sys/kern/tty.c
@@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$");
#include <sys/conf.h>
#include <sys/cons.h>
#include <sys/fcntl.h>
+#include <sys/file.h>
#include <sys/filio.h>
#ifdef COMPAT_43TTY
#include <sys/ioctl_compat.h>
@@ -59,6 +60,7 @@ __FBSDID("$FreeBSD$");
#include <sys/ttydefaults.h>
#undef TTYDEFCHARS
#include <sys/ucred.h>
+#include <sys/vnode.h>
#include <machine/stdarg.h>
@@ -855,7 +857,7 @@ tty_alloc(struct ttydevsw *tsw, void *sc, struct mtx *mutex)
tp = malloc(sizeof(struct tty), M_TTY, M_WAITOK|M_ZERO);
tp->t_devsw = tsw;
- tp->t_softc = sc;
+ tp->t_devswsoftc = sc;
tp->t_flags = tsw->tsw_flags;
tty_init_termios(tp);
@@ -923,7 +925,7 @@ tty_rel_free(struct tty *tp)
tty_lock_assert(tp, MA_OWNED);
if (tp->t_sessioncnt != 0 ||
- (tp->t_flags & (TF_GONE|TF_OPENED)) != TF_GONE) {
+ (tp->t_flags & (TF_GONE|TF_OPENED|TF_HOOK)) != TF_GONE) {
/* TTY is still in use. */
tty_unlock(tp);
return;
@@ -1653,6 +1655,89 @@ tty_hiwat_in_unblock(struct tty *tp)
ttydevsw_inwakeup(tp);
}
+static int
+ttyhook_defrint(struct tty *tp, char c, int flags)
+{
+
+ if (ttyhook_rint_bypass(tp, &c, 1) != 1)
+ return (-1);
+
+ return (0);
+}
+
+int
+ttyhook_register(struct tty **rtp, struct thread *td, int fd,
+ struct ttyhook *th, void *softc)
+{
+ struct tty *tp;
+ struct file *fp;
+ struct cdev *dev;
+ struct cdevsw *cdp;
+ int error;
+
+ /* Validate the file descriptor. */
+ if (fget(td, fd, &fp) != 0)
+ return (EINVAL);
+
+ /* Make sure the vnode is bound to a character device. */
+ error = EINVAL;
+ if (fp->f_type != DTYPE_VNODE || fp->f_vnode->v_type != VCHR ||
+ fp->f_vnode->v_rdev == NULL)
+ goto done1;
+ dev = fp->f_vnode->v_rdev;
+
+ /* Make sure it is a TTY. */
+ cdp = dev_refthread(dev);
+ if (cdp == NULL)
+ goto done1;
+ if ((cdp->d_flags & D_TTY) == 0)
+ goto done2;
+ tp = dev->si_drv1;
+
+ /* Try to attach the hook to the TTY. */
+ error = EBUSY;
+ tty_lock(tp);
+ MPASS((tp->t_hook == NULL) == ((tp->t_flags & TF_HOOK) == 0));
+ if (tp->t_flags & TF_HOOK)
+ goto done3;
+
+ tp->t_flags |= TF_HOOK;
+ tp->t_hook = th;
+ tp->t_hooksoftc = softc;
+ *rtp = tp;
+ error = 0;
+
+ /* Maybe we can switch into bypass mode now. */
+ ttydisc_optimize(tp);
+
+ /* Silently convert rint() calls to rint_bypass() when possible. */
+ if (!ttyhook_hashook(tp, rint) && ttyhook_hashook(tp, rint_bypass))
+ th->th_rint = ttyhook_defrint;
+
+done3: tty_unlock(tp);
+done2: dev_relthread(dev);
+done1: fdrop(fp, td);
+ return (error);
+}
+
+void
+ttyhook_unregister(struct tty *tp)
+{
+
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(tp->t_flags & TF_HOOK);
+
+ /* Disconnect the hook. */
+ tp->t_flags &= ~TF_HOOK;
+ tp->t_hook = NULL;
+
+ /* Maybe we need to leave bypass mode. */
+ ttydisc_optimize(tp);
+
+ /* Maybe deallocate the TTY as well. */
+ tty_rel_free(tp);
+}
+
#include "opt_ddb.h"
#ifdef DDB
#include <ddb/ddb.h>
@@ -1686,6 +1771,7 @@ static struct {
{ TF_EXCLUDE, 'X' },
{ TF_BYPASS, 'l' },
{ TF_ZOMBIE, 'Z' },
+ { TF_HOOK, 's' },
{ 0, '\0' },
};
diff --git a/sys/kern/tty_ttydisc.c b/sys/kern/tty_ttydisc.c
index 693b480..0d7dd7f 100644
--- a/sys/kern/tty_ttydisc.c
+++ b/sys/kern/tty_ttydisc.c
@@ -79,6 +79,8 @@ SYSCTL_LONG(_kern, OID_AUTO, tty_nout, CTLFLAG_RD,
#define CTL_ALNUM(c) (((c) >= '0' && (c) <= '9') || \
((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))
+#define TTY_STACKBUF 256
+
void
ttydisc_open(struct tty *tp)
{
@@ -100,6 +102,9 @@ ttydisc_close(struct tty *tp)
ttydevsw_inwakeup(tp);
ttydevsw_outwakeup(tp);
}
+
+ if (ttyhook_hashook(tp, close))
+ ttyhook_close(tp);
}
static int
@@ -433,7 +438,7 @@ ttydisc_write_oproc(struct tty *tp, char c)
int
ttydisc_write(struct tty *tp, struct uio *uio, int ioflag)
{
- char ob[256];
+ char ob[TTY_STACKBUF];
char *obstart;
int error = 0;
unsigned int oblen = 0;
@@ -557,11 +562,12 @@ ttydisc_optimize(struct tty *tp)
{
tty_lock_assert(tp, MA_OWNED);
- if (!CMP_FLAG(i, ICRNL|IGNCR|IMAXBEL|INLCR|ISTRIP|IXON) &&
+ 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)) {
+ !CMP_FLAG(l, ECHO|ICANON|IEXTEN|ISIG|PENDIN)) ||
+ ttyhook_hashook(tp, rint_bypass)) {
tp->t_flags |= TF_BYPASS;
} else {
tp->t_flags &= ~TF_BYPASS;
@@ -818,6 +824,9 @@ ttydisc_rint(struct tty *tp, char c, int flags)
tty_lock_assert(tp, MA_OWNED);
atomic_add_long(&tty_nin, 1);
+
+ if (ttyhook_hashook(tp, rint))
+ return ttyhook_rint(tp, c, flags);
if (tp->t_flags & TF_BYPASS)
goto processed;
@@ -1045,6 +1054,9 @@ ttydisc_rint_bypass(struct tty *tp, const void *buf, size_t len)
atomic_add_long(&tty_nin, len);
+ if (ttyhook_hashook(tp, rint_bypass))
+ return ttyhook_rint_bypass(tp, buf, len);
+
ret = ttyinq_write(&tp->t_inq, buf, len, 0);
ttyinq_canonicalize(&tp->t_inq);
@@ -1057,12 +1069,38 @@ ttydisc_rint_done(struct tty *tp)
tty_lock_assert(tp, MA_OWNED);
+ if (ttyhook_hashook(tp, rint_done))
+ ttyhook_rint_done(tp);
+
/* Wake up readers. */
tty_wakeup(tp, FREAD);
/* Wake up driver for echo. */
ttydevsw_outwakeup(tp);
}
+size_t
+ttydisc_rint_poll(struct tty *tp)
+{
+ size_t l;
+
+ tty_lock_assert(tp, MA_OWNED);
+
+ if (ttyhook_hashook(tp, rint_poll))
+ return ttyhook_rint_poll(tp);
+
+ /*
+ * XXX: Still allow character input when there's no space in the
+ * buffers, but we haven't entered the high watermark. This is
+ * to allow backspace characters to be inserted when in
+ * canonical mode.
+ */
+ l = ttyinq_bytesleft(&tp->t_inq);
+ if (l == 0 && (tp->t_flags & TF_HIWAT_IN) == 0)
+ return (1);
+
+ return (l);
+}
+
static void
ttydisc_wakeup_watermark(struct tty *tp)
{
@@ -1093,9 +1131,15 @@ ttydisc_getc(struct tty *tp, void *buf, size_t len)
if (tp->t_flags & TF_STOPPED)
return (0);
+ if (ttyhook_hashook(tp, getc_inject))
+ return ttyhook_getc_inject(tp, buf, len);
+
len = ttyoutq_read(&tp->t_outq, buf, len);
- ttydisc_wakeup_watermark(tp);
+ if (ttyhook_hashook(tp, getc_capture))
+ ttyhook_getc_capture(tp, buf, len);
+
+ ttydisc_wakeup_watermark(tp);
atomic_add_long(&tty_nout, len);
return (len);
@@ -1104,22 +1148,63 @@ ttydisc_getc(struct tty *tp, void *buf, size_t len)
int
ttydisc_getc_uio(struct tty *tp, struct uio *uio)
{
- int error;
+ int error = 0;
int obytes = uio->uio_resid;
+ size_t len;
+ char buf[TTY_STACKBUF];
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);
+ /*
+ * When a TTY hook is attached, we cannot perform unbuffered
+ * copying to userspace. Just call ttydisc_getc() and
+ * temporarily store data in a shadow buffer.
+ */
+ if (ttyhook_hashook(tp, getc_capture) ||
+ ttyhook_hashook(tp, getc_inject)) {
+ while (uio->uio_resid > 0) {
+ /* Read to shadow buffer. */
+ len = ttydisc_getc(tp, buf,
+ MIN(uio->uio_resid, sizeof buf));
+ if (len == 0)
+ break;
+
+ /* Copy to userspace. */
+ tty_unlock(tp);
+ error = uiomove(buf, len, uio);
+ tty_lock(tp);
+
+ if (error != 0)
+ break;
+ }
+ } else {
+ error = ttyoutq_read_uio(&tp->t_outq, tp, uio);
- atomic_add_long(&tty_nout, obytes - uio->uio_resid);
+ ttydisc_wakeup_watermark(tp);
+ atomic_add_long(&tty_nout, obytes - uio->uio_resid);
+ }
return (error);
}
+size_t
+ttydisc_getc_poll(struct tty *tp)
+{
+
+ tty_lock_assert(tp, MA_OWNED);
+
+ if (tp->t_flags & TF_STOPPED)
+ return (0);
+
+ if (ttyhook_hashook(tp, getc_poll))
+ return ttyhook_getc_poll(tp);
+
+ return ttyoutq_bytesused(&tp->t_outq);
+}
+
/*
* XXX: not really related to the TTYDISC, but we'd better put
* tty_putchar() here, because we need to perform proper output
diff --git a/sys/sys/tty.h b/sys/sys/tty.h
index 54d487c..2250cbc 100644
--- a/sys/sys/tty.h
+++ b/sys/sys/tty.h
@@ -80,6 +80,7 @@ struct tty {
#define TF_EXCLUDE 0x1000 /* Exclusive access. */
#define TF_BYPASS 0x2000 /* Optimized input path. */
#define TF_ZOMBIE 0x4000 /* Modem disconnect received. */
+#define TF_HOOK 0x8000 /* TTY has hook attached. */
unsigned int t_revokecnt; /* (t) revoke() count. */
/* Buffering mechanisms. */
@@ -112,13 +113,15 @@ struct tty {
struct termios t_termios_lock_out; /* cua%s.lock. */
struct ttydevsw *t_devsw; /* (c) Driver hooks. */
+ struct ttyhook *t_hook; /* (t) Capture/inject hook. */
/* Process signal delivery. */
struct pgrp *t_pgrp; /* (t) Foreground process group. */
struct session *t_session; /* (t) Associated session. */
unsigned int t_sessioncnt; /* (t) Backpointing sessions. */
- void *t_softc; /* (c) Soft config, for drivers. */
+ void *t_devswsoftc; /* (c) Soft config, for drivers. */
+ void *t_hooksoftc; /* (t) Soft config, for hooks. */
struct cdev *t_dev; /* (c) Primary character device. */
};
@@ -181,7 +184,7 @@ void tty_hiwat_in_unblock(struct tty *tp);
dev_t tty_udev(struct tty *tp);
#define tty_opened(tp) ((tp)->t_flags & TF_OPENED)
#define tty_gone(tp) ((tp)->t_flags & TF_GONE)
-#define tty_softc(tp) ((tp)->t_softc)
+#define tty_softc(tp) ((tp)->t_devswsoftc)
#define tty_devname(tp) devtoname((tp)->t_dev)
/* Status line printing. */
@@ -192,8 +195,9 @@ int pts_alloc_external(int fd, struct thread *td, struct file *fp,
struct cdev *dev, const char *name);
/* Drivers and line disciplines also need to call these. */
-#include <sys/ttydevsw.h>
#include <sys/ttydisc.h>
+#include <sys/ttydevsw.h>
+#include <sys/ttyhook.h>
#endif /* _KERNEL */
#endif /* !_SYS_TTY_H_ */
diff --git a/sys/sys/ttydevsw.h b/sys/sys/ttydevsw.h
index 842143d..e2278c3 100644
--- a/sys/sys/ttydevsw.h
+++ b/sys/sys/ttydevsw.h
@@ -97,9 +97,7 @@ ttydevsw_outwakeup(struct tty *tp)
MPASS(!tty_gone(tp));
/* Prevent spurious wakeups. */
- if (tp->t_flags & TF_STOPPED)
- return;
- if (ttyoutq_bytesused(&tp->t_outq) == 0)
+ if (ttydisc_getc_poll(tp) == 0)
return;
tp->t_devsw->tsw_outwakeup(tp);
diff --git a/sys/sys/ttydisc.h b/sys/sys/ttydisc.h
index aeaabc6..2ea5466 100644
--- a/sys/sys/ttydisc.h
+++ b/sys/sys/ttydisc.h
@@ -54,8 +54,10 @@ void ttydisc_modem(struct tty *tp, int open);
int ttydisc_rint(struct tty *tp, char c, int flags);
size_t ttydisc_rint_bypass(struct tty *tp, const void *buf, size_t len);
void ttydisc_rint_done(struct tty *tp);
+size_t ttydisc_rint_poll(struct tty *tp);
size_t ttydisc_getc(struct tty *tp, void *buf, size_t len);
int ttydisc_getc_uio(struct tty *tp, struct uio *uio);
+size_t ttydisc_getc_poll(struct tty *tp);
/* Error codes for ttydisc_rint(). */
#define TRE_FRAMING 0x01
@@ -81,36 +83,4 @@ ttydisc_write_poll(struct tty *tp)
return ttyoutq_bytesleft(&tp->t_outq);
}
-static __inline size_t
-ttydisc_rint_poll(struct tty *tp)
-{
- size_t l;
-
- tty_lock_assert(tp, MA_OWNED);
-
- /*
- * XXX: Still allow character input when there's no space in the
- * buffers, but we haven't entered the high watermark. This is
- * to allow backspace characters to be inserted when in
- * canonical mode.
- */
- l = ttyinq_bytesleft(&tp->t_inq);
- if (l == 0 && (tp->t_flags & TF_HIWAT_IN) == 0)
- return (1);
-
- return (l);
-}
-
-static __inline size_t
-ttydisc_getc_poll(struct tty *tp)
-{
-
- tty_lock_assert(tp, MA_OWNED);
-
- if (tp->t_flags & TF_STOPPED)
- return (0);
-
- return ttyoutq_bytesused(&tp->t_outq);
-}
-
#endif /* !_SYS_TTYDISC_H_ */
diff --git a/sys/sys/ttyhook.h b/sys/sys/ttyhook.h
new file mode 100644
index 0000000..7a0a0bc
--- /dev/null
+++ b/sys/sys/ttyhook.h
@@ -0,0 +1,147 @@
+/*-
+ * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _SYS_TTYHOOK_H_
+#define _SYS_TTYHOOK_H_
+
+#ifndef _SYS_TTY_H_
+#error "can only be included through <sys/tty.h>"
+#endif /* !_SYS_TTY_H_ */
+
+struct tty;
+
+/*
+ * Hooks interface, which allows to capture and inject traffic into the
+ * input and output paths of a TTY.
+ */
+
+typedef int th_rint_t(struct tty *tp, char c, int flags);
+typedef size_t th_rint_bypass_t(struct tty *tp, const void *buf, size_t len);
+typedef void th_rint_done_t(struct tty *tp);
+typedef size_t th_rint_poll_t(struct tty *tp);
+
+typedef size_t th_getc_inject_t(struct tty *tp, void *buf, size_t len);
+typedef void th_getc_capture_t(struct tty *tp, const void *buf, size_t len);
+typedef size_t th_getc_poll_t(struct tty *tp);
+
+typedef void th_close_t(struct tty *tp);
+
+struct ttyhook {
+ /* Character input. */
+ th_rint_t *th_rint;
+ th_rint_bypass_t *th_rint_bypass;
+ th_rint_done_t *th_rint_done;
+ th_rint_poll_t *th_rint_poll;
+
+ /* Character output. */
+ th_getc_inject_t *th_getc_inject;
+ th_getc_capture_t *th_getc_capture;
+ th_getc_poll_t *th_getc_poll;
+
+ th_close_t *th_close;
+};
+
+int ttyhook_register(struct tty **, struct thread *, int,
+ struct ttyhook *, void *);
+void ttyhook_unregister(struct tty *);
+#define ttyhook_softc(tp) ((tp)->t_hooksoftc)
+#define ttyhook_hashook(tp,hook) ((tp)->t_hook != NULL && \
+ (tp)->t_hook->th_ ## hook != NULL)
+
+static __inline int
+ttyhook_rint(struct tty *tp, char c, int flags)
+{
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(!tty_gone(tp));
+
+ return tp->t_hook->th_rint(tp, c, flags);
+}
+
+static __inline size_t
+ttyhook_rint_bypass(struct tty *tp, const void *buf, size_t len)
+{
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(!tty_gone(tp));
+
+ return tp->t_hook->th_rint_bypass(tp, buf, len);
+}
+
+static __inline void
+ttyhook_rint_done(struct tty *tp)
+{
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(!tty_gone(tp));
+
+ tp->t_hook->th_rint_done(tp);
+}
+
+static __inline size_t
+ttyhook_rint_poll(struct tty *tp)
+{
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(!tty_gone(tp));
+
+ return tp->t_hook->th_rint_poll(tp);
+}
+
+static __inline size_t
+ttyhook_getc_inject(struct tty *tp, void *buf, size_t len)
+{
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(!tty_gone(tp));
+
+ return tp->t_hook->th_getc_inject(tp, buf, len);
+}
+
+static __inline void
+ttyhook_getc_capture(struct tty *tp, const void *buf, size_t len)
+{
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(!tty_gone(tp));
+
+ tp->t_hook->th_getc_capture(tp, buf, len);
+}
+
+static __inline size_t
+ttyhook_getc_poll(struct tty *tp)
+{
+ tty_lock_assert(tp, MA_OWNED);
+ MPASS(!tty_gone(tp));
+
+ return tp->t_hook->th_getc_poll(tp);
+}
+
+static __inline void
+ttyhook_close(struct tty *tp)
+{
+ tty_lock_assert(tp, MA_OWNED);
+
+ tp->t_hook->th_close(tp);
+}
+
+#endif /* !_SYS_TTYHOOK_H_ */
diff --git a/usr.sbin/pstat/pstat.8 b/usr.sbin/pstat/pstat.8
index 2770cb7..5474f8b 100644
--- a/usr.sbin/pstat/pstat.8
+++ b/usr.sbin/pstat/pstat.8
@@ -202,6 +202,8 @@ output stopped (ixon flow control)
block mode input routine in use
.It Z
connection lost
+.It s
+i/o being snooped
.El
.Pp
The
diff --git a/usr.sbin/pstat/pstat.c b/usr.sbin/pstat/pstat.c
index 51828cb..a1e81b3 100644
--- a/usr.sbin/pstat/pstat.c
+++ b/usr.sbin/pstat/pstat.c
@@ -312,6 +312,7 @@ static struct {
{ TF_EXCLUDE, 'X' },
{ TF_BYPASS, 'l' },
{ TF_ZOMBIE, 'Z' },
+ { TF_HOOK, 's' },
{ 0, '\0' },
};
OpenPOWER on IntegriCloud