OpenSolaris_b135/cmd/iscsi/iscsitgtd/iscsi_ffp.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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.
 */

/*
 * This file contains methods to handle the iSCSI Full Feature Phase aspects
 * of the protocol.
 */

#include <unistd.h>
#include <poll.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/types.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <utility.h>
#include <netinet/in.h>
#include <inttypes.h>
#include <sys/socket.h>
#include <sys/iscsi_protocol.h>

#include <arpa/inet.h>

#include "iscsi_ffp.h"
#include "iscsi_cmd.h"
#include "t10_spc.h"
#include "utility.h"
#include "iscsi_provider_impl.h"

static Boolean_t handle_text_msg(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_logout_msg(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_scsi_cmd(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_noop_cmd(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_scsi_data(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_task_mgt(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t dataout_delayed(iscsi_cmd_t *cmd, msg_type_t type);
void dataout_callback(t10_cmd_t *t, char *data, size_t *xfer);

Boolean_t
iscsi_full_feature(iscsi_conn_t *c)
{
	iscsi_hdr_t	h;
	Boolean_t	rval		= False;
	char		debug[128];
	char		*ahs		= NULL;
	int		cc;
	int		ahslen;

	if ((cc = recv(c->c_fd, &h, sizeof (h), MSG_WAITALL)) != sizeof (h)) {
		if (errno == ECONNRESET) {
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  full_feature -- initiator reset socket\n",
			    c->c_num);
		} else {
			(void) snprintf(debug, sizeof (debug),
			    "CON%x full_feature(got-%d, expect-%d), errno=%d\n",
			    c->c_num, cc, sizeof (h), errno);
		}
		queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
		conn_state(c, T8);
		return (False);
	}

	/*
	 * Look to see if there's an Additional Header Segment available.
	 * If so, read it in.
	 */
	if ((ahslen = (h.hlength * sizeof (uint32_t))) != 0) {
		if ((ahs = malloc(ahslen)) == NULL)
			return (False);
		if (recv(c->c_fd, ahs, ahslen, MSG_WAITALL) != ahslen) {
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  Failed to read in AHS", c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			free(ahs);
			return (False);
		}
	}

	(void) pthread_mutex_lock(&c->c_state_mutex);
	if (c->c_state != S5_LOGGED_IN && c->c_state != S7_LOGOUT_REQUESTED) {
		(void) snprintf(debug, sizeof (debug),
		    "CON%x  full_feature -- not in S5_LOGGED_IN state\n",
		    c->c_num);
		queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
		if (ahs != NULL)
			free(ahs);
		(void) pthread_mutex_unlock(&c->c_state_mutex);
		return (False);
	}
	(void) pthread_mutex_unlock(&c->c_state_mutex);

	if (c->c_header_digest == True) {
		uint32_t	crc_actual;
		uint32_t	crc_calculated;

		(void) recv(c->c_fd, (char *)&crc_actual,
		    sizeof (crc_actual), MSG_WAITALL);
		crc_calculated = iscsi_crc32c((void *)&h, sizeof (h));
		if (ahslen)
			crc_calculated = iscsi_crc32c_continued(ahs,
			    ahslen, crc_calculated);
		if (crc_actual != crc_calculated) {
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  CRC error: actual 0x%x v. calc 0x%x",
			    c->c_num, crc_actual, crc_calculated);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			if (ahs != NULL)
				free(ahs);
			return (False);
		}
	}

	if (c->c_sess->s_type == SessionDiscovery) {
		switch (h.opcode & ISCSI_OPCODE_MASK) {
		default:
			/*
			 * Need to handle the error case here.
			 */
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  Wrong opcode for Discovery, %d",
			    c->c_num, h.opcode & ISCSI_OPCODE_MASK);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			rval = False;
			break;

		case ISCSI_OP_LOGOUT_CMD:
			/*
			 * This will transition from S5_LOGGED_IN
			 * to S6_IN_LOGOUT to S1_FREE;
			 */
			rval = handle_logout_msg(c, &h, ahs, ahslen);
			break;

		case ISCSI_OP_TEXT_CMD:
			rval = handle_text_msg(c, &h, ahs, ahslen);
			break;
		}
	} else {
		iscsi_cmd_remove(c, ntohl(h.expstatsn));
		switch (h.opcode & ISCSI_OPCODE_MASK) {
		case ISCSI_OP_NOOP_OUT:
			rval = handle_noop_cmd(c, &h, ahs, ahslen);
			break;

		case ISCSI_OP_SCSI_CMD:
			rval = handle_scsi_cmd(c, &h, ahs, ahslen);
			break;

		case ISCSI_OP_SCSI_TASK_MGT_MSG:
			rval = handle_task_mgt(c, &h, ahs, ahslen);
			break;

		case ISCSI_OP_LOGIN_CMD:
			/*
			 * This is an illegal state transition. Should
			 * we drop the connection?
			 */
			break;

		case ISCSI_OP_TEXT_CMD:
			rval = handle_text_msg(c, &h, ahs, ahslen);
			break;

		case ISCSI_OP_SCSI_DATA:
			rval = handle_scsi_data(c, &h, ahs, ahslen);
			break;

		case ISCSI_OP_LOGOUT_CMD:
			/*
			 * This will transition from S5_LOGGED_IN
			 * to S6_IN_LOGOUT.
			 */
			rval = handle_logout_msg(c, &h, ahs, ahslen);
			break;

		case ISCSI_OP_SNACK_CMD:
		default:
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  Opcode: %d not handled",
			    c->c_num, h.opcode & ISCSI_OPCODE_MASK);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			conn_state(c, T8);
			rval = True;
			break;
		}
	}

	if (ahs != NULL)
		free(ahs);
	return (rval);
}

/*ARGSUSED*/
static Boolean_t
handle_task_mgt(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
	iscsi_scsi_task_mgt_hdr_t	*hp = (iscsi_scsi_task_mgt_hdr_t *)p;
	iscsi_scsi_task_mgt_rsp_hdr_t	*rsp;
	iscsi_cmd_t			*cmd;
	uint32_t			lun;
	Boolean_t			lu_reset	= False;

	if (spc_decode_lu_addr(&hp->lun[0], 8, &lun) == False)
		return (False);

	if (ISCSI_TASK_COMMAND_ENABLED()) {
		uiscsiproto_t info;

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = c->c_sess->s_t_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_lun = lun;

		info.uip_itt = hp->itt;
		info.uip_ttt = hp->itt;

		info.uip_cmdsn = ntohl(hp->cmdsn);
		info.uip_statsn = ntohl(hp->expstatsn);
		info.uip_datasn = ntohl(hp->expdatasn);

		info.uip_datalen = ntoh24(hp->dlength);
		info.uip_flags = 0;

		ISCSI_TASK_COMMAND(&info);
	}

	if ((rsp = calloc(1, sizeof (*rsp))) == NULL)
		return (False);

	rsp->opcode	= ISCSI_OP_SCSI_TASK_MGT_RSP;
	rsp->flags	= ISCSI_FLAG_FINAL;
	rsp->itt	= hp->itt;

	(void) pthread_mutex_lock(&c->c_mutex);
	rsp->statsn	= htonl(c->c_statsn++);
	(void) pthread_mutex_unlock(&c->c_mutex);

	(void) pthread_mutex_lock(&c->c_sess->s_mutex);
	if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
		c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
	(void) pthread_mutex_unlock(&c->c_sess->s_mutex);

	queue_prt(c->c_mgmtq, Q_CONN_NONIO,
	    "CON%x  PDU(Task Mgt): %s, cmdsn 0x%x\n",
	    c->c_num,
	    task_to_str(hp->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK),
	    ntohl(hp->cmdsn));

	switch (hp->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) {
	case ISCSI_TM_FUNC_ABORT_TASK:
		queue_prt(c->c_mgmtq, Q_CONN_NONIO,
		    "CON%x  Abort ITT 0x%x\n", c->c_num, hp->rtt);
		if ((cmd = iscsi_cmd_find(c, hp->rtt, FindITT)) == NULL) {
			queue_prt(c->c_mgmtq, Q_CONN_ERRS,
			    "CON%x  Invalid AbortTask rtt 0x%x\n",
			    c->c_num, hp->rtt);
			rsp->response = SCSI_TCP_TM_RESP_NO_TASK;
		} else {
			(void) pthread_mutex_lock(&c->c_mutex);
			iscsi_cmd_cancel(c, cmd);
			(void) pthread_mutex_unlock(&c->c_mutex);
			rsp->response = SCSI_TCP_TM_RESP_COMPLETE;
		}
		break;

	case ISCSI_TM_FUNC_ABORT_TASK_SET:
		/* ---- This is actually "Function not support" ---- */
		rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
		break;

	case ISCSI_TM_FUNC_CLEAR_ACA:
		/* ---- This is actually "Function not support" ---- */
		rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
		break;

	case ISCSI_TM_FUNC_CLEAR_TASK_SET:
		/* ---- This is actually "Function not support" ---- */
		rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
		break;

	case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
		lu_reset	= True;
	/*FALLTHRU*/
	case ISCSI_TM_FUNC_TARGET_WARM_RESET:
		(void) pthread_mutex_lock(&c->c_mutex);
		for (cmd = c->c_cmd_head; cmd; cmd = cmd->c_next) {
			if (((hp->function &
			    ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) ==
			    ISCSI_TM_FUNC_TARGET_WARM_RESET) ||
			    (lun == cmd->c_lun)) {
				iscsi_cmd_cancel(c, cmd);

			}
		}
		(void) pthread_mutex_unlock(&c->c_mutex);

		if (lu_reset == True)
			queue_message_set(c->c_sessq, 0, msg_reset_lu,
			    (void *)(uintptr_t)lun);
		else
			queue_message_set(c->c_sessq, 0, msg_reset_targ, 0);
		rsp->response = SCSI_TCP_TM_RESP_COMPLETE;
		break;

	case ISCSI_TM_FUNC_TARGET_COLD_RESET:
		/*
		 * According to the specification a cold reset should
		 * close *all* connections on the target, not just those
		 * for this current session.
		 */
		queue_message_set(c->c_sessq, 0, msg_reset_targ, (void *)1);
		conn_state(c, T8);
		break;

	case ISCSI_TM_FUNC_TASK_REASSIGN:
	default:
		/* ---- This is actually "Function not support" ---- */
		rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
		break;
	}

	(void) pthread_mutex_lock(&c->c_state_mutex);
	if (c->c_state == S5_LOGGED_IN)
		queue_message_set(c->c_dataq,
		    hp->opcode & ISCSI_OP_IMMEDIATE ? Q_HIGH : 0,
		    msg_send_pkt, rsp);
	(void) pthread_mutex_unlock(&c->c_state_mutex);
	return (True);
}

/*ARGSUSED*/
static Boolean_t
handle_noop_cmd(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
	iscsi_nop_out_hdr_t	*hp = (iscsi_nop_out_hdr_t *)p;
	iscsi_nop_in_hdr_t	*in;

	if (ISCSI_NOP_RECEIVE_ENABLED()) {
		uiscsiproto_t info;

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = c->c_sess->s_t_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_lun = 0;

		info.uip_itt = hp->itt;
		info.uip_ttt = hp->ttt;

		info.uip_cmdsn = ntohl(hp->cmdsn);
		info.uip_statsn = ntohl(hp->expstatsn);
		info.uip_datasn = 0;

		info.uip_datalen = ntoh24(hp->dlength);
		info.uip_flags = hp->flags;

		ISCSI_NOP_RECEIVE(&info);
	}

	/*
	 * Just an answer to our ping
	 */
	if (hp->ttt != ISCSI_RSVD_TASK_TAG)
		return (True);

	if ((in = calloc(1, sizeof (*in))) == NULL) {
		queue_prt(c->c_mgmtq, Q_CONN_ERRS,
		    "CON%x  NopIn -- failed to malloc space for header",
		    c->c_num);
		return (False);
	}

	in->opcode = ISCSI_OP_NOOP_IN;
	in->flags = ISCSI_FLAG_FINAL;
	/*
	 * Need to handle possible data associated with NOP-Out
	 */
	bcopy(hp->lun, in->lun, 8);
	in->itt		= hp->itt;
	in->ttt		= ISCSI_RSVD_TASK_TAG;
	(void) pthread_mutex_lock(&c->c_sess->s_mutex);
	if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
		c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
	(void) pthread_mutex_unlock(&c->c_sess->s_mutex);

	(void) pthread_mutex_lock(&c->c_state_mutex);
	if (c->c_state == S5_LOGGED_IN)
		queue_message_set(c->c_dataq,
		    hp->opcode & ISCSI_OP_IMMEDIATE ? Q_HIGH : 0,
		    msg_send_pkt, in);
	(void) pthread_mutex_unlock(&c->c_state_mutex);
	return (True);
}

/*ARGSUSED*/
static Boolean_t
handle_scsi_data(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
	iscsi_data_hdr_t	*hp = (iscsi_data_hdr_t *)p;
	int			dlen = ntoh24(hp->dlength);
	iscsi_cmd_t		*cmd;

	if (ISCSI_DATA_RECEIVE_ENABLED()) {
		uiscsiproto_t info;

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = c->c_sess->s_t_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_lun = 0;

		info.uip_itt = hp->itt;
		info.uip_ttt = hp->itt;

		info.uip_cmdsn = 0;
		info.uip_statsn = ntohl(hp->expstatsn);
		info.uip_datasn = ntohl(hp->datasn);

		info.uip_datalen = dlen;
		info.uip_flags = hp->flags;

		ISCSI_DATA_RECEIVE(&info);
	}

	if ((cmd = iscsi_cmd_find(c, hp->ttt, FindTTT)) == NULL) {
		queue_prt(c->c_mgmtq, Q_CONN_ERRS,
		    "CON%x  failed to find ttt 0x%x\n", c->c_num, hp->ttt);
		/*
		 * Need to handle error case.
		 */
		return (False);
	}
	cmd->c_opcode = hp->opcode & ISCSI_OPCODE_MASK;

	/*
	 * assert(cmd->c_lun == hp->lun[1]);
	 * Previously this check was done, but is caused a problem with
	 * the RedHat initiator. There was a discussion on the IPS alias
	 * around this very topic. Even though section 10.7.4 states:
	 *    "If the Target Transfer Tag is provided, then the LUN field
	 *    MUST hold a valid value and be consistent with whatever was
	 *    specified with the command; otherwise, the LUN field is
	 *    reserved."
	 * Everyone agreed though that for a DataOut command the LUN field
	 * wasn't required to be valid because the TTT gives the Target
	 * enough information to complete the command.
	 */
	assert(cmd->c_allegiance == c);
	assert(cmd->c_itt == hp->itt);

	cmd->c_offset_out	= ntohl(hp->offset);
	cmd->c_data_len		= dlen;
	(void) pthread_mutex_lock(&c->c_mutex);
	(void) pthread_mutex_lock(&c->c_state_mutex);
	if (c->c_state == S5_LOGGED_IN) {
		if (cmd->c_state != CmdCanceled) {
			t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T4);
		}
	}
	(void) pthread_mutex_unlock(&c->c_state_mutex);
	(void) pthread_mutex_unlock(&c->c_mutex);

#ifdef FULL_DEBUG
	queue_prt(c->c_mgmtq, Q_CONN_IO,
	    "CON%x  PDU(DataOut) TTT 0x%x, offset=0x%x, len=0x%x\n",
	    c->c_num, cmd->c_ttt, cmd->c_t10_cmd->c_offset, dlen);
#endif

	return (dataout_delayed(cmd, msg_cmd_data_out));
}

static Boolean_t
handle_scsi_cmd(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
	iscsi_scsi_cmd_hdr_t	*hp	= (iscsi_scsi_cmd_hdr_t *)p;
	int			dlen	= ntoh24(hp->dlength);
	iscsi_cmd_t		*cmd;

	(void) pthread_mutex_lock(&c->c_sess->s_mutex);
	if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
		c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
	(void) pthread_mutex_unlock(&c->c_sess->s_mutex);

	if ((cmd = iscsi_cmd_alloc(c, hp->opcode & ISCSI_OPCODE_MASK)) == NULL)
		return (False);

	bcopy(hp->scb, cmd->c_scb_default, sizeof (cmd->c_scb_default));
	cmd->c_scb	= cmd->c_scb_default;
	cmd->c_scb_len	= sizeof (cmd->c_scb_default);
	cmd->c_data_len	= dlen;

	if (ahslen) {

		/*
		 * Additional Header Section ----
		 *
		 * For Object Storage Devices the SCB is quite large. On
		 * the order of 140 bytes which means the data must be
		 * found in the AHS.
		 */
		uint16_t	hslen;
		uint16_t	next_seg;
		uint8_t		hstyp;

		do {
			/*
			 * Find this header segment's length and type
			 */
			bcopy(ahs, &hslen, sizeof (hslen));
			hslen = ntohs(hslen);
			hstyp = ahs[2];

			switch (hstyp) {
			/* ---- Extended CDB ---- */
			case 1:
				/*
				 * The hslen accounts for the reserved
				 * data byte in the segment. So the first
				 * sixteen bytes are in hp->scb with the
				 * remainder here. By only adding 15 bytes
				 * we allocate the correct amount of space
				 */
				cmd->c_scb_extended = malloc(hslen + 15);
				cmd->c_scb_len = hslen + 15;
				if (cmd->c_scb_extended == NULL)
					return (False);

				/*
				 * First 16 bytes of extended SCB are
				 * found in the normal location.
				 */
				bcopy(hp->scb, cmd->c_scb_extended, 16);
				bcopy(&ahs[4], &cmd->c_scb_extended[16],
				    hslen - 16);
				cmd->c_scb = cmd->c_scb_extended;
				break;

			/* ---- Expected bidirectional read data len ---- */
			case 2:
				/*
				 * We shouldn't need this since we're
				 * not prealloc'ing resources. If that should
				 * change or the need for error checking
				 * here's the spot to locate the data.
				 */
				break;
			}

			/*
			 * hslen contains the effective length in bytes of
			 * segment, excluding type and length (not including
			 * padding). Each segment is padded to a 4 byte
			 * boundary.
			 */
			next_seg = ((hslen + sizeof (hslen) +
			    sizeof (hstyp) + 3) & ~3);
			ahs += next_seg;
			ahslen -= next_seg;

		} while (ahslen);
	}

	/*
	 * XXX Need to handle error case better.
	 */
	if (spc_decode_lu_addr(&hp->lun[0], sizeof (hp->lun), &cmd->c_lun) ==
	    False) {
		return (False);
	}

	if (ISCSI_SCSI_COMMAND_ENABLED()) {
		uiscsiproto_t info;
		uiscsicmd_t uc;

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = c->c_sess->s_t_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_lun = cmd->c_lun;

		info.uip_itt = hp->itt;
		info.uip_ttt = ISCSI_RSVD_TASK_TAG;

		info.uip_cmdsn = ntohl(hp->cmdsn);
		info.uip_statsn = ntohl(hp->expstatsn);
		info.uip_datasn = 0;

		info.uip_datalen = dlen;
		info.uip_flags = hp->flags;

		uc.uic_len = cmd->c_scb_len;
		uc.uic_cdb = cmd->c_scb;

		ISCSI_SCSI_COMMAND(&info, &uc);
	}

	cmd->c_itt		= hp->itt;
	cmd->c_cmdsn		= ntohl(hp->cmdsn);
	cmd->c_dlen_expected	= ntohl(hp->data_length);
	cmd->c_writeop		= hp->flags & ISCSI_FLAG_CMD_WRITE ?
	    True : False;

#ifdef FULL_DEBUG
	queue_prt(c->c_mgmtq, Q_CONN_IO,
	    "CON%x  PDU(SCSI Cmd, TA=%d) CmdSN 0x%x ITT 0x%x TTT 0x%x "
	    "LUN[%02x] id=%p\n", c->c_num,
	    hp->flags & ISCSI_FLAG_CMD_ATTR_MASK, cmd->c_cmdsn, cmd->c_itt,
	    cmd->c_ttt, cmd->c_lun, cmd);
#endif

	if (dlen && (hp->flags & ISCSI_FLAG_CMD_WRITE)) {
		/*
		 * NOTE: This should only occur if ImmediateData==Yes.
		 * We can handle this even if the initiator violates
		 * the specification so no need to worry. Use the rule
		 * of "Be strict in what is sent, but lenient in what
		 * is accepted."
		 */
		return (dataout_delayed(cmd, msg_cmd_send));
	} else {
		(void) pthread_mutex_lock(&c->c_state_mutex);
		if (c->c_state == S5_LOGGED_IN)
			queue_message_set(c->c_sessq, 0,
			    msg_cmd_send, (void *)cmd);
		(void) pthread_mutex_unlock(&c->c_state_mutex);
		return (True);
	}
}

/*
 * []----
 * | handle_text_msg -- process incoming test parameters
 * |
 * | NOTE: Need to handle continuation packets sent by the initiator.
 * []----
 */
/*ARGSUSED*/
static Boolean_t
handle_text_msg(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
	iscsi_text_rsp_hdr_t	rsp;
	iscsi_text_hdr_t	*hp		= (iscsi_text_hdr_t *)p;
	char			*text		= NULL;
	int			text_length	= 0;
	Boolean_t		release_at_end	= False;
	int			dlen		= ntoh24(hp->dlength);

	if (ISCSI_TEXT_COMMAND_ENABLED()) {
		uiscsiproto_t info;
		char nil = '\0';

		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = c->c_sess->s_t_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_target = &nil;
		info.uip_lun = 0;

		info.uip_itt = hp->itt;
		info.uip_ttt = hp->ttt;

		info.uip_cmdsn = ntohl(hp->cmdsn);
		info.uip_statsn = ntohl(hp->expstatsn);
		info.uip_datasn = 0;

		info.uip_datalen = dlen;
		info.uip_flags = hp->flags;

		ISCSI_TEXT_COMMAND(&info);
	}

	bzero(&rsp, sizeof (rsp));
	rsp.opcode	= ISCSI_OP_TEXT_RSP;
	rsp.itt		= hp->itt;

	queue_prt(c->c_mgmtq, Q_CONN_NONIO, "CON%x  PDU(Text Message)\n",
	    c->c_num);

	/*
	 * Need to determine if this incoming text PDU is an initial message
	 * or a continuation.
	 */
	if (hp->ttt == ISCSI_RSVD_TASK_TAG) {

		/* ---- Initial text PDU, so parse the incoming data ---- */
		if (parse_text(c, dlen, &text, &text_length, NULL) == False) {
			queue_prt(c->c_mgmtq, Q_CONN_ERRS,
			    "Failed to parse Text\n");
			if (text) {
				/*
				 * It's possible that we started to create
				 * a response, but yet an error occurred.
				 * Release the partial text response if that
				 * occurred.
				 */
				free(text);
			}
			return (False);
		}

		/*
		 * 10.11.4 --
		 * When the target receives a Text Request with the Target
		 * Transfer Tag set to the reserved value of 0xffffffff, it
		 * resets its internal information (resets state) associated
		 * with the given Initiator Task Tag (restarts the negotiation).
		 */
		if (c->c_text_area != NULL)
			free(c->c_text_area);

		c->c_text_area = text;
		if (text_length > c->c_max_recv_data) {

			/*
			 * Too much data to send at once, break it up into
			 * multiple transfers.
			 */
			rsp.flags	= ISCSI_FLAG_TEXT_CONTINUE;
			rsp.ttt		= 1;
			c->c_text_len	= text_length;
			text_length	= c->c_max_recv_data;
			c->c_text_sent	= text_length;
		} else {
			rsp.flags	= ISCSI_FLAG_FINAL;
			rsp.ttt		= ISCSI_RSVD_TASK_TAG;
			release_at_end	= True;
		}
	} else {

		/* ---- Continuation of previous text request ---- */
		text_length	= c->c_text_len - c->c_text_sent;
		text		= c->c_text_area + c->c_text_sent;
		if (text_length > c->c_max_recv_data) {
			rsp.flags	= ISCSI_FLAG_TEXT_CONTINUE;
			rsp.ttt		= 1;
			text_length	= c->c_max_recv_data;
			c->c_text_sent	+= text_length;
		} else {
			rsp.flags	= ISCSI_FLAG_FINAL;
			rsp.ttt		= ISCSI_RSVD_TASK_TAG;
			release_at_end	= True;
		}
	}

	queue_prt(c->c_mgmtq, Q_CONN_NONIO,
	    "CON%x  Text PDU: flags=0x%02x, ttt=0x%08x, len=%d\n",
	    c->c_num, rsp.flags, rsp.ttt, text_length);

	hton24(rsp.dlength, text_length);
	(void) pthread_mutex_lock(&c->c_mutex);
	rsp.statsn	= htonl(c->c_statsn++);
	(void) pthread_mutex_lock(&c->c_sess->s_mutex);
	if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
		c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
	rsp.maxcmdsn	= htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn);
	rsp.expcmdsn	= htonl(c->c_sess->s_seencmdsn + 1);
	(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
	(void) pthread_mutex_unlock(&c->c_mutex);

	if (ISCSI_TEXT_RESPONSE_ENABLED()) {
		uiscsiproto_t info;
		char nil = '\0';

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = c->c_sess->s_t_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_target = &nil;
		info.uip_lun = 0;

		info.uip_itt = rsp.itt;
		info.uip_ttt = rsp.ttt;

		info.uip_cmdsn = ntohl(rsp.expcmdsn);
		info.uip_statsn = ntohl(rsp.statsn);
		info.uip_datasn = 0;

		info.uip_datalen = text_length;
		info.uip_flags = rsp.flags;

		ISCSI_TEXT_RESPONSE(&info);
	}

	send_iscsi_pkt(c, (iscsi_hdr_t *)&rsp, text);

	if (release_at_end == True) {
		free(c->c_text_area);
		c->c_text_area = NULL;
	}
	return (True);
}

/*ARGSUSED*/
static Boolean_t
handle_logout_msg(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
	iscsi_logout_rsp_hdr_t	*rsp;
	iscsi_logout_hdr_t	*hp = (iscsi_logout_hdr_t *)p;
	char			debug[80];

	if (ISCSI_LOGOUT_COMMAND_ENABLED()) {
		uiscsiproto_t info;
		char nil = '\0';

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = c->c_sess->s_t_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_initiator = c->c_sess->s_i_name;
		info.uip_target = &nil;
		info.uip_lun = 0;

		info.uip_itt = hp->itt;
		info.uip_ttt = ISCSI_RSVD_TASK_TAG;

		info.uip_cmdsn = ntohl(hp->cmdsn);
		info.uip_statsn = ntohl(hp->expstatsn);
		info.uip_datasn = 0;

		info.uip_datalen = ntoh24(hp->dlength);
		info.uip_flags = hp->flags;

		ISCSI_LOGOUT_COMMAND(&info);
	}

	if ((rsp = calloc(1, sizeof (*rsp))) == NULL)
		return (False);

	(void) snprintf(debug, sizeof (debug),
	    "CON%x  PDU(Logout Request)", c->c_num);
	queue_str(c->c_mgmtq, Q_CONN_NONIO, msg_log, debug);

	(void) pthread_mutex_lock(&c->c_mutex);
	(void) pthread_mutex_lock(&c->c_sess->s_mutex);
	if (hp->cmdsn > c->c_sess->s_seencmdsn)
		c->c_sess->s_seencmdsn = htonl(hp->cmdsn);
	rsp->expcmdsn = htonl(c->c_sess->s_seencmdsn + 1);
	rsp->maxcmdsn = htonl(iscsi_cmd_window(c) +
	    c->c_sess->s_seencmdsn);
	(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
	(void) pthread_mutex_unlock(&c->c_mutex);

	rsp->opcode	= ISCSI_OP_LOGOUT_RSP;
	rsp->flags	= ISCSI_FLAG_FINAL;
	rsp->itt	= hp->itt;
	(void) pthread_mutex_lock(&c->c_mutex);
	rsp->statsn	= htonl(c->c_statsn++);
	(void) pthread_mutex_unlock(&c->c_mutex);

	c->c_last_pkg	= (iscsi_hdr_t *)rsp;

	/*
	 * Call the state transition last. This will send out
	 * an asynchronous message to shutdown the session and STE.
	 * Once that's complete a shutdown reply will be sent to
	 * the transmit connection thread. That will cause another
	 * transition to T13 which expects to send out this logout
	 * response.
	 */
	if (c->c_state == S7_LOGOUT_REQUESTED)
		conn_state(c, T10);
	else
		conn_state(c, T9);

	return (True);
}

/*
 * dataout_delayed -- possibly copy data from initiator
 *
 * If DataDigests are enabled copy the data from the socket into a buffer
 * and perform the CRC check now.
 *
 * If MaxConnections==1 don't copy the data now and wait until the STE is
 * ready to copy the data directly from the socket to it's final location.
 * This is extremely beneficial when using mmap'd data.
 * NOTE:
 *    (1) For this to work we must not use the queues and instead
 *        call the STE functions directly. If the queues are used
 *        this routine must pause until STE processes the data to
 *        prevent this thread from attempting to read data from
 *        the socket as if it's the next PDU header.
 *    (2) Currently we don't call STE directly. To prevent a performance
 *        issue we'll have the code in place to support calling
 *        STE directly, but any time MaxConnections is greater than 0
 *        we'll copy the buffer. This will be removed at some future
 *        point.
 */
static Boolean_t
dataout_delayed(iscsi_cmd_t *cmd, msg_type_t type)
{
	iscsi_conn_t	*c	= cmd->c_allegiance;
	int		dlen	= cmd->c_data_len;
	int		cc;
	uint32_t	crc_calc;
	uint32_t	crc_actual;
	char		pad_buf[ISCSI_PAD_WORD_LEN - 1];
	char		pad_len;
	char		debug[80];

	cmd->c_dataout_cb = dataout_callback;

	if (cmd->c_data == NULL) {
		if ((cmd->c_data = (char *)malloc(dlen)) == NULL) {
			(void) pthread_mutex_lock(&c->c_mutex);
			t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
			iscsi_cmd_free(c, cmd);
			(void) pthread_mutex_unlock(&c->c_mutex);
			return (False);
		}
		cmd->c_data_alloc = True;
	}

	if ((cc = recv(c->c_fd, cmd->c_data, dlen, MSG_WAITALL)) != dlen) {
		if (errno == ECONNRESET) {
			queue_prt(c->c_mgmtq, Q_CONN_ERRS,
			    "CON%x  dataout_delayed -- "
			    "initiator reset socket\n", c->c_num);
		} else {
			queue_prt(c->c_mgmtq, Q_CONN_ERRS,
			    "CON%x  recv(got-%d, expect-%d), errno=%d\n",
			    c->c_num, cc, dlen, errno);
		}

		(void) pthread_mutex_lock(&c->c_mutex);
		t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
		iscsi_cmd_free(c, cmd);
		(void) pthread_mutex_unlock(&c->c_mutex);
		conn_state(c, T8);
		return (True);
	}

	pad_len = ((ISCSI_PAD_WORD_LEN -
	    (dlen & (ISCSI_PAD_WORD_LEN - 1))) & (ISCSI_PAD_WORD_LEN - 1));

	if (pad_len) {
		if (recv(c->c_fd, pad_buf, pad_len, MSG_WAITALL) != pad_len) {
			if (errno == ECONNRESET) {
				queue_prt(c->c_mgmtq, Q_CONN_ERRS,
				    "CON%x  dataout_delayed -- "
				    "initiator reset socket\n", c->c_num);
			} else {
				queue_prt(c->c_mgmtq, Q_CONN_ERRS,
				    "CON%x Pad Word read errno=%d\n", c->c_num,
				    errno);
			}

			(void) pthread_mutex_lock(&c->c_mutex);
			t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
			iscsi_cmd_free(c, cmd);
			(void) pthread_mutex_unlock(&c->c_mutex);
			conn_state(c, T8);
			return (True);
		}
	}

	if (c->c_data_digest == True) {
		if (recv(c->c_fd, (char *)&crc_actual, sizeof (crc_actual),
		    MSG_WAITALL) != sizeof (crc_actual)) {
			if (errno == ECONNRESET) {
				queue_prt(c->c_mgmtq, Q_CONN_ERRS,
				    "CON%x  dataout_delayed -- "
				    "initiator reset socket\n", c->c_num);
			} else {
				queue_prt(c->c_mgmtq, Q_CONN_ERRS,
				    "CON%x  CRC32 read errno=%d\n", c->c_num,
				    errno);
			}

			(void) pthread_mutex_lock(&c->c_mutex);
			t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
			iscsi_cmd_free(c, cmd);
			(void) pthread_mutex_unlock(&c->c_mutex);
			conn_state(c, T8);
			return (True);
		}
		crc_calc = iscsi_crc32c((void *)cmd->c_data, dlen);
		if (crc_calc != crc_actual) {

			(void) snprintf(debug, sizeof (debug),
			    "CON%x  CRC Error: actual %x vs. calc 0x%x",
			    c->c_num, crc_actual, crc_calc);

			/*
			 * NOTE: Need to think about this one some more.
			 * Just because we get a data error doesn't mean
			 * we should drop the connection. Look at the
			 * spec and determine what's the appropriate
			 * error recovery for this issue.
			 */
			(void) pthread_mutex_lock(&c->c_mutex);
			t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
			iscsi_cmd_free(c, cmd);
			(void) pthread_mutex_unlock(&c->c_mutex);
			conn_state(c, T8);
			return (True);
		}
	}

	/*
	 * We'll update the offset with the amount of data that
	 * has been received. During a SCSI response PDU this value
	 * will be used to determine if there's an overrun condition.
	 */
	cmd->c_offset_out += dlen;

	(void) pthread_mutex_lock(&c->c_mutex);
	(void) pthread_mutex_lock(&c->c_state_mutex);
	if (c->c_state == S5_LOGGED_IN) {
		if ((cmd->c_state == CmdCanceled) &&
		    (type == msg_cmd_data_out))
			t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
		else
			queue_message_set(c->c_sessq, 0, type, (void *)cmd);
	} else if (cmd->c_state == CmdCanceled) {
		t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
	}
	(void) pthread_mutex_unlock(&c->c_state_mutex);
	(void) pthread_mutex_unlock(&c->c_mutex);

	/*
	 * The else case here is if we're calling STE directly and the data
	 * will be read from the socket when STE is ready for it.
	 */

	return (True);
}

/*
 * []----
 * | dataout_callback -- copy data from socket to emulation buffer
 * []----
 */
void
dataout_callback(t10_cmd_t *t, char *data, size_t *xfer)
{
	iscsi_cmd_t	*cmd	= (iscsi_cmd_t *)T10_TRANS_ID(t);
	iscsi_conn_t	*c	= cmd->c_allegiance;
	int		dlen	= cmd->c_data_len;
	int		cc;
	char		pad_buf[ISCSI_PAD_WORD_LEN - 1];
	char		pad_len = 0;

	pad_len = ((ISCSI_PAD_WORD_LEN -
	    (dlen & (ISCSI_PAD_WORD_LEN - 1))) &
	    (ISCSI_PAD_WORD_LEN - 1));


	if (T10_DATA(t) != NULL) {
		assert(T10_DATA(t) == cmd->c_data);
		assert(cmd->c_data_alloc == True);
		free(T10_DATA(t));
		T10_DATA(t)		= NULL;
		cmd->c_data		= NULL;
		cmd->c_data_alloc	= False;
		return;
	}

	if ((cc = recv(c->c_fd, data, dlen, MSG_WAITALL)) != dlen) {
		if (errno == ECONNRESET) {
			queue_prt(c->c_mgmtq, Q_CONN_ERRS,
			    "CON%x  data_callback -- initiator reset socket\n",
			    c->c_num);
		} else {
			queue_prt(c->c_mgmtq, Q_CONN_ERRS,
			    "CON%x  recv(got-%d, expect-%d) errno=%d",
			    c->c_num, cc, dlen, errno);
		}

		conn_state(c, T8);
		goto finish;
	}

	if (pad_len) {
		if (recv(c->c_fd, pad_buf, pad_len, MSG_WAITALL) != pad_len) {
			if (errno == ECONNRESET) {
				queue_prt(c->c_mgmtq, Q_CONN_ERRS,
				    "CON%x  data_callback -- "
				    "initiator reset socket\n", c->c_num);
			} else {
				queue_prt(c->c_mgmtq, Q_CONN_ERRS,
				    "CON%x data_callback -- "
				    "pad read errno=%d\n", c->c_num, errno);
			}
			conn_state(c, T8);
			goto finish;
		}
	}

finish:
	*xfer = cc;
	/* ---- Send msg that receive side of the connection can go ---- */
	(void) sema_post(&c->c_datain);
}