/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Description: logindmux.c * * The logindmux driver is used with login modules (like telmod/rlmod). * This is a 1x1 cloning mux and two of these muxes are used. The lower link * of one of the muxes receives input from net and the lower link of the * other mux receives input from pseudo terminal subsystem. * * The logdmux_qexch_lock mutex manages the race between LOGDMX_IOC_QEXCHANGE, * logdmuxunlink() and logdmuxclose(), so that the instance selected as a peer * in LOGDMX_IOC_QEXCHANGE cannot be unlinked or closed until the qexchange * is complete; see the inline comments in the code for details. * * The logdmux_peerq_lock mutex manages the race between logdmuxlwsrv() and * logdmuxlrput() (when null'ing tmxp->peerq during LOGDMUX_UNLINK_REQ * processing). * * The logdmux_minor_lock mutex serializes the growth of logdmux_minor_arena * (the arena is grown gradually rather than allocated all at once so that * minor numbers are recycled sooner; for simplicity it is never shrunk). * * The unlink operation is implemented using protocol messages that flow * between the two logindmux peer instances. The instance processing the * I_UNLINK ioctl will send a LOGDMUX_UNLINK_REQ protocol message to its * peer to indicate that it wishes to unlink; the peer will process this * message in its lrput, null its tmxp->peerq and then send a * LOGDMUX_UNLINK_RESP protocol message in reply to indicate that the * unlink can proceed; having received the reply in its lrput, the * instance processing the I_UNLINK can then continue. To ensure that only * one of the peer instances will be actively processing an I_UNLINK at * any one time, a single structure (an unlinkinfo_t containing a mutex, * state variable and pointer to an M_CTL mblk) is allocated during * the processing of the LOGDMX_IOC_QEXCHANGE ioctl. The two instances, if * trying to unlink simultaneously, will race to get control of this * structure which contains the resources necessary to process the * I_UNLINK. The instance that wins this race will be able to continue * with the unlink whilst the other instance will be obliged to wait. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int logdmuxopen(queue_t *, dev_t *, int, int, cred_t *); static int logdmuxclose(queue_t *, int, cred_t *); static int logdmuxursrv(queue_t *); static int logdmuxuwput(queue_t *, mblk_t *); static int logdmuxlrput(queue_t *, mblk_t *); static int logdmuxlrsrv(queue_t *); static int logdmuxlwsrv(queue_t *); static int logdmuxuwsrv(queue_t *); static int logdmux_alloc_unlinkinfo(struct tmx *, struct tmx *); static void logdmuxlink(queue_t *, mblk_t *); static void logdmuxunlink(queue_t *, mblk_t *); static void logdmux_finish_unlink(queue_t *, mblk_t *); static void logdmux_unlink_timer(void *arg); static void recover(queue_t *, mblk_t *, size_t); static void flushq_dataonly(queue_t *); static kmutex_t logdmux_qexch_lock; static kmutex_t logdmux_peerq_lock; static kmutex_t logdmux_minor_lock; static minor_t logdmux_maxminor = 256; /* grown as necessary */ static vmem_t *logdmux_minor_arena; static void *logdmux_statep; static struct module_info logdmuxm_info = { LOGDMX_ID, "logindmux", 0, 256, 512, 256 }; static struct qinit logdmuxurinit = { NULL, logdmuxursrv, logdmuxopen, logdmuxclose, NULL, &logdmuxm_info }; static struct qinit logdmuxuwinit = { logdmuxuwput, logdmuxuwsrv, NULL, NULL, NULL, &logdmuxm_info }; static struct qinit logdmuxlrinit = { logdmuxlrput, logdmuxlrsrv, NULL, NULL, NULL, &logdmuxm_info }; static struct qinit logdmuxlwinit = { NULL, logdmuxlwsrv, NULL, NULL, NULL, &logdmuxm_info }; struct streamtab logdmuxinfo = { &logdmuxurinit, &logdmuxuwinit, &logdmuxlrinit, &logdmuxlwinit }; static int logdmux_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static int logdmux_attach(dev_info_t *, ddi_attach_cmd_t); static int logdmux_detach(dev_info_t *, ddi_detach_cmd_t); static dev_info_t *logdmux_dip; DDI_DEFINE_STREAM_OPS(logdmux_ops, nulldev, nulldev, logdmux_attach, logdmux_detach, nulldev, logdmux_info, D_MP | D_MTPERQ, &logdmuxinfo, ddi_quiesce_not_needed); static struct modldrv modldrv = { &mod_driverops, "logindmux driver", &logdmux_ops }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init(void) { int ret; mutex_init(&logdmux_peerq_lock, NULL, MUTEX_DRIVER, NULL); mutex_init(&logdmux_qexch_lock, NULL, MUTEX_DRIVER, NULL); if ((ret = mod_install(&modlinkage)) != 0) { mutex_destroy(&logdmux_peerq_lock); mutex_destroy(&logdmux_qexch_lock); return (ret); } logdmux_minor_arena = vmem_create("logdmux_minor", (void *)1, logdmux_maxminor, 1, NULL, NULL, NULL, 0, VM_SLEEP | VMC_IDENTIFIER); (void) ddi_soft_state_init(&logdmux_statep, sizeof (struct tmx), 1); return (0); } int _fini(void) { int ret; if ((ret = mod_remove(&modlinkage)) == 0) { mutex_destroy(&logdmux_peerq_lock); mutex_destroy(&logdmux_qexch_lock); ddi_soft_state_fini(&logdmux_statep); vmem_destroy(logdmux_minor_arena); logdmux_minor_arena = NULL; } return (ret); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } static int logdmux_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { if (cmd != DDI_ATTACH) return (DDI_FAILURE); if (ddi_create_minor_node(devi, "logindmux", S_IFCHR, 0, DDI_PSEUDO, CLONE_DEV) == DDI_FAILURE) return (DDI_FAILURE); logdmux_dip = devi; return (DDI_SUCCESS); } static int logdmux_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); ddi_remove_minor_node(devi, NULL); return (DDI_SUCCESS); } /* ARGSUSED */ static int logdmux_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { int error; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if (logdmux_dip == NULL) { error = DDI_FAILURE; } else { *result = logdmux_dip; error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)0; error = DDI_SUCCESS; break; default: error = DDI_FAILURE; } return (error); } /* * Logindmux open routine */ /*ARGSUSED*/ static int logdmuxopen(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp) { struct tmx *tmxp; minor_t minor, omaxminor; if (sflag != CLONEOPEN) return (EINVAL); mutex_enter(&logdmux_minor_lock); if (vmem_size(logdmux_minor_arena, VMEM_FREE) == 0) { /* * The arena has been exhausted; grow by powers of two * up to MAXMIN; bail if we've run out of minors. */ if (logdmux_maxminor == MAXMIN) { mutex_exit(&logdmux_minor_lock); return (ENOMEM); } omaxminor = logdmux_maxminor; logdmux_maxminor = MIN(logdmux_maxminor << 1, MAXMIN); (void) vmem_add(logdmux_minor_arena, (void *)(uintptr_t)(omaxminor + 1), logdmux_maxminor - omaxminor, VM_SLEEP); } minor = (minor_t)(uintptr_t) vmem_alloc(logdmux_minor_arena, 1, VM_SLEEP); mutex_exit(&logdmux_minor_lock); if (ddi_soft_state_zalloc(logdmux_statep, minor) == DDI_FAILURE) { vmem_free(logdmux_minor_arena, (void *)(uintptr_t)minor, 1); return (ENOMEM); } tmxp = ddi_get_soft_state(logdmux_statep, minor); tmxp->rdq = q; tmxp->muxq = NULL; tmxp->peerq = NULL; tmxp->unlinkinfop = NULL; tmxp->dev0 = minor; *devp = makedevice(getmajor(*devp), tmxp->dev0); q->q_ptr = tmxp; WR(q)->q_ptr = tmxp; qprocson(q); return (0); } /* * Logindmux close routine gets called when telnet connection is closed */ /*ARGSUSED*/ static int logdmuxclose(queue_t *q, int flag, cred_t *crp) { struct tmx *tmxp = q->q_ptr; minor_t minor = tmxp->dev0; ASSERT(tmxp->muxq == NULL); ASSERT(tmxp->peerq == NULL); qprocsoff(q); if (tmxp->wbufcid != 0) { qunbufcall(q, tmxp->wbufcid); tmxp->wbufcid = 0; } if (tmxp->rbufcid != 0) { qunbufcall(q, tmxp->rbufcid); tmxp->rbufcid = 0; } if (tmxp->rtimoutid != 0) { (void) quntimeout(q, tmxp->rtimoutid); tmxp->rtimoutid = 0; } if (tmxp->wtimoutid != 0) { (void) quntimeout(q, tmxp->wtimoutid); tmxp->wtimoutid = 0; } if (tmxp->utimoutid != 0) { (void) quntimeout(q, tmxp->utimoutid); tmxp->utimoutid = 0; } /* * Hold logdmux_qexch_lock to prevent another thread that might be * in LOGDMX_IOC_QEXCHANGE from looking up our state while we're * disposing of it. */ mutex_enter(&logdmux_qexch_lock); ddi_soft_state_free(logdmux_statep, minor); vmem_free(logdmux_minor_arena, (void *)(uintptr_t)minor, 1); mutex_exit(&logdmux_qexch_lock); q->q_ptr = NULL; WR(q)->q_ptr = NULL; return (0); } /* * Upper read service routine */ static int logdmuxursrv(queue_t *q) { struct tmx *tmxp = q->q_ptr; if (tmxp->muxq != NULL) qenable(RD(tmxp->muxq)); return (0); } /* * This routine gets called when telnet daemon sends data or ioctl messages * to upper mux queue. */ static int logdmuxuwput(queue_t *q, mblk_t *mp) { queue_t *qp; mblk_t *newmp; struct iocblk *ioc; minor_t minor; STRUCT_HANDLE(protocol_arg, protoh); struct tmx *tmxp, *tmxpeerp; int error; tmxp = q->q_ptr; switch (mp->b_datap->db_type) { case M_IOCTL: ASSERT(MBLKL(mp) == sizeof (struct iocblk)); ioc = (struct iocblk *)mp->b_rptr; switch (ioc->ioc_cmd) { /* * This is a special ioctl which exchanges q info * of the two peers, connected to netf and ptmx. */ case LOGDMX_IOC_QEXCHANGE: error = miocpullup(mp, SIZEOF_STRUCT(protocol_arg, ioc->ioc_flag)); if (error != 0) { miocnak(q, mp, 0, error); break; } STRUCT_SET_HANDLE(protoh, ioc->ioc_flag, (struct protocol_arg *)mp->b_cont->b_rptr); #ifdef _SYSCALL32_IMPL if ((ioc->ioc_flag & DATAMODEL_MASK) == DATAMODEL_ILP32) { minor = getminor(expldev( STRUCT_FGET(protoh, dev))); } else #endif { minor = getminor(STRUCT_FGET(protoh, dev)); } /* * The second argument to ddi_get_soft_state() is * interpreted as an `int', so prohibit negative * values. */ if ((int)minor < 0) { miocnak(q, mp, 0, EINVAL); break; } /* * We must hold logdmux_qexch_lock while looking up * the proposed peer to prevent another thread from * simultaneously I_UNLINKing or closing it. */ mutex_enter(&logdmux_qexch_lock); /* * For LOGDMX_IOC_QEXCHANGE to succeed, our peer must * exist (and not be us), and both we and our peer * must be I_LINKed (i.e., muxq must not be NULL) and * not already have a peer. */ tmxpeerp = ddi_get_soft_state(logdmux_statep, minor); if (tmxpeerp == NULL || tmxpeerp == tmxp || tmxpeerp->muxq == NULL || tmxpeerp->peerq != NULL || tmxp->muxq == NULL || tmxp->peerq != NULL) { mutex_exit(&logdmux_qexch_lock); miocnak(q, mp, 0, EINVAL); break; } /* * If `flag' is set then exchange queues and assume * tmxp refers to the ptmx stream. */ if (STRUCT_FGET(protoh, flag)) { /* * Allocate and populate the structure we * need when processing an I_UNLINK ioctl. * Give both logindmux instances a pointer * to it from their tmx structure. */ if ((error = logdmux_alloc_unlinkinfo( tmxp, tmxpeerp)) != 0) { mutex_exit(&logdmux_qexch_lock); miocnak(q, mp, 0, error); break; } tmxp->peerq = tmxpeerp->muxq; tmxpeerp->peerq = tmxp->muxq; tmxp->isptm = B_TRUE; } mutex_exit(&logdmux_qexch_lock); miocack(q, mp, 0, 0); break; case I_LINK: ASSERT(MBLKL(mp->b_cont) == sizeof (struct linkblk)); logdmuxlink(q, mp); break; case I_UNLINK: ASSERT(MBLKL(mp->b_cont) == sizeof (struct linkblk)); logdmuxunlink(q, mp); break; default: if (tmxp->muxq == NULL) { miocnak(q, mp, 0, EINVAL); return (0); } putnext(tmxp->muxq, mp); break; } break; case M_DATA: if (!tmxp->isptm) { if ((newmp = allocb(sizeof (char), BPRI_MED)) == NULL) { recover(q, mp, sizeof (char)); return (0); } newmp->b_datap->db_type = M_CTL; *newmp->b_wptr++ = M_CTL_MAGIC_NUMBER; newmp->b_cont = mp; mp = newmp; } /* FALLTHRU */ case M_PROTO: case M_PCPROTO: qp = tmxp->muxq; if (qp == NULL) { merror(q, mp, EINVAL); return (0); } if (queclass(mp) < QPCTL) { if (q->q_first != NULL || !canputnext(qp)) { (void) putq(q, mp); return (0); } } putnext(qp, mp); break; case M_FLUSH: if (*mp->b_rptr & FLUSHW) flushq(q, FLUSHALL); if (tmxp->muxq != NULL) { putnext(tmxp->muxq, mp); return (0); } *mp->b_rptr &= ~FLUSHW; if (*mp->b_rptr & FLUSHR) qreply(q, mp); else freemsg(mp); break; default: cmn_err(CE_NOTE, "logdmuxuwput: received unexpected message" " of type 0x%x", mp->b_datap->db_type); freemsg(mp); } return (0); } /* * Upper write service routine */ static int logdmuxuwsrv(queue_t *q) { mblk_t *mp, *newmp; queue_t *qp; struct tmx *tmxp = q->q_ptr; while ((mp = getq(q)) != NULL) { switch (mp->b_datap->db_type) { case M_DATA: if (!tmxp->isptm) { if ((newmp = allocb(sizeof (char), BPRI_MED)) == NULL) { recover(q, mp, sizeof (char)); return (0); } newmp->b_datap->db_type = M_CTL; *newmp->b_wptr++ = M_CTL_MAGIC_NUMBER; newmp->b_cont = mp; mp = newmp; } /* FALLTHRU */ case M_CTL: case M_PROTO: if (tmxp->muxq == NULL) { merror(q, mp, EIO); break; } qp = tmxp->muxq; if (!canputnext(qp)) { (void) putbq(q, mp); return (0); } putnext(qp, mp); break; default: cmn_err(CE_NOTE, "logdmuxuwsrv: received unexpected" " message of type 0x%x", mp->b_datap->db_type); freemsg(mp); } } return (0); } /* * Logindmux lower put routine detects from which of the two lower queues * the data needs to be read from and writes it out to its peer queue. * For protocol, it detects M_CTL and sends its data to the daemon. Also, * for ioctl and other types of messages, it lets the daemon handle it. */ static int logdmuxlrput(queue_t *q, mblk_t *mp) { mblk_t *savemp; queue_t *qp; struct iocblk *ioc; struct tmx *tmxp = q->q_ptr; uchar_t flush; uint_t *messagep; unlinkinfo_t *unlinkinfop = tmxp->unlinkinfop; if (tmxp->muxq == NULL || tmxp->peerq == NULL) { freemsg(mp); return (0); } /* * If there's already a message on our queue and the incoming * message is not of a high-priority, enqueue the message -- * but not if it's a logindmux protocol message. */ if ((q->q_first != NULL) && (queclass(mp) < QPCTL) && (!LOGDMUX_PROTO_MBLK(mp))) { (void) putq(q, mp); return (0); } switch (mp->b_datap->db_type) { case M_IOCTL: ioc = (struct iocblk *)mp->b_rptr; switch (ioc->ioc_cmd) { case TIOCSWINSZ: case TCSETAF: case TCSETSF: case TCSETA: case TCSETAW: case TCSETS: case TCSETSW: case TCSBRK: case TIOCSTI: qp = tmxp->peerq; break; default: cmn_err(CE_NOTE, "logdmuxlrput: received unexpected" " request for ioctl 0x%x", ioc->ioc_cmd); /* NAK unrecognized ioctl's. */ miocnak(q, mp, 0, 0); return (0); } break; case M_DATA: case M_HANGUP: qp = tmxp->peerq; break; case M_CTL: /* * The protocol messages that flow between the peers * to implement the unlink functionality are M_CTLs * which have the M_IOCTL/I_UNLINK mblk of the ioctl * attached via b_cont. LOGDMUX_PROTO_MBLK() uses * this to determine whether a particular M_CTL is a * peer protocol message. */ if (LOGDMUX_PROTO_MBLK(mp)) { messagep = (uint_t *)mp->b_rptr; switch (*messagep) { case LOGDMUX_UNLINK_REQ: /* * We've received a message from our * peer indicating that it wants to * unlink. */ *messagep = LOGDMUX_UNLINK_RESP; qp = tmxp->peerq; mutex_enter(&logdmux_peerq_lock); tmxp->peerq = NULL; mutex_exit(&logdmux_peerq_lock); put(RD(qp), mp); return (0); case LOGDMUX_UNLINK_RESP: /* * We've received a positive response * from our peer to an earlier * LOGDMUX_UNLINK_REQ that we sent. * We can now carry on with the unlink. */ qp = tmxp->rdq; mutex_enter(&unlinkinfop->state_lock); ASSERT(unlinkinfop->state == LOGDMUX_UNLINK_PENDING); unlinkinfop->state = LOGDMUX_UNLINKED; mutex_exit(&unlinkinfop->state_lock); logdmux_finish_unlink(WR(qp), mp->b_cont); return (0); } } qp = tmxp->rdq; if (q->q_first != NULL || !canputnext(qp)) { (void) putq(q, mp); return (0); } if ((MBLKL(mp) == 1) && (*mp->b_rptr == M_CTL_MAGIC_NUMBER)) { savemp = mp->b_cont; freeb(mp); mp = savemp; } putnext(qp, mp); return (0); case M_IOCACK: case M_IOCNAK: case M_PROTO: case M_PCPROTO: case M_PCSIG: case M_SETOPTS: qp = tmxp->rdq; break; case M_ERROR: if (tmxp->isptm) { /* * This error is from ptm. We could tell TCP to * shutdown the connection, but it's easier to just * wait for the daemon to get SIGCHLD and close from * above. */ freemsg(mp); return (0); } /* * This is from TCP. Don't really know why we'd * get this, but we have a pretty good idea what * to do: Send M_HANGUP to the pty. */ mp->b_datap->db_type = M_HANGUP; mp->b_wptr = mp->b_rptr; qp = tmxp->peerq; break; case M_FLUSH: if (*mp->b_rptr & FLUSHR) flushq_dataonly(q); if (mp->b_flag & MSGMARK) { /* * This M_FLUSH has been marked by the module * below as intended for the upper queue, * not the peer queue. */ qp = tmxp->rdq; mp->b_flag &= ~MSGMARK; } else { /* * Wrap this M_FLUSH through the mux. * The FLUSHR and FLUSHW bits must be * reversed. */ qp = tmxp->peerq; flush = *mp->b_rptr; *mp->b_rptr &= ~(FLUSHR | FLUSHW); if (flush & FLUSHW) *mp->b_rptr |= FLUSHR; if (flush & FLUSHR) *mp->b_rptr |= FLUSHW; } break; case M_START: case M_STOP: case M_STARTI: case M_STOPI: freemsg(mp); return (0); default: cmn_err(CE_NOTE, "logdmuxlrput: received unexpected " "message of type 0x%x", mp->b_datap->db_type); freemsg(mp); return (0); } if (queclass(mp) < QPCTL) { if (q->q_first != NULL || !canputnext(qp)) { (void) putq(q, mp); return (0); } } putnext(qp, mp); return (0); } /* * Lower read service routine */ static int logdmuxlrsrv(queue_t *q) { mblk_t *mp, *savemp; queue_t *qp; struct iocblk *ioc; struct tmx *tmxp = q->q_ptr; while ((mp = getq(q)) != NULL) { if (tmxp->muxq == NULL || tmxp->peerq == NULL) { freemsg(mp); continue; } switch (mp->b_datap->db_type) { case M_IOCTL: ioc = (struct iocblk *)mp->b_rptr; switch (ioc->ioc_cmd) { case TIOCSWINSZ: case TCSETAF: case TCSETSF: case TCSETA: case TCSETAW: case TCSETS: case TCSETSW: case TCSBRK: case TIOCSTI: qp = tmxp->peerq; break; default: cmn_err(CE_NOTE, "logdmuxlrsrv: received " "unexpected request for ioctl 0x%x", ioc->ioc_cmd); /* NAK unrecognized ioctl's. */ miocnak(q, mp, 0, 0); continue; } break; case M_DATA: case M_HANGUP: qp = tmxp->peerq; break; case M_CTL: qp = tmxp->rdq; if (!canputnext(qp)) { (void) putbq(q, mp); return (0); } if (MBLKL(mp) == 1 && (*mp->b_rptr == M_CTL_MAGIC_NUMBER)) { savemp = mp->b_cont; freeb(mp); mp = savemp; } putnext(qp, mp); continue; case M_PROTO: case M_SETOPTS: qp = tmxp->rdq; break; default: cmn_err(CE_NOTE, "logdmuxlrsrv: received unexpected " "message of type 0x%x", mp->b_datap->db_type); freemsg(mp); continue; } ASSERT(queclass(mp) < QPCTL); if (!canputnext(qp)) { (void) putbq(q, mp); return (0); } putnext(qp, mp); } return (0); } /* * Lower side write service procedure. No messages are ever placed on * the write queue here, this just back-enables all of the upper side * write service procedures. */ static int logdmuxlwsrv(queue_t *q) { struct tmx *tmxp = q->q_ptr; /* * Qenable upper write queue and find out which lower * queue needs to be restarted with flow control. * Qenable the peer queue so canputnext will * succeed on next call to logdmuxlrput. */ qenable(WR(tmxp->rdq)); mutex_enter(&logdmux_peerq_lock); if (tmxp->peerq != NULL) qenable(RD(tmxp->peerq)); mutex_exit(&logdmux_peerq_lock); return (0); } /* * This routine does I_LINK operation. */ static void logdmuxlink(queue_t *q, mblk_t *mp) { struct tmx *tmxp = q->q_ptr; struct linkblk *lp = (struct linkblk *)mp->b_cont->b_rptr; /* * Fail if we're already linked. */ if (tmxp->muxq != NULL) { miocnak(q, mp, 0, EINVAL); return; } tmxp->muxq = lp->l_qbot; tmxp->muxq->q_ptr = tmxp; RD(tmxp->muxq)->q_ptr = tmxp; miocack(q, mp, 0, 0); } /* * logdmuxunlink() is called from logdmuxuwput() and is the first of two * functions which process an I_UNLINK ioctl. logdmuxunlink() will determine * the state of logindmux peer linkage and, based on this, control when the * second function, logdmux_finish_unlink(), is called. It's * logdmux_finish_unlink() that's sending the M_IOCACK upstream and * resetting the link state. */ static void logdmuxunlink(queue_t *q, mblk_t *mp) { struct tmx *tmxp = q->q_ptr; unlinkinfo_t *unlinkinfop; /* * If we don't have a peer, just unlink. Note that this check needs * to be done under logdmux_qexch_lock to prevent racing with * LOGDMX_IOC_QEXCHANGE, and we *must* set muxq to NULL prior to * releasing the lock so that LOGDMX_IOC_QEXCHANGE will not consider * us as a possible peer anymore (if it already considers us to be a * peer, then unlinkinfop will not be NULL) -- NULLing muxq precludes * use of logdmux_finish_unlink() here. */ mutex_enter(&logdmux_qexch_lock); unlinkinfop = tmxp->unlinkinfop; if (unlinkinfop == NULL) { ASSERT(tmxp->peerq == NULL); tmxp->muxq = NULL; mutex_exit(&logdmux_qexch_lock); miocack(q, mp, 0, 0); return; } mutex_exit(&logdmux_qexch_lock); mutex_enter(&unlinkinfop->state_lock); switch (unlinkinfop->state) { case LOGDMUX_LINKED: /* * We're the first instance to process an I_UNLINK -- * ie, the peer instance is still there. We'll change * the state so that only one instance is executing an * I_UNLINK at any one time. */ unlinkinfop->state = LOGDMUX_UNLINK_PENDING; mutex_exit(&unlinkinfop->state_lock); /* * Attach the original M_IOCTL message to a * LOGDMUX_UNLINK_REQ message and send it to our peer to * tell it to unlink from us. When it has completed the * task, it will send us a LOGDMUX_UNLINK_RESP message * with the original M_IOCTL still attached, which will be * processed in our logdmuxlrput(). At that point, we will * call logdmux_finish_unlink() to complete the unlink * operation using the attached M_IOCTL. */ unlinkinfop->prot_mp->b_cont = mp; /* * Put the M_CTL directly to the peer's lower RQ. */ put(RD(tmxp->peerq), unlinkinfop->prot_mp); break; case LOGDMUX_UNLINK_PENDING: mutex_exit(&unlinkinfop->state_lock); /* * Our peer is actively processing an I_UNLINK itself. * We have to wait for the peer to complete and we use * qtimeout as a way to poll for its completion. * We save a reference to our mblk so that we can send * it upstream once our peer is done. */ tmxp->unlink_mp = mp; tmxp->utimoutid = qtimeout(q, logdmux_unlink_timer, q, drv_usectohz(LOGDMUX_POLL_WAIT)); break; case LOGDMUX_UNLINKED: /* * Our peer is no longer linked so we can proceed. */ mutex_exit(&unlinkinfop->state_lock); mutex_destroy(&unlinkinfop->state_lock); freeb(unlinkinfop->prot_mp); kmem_free(unlinkinfop, sizeof (unlinkinfo_t)); logdmux_finish_unlink(q, mp); break; default: mutex_exit(&unlinkinfop->state_lock); cmn_err(CE_PANIC, "logdmuxunlink: peer linkage is in an unrecognized state"); break; } } /* * Finish the unlink operation. Note that no locks should be held since * this routine calls into other queues. */ static void logdmux_finish_unlink(queue_t *q, mblk_t *unlink_mp) { struct tmx *tmxp = q->q_ptr; mblk_t *mp; /* * Flush any write side data downstream. */ while ((mp = getq(WR(q))) != NULL) putnext(tmxp->muxq, mp); /* * Note that we do not NULL out q_ptr since another thread (e.g., a * STREAMS service thread) might call logdmuxlrput() between the time * we exit the logindmux perimeter and the time the STREAMS framework * resets q_ptr to stdata (since muxq is set to NULL, any messages * will just be discarded). */ tmxp->muxq = NULL; tmxp->unlinkinfop = NULL; tmxp->peerq = NULL; miocack(q, unlink_mp, 0, 0); } /* * logdmux_unlink_timer() is executed by qtimeout(). This function will * check unlinkinfop->state to determine whether the peer has completed * its I_UNLINK. If it hasn't, we use qtimeout() to initiate another poll. */ static void logdmux_unlink_timer(void *arg) { queue_t *q = arg; struct tmx *tmxp = q->q_ptr; unlinkinfo_t *unlinkinfop = tmxp->unlinkinfop; tmxp->utimoutid = 0; mutex_enter(&unlinkinfop->state_lock); if (unlinkinfop->state != LOGDMUX_UNLINKED) { ASSERT(unlinkinfop->state == LOGDMUX_UNLINK_PENDING); mutex_exit(&unlinkinfop->state_lock); /* * We need to wait longer for our peer to complete. */ tmxp->utimoutid = qtimeout(q, logdmux_unlink_timer, q, drv_usectohz(LOGDMUX_POLL_WAIT)); } else { /* * Our peer is no longer linked so we can proceed with * the cleanup. */ mutex_exit(&unlinkinfop->state_lock); mutex_destroy(&unlinkinfop->state_lock); freeb(unlinkinfop->prot_mp); kmem_free(unlinkinfop, sizeof (unlinkinfo_t)); logdmux_finish_unlink(q, tmxp->unlink_mp); } } static void logdmux_timer(void *arg) { queue_t *q = arg; struct tmx *tmxp = q->q_ptr; ASSERT(tmxp != NULL); if (q->q_flag & QREADR) { ASSERT(tmxp->rtimoutid != 0); tmxp->rtimoutid = 0; } else { ASSERT(tmxp->wtimoutid != 0); tmxp->wtimoutid = 0; } enableok(q); qenable(q); } static void logdmux_buffer(void *arg) { queue_t *q = arg; struct tmx *tmxp = q->q_ptr; ASSERT(tmxp != NULL); if (q->q_flag & QREADR) { ASSERT(tmxp->rbufcid != 0); tmxp->rbufcid = 0; } else { ASSERT(tmxp->wbufcid != 0); tmxp->wbufcid = 0; } enableok(q); qenable(q); } static void recover(queue_t *q, mblk_t *mp, size_t size) { timeout_id_t tid; bufcall_id_t bid; struct tmx *tmxp = q->q_ptr; /* * Avoid re-enabling the queue. */ ASSERT(queclass(mp) < QPCTL); ASSERT(WR(q)->q_next == NULL); /* Called from upper queue only */ noenable(q); (void) putbq(q, mp); /* * Make sure there is at most one outstanding request per queue. */ if (q->q_flag & QREADR) { if (tmxp->rtimoutid != 0 || tmxp->rbufcid != 0) return; } else { if (tmxp->wtimoutid != 0 || tmxp->wbufcid != 0) return; } if (!(bid = qbufcall(RD(q), size, BPRI_MED, logdmux_buffer, q))) { tid = qtimeout(RD(q), logdmux_timer, q, drv_usectohz(SIMWAIT)); if (q->q_flag & QREADR) tmxp->rtimoutid = tid; else tmxp->wtimoutid = tid; } else { if (q->q_flag & QREADR) tmxp->rbufcid = bid; else tmxp->wbufcid = bid; } } static void flushq_dataonly(queue_t *q) { mblk_t *mp, *nmp; /* * Since we are already in the perimeter, and we are not a put-shared * perimeter, we don't need to freeze the stream or anything to * be ensured of exclusivity. */ mp = q->q_first; while (mp != NULL) { if (mp->b_datap->db_type == M_DATA) { nmp = mp->b_next; rmvq(q, mp); freemsg(mp); mp = nmp; } else { mp = mp->b_next; } } } /* * logdmux_alloc_unlinkinfo() is called from logdmuxuwput() during the * processing of a LOGDMX_IOC_QEXCHANGE ioctl() to allocate the * unlinkinfo_t which is needed during the processing of an I_UNLINK. */ static int logdmux_alloc_unlinkinfo(struct tmx *t0, struct tmx *t1) { unlinkinfo_t *p; uint_t *messagep; if ((p = kmem_zalloc(sizeof (unlinkinfo_t), KM_NOSLEEP)) == NULL) return (ENOSR); if ((p->prot_mp = allocb(sizeof (uint_t), BPRI_MED)) == NULL) { kmem_free(p, sizeof (unlinkinfo_t)); return (ENOSR); } DB_TYPE(p->prot_mp) = M_CTL; messagep = (uint_t *)p->prot_mp->b_wptr; *messagep = LOGDMUX_UNLINK_REQ; p->prot_mp->b_wptr += sizeof (*messagep); p->state = LOGDMUX_LINKED; mutex_init(&p->state_lock, NULL, MUTEX_DRIVER, NULL); t0->unlinkinfop = t1->unlinkinfop = p; return (0); }