/*
* 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
* 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
*
* 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 <sys/logindmux.h>
#include <sys/logindmux_impl.h>
#include <sys/sysmacros.h>
static int logdmuxursrv(queue_t *);
static int logdmuxlrsrv(queue_t *);
static int logdmuxlwsrv(queue_t *);
static int logdmuxuwsrv(queue_t *);
static void logdmux_unlink_timer(void *arg);
static void flushq_dataonly(queue_t *);
static void *logdmux_statep;
"logindmux",
0,
256,
512,
256
};
NULL,
NULL,
};
NULL,
NULL,
NULL,
};
NULL,
NULL,
NULL,
};
NULL,
NULL,
NULL,
NULL,
};
};
"logindmux driver",
};
};
int
_init(void)
{
int ret;
return (ret);
}
return (0);
}
int
_fini(void)
{
int ret;
}
return (ret);
}
int
{
}
static int
{
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
CLONE_DEV) == DDI_FAILURE)
return (DDI_FAILURE);
logdmux_dip = devi;
return (DDI_SUCCESS);
}
static int
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
{
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
{
return (EINVAL);
/*
* 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) {
return (ENOMEM);
}
(void) vmem_add(logdmux_minor_arena,
}
return (ENOMEM);
}
qprocson(q);
return (0);
}
/*
* Logindmux close routine gets called when telnet connection is closed
*/
/*ARGSUSED*/
static int
{
qprocsoff(q);
}
}
}
}
}
/*
* 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.
*/
return (0);
}
/*
* Upper read service routine
*/
static int
{
return (0);
}
/*
* This routine gets called when telnet daemon sends data or ioctl messages
* to upper mux queue.
*/
static int
{
int error;
case M_IOCTL:
/*
* This is a special ioctl which exchanges q info
* of the two peers, connected to netf and ptmx.
*/
case LOGDMX_IOC_QEXCHANGE:
if (error != 0) {
break;
}
#ifdef _SYSCALL32_IMPL
} else
#endif
{
}
/*
* The second argument to ddi_get_soft_state() is
* interpreted as an `int', so prohibit negative
* values.
*/
if ((int)minor < 0) {
break;
}
/*
* We must hold logdmux_qexch_lock while looking up
* the proposed peer to prevent another thread from
* simultaneously I_UNLINKing or closing it.
*/
/*
* 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.
*/
break;
}
/*
* If `flag' is set then exchange queues and assume
* tmxp refers to the ptmx stream.
*/
/*
* 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(
break;
}
}
break;
case I_LINK:
logdmuxlink(q, mp);
break;
case I_UNLINK:
logdmuxunlink(q, mp);
break;
default:
return (0);
}
break;
}
break;
case M_DATA:
return (0);
}
}
/* FALLTHRU */
case M_PROTO:
case M_PCPROTO:
return (0);
}
return (0);
}
}
break;
case M_FLUSH:
return (0);
}
else
break;
default:
}
return (0);
}
/*
* Upper write service routine
*/
static int
{
case M_DATA:
NULL) {
return (0);
}
}
/* FALLTHRU */
case M_CTL:
case M_PROTO:
break;
}
if (!canputnext(qp)) {
return (0);
}
break;
default:
}
}
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
{
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.
*/
(!LOGDMUX_PROTO_MBLK(mp))) {
return (0);
}
case M_IOCTL:
case TIOCSWINSZ:
case TCSETAF:
case TCSETSF:
case TCSETA:
case TCSETAW:
case TCSETS:
case TCSETSW:
case TCSBRK:
case TIOCSTI:
break;
default:
/* NAK unrecognized ioctl's. */
return (0);
}
break;
case M_DATA:
case M_HANGUP:
break;
case M_CTL:
/*
* The protocol messages that flow between the peers
* to implement the unlink functionality are M_CTLs
* 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)) {
switch (*messagep) {
case LOGDMUX_UNLINK_REQ:
/*
* We've received a message from our
* peer indicating that it wants to
* unlink.
*/
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.
*/
return (0);
}
}
return (0);
}
}
return (0);
case M_IOCACK:
case M_IOCNAK:
case M_PROTO:
case M_PCPROTO:
case M_PCSIG:
case M_SETOPTS:
break;
case M_ERROR:
/*
* 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.
*/
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.
*/
break;
case M_FLUSH:
flushq_dataonly(q);
/*
* This M_FLUSH has been marked by the module
* below as intended for the upper queue,
* not the peer queue.
*/
} else {
/*
* Wrap this M_FLUSH through the mux.
* The FLUSHR and FLUSHW bits must be
* reversed.
*/
}
break;
case M_START:
case M_STOP:
case M_STARTI:
case M_STOPI:
return (0);
default:
return (0);
}
return (0);
}
}
return (0);
}
/*
* Lower read service routine
*/
static int
{
continue;
}
case M_IOCTL:
case TIOCSWINSZ:
case TCSETAF:
case TCSETSF:
case TCSETA:
case TCSETAW:
case TCSETS:
case TCSETSW:
case TCSBRK:
case TIOCSTI:
break;
default:
"unexpected request for ioctl 0x%x",
/* NAK unrecognized ioctl's. */
continue;
}
break;
case M_DATA:
case M_HANGUP:
break;
case M_CTL:
if (!canputnext(qp)) {
return (0);
}
}
continue;
case M_PROTO:
case M_SETOPTS:
break;
default:
continue;
}
if (!canputnext(qp)) {
return (0);
}
}
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
{
/*
* 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.
*/
return (0);
}
/*
* This routine does I_LINK operation.
*/
static void
{
/*
* Fail if we're already linked.
*/
return;
}
}
/*
* 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
{
/*
* 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.
*/
if (unlinkinfop == NULL) {
return;
}
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.
*/
/*
* 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.
*/
/*
* Put the M_CTL directly to the peer's lower RQ.
*/
break;
case LOGDMUX_UNLINK_PENDING:
/*
* 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.
*/
break;
case LOGDMUX_UNLINKED:
/*
* Our peer is no longer linked so we can proceed.
*/
logdmux_finish_unlink(q, mp);
break;
default:
"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
{
/*
* Flush any write side data downstream.
*/
/*
* 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).
*/
}
/*
* 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
{
/*
* We need to wait longer for our peer to complete.
*/
} else {
/*
* Our peer is no longer linked so we can proceed with
* the cleanup.
*/
}
}
static void
{
} else {
}
enableok(q);
qenable(q);
}
static void
{
} else {
}
enableok(q);
qenable(q);
}
static void
{
/*
* Avoid re-enabling the queue.
*/
noenable(q);
/*
* Make sure there is at most one outstanding request per queue.
*/
return;
} else {
return;
}
else
} else {
else
}
}
static void
{
/*
* 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.
*/
} else {
}
}
}
/*
* 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
{
unlinkinfo_t *p;
return (ENOSR);
kmem_free(p, sizeof (unlinkinfo_t));
return (ENOSR);
}
p->state = LOGDMUX_LINKED;
return (0);
}