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