/* * IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. By downloading, copying, installing or * using the software you agree to this license. If you do not agree to this license, do not download, install, * copy or use the software. * * Intel License Agreement * * Copyright (c) 2000, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: * * -Redistributions of source code must retain the above copyright notice, this list of conditions and the * following disclaimer. * * -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * -The name of Intel Corporation may not be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include <sys/types.h> #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> #endif #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif #ifdef HAVE_NETINET_TCP_H #include <netinet/tcp.h> #endif #ifdef HAVE_SIGNAL_H #include <signal.h> #endif #include <stdio.h> #include <stdlib.h> #ifdef HAVE_STRING_H #include <string.h> #endif #include <unistd.h> #include "iscsi.h" #include "initiator.h" static initiator_target_t g_target[CONFIG_INITIATOR_NUM_TARGETS]; /* * Globals */ static uint32_t g_tag; static iscsi_spin_t g_tag_spin; static hash_t g_tag_hash; static iscsi_worker_t g_enqueue_worker; static iscsi_queue_t g_enqueue_q; static iscsi_queue_t g_session_q; static int g_initiator_state; static char *gfilename; /* Testing of initiator_abort */ static initiator_cmd_t *g_cmd = NULL; /* * Enqueue worker functions. The enqueue worker is responsible for enqueing * all iSCSI commands to one of the Tx workers. It is also the thread * responsible for initializing sessions, discovering targets and getting * each session into full feature phase. */ static int enqueue_worker_proc(void *); static int login_phase_i(initiator_session_t *, char *, int); static int logout_phase_i(initiator_session_t *); /* * Tx functions. initiator_cmd_t pointers are enqueued to the Tx worker * for a given session by the enqueue worker. The Tx worker will send out these * commands and wait for the Rx worker to process the response. The pointer is * inserted into the hashtable g_tag_hash, keyed by the initiator tag of the iSCSI * commands. */ static int tx_worker_proc_i(void *); static int text_command_i(initiator_cmd_t *); static int login_command_i(initiator_cmd_t *); static int logout_command_i(initiator_cmd_t *); static int scsi_command_i(initiator_cmd_t *); static int nop_out_i(initiator_cmd_t *); /* * Rx functions. Upon receipt of an incoming PDU, the Rx worker will first * extract the tag (if it exists for the PDU) and then the associated * initiator_cmd_t pointer stored in the hash table. One of Rx functions * will be called to processs the PDU. The Rx worker will invoke the callback * function associated with the command once the command has been retired. */ static int rx_worker_proc_i(void *); static int login_response_i(initiator_session_t *, initiator_cmd_t *, uint8_t *); static int text_response_i(initiator_session_t *, initiator_cmd_t *, uint8_t *); static int logout_response_i(initiator_session_t *, initiator_cmd_t *, uint8_t *); static int scsi_response_i(initiator_session_t *, initiator_cmd_t *, uint8_t *); static int scsi_read_data_i(initiator_session_t *, initiator_cmd_t *, uint8_t *); static int scsi_r2t_i(initiator_session_t *, initiator_cmd_t *, uint8_t *); static int nop_in_i(initiator_session_t *, initiator_cmd_t *, uint8_t *); static int reject_i(initiator_session_t *, uint8_t *); static int async_msg_i(initiator_session_t *, uint8_t *); /* * Misc. Prototypes */ static int session_init_i(initiator_session_t **, uint64_t ); static int session_destroy_i(initiator_session_t *); static int wait_callback_i(void *); static int discovery_phase(int, strv_t *); /* * Private Functions */ #if 0 static void dump_session(initiator_session_t * sess) { iscsi_parameter_value_t *vp; iscsi_parameter_t *ip; for (ip = sess->params ; ip ; ip = ip->next) { printf("Key: %s Type: %d\n",ip->key,ip->type); for (vp = ip->value_l ; vp ; vp = vp->next) { printf("Value: %s\n",vp->value); } } } #endif /* This function reads the target IP and target name information */ /* from the input configuration file, and populates the */ /* g_target data structure fields. */ static int get_target_config(const char *hostname, int port) { int i; for (i = 0 ; i < CONFIG_INITIATOR_NUM_TARGETS ; i++) { (void) strlcpy(g_target[i].name, hostname, sizeof(g_target[i].name)); g_target[i].port = port; } return 0; } static int session_init_i(initiator_session_t ** sess, uint64_t isid) { initiator_session_t *s; iscsi_parameter_t **l; char *user; int auth_type; int mutual_auth; int one = 1; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initializing session %llu\n", isid); /* Get free session */ if ((*sess = iscsi_queue_remove(&g_session_q)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_remove() failed\n"); return -1; } s = *sess; user = NULL; if (s->sess_params.cred.user) { user = s->sess_params.cred.user; } auth_type = s->sess_params.auth_type; mutual_auth = s->sess_params.mutual_auth; (void) memset(s, 0x0, sizeof(*s)); s->state = INITIATOR_SESSION_STATE_INITIALIZING; s->isid = s->tx_worker.id = s->rx_worker.id = isid; s->cmds = NULL; s->sess_params.cred.user = user; s->sess_params.auth_type = auth_type; s->sess_params.mutual_auth = mutual_auth; iscsi_spin_init(&s->cmds_spin); g_target[isid].has_session = 1; /* Create socket */ if (!iscsi_sock_create(&s->sock)) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_create() failed\n"); return -1; } if (!iscsi_sock_setsockopt(&s->sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one))) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_setsockopt() failed\n"); return -1; } /* Initialize wait queues */ ISCSI_MUTEX_INIT(&s->tx_worker.work_mutex, return -1); ISCSI_COND_INIT(&s->tx_worker.work_cond, return -1); ISCSI_MUTEX_INIT(&s->tx_worker.exit_mutex, return -1); ISCSI_COND_INIT(&s->tx_worker.exit_cond, return -1); ISCSI_MUTEX_INIT(&s->rx_worker.work_mutex, return -1); ISCSI_COND_INIT(&s->rx_worker.work_cond, return -1); ISCSI_MUTEX_INIT(&s->rx_worker.exit_mutex, return -1); ISCSI_COND_INIT(&s->rx_worker.exit_cond, return -1); /* Build parameter list */ /* * ISCSI_PARAM_TYPE_LIST format: <type> <key> <dflt> <valid list values> * ISCSI_PARAM_TYPE_BINARY format: <type> <key> <dflt> <valid binary values> * ISCSI_PARAM_TYPE_NUMERICAL format: <type> <key> <dflt> <max> * ISCSI_PARAM_TYPE_DECLARATIVE format: <type> <key> <dflt> "" */ s->params = NULL; l = &(s->params); /* CHAP Support Parameters */ PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_LIST, "AuthMethod", "None", "CHAP,None", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_LIST, "CHAP_A", "None", "5", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "CHAP_N", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "CHAP_R", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "CHAP_I", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "CHAP_C", "", "", return -1); /* CHAP Support Parameters */ PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_LIST, "HeaderDigest", "None", "None", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_LIST, "DataDigest", "None", "None", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL, "MaxConnections", "1", "1", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "SendTargets", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARE_MULTI, "TargetName", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "InitiatorName", "iqn.1994-04.org.NetBSD:iscsi-initiator", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "TargetAlias", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "InitiatorAlias", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARE_MULTI, "TargetAddress", "", "", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_BINARY_OR, "InitialR2T", "Yes", "Yes,No", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_BINARY_AND, "ImmediateData", "Yes", "Yes,No", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL_Z, "MaxRecvDataSegmentLength", "8192", "16777215", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL_Z, "MaxBurstLength", "262144", "16777215", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL_Z, "FirstBurstLength", "65536", "16777215", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "TargetPortalGroupTag", "1", "65535", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL, "DefaultTime2Wait", "2", "2", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL, "DefaultTime2Retain", "20", "20", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL, "MaxOutstandingR2T", "1", "1", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_BINARY_OR, "DataPDUInOrder", "Yes", "Yes,No", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_BINARY_OR, "DataSequenceInOrder", "Yes", "Yes,No", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_NUMERICAL, "ErrorRecoveryLevel", "0", "0", return -1); PARAM_LIST_ADD(l, ISCSI_PARAM_TYPE_DECLARATIVE, "SessionType", "Normal", "Normal,Discovery", return -1); /* Start Tx worker */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "starting Tx worker %llu\n", isid); if (iscsi_queue_init(&s->tx_queue, CONFIG_INITIATOR_QUEUE_DEPTH) == -1) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_init() failed\n"); return -1; } ISCSI_LOCK(&s->tx_worker.exit_mutex, return -1); if (iscsi_thread_create(&s->tx_worker.thread, (void *) tx_worker_proc_i, &s->tx_worker) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_threads_create() failed\n"); return -1; } ISCSI_WAIT(&s->tx_worker.exit_cond, &s->tx_worker.exit_mutex, return -1); ISCSI_UNLOCK(&s->tx_worker.exit_mutex, return -1); if (s->state == INITIATOR_SESSION_STATE_DESTROYING) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "session %llu is being destroyed, exiting\n", isid); return -1; } if (s->tx_worker.state & ISCSI_WORKER_STATE_ERROR) { iscsi_trace_error(__FILE__, __LINE__, "Tx worker %llu started with an error\n", isid); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "got signal from Tx worker\n"); s->state = INITIATOR_SESSION_STATE_INITIALIZED; return 0; } static int session_destroy_i(initiator_session_t * sess) { initiator_cmd_t *ptr; uint64_t isid = sess->isid; if (sess == NULL) { iscsi_trace_error(__FILE__, __LINE__, "session pointer is NULL\n"); return -1; } if (g_target[sess->isid].has_session == 0) { iscsi_trace_error(__FILE__, __LINE__, "g_target[%llu].has_session==0??\n", sess->isid); return -1; } sess->state = INITIATOR_SESSION_STATE_DESTROYING; /* Abort all outstanding commands */ for (ptr = sess->cmds; ptr != NULL; ptr = ptr->next) { if (initiator_abort(ptr) != 0) { iscsi_trace_error(__FILE__, __LINE__, "initiator_abort() failed\n"); return -1; } } if (sess->tx_worker.state & ISCSI_WORKER_STATE_STARTED) { if (sess->tx_worker.state & ISCSI_WORKER_STATE_EXITING) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Tx worker %llu already signalled for exit\n", sess->isid); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "signaling Tx worker %llu into exiting state\n", sess->isid); ISCSI_LOCK(&sess->tx_worker.work_mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "signaling socket shutdown to Tx worker %llu\n", sess->isid); if (iscsi_sock_shutdown(sess->sock, 1) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_shutdown() failed\n"); } ISCSI_SIGNAL(&sess->tx_worker.work_cond, return -1); ISCSI_UNLOCK(&sess->tx_worker.work_mutex, return -1); } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Checking exit condition of Tx worker\n"); while ((sess->tx_worker.state & ISCSI_WORKER_STATE_EXITING) != ISCSI_WORKER_STATE_EXITING) { ISCSI_SPIN; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Tx worker %llu has exited\n", sess->isid); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Tx worker was not started. Nothing to signal\n"); } /* Destroy Tx state */ while ((ptr = iscsi_queue_remove(&sess->tx_queue)) != NULL) { ptr->status = -1; if (ptr->callback && ((*ptr->callback)(ptr) != 0)) { iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); } } iscsi_queue_destroy(&sess->tx_queue); if (sess->rx_worker.state & ISCSI_WORKER_STATE_STARTED) { if (sess->rx_worker.state & ISCSI_WORKER_STATE_EXITING) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Rx worker %llu already signalled for exit\n", sess->isid); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "signaling Rx worker %llu into exiting state\n", sess->isid); if (iscsi_sock_shutdown(sess->sock, 0) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_shutdown() failed\n"); } } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Checking exit condition of Rx worker\n"); while ((sess->rx_worker.state & ISCSI_WORKER_STATE_EXITING) != ISCSI_WORKER_STATE_EXITING) { ISCSI_SPIN; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Rx worker %llu has exited\n", sess->isid); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Rx worker was not started. Nothing to signal\n"); } /* Close socket */ if (iscsi_sock_close(sess->sock) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_close() failed\n"); return -1; } /* Destroy wait queues */ ISCSI_MUTEX_DESTROY(&sess->tx_worker.work_mutex, return -1); ISCSI_COND_DESTROY(&sess->tx_worker.work_cond, return -1); ISCSI_MUTEX_DESTROY(&sess->tx_worker.exit_mutex, return -1); ISCSI_COND_DESTROY(&sess->tx_worker.exit_cond, return -1); ISCSI_MUTEX_DESTROY(&sess->rx_worker.work_mutex, return -1); ISCSI_COND_DESTROY(&sess->rx_worker.work_cond, return -1); ISCSI_MUTEX_DESTROY(&sess->rx_worker.exit_mutex, return -1); ISCSI_COND_DESTROY(&sess->rx_worker.exit_cond, return -1); /* Destroy param list */ PARAM_LIST_DESTROY(sess->params, return -1); /* Enqueue session to free list */ if (iscsi_queue_insert(&g_session_q, sess) == -1) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_insert() failed\n"); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "session %p destroyed and requeued\n", sess); g_target[isid].has_session = 0; return 0; } #define IS_DISCOVERY 1 #define IS_SECURITY 1 enum { SESS_TYPE_DISCOVERY = 1, SESS_TYPE_NORMAL = 2, SESS_TYPE_NONE = 3 }; static int params_out(initiator_session_t * sess, char *text, int *len, int textsize, int sess_type, int security) { if (security == IS_SECURITY) { PARAM_TEXT_ADD(sess->params, "InitiatorName", "iqn.1994-04.org.NetBSD.iscsi-initiator:agc", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "InitiatorAlias", "NetBSD", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "AuthMethod", "CHAP,None", text, len, textsize, 1, return -1); } else { PARAM_TEXT_ADD(sess->params, "HeaderDigest", "None", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "DataDigest", "None", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "MaxConnections", "1", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "InitialR2T", "Yes", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "ImmediateData", "Yes", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "MaxRecvDataSegmentLength", "8192", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "FirstBurstLength", "65536", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "MaxBurstLength", "262144", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "DefaultTime2Wait", "2", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "DefaultTime2Retain", "20", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "MaxOutstandingR2T", "1", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "DataPDUInOrder", "No", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "DataSequenceInOrder", "No", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "ErrorRecoveryLevel", "0", text, len, textsize, 1, return -1); } switch (sess_type) { case SESS_TYPE_DISCOVERY: PARAM_TEXT_ADD(sess->params, "SessionType", "Discovery", text, len, textsize, 1, return -1); break; case SESS_TYPE_NORMAL: PARAM_TEXT_ADD(sess->params, "SessionType", "Normal", text, len, textsize, 1, return -1); PARAM_TEXT_ADD(sess->params, "TargetName", g_target[sess->isid].TargetName, text, len, textsize, 1, return -1); break; default: break; } PARAM_TEXT_PARSE(sess->params, &sess->sess_params.cred, text, *len, NULL, NULL, 0, 1, return -1); return 0; } static int full_feature_negotiation_phase_i(initiator_session_t * sess, char *text, int text_len) { initiator_cmd_t *cmd = NULL; iscsi_text_cmd_args_t *text_cmd = NULL; initiator_wait_t wait; /* Allocate command pointers */ if ((cmd = iscsi_malloc_atomic(sizeof(initiator_cmd_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); return -1; } (void) memset(cmd, 0x0, sizeof(*cmd)); if ((text_cmd = iscsi_malloc_atomic(sizeof(iscsi_text_cmd_args_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); if (cmd != NULL) iscsi_free_atomic(cmd); /* initiator command */ return -1; } #define FFN_ERROR {if (cmd != NULL) iscsi_free_atomic(cmd); if (text_cmd != NULL) iscsi_free_atomic(text_cmd); return -1;} (void) memset(text_cmd, 0x0, sizeof(*text_cmd)); /* * Note that <final>, <length> and <text> are updated * by text_response_i when we receive offers from * the target. */ text_cmd->text = text; text_cmd->length = text_len; do { /* Build text command */ text_cmd->final = 1; text_cmd->cont = 0; ISCSI_SET_TAG(&text_cmd->tag); text_cmd->transfer_tag = 0xffffffff; /* Build wait for callback */ ISCSI_MUTEX_INIT(&wait.mutex, FFN_ERROR); ISCSI_COND_INIT(&wait.cond, FFN_ERROR); /* Build initiator command */ cmd->type = ISCSI_TEXT_CMD; cmd->ptr = text_cmd; cmd->callback = wait_callback_i; cmd->callback_arg = &wait; cmd->isid = sess->isid; /* Enqueue initiator command to Tx worker */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueing text command to tx worker %llu\n", sess->isid); ISCSI_LOCK(&wait.mutex, FFN_ERROR); ISCSI_LOCK(&sess->tx_worker.work_mutex, FFN_ERROR); if (iscsi_queue_insert(&sess->tx_queue, cmd) == -1) { ISCSI_UNLOCK(&wait.mutex, ); iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_insert() failed\n"); FFN_ERROR; } ISCSI_SIGNAL(&sess->tx_worker.work_cond, FFN_ERROR); ISCSI_UNLOCK(&sess->tx_worker.work_mutex, FFN_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueued text command ok\n"); /* Wait for callback */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "waiting on text callback\n"); ISCSI_WAIT(&wait.cond, &wait.mutex, FFN_ERROR); ISCSI_UNLOCK(&wait.mutex, FFN_ERROR); ISCSI_COND_DESTROY(&wait.cond, FFN_ERROR); ISCSI_MUTEX_DESTROY(&wait.mutex, FFN_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "received text callback ok\n"); /* * See if we're done. text_response_i() overwrites * text_cmd->final */ /* with the final bit in the text response from the target. */ if (!text_cmd->final) { iscsi_trace(TRACE_ISCSI_PARAM, __FILE__, __LINE__, "more negotiation needed (sending %d bytes response parameters)\n", text_cmd->length); } } while (!text_cmd->final); /* Free command pointers */ iscsi_free_atomic(cmd->ptr); /* text command */ iscsi_free_atomic(cmd); /* initiator command */ return 0; } #define DISCOVERY_PHASE_TEXT_LEN 1024 #define DP_CLEANUP {if (text != NULL) iscsi_free_atomic(text);} #define DP_ERROR {DP_CLEANUP; return -1;} int initiator_set_target_name(int target, char *target_name) { (void) strlcpy(g_target[target].iqnwanted, target_name, sizeof(g_target[target].iqnwanted)); (void) strlcpy(g_target[target].TargetName, target_name, sizeof(g_target[target].TargetName)); return 0; } int initiator_get_targets(int target, strv_t *svp) { initiator_session_t *sess = g_target[target].sess; iscsi_parameter_value_t *vp; iscsi_parameter_t *ip; char *text = NULL; int text_len = 0; int i; if ((text = iscsi_malloc_atomic(DISCOVERY_PHASE_TEXT_LEN)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } text_len = 0; text[0] = 0x0; PARAM_TEXT_ADD(sess->params, "SendTargets", "all", text, &text_len, DISCOVERY_PHASE_TEXT_LEN, 1, DP_ERROR); PARAM_TEXT_PARSE(sess->params, &sess->sess_params.cred, text, text_len, NULL, NULL, DISCOVERY_PHASE_TEXT_LEN, 1, DP_ERROR); if (full_feature_negotiation_phase_i(sess, text, text_len) != 0) { iscsi_trace_error(__FILE__, __LINE__, "full_feature_negotiation_phase_i() failed\n"); DP_ERROR; } i=0; for (ip = sess->params ; ip ; ip = ip->next) { if (strcmp(ip->key, "TargetName") == 0) { for (vp = ip->value_l ; vp ; vp = vp->next) { ALLOC(char *, svp->v, svp->size, svp->c, 10, 10, "igt", return -1); svp->v[svp->c++] = strdup(vp->value); ALLOC(char *, svp->v, svp->size, svp->c, 10, 10, "igt2", return -1); svp->v[svp->c++] = strdup(param_val(sess->params, "TargetAddress")); } } } return 1; } static int discovery_phase(int target, strv_t *svp) { initiator_session_t *sess; iscsi_parameter_value_t *vp; iscsi_parameter_t *ip; char *ptr; char *colon_ptr; char *comma_ptr; char port[64]; char *text = NULL; int text_len = 0; int i; if (target >= CONFIG_INITIATOR_NUM_TARGETS) { iscsi_trace_error(__FILE__, __LINE__, "target (%d) out of range [0..%d]\n", target, CONFIG_INITIATOR_NUM_TARGETS); return -1; } sess = g_target[target].sess; if ((text = iscsi_malloc_atomic(DISCOVERY_PHASE_TEXT_LEN)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } /* Login to target */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "entering Discovery login phase with target %d (sock %#x)\n", target, (int) sess->sock); text[0] = 0x0; if (params_out(sess, text, &text_len, DISCOVERY_PHASE_TEXT_LEN, SESS_TYPE_DISCOVERY, IS_SECURITY) != 0) { iscsi_trace_error(__FILE__, __LINE__, "params_out() failed\n"); DP_ERROR; } if (login_phase_i(sess, text, text_len) != 0) { iscsi_trace_error(__FILE__, __LINE__, "login_phase_i() failed\n"); DP_ERROR; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "now full feature for Discovery with target %d\n", target); /* Full Feature Phase Negotiation (for SendTargets) */ text_len = 0; text[0] = 0x0; PARAM_TEXT_ADD(sess->params, "SendTargets", "all", text, &text_len, DISCOVERY_PHASE_TEXT_LEN, 1, DP_ERROR); PARAM_TEXT_PARSE(sess->params, &sess->sess_params.cred, text, text_len, NULL, NULL, DISCOVERY_PHASE_TEXT_LEN, 1, DP_ERROR); if (full_feature_negotiation_phase_i(sess, text, text_len) != 0) { iscsi_trace_error(__FILE__, __LINE__, "full_feature_negotiation_phase_i() failed\n"); DP_ERROR; } /* fill in information on the targets from the TargetName values */ (void) memset(svp, 0x0, sizeof(*svp)); for (ip = sess->params ; ip ; ip = ip->next) { if (strcmp(ip->key, "TargetName") == 0) { for (vp = ip->value_l ; vp ; vp = vp->next) { ALLOC(char *, svp->v, svp->size, svp->c, 10, 10, "discovery_phase", return -1); svp->v[svp->c++] = strdup(vp->value); ALLOC(char *, svp->v, svp->size, svp->c, 10, 10, "discovery_phase2", return -1); svp->v[svp->c++] = strdup(param_val(sess->params, "TargetAddress")); } } } if (g_target[target].iqnwanted[0] == 0x0) { /* * Use the first TargetName and TargetAddress sent to us (all others * are currently ignored) */ if (param_val(sess->params, "TargetName") != NULL) { strlcpy(g_target[target].TargetName, param_val(sess->params, "TargetName"), sizeof(g_target[target].TargetName)); } else { iscsi_trace_error(__FILE__, __LINE__, "SendTargets failed\n"); DP_ERROR; } if ((ptr = param_val(sess->params, "TargetAddress")) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "SendTargets failed\n"); DP_ERROR; } } else { /* the user has asked for a specific target - find it */ for (i = 0 ; i < svp->c ; i += 2) { if (strcmp(g_target[target].iqnwanted, svp->v[i]) == 0) { strlcpy(g_target[target].TargetName, svp->v[i], sizeof(g_target[target].TargetName)); ptr = svp->v[i + 1]; break; } } if (i >= svp->c) { iscsi_trace_error(__FILE__, __LINE__, "SendTargets failed - target `%s' not found\n", g_target[target].iqnwanted); DP_ERROR; } } if (*ptr == 0x0) { iscsi_trace_error(__FILE__, __LINE__, "Target is not allowing access\n"); DP_ERROR; } colon_ptr = strchr(ptr, ':'); if ((comma_ptr = strchr(ptr, ',')) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "portal group tag is missing in \"%s\"\n", param_val(sess->params, "TargetAddress")); DP_ERROR; } if (colon_ptr) { strncpy(g_target[target].ip, ptr, (int)(colon_ptr - ptr)); strncpy(port, colon_ptr + 1, (int)(comma_ptr - colon_ptr - 1)); port[comma_ptr - colon_ptr - 1] = 0x0; g_target[target].port = iscsi_atoi(port); } else { strncpy(g_target[target].ip, ptr, (int)(comma_ptr - ptr)); g_target[target].port = ISCSI_PORT; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Discovered \"%s\" at \"%s:%u\"\n", g_target[target].TargetName, g_target[target].name, g_target[target].port); /* Logout from target */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "entering logout phase with target %d\n", target); if (logout_phase_i(sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "logout_phase_i() failed\n"); DP_ERROR; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "target %d logout phase complete\n", target); DP_CLEANUP; return 0; } #define FULL_FEATURE_PHASE_TEXT_LEN 1024 static int full_feature_phase(initiator_session_t * sess) { char *text; int text_len; if ((text = iscsi_malloc_atomic(FULL_FEATURE_PHASE_TEXT_LEN)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } #define FFP_CLEANUP {if (text != NULL) iscsi_free_atomic(text);} #define FFP_ERROR {FFP_CLEANUP; return -1;} /* Set text parameters */ text[0] = 0x0; text_len = 0; if (params_out(sess, text, &text_len, FULL_FEATURE_PHASE_TEXT_LEN, SESS_TYPE_NORMAL, IS_SECURITY) != 0) { iscsi_trace_error(__FILE__, __LINE__, "params_out() failed\n"); FFP_ERROR; } /* Send login command */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "entering login phase\n"); if (login_phase_i(sess, text, text_len) != 0) { iscsi_trace_error(__FILE__, __LINE__, "login_phase_i() failed\n"); FFP_ERROR; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "login phase successful\n"); FFP_CLEANUP; return 0; } int initiator_init(const char *hostname, int port, int address_family, const char *user, int auth_type, int mutual_auth, int digest_type) { int i; initiator_session_t *sess = NULL; #define INIT_CLEANUP {if (sess != NULL) iscsi_free_atomic(sess);} #define INIT_ERROR {INIT_CLEANUP; return -1;} iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initializing initiator\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "target config filename to read from:%s\n", gfilename); if (get_target_config(hostname, port) != 0) { iscsi_trace_error(__FILE__, __LINE__, "Error getting target configuration from config file\n"); return -1; } g_initiator_state = 0; if (iscsi_queue_init(&g_session_q, CONFIG_INITIATOR_MAX_SESSIONS) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_init() failed\n"); return -1; } for (i = 0; i < CONFIG_INITIATOR_MAX_SESSIONS; i++) { if ((sess = iscsi_malloc_atomic(sizeof(initiator_session_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } if (iscsi_queue_insert(&g_session_q, sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_init() failed\n"); INIT_CLEANUP; return -1; } sess->sess_params.cred.user = strdup(user); sess->sess_params.auth_type = auth_type; sess->sess_params.mutual_auth = mutual_auth; sess->sess_params.digest_wanted = digest_type; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "%d free sessions available\n", CONFIG_INITIATOR_MAX_SESSIONS); g_tag = 0xabc123; if (hash_init(&g_tag_hash, CONFIG_INITIATOR_QUEUE_DEPTH) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_init() failed\n"); INIT_CLEANUP; return -1; } iscsi_spin_init(&g_tag_spin); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tag hash table initialized with queue depth %d\n", CONFIG_INITIATOR_QUEUE_DEPTH); /* * Start enqueue worker. This thread accepts scsi commands from * initiator_enqueue() */ /* and queues them onto one of the tx worker queues. */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "starting enqueue worker\n"); if (iscsi_queue_init(&g_enqueue_q, CONFIG_INITIATOR_QUEUE_DEPTH) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_init() failed\n"); INIT_CLEANUP; return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "about to initialize mutex\n"); ISCSI_MUTEX_INIT(&g_enqueue_worker.work_mutex, INIT_ERROR); ISCSI_COND_INIT(&g_enqueue_worker.work_cond, INIT_ERROR); ISCSI_MUTEX_INIT(&g_enqueue_worker.exit_mutex, INIT_ERROR); ISCSI_COND_INIT(&g_enqueue_worker.exit_cond, INIT_ERROR); ISCSI_LOCK(&g_enqueue_worker.exit_mutex, INIT_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "spawning thread for enqueue worker\n"); if (iscsi_thread_create(&g_enqueue_worker.thread, (void *) &enqueue_worker_proc, &g_enqueue_worker) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_threads_create() failed\n"); INIT_CLEANUP; return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "thread spawned, waiting for signal\n"); ISCSI_WAIT(&g_enqueue_worker.exit_cond, &g_enqueue_worker.exit_mutex, INIT_ERROR); ISCSI_UNLOCK(&g_enqueue_worker.exit_mutex, INIT_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "successfully started enqueue worker\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initiator initialization complete\n"); return 0; } int initiator_shutdown(void) { initiator_session_t *sess; int i; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "shutting down initiator\n"); for (i = 0; i < CONFIG_INITIATOR_NUM_TARGETS; i++) { if (g_target[i].has_session) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "entering logout phase for target %d\n", i); if (g_target[i].sess->rx_worker.state & ISCSI_WORKER_STATE_ERROR) { iscsi_trace_warning(__FILE__, __LINE__, "rx worker exited abnormal, skipping logout phase\n"); } else { if (logout_phase_i(g_target[i].sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "logout_phase_i() failed for target %d\n", i); } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "logout phase complete for target %d (state %#x)\n", i, g_target[i].sess->state); } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "destroying session for target %d\n", i); if (session_destroy_i(g_target[i].sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "session_destroy_i() failed for target %d\n", i); } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "session destroyed for target %d\n", i); } } g_initiator_state = INITIATOR_STATE_SHUTDOWN; if (g_enqueue_worker.state & ISCSI_WORKER_STATE_EXITING) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue already exiting\n"); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "signaling enqueue worker into exiting state\n"); ISCSI_LOCK(&g_enqueue_worker.work_mutex, return -1); ISCSI_SIGNAL(&g_enqueue_worker.work_cond, return -1); ISCSI_UNLOCK(&g_enqueue_worker.work_mutex, return -1); } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "Checking exit condition of enqueue worker\n"); while ((g_enqueue_worker.state & ISCSI_WORKER_STATE_EXITING) != ISCSI_WORKER_STATE_EXITING) { ISCSI_SPIN; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue worker has exited\n"); iscsi_queue_destroy(&g_enqueue_q); ISCSI_MUTEX_DESTROY(&g_enqueue_worker.work_mutex, return -1); ISCSI_COND_DESTROY(&g_enqueue_worker.work_cond, return -1); ISCSI_MUTEX_DESTROY(&g_enqueue_worker.exit_mutex, return -1); ISCSI_COND_DESTROY(&g_enqueue_worker.exit_cond, return -1); while ((sess = iscsi_queue_remove(&g_session_q)) != NULL) { iscsi_free_atomic(sess); } iscsi_queue_destroy(&g_session_q); iscsi_spin_destroy(&g_tag_spin); hash_destroy(&g_tag_hash); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initiator shutdown complete\n"); return 0; } static int wait_callback_i(void *ptr) { initiator_wait_t *wait = (initiator_wait_t *) (((initiator_cmd_t *) ptr)->callback_arg); ISCSI_LOCK(&wait->mutex, return -1); ISCSI_SIGNAL(&wait->cond, return -1); ISCSI_UNLOCK(&wait->mutex, return -1); return 0; } int initiator_abort(initiator_cmd_t * cmd) { initiator_cmd_t *ptr, *prev; initiator_session_t *sess; iscsi_trace_error(__FILE__, __LINE__, "aborting iSCSI cmd 0x%p (type %d, isid %llu)\n", cmd, cmd->type, cmd->isid); hash_remove(&g_tag_hash, cmd->key); if (g_target[cmd->isid].has_session) { sess = g_target[cmd->isid].sess; iscsi_spin_lock(&sess->cmds_spin); prev = ptr = sess->cmds; while (ptr != NULL) { prev = ptr; if (ptr == cmd) break; ptr = ptr->next; } if (ptr != NULL) { if (prev == sess->cmds) { sess->cmds = cmd->next; } else { prev->next = cmd->next; } } iscsi_spin_unlock(&sess->cmds_spin); } else { iscsi_trace_error(__FILE__, __LINE__, "cmd 0x%p has no session\n", cmd); } cmd->status = -1; if (cmd->callback) { if ((*cmd->callback)(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "cmd->callback() failed\n"); return -1; } } iscsi_trace_error(__FILE__, __LINE__, "successfully aborted iSCSI cmd 0x%p (type %d, isid %llu)\n", cmd, cmd->type, cmd->isid); return 0; } int initiator_command(initiator_cmd_t * cmd) { initiator_wait_t wait; ISCSI_MUTEX_INIT(&wait.mutex, return -1); ISCSI_COND_INIT(&wait.cond, return -1); ISCSI_LOCK(&wait.mutex, return -1); cmd->callback = wait_callback_i; cmd->callback_arg = &wait; cmd->status = -1; if (initiator_enqueue(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "initiator_enqueue() failed\n"); return -1; } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "command (type %d) enqueued, waiting on condition\n", cmd->type); ISCSI_WAIT(&wait.cond, &wait.mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "condition signaled\n"); } ISCSI_UNLOCK(&wait.mutex, return -1); ISCSI_COND_DESTROY(&wait.cond, return -1); ISCSI_MUTEX_DESTROY(&wait.mutex, return -1); return cmd->status; } /* * initiator_enqueue() may be called from within interrupt context within * the midlayer. This function cannot block or be scheduled within. * All we do is enqueue the args ptr to g_enqueue_q. The thread in * enqueue_worker_proc will enqueue the ptr onto one of the tx queues. */ int initiator_enqueue(initiator_cmd_t * cmd) { initiator_session_t *sess; iscsi_scsi_cmd_args_t *scsi_cmd; iscsi_nop_out_args_t *nop_out; uint64_t target; uint32_t tag; if ((target = cmd->isid) >= CONFIG_INITIATOR_NUM_TARGETS) { iscsi_trace_error(__FILE__, __LINE__, "target (%d) out of range [0..%d]\n", target, CONFIG_INITIATOR_NUM_TARGETS); return -1; } sess = g_target[target].sess; if (g_target[target].has_session && (sess->state == INITIATOR_SESSION_STATE_LOGGED_IN_NORMAL)) { /* Give command directly to tx worker */ ISCSI_SET_TAG_IN_INTR(&tag); target = cmd->isid; switch (cmd->type) { case ISCSI_SCSI_CMD: scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr; scsi_cmd->tag = tag; break; case ISCSI_NOP_OUT: nop_out = (iscsi_nop_out_args_t *) cmd->ptr; if (nop_out->tag != 0xffffffff) { nop_out->tag = tag; } break; default: iscsi_trace_error(__FILE__, __LINE__, "enqueue_worker: unknown command type %d\n", cmd->type); return -1; } if (iscsi_queue_insert(&sess->tx_queue, cmd) == -1) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_insert() failed\n"); return -1; } ISCSI_LOCK(&sess->tx_worker.work_mutex, return -1); ISCSI_SIGNAL(&sess->tx_worker.work_cond, return -1); ISCSI_UNLOCK(&sess->tx_worker.work_mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initiator_cmd_t 0x%p given to tx_worker[%llu]\n", cmd, cmd->isid); } else { /* * Give command to enqueue worker to get us into full feature * and then issue the command */ /* to one of the tx workers. */ if (iscsi_queue_insert(&g_enqueue_q, cmd) == -1) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_insert() failed\n"); return -1; } ISCSI_LOCK(&g_enqueue_worker.work_mutex, return -1); ISCSI_SIGNAL(&g_enqueue_worker.work_cond, return -1); ISCSI_UNLOCK(&g_enqueue_worker.work_mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initiator_cmd_t 0x%p given to enqueue worker\n", cmd); } return 0; } static int enqueue_worker_proc(void *arg) { initiator_session_t *sess; initiator_cmd_t *cmd; iscsi_scsi_cmd_args_t *scsi_cmd; iscsi_nop_out_args_t *nop_out; iscsi_worker_t *me = (iscsi_worker_t *) arg; uint64_t target; uint32_t tag; strv_t sv; int rc; ISCSI_THREAD_START("enqueue_worker"); ISCSI_SET_THREAD(me) ISCSI_LOCK(&me->exit_mutex, goto done); me->pid = ISCSI_GETPID; me->state = ISCSI_WORKER_STATE_STARTED; ISCSI_SIGNAL(&me->exit_cond, goto done); ISCSI_UNLOCK(&me->exit_mutex, goto done); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: started\n"); ISCSI_LOCK(&g_enqueue_worker.work_mutex, goto done); for (;;) { if (iscsi_queue_depth(&g_enqueue_q) || (g_initiator_state == INITIATOR_STATE_SHUTDOWN)) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueu, start to work\n"); ISCSI_UNLOCK(&g_enqueue_worker.work_mutex, goto done); if (g_initiator_state == INITIATOR_STATE_SHUTDOWN) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "got shutdown signal\n"); goto done; } if ((cmd = iscsi_queue_remove(&g_enqueue_q)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "enqueue_worker: iscsi_queue_remove() failed\n"); goto done; } ISCSI_SET_TAG(&tag); target = cmd->isid; iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "enqueue_worker: dequeued initiator_cmd_t 0x%p (type %d, target %llu)\n", cmd, cmd->type, target); switch (cmd->type) { case ISCSI_SCSI_CMD: scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr; scsi_cmd->tag = tag; break; case ISCSI_NOP_OUT: nop_out = (iscsi_nop_out_args_t *) cmd->ptr; if (nop_out->tag != 0xffffffff) { nop_out->tag = tag; } break; default: iscsi_trace_error(__FILE__, __LINE__, "enqueue_worker: unknown command type %d\n", cmd->type); goto done; } /* Initialize session (if not already) */ initialize: if (!g_target[target].has_session) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: initializing target %llu session\n", target); if (session_init_i(&g_target[target].sess, target) != 0) { iscsi_trace_error(__FILE__, __LINE__, "session_init_i() failed (ignoring command)\n"); goto next; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: target %llu session initialized\n", target); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: target %llu session already initialized\n", target); } sess = g_target[target].sess; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: session 0x%p\n", sess); /* Discovery login if TargetName is zero length */ if (strlen(g_target[target].TargetName) == 0) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: entering Discovery phase with target %llu\n", target); rc = discovery_phase(target, &sv); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: Discovery phase complete\n"); /* Destroy session */ if (sess->state != INITIATOR_SESSION_STATE_DESTROYING) { if (g_target[target].has_session) { if (session_destroy_i(g_target[target].sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "enqueue_worker: session_destroy_i() failed\n"); goto done; } } } /* * If the Discovery phase was * successful, we re-initialize the * session, enter full feature phase * and then execute the command. */ if (rc == 0) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: discovery_phase() succeeded, entering full feature\n"); goto initialize; } else { iscsi_trace_error(__FILE__, __LINE__, "enqueue_worker: discovery_phase() failed (ignoring command)\n"); goto next; } } /* Get into full feature if we're not already */ if (sess->state != INITIATOR_SESSION_STATE_LOGGED_IN_NORMAL) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: entering full feature with target %llu (sock %#x)\n", target, (int) sess->sock); if (full_feature_phase(sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "enqueue_worker: full_feature_phase() failed (ignoring command)\n"); goto next; } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: now full feature with target %llu\n", target); } } /* * Now we are in FPP, so set the mostly * accessed parameters for easy retrieval * during data transfer */ set_session_parameters(sess->params, &sess->sess_params); /* Add command to tx work queue and signal worker */ ISCSI_LOCK(&sess->tx_worker.work_mutex, goto done); if (iscsi_queue_insert(&sess->tx_queue, cmd) == -1) { ISCSI_UNLOCK(&sess->tx_worker.work_mutex, goto done); iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_insert() failed\n"); goto done; } ISCSI_SIGNAL(&sess->tx_worker.work_cond, goto done); ISCSI_UNLOCK(&sess->tx_worker.work_mutex, goto done); iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "enqueue_worker: gave initiator_cmd_t 0x%p to tx_worker[%llu]\n", cmd, cmd->isid); next: ISCSI_LOCK(&g_enqueue_worker.work_mutex, goto done); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: queue empty, awaiting condition\n"); ISCSI_WAIT(&g_enqueue_worker.work_cond, &g_enqueue_worker.work_mutex, goto done); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueue_worker: condition signaled\n"); } } done: ISCSI_WORKER_EXIT(me); } /*********** * Private * ***********/ /* * Tx Worker (one per connection) */ static int tx_worker_proc_i(void *arg) { iscsi_worker_t *me = (iscsi_worker_t *) arg; initiator_cmd_t *cmd, *ptr; initiator_session_t *sess = g_target[me->id].sess; ISCSI_THREAD_START("tx_worker"); ISCSI_SET_THREAD(me) me->pid = ISCSI_GETPID; me->state = ISCSI_WORKER_STATE_STARTED; /* Connect to target */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: connecting to %s:%d\n", me->id, g_target[me->id].name, g_target[me->id].port); sess->state = INITIATOR_SESSION_STATE_CONNECTING; if (iscsi_sock_connect(sess->sock, g_target[me->id].name, g_target[me->id].port) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_connect() failed\n"); ISCSI_LOCK(&me->exit_mutex, return -1); me->state |= ISCSI_WORKER_STATE_ERROR; ISCSI_SIGNAL(&me->exit_cond, return -1); ISCSI_UNLOCK(&me->exit_mutex, return -1); goto done; } sess->state = INITIATOR_SESSION_STATE_CONNECTED; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: connected to %s:%d\n", me->id, g_target[me->id].name, g_target[me->id].port); /* Start Rx worker */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: starting Rx worker\n", me->id); ISCSI_LOCK(&sess->rx_worker.exit_mutex, return -1); if (iscsi_thread_create(&sess->rx_worker.thread, (void *) rx_worker_proc_i, sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_thread_create() failed\n"); goto done; } ISCSI_WAIT(&sess->rx_worker.exit_cond, &sess->rx_worker.exit_mutex, return -1); ISCSI_UNLOCK(&sess->rx_worker.exit_mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: Rx worker started\n", me->id); /* Signal that we've started */ ISCSI_LOCK(&me->exit_mutex, return -1); ISCSI_SIGNAL(&me->exit_cond, return -1); ISCSI_UNLOCK(&me->exit_mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: successfully started\n", me->id); /* This Tx loop will exit when both the g_tx_queue is empty and */ /* sess->state != INITIATOR_SESSION_STATE_DESTROYING */ ISCSI_LOCK(&me->work_mutex, return -1); for (;;) { if (iscsi_queue_depth(&g_target[me->id].sess->tx_queue) || (sess->state == INITIATOR_SESSION_STATE_DESTROYING)) { if (sess->state == INITIATOR_SESSION_STATE_DESTROYING) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: session is being destroyed, exiting\n", me->id); ISCSI_UNLOCK(&me->work_mutex, return -1); goto done; } /* Get initiator command */ if ((cmd = iscsi_queue_remove(&g_target[me->id].sess->tx_queue)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: iscsi_queue_remove() failed\n", me->id); ISCSI_UNLOCK(&me->work_mutex, return -1); goto done; } ISCSI_UNLOCK(&me->work_mutex, return -1); iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "tx_worker[%d]: dequeued initiator_cmd_t 0x%p (type %d, target %llu)\n", me->id, cmd, cmd->type, cmd->isid); /* Make sure we've got the right command */ if (cmd->isid != me->id) { iscsi_trace_error(__FILE__, __LINE__, "got command %#x for target %llu, expected %d\n", cmd->type, cmd->isid, me->id); goto done; } /* * Add to list of oustanding commands in session * (unless NOP_OUT without ping) */ if (!((cmd->type == ISCSI_NOP_OUT) && (((iscsi_nop_out_args_t *) (cmd->ptr))->tag == 0xffffffff))) { cmd->next = NULL; iscsi_spin_lock(&sess->cmds_spin); for (ptr = sess->cmds; ((ptr) && (ptr->next != NULL)); ptr = ptr->next) { } if (ptr) { ptr->next = cmd; } else { sess->cmds = cmd; } iscsi_spin_unlock(&sess->cmds_spin); } cmd->tx_done = 0; switch (cmd->type) { case ISCSI_LOGIN_CMD: iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "tx_worker[%d]: ISCSI_LOGIN_CMD\n", me->id); if (login_command_i(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: login_command_i() failed\n", me->id); goto done; } break; case ISCSI_TEXT_CMD: iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "tx_worker[%d]: ISCSI_TEXT_CMD\n", me->id); if (text_command_i(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: text_command_i() failed\n", me->id); goto done; } break; case ISCSI_SCSI_CMD: iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "tx_worker[%d]: ISCSI_SCSI_CMD\n", me->id); if (scsi_command_i(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: scsi_command_i() failed\n", me->id); goto done; } break; case ISCSI_NOP_OUT: iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "tx_worker[%d]: ISCSI_NOP_OUT\n", me->id); if (nop_out_i(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: nop_out_i() failed\n", me->id); goto done; } break; case ISCSI_LOGOUT_CMD: iscsi_trace(TRACE_ISCSI_CMD, __FILE__, __LINE__, "tx_worker[%d]: ISCSI_LOGOUT_CMD\n", me->id); if (logout_command_i(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: logout_command_i() failed\n", me->id); goto done; } break; default: iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: unknown iSCSI command %#x\n", me->id, cmd->type); cmd->status = -1; break; } /* Get lock for next iteration */ ISCSI_LOCK(&me->work_mutex, return -1); /* * The Rx thread will receive a response for * the command and execute the callback. We * need to make sure the callback function is * not executed before the Tx thread has * completed sending the command. This is * what tx_done is used for. The last step is * to set tx_done and signal the Rx thread, * which may be block on the condition. * NOP_OUT (without ping) will have no * response for the Rx thread to process - so * we execute the callback directly. */ if ((cmd->type == ISCSI_NOP_OUT) && (((iscsi_nop_out_args_t *) (cmd->ptr))->tag == 0xffffffff)) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "executing callback() function directly for NOP_OUT (no NOP_IN)\n"); if (cmd->callback(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "cmd->callback() failed\n"); return -1; } } else { ISCSI_LOCK(&sess->rx_worker.work_mutex, return -1); cmd->tx_done = 1; ISCSI_SIGNAL(&sess->rx_worker.work_cond, return -1); ISCSI_UNLOCK(&sess->rx_worker.work_mutex, return -1); } } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: awaiting condition\n", me->id); ISCSI_WAIT(&me->work_cond, &me->work_mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%d]: condition signaled\n", me->id); } } done: if (sess->state != INITIATOR_SESSION_STATE_DESTROYING) { iscsi_trace_error(__FILE__, __LINE__, "tx_worker[%d]: session exited prematurely (state %#x)\n", me->id, sess->state); me->state |= ISCSI_WORKER_STATE_ERROR; } ISCSI_WORKER_EXIT(me); } /* * There is one Rx worker per connection. */ static int rx_worker_proc_i(void *arg) { uint8_t header[ISCSI_HEADER_LEN]; initiator_session_t *sess = (initiator_session_t *) arg; iscsi_worker_t *me = &sess->rx_worker; initiator_cmd_t *cmd = NULL; initiator_cmd_t *prev, *ptr; uint32_t tag; ISCSI_THREAD_START("rx_worker"); ISCSI_SET_THREAD(me) me->state = ISCSI_WORKER_STATE_STARTED; me->pid = ISCSI_GETPID; ISCSI_LOCK(&me->exit_mutex, return -1); ISCSI_SIGNAL(&me->exit_cond, return -1); ISCSI_UNLOCK(&me->exit_mutex, return -1); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: started (sess %p)\n", me->id, sess); for (;;) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: reading iscsi header (sock %#x) \n", me->id, (int) sess->sock); if (iscsi_sock_msg(sess->sock, 0, ISCSI_HEADER_LEN, header, 0) != ISCSI_HEADER_LEN) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: iscsi_sock_msg() failed\n", me->id); goto done; } if (sess->state == INITIATOR_SESSION_STATE_DESTROYING) { iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: session is being destroyed\n", me->id); goto done; } /* Get cmd ptr from hash table */ if ((ISCSI_OPCODE(header) != ISCSI_REJECT) && (ISCSI_OPCODE(header) != ISCSI_ASYNC)) { tag = ISCSI_NTOHL(*((uint32_t *) (header + 16))); if (tag != 0xffffffff) { /* * remove command from g_tag_hash, cmd is * local so we only need to lock the queue * remove operation */ if ((cmd = hash_remove(&g_tag_hash, tag)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "hash_remove() failed\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "no cmd ptr associated with tag %#x\n", tag); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "cmd ptr %p associated with tag %#x\n", cmd, tag); ISCSI_LOCK(&sess->rx_worker.work_mutex, return -1); if (!cmd->tx_done) { ISCSI_WAIT(&sess->rx_worker.work_cond, &sess->rx_worker.work_mutex, return -1); } ISCSI_UNLOCK(&sess->rx_worker.work_mutex, return -1); } } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "no command associated with tag %#x\n", tag); } } /* Remove cmd ptr from outstanding list */ iscsi_spin_lock(&sess->cmds_spin); prev = ptr = sess->cmds; while (ptr != NULL) { prev = ptr; if (ptr == cmd) break; ptr = ptr->next; } if (ptr != NULL) { if (prev == sess->cmds) { sess->cmds = cmd->next; } else { prev->next = cmd->next; } } iscsi_spin_unlock(&sess->cmds_spin); switch (ISCSI_OPCODE(header)) { case ISCSI_SCSI_RSP: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_SCSI_RSP\n", me->id); if (scsi_response_i(sess, cmd, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: scsi_response_i() failed\n", me->id); goto done; } break; case ISCSI_READ_DATA: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_READ_DATA\n", me->id); if (scsi_read_data_i(sess, cmd, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: scsi_read_data_i() failed\n", me->id); goto done; } break; case ISCSI_R2T: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_R2T\n", me->id); if (scsi_r2t_i(sess, cmd, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: scsi_r2t_i() failed\n", me->id); goto done; } break; case ISCSI_NOP_IN: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_NOP_IN\n", me->id); if (nop_in_i(sess, cmd, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "nop_in_i() failed\n"); return -1; } break; case ISCSI_LOGIN_RSP: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_LOGIN_RSP\n", me->id); if (login_response_i(sess, cmd, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: login_response_i() failed\n", me->id); goto done; } break; case ISCSI_TEXT_RSP: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_TEXT_RSP\n", me->id); if (text_response_i(sess, cmd, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: text_response_i() failed\n", me->id); goto done; } break; case ISCSI_LOGOUT_RSP: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_LOGOUT_RSP\n", me->id); if (logout_response_i(sess, cmd, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: logout_response_i() failed\n", me->id); goto done; } break; case ISCSI_REJECT: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_REJECT\n", me->id); if (reject_i(sess, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "reject_i() failed\n"); return -1; } break; case ISCSI_ASYNC: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: ISCSI_ASYNC\n", me->id); if (async_msg_i(sess, header) != 0) { iscsi_trace_error(__FILE__, __LINE__, "async_msg_i() failed\n"); goto done; } break; default: iscsi_trace_error(__FILE__, __LINE__, "rx_worker[%d]: unexpected iSCSI op %#x\n", me->id, ISCSI_OPCODE(header)); goto done; } } done: if (sess->state != INITIATOR_SESSION_STATE_DESTROYING) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "rx_worker[%d]: session exited prematurely (state %#x)\n", me->id, sess->state); me->state |= ISCSI_WORKER_STATE_ERROR; } ISCSI_WORKER_EXIT(me); } static int text_command_i(initiator_cmd_t * cmd) { iscsi_text_cmd_args_t *text_cmd = (iscsi_text_cmd_args_t *) cmd->ptr; initiator_session_t *sess = g_target[cmd->isid].sess; uint8_t header[ISCSI_HEADER_LEN]; /* * Insert cmd into the hash table, keyed by the tag. The Rx thread * will */ /* retreive the cmd ptr using the tag from the response PDU. */ if (hash_insert(&g_tag_hash, cmd, text_cmd->tag) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_insert() failed\n"); return -1; } /* Send text command PDU */ text_cmd->ExpStatSN = sess->ExpStatSN; text_cmd->CmdSN = sess->CmdSN++; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending text command\n"); if (iscsi_text_cmd_encap(header, text_cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "(iscsi_text_cmd_encap() failed\n"); return -1; } if (iscsi_sock_send_header_and_data(sess->sock, header, ISCSI_HEADER_LEN, text_cmd->text, text_cmd->length, 0) != ISCSI_HEADER_LEN + text_cmd->length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_send_header_and_data() failed.\n"); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "text command sent ok\n"); return 0; } static int login_command_i(initiator_cmd_t * cmd) { iscsi_login_cmd_args_t *login_cmd = (iscsi_login_cmd_args_t *) cmd->ptr; initiator_session_t *sess = g_target[cmd->isid].sess; uint8_t header[ISCSI_HEADER_LEN]; /* * Insert cmd into the hash table, keyed by the tag. The Rx thread * will */ /* retreive the cmd ptr using the tag from the response PDU. */ if (hash_insert(&g_tag_hash, cmd, login_cmd->tag) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_insert() failed\n"); return -1; } /* Send login command PDU */ login_cmd->ExpStatSN = sess->ExpStatSN; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending login command\n"); if (iscsi_login_cmd_encap(header, login_cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "(iscsi_login_cmd_encap() failed\n"); return -1; } if (iscsi_sock_send_header_and_data(sess->sock, header, ISCSI_HEADER_LEN, login_cmd->text, login_cmd->length, 0) != ISCSI_HEADER_LEN + login_cmd->length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_send_header_and_data() failed.\n"); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "login command sent ok\n"); return 0; } static int logout_phase_i(initiator_session_t * sess) { initiator_cmd_t *cmd = NULL; iscsi_logout_cmd_args_t *logout_cmd = NULL; initiator_wait_t wait; sess->state = INITIATOR_SESSION_STATE_LOGGING_OUT; /* Allocate command pointers */ if ((cmd = iscsi_malloc_atomic(sizeof(initiator_cmd_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); return -1; } (void) memset(cmd, 0x0, sizeof(*cmd)); if ((logout_cmd = iscsi_malloc_atomic(sizeof(iscsi_logout_cmd_args_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); if (cmd != NULL) iscsi_free_atomic(cmd); return -1; } #define LO_CLEANUP {if (cmd != NULL) iscsi_free_atomic(cmd); if (logout_cmd != NULL) iscsi_free_atomic(logout_cmd); } #define LO_ERROR {LO_CLEANUP; return -1;} (void) memset(logout_cmd, 0x0, sizeof(*logout_cmd)); /* Build logout command */ logout_cmd->cid = sess->cid; logout_cmd->reason = ISCSI_LOGOUT_CLOSE_SESSION; ISCSI_SET_TAG(&logout_cmd->tag); logout_cmd->ExpStatSN = sess->ExpStatSN; logout_cmd->CmdSN = sess->CmdSN++; /* Build wait for callback */ ISCSI_MUTEX_INIT(&wait.mutex, LO_ERROR); ISCSI_COND_INIT(&wait.cond, LO_ERROR); /* Build initiator command */ cmd->type = ISCSI_LOGOUT_CMD; cmd->ptr = logout_cmd; cmd->callback = wait_callback_i; cmd->callback_arg = &wait; cmd->isid = sess->isid; /* Enqueue to Tx worker */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueing logout command to tx worker %llu\n", sess->isid); ISCSI_LOCK(&wait.mutex, LO_ERROR); ISCSI_LOCK(&sess->tx_worker.work_mutex, LO_ERROR); if (iscsi_queue_insert(&sess->tx_queue, cmd) == -1) { ISCSI_UNLOCK(&sess->tx_worker.work_mutex, LO_ERROR); iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_insert() failed\n"); LO_ERROR; } ISCSI_SIGNAL(&sess->tx_worker.work_cond, LO_ERROR); ISCSI_UNLOCK(&sess->tx_worker.work_mutex, LO_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueued logout command ok\n"); /* Wait for callback */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "waiting on logout callback\n"); ISCSI_WAIT(&wait.cond, &wait.mutex, LO_ERROR); ISCSI_UNLOCK(&wait.mutex, LO_ERROR); ISCSI_COND_DESTROY(&wait.cond, LO_ERROR); ISCSI_MUTEX_DESTROY(&wait.mutex, LO_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "received logout callback ok\n"); sess->state = INITIATOR_SESSION_STATE_LOGGED_OUT; LO_CLEANUP; return 0; } static void alarm_handler(int arg) { iscsi_trace_error(__FILE__, __LINE__, "***aborting cmd 0x%p***\n", g_cmd); if (initiator_abort(g_cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "initiator_abort() failed\n"); } } static int login_phase_i(initiator_session_t * sess, char *text, int text_len) { initiator_cmd_t *cmd = NULL; initiator_wait_t wait; iscsi_login_cmd_args_t *login_cmd = NULL; struct sigaction act; sess->state = INITIATOR_SESSION_STATE_LOGGING_IN; /* Allocate command pointers */ if ((cmd = iscsi_malloc_atomic(sizeof(initiator_cmd_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); return -1; } (void) memset(cmd, 0x0, sizeof(*cmd)); if ((login_cmd = iscsi_malloc_atomic(sizeof(iscsi_login_cmd_args_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); if (cmd != NULL) iscsi_free_atomic(cmd); return -1; } #define LI_CLEANUP {if (cmd != NULL) iscsi_free_atomic(cmd); if (login_cmd != NULL) iscsi_free_atomic(login_cmd); } #define LI_ERROR {LI_CLEANUP; return -1;} (void) memset(login_cmd, 0x0, sizeof(*login_cmd)); /* This is the length of our original offer. */ login_cmd->text = text; login_cmd->length = text_len; login_cmd->transit = 1; login_cmd->csg = ISCSI_LOGIN_STAGE_SECURITY; login_cmd->nsg = ISCSI_LOGIN_STAGE_NEGOTIATE; ISCSI_SET_TAG(&login_cmd->tag); login_cmd->CmdSN = sess->CmdSN = 0; do { /* * Build login command. Note that the <length> and * <text> fields may get updated by login_response_i. * Such is the case when we receive offers from the * target. The new <length> and <text> fields will * represent the response that we need to send to the * target on the next login. */ login_cmd->cont = 0; login_cmd->version_min = ISCSI_VERSION; login_cmd->version_max = ISCSI_VERSION; login_cmd->cid = sess->cid = sess->isid; login_cmd->isid = sess->isid = sess->isid; login_cmd->tsih = 0; /* Build wait for callback */ ISCSI_MUTEX_INIT(&wait.mutex, LI_ERROR); ISCSI_COND_INIT(&wait.cond, LI_ERROR); /* Build initiator command */ cmd->type = ISCSI_LOGIN_CMD; cmd->ptr = login_cmd; cmd->callback = wait_callback_i; cmd->callback_arg = &wait; cmd->isid = sess->isid; /* Set Alarm */ g_cmd = cmd; act.sa_handler = alarm_handler; sigaction(SIGALRM, &act, NULL); alarm(5); /* Enqueue initiator command to Tx worker */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueing login command to tx worker %llu\n", sess->isid); ISCSI_LOCK(&wait.mutex, LI_ERROR); ISCSI_LOCK(&sess->tx_worker.work_mutex, LI_ERROR); if (iscsi_queue_insert(&sess->tx_queue, cmd) == -1) { ISCSI_UNLOCK(&sess->tx_worker.work_mutex, LI_ERROR); iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_insert() failed\n"); LI_ERROR; } ISCSI_SIGNAL(&sess->tx_worker.work_cond, LI_ERROR); ISCSI_UNLOCK(&sess->tx_worker.work_mutex, LI_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "enqueued login command ok\n"); /* Wait for callback */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "waiting on login callback\n"); ISCSI_WAIT(&wait.cond, &wait.mutex, LI_ERROR); ISCSI_UNLOCK(&wait.mutex, LI_ERROR); ISCSI_COND_DESTROY(&wait.cond, LI_ERROR); ISCSI_MUTEX_DESTROY(&wait.mutex, LI_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "received login callback ok\n"); alarm(0); if (cmd->status != 0) { iscsi_trace_error(__FILE__, __LINE__, "initiator_cmd_t failed\n"); LI_ERROR; } if (sess->state == INITIATOR_SESSION_STATE_LOGGING_IN) { iscsi_trace(TRACE_ISCSI_PARAM, __FILE__, __LINE__, "more negotiation needed (sending %d bytes response parameters)\n", login_cmd->length); } } while (sess->state == INITIATOR_SESSION_STATE_LOGGING_IN); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "login phase completed successfully\n"); LI_CLEANUP; return 0; } #define TEXT_RESPONSE_TEXT_LEN 2048 static int text_response_i(initiator_session_t * sess, initiator_cmd_t * cmd, uint8_t *header) { iscsi_text_cmd_args_t *text_cmd; iscsi_text_rsp_args_t text_rsp; iscsi_parameter_t *l = sess->params; char *text_in = NULL; char *text_out = NULL; int len_in = 0; int len_out = 0; int ret = 0; #define TI_CLEANUP {if (text_in != NULL) iscsi_free_atomic(text_in); if (text_out != NULL) iscsi_free_atomic(text_out);} #define TI_ERROR {cmd->status=-1; goto callback;} if (cmd) { text_cmd = (iscsi_text_cmd_args_t *) cmd->ptr; } else { iscsi_trace_error(__FILE__, __LINE__, "no initiator_cmd_t specified for iscsi_text_cmd_args_t??\n"); return -1; } /* Check arguments & update numbering */ if (iscsi_text_rsp_decap(header, &text_rsp) != 0) { iscsi_trace_error(__FILE__, __LINE__, "text_response_decap() failed\n"); TI_ERROR; } RETURN_NOT_EQUAL("Tag", text_rsp.tag, text_cmd->tag, TI_ERROR, -1); RETURN_NOT_EQUAL("Transfer Tag", text_rsp.transfer_tag, 0xffffffff, TI_ERROR, -1); RETURN_NOT_EQUAL("StatSN", text_rsp.StatSN, sess->ExpStatSN, TI_ERROR, -1); RETURN_NOT_EQUAL("ExpCmdSN", text_rsp.ExpCmdSN, sess->CmdSN, TI_ERROR, -1); sess->ExpStatSN = text_rsp.StatSN + 1; /* Parse input text parameters and generate any response */ if ((len_in = text_rsp.length) != 0) { iscsi_trace(TRACE_ISCSI_PARAM, __FILE__, __LINE__, "allocating %d bytes input parameters\n", len_in); if ((text_in = iscsi_malloc_atomic(len_in + 1)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); TI_ERROR; } if ((text_out = iscsi_malloc_atomic(TEXT_RESPONSE_TEXT_LEN)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); if (text_in != NULL) iscsi_free_atomic(text_in); TI_ERROR; } if (iscsi_sock_msg(sess->sock, 0, len_in, text_in, 0) != len_in) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); TI_ERROR; } text_in[len_in] = 0x0; iscsi_trace(TRACE_ISCSI_PARAM, __FILE__, __LINE__, "read %d bytes input parameters ok\n", len_in); /* Reset the value lists for TargetName and TargetAddress */ if (param_val_reset(sess->params, "TargetName") != 0) { iscsi_trace_error(__FILE__, __LINE__, "parm_val_reset() failed\n"); TI_ERROR; } if (param_val_reset(sess->params, "TargetAddress") != 0) { iscsi_trace_error(__FILE__, __LINE__, "parm_val_reset() failed\n"); TI_ERROR; } /* Parse the incoming answer */ PARAM_TEXT_PARSE(l, &sess->sess_params.cred, text_in, len_in, text_out, &len_out, TEXT_RESPONSE_TEXT_LEN, 0, TI_ERROR); if (len_out) { RETURN_NOT_EQUAL("text_rsp.final", text_rsp.final, 0, TI_ERROR, -1); /* * Copy response text into text_cmd->text and * update the length text_cmd->length. This * will be sent out on the next text command. * */ PARAM_TEXT_PARSE(l, &sess->sess_params.cred, text_out, len_out, NULL, NULL, TEXT_RESPONSE_TEXT_LEN, 1, TI_ERROR); iscsi_trace(TRACE_ISCSI_PARAM, __FILE__, __LINE__, "need to send %d bytes response back to target\n", len_out); text_cmd->length = len_out; memcpy(text_cmd->text, text_out, len_out); } else { text_cmd->length = 0; } } text_cmd->final = text_rsp.final; /* Issue callback */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "iscsi_text_cmd_args_t done\n"); callback: if (cmd->status == -1) ret = -1; if (cmd->callback(cmd) != 0) { ret = -1; iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); } TI_CLEANUP; return ret; } #define LOGIN_RESPONSE_TEXT_LEN 2048 static int login_response_i(initiator_session_t * sess, initiator_cmd_t * cmd, uint8_t *header) { iscsi_login_cmd_args_t *login_cmd; iscsi_login_rsp_args_t login_rsp; iscsi_parameter_t *l = sess->params; char *text_in = NULL; char *text_out = NULL; int len_in = 0; int len_out = 0; if ((text_out = iscsi_malloc_atomic(LOGIN_RESPONSE_TEXT_LEN)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); cmd->status = -1; goto callback; } #define LIR_CLEANUP {if (text_in != NULL) iscsi_free_atomic(text_in); if (text_out != NULL) iscsi_free_atomic(text_out);} #define LIR_ERROR {cmd->status=-1; goto callback;} if (cmd) { login_cmd = (iscsi_login_cmd_args_t *) cmd->ptr; } else { iscsi_trace_error(__FILE__, __LINE__, "no initiator_cmd_t specified for iscsi_login_cmd_args_t??\n"); LIR_ERROR; } /* Read login response */ if (iscsi_login_rsp_decap(header, &login_rsp) != 0) { iscsi_trace_error(__FILE__, __LINE__, "login_response_decap() failed\n"); LIR_ERROR; } RETURN_GREATER("Length (should this be hardcoded?)", login_rsp.length, 8192, LIR_CLEANUP, -1); /* XXX - agc */ /* Read & parse text response */ if ((len_in = login_rsp.length) != 0) { if ((text_in = iscsi_malloc_atomic(len_in + 1)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); LIR_ERROR; } if (iscsi_sock_msg(sess->sock, 0, len_in, text_in, 0) != len_in) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); LIR_ERROR; } text_in[len_in] = 0x0; PARAM_TEXT_PARSE(l, &sess->sess_params.cred, text_in, len_in, text_out, &len_out, LOGIN_RESPONSE_TEXT_LEN, 0, LIR_ERROR); if (login_rsp.transit) { WARN_NOT_EQUAL("len_out", len_out, 0); } } /* Check args */ if (login_rsp.status_class != 0) { iscsi_trace_error(__FILE__, __LINE__, "Bad Status-Class: got %d, expected %d\n", login_rsp.status_class, 0); LIR_ERROR; } if (login_rsp.tag != login_cmd->tag) { iscsi_trace_error(__FILE__, __LINE__, "Bad Tag: got %x, expected %x\n", login_rsp.tag, login_cmd->tag); LIR_ERROR; } sess->ExpStatSN = login_rsp.StatSN + 1; if (login_rsp.transit) { if (login_cmd->transit != 1) iscsi_trace_warning(__FILE__, __LINE__, "incoming packet transit bit not set, csg = %d, nsg = %d\n", login_cmd->csg, login_cmd->nsg); switch (login_rsp.nsg) { case ISCSI_LOGIN_STAGE_NEGOTIATE: login_cmd->csg = login_cmd->nsg; login_cmd->nsg = ISCSI_LOGIN_STAGE_FULL_FEATURE; if (params_out(sess, text_out, &len_out, LOGIN_RESPONSE_TEXT_LEN, SESS_TYPE_NONE, !IS_SECURITY) != 0) { iscsi_trace_error(__FILE__, __LINE__, "params_out() failed\n"); LIR_ERROR; } login_cmd->length = len_out; (void) memcpy(login_cmd->text, text_out, len_out); break; case ISCSI_LOGIN_STAGE_FULL_FEATURE: /* Check post conditions */ RETURN_EQUAL("TSIH", 0, login_rsp.tsih, LIR_ERROR, -1); RETURN_NOT_EQUAL("ISID", (uint32_t) login_rsp.isid, (uint32_t) login_cmd->isid, LIR_ERROR, -1); RETURN_NOT_EQUAL("ExpCmdSN", login_rsp.ExpCmdSN, login_cmd->CmdSN, LIR_ERROR, -1); RETURN_GREATER("MaxCmdSN", login_rsp.ExpCmdSN, login_rsp.MaxCmdSN, LIR_ERROR, -1); /* Set remaining session parameters */ sess->CmdSN = login_rsp.ExpCmdSN; sess->MaxCmdSN = login_rsp.MaxCmdSN; sess->tsih = login_rsp.tsih; sess->isid = login_rsp.isid; if (param_equiv(sess->params, "SessionType", "Normal")) { sess->state = INITIATOR_SESSION_STATE_LOGGED_IN_NORMAL; } else if (param_equiv(sess->params, "SessionType", "Discovery")) { sess->state = INITIATOR_SESSION_STATE_LOGGED_IN_DISCOVERY; } else { iscsi_trace_error(__FILE__, __LINE__, "Unknown SessionType \"%s\"\n", param_val(sess->params, "SessionType")); LIR_ERROR; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "*********************************************\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* LOGIN SUCCESSFUL *\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20u *\n", "CID", sess->cid); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20llu *\n", "ISID", sess->isid); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20u *\n", "TSIH", sess->tsih); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20u *\n", "CmdSN", sess->CmdSN); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20u *\n", "MaxCmdSN", sess->MaxCmdSN); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20u *\n", "ExpStatSN", sess->ExpStatSN); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "*********************************************\n"); break; default: LIR_ERROR; } } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "received partial login response\n"); /* Copy response text into login_cmd->text and update the */ /* length login_cmd->length. This will be sent out on the */ /* next login command. */ if (len_out) { PARAM_TEXT_PARSE(l, &sess->sess_params.cred, text_out, len_out, NULL, NULL, 0, 1, LIR_ERROR); iscsi_trace(TRACE_ISCSI_PARAM, __FILE__, __LINE__, "need to send %d bytes response back to target\n", len_out); login_cmd->length = len_out; memcpy(login_cmd->text, text_out, len_out); if (strncmp(text_out, "CHAP_N=", strlen("CHAP_N=")) == 0) { login_cmd->nsg = ISCSI_LOGIN_STAGE_NEGOTIATE; login_cmd->transit = 1; } } else { login_cmd->length = 0; } } /* Callback */ callback: iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "iscsi_login_cmd_args_t done (cmd status %d, iscsi status %d)\n", cmd->status, login_rsp.status_class); if ((*cmd->callback)(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); LIR_CLEANUP; return -1; } LIR_CLEANUP; return 0; } static int logout_command_i(initiator_cmd_t * cmd) { iscsi_logout_cmd_args_t *logout_cmd = (iscsi_logout_cmd_args_t *) cmd->ptr; initiator_session_t *sess = g_target[cmd->isid].sess; uint8_t header[ISCSI_HEADER_LEN]; /* * Insert cmd into the hash table, keyed by the tag. The Rx thread * will */ /* retreive the cmd ptr using the tag from the response PDU. */ if (hash_insert(&g_tag_hash, cmd, logout_cmd->tag) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_insert() failed\n"); return -1; } /* Send logout command PDU */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending logout command\n"); if (iscsi_logout_cmd_encap(header, logout_cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_logout_cmd_encap() failed\n"); return -1; } if (iscsi_sock_msg(sess->sock, 1, ISCSI_HEADER_LEN, header, 0) != ISCSI_HEADER_LEN) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed.\n"); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "logout command sent ok\n"); return 0; } static int logout_response_i(initiator_session_t * sess, initiator_cmd_t * cmd, uint8_t *header) { iscsi_logout_cmd_args_t *logout_cmd; iscsi_logout_rsp_args_t logout_rsp; #define LOR_ERROR {cmd->status=-1; goto callback;} if (cmd) { if (cmd->ptr) { logout_cmd = (iscsi_logout_cmd_args_t *) cmd->ptr; } else { iscsi_trace_error(__FILE__, __LINE__, "no iscsi_logout_cmd_args_t specified for initiator_cmd_t??\n"); LOR_ERROR; } } else { iscsi_trace_error(__FILE__, __LINE__, "no initiator_cmd_t specified for iscsi_logout_cmd_args_t??\n"); return -1; } if (iscsi_logout_rsp_decap(header, &logout_rsp) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_logout_rsp_decap() failed\n"); LOR_ERROR; } RETURN_NOT_EQUAL("Response", logout_rsp.response, ISCSI_LOGOUT_STATUS_SUCCESS, LOR_ERROR, -1); RETURN_NOT_EQUAL("Tag", logout_rsp.tag, logout_cmd->tag, LOR_ERROR, -1); /* Check and update numbering */ RETURN_NOT_EQUAL("StatSN", logout_rsp.StatSN, sess->ExpStatSN, LOR_ERROR, -1); sess->ExpStatSN++; RETURN_NOT_EQUAL("ExpCmdSN", logout_rsp.ExpCmdSN, sess->CmdSN, LOR_ERROR, -1); sess->MaxCmdSN = logout_rsp.MaxCmdSN; /* Callback */ cmd->status = 0; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "LOGOUT_CMD_T done (cmd status %d, iscsi status %d)\n", cmd->status, logout_rsp.response); callback: if ((*cmd->callback)(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "*********************************************\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* LOGOUT SUCCESSFUL *\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20u *\n", "CID", sess->cid); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20llu *\n", "ISID", sess->isid); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "* %20s:%20u *\n", "TSIH", sess->tsih); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "*********************************************\n"); return 0; } static int nop_out_i(initiator_cmd_t * cmd) { uint8_t header[ISCSI_HEADER_LEN]; iscsi_nop_out_args_t *nop_out = cmd->ptr; initiator_session_t *sess = g_target[cmd->isid].sess; int rc, length = nop_out->length; if (nop_out->tag != 0xffffffff) { /* * Insert cmd into the hash table, keyed by * nop_out->tag. Upon receipt of the NOP_IN_T, the Rx * thread will retreive the cmd ptr using the tag from * the NOP_IN_T PDU. */ if (hash_insert(&g_tag_hash, cmd, nop_out->tag) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_insert() failed\n"); return -1; } } /* Encapsulate and send NOP */ nop_out->ExpStatSN = sess->ExpStatSN; /* nop_out->CmdSN = sess->CmdSN++; */ nop_out->transfer_tag = 0xffffffff; if (iscsi_nop_out_encap(header, nop_out) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_nop_out_encap() failed\n"); return -1; } /* * We need to make a copy of nop_out->length and save in the * variable length. Otherwise, we may get a seg fault - as if * this is a NOP_OUT without ping, the Tx thread will issue * the callback function immediately after we return - thereby * de-allocating the NOP_OUT and initiator command structures. * */ if ((rc = iscsi_sock_send_header_and_data(sess->sock, header, ISCSI_HEADER_LEN, nop_out->data, length, 0)) != ISCSI_HEADER_LEN + length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed: got %d expected %d\n", rc, ISCSI_HEADER_LEN + length); return -1; } cmd->status = 0; return 0; } static int scsi_command_i(initiator_cmd_t * cmd) { iscsi_scsi_cmd_args_t *scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr; uint8_t header[ISCSI_HEADER_LEN]; uint64_t target = cmd->isid; initiator_session_t *sess = g_target[target].sess; iscsi_write_data_t data; struct iovec sg_singleton; struct iovec *sg, *sg_copy, *sg_copy_orig, *sg_which; int sg_len, sg_len_copy, sg_len_which; int fragment_flag = 0; sg = sg_copy = sg_copy_orig = sg_which = NULL; sg_len = sg_len_copy = sg_len_which = 0; scsi_cmd->status = 0; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tx_worker[%llu]: scsi op %#x lun %llu trans_len %d length %d send_sg_len %d recv_sg_len %d\n", target, scsi_cmd->cdb[0], scsi_cmd->lun, scsi_cmd->trans_len, scsi_cmd->length, scsi_cmd->send_sg_len, scsi_cmd->recv_sg_len); RETURN_GREATER("target id", (uint32_t) target, CONFIG_INITIATOR_NUM_TARGETS - 1, NO_CLEANUP, -1); /* Set and check scsi_cmd */ if (scsi_cmd->trans_len > sess->sess_params.max_burst_length) { iscsi_trace_error(__FILE__, __LINE__, "scsi_cmd->trans_len (%u) > MaxBurstLength (%u)\n", scsi_cmd->trans_len, sess->sess_params.max_burst_length); return -1; } if (scsi_cmd->length > scsi_cmd->trans_len) { iscsi_trace_error(__FILE__, __LINE__, "scsi_cmd->length (%u) > scsi_cmd->trans_len (%u)\n", scsi_cmd->length, scsi_cmd->trans_len); return -1; } scsi_cmd->ExpStatSN = sess->ExpStatSN; scsi_cmd->CmdSN = sess->CmdSN; scsi_cmd->bytes_sent = scsi_cmd->bytes_recv = 0; /* Always use iovec for data */ if (scsi_cmd->output) { if (scsi_cmd->send_sg_len) { /* Data already an iovec */ sg = (struct iovec *) scsi_cmd->send_data; sg_len = scsi_cmd->send_sg_len; } else { /* Make iovec for data */ sg_singleton.iov_base = scsi_cmd->send_data; sg_singleton.iov_len = scsi_cmd->trans_len; sg = &sg_singleton; sg_len = 1; } } /* * Insert cmd into the hash table, keyed by scsi_cmd->tag. The Rx * thread will */ /* retreive the cmd ptr using the tag from the response PDU. */ if (hash_insert(&g_tag_hash, cmd, scsi_cmd->tag) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_insert() failed\n"); goto error; } /* Send command PDU */ if (scsi_cmd->output && sess->sess_params.immediate_data) { if (sess->sess_params.max_data_seg_length) { scsi_cmd->length = MIN(sess->sess_params.max_data_seg_length, scsi_cmd->trans_len); } else { scsi_cmd->length = scsi_cmd->trans_len; } if (scsi_cmd->length == scsi_cmd->trans_len) scsi_cmd->final = 1; } else { scsi_cmd->length = 0; scsi_cmd->final = 1; } if (iscsi_scsi_cmd_encap(header, scsi_cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_scsi_cmd_encap() failed\n"); goto error; } /* * If we're sending any immediate data, we need to make a new * iovec that contains only the immediata data (a subset of * the original iovec). */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending command PDU with %u bytes immediate data\n", scsi_cmd->length); if (scsi_cmd->length && sess->sess_params.immediate_data) { if ((sg_copy = iscsi_malloc_atomic(sg_len * sizeof(struct iovec))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); goto error; } fragment_flag++; sg_copy_orig = sg_copy; memcpy(sg_copy, sg, sizeof(struct iovec) * sg_len); sg_len_copy = sg_len; if (modify_iov(&sg_copy, &sg_len_copy, 0, scsi_cmd->length) != 0) { iscsi_trace_error(__FILE__, __LINE__, "modify_iov() failed\n"); goto error; } if (scsi_cmd->ahs) { if (iscsi_sock_msg(sess->sock, 1, ISCSI_HEADER_LEN, header, 0) != ISCSI_HEADER_LEN) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); goto error; } if (iscsi_sock_msg(sess->sock, 1, scsi_cmd->ahs_len, scsi_cmd->ahs, 0) != scsi_cmd->ahs_len) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); goto error; } if (iscsi_sock_msg(sess->sock, 1, scsi_cmd->length, sg_copy, sg_len_copy) != scsi_cmd->length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); goto error; } } else { if (iscsi_sock_send_header_and_data(sess->sock, header, ISCSI_HEADER_LEN, sg_copy, scsi_cmd->length, sg_len_copy) != ISCSI_HEADER_LEN + scsi_cmd->length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_send_header_and_data() failed\n"); goto error; } } scsi_cmd->bytes_sent += scsi_cmd->length; } else { if (iscsi_sock_msg(sess->sock, 1, ISCSI_HEADER_LEN, header, 0) != ISCSI_HEADER_LEN) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); goto error; } if (scsi_cmd->ahs_len) { if (iscsi_sock_msg(sess->sock, 1, scsi_cmd->ahs_len, scsi_cmd->ahs, 0) != scsi_cmd->ahs_len) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); goto error; } } } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "command PDU sent with %u bytes immediate data (%u bytes AHS)\n", scsi_cmd->length, scsi_cmd->ahs_len); /* * Send data PDUS if 1) we're not in R2T mode and 2) we * haven't sent everything as immediate data and 3) we have * not reached the first burst when sending immediate data */ if (scsi_cmd->output && (!sess->sess_params.initial_r2t) && (scsi_cmd->bytes_sent != scsi_cmd->trans_len) && ((!sess->sess_params.first_burst_length) || (scsi_cmd->bytes_sent < sess->sess_params.first_burst_length))) { uint32_t DataSN = 0; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "preparing to send %d bytes write data\n", scsi_cmd->trans_len - scsi_cmd->bytes_sent); do { (void) memset(&data, 0x0, sizeof(data)); /* * Take into account that MaxRecvPDULength and * FirstBurstLength could both be "0" (no limit) */ if (sess->sess_params.max_data_seg_length) { if (sess->sess_params.first_burst_length) { data.length = MIN_3( sess->sess_params.first_burst_length - scsi_cmd->bytes_sent, sess->sess_params.max_data_seg_length, scsi_cmd->trans_len - scsi_cmd->bytes_sent); } else { data.length = MIN( sess->sess_params.max_data_seg_length, scsi_cmd->trans_len - scsi_cmd->bytes_sent); } } else { if (sess->sess_params.first_burst_length) { data.length = MIN( sess->sess_params.first_burst_length - scsi_cmd->bytes_sent, scsi_cmd->trans_len - scsi_cmd->bytes_sent); } else { data.length = scsi_cmd->trans_len - scsi_cmd->bytes_sent; } } #define FRAG_CLEANUP {if (fragment_flag) iscsi_free_atomic(sg_copy);} RETURN_EQUAL("data.length", data.length, 0, FRAG_CLEANUP, -1); if (scsi_cmd->bytes_sent + data.length == scsi_cmd->trans_len) data.final = 1; data.tag = scsi_cmd->tag; data.transfer_tag = 0xffffffff; data.ExpStatSN = sess->ExpStatSN; data.DataSN = DataSN++; data.offset = scsi_cmd->bytes_sent; if (iscsi_write_data_encap(header, &data) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_write_data_encap() failed\n"); goto error; } if (data.length != scsi_cmd->trans_len) { /* * Make copy of iovec and modify with offset * and length */ if (!fragment_flag) { if ((sg_copy = iscsi_malloc_atomic(sg_len * sizeof(struct iovec))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); goto error; } sg_copy_orig = sg_copy; fragment_flag++; } sg_copy = sg_copy_orig; memcpy(sg_copy, sg, sizeof(struct iovec) * sg_len); sg_len_copy = sg_len; if (modify_iov(&sg_copy, &sg_len_copy, scsi_cmd->bytes_sent, data.length) != 0) { iscsi_trace_error(__FILE__, __LINE__, "modify_iov() failed\n"); goto error; } sg_which = sg_copy; sg_len_which = sg_len_copy; } else { /* * Data was not fragmented; use the original * iovec. */ sg_which = sg; sg_len_which = sg_len; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending write data PDU (offset %u, len %u, sg_len %u)\n", data.offset, data.length, sg_len_which); if (iscsi_sock_send_header_and_data(sess->sock, header, ISCSI_HEADER_LEN, sg_which, data.length, sg_len_which) != ISCSI_HEADER_LEN + data.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_send_header_and_data() failed\n"); goto error; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sent write data PDU (offset %u, len %u)\n", data.offset, data.length); scsi_cmd->bytes_sent += data.length; } while ((scsi_cmd->bytes_sent < scsi_cmd->trans_len) && ((scsi_cmd->bytes_sent < sess->sess_params.first_burst_length) || (!sess->sess_params.first_burst_length))); if (scsi_cmd->trans_len - scsi_cmd->bytes_sent) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "REACHED FIRST BURST\n"); } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "successfully sent %u of %u bytes write data\n", scsi_cmd->bytes_sent, scsi_cmd->trans_len); } if (scsi_cmd->output && (scsi_cmd->trans_len - scsi_cmd->bytes_sent)) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "expecting R2T for remaining %u bytes write data\n", scsi_cmd->trans_len - scsi_cmd->bytes_sent); } if (fragment_flag) iscsi_free_atomic(sg_copy_orig); sess->CmdSN++; return 0; error: if (fragment_flag) iscsi_free_atomic(sg_copy); return -1; } static int reject_i(initiator_session_t * sess, uint8_t *header) { initiator_cmd_t *cmd = NULL; iscsi_reject_t reject; uint8_t bad_header[ISCSI_HEADER_LEN]; uint32_t tag; /* Get & check args */ if (iscsi_reject_decap(header, &reject) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_reject_decap() failed\n"); return -1; } RETURN_NOT_EQUAL("reject.length", reject.length, ISCSI_HEADER_LEN, NO_CLEANUP, -1); /* Read bad header, extract tag, and get cmd from hash table */ if (iscsi_sock_msg(sess->sock, 0, ISCSI_HEADER_LEN, bad_header, 0) != ISCSI_HEADER_LEN) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); return -1; } tag = ISCSI_NTOHL(*((uint32_t *) (bad_header + 16))); iscsi_trace_error(__FILE__, __LINE__, "REJECT PDU: tag %#x (reason %#x)\n", tag, reject.reason); if (tag != 0xffffffff) { if ((cmd = hash_remove(&g_tag_hash, tag)) == NULL) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "no cmd ptr associated with tag %#x\n", tag); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "cmd %p associated with tag %#x\n", cmd, tag); ISCSI_LOCK(&sess->rx_worker.work_mutex, return -1); if (!cmd->tx_done) ISCSI_WAIT(&sess->rx_worker.work_cond, &sess->rx_worker.work_mutex, return -1); ISCSI_UNLOCK(&sess->rx_worker.work_mutex, return -1); } } else { iscsi_trace_error(__FILE__, __LINE__, "no command associated with tag %#x\n", tag); } /* Execute callback to complete initiator_cmd_t */ if (cmd) { cmd->status = -1; if (cmd->callback) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "issuing callback for cmd associated with tag %#x\n", tag); if ((*cmd->callback)(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); return -1; } } else { iscsi_trace_error(__FILE__, __LINE__, "no callback associated with tag %#x\n", tag); } } return 0; } static int async_msg_i(initiator_session_t * sess, uint8_t *header) { iscsi_async_msg_t msg; /* Get & check args */ if (iscsi_amsg_decap(header, &msg) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_amsg_decap() failed\n"); return -1; } sess->CmdSN = msg.ExpCmdSN; sess->MaxCmdSN = msg.MaxCmdSN; sess->ExpStatSN = msg.StatSN + 1; /* Read Sense Data */ if (msg.length) { uint8_t *sense_data = NULL; if ((sense_data = iscsi_malloc(msg.length)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "reading %d bytes sense data \n", msg.length); if (iscsi_sock_msg(sess->sock, 0, msg.length, sense_data, 0) != msg.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); if (sense_data != NULL) iscsi_free(sense_data); return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "read %d bytes sense data ok (currently discarding)\n", msg.length); if (sense_data != NULL) iscsi_free(sense_data); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "no sense data available\n"); } switch (msg.AsyncEvent) { case 0: /* Ignore SCSI asyn messages for now */ break; case 1: case 4: /* Ignore Parameter Negotiation. Send Logout */ logout_phase_i(sess); /* FALLTHROUGH */ case 2: case 3: if (iscsi_sock_shutdown(sess->sock, 1) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_shutdown() failed\n"); } return -1; case 255: break; default: break; } return 0; } static int nop_in_i(initiator_session_t * sess, initiator_cmd_t * cmd, uint8_t *header) { iscsi_nop_out_args_t *nop_out = NULL; iscsi_nop_in_args_t nop_in; uint8_t *ping_data = NULL; int i; if (cmd) { nop_out = (iscsi_nop_out_args_t *) cmd->ptr; } else { iscsi_trace_error(__FILE__, __LINE__, "no initiator_cmd_t associated with this NOP_IN\n"); } if (iscsi_nop_in_decap(header, &nop_in) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_nop_in() failed\n"); return -1; } if (cmd) RETURN_NOT_EQUAL("nop_in.length", nop_in.length, nop_out->length, NO_CLEANUP, -1); if (nop_in.length) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "reading %d bytes ping data\n", nop_in.length); if ((ping_data = iscsi_malloc_atomic(nop_in.length)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } #define NOI_CLEANUP {if (ping_data) iscsi_free_atomic(ping_data);} #define NOI_ERROR {NOI_CLEANUP; return -1;} if (iscsi_sock_msg(sess->sock, 0, nop_in.length, ping_data, 0) != nop_in.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); NOI_ERROR; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "successfully read %d bytes ping data\n", nop_in.length); if (cmd) { for (i = 0; i < nop_in.length; i++) { if (nop_out->data[i] != ping_data[i]) { iscsi_trace_error(__FILE__, __LINE__, "Bad ping data[%d]. Got %#x, expected %#x\n", i, ping_data[i], nop_out->data[i]); NOI_ERROR; } } } } /* Send ping response (if initiated by target) */ if (nop_in.transfer_tag != 0xffffffff) { uint8_t nop_header[ISCSI_HEADER_LEN]; iscsi_nop_out_args_t nop_out_args; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending %d byte ping response\n", nop_in.length); (void) memset(&nop_out_args, 0x0, sizeof(nop_out_args)); nop_out_args.tag = 0xffffffff; nop_out_args.immediate = 0x40; nop_out_args.transfer_tag = nop_in.transfer_tag; nop_out_args.length = nop_in.length; nop_out_args.lun = nop_in.lun; nop_out_args.ExpStatSN = sess->ExpStatSN; nop_out_args.CmdSN = sess->CmdSN; if (iscsi_nop_out_encap(nop_header, &nop_out_args) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_nop_out_encap() failed\n"); NOI_ERROR; } if (iscsi_sock_send_header_and_data(sess->sock, nop_header, nop_out_args.length, ping_data, nop_in.length, 0) != nop_in.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); NOI_ERROR; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "successfully sent %d byte ping response\n", nop_in.length); } NOI_CLEANUP; /* Check and update numbering */ sess->ExpStatSN = nop_in.StatSN + 1; /* * RETURN_NOT_EQUAL("StatSN", nop_in.StatSN, sess->ExpStatSN++, * NO_CLEANUP, -1); */ sess->CmdSN = nop_in.ExpCmdSN; /* * RETURN_NOT_EQUAL("ExpCmdSN", nop_in.ExpCmdSN, sess->CmdSN, * NO_CLEANUP, -1); */ sess->MaxCmdSN = nop_in.MaxCmdSN; /* Callback */ if (cmd) { cmd->status = 0; if (cmd->callback) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "NOP_OUT_T done (cmd status %d)\n", cmd->status); if ((*cmd->callback)(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); return -1; } } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "no callback associated with NOP_IN_T??\n"); return -1; } } return 0; } static int scsi_r2t_i(initiator_session_t * sess, initiator_cmd_t * cmd, uint8_t *header) { iscsi_r2t_t r2t; iscsi_scsi_cmd_args_t *scsi_cmd; iscsi_write_data_t data; uint32_t bytes_sent; uint32_t DataSN; struct iovec sg_singleton; struct iovec *sg, *sg_copy, *sg_copy_orig, *sg_which; int sg_len, sg_len_copy, sg_len_which; int fragment_flag; /* Make sure an initiator_cmd_t was specified, that it has a * callback function specified and that it also has a * iscsi_scsi_cmd_args_t associated with it. */ if (cmd) { if ((scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "no iscsi_scsi_cmd_args_t associated with this initiator_cmd_t??\n"); return -1; } else if (cmd->callback == NULL) { iscsi_trace_error(__FILE__, __LINE__, "no callback associated with this initiator_cmd_t??\n"); return -1; } } else { iscsi_trace_error(__FILE__, __LINE__, "no initiator_cmd_t associated with this iscsi_r2t_t??\n"); return -1; } sg = sg_copy = sg_copy_orig = sg_which = NULL; sg_len = sg_len_copy = sg_len_which = 0; if (iscsi_r2t_decap(header, &r2t) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_r2t_decap() failed\n"); return -1; } /* Check args */ RETURN_EQUAL("r2t.length", r2t.length, 0, NO_CLEANUP, -1); /* Check and update numbering */ RETURN_NOT_EQUAL("StatSN", r2t.StatSN, sess->ExpStatSN, NO_CLEANUP, -1); RETURN_NOT_EQUAL("ExpCmdSN", r2t.ExpCmdSN, sess->CmdSN, NO_CLEANUP, -1); sess->MaxCmdSN = r2t.MaxCmdSN; /* Send back requested data */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending %d bytes R2T write data (offset %u)\n", r2t.length, r2t.offset); if (scsi_cmd->send_sg_len) { sg = (struct iovec *) scsi_cmd->send_data; sg_len = scsi_cmd->send_sg_len; } else { sg_singleton.iov_base = scsi_cmd->send_data; sg_singleton.iov_len = scsi_cmd->trans_len; sg = &sg_singleton; sg_len = 1; } fragment_flag = 0; bytes_sent = 0; DataSN = 0; #define FF_CLEANUP {if (fragment_flag) iscsi_free_atomic(sg_copy_orig);} do { (void) memset(&data, 0x0, sizeof(data)); if (sess->sess_params.max_data_seg_length) { data.length = MIN(sess->sess_params.max_data_seg_length, r2t.length - bytes_sent); } else { data.length = r2t.length - bytes_sent; } if (bytes_sent + data.length == r2t.length) { data.final = 1; } data.tag = r2t.tag; data.transfer_tag = r2t.transfer_tag; data.ExpStatSN = sess->ExpStatSN; data.DataSN = DataSN++; data.offset = r2t.offset + bytes_sent; data.lun = scsi_cmd->lun; if (iscsi_write_data_encap(header, &data) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_write_data_encap() failed\n"); FF_CLEANUP; return -1; } if ((data.length < r2t.length) || (r2t.offset)) { if (data.length < r2t.length) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "R2T data is being fragmented: sending %u bytes of %u requested\n", data.length, r2t.length); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "R2T data starts at offset %u, desired length %u\n", r2t.offset, r2t.length); } /* Allocate space for a copy of the original iovec */ if (!fragment_flag) { if ((sg_copy_orig = iscsi_malloc_atomic(sg_len * sizeof(struct iovec))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } fragment_flag++; } /* * Copy and modify original iovec with new offset and * length */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "modifying original iovec with offset %u length %u\n", r2t.offset + bytes_sent, data.length); sg_copy = sg_copy_orig; sg_len_copy = sg_len; memcpy(sg_copy, sg, sizeof(struct iovec) * sg_len); if (modify_iov(&sg_copy, &sg_len_copy, r2t.offset + bytes_sent, data.length) != 0) { iscsi_trace_error(__FILE__, __LINE__, "modify_iov() failed\n"); FF_CLEANUP; return -1; } sg_which = sg_copy; sg_len_which = sg_len_copy; } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "using original iovec for R2T transfer (offset %u, length %u)\n", r2t.offset, r2t.length); sg_which = sg; sg_len_which = sg_len; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sending R2T write data PDU (offset %u, len %u, sg_len %u)\n", data.offset, data.length, sg_len_which); if (iscsi_sock_send_header_and_data(sess->sock, header, ISCSI_HEADER_LEN, sg_which, data.length, sg_len_which) != ISCSI_HEADER_LEN + data.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_send_header_and_data() failed\n"); FF_CLEANUP; return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "sent write data PDU OK (offset %u, len %u)\n", data.offset, data.length); bytes_sent += data.length; scsi_cmd->bytes_sent += data.length; } while (bytes_sent < r2t.length); FF_CLEANUP; if (hash_insert(&g_tag_hash, cmd, scsi_cmd->tag) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_insert() failed\n"); return -1; } return 0; } static int scsi_response_i(initiator_session_t * sess, initiator_cmd_t * cmd, uint8_t *header) { iscsi_scsi_cmd_args_t *scsi_cmd; iscsi_scsi_rsp_t scsi_rsp; /* Make sure an initiator_cmd_t was specified, that it has a * callback function specified and that it also has a * iscsi_scsi_cmd_args_t associated with it. */ if (cmd) { if ((scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "no iscsi_scsi_cmd_args_t associated with this initiator_cmd_t??\n"); return -1; } else if (cmd->callback == NULL) { iscsi_trace_error(__FILE__, __LINE__, "no callback associated with this initiator_cmd_t??\n"); return -1; } } else { iscsi_trace_error(__FILE__, __LINE__, "no initiator_cmd_t associated with this iscsi_scsi_rsp_t??\n"); return -1; } /* * Read SCSI response and check return args. Those marked * "FIX ME" are not yet implemented. */ if (iscsi_scsi_rsp_decap(header, &scsi_rsp) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_scsi_rsp_decap() failed\n"); return -1; } RETURN_NOT_EQUAL("o bit (FIX ME)", scsi_rsp.bidi_overflow, 0, NO_CLEANUP, -1); RETURN_NOT_EQUAL("u bit (FIX ME)", scsi_rsp.bidi_underflow, 0, NO_CLEANUP, -1); RETURN_NOT_EQUAL("O bit (FIX ME)", scsi_rsp.overflow, 0, NO_CLEANUP, -1); RETURN_NOT_EQUAL("iSCSI Response (FIX ME)", scsi_rsp.response, 0, NO_CLEANUP, -1); RETURN_NOT_EQUAL("Tag", scsi_rsp.tag, scsi_cmd->tag, NO_CLEANUP, -1); RETURN_NOT_EQUAL("Bidi Residual Count", scsi_rsp.bidi_res_cnt, 0, NO_CLEANUP, -1); RETURN_NOT_EQUAL("StatSN", scsi_rsp.StatSN, sess->ExpStatSN, NO_CLEANUP, -1); sess->ExpStatSN = scsi_rsp.StatSN + 1; if (sess->sess_params.max_data_seg_length) { RETURN_GREATER("DataSegmentLength (FIX ME)", scsi_rsp.length, sess->sess_params.max_data_seg_length, NO_CLEANUP, -1); } if ((scsi_rsp.status == 0) && (scsi_rsp.length != 0)) { iscsi_trace_error(__FILE__, __LINE__, "Unexpected DataSegmentLength %u with GOOD SCSI status\n", scsi_rsp.length); return -1; } /* * Make sure all data was successfully transferred if command * completed successfully, otherwise read sense data. */ if (scsi_rsp.status == 0) { if (scsi_cmd->output) { RETURN_NOT_EQUAL("scsi_cmd->bytes_sent", scsi_cmd->bytes_sent, scsi_cmd->trans_len, NO_CLEANUP, -1); if (scsi_cmd->input) { RETURN_NOT_EQUAL("scsi_cmd->bytes_recv", scsi_cmd->bytes_recv, scsi_cmd->bidi_trans_len, NO_CLEANUP, -1); } } else if (scsi_cmd->input) { } } else if (scsi_rsp.length) { uint8_t *sense_data = NULL; if ((sense_data = iscsi_malloc(scsi_rsp.length)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc() failed\n"); return -1; } iscsi_trace_error(__FILE__, __LINE__, "reading %d bytes sense data (recv_sg_len %u)\n", scsi_rsp.length, scsi_cmd->recv_sg_len); if (iscsi_sock_msg(sess->sock, 0, scsi_rsp.length, sense_data, 0) != scsi_rsp.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); if (sense_data != NULL) iscsi_free(sense_data); return -1; } iscsi_trace_error(__FILE__, __LINE__, "read %d bytes sense data ok (currently discarding)\n", scsi_rsp.length); if (sense_data != NULL) iscsi_free(sense_data); } else { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "no sense data available\n"); } /* Check and update numbering */ /* * RETURN_NOT_EQUAL("ExpCmdSN", scsi_rsp.ExpCmdSN, sess->CmdSN, * NO_CLEANUP, -1); */ sess->MaxCmdSN = scsi_rsp.MaxCmdSN; /* Set initiator_cmd_t status, iscsi_scsi_cmd_args_t status */ /* and execute callback function */ cmd->status = 0; scsi_cmd->status = scsi_rsp.status; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "iscsi_scsi_cmd_args_t done (cmd status %d, iscsi status %d, scsi status %d)\n", cmd->status, scsi_rsp.response, scsi_rsp.status); if ((*cmd->callback)(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); return -1; } return 0; } static int scsi_read_data_i(initiator_session_t * sess, initiator_cmd_t * cmd, uint8_t *header) { iscsi_read_data_t data; iscsi_scsi_cmd_args_t *scsi_cmd; int rc; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "processing read data\n"); /* Make sure an initiator_cmd_t was specified, that it has a * callback function specified and that it also has a * iscsi_scsi_cmd_args_t associated with it. */ if (cmd) { if (cmd->type != ISCSI_SCSI_CMD) { iscsi_trace_error(__FILE__, __LINE__, "Invalid response from target for cmd type (%#x)\n", cmd->type); cmd->status = -1; if (cmd->callback) { (*cmd->callback)(cmd); } return -1; } if ((scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "no iscsi_scsi_cmd_args_t associated with this initiator_cmd_t??\n"); return -1; } else if (cmd->callback == NULL) { iscsi_trace_error(__FILE__, __LINE__, "no callback associated with this initiator_cmd_t??\n"); return -1; } } else { iscsi_trace_error(__FILE__, __LINE__, "no initiator_cmd_t associated with this iscsi_read_data_t??\n"); return -1; } if (iscsi_read_data_decap(header, &data) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_scsi_rsp_decap() failed\n"); return -1; } /* Check args */ RETURN_NOT_EQUAL("Overflow bit", data.overflow, 0, NO_CLEANUP, -1); RETURN_NOT_EQUAL("Underflow bit", data.underflow, 0, NO_CLEANUP, -1); RETURN_NOT_EQUAL("Tag", data.task_tag, scsi_cmd->tag, NO_CLEANUP, -1); RETURN_NOT_EQUAL("Residual Count", data.res_count, 0, NO_CLEANUP, -1); if (sess->sess_params.max_data_seg_length) { RETURN_GREATER("Length", data.length, sess->sess_params.max_data_seg_length, NO_CLEANUP, -1); } /* Check and update numbering */ WARN_NOT_EQUAL("ExpCmdSN", data.ExpCmdSN, sess->CmdSN); sess->MaxCmdSN = data.MaxCmdSN; /* Need to optimize this section */ if (scsi_cmd->recv_sg_len) { int sg_len = scsi_cmd->recv_sg_len; struct iovec *sg; struct iovec *sg_orig = NULL; char *sgp; uint32_t total_len, disp; int i; if (data.length != scsi_cmd->trans_len) { /* Make a copy of the iovec */ if ((sg_orig = sg = iscsi_malloc_atomic(sizeof(struct iovec) * sg_len)) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } memcpy(sg, scsi_cmd->recv_data, sizeof(struct iovec) * sg_len); /* Find offset in iovecs */ total_len = 0; disp = data.offset; for (i = 0; i < sg_len; i++) { total_len += sg[i].iov_len; if (total_len > data.offset) { break; } disp -= sg[i].iov_len; } sg[i].iov_len -= disp; sgp = sg[i].iov_base; sgp += disp; sg[i].iov_base = sgp; sg_len -= i; sg = &sg[i]; /* Find last iovec needed for read */ total_len = 0; for (i = 0; i < sg_len; i++) { total_len += sg[i].iov_len; if (total_len >= data.length) { break; } } sg[i].iov_len -= (total_len - data.length); sg_len = i + 1; } else { sg = (struct iovec *) scsi_cmd->recv_data; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "reading %d bytes into sg buffer (total offset %u)\n", data.length, data.offset); if ((rc = iscsi_sock_msg(sess->sock, 0, data.length, (uint8_t *) sg, sg_len)) != data.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed: got %u, expected %u\n", rc, data.length); if (sg_orig) iscsi_free_atomic(sg_orig); return -1; } scsi_cmd->bytes_recv += data.length; if (sg_orig) iscsi_free_atomic(sg_orig); } else { if (data.length) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "reading %d bytes into dest buffer (offset %u)\n", data.length, data.offset); if (iscsi_sock_msg(sess->sock, 0, data.length, scsi_cmd->recv_data + data.offset, 0) != data.length) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_sock_msg() failed\n"); return -1; } scsi_cmd->bytes_recv += data.length; } } /* Check for status */ if (data.S_bit) { iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "received status with final PDU\n"); RETURN_NOT_EQUAL("Final Bit", data.final, 1, NO_CLEANUP, -1); RETURN_NOT_EQUAL("StatSN", data.StatSN, sess->ExpStatSN++, NO_CLEANUP, -1); scsi_cmd->status = data.status = 0; cmd->status = 0; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "scsi op %#x done (tag %u, status %d)\n", scsi_cmd->cdb[0], scsi_cmd->tag, scsi_cmd->status); if ((*cmd->callback)(cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "callback() failed\n"); return -1; } } else { if (hash_insert(&g_tag_hash, cmd, scsi_cmd->tag) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_insert() failed\n"); return -1; } } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "read data processed\n"); return 0; } int initiator_info(char *ptr, int size, int len) { initiator_session_t *sess; int i; ptr[0] = 0x0; len += snprintf(ptr, size - len, " %3s %30s %25s\n\n", "TID", "TargetName", "TargetAddress"); for (i = 0; i < CONFIG_INITIATOR_NUM_TARGETS; i++) { len += snprintf(ptr + len, size - len, " %3i %30s %20s:%d (", i, g_target[i].TargetName, g_target[i].ip, g_target[i].port); if (g_target[i].has_session) { sess = g_target[i].sess; if (sess->state & INITIATOR_SESSION_STATE_INITIALIZING) len += snprintf(ptr + len, size - len, "%s", "initializing"); if (sess->state & INITIATOR_SESSION_STATE_INITIALIZED) len += snprintf(ptr + len, size - len, "%s", "initialized"); if (sess->state & INITIATOR_SESSION_STATE_CONNECTING) len += snprintf(ptr + len, size - len, "%s", "connecting"); if (sess->state & INITIATOR_SESSION_STATE_CONNECTED) len += snprintf(ptr + len, size - len, "%s", "connected"); if (sess->state & INITIATOR_SESSION_STATE_LOGGING_IN) len += snprintf(ptr + len, size - len, "%s", "logging in"); if (sess->state & INITIATOR_SESSION_STATE_LOGGED_IN_NORMAL) len += snprintf(ptr + len, size - len, "%s", "Normal session"); if (sess->state & INITIATOR_SESSION_STATE_LOGGED_IN_DISCOVERY) len += snprintf(ptr + len, size - len, "%s", "Discovery session"); if (sess->state & INITIATOR_SESSION_STATE_LOGGING_OUT) len += snprintf(ptr + len, size - len, "%s", "logging out"); if (sess->state & INITIATOR_SESSION_STATE_LOGGED_OUT) len += snprintf(ptr + len, size - len, "%s", "logged out"); if (sess->state & INITIATOR_SESSION_STATE_DESTROYING) len += snprintf(ptr + len, size - len, "%s", "destroying"); if (sess->tx_worker.state & ISCSI_WORKER_STATE_ERROR) len += snprintf(ptr + len, size - len, "%s", " **Tx Error** "); if (sess->rx_worker.state & ISCSI_WORKER_STATE_ERROR) len += snprintf(ptr + len, size - len, "%s", " **Rx Error** "); } else { len += snprintf(ptr + len, size - len, "%s", "No Session"); } len += snprintf(ptr + len, size - len, ")\n"); } return len; } int initiator_discover(char *host, uint64_t target, int lun) { iscsi_nop_out_args_t discover_cmd; initiator_cmd_t cmd; cmd.type = ISCSI_NOP_OUT; cmd.ptr = &discover_cmd; cmd.isid = target; (void) strlcpy(cmd.targetname, host, sizeof(cmd.targetname)); (void) memset(&discover_cmd, 0x0, sizeof(iscsi_nop_out_args_t)); discover_cmd.length = 1; discover_cmd.data = (const uint8_t *) ""; discover_cmd.lun = lun; discover_cmd.tag = 0xffffffff; if (initiator_command(&cmd) != 0) { iscsi_trace_error(__FILE__, __LINE__, "initiator_command() failed\n"); return -1; } return 0; } void get_target_info(uint64_t target, initiator_target_t *ip) { (void) memcpy(ip, &g_target[target], sizeof(*ip)); } int ii_initiator_init(const char *hostname, int port, int address_family, const char *user, char *lun, int auth_type, int mutual_auth, int digest_type) { initiator_session_t *sess = NULL; #define INIT_CLEANUP {if (sess != NULL) iscsi_free_atomic(sess);} #define INIT_ERROR {INIT_CLEANUP; return -1;} iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initializing initiator\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "target config filename to read from:%s\n", gfilename); if (get_target_config(hostname, port) != 0) { iscsi_trace_error(__FILE__, __LINE__, "Error getting target configuration from config file\n"); return -1; } (void) strlcpy(g_target[0].iqnwanted, lun, sizeof(g_target[0].iqnwanted)); g_initiator_state = 0; if (iscsi_queue_init(&g_session_q, CONFIG_INITIATOR_MAX_SESSIONS) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_init() failed\n"); return -1; } if ((sess = iscsi_malloc_atomic(sizeof(initiator_session_t))) == NULL) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_malloc_atomic() failed\n"); return -1; } if (iscsi_queue_insert(&g_session_q, sess) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_init() failed\n"); INIT_CLEANUP; return -1; } sess->sess_params.cred.user = strdup(user); sess->sess_params.auth_type = auth_type; sess->sess_params.mutual_auth = mutual_auth; sess->sess_params.digest_wanted = digest_type; iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "%d free sessions available\n", CONFIG_INITIATOR_MAX_SESSIONS); g_tag = 0xabc123; if (hash_init(&g_tag_hash, CONFIG_INITIATOR_QUEUE_DEPTH) != 0) { iscsi_trace_error(__FILE__, __LINE__, "hash_init() failed\n"); INIT_CLEANUP; return -1; } iscsi_spin_init(&g_tag_spin); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "tag hash table initialized with queue depth %d\n", CONFIG_INITIATOR_QUEUE_DEPTH); /* * Start enqueue worker. This thread accepts scsi commands * from initiator_enqueue() and queues them onto one of the tx * worker queues. */ iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "starting enqueue worker\n"); if (iscsi_queue_init(&g_enqueue_q, CONFIG_INITIATOR_QUEUE_DEPTH) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_queue_init() failed\n"); INIT_CLEANUP; return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "about to initialize mutex\n"); ISCSI_MUTEX_INIT(&g_enqueue_worker.work_mutex, INIT_ERROR); ISCSI_COND_INIT(&g_enqueue_worker.work_cond, INIT_ERROR); ISCSI_MUTEX_INIT(&g_enqueue_worker.exit_mutex, INIT_ERROR); ISCSI_COND_INIT(&g_enqueue_worker.exit_cond, INIT_ERROR); ISCSI_LOCK(&g_enqueue_worker.exit_mutex, INIT_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "spawning thread for enqueue worker\n"); if (iscsi_thread_create(&g_enqueue_worker.thread, (void *) &enqueue_worker_proc, &g_enqueue_worker) != 0) { iscsi_trace_error(__FILE__, __LINE__, "iscsi_threads_create() failed\n"); INIT_CLEANUP; return -1; } iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "thread spawned, waiting for signal\n"); ISCSI_WAIT(&g_enqueue_worker.exit_cond, &g_enqueue_worker.exit_mutex, INIT_ERROR); ISCSI_UNLOCK(&g_enqueue_worker.exit_mutex, INIT_ERROR); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "successfully started enqueue worker\n"); iscsi_trace(TRACE_ISCSI_DEBUG, __FILE__, __LINE__, "initiator initialization complete\n"); return 0; }