1098ca2bdSWarner Losh /*- 2932ef5b5SEd Schouten * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> 3932ef5b5SEd Schouten * All rights reserved. 4dde8a05bSUgen J.S. Antsilevich * 5932ef5b5SEd Schouten * Redistribution and use in source and binary forms, with or without 6932ef5b5SEd Schouten * modification, are permitted provided that the following conditions 7932ef5b5SEd Schouten * are met: 8932ef5b5SEd Schouten * 1. Redistributions of source code must retain the above copyright 9932ef5b5SEd Schouten * notice, this list of conditions and the following disclaimer. 10932ef5b5SEd Schouten * 2. Redistributions in binary form must reproduce the above copyright 11932ef5b5SEd Schouten * notice, this list of conditions and the following disclaimer in the 12932ef5b5SEd Schouten * documentation and/or other materials provided with the distribution. 13dde8a05bSUgen J.S. Antsilevich * 14932ef5b5SEd Schouten * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15932ef5b5SEd Schouten * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16932ef5b5SEd Schouten * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17932ef5b5SEd Schouten * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18932ef5b5SEd Schouten * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19932ef5b5SEd Schouten * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20932ef5b5SEd Schouten * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21932ef5b5SEd Schouten * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22932ef5b5SEd Schouten * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23932ef5b5SEd Schouten * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24932ef5b5SEd Schouten * SUCH DAMAGE. 25dde8a05bSUgen J.S. Antsilevich */ 26dde8a05bSUgen J.S. Antsilevich 27945ff31aSDavid E. O'Brien #include <sys/cdefs.h> 28945ff31aSDavid E. O'Brien __FBSDID("$FreeBSD$"); 29945ff31aSDavid E. O'Brien 30dde8a05bSUgen J.S. Antsilevich #include <sys/param.h> 31932ef5b5SEd Schouten #include <sys/conf.h> 321ef0fc1dSPoul-Henning Kamp #include <sys/fcntl.h> 3371455815SBruce Evans #include <sys/filio.h> 34dde8a05bSUgen J.S. Antsilevich #include <sys/kernel.h> 35932ef5b5SEd Schouten #include <sys/malloc.h> 36fe12f24bSPoul-Henning Kamp #include <sys/module.h> 37932ef5b5SEd Schouten #include <sys/poll.h> 38a9385ad1SAlexander Motin #include <sys/proc.h> 3987f6c662SJulian Elischer #include <sys/snoop.h> 40932ef5b5SEd Schouten #include <sys/sx.h> 41932ef5b5SEd Schouten #include <sys/systm.h> 42932ef5b5SEd Schouten #include <sys/tty.h> 431ef0fc1dSPoul-Henning Kamp #include <sys/uio.h> 4487f6c662SJulian Elischer 45932ef5b5SEd Schouten static struct cdev *snp_dev; 467fb6f685SEd Schouten static MALLOC_DEFINE(M_SNP, "snp", "tty snoop device"); 477fb6f685SEd Schouten 48932ef5b5SEd Schouten /* XXX: should be mtx, but TTY can be locked by Giant. */ 497fb6f685SEd Schouten #if 0 507fb6f685SEd Schouten static struct mtx snp_register_lock; 517fb6f685SEd Schouten MTX_SYSINIT(snp_register_lock, &snp_register_lock, 527fb6f685SEd Schouten "tty snoop registration", MTX_DEF); 537fb6f685SEd Schouten #define SNP_LOCK() mtx_lock(&snp_register_lock) 547fb6f685SEd Schouten #define SNP_UNLOCK() mtx_unlock(&snp_register_lock) 557fb6f685SEd Schouten #else 56932ef5b5SEd Schouten static struct sx snp_register_lock; 57932ef5b5SEd Schouten SX_SYSINIT(snp_register_lock, &snp_register_lock, 58932ef5b5SEd Schouten "tty snoop registration"); 597fb6f685SEd Schouten #define SNP_LOCK() sx_xlock(&snp_register_lock) 607fb6f685SEd Schouten #define SNP_UNLOCK() sx_xunlock(&snp_register_lock) 617fb6f685SEd Schouten #endif 62932ef5b5SEd Schouten 63932ef5b5SEd Schouten /* 64932ef5b5SEd Schouten * There is no need to have a big input buffer. In most typical setups, 65932ef5b5SEd Schouten * we won't inject much data into the TTY, because users can't type 66932ef5b5SEd Schouten * really fast. 67932ef5b5SEd Schouten */ 68932ef5b5SEd Schouten #define SNP_INPUT_BUFSIZE 16 69932ef5b5SEd Schouten /* 70932ef5b5SEd Schouten * The output buffer has to be really big. Right now we don't support 71932ef5b5SEd Schouten * any form of flow control, which means we lost any data we can't 72932ef5b5SEd Schouten * accept. We set the output buffer size to about twice the size of a 73932ef5b5SEd Schouten * pseudo-terminal/virtual console's output buffer. 74932ef5b5SEd Schouten */ 75932ef5b5SEd Schouten #define SNP_OUTPUT_BUFSIZE 16384 76932ef5b5SEd Schouten 77932ef5b5SEd Schouten static d_open_t snp_open; 78932ef5b5SEd Schouten static d_read_t snp_read; 79932ef5b5SEd Schouten static d_write_t snp_write; 80932ef5b5SEd Schouten static d_ioctl_t snp_ioctl; 81932ef5b5SEd Schouten static d_poll_t snp_poll; 8287f6c662SJulian Elischer 834e2f199eSPoul-Henning Kamp static struct cdevsw snp_cdevsw = { 84dc08ffecSPoul-Henning Kamp .d_version = D_VERSION, 85932ef5b5SEd Schouten .d_open = snp_open, 86932ef5b5SEd Schouten .d_read = snp_read, 87932ef5b5SEd Schouten .d_write = snp_write, 88932ef5b5SEd Schouten .d_ioctl = snp_ioctl, 89932ef5b5SEd Schouten .d_poll = snp_poll, 907ac40f5fSPoul-Henning Kamp .d_name = "snp", 914e2f199eSPoul-Henning Kamp }; 9287f6c662SJulian Elischer 93932ef5b5SEd Schouten static th_getc_capture_t snp_getc_capture; 94932ef5b5SEd Schouten 95932ef5b5SEd Schouten static struct ttyhook snp_hook = { 96932ef5b5SEd Schouten .th_getc_capture = snp_getc_capture, 97f09f49f1SDima Dorfman }; 9853ac6efbSJulian Elischer 99101f105dSDima Dorfman /* 100932ef5b5SEd Schouten * Per-instance structure. 101932ef5b5SEd Schouten * 102932ef5b5SEd Schouten * List of locks 103932ef5b5SEd Schouten * (r) locked by snp_register_lock on assignment 104932ef5b5SEd Schouten * (t) locked by tty_lock 105101f105dSDima Dorfman */ 106932ef5b5SEd Schouten struct snp_softc { 107932ef5b5SEd Schouten struct tty *snp_tty; /* (r) TTY we're snooping. */ 108932ef5b5SEd Schouten struct ttyoutq snp_outq; /* (t) Output queue. */ 109932ef5b5SEd Schouten struct cv snp_outwait; /* (t) Output wait queue. */ 110932ef5b5SEd Schouten struct selinfo snp_outpoll; /* (t) Output polling. */ 111101f105dSDima Dorfman }; 112101f105dSDima Dorfman 1132e37c8eaSEd Schouten static void 1142e37c8eaSEd Schouten snp_dtor(void *data) 1152e37c8eaSEd Schouten { 116932ef5b5SEd Schouten struct snp_softc *ss = data; 117932ef5b5SEd Schouten struct tty *tp; 1182e37c8eaSEd Schouten 119932ef5b5SEd Schouten tp = ss->snp_tty; 120932ef5b5SEd Schouten if (tp != NULL) { 121932ef5b5SEd Schouten tty_lock(tp); 122932ef5b5SEd Schouten ttyoutq_free(&ss->snp_outq); 123932ef5b5SEd Schouten ttyhook_unregister(tp); 124932ef5b5SEd Schouten } 125932ef5b5SEd Schouten 126932ef5b5SEd Schouten cv_destroy(&ss->snp_outwait); 127932ef5b5SEd Schouten free(ss, M_SNP); 128932ef5b5SEd Schouten } 129932ef5b5SEd Schouten 130932ef5b5SEd Schouten /* 131932ef5b5SEd Schouten * Snoop device node routines. 132932ef5b5SEd Schouten */ 133932ef5b5SEd Schouten 134932ef5b5SEd Schouten static int 135932ef5b5SEd Schouten snp_open(struct cdev *dev, int flag, int mode, struct thread *td) 136932ef5b5SEd Schouten { 137932ef5b5SEd Schouten struct snp_softc *ss; 138932ef5b5SEd Schouten 139932ef5b5SEd Schouten /* Allocate per-snoop data. */ 140932ef5b5SEd Schouten ss = malloc(sizeof(struct snp_softc), M_SNP, M_WAITOK|M_ZERO); 141932ef5b5SEd Schouten cv_init(&ss->snp_outwait, "snp out"); 142932ef5b5SEd Schouten 143932ef5b5SEd Schouten devfs_set_cdevpriv(ss, snp_dtor); 144932ef5b5SEd Schouten 145932ef5b5SEd Schouten return (0); 1462e37c8eaSEd Schouten } 1472e37c8eaSEd Schouten 14887f6c662SJulian Elischer static int 149932ef5b5SEd Schouten snp_read(struct cdev *dev, struct uio *uio, int flag) 150dde8a05bSUgen J.S. Antsilevich { 151932ef5b5SEd Schouten int error, oresid = uio->uio_resid; 152932ef5b5SEd Schouten struct snp_softc *ss; 153932ef5b5SEd Schouten struct tty *tp; 154dde8a05bSUgen J.S. Antsilevich 155932ef5b5SEd Schouten if (uio->uio_resid == 0) 156932ef5b5SEd Schouten return (0); 157932ef5b5SEd Schouten 158932ef5b5SEd Schouten error = devfs_get_cdevpriv((void **)&ss); 159932ef5b5SEd Schouten if (error != 0) 160932ef5b5SEd Schouten return (error); 161932ef5b5SEd Schouten 162932ef5b5SEd Schouten tp = ss->snp_tty; 163932ef5b5SEd Schouten if (tp == NULL || tty_gone(tp)) 164932ef5b5SEd Schouten return (EIO); 165932ef5b5SEd Schouten 166932ef5b5SEd Schouten tty_lock(tp); 167932ef5b5SEd Schouten for (;;) { 168932ef5b5SEd Schouten error = ttyoutq_read_uio(&ss->snp_outq, tp, uio); 169932ef5b5SEd Schouten if (error != 0 || uio->uio_resid != oresid) 170932ef5b5SEd Schouten break; 171932ef5b5SEd Schouten 172932ef5b5SEd Schouten /* Wait for more data. */ 173932ef5b5SEd Schouten if (flag & O_NONBLOCK) { 174932ef5b5SEd Schouten error = EWOULDBLOCK; 175932ef5b5SEd Schouten break; 176932ef5b5SEd Schouten } 177932ef5b5SEd Schouten error = cv_wait_sig(&ss->snp_outwait, tp->t_mtx); 178932ef5b5SEd Schouten if (error != 0) 179932ef5b5SEd Schouten break; 180932ef5b5SEd Schouten if (tty_gone(tp)) { 181932ef5b5SEd Schouten error = EIO; 182932ef5b5SEd Schouten break; 183932ef5b5SEd Schouten } 184932ef5b5SEd Schouten } 185932ef5b5SEd Schouten tty_unlock(tp); 186932ef5b5SEd Schouten 1872e37c8eaSEd Schouten return (error); 1882e37c8eaSEd Schouten } 18977f77631SPaul Traina 190932ef5b5SEd Schouten static int 191932ef5b5SEd Schouten snp_write(struct cdev *dev, struct uio *uio, int flag) 192019b4d63SUgen J.S. Antsilevich { 193932ef5b5SEd Schouten struct snp_softc *ss; 194019b4d63SUgen J.S. Antsilevich struct tty *tp; 195932ef5b5SEd Schouten int error, len, i; 196932ef5b5SEd Schouten char in[SNP_INPUT_BUFSIZE]; 197019b4d63SUgen J.S. Antsilevich 198932ef5b5SEd Schouten error = devfs_get_cdevpriv((void **)&ss); 199932ef5b5SEd Schouten if (error != 0) 200932ef5b5SEd Schouten return (error); 201932ef5b5SEd Schouten 202932ef5b5SEd Schouten tp = ss->snp_tty; 203932ef5b5SEd Schouten if (tp == NULL || tty_gone(tp)) 204932ef5b5SEd Schouten return (EIO); 205932ef5b5SEd Schouten 206932ef5b5SEd Schouten while (uio->uio_resid > 0) { 207932ef5b5SEd Schouten /* Read new data. */ 208932ef5b5SEd Schouten len = imin(uio->uio_resid, sizeof in); 209932ef5b5SEd Schouten error = uiomove(in, len, uio); 210932ef5b5SEd Schouten if (error != 0) 211932ef5b5SEd Schouten return (error); 212932ef5b5SEd Schouten 213932ef5b5SEd Schouten tty_lock(tp); 214932ef5b5SEd Schouten 215932ef5b5SEd Schouten /* Driver could have abandoned the TTY in the mean time. */ 216932ef5b5SEd Schouten if (tty_gone(tp)) { 217932ef5b5SEd Schouten tty_unlock(tp); 218932ef5b5SEd Schouten return (ENXIO); 219932ef5b5SEd Schouten } 220019b4d63SUgen J.S. Antsilevich 221019b4d63SUgen J.S. Antsilevich /* 222932ef5b5SEd Schouten * Deliver data to the TTY. Ignore errors for now, 223932ef5b5SEd Schouten * because we shouldn't bail out when we're running 224932ef5b5SEd Schouten * close to the watermarks. 225019b4d63SUgen J.S. Antsilevich */ 226932ef5b5SEd Schouten if (ttydisc_can_bypass(tp)) { 227932ef5b5SEd Schouten ttydisc_rint_bypass(tp, in, len); 228932ef5b5SEd Schouten } else { 229932ef5b5SEd Schouten for (i = 0; i < len; i++) 230932ef5b5SEd Schouten ttydisc_rint(tp, in[i], 0); 231019b4d63SUgen J.S. Antsilevich } 232019b4d63SUgen J.S. Antsilevich 233932ef5b5SEd Schouten ttydisc_rint_done(tp); 234932ef5b5SEd Schouten tty_unlock(tp); 23576e90dbcSBrian Feldman } 236dde8a05bSUgen J.S. Antsilevich 237542a8db5SKonstantin Belousov return (0); 238964587caSUgen J.S. Antsilevich } 239dde8a05bSUgen J.S. Antsilevich 24087f6c662SJulian Elischer static int 241932ef5b5SEd Schouten snp_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, 2427409f6cdSCraig Rodrigues struct thread *td) 243dde8a05bSUgen J.S. Antsilevich { 244932ef5b5SEd Schouten struct snp_softc *ss; 245316d90a3SKonstantin Belousov struct tty *tp; 246932ef5b5SEd Schouten int error; 247dde8a05bSUgen J.S. Antsilevich 248932ef5b5SEd Schouten error = devfs_get_cdevpriv((void **)&ss); 2492e37c8eaSEd Schouten if (error != 0) 2502e37c8eaSEd Schouten return (error); 2512e37c8eaSEd Schouten 252dde8a05bSUgen J.S. Antsilevich switch (cmd) { 253dde8a05bSUgen J.S. Antsilevich case SNPSTTY: 254932ef5b5SEd Schouten /* Bind TTY to snoop instance. */ 2557fb6f685SEd Schouten SNP_LOCK(); 256932ef5b5SEd Schouten if (ss->snp_tty != NULL) { 2577fb6f685SEd Schouten SNP_UNLOCK(); 258932ef5b5SEd Schouten return (EBUSY); 25930ce1aadSOlivier Houchard } 260aa16f86cSEd Schouten error = ttyhook_register(&ss->snp_tty, td->td_proc, 261aa16f86cSEd Schouten *(int *)data, &snp_hook, ss); 2627fb6f685SEd Schouten SNP_UNLOCK(); 263932ef5b5SEd Schouten if (error != 0) 264932ef5b5SEd Schouten return (error); 265964587caSUgen J.S. Antsilevich 266932ef5b5SEd Schouten /* Now that went okay, allocate a buffer for the queue. */ 267932ef5b5SEd Schouten tp = ss->snp_tty; 268932ef5b5SEd Schouten tty_lock(tp); 269932ef5b5SEd Schouten ttyoutq_setsize(&ss->snp_outq, tp, SNP_OUTPUT_BUFSIZE); 270932ef5b5SEd Schouten tty_unlock(tp); 271316d90a3SKonstantin Belousov 272932ef5b5SEd Schouten return (0); 273dde8a05bSUgen J.S. Antsilevich case SNPGTTY: 274932ef5b5SEd Schouten /* Obtain device number of associated TTY. */ 275932ef5b5SEd Schouten if (ss->snp_tty == NULL) 276932ef5b5SEd Schouten *(dev_t *)data = NODEV; 277dde8a05bSUgen J.S. Antsilevich else 278932ef5b5SEd Schouten *(dev_t *)data = tty_udev(ss->snp_tty); 279932ef5b5SEd Schouten return (0); 280dde8a05bSUgen J.S. Antsilevich case FIONREAD: 281932ef5b5SEd Schouten tp = ss->snp_tty; 282932ef5b5SEd Schouten if (tp != NULL) { 283932ef5b5SEd Schouten tty_lock(tp); 284932ef5b5SEd Schouten *(int *)data = ttyoutq_bytesused(&ss->snp_outq); 285932ef5b5SEd Schouten tty_unlock(tp); 286964587caSUgen J.S. Antsilevich } else { 287932ef5b5SEd Schouten *(int *)data = 0; 288964587caSUgen J.S. Antsilevich } 289932ef5b5SEd Schouten return (0); 290dde8a05bSUgen J.S. Antsilevich default: 291dde8a05bSUgen J.S. Antsilevich return (ENOTTY); 292dde8a05bSUgen J.S. Antsilevich } 293dde8a05bSUgen J.S. Antsilevich } 294dde8a05bSUgen J.S. Antsilevich 29587f6c662SJulian Elischer static int 296932ef5b5SEd Schouten snp_poll(struct cdev *dev, int events, struct thread *td) 297dde8a05bSUgen J.S. Antsilevich { 298932ef5b5SEd Schouten struct snp_softc *ss; 299932ef5b5SEd Schouten struct tty *tp; 300f09f49f1SDima Dorfman int revents; 301dde8a05bSUgen J.S. Antsilevich 302932ef5b5SEd Schouten if (devfs_get_cdevpriv((void **)&ss) != 0) 3032e37c8eaSEd Schouten return (events & 3042e37c8eaSEd Schouten (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); 3052e37c8eaSEd Schouten 306f09f49f1SDima Dorfman revents = 0; 307932ef5b5SEd Schouten 308dfd5dee1SPeter Wemm if (events & (POLLIN | POLLRDNORM)) { 309932ef5b5SEd Schouten tp = ss->snp_tty; 310932ef5b5SEd Schouten if (tp != NULL) { 311932ef5b5SEd Schouten tty_lock(tp); 312932ef5b5SEd Schouten if (ttyoutq_bytesused(&ss->snp_outq) > 0) 313659ffb48SPeter Wemm revents |= events & (POLLIN | POLLRDNORM); 314932ef5b5SEd Schouten tty_unlock(tp); 315dfd5dee1SPeter Wemm } 316932ef5b5SEd Schouten } 317932ef5b5SEd Schouten 318932ef5b5SEd Schouten if (revents == 0) 319932ef5b5SEd Schouten selrecord(td, &ss->snp_outpoll); 320932ef5b5SEd Schouten 321659ffb48SPeter Wemm return (revents); 322dde8a05bSUgen J.S. Antsilevich } 323dde8a05bSUgen J.S. Antsilevich 324932ef5b5SEd Schouten /* 325932ef5b5SEd Schouten * TTY hook events. 326932ef5b5SEd Schouten */ 327932ef5b5SEd Schouten 32847eaa5f5SDima Dorfman static int 3297409f6cdSCraig Rodrigues snp_modevent(module_t mod, int type, void *data) 33053ac6efbSJulian Elischer { 33153ac6efbSJulian Elischer 33247eaa5f5SDima Dorfman switch (type) { 33347eaa5f5SDima Dorfman case MOD_LOAD: 334932ef5b5SEd Schouten snp_dev = make_dev(&snp_cdevsw, 0, 335932ef5b5SEd Schouten UID_ROOT, GID_WHEEL, 0600, "snp"); 336932ef5b5SEd Schouten return (0); 33747eaa5f5SDima Dorfman case MOD_UNLOAD: 338932ef5b5SEd Schouten /* XXX: Make existing users leave. */ 339932ef5b5SEd Schouten destroy_dev(snp_dev); 340932ef5b5SEd Schouten return (0); 34147eaa5f5SDima Dorfman default: 3423e019deaSPoul-Henning Kamp return (EOPNOTSUPP); 34347eaa5f5SDima Dorfman } 344932ef5b5SEd Schouten } 345932ef5b5SEd Schouten 346932ef5b5SEd Schouten static void 347932ef5b5SEd Schouten snp_getc_capture(struct tty *tp, const void *buf, size_t len) 348932ef5b5SEd Schouten { 349932ef5b5SEd Schouten struct snp_softc *ss = ttyhook_softc(tp); 350932ef5b5SEd Schouten 351932ef5b5SEd Schouten ttyoutq_write(&ss->snp_outq, buf, len); 352932ef5b5SEd Schouten 353932ef5b5SEd Schouten cv_broadcast(&ss->snp_outwait); 354932ef5b5SEd Schouten selwakeup(&ss->snp_outpoll); 3557198bf47SJulian Elischer } 35653ac6efbSJulian Elischer 35747eaa5f5SDima Dorfman static moduledata_t snp_mod = { 35847eaa5f5SDima Dorfman "snp", 35947eaa5f5SDima Dorfman snp_modevent, 36047eaa5f5SDima Dorfman NULL 36147eaa5f5SDima Dorfman }; 362932ef5b5SEd Schouten 363b0b03348SPoul-Henning Kamp DECLARE_MODULE(snp, snp_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); 364