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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * Zone Console Driver. 30 * 31 * This driver, derived from the pts/ptm drivers, is the pseudo console driver 32 * for system zones. Its implementation is straightforward. Each instance 33 * of the driver represents a global-zone/local-zone pair (this maps in a 34 * straightforward way to the commonly used terminal notion of "master side" 35 * and "slave side", and we use that terminology throughout). 36 * 37 * Instances of zcons are onlined as children of /pseudo/zconsnex@1/ 38 * by zoneadmd in userland, using the devctl framework; thus the driver 39 * does not need to maintain any sort of "admin" node. 40 * 41 * The driver shuttles I/O from master side to slave side and back. In a break 42 * from the pts/ptm semantics, if one side is not open, I/O directed towards 43 * it will simply be discarded. This is so that if zoneadmd is not holding 44 * the master side console open (i.e. it has died somehow), processes in 45 * the zone do not experience any errors and I/O to the console does not 46 * hang. 47 * 48 * TODO: we may want to revisit the other direction; i.e. we may want 49 * zoneadmd to be able to detect whether no zone processes are holding the 50 * console open, an unusual situation. 51 */ 52 53 #include <sys/types.h> 54 #include <sys/cmn_err.h> 55 #include <sys/conf.h> 56 #include <sys/cred.h> 57 #include <sys/ddi.h> 58 #include <sys/debug.h> 59 #include <sys/devops.h> 60 #include <sys/errno.h> 61 #include <sys/file.h> 62 #include <sys/modctl.h> 63 #include <sys/param.h> 64 #include <sys/stat.h> 65 #include <sys/stream.h> 66 #include <sys/stropts.h> 67 #include <sys/strsun.h> 68 #include <sys/sunddi.h> 69 #include <sys/sysmacros.h> 70 #include <sys/systm.h> 71 #include <sys/types.h> 72 #include <sys/zcons.h> 73 74 static int zc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 75 static int zc_attach(dev_info_t *, ddi_attach_cmd_t); 76 static int zc_detach(dev_info_t *, ddi_detach_cmd_t); 77 78 static int zc_open(queue_t *, dev_t *, int, int, cred_t *); 79 static int zc_close(queue_t *, int, cred_t *); 80 static void zc_wput(queue_t *, mblk_t *); 81 static void zc_rsrv(queue_t *); 82 static void zc_wsrv(queue_t *); 83 84 /* 85 * The instance number is encoded in the dev_t in the minor number; the lowest 86 * bit of the minor number is used to track the master vs. slave side of the 87 * virtual console. The rest of the bits in the minor number are the instance. 88 */ 89 #define ZC_MASTER_MINOR 0 90 #define ZC_SLAVE_MINOR 1 91 92 #define ZC_INSTANCE(x) (getminor((x)) >> 1) 93 #define ZC_NODE(x) (getminor((x)) & 0x01) 94 95 int zcons_debug = 0; 96 #define DBG(a) if (zcons_debug) cmn_err(CE_NOTE, a) 97 #define DBG1(a, b) if (zcons_debug) cmn_err(CE_NOTE, a, b) 98 99 100 /* 101 * Zone Console Pseudo Terminal Module: stream data structure definitions 102 */ 103 static struct module_info zc_info = { 104 31337, /* c0z we r hAx0rs */ 105 "zcons", 106 0, 107 INFPSZ, 108 2048, 109 128 110 }; 111 112 static struct qinit zc_rinit = { 113 NULL, 114 (int (*)()) zc_rsrv, 115 zc_open, 116 zc_close, 117 NULL, 118 &zc_info, 119 NULL 120 }; 121 122 static struct qinit zc_winit = { 123 (int (*)()) zc_wput, 124 (int (*)()) zc_wsrv, 125 NULL, 126 NULL, 127 NULL, 128 &zc_info, 129 NULL 130 }; 131 132 static struct streamtab zc_tab_info = { 133 &zc_rinit, 134 &zc_winit, 135 NULL, 136 NULL 137 }; 138 139 #define ZC_CONF_FLAG (D_MP | D_MTQPAIR | D_MTOUTPERIM | D_MTOCEXCL) 140 141 /* 142 * this will define (struct cb_ops cb_zc_ops) and (struct dev_ops zc_ops) 143 */ 144 DDI_DEFINE_STREAM_OPS(zc_ops, nulldev, nulldev, zc_attach, zc_detach, nodev, \ 145 zc_getinfo, ZC_CONF_FLAG, &zc_tab_info); 146 147 /* 148 * Module linkage information for the kernel. 149 */ 150 151 static struct modldrv modldrv = { 152 &mod_driverops, /* Type of module. This one is a pseudo driver */ 153 "Zone console driver 'zcons' %I%", 154 &zc_ops /* driver ops */ 155 }; 156 157 static struct modlinkage modlinkage = { 158 MODREV_1, 159 &modldrv, 160 NULL 161 }; 162 163 typedef struct zc_state { 164 dev_info_t *zc_devinfo; 165 queue_t *zc_master_rdq; 166 queue_t *zc_slave_rdq; 167 int zc_state; 168 } zc_state_t; 169 170 #define ZC_STATE_MOPEN 0x01 171 #define ZC_STATE_SOPEN 0x02 172 173 static void *zc_soft_state; 174 175 int 176 _init(void) 177 { 178 int err; 179 180 if ((err = ddi_soft_state_init(&zc_soft_state, 181 sizeof (zc_state_t), 0)) != 0) { 182 return (err); 183 } 184 185 if ((err = mod_install(&modlinkage)) != 0) 186 ddi_soft_state_fini(zc_soft_state); 187 188 return (err); 189 } 190 191 192 int 193 _fini(void) 194 { 195 int err; 196 197 if ((err = mod_remove(&modlinkage)) != 0) { 198 return (err); 199 } 200 201 ddi_soft_state_fini(&zc_soft_state); 202 return (0); 203 } 204 205 int 206 _info(struct modinfo *modinfop) 207 { 208 return (mod_info(&modlinkage, modinfop)); 209 } 210 211 static int 212 zc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 213 { 214 zc_state_t *zcs; 215 int instance; 216 217 if (cmd != DDI_ATTACH) 218 return (DDI_FAILURE); 219 220 instance = ddi_get_instance(dip); 221 if (ddi_soft_state_zalloc(zc_soft_state, instance) != DDI_SUCCESS) 222 return (DDI_FAILURE); 223 224 if ((ddi_create_minor_node(dip, ZCONS_SLAVE_NAME, S_IFCHR, 225 instance << 1 | ZC_SLAVE_MINOR, DDI_PSEUDO, 0) == DDI_FAILURE) || 226 (ddi_create_minor_node(dip, ZCONS_MASTER_NAME, S_IFCHR, 227 instance << 1 | ZC_MASTER_MINOR, DDI_PSEUDO, 0) == DDI_FAILURE)) { 228 ddi_remove_minor_node(dip, NULL); 229 ddi_soft_state_free(zc_soft_state, instance); 230 return (DDI_FAILURE); 231 } 232 233 if ((zcs = ddi_get_soft_state(zc_soft_state, instance)) == NULL) { 234 ddi_remove_minor_node(dip, NULL); 235 ddi_soft_state_free(zc_soft_state, instance); 236 return (DDI_FAILURE); 237 } 238 zcs->zc_devinfo = dip; 239 240 return (DDI_SUCCESS); 241 } 242 243 static int 244 zc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 245 { 246 zc_state_t *zcs; 247 int instance; 248 249 if (cmd != DDI_DETACH) 250 return (DDI_FAILURE); 251 252 instance = ddi_get_instance(dip); 253 if ((zcs = ddi_get_soft_state(zc_soft_state, instance)) == NULL) 254 return (DDI_FAILURE); 255 256 if ((zcs->zc_state & ZC_STATE_MOPEN) || 257 (zcs->zc_state & ZC_STATE_SOPEN)) { 258 DBG1("zc_detach: device (dip=%p) still open\n", (void *)dip); 259 return (DDI_FAILURE); 260 } 261 262 ddi_remove_minor_node(dip, NULL); 263 ddi_soft_state_free(zc_soft_state, instance); 264 265 return (DDI_SUCCESS); 266 } 267 268 /* 269 * zc_getinfo() 270 * getinfo(9e) entrypoint. 271 */ 272 /*ARGSUSED*/ 273 static int 274 zc_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) 275 { 276 zc_state_t *zcs; 277 int instance = ZC_INSTANCE((dev_t)arg); 278 279 switch (infocmd) { 280 case DDI_INFO_DEVT2DEVINFO: 281 if ((zcs = ddi_get_soft_state(zc_soft_state, instance)) == NULL) 282 return (DDI_FAILURE); 283 *result = zcs->zc_devinfo; 284 return (DDI_SUCCESS); 285 case DDI_INFO_DEVT2INSTANCE: 286 *result = (void *)(uintptr_t)instance; 287 return (DDI_SUCCESS); 288 } 289 return (DDI_FAILURE); 290 } 291 292 /* 293 * Return the equivalent queue from the other side of the relationship. 294 * e.g.: given the slave's write queue, return the master's write queue. 295 */ 296 static queue_t * 297 zc_switch(queue_t *qp) 298 { 299 zc_state_t *zcs = qp->q_ptr; 300 ASSERT(zcs != NULL); 301 302 if (qp == zcs->zc_master_rdq) 303 return (zcs->zc_slave_rdq); 304 else if (OTHERQ(qp) == zcs->zc_master_rdq && zcs->zc_slave_rdq != NULL) 305 return (OTHERQ(zcs->zc_slave_rdq)); 306 else if (qp == zcs->zc_slave_rdq) 307 return (zcs->zc_master_rdq); 308 else if (OTHERQ(qp) == zcs->zc_slave_rdq && zcs->zc_master_rdq != NULL) 309 return (OTHERQ(zcs->zc_master_rdq)); 310 else 311 return (NULL); 312 } 313 314 /* 315 * For debugging and outputting messages. Returns the name of the side of 316 * the relationship associated with this queue. 317 */ 318 static const char * 319 zc_side(queue_t *qp) 320 { 321 zc_state_t *zcs = qp->q_ptr; 322 ASSERT(zcs != NULL); 323 324 if (qp == zcs->zc_master_rdq || 325 OTHERQ(qp) == zcs->zc_master_rdq) { 326 return ("master"); 327 } 328 ASSERT(qp == zcs->zc_slave_rdq || OTHERQ(qp) == zcs->zc_slave_rdq); 329 return ("slave"); 330 } 331 332 /*ARGSUSED*/ 333 static int 334 zc_master_open(zc_state_t *zcs, 335 queue_t *rqp, /* pointer to the read side queue */ 336 dev_t *devp, /* pointer to stream tail's dev */ 337 int oflag, /* the user open(2) supplied flags */ 338 int sflag, /* open state flag */ 339 cred_t *credp) /* credentials */ 340 { 341 mblk_t *mop; 342 struct stroptions *sop; 343 344 /* 345 * Enforce exclusivity on the master side; the only consumer should 346 * be the zoneadmd for the zone. 347 */ 348 if ((zcs->zc_state & ZC_STATE_MOPEN) != 0) 349 return (EBUSY); 350 351 if ((mop = allocb(sizeof (struct stroptions), BPRI_MED)) == NULL) { 352 DBG("zc_master_open(): mop allocation failed\n"); 353 return (ENOMEM); 354 } 355 356 zcs->zc_state |= ZC_STATE_MOPEN; 357 358 /* 359 * q_ptr stores driver private data; stash the soft state data on both 360 * read and write sides of the queue. 361 */ 362 WR(rqp)->q_ptr = rqp->q_ptr = zcs; 363 qprocson(rqp); 364 365 /* 366 * Following qprocson(), the master side is fully plumbed into the 367 * STREAM and may send/receive messages. Setting zcs->zc_master_rdq 368 * will allow the slave to send messages to us (the master). 369 * This cannot occur before qprocson() because the master is not 370 * ready to process them until that point. 371 */ 372 zcs->zc_master_rdq = rqp; 373 374 /* 375 * set up hi/lo water marks on stream head read queue and add 376 * controlling tty as needed. 377 */ 378 mop->b_datap->db_type = M_SETOPTS; 379 mop->b_wptr += sizeof (struct stroptions); 380 sop = (struct stroptions *)(void *)mop->b_rptr; 381 if (oflag & FNOCTTY) 382 sop->so_flags = SO_HIWAT | SO_LOWAT; 383 else 384 sop->so_flags = SO_HIWAT | SO_LOWAT | SO_ISTTY; 385 sop->so_hiwat = 512; 386 sop->so_lowat = 256; 387 putnext(rqp, mop); 388 389 return (0); 390 } 391 392 /*ARGSUSED*/ 393 static int 394 zc_slave_open(zc_state_t *zcs, 395 queue_t *rqp, /* pointer to the read side queue */ 396 dev_t *devp, /* pointer to stream tail's dev */ 397 int oflag, /* the user open(2) supplied flags */ 398 int sflag, /* open state flag */ 399 cred_t *credp) /* credentials */ 400 { 401 mblk_t *mop; 402 struct stroptions *sop; 403 404 /* 405 * The slave side can be opened as many times as needed. 406 */ 407 if ((zcs->zc_state & ZC_STATE_SOPEN) != 0) { 408 ASSERT((rqp != NULL) && (WR(rqp)->q_ptr == zcs)); 409 return (0); 410 } 411 412 if ((mop = allocb(sizeof (struct stroptions), BPRI_MED)) == NULL) { 413 DBG("zc_slave_open(): mop allocation failed\n"); 414 return (ENOMEM); 415 } 416 417 zcs->zc_state |= ZC_STATE_SOPEN; 418 419 /* 420 * q_ptr stores driver private data; stash the soft state data on both 421 * read and write sides of the queue. 422 */ 423 WR(rqp)->q_ptr = rqp->q_ptr = zcs; 424 425 qprocson(rqp); 426 427 /* 428 * Must follow qprocson(), since we aren't ready to process until then. 429 */ 430 zcs->zc_slave_rdq = rqp; 431 432 /* 433 * set up hi/lo water marks on stream head read queue and add 434 * controlling tty as needed. 435 */ 436 mop->b_datap->db_type = M_SETOPTS; 437 mop->b_wptr += sizeof (struct stroptions); 438 sop = (struct stroptions *)(void *)mop->b_rptr; 439 sop->so_flags = SO_HIWAT | SO_LOWAT | SO_ISTTY; 440 sop->so_hiwat = 512; 441 sop->so_lowat = 256; 442 putnext(rqp, mop); 443 444 return (0); 445 } 446 447 /* 448 * open(9e) entrypoint; checks sflag, and rejects anything unordinary. 449 */ 450 static int 451 zc_open(queue_t *rqp, /* pointer to the read side queue */ 452 dev_t *devp, /* pointer to stream tail's dev */ 453 int oflag, /* the user open(2) supplied flags */ 454 int sflag, /* open state flag */ 455 cred_t *credp) /* credentials */ 456 { 457 int instance = ZC_INSTANCE(*devp); 458 int ret; 459 zc_state_t *zcs; 460 461 if (sflag != 0) 462 return (EINVAL); 463 464 if ((zcs = ddi_get_soft_state(zc_soft_state, instance)) == NULL) 465 return (ENXIO); 466 467 switch (ZC_NODE(*devp)) { 468 case ZC_MASTER_MINOR: 469 ret = zc_master_open(zcs, rqp, devp, oflag, sflag, credp); 470 break; 471 case ZC_SLAVE_MINOR: 472 ret = zc_slave_open(zcs, rqp, devp, oflag, sflag, credp); 473 break; 474 default: 475 ret = ENXIO; 476 break; 477 } 478 479 return (ret); 480 } 481 482 /* 483 * close(9e) entrypoint. 484 */ 485 /*ARGSUSED1*/ 486 static int 487 zc_close(queue_t *rqp, int flag, cred_t *credp) 488 { 489 queue_t *wqp; 490 mblk_t *bp; 491 zc_state_t *zcs; 492 493 zcs = (zc_state_t *)rqp->q_ptr; 494 495 if (rqp == zcs->zc_master_rdq) { 496 DBG("Closing master side"); 497 498 zcs->zc_master_rdq = NULL; 499 zcs->zc_state &= ~ZC_STATE_MOPEN; 500 501 /* 502 * qenable slave side write queue so that it can flush 503 * its messages as master's read queue is going away 504 */ 505 if (zcs->zc_slave_rdq != NULL) { 506 qenable(WR(zcs->zc_slave_rdq)); 507 } 508 509 qprocsoff(rqp); 510 WR(rqp)->q_ptr = rqp->q_ptr = NULL; 511 512 } else if (rqp == zcs->zc_slave_rdq) { 513 514 DBG("Closing slave side"); 515 zcs->zc_state &= ~ZC_STATE_SOPEN; 516 zcs->zc_slave_rdq = NULL; 517 518 wqp = WR(rqp); 519 while ((bp = getq(wqp)) != NULL) { 520 if (zcs->zc_master_rdq != NULL) 521 putnext(zcs->zc_master_rdq, bp); 522 else if (bp->b_datap->db_type == M_IOCTL) 523 miocnak(wqp, bp, 0, 0); 524 else 525 freemsg(bp); 526 } 527 528 /* 529 * Qenable master side write queue so that it can flush its 530 * messages as slaves's read queue is going away. 531 */ 532 if (zcs->zc_master_rdq != NULL) 533 qenable(WR(zcs->zc_master_rdq)); 534 535 qprocsoff(rqp); 536 WR(rqp)->q_ptr = rqp->q_ptr = NULL; 537 } 538 539 return (0); 540 } 541 542 static void 543 handle_mflush(queue_t *qp, mblk_t *mp) 544 { 545 mblk_t *nmp; 546 DBG1("M_FLUSH on %s side", zc_side(qp)); 547 548 if (*mp->b_rptr & FLUSHW) { 549 DBG1("M_FLUSH, FLUSHW, %s side", zc_side(qp)); 550 flushq(qp, FLUSHDATA); 551 *mp->b_rptr &= ~FLUSHW; 552 if ((*mp->b_rptr & FLUSHR) == 0) { 553 /* 554 * FLUSHW only. Change to FLUSHR and putnext other side, 555 * then we are done. 556 */ 557 *mp->b_rptr |= FLUSHR; 558 if (zc_switch(RD(qp)) != NULL) { 559 putnext(zc_switch(RD(qp)), mp); 560 return; 561 } 562 } else if ((zc_switch(RD(qp)) != NULL) && 563 (nmp = copyb(mp)) != NULL) { 564 /* 565 * It is a FLUSHRW; we copy the mblk and send 566 * it to the other side, since we still need to use 567 * the mblk in FLUSHR processing, below. 568 */ 569 putnext(zc_switch(RD(qp)), nmp); 570 } 571 } 572 573 if (*mp->b_rptr & FLUSHR) { 574 DBG("qreply(qp) turning FLUSHR around\n"); 575 qreply(qp, mp); 576 return; 577 } 578 freemsg(mp); 579 } 580 581 /* 582 * wput(9E) is symmetric for master and slave sides, so this handles both 583 * without splitting the codepath. 584 * 585 * zc_wput() looks at the other side; if there is no process holding that 586 * side open, it frees the message. This prevents processes from hanging 587 * if no one is holding open the console. Otherwise, it putnext's high 588 * priority messages, putnext's normal messages if possible, and otherwise 589 * enqueues the messages; in the case that something is enqueued, wsrv(9E) 590 * will take care of eventually shuttling I/O to the other side. 591 */ 592 static void 593 zc_wput(queue_t *qp, mblk_t *mp) 594 { 595 unsigned char type = mp->b_datap->db_type; 596 597 ASSERT(qp->q_ptr); 598 599 DBG1("entering zc_wput, %s side", zc_side(qp)); 600 601 if (zc_switch(RD(qp)) == NULL) { 602 DBG1("wput to %s side (no one listening)", zc_side(qp)); 603 switch (type) { 604 case M_FLUSH: 605 handle_mflush(qp, mp); 606 break; 607 case M_IOCTL: 608 miocnak(qp, mp, 0, 0); 609 break; 610 default: 611 freemsg(mp); 612 break; 613 } 614 return; 615 } 616 617 if (type >= QPCTL) { 618 DBG1("(hipri) wput, %s side", zc_side(qp)); 619 switch (type) { 620 case M_READ: /* supposedly from ldterm? */ 621 DBG("zc_wput: tossing M_READ\n"); 622 freemsg(mp); 623 break; 624 case M_FLUSH: 625 handle_mflush(qp, mp); 626 break; 627 default: 628 /* 629 * Put this to the other side. 630 */ 631 ASSERT(zc_switch(RD(qp)) != NULL); 632 putnext(zc_switch(RD(qp)), mp); 633 break; 634 } 635 DBG1("done (hipri) wput, %s side", zc_side(qp)); 636 return; 637 } 638 639 /* 640 * Only putnext if there isn't already something in the queue. 641 * otherwise things would wind up out of order. 642 */ 643 if (qp->q_first == NULL && bcanputnext(RD(zc_switch(qp)), mp->b_band)) { 644 DBG("wput: putting message to other side\n"); 645 putnext(RD(zc_switch(qp)), mp); 646 } else { 647 DBG("wput: putting msg onto queue\n"); 648 (void) putq(qp, mp); 649 } 650 DBG1("done wput, %s side", zc_side(qp)); 651 } 652 653 /* 654 * rsrv(9E) is symmetric for master and slave, so zc_rsrv() handles both 655 * without splitting up the codepath. 656 * 657 * Enable the write side of the partner. This triggers the partner to send 658 * messages queued on its write side to this queue's read side. 659 */ 660 static void 661 zc_rsrv(queue_t *qp) 662 { 663 zc_state_t *zcs; 664 zcs = (zc_state_t *)qp->q_ptr; 665 666 /* 667 * Care must be taken here, as either of the master or slave side 668 * qptr could be NULL. 669 */ 670 ASSERT(qp == zcs->zc_master_rdq || qp == zcs->zc_slave_rdq); 671 if (zc_switch(qp) == NULL) { 672 DBG("zc_rsrv: other side isn't listening\n"); 673 return; 674 } 675 qenable(WR(zc_switch(qp))); 676 } 677 678 /* 679 * This routine is symmetric for master and slave, so it handles both without 680 * splitting up the codepath. 681 * 682 * If there are messages on this queue that can be sent to the other, send 683 * them via putnext(). Else, if queued messages cannot be sent, leave them 684 * on this queue. 685 */ 686 static void 687 zc_wsrv(queue_t *qp) 688 { 689 mblk_t *mp; 690 691 DBG1("zc_wsrv master (%s) side", zc_side(qp)); 692 693 /* 694 * Partner has no read queue, so take the data, and throw it away. 695 */ 696 if (zc_switch(RD(qp)) == NULL) { 697 DBG("zc_wsrv: other side isn't listening"); 698 while ((mp = getq(qp)) != NULL) { 699 if (mp->b_datap->db_type == M_IOCTL) 700 miocnak(qp, mp, 0, 0); 701 else 702 freemsg(mp); 703 } 704 flushq(qp, FLUSHALL); 705 return; 706 } 707 708 /* 709 * while there are messages on this write queue... 710 */ 711 while ((mp = getq(qp)) != NULL) { 712 /* 713 * Due to the way zc_wput is implemented, we should never 714 * see a control message here. 715 */ 716 ASSERT(mp->b_datap->db_type < QPCTL); 717 718 if (bcanputnext(RD(zc_switch(qp)), mp->b_band)) { 719 DBG("wsrv: send message to other side\n"); 720 putnext(RD(zc_switch(qp)), mp); 721 } else { 722 DBG("wsrv: putting msg back on queue\n"); 723 (void) putbq(qp, mp); 724 break; 725 } 726 } 727 } 728