1139749Simp/*-
2184689Sed * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
3184689Sed * All rights reserved.
46390Sugen *
5184689Sed * Redistribution and use in source and binary forms, with or without
6184689Sed * modification, are permitted provided that the following conditions
7184689Sed * are met:
8184689Sed * 1. Redistributions of source code must retain the above copyright
9184689Sed *    notice, this list of conditions and the following disclaimer.
10184689Sed * 2. Redistributions in binary form must reproduce the above copyright
11184689Sed *    notice, this list of conditions and the following disclaimer in the
12184689Sed *    documentation and/or other materials provided with the distribution.
136390Sugen *
14184689Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15184689Sed * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16184689Sed * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17184689Sed * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18184689Sed * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19184689Sed * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20184689Sed * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21184689Sed * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22184689Sed * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23184689Sed * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24184689Sed * SUCH DAMAGE.
256390Sugen */
266390Sugen
27119419Sobrien#include <sys/cdefs.h>
28119419Sobrien__FBSDID("$FreeBSD$");
29119419Sobrien
306390Sugen#include <sys/param.h>
31184689Sed#include <sys/conf.h>
32139195Sphk#include <sys/fcntl.h>
3324205Sbde#include <sys/filio.h>
34184689Sed#include <sys/kernel.h>
3530354Sphk#include <sys/malloc.h>
36184689Sed#include <sys/module.h>
3729355Speter#include <sys/poll.h>
38186056Smav#include <sys/proc.h>
396390Sugen#include <sys/snoop.h>
40184689Sed#include <sys/sx.h>
41184689Sed#include <sys/systm.h>
42184689Sed#include <sys/tty.h>
43139195Sphk#include <sys/uio.h>
446390Sugen
45184689Sedstatic struct cdev	*snp_dev;
46191999Sedstatic MALLOC_DEFINE(M_SNP, "snp", "tty snoop device");
47191999Sed
48184689Sed/* XXX: should be mtx, but TTY can be locked by Giant. */
49191999Sed#if 0
50191999Sedstatic struct mtx	snp_register_lock;
51191999SedMTX_SYSINIT(snp_register_lock, &snp_register_lock,
52191999Sed    "tty snoop registration", MTX_DEF);
53191999Sed#define	SNP_LOCK()	mtx_lock(&snp_register_lock)
54191999Sed#define	SNP_UNLOCK()	mtx_unlock(&snp_register_lock)
55191999Sed#else
56184689Sedstatic struct sx	snp_register_lock;
57184689SedSX_SYSINIT(snp_register_lock, &snp_register_lock,
58184689Sed    "tty snoop registration");
59191999Sed#define	SNP_LOCK()	sx_xlock(&snp_register_lock)
60191999Sed#define	SNP_UNLOCK()	sx_xunlock(&snp_register_lock)
61191999Sed#endif
6212675Sjulian
6380326Sdd/*
64184689Sed * There is no need to have a big input buffer. In most typical setups,
65184689Sed * we won't inject much data into the TTY, because users can't type
66184689Sed * really fast.
6780326Sdd */
68184689Sed#define SNP_INPUT_BUFSIZE	16
6980326Sdd/*
70184689Sed * The output buffer has to be really big. Right now we don't support
71184689Sed * any form of flow control, which means we lost any data we can't
72184689Sed * accept. We set the output buffer size to about twice the size of a
73184689Sed * pseudo-terminal/virtual console's output buffer.
7480326Sdd */
75184689Sed#define SNP_OUTPUT_BUFSIZE	16384
7680326Sdd
77184689Sedstatic d_open_t		snp_open;
78184689Sedstatic d_read_t		snp_read;
79184689Sedstatic d_write_t	snp_write;
80184689Sedstatic d_ioctl_t	snp_ioctl;
81184689Sedstatic d_poll_t		snp_poll;
8280326Sdd
83184689Sedstatic struct cdevsw snp_cdevsw = {
84184689Sed	.d_version	= D_VERSION,
85184689Sed	.d_open		= snp_open,
86184689Sed	.d_read		= snp_read,
87184689Sed	.d_write	= snp_write,
88184689Sed	.d_ioctl	= snp_ioctl,
89184689Sed	.d_poll		= snp_poll,
90184689Sed	.d_name		= "snp",
91184689Sed};
9277016Sdd
93184689Sedstatic th_getc_capture_t	snp_getc_capture;
9479864Sdd
95184689Sedstatic struct ttyhook snp_hook = {
96184689Sed	.th_getc_capture	= snp_getc_capture,
97184689Sed};
9877748Sdd
99184689Sed/*
100184689Sed * Per-instance structure.
101184689Sed *
102184689Sed * List of locks
103184689Sed * (r)	locked by snp_register_lock on assignment
104184689Sed * (t)	locked by tty_lock
105184689Sed */
106184689Sedstruct snp_softc {
107184689Sed	struct tty	*snp_tty;	/* (r) TTY we're snooping. */
108184689Sed	struct ttyoutq	snp_outq;	/* (t) Output queue. */
109184689Sed	struct cv	snp_outwait;	/* (t) Output wait queue. */
110184689Sed	struct selinfo	snp_outpoll;	/* (t) Output polling. */
111184689Sed};
112137265Scognet
113184689Sedstatic void
114184689Sedsnp_dtor(void *data)
11577749Sdd{
116184689Sed	struct snp_softc *ss = data;
117184689Sed	struct tty *tp;
11877749Sdd
119184689Sed	tp = ss->snp_tty;
120184689Sed	if (tp != NULL) {
121184689Sed		tty_lock(tp);
122184689Sed		ttyoutq_free(&ss->snp_outq);
123184689Sed		ttyhook_unregister(tp);
12477016Sdd	}
12577016Sdd
126184689Sed	cv_destroy(&ss->snp_outwait);
127184689Sed	free(ss, M_SNP);
1286712Spst}
1296712Spst
130184689Sed/*
131184689Sed * Snoop device node routines.
132184689Sed */
1336774Sugen
13477748Sddstatic int
135184689Sedsnp_open(struct cdev *dev, int flag, int mode, struct thread *td)
1366774Sugen{
137184689Sed	struct snp_softc *ss;
1386774Sugen
139184689Sed	/* Allocate per-snoop data. */
140184689Sed	ss = malloc(sizeof(struct snp_softc), M_SNP, M_WAITOK|M_ZERO);
141184689Sed	cv_init(&ss->snp_outwait, "snp out");
142181755Sed
143184689Sed	devfs_set_cdevpriv(ss, snp_dtor);
1446774Sugen
14577748Sdd	return (0);
1466774Sugen}
1476774Sugen
14877748Sddstatic int
149184689Sedsnp_read(struct cdev *dev, struct uio *uio, int flag)
1506390Sugen{
151184689Sed	int error, oresid = uio->uio_resid;
152184689Sed	struct snp_softc *ss;
153184689Sed	struct tty *tp;
1546447Sugen
155184689Sed	if (uio->uio_resid == 0)
156184689Sed		return (0);
157184689Sed
158184689Sed	error = devfs_get_cdevpriv((void **)&ss);
159181755Sed	if (error != 0)
160181755Sed		return (error);
161223575Sed
162184689Sed	tp = ss->snp_tty;
163184689Sed	if (tp == NULL || tty_gone(tp))
1646390Sugen		return (EIO);
1656390Sugen
166184689Sed	tty_lock(tp);
167184689Sed	for (;;) {
168184689Sed		error = ttyoutq_read_uio(&ss->snp_outq, tp, uio);
169184689Sed		if (error != 0 || uio->uio_resid != oresid)
170184689Sed			break;
1716390Sugen
172184689Sed		/* Wait for more data. */
173184689Sed		if (flag & O_NONBLOCK) {
174184689Sed			error = EWOULDBLOCK;
175184689Sed			break;
1766390Sugen		}
177184689Sed		error = cv_wait_sig(&ss->snp_outwait, tp->t_mtx);
178184689Sed		if (error != 0)
1796390Sugen			break;
180184689Sed		if (tty_gone(tp)) {
181184689Sed			error = EIO;
182184689Sed			break;
1836390Sugen		}
1846390Sugen	}
185184689Sed	tty_unlock(tp);
1866390Sugen
18777748Sdd	return (error);
1886390Sugen}
1896390Sugen
19077748Sddstatic int
191184689Sedsnp_write(struct cdev *dev, struct uio *uio, int flag)
1926390Sugen{
193184689Sed	struct snp_softc *ss;
194184689Sed	struct tty *tp;
195196452Sed	int error, len;
196184689Sed	char in[SNP_INPUT_BUFSIZE];
1976390Sugen
198184689Sed	error = devfs_get_cdevpriv((void **)&ss);
199184689Sed	if (error != 0)
200184689Sed		return (error);
201223575Sed
202184689Sed	tp = ss->snp_tty;
203184689Sed	if (tp == NULL || tty_gone(tp))
204184689Sed		return (EIO);
2056390Sugen
206184689Sed	while (uio->uio_resid > 0) {
207184689Sed		/* Read new data. */
208184689Sed		len = imin(uio->uio_resid, sizeof in);
209184689Sed		error = uiomove(in, len, uio);
210184689Sed		if (error != 0)
211184689Sed			return (error);
2126390Sugen
213184689Sed		tty_lock(tp);
2146455Sugen
215184689Sed		/* Driver could have abandoned the TTY in the mean time. */
216184689Sed		if (tty_gone(tp)) {
217184689Sed			tty_unlock(tp);
218184689Sed			return (ENXIO);
219184689Sed		}
220184689Sed
2216447Sugen		/*
222184689Sed		 * Deliver data to the TTY. Ignore errors for now,
223184689Sed		 * because we shouldn't bail out when we're running
224184689Sed		 * close to the watermarks.
2256447Sugen		 */
226196452Sed		ttydisc_rint_simple(tp, in, len);
227196452Sed		ttydisc_rint_done(tp);
2286390Sugen
229184689Sed		tty_unlock(tp);
2306447Sugen	}
2316390Sugen
2326390Sugen	return (0);
2336390Sugen}
2346390Sugen
23577748Sddstatic int
236184689Sedsnp_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags,
237167851Srodrigc    struct thread *td)
2386390Sugen{
239184689Sed	struct snp_softc *ss;
240174888Skib	struct tty *tp;
241184689Sed	int error;
2426390Sugen
243184689Sed	error = devfs_get_cdevpriv((void **)&ss);
244181755Sed	if (error != 0)
245181755Sed		return (error);
246181755Sed
2476390Sugen	switch (cmd) {
2486390Sugen	case SNPSTTY:
249184689Sed		/* Bind TTY to snoop instance. */
250191999Sed		SNP_LOCK();
251184689Sed		if (ss->snp_tty != NULL) {
252191999Sed			SNP_UNLOCK();
253184689Sed			return (EBUSY);
254150298Scognet		}
255224778Srwatson		/*
256224778Srwatson		 * XXXRW / XXXJA: no capability check here.
257224778Srwatson		 */
258192062Sed		error = ttyhook_register(&ss->snp_tty, td->td_proc,
259192062Sed		    *(int *)data, &snp_hook, ss);
260191999Sed		SNP_UNLOCK();
261184689Sed		if (error != 0)
262184689Sed			return (error);
2636455Sugen
264184689Sed		/* Now that went okay, allocate a buffer for the queue. */
265184689Sed		tp = ss->snp_tty;
266184689Sed		tty_lock(tp);
267184689Sed		ttyoutq_setsize(&ss->snp_outq, tp, SNP_OUTPUT_BUFSIZE);
268184689Sed		tty_unlock(tp);
269174888Skib
270184689Sed		return (0);
2716390Sugen	case SNPGTTY:
272184689Sed		/* Obtain device number of associated TTY. */
273184689Sed		if (ss->snp_tty == NULL)
274184689Sed			*(dev_t *)data = NODEV;
2756390Sugen		else
276184689Sed			*(dev_t *)data = tty_udev(ss->snp_tty);
277184689Sed		return (0);
2786390Sugen	case FIONREAD:
279184689Sed		tp = ss->snp_tty;
280184689Sed		if (tp != NULL) {
281184689Sed			tty_lock(tp);
282184689Sed			*(int *)data = ttyoutq_bytesused(&ss->snp_outq);
283184689Sed			tty_unlock(tp);
284184689Sed		} else {
285184689Sed			*(int *)data = 0;
286184689Sed		}
287184689Sed		return (0);
2886390Sugen	default:
2896390Sugen		return (ENOTTY);
2906390Sugen	}
2916390Sugen}
2926390Sugen
29377748Sddstatic int
294184689Sedsnp_poll(struct cdev *dev, int events, struct thread *td)
2956390Sugen{
296184689Sed	struct snp_softc *ss;
297184689Sed	struct tty *tp;
29877748Sdd	int revents;
2996390Sugen
300184689Sed	if (devfs_get_cdevpriv((void **)&ss) != 0)
301181755Sed		return (events &
302181755Sed		    (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
303181755Sed
30477748Sdd	revents = 0;
305184689Sed
30646568Speter	if (events & (POLLIN | POLLRDNORM)) {
307184689Sed		tp = ss->snp_tty;
308184689Sed		if (tp != NULL) {
309184689Sed			tty_lock(tp);
310184689Sed			if (ttyoutq_bytesused(&ss->snp_outq) > 0)
311184689Sed				revents |= events & (POLLIN | POLLRDNORM);
312184689Sed			tty_unlock(tp);
313184689Sed		}
31446568Speter	}
315184689Sed
316184689Sed	if (revents == 0)
317184689Sed		selrecord(td, &ss->snp_outpoll);
318184689Sed
31929355Speter	return (revents);
3206390Sugen}
3216390Sugen
322184689Sed/*
323184689Sed * TTY hook events.
324184689Sed */
325184689Sed
32677016Sddstatic int
327167851Srodrigcsnp_modevent(module_t mod, int type, void *data)
32812517Sjulian{
32912517Sjulian
33077016Sdd	switch (type) {
33177016Sdd	case MOD_LOAD:
332184689Sed		snp_dev = make_dev(&snp_cdevsw, 0,
333184689Sed		    UID_ROOT, GID_WHEEL, 0600, "snp");
334184689Sed		return (0);
33577016Sdd	case MOD_UNLOAD:
336184689Sed		/* XXX: Make existing users leave. */
337184689Sed		destroy_dev(snp_dev);
338184689Sed		return (0);
33977016Sdd	default:
340132199Sphk		return (EOPNOTSUPP);
34177016Sdd	}
34212517Sjulian}
34312517Sjulian
344184689Sedstatic void
345184689Sedsnp_getc_capture(struct tty *tp, const void *buf, size_t len)
346184689Sed{
347184689Sed	struct snp_softc *ss = ttyhook_softc(tp);
348184689Sed
349184689Sed	ttyoutq_write(&ss->snp_outq, buf, len);
350184689Sed
351184689Sed	cv_broadcast(&ss->snp_outwait);
352184689Sed	selwakeup(&ss->snp_outpoll);
353184689Sed}
354184689Sed
35577016Sddstatic moduledata_t snp_mod = {
356184689Sed	"snp",
357184689Sed	snp_modevent,
358184689Sed	NULL
35977016Sdd};
360184689Sed
361126077SphkDECLARE_MODULE(snp, snp_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
362