1 /* $NetBSD: tty_ptm.c,v 1.42 2020/05/23 22:16:17 ad Exp $ */ 2 3 /*- 4 * Copyright (c) 2004, 2020 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.42 2020/05/23 22:16:17 ad 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 #include <sys/compat_stub.h> 57 58 #include <miscfs/specfs/specdev.h> 59 60 #include <compat/sys/ttycom.h> 61 62 #include "ioconf.h" 63 64 #ifdef DEBUG_PTM 65 #define DPRINTF(a) printf a 66 #else 67 #define DPRINTF(a) 68 #endif 69 70 #ifdef NO_DEV_PTM 71 const struct cdevsw ptm_cdevsw = { 72 .d_open = noopen, 73 .d_close = noclose, 74 .d_read = noread, 75 .d_write = nowrite, 76 .d_ioctl = noioctl, 77 .d_stop = nostop, 78 .d_tty = notty, 79 .d_poll = nopoll, 80 .d_mmap = nommap, 81 .d_kqfilter = nokqfilter, 82 .d_discard = nodiscard, 83 .d_flag = D_TTY 84 }; 85 #else 86 87 int pts_major, ptc_major; 88 89 static dev_t pty_getfree(void); 90 static int pty_alloc_master(struct lwp *, int *, dev_t *, struct mount *); 91 static int pty_alloc_slave(struct lwp *, int *, dev_t, struct mount *); 92 static int pty_vn_open(struct vnode *, struct lwp *); 93 94 int 95 pty_getmp(struct lwp *l, struct mount **mpp) 96 { 97 if (ptm == NULL) 98 return EOPNOTSUPP; 99 100 return (*ptm->getmp)(l, mpp); 101 } 102 103 dev_t 104 pty_makedev(char ms, int minor) 105 { 106 return makedev(ms == 't' ? pts_major : ptc_major, minor); 107 } 108 109 110 static dev_t 111 pty_getfree(void) 112 { 113 extern kmutex_t pt_softc_mutex; 114 int i; 115 116 mutex_enter(&pt_softc_mutex); 117 for (i = 0; i < npty; i++) { 118 if (pty_isfree(i, 0)) 119 break; 120 } 121 mutex_exit(&pt_softc_mutex); 122 return pty_makedev('t', i); 123 } 124 125 /* 126 * Hacked up version of vn_open. We _only_ handle ptys and only open 127 * them with FREAD|FWRITE and never deal with creat or stuff like that. 128 * 129 * We need it because we have to fake up root credentials to open the pty. 130 */ 131 int 132 pty_vn_open(struct vnode *vp, struct lwp *l) 133 { 134 int error; 135 136 if (vp->v_type != VCHR) { 137 vput(vp); 138 return EINVAL; 139 } 140 141 error = VOP_OPEN(vp, FREAD|FWRITE, lwp0.l_cred); 142 143 if (error) { 144 vput(vp); 145 return error; 146 } 147 148 mutex_enter(vp->v_interlock); 149 vp->v_writecount++; 150 mutex_exit(vp->v_interlock); 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 vput(vp); 240 return error; 241 } 242 } 243 VOP_UNLOCK(vp); 244 VOP_REVOKE(vp, REVOKEALL); 245 246 /* 247 * The vnode is useless after the revoke, we need to get it again. 248 */ 249 vrele(vp); 250 return 0; 251 } 252 253 static int 254 pty_alloc_slave(struct lwp *l, int *fd, dev_t dev, struct mount *mp) 255 { 256 int error; 257 struct file *fp; 258 struct vnode *vp; 259 260 /* Grab a filedescriptor for the slave */ 261 if ((error = fd_allocfile(&fp, fd)) != 0) { 262 DPRINTF(("fd_allocfile %d\n", error)); 263 return error; 264 } 265 266 if (ptm == NULL) { 267 error = EOPNOTSUPP; 268 goto bad; 269 } 270 271 if ((error = (*ptm->allocvp)(mp, l, &vp, dev, 't')) != 0) 272 goto bad; 273 if ((error = pty_vn_open(vp, l)) != 0) 274 goto bad; 275 276 fp->f_flag = FREAD|FWRITE; 277 fp->f_type = DTYPE_VNODE; 278 fp->f_ops = &vnops; 279 fp->f_vnode = vp; 280 VOP_UNLOCK(vp); 281 fd_affix(curproc, fp, *fd); 282 return 0; 283 bad: 284 fd_abort(curproc, fp, *fd); 285 return error; 286 } 287 288 struct ptm_pty * 289 pty_sethandler(struct ptm_pty *nptm) 290 { 291 struct ptm_pty *optm = ptm; 292 ptm = nptm; 293 return optm; 294 } 295 296 int 297 pty_fill_ptmget(struct lwp *l, dev_t dev, int cfd, int sfd, void *data, struct mount *mp) 298 { 299 struct ptmget *ptmg = data; 300 int error; 301 302 if (ptm == NULL) 303 return EOPNOTSUPP; 304 305 ptmg->cfd = cfd == -1 ? minor(dev) : cfd; 306 ptmg->sfd = sfd == -1 ? minor(dev) : sfd; 307 308 error = (*ptm->makename)(mp, l, ptmg->cn, sizeof(ptmg->cn), dev, 'p'); 309 if (error) 310 return error; 311 312 return (*ptm->makename)(mp, l, ptmg->sn, sizeof(ptmg->sn), dev, 't'); 313 } 314 315 void 316 /*ARGSUSED*/ 317 ptmattach(int n) 318 { 319 extern const struct cdevsw pts_cdevsw, ptc_cdevsw; 320 /* find the major and minor of the pty devices */ 321 if ((pts_major = cdevsw_lookup_major(&pts_cdevsw)) == -1) 322 panic("ptmattach: Can't find pty slave in cdevsw"); 323 if ((ptc_major = cdevsw_lookup_major(&ptc_cdevsw)) == -1) 324 panic("ptmattach: Can't find pty master in cdevsw"); 325 #ifdef COMPAT_BSDPTY 326 ptm = &ptm_bsdpty; 327 #endif 328 } 329 330 static int 331 /*ARGSUSED*/ 332 ptmopen(dev_t dev, int flag, int mode, struct lwp *l) 333 { 334 int error; 335 int fd; 336 dev_t ttydev; 337 struct mount *mp; 338 339 switch(minor(dev)) { 340 case 0: /* /dev/ptmx */ 341 case 2: /* /emul/linux/dev/ptmx */ 342 if ((error = pty_getmp(l, &mp)) != 0) 343 return error; 344 if ((error = pty_alloc_master(l, &fd, &ttydev, mp)) != 0) 345 return error; 346 if (minor(dev) == 2) { 347 /* 348 * Linux ptyfs grants the pty right here. 349 * Handle this case here, instead of writing 350 * a new linux module. 351 */ 352 if ((error = pty_grant_slave(l, ttydev, mp)) != 0) { 353 file_t *fp = fd_getfile(fd); 354 if (fp != NULL) { 355 fd_close(fd); 356 } 357 return error; 358 } 359 } 360 curlwp->l_dupfd = fd; 361 return EMOVEFD; 362 case 1: /* /dev/ptm */ 363 return 0; 364 default: 365 return ENODEV; 366 } 367 } 368 369 static int 370 /*ARGSUSED*/ 371 ptmclose(dev_t dev, int flag, int mode, struct lwp *l) 372 { 373 374 return (0); 375 } 376 377 static int 378 /*ARGSUSED*/ 379 ptmioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 380 { 381 int error; 382 dev_t newdev; 383 int cfd, sfd; 384 file_t *fp; 385 struct mount *mp; 386 387 error = 0; 388 switch (cmd) { 389 case TIOCPTMGET: 390 if ((error = pty_getmp(l, &mp)) != 0) 391 return error; 392 393 if ((error = pty_alloc_master(l, &cfd, &newdev, mp)) != 0) 394 return error; 395 396 if ((error = pty_grant_slave(l, newdev, mp)) != 0) 397 goto bad; 398 399 if ((error = pty_alloc_slave(l, &sfd, newdev, mp)) != 0) 400 goto bad; 401 402 /* now, put the indices and names into struct ptmget */ 403 if ((error = pty_fill_ptmget(l, newdev, cfd, sfd, data, mp)) != 0) 404 goto bad2; 405 return 0; 406 default: 407 MODULE_HOOK_CALL(tty_ptmioctl_60_hook, 408 (dev, cmd, data, flag, l), EPASSTHROUGH, error); 409 if (error != EPASSTHROUGH) 410 return error; 411 DPRINTF(("ptmioctl EINVAL\n")); 412 return EINVAL; 413 } 414 bad2: 415 fp = fd_getfile(sfd); 416 if (fp != NULL) { 417 fd_close(sfd); 418 } 419 bad: 420 fp = fd_getfile(cfd); 421 if (fp != NULL) { 422 fd_close(cfd); 423 } 424 return error; 425 } 426 427 const struct cdevsw ptm_cdevsw = { 428 .d_open = ptmopen, 429 .d_close = ptmclose, 430 .d_read = noread, 431 .d_write = nowrite, 432 .d_ioctl = ptmioctl, 433 .d_stop = nullstop, 434 .d_tty = notty, 435 .d_poll = nopoll, 436 .d_mmap = nommap, 437 .d_kqfilter = nokqfilter, 438 .d_discard = nodiscard, 439 .d_flag = D_TTY 440 }; 441 #endif 442