1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. 23 */ 24 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 25 /* All Rights Reserved */ 26 27 /* 28 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. 29 * Copyright 2021 Oxide Computer Company 30 */ 31 32 /* 33 * PSEUDO-TERMINAL MANAGER DRIVER (PTM) 34 * 35 * The pseudo-terminal subsystem simulates a terminal connection, where the 36 * manager side represents the terminal and the subsidiary represents the user 37 * process's special device end point. The manager device is set up as a 38 * cloned device where its major device number is the major for the clone 39 * device and its minor device number is the major for the ptm driver. There 40 * are no nodes in the file system for manager devices. The manager pseudo 41 * driver is opened using the open(2) system call with /dev/ptmx as the device 42 * parameter. The clone open finds the next available minor device for the ptm 43 * major device. 44 * 45 * A manager device is available only if it and its corresponding subsidiary 46 * device are not already open. When the manager device is opened, the 47 * corresponding subsidiary device is automatically locked out. Only one open 48 * is allowed on a manager device. Multiple opens are allowed on the 49 * subsidiary device. After both the manager and subsidiary have been opened, 50 * the user has two file descriptors which are the end points of a full duplex 51 * connection composed of two streams which are automatically connected at the 52 * manager and subsidiary drivers. The user may then push modules onto either 53 * side of the stream pair. 54 * 55 * The manager and subsidiary drivers pass all messages to their adjacent 56 * queues. Only the M_FLUSH needs some processing. Because the read queue of 57 * one side is connected to the write queue of the other, the FLUSHR flag is 58 * changed to the FLUSHW flag and vice versa. When the manager device is 59 * closed an M_HANGUP message is sent to the subsidiary device which will 60 * render the device unusable. The process on the subsidiary side gets an EIO 61 * error when attempting to write on that stream but it will be able to read 62 * any data remaining on the stream head read queue. When all the data has 63 * been read, read() returns 0 indicating that the stream can no longer be 64 * used. On the last close of the subsidiary device, a 0-length message is 65 * sent to the manager device. When the application on the manager side issues 66 * a read() or getmsg() and 0 is returned, the user of the manager device 67 * decides whether to issue a close() that dismantles the pseudo-terminal 68 * subsystem. If the manager device is not closed, the pseudo-terminal 69 * subsystem will be available to another user to open the subsidiary device. 70 * 71 * If O_NONBLOCK or O_NDELAY is set, read on the manager side returns -1 with 72 * errno set to EAGAIN if no data is available, and write returns -1 with errno 73 * set to EAGAIN if there is internal flow control. 74 * 75 * 76 * IOCTLS 77 * 78 * ISPTM 79 * Determines whether the file descriptor is that of an open 80 * manager device. Return code of zero indicates that the file 81 * descriptor represents a manager device. 82 * 83 * UNLKPT 84 * Unlocks the manager and subsidiary devices. It returns 0 on 85 * success. On failure, the errno is set to EINVAL indicating that 86 * the manager device is not open. 87 * 88 * ZONEPT 89 * Sets the zone membership of the associated subsidiary device. 90 * 91 * GRPPT 92 * Sets the group owner of the associated subsidiary device. 93 * 94 * 95 * SYNCHRONIZATION 96 * 97 * All global data synchronization between ptm/pts is done via global ptms_lock 98 * mutex which is initialized at system boot time from ptms_initspace (called 99 * from space.c). 100 * 101 * Individual fields of pt_ttys structure (except ptm_rdq, pts_rdq and 102 * pt_nullmsg) are protected by pt_ttys.pt_lock mutex. 103 * 104 * PT_ENTER_READ/PT_ENTER_WRITE are reference counter based read-write locks 105 * which allow reader locks to be reacquired by the same thread (usual 106 * reader/writer locks can't be used for that purpose since it is illegal for a 107 * thread to acquire a lock it already holds, even as a reader). The sole 108 * purpose of these macros is to guarantee that the peer queue will not 109 * disappear (due to closing peer) while it is used. It is safe to use 110 * PT_ENTER_READ/PT_EXIT_READ brackets across calls like putq/putnext (since 111 * they are not real locks but reference counts). 112 * 113 * PT_ENTER_WRITE/PT_EXIT_WRITE brackets are used ONLY in manager/subsidiary 114 * open/close paths to modify ptm_rdq and pts_rdq fields. These fields should 115 * be set to appropriate queues *after* qprocson() is called during open (to 116 * prevent peer from accessing the queue with incomplete plumbing) and set to 117 * NULL before qprocsoff() is called during close. 118 * 119 * The pt_nullmsg field is only used in open/close routines and it is also 120 * protected by PT_ENTER_WRITE/PT_EXIT_WRITE brackets to avoid extra mutex 121 * holds. 122 * 123 * 124 * LOCK ORDERING 125 * 126 * If both ptms_lock and per-pty lock should be held, ptms_lock should always 127 * be entered first, followed by per-pty lock. 128 * 129 * See ptms.h, pts.c, and ptms_conf.c for more information. 130 */ 131 132 #include <sys/types.h> 133 #include <sys/param.h> 134 #include <sys/file.h> 135 #include <sys/sysmacros.h> 136 #include <sys/stream.h> 137 #include <sys/stropts.h> 138 #include <sys/proc.h> 139 #include <sys/errno.h> 140 #include <sys/debug.h> 141 #include <sys/cmn_err.h> 142 #include <sys/ptms.h> 143 #include <sys/stat.h> 144 #include <sys/strsun.h> 145 #include <sys/systm.h> 146 #include <sys/modctl.h> 147 #include <sys/conf.h> 148 #include <sys/ddi.h> 149 #include <sys/sunddi.h> 150 #include <sys/zone.h> 151 152 #ifdef DEBUG 153 int ptm_debug = 0; 154 #define DBG(a) if (ptm_debug) cmn_err(CE_NOTE, a) 155 #else 156 #define DBG(a) 157 #endif 158 159 static int ptmopen(queue_t *, dev_t *, int, int, cred_t *); 160 static int ptmclose(queue_t *, int, cred_t *); 161 static int ptmwput(queue_t *, mblk_t *); 162 static int ptmrsrv(queue_t *); 163 static int ptmwsrv(queue_t *); 164 165 static struct module_info ptm_info = { 166 0xdead, 167 "ptm", 168 0, 169 512, 170 512, 171 128 172 }; 173 174 static struct qinit ptmrint = { 175 NULL, 176 ptmrsrv, 177 ptmopen, 178 ptmclose, 179 NULL, 180 &ptm_info, 181 NULL 182 }; 183 184 static struct qinit ptmwint = { 185 ptmwput, 186 ptmwsrv, 187 NULL, 188 NULL, 189 NULL, 190 &ptm_info, 191 NULL 192 }; 193 194 static struct streamtab ptminfo = { 195 &ptmrint, 196 &ptmwint, 197 NULL, 198 NULL 199 }; 200 201 static int ptm_attach(dev_info_t *, ddi_attach_cmd_t); 202 static int ptm_detach(dev_info_t *, ddi_detach_cmd_t); 203 static int ptm_devinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 204 205 static dev_info_t *ptm_dip; /* private devinfo pointer */ 206 207 /* 208 * this will define (struct cb_ops cb_ptm_ops) and (struct dev_ops ptm_ops) 209 */ 210 DDI_DEFINE_STREAM_OPS(ptm_ops, nulldev, nulldev, ptm_attach, ptm_detach, 211 nodev, ptm_devinfo, D_MP, &ptminfo, ddi_quiesce_not_supported); 212 213 /* 214 * Module linkage information for the kernel. 215 */ 216 217 static struct modldrv modldrv = { 218 &mod_driverops, 219 "Pseudo-Terminal Manager Driver", 220 &ptm_ops, 221 }; 222 223 static struct modlinkage modlinkage = { 224 MODREV_1, 225 &modldrv, 226 NULL 227 }; 228 229 int 230 _init(void) 231 { 232 int rc; 233 234 if ((rc = mod_install(&modlinkage)) == 0) 235 ptms_init(); 236 return (rc); 237 } 238 239 int 240 _fini(void) 241 { 242 return (mod_remove(&modlinkage)); 243 } 244 245 int 246 _info(struct modinfo *modinfop) 247 { 248 return (mod_info(&modlinkage, modinfop)); 249 } 250 251 static int 252 ptm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) 253 { 254 if (cmd != DDI_ATTACH) 255 return (DDI_FAILURE); 256 257 if (ddi_create_minor_node(devi, "ptmajor", S_IFCHR, 258 0, DDI_PSEUDO, 0) == DDI_FAILURE) { 259 ddi_remove_minor_node(devi, NULL); 260 return (DDI_FAILURE); 261 } 262 if (ddi_create_minor_node(devi, "ptmx", S_IFCHR, 263 0, DDI_PSEUDO, CLONE_DEV) == DDI_FAILURE) { 264 ddi_remove_minor_node(devi, NULL); 265 return (DDI_FAILURE); 266 } 267 ptm_dip = devi; 268 269 return (DDI_SUCCESS); 270 } 271 272 static int 273 ptm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) 274 { 275 if (cmd != DDI_DETACH) 276 return (DDI_FAILURE); 277 278 ddi_remove_minor_node(devi, NULL); 279 return (DDI_SUCCESS); 280 } 281 282 static int 283 ptm_devinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, 284 void **result) 285 { 286 int error; 287 288 switch (infocmd) { 289 case DDI_INFO_DEVT2DEVINFO: 290 if (ptm_dip == NULL) { 291 error = DDI_FAILURE; 292 } else { 293 *result = (void *)ptm_dip; 294 error = DDI_SUCCESS; 295 } 296 break; 297 case DDI_INFO_DEVT2INSTANCE: 298 *result = (void *)0; 299 error = DDI_SUCCESS; 300 break; 301 default: 302 error = DDI_FAILURE; 303 } 304 return (error); 305 } 306 307 308 /* 309 * Open a minor of the manager device. Store the write queue pointer and set 310 * the pt_state field to (PTMOPEN | PTLOCK). 311 * This code will work properly with both clone opens and direct opens of the 312 * manager device. 313 */ 314 static int 315 ptmopen( 316 queue_t *rqp, /* pointer to the read side queue */ 317 dev_t *devp, /* pointer to stream tail's dev */ 318 int oflag, /* the user open(2) supplied flags */ 319 int sflag, /* open state flag */ 320 cred_t *credp) /* credentials */ 321 { 322 struct pt_ttys *ptmp; 323 mblk_t *mop; /* ptr to a setopts message block */ 324 struct stroptions *sop; 325 minor_t dminor = getminor(*devp); 326 327 /* Allow reopen */ 328 if (rqp->q_ptr != NULL) 329 return (0); 330 331 if (sflag & MODOPEN) 332 return (ENXIO); 333 334 if (!(sflag & CLONEOPEN) && dminor != 0) { 335 /* 336 * This is a direct open to specific manager device through an 337 * artificially created entry with specific minor in 338 * /dev/directory. Such behavior is not supported. 339 */ 340 return (ENXIO); 341 } 342 343 /* 344 * The manager open requires that the subsidiary be attached before it 345 * returns so that attempts to open the subsidiary will succeeed 346 */ 347 if (ptms_attach_subsidiary() != 0) { 348 return (ENXIO); 349 } 350 351 mop = allocb(sizeof (struct stroptions), BPRI_MED); 352 if (mop == NULL) { 353 DDBG("ptmopen(): mop allocation failed\n", 0); 354 return (ENOMEM); 355 } 356 357 if ((ptmp = pt_ttys_alloc()) == NULL) { 358 DDBG("ptmopen(): pty allocation failed\n", 0); 359 freemsg(mop); 360 return (ENOMEM); 361 } 362 363 dminor = ptmp->pt_minor; 364 365 DDBGP("ptmopen(): allocated ptmp %p\n", (uintptr_t)ptmp); 366 DDBG("ptmopen(): allocated minor %d\n", dminor); 367 368 WR(rqp)->q_ptr = rqp->q_ptr = ptmp; 369 370 qprocson(rqp); 371 372 /* Allow subsidiary to send messages to manager */ 373 PT_ENTER_WRITE(ptmp); 374 ptmp->ptm_rdq = rqp; 375 PT_EXIT_WRITE(ptmp); 376 377 /* 378 * set up hi/lo water marks on stream head read queue 379 * and add controlling tty if not set 380 */ 381 mop->b_datap->db_type = M_SETOPTS; 382 mop->b_wptr += sizeof (struct stroptions); 383 sop = (struct stroptions *)mop->b_rptr; 384 if (oflag & FNOCTTY) 385 sop->so_flags = SO_HIWAT | SO_LOWAT; 386 else 387 sop->so_flags = SO_HIWAT | SO_LOWAT | SO_ISTTY; 388 sop->so_hiwat = _TTY_BUFSIZ; 389 sop->so_lowat = 256; 390 putnext(rqp, mop); 391 392 /* 393 * The input, devp, is a major device number, the output is put 394 * into the same parm as a major,minor pair. 395 */ 396 *devp = makedevice(getmajor(*devp), dminor); 397 398 return (0); 399 } 400 401 402 /* 403 * Find the address to private data identifying the subsidiary's write queue. 404 * Send a hang-up message up the subsidiary's read queue to designate the 405 * manager/subsidiary pair is tearing down. Uattach the manager and subsidiary 406 * by nulling out the write queue fields in the private data structure. 407 * Finally, unlock the manager/subsidiary pair and mark the manager as closed. 408 */ 409 static int 410 ptmclose(queue_t *rqp, int flag, cred_t *credp) 411 { 412 struct pt_ttys *ptmp; 413 queue_t *pts_rdq; 414 415 ASSERT(rqp->q_ptr); 416 417 ptmp = (struct pt_ttys *)rqp->q_ptr; 418 PT_ENTER_READ(ptmp); 419 if (ptmp->pts_rdq) { 420 pts_rdq = ptmp->pts_rdq; 421 if (pts_rdq->q_next) { 422 DBG(("send hangup message to subsidiary\n")); 423 (void) putnextctl(pts_rdq, M_HANGUP); 424 } 425 } 426 PT_EXIT_READ(ptmp); 427 /* 428 * ptm_rdq should be cleared before call to qprocsoff() to prevent pts 429 * write procedure to attempt using ptm_rdq after qprocsoff. 430 */ 431 PT_ENTER_WRITE(ptmp); 432 ptmp->ptm_rdq = NULL; 433 freemsg(ptmp->pt_nullmsg); 434 ptmp->pt_nullmsg = NULL; 435 /* 436 * qenable subsidiary side write queue so that it can flush 437 * its messages as manager's read queue is going away 438 */ 439 if (ptmp->pts_rdq) 440 qenable(WR(ptmp->pts_rdq)); 441 PT_EXIT_WRITE(ptmp); 442 443 qprocsoff(rqp); 444 445 /* Finish the close */ 446 rqp->q_ptr = NULL; 447 WR(rqp)->q_ptr = NULL; 448 449 ptms_close(ptmp, PTMOPEN | PTLOCK); 450 451 return (0); 452 } 453 454 /* 455 * The wput procedure will only handle ioctl and flush messages. 456 */ 457 static int 458 ptmwput(queue_t *qp, mblk_t *mp) 459 { 460 struct pt_ttys *ptmp; 461 struct iocblk *iocp; 462 463 DBG(("entering ptmwput\n")); 464 ASSERT(qp->q_ptr); 465 466 ptmp = (struct pt_ttys *)qp->q_ptr; 467 PT_ENTER_READ(ptmp); 468 469 switch (mp->b_datap->db_type) { 470 /* 471 * If this is a write queue request, flush manager's write queue and 472 * send FLUSHR up subsidiary side. If it is a read queue request, 473 * convert to FLUSHW and putnext(). 474 */ 475 case M_FLUSH: 476 { 477 unsigned char flush_flg = 0; 478 479 DBG(("ptm got flush request\n")); 480 if (*mp->b_rptr & FLUSHW) { 481 DBG(("got FLUSHW, flush ptm write Q\n")); 482 if (*mp->b_rptr & FLUSHBAND) { 483 /* 484 * if it is a FLUSHBAND, do flushband. 485 */ 486 flushband(qp, *(mp->b_rptr + 1), 487 FLUSHDATA); 488 } else { 489 flushq(qp, FLUSHDATA); 490 } 491 flush_flg = (*mp->b_rptr & ~FLUSHW) | FLUSHR; 492 } 493 if (*mp->b_rptr & FLUSHR) { 494 DBG(("got FLUSHR, set FLUSHW\n")); 495 flush_flg |= (*mp->b_rptr & ~FLUSHR) | FLUSHW; 496 } 497 if (flush_flg != 0 && ptmp->pts_rdq && 498 !(ptmp->pt_state & PTLOCK)) { 499 DBG(("putnext to pts\n")); 500 *mp->b_rptr = flush_flg; 501 putnext(ptmp->pts_rdq, mp); 502 } else { 503 freemsg(mp); 504 } 505 break; 506 } 507 508 case M_IOCTL: 509 iocp = (struct iocblk *)mp->b_rptr; 510 switch (iocp->ioc_cmd) { 511 default: 512 if ((ptmp->pt_state & PTLOCK) || 513 (ptmp->pts_rdq == NULL)) { 514 DBG(("got M_IOCTL but no subsidiary\n")); 515 miocnak(qp, mp, 0, EINVAL); 516 PT_EXIT_READ(ptmp); 517 return (0); 518 } 519 (void) putq(qp, mp); 520 break; 521 case UNLKPT: 522 mutex_enter(&ptmp->pt_lock); 523 ptmp->pt_state &= ~PTLOCK; 524 mutex_exit(&ptmp->pt_lock); 525 /*FALLTHROUGH*/ 526 case ISPTM: 527 DBG(("ack the UNLKPT/ISPTM\n")); 528 miocack(qp, mp, 0, 0); 529 break; 530 case PTSSTTY: 531 mutex_enter(&ptmp->pt_lock); 532 ptmp->pt_state |= PTSTTY; 533 mutex_exit(&ptmp->pt_lock); 534 DBG(("ack PTSSTTY\n")); 535 miocack(qp, mp, 0, 0); 536 break; 537 case ZONEPT: 538 { 539 zoneid_t z; 540 int error; 541 542 if ((error = drv_priv(iocp->ioc_cr)) != 0) { 543 miocnak(qp, mp, 0, error); 544 break; 545 } 546 if ((error = miocpullup(mp, sizeof (zoneid_t))) != 0) { 547 miocnak(qp, mp, 0, error); 548 break; 549 } 550 z = *((zoneid_t *)mp->b_cont->b_rptr); 551 if (z < MIN_ZONEID || z > MAX_ZONEID) { 552 miocnak(qp, mp, 0, EINVAL); 553 break; 554 } 555 556 mutex_enter(&ptmp->pt_lock); 557 ptmp->pt_zoneid = z; 558 mutex_exit(&ptmp->pt_lock); 559 miocack(qp, mp, 0, 0); 560 break; 561 } 562 case OWNERPT: 563 { 564 pt_own_t *ptop; 565 int error; 566 zone_t *zone; 567 568 if ((error = miocpullup(mp, sizeof (pt_own_t))) != 0) { 569 miocnak(qp, mp, 0, error); 570 break; 571 } 572 573 zone = zone_find_by_id(ptmp->pt_zoneid); 574 ptop = (pt_own_t *)mp->b_cont->b_rptr; 575 576 if (!VALID_UID(ptop->pto_ruid, zone) || 577 !VALID_GID(ptop->pto_rgid, zone)) { 578 zone_rele(zone); 579 miocnak(qp, mp, 0, EINVAL); 580 break; 581 } 582 zone_rele(zone); 583 mutex_enter(&ptmp->pt_lock); 584 ptmp->pt_ruid = ptop->pto_ruid; 585 ptmp->pt_rgid = ptop->pto_rgid; 586 mutex_exit(&ptmp->pt_lock); 587 miocack(qp, mp, 0, 0); 588 break; 589 } 590 } 591 break; 592 593 case M_READ: 594 /* Caused by ldterm - can not pass to subsidiary */ 595 freemsg(mp); 596 break; 597 598 /* 599 * Send other messages to the subsidiary: 600 */ 601 default: 602 if ((ptmp->pt_state & PTLOCK) || (ptmp->pts_rdq == NULL)) { 603 DBG(("got msg. but no subsidiary\n")); 604 mp = mexchange(NULL, mp, 2, M_ERROR, -1); 605 if (mp != NULL) { 606 mp->b_rptr[0] = NOERROR; 607 mp->b_rptr[1] = EINVAL; 608 qreply(qp, mp); 609 } 610 PT_EXIT_READ(ptmp); 611 return (0); 612 } 613 DBG(("put msg on manager's write queue\n")); 614 (void) putq(qp, mp); 615 break; 616 } 617 DBG(("return from ptmwput()\n")); 618 PT_EXIT_READ(ptmp); 619 return (0); 620 } 621 622 623 /* 624 * Enable the write side of the subsidiary. This triggers the subsidiary to 625 * send any messages queued on its write side to the read side of this manager. 626 */ 627 static int 628 ptmrsrv(queue_t *qp) 629 { 630 struct pt_ttys *ptmp; 631 632 DBG(("entering ptmrsrv\n")); 633 ASSERT(qp->q_ptr); 634 635 ptmp = (struct pt_ttys *)qp->q_ptr; 636 PT_ENTER_READ(ptmp); 637 if (ptmp->pts_rdq) { 638 qenable(WR(ptmp->pts_rdq)); 639 } 640 PT_EXIT_READ(ptmp); 641 DBG(("leaving ptmrsrv\n")); 642 return (0); 643 } 644 645 646 /* 647 * If there are messages on this queue that can be sent to subsidiary, send 648 * them via putnext(). Otherwise, if queued messages cannot be sent, leave 649 * them on this queue. If priority messages on this queue, send them to the 650 * subsidiary no matter what. 651 */ 652 static int 653 ptmwsrv(queue_t *qp) 654 { 655 struct pt_ttys *ptmp; 656 mblk_t *mp; 657 658 DBG(("entering ptmwsrv\n")); 659 ASSERT(qp->q_ptr); 660 661 ptmp = (struct pt_ttys *)qp->q_ptr; 662 663 if ((mp = getq(qp)) == NULL) { 664 /* If there are no messages there's nothing to do. */ 665 DBG(("leaving ptmwsrv (no messages)\n")); 666 return (0); 667 } 668 669 PT_ENTER_READ(ptmp); 670 if ((ptmp->pt_state & PTLOCK) || (ptmp->pts_rdq == NULL)) { 671 DBG(("in manager write srv proc but no subsidiary\n")); 672 /* 673 * Free messages on the write queue and send 674 * NAK for any M_IOCTL type messages to wakeup 675 * the user process waiting for ACK/NAK from 676 * the ioctl invocation 677 */ 678 do { 679 if (mp->b_datap->db_type == M_IOCTL) 680 miocnak(qp, mp, 0, EINVAL); 681 else 682 freemsg(mp); 683 } while ((mp = getq(qp)) != NULL); 684 flushq(qp, FLUSHALL); 685 686 mp = mexchange(NULL, NULL, 2, M_ERROR, -1); 687 if (mp != NULL) { 688 mp->b_rptr[0] = NOERROR; 689 mp->b_rptr[1] = EINVAL; 690 qreply(qp, mp); 691 } 692 PT_EXIT_READ(ptmp); 693 return (0); 694 } 695 /* 696 * While there are messages on this write queue... 697 */ 698 do { 699 /* 700 * If this is not a control message, and we cannot put messages 701 * on the subsidiary's read queue, put it back on this queue. 702 */ 703 if (mp->b_datap->db_type <= QPCTL && 704 !bcanputnext(ptmp->pts_rdq, mp->b_band)) { 705 DBG(("put msg. back on queue\n")); 706 (void) putbq(qp, mp); 707 break; 708 } 709 /* 710 * Otherwise send the message up subsidiary's stream 711 */ 712 DBG(("send message to subsidiary\n")); 713 putnext(ptmp->pts_rdq, mp); 714 } while ((mp = getq(qp)) != NULL); 715 DBG(("leaving ptmwsrv\n")); 716 PT_EXIT_READ(ptmp); 717 return (0); 718 } 719