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 45 static struct cdev *snp_dev; 46 static MALLOC_DEFINE(M_SNP, "snp", "tty snoop device"); 47 48 /* XXX: should be mtx, but TTY can be locked by Giant. */ 49 #if 0 50 static struct mtx snp_register_lock; 51 MTX_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 56 static struct sx snp_register_lock; 57 SX_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 77 static d_open_t snp_open; 78 static d_read_t snp_read; 79 static d_write_t snp_write; 80 static d_ioctl_t snp_ioctl; 81 static d_poll_t snp_poll; 82 83 static 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 93 static th_getc_capture_t snp_getc_capture; 94 95 static 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 */ 106 struct 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 113 static void 114 snp_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 134 static int 135 snp_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 148 static int 149 snp_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 190 static int 191 snp_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 235 static int 236 snp_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 293 static int 294 snp_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 326 static int 327 snp_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 344 static void 345 snp_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 355 static moduledata_t snp_mod = { 356 "snp", 357 snp_modevent, 358 NULL 359 }; 360 361 DECLARE_MODULE(snp, snp_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); 362