1/*-
2 * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <sys/param.h>
31#include <sys/conf.h>
32#include <sys/fcntl.h>
33#include <sys/filio.h>
34#include <sys/kernel.h>
35#include <sys/malloc.h>
36#include <sys/module.h>
37#include <sys/poll.h>
38#include <sys/proc.h>
39#include <sys/snoop.h>
40#include <sys/sx.h>
41#include <sys/systm.h>
42#include <sys/tty.h>
43#include <sys/uio.h>
44
45static struct cdev	*snp_dev;
46static MALLOC_DEFINE(M_SNP, "snp", "tty snoop device");
47
48/* XXX: should be mtx, but TTY can be locked by Giant. */
49#if 0
50static struct mtx	snp_register_lock;
51MTX_SYSINIT(snp_register_lock, &snp_register_lock,
52    "tty snoop registration", MTX_DEF);
53#define	SNP_LOCK()	mtx_lock(&snp_register_lock)
54#define	SNP_UNLOCK()	mtx_unlock(&snp_register_lock)
55#else
56static struct sx	snp_register_lock;
57SX_SYSINIT(snp_register_lock, &snp_register_lock,
58    "tty snoop registration");
59#define	SNP_LOCK()	sx_xlock(&snp_register_lock)
60#define	SNP_UNLOCK()	sx_xunlock(&snp_register_lock)
61#endif
62
63/*
64 * There is no need to have a big input buffer. In most typical setups,
65 * we won't inject much data into the TTY, because users can't type
66 * really fast.
67 */
68#define SNP_INPUT_BUFSIZE	16
69/*
70 * The output buffer has to be really big. Right now we don't support
71 * any form of flow control, which means we lost any data we can't
72 * accept. We set the output buffer size to about twice the size of a
73 * pseudo-terminal/virtual console's output buffer.
74 */
75#define SNP_OUTPUT_BUFSIZE	16384
76
77static d_open_t		snp_open;
78static d_read_t		snp_read;
79static d_write_t	snp_write;
80static d_ioctl_t	snp_ioctl;
81static d_poll_t		snp_poll;
82
83static struct cdevsw snp_cdevsw = {
84	.d_version	= D_VERSION,
85	.d_open		= snp_open,
86	.d_read		= snp_read,
87	.d_write	= snp_write,
88	.d_ioctl	= snp_ioctl,
89	.d_poll		= snp_poll,
90	.d_name		= "snp",
91};
92
93static th_getc_capture_t	snp_getc_capture;
94
95static struct ttyhook snp_hook = {
96	.th_getc_capture	= snp_getc_capture,
97};
98
99/*
100 * Per-instance structure.
101 *
102 * List of locks
103 * (r)	locked by snp_register_lock on assignment
104 * (t)	locked by tty_lock
105 */
106struct snp_softc {
107	struct tty	*snp_tty;	/* (r) TTY we're snooping. */
108	struct ttyoutq	snp_outq;	/* (t) Output queue. */
109	struct cv	snp_outwait;	/* (t) Output wait queue. */
110	struct selinfo	snp_outpoll;	/* (t) Output polling. */
111};
112
113static void
114snp_dtor(void *data)
115{
116	struct snp_softc *ss = data;
117	struct tty *tp;
118
119	tp = ss->snp_tty;
120	if (tp != NULL) {
121		tty_lock(tp);
122		ttyoutq_free(&ss->snp_outq);
123		ttyhook_unregister(tp);
124	}
125
126	cv_destroy(&ss->snp_outwait);
127	free(ss, M_SNP);
128}
129
130/*
131 * Snoop device node routines.
132 */
133
134static int
135snp_open(struct cdev *dev, int flag, int mode, struct thread *td)
136{
137	struct snp_softc *ss;
138
139	/* Allocate per-snoop data. */
140	ss = malloc(sizeof(struct snp_softc), M_SNP, M_WAITOK|M_ZERO);
141	cv_init(&ss->snp_outwait, "snp out");
142
143	devfs_set_cdevpriv(ss, snp_dtor);
144
145	return (0);
146}
147
148static int
149snp_read(struct cdev *dev, struct uio *uio, int flag)
150{
151	int error, oresid = uio->uio_resid;
152	struct snp_softc *ss;
153	struct tty *tp;
154
155	if (uio->uio_resid == 0)
156		return (0);
157
158	error = devfs_get_cdevpriv((void **)&ss);
159	if (error != 0)
160		return (error);
161
162	tp = ss->snp_tty;
163	if (tp == NULL || tty_gone(tp))
164		return (EIO);
165
166	tty_lock(tp);
167	for (;;) {
168		error = ttyoutq_read_uio(&ss->snp_outq, tp, uio);
169		if (error != 0 || uio->uio_resid != oresid)
170			break;
171
172		/* Wait for more data. */
173		if (flag & O_NONBLOCK) {
174			error = EWOULDBLOCK;
175			break;
176		}
177		error = cv_wait_sig(&ss->snp_outwait, tp->t_mtx);
178		if (error != 0)
179			break;
180		if (tty_gone(tp)) {
181			error = EIO;
182			break;
183		}
184	}
185	tty_unlock(tp);
186
187	return (error);
188}
189
190static int
191snp_write(struct cdev *dev, struct uio *uio, int flag)
192{
193	struct snp_softc *ss;
194	struct tty *tp;
195	int error, len;
196	char in[SNP_INPUT_BUFSIZE];
197
198	error = devfs_get_cdevpriv((void **)&ss);
199	if (error != 0)
200		return (error);
201
202	tp = ss->snp_tty;
203	if (tp == NULL || tty_gone(tp))
204		return (EIO);
205
206	while (uio->uio_resid > 0) {
207		/* Read new data. */
208		len = imin(uio->uio_resid, sizeof in);
209		error = uiomove(in, len, uio);
210		if (error != 0)
211			return (error);
212
213		tty_lock(tp);
214
215		/* Driver could have abandoned the TTY in the mean time. */
216		if (tty_gone(tp)) {
217			tty_unlock(tp);
218			return (ENXIO);
219		}
220
221		/*
222		 * Deliver data to the TTY. Ignore errors for now,
223		 * because we shouldn't bail out when we're running
224		 * close to the watermarks.
225		 */
226		ttydisc_rint_simple(tp, in, len);
227		ttydisc_rint_done(tp);
228
229		tty_unlock(tp);
230	}
231
232	return (0);
233}
234
235static int
236snp_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags,
237    struct thread *td)
238{
239	struct snp_softc *ss;
240	struct tty *tp;
241	int error;
242
243	error = devfs_get_cdevpriv((void **)&ss);
244	if (error != 0)
245		return (error);
246
247	switch (cmd) {
248	case SNPSTTY:
249		/* Bind TTY to snoop instance. */
250		SNP_LOCK();
251		if (ss->snp_tty != NULL) {
252			SNP_UNLOCK();
253			return (EBUSY);
254		}
255		/*
256		 * XXXRW / XXXJA: no capability check here.
257		 */
258		error = ttyhook_register(&ss->snp_tty, td->td_proc,
259		    *(int *)data, &snp_hook, ss);
260		SNP_UNLOCK();
261		if (error != 0)
262			return (error);
263
264		/* Now that went okay, allocate a buffer for the queue. */
265		tp = ss->snp_tty;
266		tty_lock(tp);
267		ttyoutq_setsize(&ss->snp_outq, tp, SNP_OUTPUT_BUFSIZE);
268		tty_unlock(tp);
269
270		return (0);
271	case SNPGTTY:
272		/* Obtain device number of associated TTY. */
273		if (ss->snp_tty == NULL)
274			*(dev_t *)data = NODEV;
275		else
276			*(dev_t *)data = tty_udev(ss->snp_tty);
277		return (0);
278	case FIONREAD:
279		tp = ss->snp_tty;
280		if (tp != NULL) {
281			tty_lock(tp);
282			*(int *)data = ttyoutq_bytesused(&ss->snp_outq);
283			tty_unlock(tp);
284		} else {
285			*(int *)data = 0;
286		}
287		return (0);
288	default:
289		return (ENOTTY);
290	}
291}
292
293static int
294snp_poll(struct cdev *dev, int events, struct thread *td)
295{
296	struct snp_softc *ss;
297	struct tty *tp;
298	int revents;
299
300	if (devfs_get_cdevpriv((void **)&ss) != 0)
301		return (events &
302		    (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
303
304	revents = 0;
305
306	if (events & (POLLIN | POLLRDNORM)) {
307		tp = ss->snp_tty;
308		if (tp != NULL) {
309			tty_lock(tp);
310			if (ttyoutq_bytesused(&ss->snp_outq) > 0)
311				revents |= events & (POLLIN | POLLRDNORM);
312			tty_unlock(tp);
313		}
314	}
315
316	if (revents == 0)
317		selrecord(td, &ss->snp_outpoll);
318
319	return (revents);
320}
321
322/*
323 * TTY hook events.
324 */
325
326static int
327snp_modevent(module_t mod, int type, void *data)
328{
329
330	switch (type) {
331	case MOD_LOAD:
332		snp_dev = make_dev(&snp_cdevsw, 0,
333		    UID_ROOT, GID_WHEEL, 0600, "snp");
334		return (0);
335	case MOD_UNLOAD:
336		/* XXX: Make existing users leave. */
337		destroy_dev(snp_dev);
338		return (0);
339	default:
340		return (EOPNOTSUPP);
341	}
342}
343
344static void
345snp_getc_capture(struct tty *tp, const void *buf, size_t len)
346{
347	struct snp_softc *ss = ttyhook_softc(tp);
348
349	ttyoutq_write(&ss->snp_outq, buf, len);
350
351	cv_broadcast(&ss->snp_outwait);
352	selwakeup(&ss->snp_outpoll);
353}
354
355static moduledata_t snp_mod = {
356	"snp",
357	snp_modevent,
358	NULL
359};
360
361DECLARE_MODULE(snp, snp_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
362