idm_conn_sm.c revision ff9de39482f57a342b973f41ee343e24db962236
/*
* 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 (c) 2013 by Delphix. All rights reserved.
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
*/
#define IDM_CONN_SM_STRINGS
#define IDM_CN_NOTIFY_STRINGS
static void
static void
static void
static void
static void
static void
static void
static void
idm_logout_req_timeout(void *arg);
static void
static void
static void
static void
static void
static void
static void
static void
static void
static void
static void
idm_conn_unref(void *ic_void);
static void
idm_conn_reject_unref(void *ic_void);
static idm_pdu_event_action_t
static idm_status_t
static void
static void
static void
{
char taskq_name[32];
/*
* Caller should have assigned a unique connection ID. Use this
* connection ID to create a unique connection name string
*/
return (IDM_STATUS_FAIL);
}
return (IDM_STATUS_SUCCESS);
}
void
{
/*
* The connection may only be partially created. If there
* is no taskq, then the connection SM was not initialized.
*/
return;
}
/*
* The thread that generated the event that got us here may still
* hold the ic_state_mutex. Once it is released we can safely
* destroy it since there is no way to locate the object now.
*/
}
void
{
}
{
int result;
} else {
}
return (result);
}
void
{
ic->ic_pdu_events++;
}
void
{
ic->ic_pdu_events++;
}
void
{
/*
* It's very difficult to prevent a few straggling events
* at the end. For example idm_sorx_thread will generate
* a CE_TRANSPORT_FAIL event when it exits. Rather than
* push complicated restrictions all over the code to
* prevent this we will simply drop the events (and in
* the case of PDU events release them appropriately)
* since they are irrelevant once we are in a terminal state.
* Of course those threads need to have appropriate holds on
* the connection otherwise it might disappear.
*/
if ((pdu_event_type == CT_TX_PDU) ||
(pdu_event_type == CT_RX_PDU)) {
ic->ic_pdu_events--;
}
"state %s (%d)",
return;
}
/*
* Normal event handling
*/
}
static void
{
/*
* Validate event
*/
/*
* Validate current state
*/
/*
* Validate PDU-related events against the current state. If a PDU
* is not allowed in the current state we change the event to a
* protocol error. This simplifies the state-specific event handlers.
* For example the CS_S2_XPT_WAIT state only needs to handle the
* CE_TX_PROTOCOL_ERROR and CE_RX_PROTOCOL_ERROR events since
* no PDU's can be transmitted or received in that state.
*/
switch (action) {
case CA_TX_PROTOCOL_ERROR:
/*
* Change event and forward the PDU
*/
break;
case CA_RX_PROTOCOL_ERROR:
/*
* Change event and forward the PDU.
*/
break;
case CA_FORWARD:
/*
* Let the state-specific event handlers take
* care of it.
*/
break;
case CA_DROP:
/*
* It never even happened
*/
break;
default:
ASSERT(0);
break;
}
}
case CS_S1_FREE:
break;
case CS_S2_XPT_WAIT:
break;
case CS_S3_XPT_UP:
break;
case CS_S4_IN_LOGIN:
break;
case CS_S5_LOGGED_IN:
break;
case CS_S6_IN_LOGOUT:
break;
case CS_S7_LOGOUT_REQ:
break;
case CS_S8_CLEANUP:
break;
case CS_S9A_REJECTED:
break;
case CS_S9B_WAIT_SND_DONE:
break;
case CS_S9_INIT_ERROR:
break;
case CS_S10_IN_CLEANUP:
break;
case CS_S11_COMPLETE:
break;
case CS_S12_ENABLE_DM:
break;
default:
ASSERT(0);
break;
}
/*
* Now that we've updated the state machine, if this was
* a PDU-related event take the appropriate action on the PDU
* (transmit it, forward it to the clients RX callback, drop
* it, etc).
*/
switch (action) {
case CA_TX_PROTOCOL_ERROR:
break;
case CA_RX_PROTOCOL_ERROR:
break;
case CA_FORWARD:
if (!event_ctx->iec_pdu_forwarded) {
if (event_ctx->iec_pdu_event_type ==
CT_RX_PDU) {
} else {
}
}
break;
default:
ASSERT(0);
break;
}
}
/*
* Update outstanding PDU event count (see idm_pdu_tx for
* how this is used)
*/
ic->ic_pdu_events--;
}
}
static void
{
case CE_CONNECT_REQ:
/* T1 */
break;
case CE_CONNECT_ACCEPT:
/* T3 */
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
/* This should never happen */
break;
default:
ASSERT(0);
/*NOTREACHED*/
}
}
static void
{
case CE_CONNECT_SUCCESS:
/* T4 */
break;
case CE_TRANSPORT_FAIL:
case CE_CONNECT_FAIL:
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
/* T2 */
break;
default:
ASSERT(0);
/*NOTREACHED*/
}
}
static void
idm_login_timeout(void *arg)
{
ic->ic_state_timeout = 0;
}
static void
{
case CE_LOGIN_RCV:
/* T4 */
/* Keep login timeout active through S3 and into S4 */
break;
case CE_LOGIN_TIMEOUT:
/*
* Don't need to cancel login timer since the timer is
* presumed to be the source of this event.
*/
break;
case CE_CONNECT_REJECT:
/*
* Iscsit doesn't want to hear from us again in this case.
* Since it rejected the connection it doesn't have a
* connection context to handle additional notifications.
* IDM needs to just clean things up on its own.
*/
break;
case CE_CONNECT_FAIL:
case CE_TRANSPORT_FAIL:
case CE_LOGOUT_OTHER_CONN_SND:
/* T6 */
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
/* Don't care */
break;
default:
ASSERT(0);
/*NOTREACHED*/
}
}
static void
{
/*
* Login timer should no longer be active after leaving this
* state.
*/
case CE_LOGIN_SUCCESS_RCV:
case CE_LOGIN_SUCCESS_SND:
if (ic->ic_rdma_extensions) {
/* T19 */
} else {
/* T5 */
}
break;
case CE_LOGIN_TIMEOUT:
/* T7 */
break;
case CE_LOGIN_FAIL_SND:
/*
* Allow the logout response pdu to be sent and defer
* the state machine cleanup until the completion callback.
* Only 1 level or callback interposition is allowed.
*/
pdu->isp_callback =
break;
case CE_LOGIN_FAIL_RCV:
/*
* Need to deliver this PDU to the initiator now because after
* we update the state to CS_S9_INIT_ERROR the initiator will
* no longer be in an appropriate state.
*/
/* FALLTHROUGH */
case CE_TRANSPORT_FAIL:
case CE_LOGOUT_OTHER_CONN_SND:
case CE_LOGOUT_OTHER_CONN_RCV:
/* T7 */
break;
/*
* T8
* A session reinstatement request can be received while a
* session is active and a login is in process. The iSCSI
* connections are shut down by a CE_LOGOUT_SESSION_SUCCESS
* event sent from the session to the IDM layer.
*/
if (IDM_CONN_ISTGT(ic)) {
} else {
}
break;
case CE_LOGIN_SND:
/*
* Initiator connections will see initial login PDU
* in this state. Target connections see initial
* login PDU in "xpt up" state.
*/
}
break;
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_RCV:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
/* Don't care */
break;
default:
ASSERT(0);
/*NOTREACHED*/
}
}
static void
{
case CE_MISC_RX:
/* MC/S: when removing the non-leading connection */
case CE_LOGOUT_THIS_CONN_RCV:
case CE_LOGOUT_THIS_CONN_SND:
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_LOGOUT_OTHER_CONN_SND:
/* T9 */
break;
case CE_LOGOUT_SESSION_RCV:
case CE_LOGOUT_SESSION_SND:
/* T9 */
break;
/* T8 */
/* Close connection */
if (IDM_CONN_ISTGT(ic)) {
} else {
}
break;
case CE_ASYNC_LOGOUT_RCV:
case CE_ASYNC_LOGOUT_SND:
/* T11 */
break;
case CE_TRANSPORT_FAIL:
case CE_ASYNC_DROP_CONN_RCV:
case CE_ASYNC_DROP_CONN_SND:
/* T15 */
break;
case CE_MISC_TX:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_LOGIN_TIMEOUT:
/* Don't care */
break;
default:
ASSERT(0);
}
}
static void
{
/*
* This pdu callback can be invoked by the tx thread,
* so run the disconnect code from another thread.
*/
}
static void
{
/*
* This pdu callback can be invoked by the tx thread,
* so run the disconnect code from another thread.
*/
}
static void
{
/* Close connection (if it's not already closed) */
/* restore client callback */
break;
case CE_LOGOUT_FAIL_SND_DONE:
/* restore client callback */
break;
case CE_LOGOUT_SUCCESS_SND:
case CE_LOGOUT_FAIL_SND:
/*
* Allow the logout response pdu to be sent and defer
* the state machine update until the completion callback.
* Only 1 level or callback interposition is allowed.
*/
pdu->isp_callback =
} else {
pdu->isp_callback =
}
break;
case CE_LOGOUT_SUCCESS_RCV:
/*
* Need to deliver this PDU to the initiator now because after
* we update the state to CS_S11_COMPLETE the initiator will
* no longer be in an appropriate state.
*/
/* FALLTHROUGH */
/* T13 */
/* Close connection (if it's not already closed) */
if (IDM_CONN_ISTGT(ic)) {
} else {
}
break;
case CE_ASYNC_LOGOUT_RCV:
/* T14 Do nothing */
break;
case CE_TRANSPORT_FAIL:
case CE_ASYNC_DROP_CONN_RCV:
case CE_ASYNC_DROP_CONN_SND:
case CE_LOGOUT_FAIL_RCV:
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_TIMEOUT:
/* Don't care */
break;
default:
ASSERT(0);
}
}
static void
idm_logout_req_timeout(void *arg)
{
ic->ic_state_timeout = 0;
}
static void
{
/* Must cancel logout timer before leaving this state */
case CE_LOGOUT_THIS_CONN_RCV:
case CE_LOGOUT_THIS_CONN_SND:
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_LOGOUT_OTHER_CONN_SND:
/* T10 */
if (IDM_CONN_ISTGT(ic)) {
}
break;
case CE_LOGOUT_SESSION_RCV:
case CE_LOGOUT_SESSION_SND:
/* T10 */
if (IDM_CONN_ISTGT(ic)) {
}
break;
case CE_ASYNC_LOGOUT_RCV:
case CE_ASYNC_LOGOUT_SND:
/* T12 Do nothing */
break;
case CE_TRANSPORT_FAIL:
case CE_ASYNC_DROP_CONN_RCV:
case CE_ASYNC_DROP_CONN_SND:
/* T16 */
if (IDM_CONN_ISTGT(ic)) {
}
/* FALLTHROUGH */
case CE_LOGOUT_TIMEOUT:
break;
/* T18 */
if (IDM_CONN_ISTGT(ic)) {
}
/* Close connection (if it's not already closed) */
if (IDM_CONN_ISTGT(ic)) {
} else {
}
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_TIMEOUT:
/* Don't care */
break;
default:
ASSERT(0);
}
}
static void
idm_cleanup_timeout(void *arg)
{
ic->ic_state_timeout = 0;
}
static void
{
/*
* Need to cancel the cleanup timeout before leaving this state
* if it hasn't already fired.
*/
case CE_LOGOUT_SUCCESS_RCV:
case CE_LOGOUT_SUCCESS_SND:
/*FALLTHROUGH*/
case CE_CLEANUP_TIMEOUT:
/* M1 */
break;
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_LOGOUT_OTHER_CONN_SND:
/* M2 */
break;
case CE_LOGOUT_FAIL_SND_DONE:
/* restore client callback */
break;
case CE_LOGOUT_SESSION_RCV:
case CE_LOGOUT_SESSION_SND:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_TRANSPORT_FAIL:
case CE_LOGIN_TIMEOUT:
case CE_LOGOUT_TIMEOUT:
/* Don't care */
break;
default:
ASSERT(0);
}
}
/* ARGSUSED */
static void
{
/* All events ignored in this state */
}
/* ARGSUSED */
static void
{
/* All events ignored in this state */
}
static void
{
/*
* This pdu callback can be invoked by the tx thread,
* so run the disconnect code from another thread.
*/
}
/*
* CS_S9B_WAIT_SND_DONE -- wait for callback completion.
*/
/* ARGSUSED */
static void
{
/*
* Wait for completion of the login fail sequence and then
* go to state S9_INIT_ERROR to clean up the connection.
*/
case CE_LOGIN_FAIL_SND_DONE:
/* restore client callback */
break;
/* All other events ignored */
}
}
static void
{
/*
* Need to cancel the cleanup timeout before leaving this state
* if it hasn't already fired.
*/
case CE_LOGOUT_FAIL_RCV:
case CE_LOGOUT_FAIL_SND:
break;
case CE_LOGOUT_SUCCESS_SND:
case CE_LOGOUT_SUCCESS_RCV:
/*FALLTHROUGH*/
case CE_CLEANUP_TIMEOUT:
break;
case CE_LOGOUT_FAIL_SND_DONE:
/* restore client callback */
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_TIMEOUT:
case CE_LOGOUT_TIMEOUT:
/* Don't care */
break;
default:
ASSERT(0);
}
}
/* ARGSUSED */
static void
{
/*
* until now.
*
* All new events are filtered out before reaching this state, but
* there might already be events in the event queue, so handle the
* SND_DONE events here. Note that if either of the following
* SND_DONE events happens AFTER the change to state S11, then the
* event filter inside dm_conn_event_locked does enough cleanup.
*/
case CE_LOGOUT_FAIL_SND_DONE:
/* restore client callback */
break;
}
}
static void
{
case CE_ENABLE_DM_SUCCESS:
/* T20 */
break;
case CE_ENABLE_DM_FAIL:
/* T21 */
break;
case CE_TRANSPORT_FAIL:
/*
* We expect to always hear back from the transport layer
* once we have an "enable data-mover" request outstanding.
* Therefore we'll ignore other events that may occur even
* when they clearly indicate a problem and wait for
* CE_ENABLE_DM_FAIL. On a related note this means the
* transport must ensure that it eventually completes the
* "enable data-mover" operation with either success or
* failure -- otherwise we'll be stuck here.
*/
break;
default:
ASSERT(0);
break;
}
}
static void
{
int rc;
/*
* Validate new state
*/
/*
* Update state in context. We protect this with a mutex
* even though the state machine code is single threaded so that
* other threads can check the state value atomically.
*/
"%s(%d) --> %s(%d)", (void *)ic,
case CS_S1_FREE:
ASSERT(0); /* Initial state, can't return */
break;
case CS_S2_XPT_WAIT:
} else {
}
break;
case CS_S3_XPT_UP:
/*
* Finish any connection related setup including
* waking up the idm_tgt_conn_accept thread.
* and starting the login timer. If the function
* fails then we return to "free" state.
*/
switch (rc) {
case IDM_STATUS_REJECT:
break;
default:
break;
}
}
/*
* First login received will cause a transition to
* CS_S4_IN_LOGIN. Start login timer.
*/
break;
case CS_S4_IN_LOGIN:
}
break;
case CS_S5_LOGGED_IN:
/*
* IDM can go to FFP before the initiator but it
* needs to go to FFP after the target (IDM target should
* go to FFP after notify_ack).
*/
if (idm_status != IDM_STATUS_SUCCESS) {
}
if (ic->ic_reinstate_conn) {
/* Connection reinstatement is complete */
}
break;
case CS_S6_IN_LOGOUT:
break;
case CS_S7_LOGOUT_REQ:
/* Start logout timer for target connections */
if (IDM_CONN_ISTGT(ic)) {
}
break;
case CS_S8_CLEANUP:
/* Close connection (if it's not already closed) */
if (IDM_CONN_ISTGT(ic)) {
} else {
}
/* Stop executing active tasks */
/* Start logout timer */
break;
case CS_S10_IN_CLEANUP:
break;
case CS_S9A_REJECTED:
/*
* We never finished establishing the connection so no
* disconnect. No client notifications because the client
* rejected the connection.
*/
break;
case CS_S9B_WAIT_SND_DONE:
break;
case CS_S9_INIT_ERROR:
if (IDM_CONN_ISTGT(ic)) {
} else {
ic);
} else {
NULL);
}
}
/*FALLTHROUGH*/
case CS_S11_COMPLETE:
/*
* No more traffic on this connection. If this is an
* initiator connection and we weren't connected yet
* then don't send the "connect lost" event.
* It's useful to the initiator to know whether we were
* logging in at the time so send that information in the
* data field.
*/
if (IDM_CONN_ISTGT(ic) ||
}
/* Abort all tasks */
/*
* Handle terminal state actions on the global taskq so
* we can clean up all the connection resources from
* a separate thread context.
*/
break;
case CS_S12_ENABLE_DM:
/*
* The Enable DM state indicates the initiator to initiate
* the hello sequence and the target to get ready to accept
* the iSER Hello Message.
*/
if (idm_status == IDM_STATUS_SUCCESS) {
} else {
}
break;
default:
ASSERT(0);
break;
}
}
static void
idm_conn_unref(void *ic_void)
{
/*
* Client should not be notified that the connection is destroyed
* until all references on the idm connection have been removed.
* Otherwise references on the associated client context would need
* to be tracked separately which seems like a waste (at least when
* there is a one for one correspondence with references on the
* IDM connection).
*/
if (IDM_CONN_ISTGT(ic)) {
} else {
/* Initiator may destroy connection during this call */
}
}
static void
idm_conn_reject_unref(void *ic_void)
{
/* Don't notify the client since it rejected the connection */
}
static idm_pdu_event_action_t
{
char *reason_string;
/*
* Let's check the simple stuff first. Make sure if this is a
* target connection that the PDU is appropriate for a target
* and if this is an initiator connection that the PDU is
* appropriate for an initiator. This code is not in the data
* path so organization is more important than performance.
*/
switch (IDM_PDU_OPCODE(pdu)) {
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_SCSI_CMD:
case ISCSI_OP_LOGIN_CMD:
case ISCSI_OP_TEXT_CMD:
case ISCSI_OP_SCSI_DATA:
case ISCSI_OP_LOGOUT_CMD:
case ISCSI_OP_SNACK_CMD:
/*
* Only the initiator should send these PDU's and
* only the target should receive them.
*/
if (IDM_CONN_ISINI(ic) &&
reason_string = "Invalid RX PDU for initiator";
goto validate_pdu_done;
}
if (IDM_CONN_ISTGT(ic) &&
reason_string = "Invalid TX PDU for target";
goto validate_pdu_done;
}
break;
case ISCSI_OP_NOOP_IN:
case ISCSI_OP_SCSI_RSP:
case ISCSI_OP_LOGIN_RSP:
case ISCSI_OP_TEXT_RSP:
case ISCSI_OP_SCSI_DATA_RSP:
case ISCSI_OP_LOGOUT_RSP:
case ISCSI_OP_RTT_RSP:
case ISCSI_OP_ASYNC_EVENT:
case ISCSI_OP_REJECT_MSG:
/*
* Only the target should send these PDU's and
* only the initiator should receive them.
*/
if (IDM_CONN_ISTGT(ic) &&
reason_string = "Invalid RX PDU for target";
goto validate_pdu_done;
}
if (IDM_CONN_ISINI(ic) &&
reason_string = "Invalid TX PDU for initiator";
goto validate_pdu_done;
}
break;
default:
reason_string = "Unknown PDU Type";
goto validate_pdu_done;
}
/*
* Now validate the opcodes against the current state.
*/
reason_string = "PDU not allowed in current state";
switch (IDM_PDU_OPCODE(pdu)) {
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_NOOP_IN:
/*
* Obviously S1-S3 are not allowed since login hasn't started.
* S8 is probably out as well since the connection has been
* dropped.
*/
case CS_S4_IN_LOGIN:
case CS_S5_LOGGED_IN:
case CS_S6_IN_LOGOUT:
case CS_S7_LOGOUT_REQ:
action = CA_FORWARD;
goto validate_pdu_done;
case CS_S8_CLEANUP:
case CS_S10_IN_CLEANUP:
break;
default:
goto validate_pdu_done;
}
/*NOTREACHED*/
case ISCSI_OP_SCSI_CMD:
case ISCSI_OP_SCSI_RSP:
case ISCSI_OP_SCSI_DATA:
case ISCSI_OP_SCSI_DATA_RSP:
case ISCSI_OP_RTT_RSP:
case ISCSI_OP_SNACK_CMD:
case ISCSI_OP_TEXT_CMD:
case ISCSI_OP_TEXT_RSP:
case CS_S5_LOGGED_IN:
case CS_S6_IN_LOGOUT:
case CS_S7_LOGOUT_REQ:
action = CA_FORWARD;
goto validate_pdu_done;
case CS_S8_CLEANUP:
case CS_S10_IN_CLEANUP:
break;
default:
goto validate_pdu_done;
}
/*NOTREACHED*/
case ISCSI_OP_LOGOUT_CMD:
case ISCSI_OP_LOGOUT_RSP:
case ISCSI_OP_REJECT_MSG:
case ISCSI_OP_ASYNC_EVENT:
case CS_S5_LOGGED_IN:
case CS_S6_IN_LOGOUT:
case CS_S7_LOGOUT_REQ:
action = CA_FORWARD;
goto validate_pdu_done;
case CS_S8_CLEANUP:
case CS_S10_IN_CLEANUP:
break;
default:
goto validate_pdu_done;
}
/*NOTREACHED*/
case ISCSI_OP_LOGIN_CMD:
case ISCSI_OP_LOGIN_RSP:
case CS_S3_XPT_UP:
case CS_S4_IN_LOGIN:
action = CA_FORWARD;
goto validate_pdu_done;
default:
goto validate_pdu_done;
}
/*NOTREACHED*/
default:
/* This should never happen -- we already checked above */
ASSERT(0);
/*NOTREACHED*/
}
if (action != CA_FORWARD) {
char *, reason_string);
}
return (action);
}
/* ARGSUSED */
void
{
/*
* Return the PDU to the caller indicating it was a protocol error.
* Caller can take appropriate action.
*/
}
void
{
/*
* Forward PDU to caller indicating it is a protocol error.
* Caller should take appropriate action.
*/
}
{
/*
* We may want to make this more complicated at some point but
* for now lets just call the client's notify function and return
* the status.
*/
}
static idm_status_t
{
/*
* On the initiator side the client will see this notification
* before the actual login succes PDU. This shouldn't be a big
* deal since the initiator drives the connection. It can simply
* wait for the login response then start sending SCSI commands.
* Kind ugly though compared with the way things work on target
* connections.
*/
if (rc != IDM_STATUS_SUCCESS) {
}
return (rc);
}
static void
{
/* Client can't "fail" CN_FFP_DISABLED */
}
static void
{
/*
* Currently it's not clear what we would do here -- since
* we went to the trouble of coding an "initial login" hook
* we'll leave it in for now. Remove before integration if
* it's not used for anything.
*/
}
static void
{
/*
* Save off CID
*/
}