1 /* $NetBSD: tty_ptm.c,v 1.37 2015/08/24 22:50:32 pooka Exp $ */ 2 3 /*- 4 * Copyright (c) 2004 The NetBSD Foundation, Inc. 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: tty_ptm.c,v 1.37 2015/08/24 22:50:32 pooka Exp $"); 31 32 #ifdef _KERNEL_OPT 33 #include "opt_compat_netbsd.h" 34 #include "opt_ptm.h" 35 #endif 36 37 /* pty multiplexor driver /dev/ptm{,x} */ 38 39 #include <sys/param.h> 40 #include <sys/systm.h> 41 #include <sys/ioctl.h> 42 #include <sys/proc.h> 43 #include <sys/tty.h> 44 #include <sys/stat.h> 45 #include <sys/file.h> 46 #include <sys/uio.h> 47 #include <sys/kernel.h> 48 #include <sys/vnode.h> 49 #include <sys/namei.h> 50 #include <sys/signalvar.h> 51 #include <sys/filedesc.h> 52 #include <sys/conf.h> 53 #include <sys/poll.h> 54 #include <sys/pty.h> 55 #include <sys/kauth.h> 56 57 #include <miscfs/specfs/specdev.h> 58 59 #ifdef COMPAT_60 60 #include <compat/sys/ttycom.h> 61 #endif /* COMPAT_60 */ 62 63 #include "ioconf.h" 64 65 #ifdef DEBUG_PTM 66 #define DPRINTF(a) printf a 67 #else 68 #define DPRINTF(a) 69 #endif 70 71 #ifdef NO_DEV_PTM 72 const struct cdevsw ptm_cdevsw = { 73 .d_open = noopen, 74 .d_close = noclose, 75 .d_read = noread, 76 .d_write = nowrite, 77 .d_ioctl = noioctl, 78 .d_stop = nostop, 79 .d_tty = notty, 80 .d_poll = nopoll, 81 .d_mmap = nommap, 82 .d_kqfilter = nokqfilter, 83 .d_discard = nodiscard, 84 .d_flag = D_TTY 85 }; 86 #else 87 88 static struct ptm_pty *ptm; 89 int pts_major, ptc_major; 90 91 static dev_t pty_getfree(void); 92 static int pty_alloc_master(struct lwp *, int *, dev_t *, struct mount *); 93 static int pty_alloc_slave(struct lwp *, int *, dev_t, struct mount *); 94 static int pty_vn_open(struct vnode *, struct lwp *); 95 96 int 97 pty_getmp(struct lwp *l, struct mount **mpp) 98 { 99 if (ptm == NULL) 100 return EOPNOTSUPP; 101 102 return (*ptm->getmp)(l, mpp); 103 } 104 105 dev_t 106 pty_makedev(char ms, int minor) 107 { 108 return makedev(ms == 't' ? pts_major : ptc_major, minor); 109 } 110 111 112 static dev_t 113 pty_getfree(void) 114 { 115 extern kmutex_t pt_softc_mutex; 116 int i; 117 118 mutex_enter(&pt_softc_mutex); 119 for (i = 0; i < npty; i++) { 120 if (pty_isfree(i, 0)) 121 break; 122 } 123 mutex_exit(&pt_softc_mutex); 124 return pty_makedev('t', i); 125 } 126 127 /* 128 * Hacked up version of vn_open. We _only_ handle ptys and only open 129 * them with FREAD|FWRITE and never deal with creat or stuff like that. 130 * 131 * We need it because we have to fake up root credentials to open the pty. 132 */ 133 int 134 pty_vn_open(struct vnode *vp, struct lwp *l) 135 { 136 int error; 137 138 if (vp->v_type != VCHR) { 139 vput(vp); 140 return EINVAL; 141 } 142 143 error = VOP_OPEN(vp, FREAD|FWRITE, lwp0.l_cred); 144 145 if (error) { 146 vput(vp); 147 return error; 148 } 149 150 vp->v_writecount++; 151 152 return 0; 153 } 154 155 static int 156 pty_alloc_master(struct lwp *l, int *fd, dev_t *dev, struct mount *mp) 157 { 158 int error; 159 struct file *fp; 160 struct vnode *vp; 161 int md; 162 163 if ((error = fd_allocfile(&fp, fd)) != 0) { 164 DPRINTF(("fd_allocfile %d\n", error)); 165 return error; 166 } 167 retry: 168 /* Find and open a free master pty. */ 169 *dev = pty_getfree(); 170 md = minor(*dev); 171 if ((error = pty_check(md)) != 0) { 172 DPRINTF(("pty_check %d\n", error)); 173 goto bad; 174 } 175 if (ptm == NULL) { 176 DPRINTF(("no ptm\n")); 177 error = EOPNOTSUPP; 178 goto bad; 179 } 180 if ((error = (*ptm->allocvp)(mp, l, &vp, *dev, 'p')) != 0) { 181 DPRINTF(("pty_allocvp %d\n", error)); 182 goto bad; 183 } 184 185 if ((error = pty_vn_open(vp, l)) != 0) { 186 DPRINTF(("pty_vn_open %d\n", error)); 187 /* 188 * Check if the master open failed because we lost 189 * the race to grab it. 190 */ 191 if (error != EIO) 192 goto bad; 193 error = !pty_isfree(md, 1); 194 DPRINTF(("pty_isfree %d\n", error)); 195 if (error) 196 goto retry; 197 else 198 goto bad; 199 } 200 fp->f_flag = FREAD|FWRITE; 201 fp->f_type = DTYPE_VNODE; 202 fp->f_ops = &vnops; 203 fp->f_vnode = vp; 204 VOP_UNLOCK(vp); 205 fd_affix(curproc, fp, *fd); 206 return 0; 207 bad: 208 fd_abort(curproc, fp, *fd); 209 return error; 210 } 211 212 int 213 pty_grant_slave(struct lwp *l, dev_t dev, struct mount *mp) 214 { 215 int error; 216 struct vnode *vp; 217 218 /* 219 * Open the slave. 220 * namei -> setattr -> unlock -> revoke -> vrele -> 221 * namei -> open -> unlock 222 * Three stage rocket: 223 * 1. Change the owner and permissions on the slave. 224 * 2. Revoke all the users of the slave. 225 * 3. open the slave. 226 */ 227 if (ptm == NULL) 228 return EOPNOTSUPP; 229 if ((error = (*ptm->allocvp)(mp, l, &vp, dev, 't')) != 0) 230 return error; 231 232 if ((vp->v_mount->mnt_flag & MNT_RDONLY) == 0) { 233 struct vattr vattr; 234 (*ptm->getvattr)(mp, l, &vattr); 235 /* Do the VOP_SETATTR() as root. */ 236 error = VOP_SETATTR(vp, &vattr, lwp0.l_cred); 237 if (error) { 238 DPRINTF(("setattr %d\n", error)); 239 VOP_UNLOCK(vp); 240 vrele(vp); 241 return error; 242 } 243 } 244 VOP_UNLOCK(vp); 245 VOP_REVOKE(vp, REVOKEALL); 246 247 /* 248 * The vnode is useless after the revoke, we need to get it again. 249 */ 250 vrele(vp); 251 return 0; 252 } 253 254 static int 255 pty_alloc_slave(struct lwp *l, int *fd, dev_t dev, struct mount *mp) 256 { 257 int error; 258 struct file *fp; 259 struct vnode *vp; 260 261 /* Grab a filedescriptor for the slave */ 262 if ((error = fd_allocfile(&fp, fd)) != 0) { 263 DPRINTF(("fd_allocfile %d\n", error)); 264 return error; 265 } 266 267 if (ptm == NULL) { 268 error = EOPNOTSUPP; 269 goto bad; 270 } 271 272 if ((error = (*ptm->allocvp)(mp, l, &vp, dev, 't')) != 0) 273 goto bad; 274 if ((error = pty_vn_open(vp, l)) != 0) 275 goto bad; 276 277 fp->f_flag = FREAD|FWRITE; 278 fp->f_type = DTYPE_VNODE; 279 fp->f_ops = &vnops; 280 fp->f_vnode = vp; 281 VOP_UNLOCK(vp); 282 fd_affix(curproc, fp, *fd); 283 return 0; 284 bad: 285 fd_abort(curproc, fp, *fd); 286 return error; 287 } 288 289 struct ptm_pty * 290 pty_sethandler(struct ptm_pty *nptm) 291 { 292 struct ptm_pty *optm = ptm; 293 ptm = nptm; 294 return optm; 295 } 296 297 int 298 pty_fill_ptmget(struct lwp *l, dev_t dev, int cfd, int sfd, void *data, struct mount *mp) 299 { 300 struct ptmget *ptmg = data; 301 int error; 302 303 if (ptm == NULL) 304 return EOPNOTSUPP; 305 306 ptmg->cfd = cfd == -1 ? minor(dev) : cfd; 307 ptmg->sfd = sfd == -1 ? minor(dev) : sfd; 308 309 error = (*ptm->makename)(mp, l, ptmg->cn, sizeof(ptmg->cn), dev, 'p'); 310 if (error) 311 return error; 312 313 return (*ptm->makename)(mp, l, ptmg->sn, sizeof(ptmg->sn), dev, 't'); 314 } 315 316 void 317 /*ARGSUSED*/ 318 ptmattach(int n) 319 { 320 extern const struct cdevsw pts_cdevsw, ptc_cdevsw; 321 /* find the major and minor of the pty devices */ 322 if ((pts_major = cdevsw_lookup_major(&pts_cdevsw)) == -1) 323 panic("ptmattach: Can't find pty slave in cdevsw"); 324 if ((ptc_major = cdevsw_lookup_major(&ptc_cdevsw)) == -1) 325 panic("ptmattach: Can't find pty master in cdevsw"); 326 #ifdef COMPAT_BSDPTY 327 ptm = &ptm_bsdpty; 328 #endif 329 } 330 331 static int 332 /*ARGSUSED*/ 333 ptmopen(dev_t dev, int flag, int mode, struct lwp *l) 334 { 335 int error; 336 int fd; 337 dev_t ttydev; 338 struct mount *mp; 339 340 switch(minor(dev)) { 341 case 0: /* /dev/ptmx */ 342 case 2: /* /emul/linux/dev/ptmx */ 343 if ((error = pty_getmp(l, &mp)) != 0) 344 return error; 345 if ((error = pty_alloc_master(l, &fd, &ttydev, mp)) != 0) 346 return error; 347 if (minor(dev) == 2) { 348 /* 349 * Linux ptyfs grants the pty right here. 350 * Handle this case here, instead of writing 351 * a new linux module. 352 */ 353 if ((error = pty_grant_slave(l, ttydev, mp)) != 0) { 354 file_t *fp = fd_getfile(fd); 355 if (fp != NULL) { 356 fd_close(fd); 357 } 358 return error; 359 } 360 } 361 curlwp->l_dupfd = fd; 362 return EMOVEFD; 363 case 1: /* /dev/ptm */ 364 return 0; 365 default: 366 return ENODEV; 367 } 368 } 369 370 static int 371 /*ARGSUSED*/ 372 ptmclose(dev_t dev, int flag, int mode, struct lwp *l) 373 { 374 375 return (0); 376 } 377 378 static int 379 /*ARGSUSED*/ 380 ptmioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 381 { 382 int error; 383 dev_t newdev; 384 int cfd, sfd; 385 file_t *fp; 386 struct mount *mp; 387 388 error = 0; 389 switch (cmd) { 390 case TIOCPTMGET: 391 if ((error = pty_getmp(l, &mp)) != 0) 392 return error; 393 394 if ((error = pty_alloc_master(l, &cfd, &newdev, mp)) != 0) 395 return error; 396 397 if ((error = pty_grant_slave(l, newdev, mp)) != 0) 398 goto bad; 399 400 if ((error = pty_alloc_slave(l, &sfd, newdev, mp)) != 0) 401 goto bad; 402 403 /* now, put the indices and names into struct ptmget */ 404 if ((error = pty_fill_ptmget(l, newdev, cfd, sfd, data, mp)) != 0) 405 goto bad2; 406 return 0; 407 default: 408 #ifdef COMPAT_60 409 error = compat_60_ptmioctl(dev, cmd, data, flag, l); 410 if (error != EPASSTHROUGH) 411 return error; 412 #endif /* COMPAT_60 */ 413 DPRINTF(("ptmioctl EINVAL\n")); 414 return EINVAL; 415 } 416 bad2: 417 fp = fd_getfile(sfd); 418 if (fp != NULL) { 419 fd_close(sfd); 420 } 421 bad: 422 fp = fd_getfile(cfd); 423 if (fp != NULL) { 424 fd_close(cfd); 425 } 426 return error; 427 } 428 429 const struct cdevsw ptm_cdevsw = { 430 .d_open = ptmopen, 431 .d_close = ptmclose, 432 .d_read = noread, 433 .d_write = nowrite, 434 .d_ioctl = ptmioctl, 435 .d_stop = nullstop, 436 .d_tty = notty, 437 .d_poll = nopoll, 438 .d_mmap = nommap, 439 .d_kqfilter = nokqfilter, 440 .d_discard = nodiscard, 441 .d_flag = D_TTY 442 }; 443 #endif 444