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