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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 30 #pragma ident "%Z%%M% %I% %E% SMI" /* SVR4 1.13 */ 31 32 /* 33 * Pseudo Terminal Slave Driver. 34 * 35 * The pseudo-tty subsystem simulates a terminal connection, where the master 36 * side represents the terminal and the slave represents the user process's 37 * special device end point. The master device is set up as a cloned device 38 * where its major device number is the major for the clone device and its minor 39 * device number is the major for the ptm driver. There are no nodes in the file 40 * system for master devices. The master pseudo driver is opened using the 41 * open(2) system call with /dev/ptmx as the device parameter. The clone open 42 * finds the next available minor device for the ptm major device. 43 * 44 * A master device is available only if it and its corresponding slave device 45 * are not already open. When the master device is opened, the corresponding 46 * slave device is automatically locked out. Only one open is allowed on a 47 * master device. Multiple opens are allowed on the slave device. After both 48 * the master and slave have been opened, the user has two file descriptors 49 * which are the end points of a full duplex connection composed of two streams 50 * which are automatically connected at the master and slave drivers. The user 51 * may then push modules onto either side of the stream pair. 52 * 53 * The master and slave drivers pass all messages to their adjacent queues. 54 * Only the M_FLUSH needs some processing. Because the read queue of one side 55 * is connected to the write queue of the other, the FLUSHR flag is changed to 56 * the FLUSHW flag and vice versa. When the master device is closed an M_HANGUP 57 * message is sent to the slave device which will render the device 58 * unusable. The process on the slave side gets the EIO when attempting to write 59 * on that stream but it will be able to read any data remaining on the stream 60 * head read queue. When all the data has been read, read() returns 0 61 * indicating that the stream can no longer be used. On the last close of the 62 * slave device, a 0-length message is sent to the master device. When the 63 * application on the master side issues a read() or getmsg() and 0 is returned, 64 * the user of the master device decides whether to issue a close() that 65 * dismantles the pseudo-terminal subsystem. If the master device is not closed, 66 * the pseudo-tty subsystem will be available to another user to open the slave 67 * device. 68 * 69 * Synchronization: 70 * 71 * All global data synchronization between ptm/pts is done via global 72 * ptms_lock mutex which is initialized at system boot time from 73 * ptms_initspace (called from space.c). 74 * 75 * Individual fields of pt_ttys structure (except ptm_rdq, pts_rdq and 76 * pt_nullmsg) are protected by pt_ttys.pt_lock mutex. 77 * 78 * PT_ENTER_READ/PT_ENTER_WRITE are reference counter based read-write locks 79 * which allow reader locks to be reacquired by the same thread (usual 80 * reader/writer locks can't be used for that purpose since it is illegal for 81 * a thread to acquire a lock it already holds, even as a reader). The sole 82 * purpose of these macros is to guarantee that the peer queue will not 83 * disappear (due to closing peer) while it is used. It is safe to use 84 * PT_ENTER_READ/PT_EXIT_READ brackets across calls like putq/putnext (since 85 * they are not real locks but reference counts). 86 * 87 * PT_ENTER_WRITE/PT_EXIT_WRITE brackets are used ONLY in master/slave 88 * open/close paths to modify ptm_rdq and pts_rdq fields. These fields should 89 * be set to appropriate queues *after* qprocson() is called during open (to 90 * prevent peer from accessing the queue with incomplete plumbing) and set to 91 * NULL before qprocsoff() is called during close. 92 * 93 * The pt_nullmsg field is only used in open/close routines and it is also 94 * protected by PT_ENTER_WRITE/PT_EXIT_WRITE brackets to avoid extra mutex 95 * holds. 96 * 97 * Lock Ordering: 98 * 99 * If both ptms_lock and per-pty lock should be held, ptms_lock should always 100 * be entered first, followed by per-pty lock. 101 * 102 * See ptms.h, ptm.c and ptms_conf.c fore more information. 103 * 104 */ 105 106 #include <sys/types.h> 107 #include <sys/param.h> 108 #include <sys/sysmacros.h> 109 #include <sys/stream.h> 110 #include <sys/stropts.h> 111 #include <sys/stat.h> 112 #include <sys/errno.h> 113 #include <sys/debug.h> 114 #include <sys/cmn_err.h> 115 #include <sys/ptms.h> 116 #include <sys/systm.h> 117 #include <sys/modctl.h> 118 #include <sys/conf.h> 119 #include <sys/ddi.h> 120 #include <sys/sunddi.h> 121 #include <sys/cred.h> 122 #include <sys/zone.h> 123 124 #ifdef DEBUG 125 int pts_debug = 0; 126 #define DBG(a) if (pts_debug) cmn_err(CE_NOTE, a) 127 #else 128 #define DBG(a) 129 #endif 130 131 static int ptsopen(queue_t *, dev_t *, int, int, cred_t *); 132 static int ptsclose(queue_t *, int, cred_t *); 133 static void ptswput(queue_t *, mblk_t *); 134 static void ptsrsrv(queue_t *); 135 static void ptswsrv(queue_t *); 136 137 /* 138 * Slave Stream Pseudo Terminal Module: stream data structure definitions 139 */ 140 static struct module_info pts_info = { 141 0xface, 142 "pts", 143 0, 144 512, 145 512, 146 128 147 }; 148 149 static struct qinit ptsrint = { 150 NULL, 151 (int (*)()) ptsrsrv, 152 ptsopen, 153 ptsclose, 154 NULL, 155 &pts_info, 156 NULL 157 }; 158 159 static struct qinit ptswint = { 160 (int (*)()) ptswput, 161 (int (*)()) ptswsrv, 162 NULL, 163 NULL, 164 NULL, 165 &pts_info, 166 NULL 167 }; 168 169 static struct streamtab ptsinfo = { 170 &ptsrint, 171 &ptswint, 172 NULL, 173 NULL 174 }; 175 176 static int pts_devinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 177 static int pts_attach(dev_info_t *, ddi_attach_cmd_t); 178 static int pts_detach(dev_info_t *, ddi_detach_cmd_t); 179 180 #define PTS_CONF_FLAG (D_NEW | D_MP) 181 182 /* 183 * this will define (struct cb_ops cb_pts_ops) and (struct dev_ops pts_ops) 184 */ 185 DDI_DEFINE_STREAM_OPS(pts_ops, nulldev, nulldev, \ 186 pts_attach, pts_detach, nodev, \ 187 pts_devinfo, PTS_CONF_FLAG, &ptsinfo); 188 189 /* 190 * Module linkage information for the kernel. 191 */ 192 193 static struct modldrv modldrv = { 194 &mod_driverops, /* Type of module. This one is a pseudo driver */ 195 "Slave Stream Pseudo Terminal driver 'pts'", 196 &pts_ops, /* driver ops */ 197 }; 198 199 static struct modlinkage modlinkage = { 200 MODREV_1, 201 &modldrv, 202 NULL 203 }; 204 205 int 206 _init(void) 207 { 208 int rc; 209 210 if ((rc = mod_install(&modlinkage)) == 0) 211 ptms_init(); 212 return (rc); 213 } 214 215 216 int 217 _fini(void) 218 { 219 return (mod_remove(&modlinkage)); 220 } 221 222 int 223 _info(struct modinfo *modinfop) 224 { 225 return (mod_info(&modlinkage, modinfop)); 226 } 227 228 static int 229 pts_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) 230 { 231 if (cmd != DDI_ATTACH) 232 return (DDI_FAILURE); 233 234 return (ptms_create_pts_nodes(devi)); 235 } 236 237 static int 238 pts_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) 239 { 240 if (cmd != DDI_DETACH) 241 return (DDI_FAILURE); 242 243 return (ptms_destroy_pts_nodes(devi)); 244 } 245 246 /*ARGSUSED*/ 247 static int 248 pts_devinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, 249 void **result) 250 { 251 int error; 252 253 switch (infocmd) { 254 case DDI_INFO_DEVT2DEVINFO: 255 if (pts_dip == NULL) { 256 error = DDI_FAILURE; 257 } else { 258 *result = (void *)pts_dip; 259 error = DDI_SUCCESS; 260 } 261 break; 262 case DDI_INFO_DEVT2INSTANCE: 263 *result = (void *)0; 264 error = DDI_SUCCESS; 265 break; 266 default: 267 error = DDI_FAILURE; 268 } 269 return (error); 270 } 271 272 /* ARGSUSED */ 273 /* 274 * Open the slave device. Reject a clone open and do not allow the 275 * driver to be pushed. If the slave/master pair is locked or if 276 * the master is not open, return EACCESS. 277 * Upon success, store the write queue pointer in private data and 278 * set the PTSOPEN bit in the pt_state field. 279 */ 280 static int 281 ptsopen( 282 queue_t *rqp, /* pointer to the read side queue */ 283 dev_t *devp, /* pointer to stream tail's dev */ 284 int oflag, /* the user open(2) supplied flags */ 285 int sflag, /* open state flag */ 286 cred_t *credp) /* credentials */ 287 { 288 struct pt_ttys *ptsp; 289 mblk_t *mp; 290 mblk_t *mop; /* ptr to a setopts message block */ 291 minor_t dminor = getminor(*devp); 292 struct stroptions *sop; 293 294 DDBG("entering ptsopen(%d)", dminor); 295 296 if (sflag != 0) { 297 return (EINVAL); 298 } 299 300 mutex_enter(&ptms_lock); 301 ptsp = ptms_minor2ptty(dminor); 302 303 if (ptsp == NULL) { 304 mutex_exit(&ptms_lock); 305 return (ENXIO); 306 } 307 mutex_enter(&ptsp->pt_lock); 308 309 /* 310 * Prevent opens from zones other than the one blessed by ptm. We 311 * can't even allow the global zone to open all pts's, as it would 312 * otherwise inproperly be able to claim pts's already opened by zones. 313 */ 314 if (ptsp->pt_zoneid != getzoneid()) { 315 mutex_exit(&ptsp->pt_lock); 316 mutex_exit(&ptms_lock); 317 return (EPERM); 318 } 319 320 /* 321 * Allow reopen of this device. 322 */ 323 if (rqp->q_ptr != NULL) { 324 ASSERT(rqp->q_ptr == ptsp); 325 ASSERT(ptsp->pts_rdq == rqp); 326 mutex_exit(&ptsp->pt_lock); 327 mutex_exit(&ptms_lock); 328 return (0); 329 } 330 331 DDBGP("ptsopen: p = %p\n", (uintptr_t)ptsp); 332 DDBG("ptsopen: state = %x\n", ptsp->pt_state); 333 334 335 ASSERT(ptsp->pt_minor == dminor); 336 337 if ((ptsp->pt_state & PTLOCK) || !(ptsp->pt_state & PTMOPEN)) { 338 mutex_exit(&ptsp->pt_lock); 339 mutex_exit(&ptms_lock); 340 return (EAGAIN); 341 } 342 343 /* 344 * if already, open simply return... 345 */ 346 if (ptsp->pt_state & PTSOPEN) { 347 ASSERT(rqp->q_ptr == ptsp); 348 ASSERT(ptsp->pts_rdq == rqp); 349 mutex_exit(&ptsp->pt_lock); 350 mutex_exit(&ptms_lock); 351 return (0); 352 } 353 354 /* 355 * Allocate message block for setting stream head options. 356 */ 357 if ((mop = allocb(sizeof (struct stroptions), BPRI_MED)) == NULL) { 358 mutex_exit(&ptsp->pt_lock); 359 mutex_exit(&ptms_lock); 360 return (ENOMEM); 361 } 362 363 /* 364 * Slave should send zero-length message to a master when it is 365 * closing. If memory is low at that time, master will not detect slave 366 * closes, this pty will not be deallocated. So, preallocate this 367 * zero-length message block early. 368 */ 369 if ((mp = allocb(0, BPRI_MED)) == NULL) { 370 mutex_exit(&ptsp->pt_lock); 371 mutex_exit(&ptms_lock); 372 freemsg(mop); 373 return (ENOMEM); 374 } 375 376 ptsp->pt_state |= PTSOPEN; 377 378 WR(rqp)->q_ptr = rqp->q_ptr = ptsp; 379 380 mutex_exit(&ptsp->pt_lock); 381 mutex_exit(&ptms_lock); 382 383 qprocson(rqp); 384 385 /* 386 * After qprocson pts driver is fully plumbed into the stream and can 387 * send/receive messages. Setting pts_rdq will allow master side to send 388 * messages to the slave. This setting can't occur before qprocson() is 389 * finished because slave is not ready to process them. 390 */ 391 PT_ENTER_WRITE(ptsp); 392 ptsp->pts_rdq = rqp; 393 ASSERT(ptsp->pt_nullmsg == NULL); 394 ptsp->pt_nullmsg = mp; 395 PT_EXIT_WRITE(ptsp); 396 397 /* 398 * set up hi/lo water marks on stream head read queue 399 * and add controlling tty if not set 400 */ 401 402 mop->b_datap->db_type = M_SETOPTS; 403 mop->b_wptr += sizeof (struct stroptions); 404 sop = (struct stroptions *)mop->b_rptr; 405 sop->so_flags = SO_HIWAT | SO_LOWAT | SO_ISTTY; 406 sop->so_hiwat = 512; 407 sop->so_lowat = 256; 408 putnext(rqp, mop); 409 410 return (0); 411 } 412 413 414 415 /* 416 * Find the address to private data identifying the slave's write 417 * queue. Send a 0-length msg up the slave's read queue to designate 418 * the master is closing. Uattach the master from the slave by nulling 419 * out master's write queue field in private data. 420 */ 421 /*ARGSUSED1*/ 422 static int 423 ptsclose(queue_t *rqp, int flag, cred_t *credp) 424 { 425 struct pt_ttys *ptsp; 426 queue_t *wqp; 427 mblk_t *mp; 428 mblk_t *bp; 429 430 /* 431 * q_ptr should never be NULL in the close routine and it is checked in 432 * DEBUG kernel by ASSERT. For non-DEBUG kernel the attempt is made to 433 * behave gracefully. 434 */ 435 ASSERT(rqp->q_ptr != NULL); 436 if (rqp->q_ptr == NULL) { 437 qprocsoff(rqp); 438 return (0); 439 } 440 441 ptsp = (struct pt_ttys *)rqp->q_ptr; 442 443 /* 444 * Slave is going to close and doesn't want any new messages coming 445 * from the master side, so set pts_rdq to NULL. This should be done 446 * before call to qprocsoff() since slave can't process additional 447 * messages from the master after qprocsoff is called. 448 */ 449 PT_ENTER_WRITE(ptsp); 450 mp = ptsp->pt_nullmsg; 451 ptsp->pt_nullmsg = NULL; 452 ptsp->pts_rdq = NULL; 453 PT_EXIT_WRITE(ptsp); 454 455 /* 456 * Drain the ouput 457 */ 458 wqp = WR(rqp); 459 PT_ENTER_READ(ptsp); 460 while ((bp = getq(wqp)) != NULL) { 461 if (ptsp->ptm_rdq) { 462 putnext(ptsp->ptm_rdq, bp); 463 } else if (bp->b_datap->db_type == M_IOCTL) { 464 bp->b_datap->db_type = M_IOCNAK; 465 freemsg(bp->b_cont); 466 bp->b_cont = NULL; 467 qreply(wqp, bp); 468 } else { 469 freemsg(bp); 470 } 471 } 472 /* 473 * qenable master side write queue so that it can flush 474 * its messages as slaves's read queue is going away 475 */ 476 if (ptsp->ptm_rdq) { 477 if (mp) 478 putnext(ptsp->ptm_rdq, mp); 479 else 480 qenable(WR(ptsp->ptm_rdq)); 481 } else 482 freemsg(mp); 483 PT_EXIT_READ(ptsp); 484 485 qprocsoff(rqp); 486 487 rqp->q_ptr = NULL; 488 WR(rqp)->q_ptr = NULL; 489 490 ptms_close(ptsp, PTSOPEN | PTSTTY); 491 492 return (0); 493 } 494 495 496 /* 497 * The wput procedure will only handle flush messages. 498 * All other messages are queued and the write side 499 * service procedure sends them off to the master side. 500 */ 501 static void 502 ptswput(queue_t *qp, mblk_t *mp) 503 { 504 struct pt_ttys *ptsp; 505 struct iocblk *iocp; 506 unsigned char type = mp->b_datap->db_type; 507 508 DBG(("entering ptswput\n")); 509 ASSERT(qp->q_ptr); 510 511 ptsp = (struct pt_ttys *)qp->q_ptr; 512 PT_ENTER_READ(ptsp); 513 if (ptsp->ptm_rdq == NULL) { 514 DBG(("in write put proc but no master\n")); 515 /* 516 * NAK ioctl as slave side read queue is gone. 517 * Or else free the message. 518 */ 519 if (mp->b_datap->db_type == M_IOCTL) { 520 mp->b_datap->db_type = M_IOCNAK; 521 freemsg(mp->b_cont); 522 mp->b_cont = NULL; 523 qreply(qp, mp); 524 } else 525 freemsg(mp); 526 PT_EXIT_READ(ptsp); 527 return; 528 } 529 530 if (type >= QPCTL) { 531 switch (type) { 532 533 /* 534 * if write queue request, flush slave's write 535 * queue and send FLUSHR to ptm. If read queue 536 * request, send FLUSHR to ptm. 537 */ 538 case M_FLUSH: 539 DBG(("pts got flush request\n")); 540 if (*mp->b_rptr & FLUSHW) { 541 542 DBG(("got FLUSHW, flush pts write Q\n")); 543 if (*mp->b_rptr & FLUSHBAND) 544 /* 545 * if it is a FLUSHBAND, do flushband. 546 */ 547 flushband(qp, *(mp->b_rptr + 1), FLUSHDATA); 548 else 549 flushq(qp, FLUSHDATA); 550 551 *mp->b_rptr &= ~FLUSHW; 552 if ((*mp->b_rptr & FLUSHR) == 0) { 553 /* 554 * FLUSHW only. Change to FLUSHR and putnext 555 * to ptm, then we are done. 556 */ 557 *mp->b_rptr |= FLUSHR; 558 if (ptsp->ptm_rdq) 559 putnext(ptsp->ptm_rdq, mp); 560 break; 561 } else { 562 mblk_t *nmp; 563 564 /* It is a FLUSHRW. Duplicate the mblk */ 565 nmp = copyb(mp); 566 if (nmp) { 567 /* 568 * Change FLUSHW to FLUSHR before 569 * putnext to ptm. 570 */ 571 DBG(("putnext nmp(FLUSHR) to ptm\n")); 572 *nmp->b_rptr |= FLUSHR; 573 if (ptsp->ptm_rdq) 574 putnext(ptsp->ptm_rdq, nmp); 575 } 576 } 577 } 578 /* 579 * Since the packet module will toss any 580 * M_FLUSHES sent to the master's stream head 581 * read queue, we simply turn it around here. 582 */ 583 if (*mp->b_rptr & FLUSHR) { 584 ASSERT(RD(qp)->q_first == NULL); 585 DBG(("qreply(qp) turning FLUSHR around\n")); 586 qreply(qp, mp); 587 } else { 588 freemsg(mp); 589 } 590 break; 591 592 case M_READ: 593 /* Caused by ldterm - can not pass to master */ 594 freemsg(mp); 595 break; 596 597 default: 598 if (ptsp->ptm_rdq) 599 putnext(ptsp->ptm_rdq, mp); 600 break; 601 } 602 PT_EXIT_READ(ptsp); 603 return; 604 } 605 606 switch (type) { 607 608 case M_IOCTL: 609 /* 610 * For case PTSSTTY set the flag PTSTTY and ACK 611 * the ioctl so that the user program can push 612 * the associated modules to get tty semantics. 613 * See bugid 4025044 614 */ 615 iocp = (struct iocblk *)mp->b_rptr; 616 switch (iocp->ioc_cmd) { 617 default: 618 break; 619 620 case PTSSTTY: 621 if (ptsp->pt_state & PTSTTY) { 622 mp->b_datap->db_type = M_IOCNAK; 623 iocp->ioc_error = EEXIST; 624 } else { 625 mp->b_datap->db_type = M_IOCACK; 626 mutex_enter(&ptsp->pt_lock); 627 ptsp->pt_state |= PTSTTY; 628 mutex_exit(&ptsp->pt_lock); 629 iocp->ioc_error = 0; 630 } 631 iocp->ioc_count = 0; 632 qreply(qp, mp); 633 PT_EXIT_READ(ptsp); 634 return; 635 } 636 637 default: 638 /* 639 * send other messages to the master 640 */ 641 DBG(("put msg on slave's write queue\n")); 642 (void) putq(qp, mp); 643 break; 644 } 645 646 PT_EXIT_READ(ptsp); 647 DBG(("return from ptswput()\n")); 648 } 649 650 651 /* 652 * enable the write side of the master. This triggers the 653 * master to send any messages queued on its write side to 654 * the read side of this slave. 655 */ 656 static void 657 ptsrsrv(queue_t *qp) 658 { 659 struct pt_ttys *ptsp; 660 661 DBG(("entering ptsrsrv\n")); 662 ASSERT(qp->q_ptr); 663 664 ptsp = (struct pt_ttys *)qp->q_ptr; 665 PT_ENTER_READ(ptsp); 666 if (ptsp->ptm_rdq == NULL) { 667 DBG(("in read srv proc but no master\n")); 668 PT_EXIT_READ(ptsp); 669 return; 670 } 671 qenable(WR(ptsp->ptm_rdq)); 672 PT_EXIT_READ(ptsp); 673 DBG(("leaving ptsrsrv\n")); 674 } 675 676 /* 677 * If there are messages on this queue that can be sent to 678 * master, send them via putnext(). Else, if queued messages 679 * cannot be sent, leave them on this queue. If priority 680 * messages on this queue, send them to master no matter what. 681 */ 682 static void 683 ptswsrv(queue_t *qp) 684 { 685 struct pt_ttys *ptsp; 686 queue_t *ptm_rdq; 687 mblk_t *mp; 688 689 DBG(("entering ptswsrv\n")); 690 ASSERT(qp->q_ptr); 691 692 ptsp = (struct pt_ttys *)qp->q_ptr; 693 PT_ENTER_READ(ptsp); 694 if (ptsp->ptm_rdq == NULL) { 695 DBG(("in write srv proc but no master\n")); 696 /* 697 * Free messages on the write queue and send 698 * NAK for any M_IOCTL type messages to wakeup 699 * the user process waiting for ACK/NAK from 700 * the ioctl invocation 701 */ 702 while ((mp = getq(qp)) != NULL) { 703 if (mp->b_datap->db_type == M_IOCTL) { 704 mp->b_datap->db_type = M_IOCNAK; 705 freemsg(mp->b_cont); 706 mp->b_cont = NULL; 707 qreply(qp, mp); 708 } else 709 freemsg(mp); 710 } 711 PT_EXIT_READ(ptsp); 712 return; 713 } else { 714 ptm_rdq = ptsp->ptm_rdq; 715 } 716 717 /* 718 * while there are messages on this write queue... 719 */ 720 while ((mp = getq(qp)) != NULL) { 721 /* 722 * if don't have control message and cannot put 723 * msg. on master's read queue, put it back on 724 * this queue. 725 */ 726 if (mp->b_datap->db_type <= QPCTL && 727 !bcanputnext(ptm_rdq, mp->b_band)) { 728 DBG(("put msg. back on Q\n")); 729 (void) putbq(qp, mp); 730 break; 731 } 732 /* 733 * else send the message up master's stream 734 */ 735 DBG(("send message to master\n")); 736 putnext(ptm_rdq, mp); 737 } 738 DBG(("leaving ptswsrv\n")); 739 PT_EXIT_READ(ptsp); 740 } 741