// SPDX-License-Identifier: GPL-2.0-or-later /******************************************************************************* * This file contains main functions related to the iSCSI Target Core Driver. * * (c) Copyright 2007-2013 Datera, Inc. * * Author: Nicholas A. Bellinger <nab@linux-iscsi.org> *
******************************************************************************/
/* * Note that IQN formatting is expected to be done in userspace, and * no explict IQN format checks are done here.
*/ struct iscsi_tiqn *iscsit_add_tiqn(unsignedchar *buf)
{ struct iscsi_tiqn *tiqn = NULL; int ret;
staticvoid iscsit_wait_for_tiqn(struct iscsi_tiqn *tiqn)
{ /* * Wait for accesses to said struct iscsi_tiqn to end.
*/
spin_lock(&tiqn->tiqn_state_lock); while (tiqn->tiqn_access_count != 0) {
spin_unlock(&tiqn->tiqn_state_lock);
msleep(10);
spin_lock(&tiqn->tiqn_state_lock);
}
spin_unlock(&tiqn->tiqn_state_lock);
}
void iscsit_del_tiqn(struct iscsi_tiqn *tiqn)
{ /* * iscsit_set_tiqn_shutdown sets tiqn->tiqn_state = TIQN_STATE_SHUTDOWN * while holding tiqn->tiqn_state_lock. This means that all subsequent * attempts to access this struct iscsi_tiqn will fail from both transport * fabric and control code paths.
*/ if (iscsit_set_tiqn_shutdown(tiqn) < 0) {
pr_err("iscsit_set_tiqn_shutdown() failed\n"); return;
}
match = iscsit_check_np_match(sockaddr, np, network_transport); if (match) { /* * Increment the np_exports reference count now to * prevent iscsit_del_np() below from being called * while iscsi_tpg_add_network_portal() is called.
*/
np->np_exports++;
spin_unlock_bh(&np->np_thread_lock); return np;
}
spin_unlock_bh(&np->np_thread_lock);
}
return NULL;
}
struct iscsi_np *iscsit_add_np( struct sockaddr_storage *sockaddr, int network_transport)
{ struct iscsi_np *np; int ret;
mutex_lock(&np_lock);
/* * Locate the existing struct iscsi_np if already active..
*/
np = iscsit_get_np(sockaddr, network_transport); if (np) {
mutex_unlock(&np_lock); return np;
}
ret = iscsi_target_setup_login_socket(np, sockaddr); if (ret != 0) {
kfree(np);
mutex_unlock(&np_lock); return ERR_PTR(ret);
}
np->np_thread = kthread_run(iscsi_target_login_thread, np, "iscsi_np"); if (IS_ERR(np->np_thread)) {
pr_err("Unable to create kthread: iscsi_np\n");
ret = PTR_ERR(np->np_thread);
kfree(np);
mutex_unlock(&np_lock); return ERR_PTR(ret);
} /* * Increment the np_exports reference count now to prevent * iscsit_del_np() below from being run while a new call to * iscsi_tpg_add_network_portal() for a matching iscsi_np is * active. We don't need to hold np->np_thread_lock at this * point because iscsi_np has not been added to g_np_list yet.
*/
np->np_exports = 1;
np->np_thread_state = ISCSI_NP_THREAD_ACTIVE;
if (np->np_thread) { /* * We need to send the signal to wakeup Linux/Net * which may be sleeping in sock_accept()..
*/
send_sig(SIGINT, np->np_thread, 1);
kthread_stop(np->np_thread);
np->np_thread = NULL;
}
cmd->buf_ptr = kmemdup(buf, ISCSI_HDR_LEN, GFP_KERNEL); if (!cmd->buf_ptr) {
pr_err("Unable to allocate memory for cmd->buf_ptr\n");
iscsit_free_cmd(cmd, false); return -1;
}
if (add_to_conn) {
spin_lock_bh(&conn->cmd_lock);
list_add_tail(&cmd->i_conn_node, &conn->conn_cmd_list);
spin_unlock_bh(&conn->cmd_lock);
}
cmd->i_state = ISTATE_SEND_REJECT;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); /* * Perform the kref_put now if se_cmd has already been setup by * scsit_setup_scsi_cmd()
*/ if (do_put) {
pr_debug("iscsi reject: calling target_put_sess_cmd >>>>>>\n");
target_put_sess_cmd(&cmd->se_cmd);
} return -1;
}
/* * Map some portion of the allocated scatterlist to an iovec, suitable for * kernel sockets to copy data in/out.
*/ staticint iscsit_map_iovec(struct iscsit_cmd *cmd, struct kvec *iov, int nvec,
u32 data_offset, u32 data_length)
{
u32 i = 0, orig_data_length = data_length; struct scatterlist *sg; unsignedint page_off;
/* * We know each entry in t_data_sg contains a page.
*/
u32 ent = data_offset / PAGE_SIZE;
/* FIXME; Add checks for AdditionalHeaderSegment */
if (!(hdr->flags & ISCSI_FLAG_CMD_WRITE) &&
!(hdr->flags & ISCSI_FLAG_CMD_FINAL)) {
pr_err("ISCSI_FLAG_CMD_WRITE & ISCSI_FLAG_CMD_FINAL" " not set. Bad iSCSI Initiator.\n"); return iscsit_add_reject_cmd(cmd,
ISCSI_REASON_BOOKMARK_INVALID, buf);
}
if (((hdr->flags & ISCSI_FLAG_CMD_READ) ||
(hdr->flags & ISCSI_FLAG_CMD_WRITE)) && !hdr->data_length) { /* * From RFC-3720 Section 10.3.1: * * "Either or both of R and W MAY be 1 when either the * Expected Data Transfer Length and/or Bidirectional Read * Expected Data Transfer Length are 0" * * For this case, go ahead and clear the unnecssary bits * to avoid any confusion with ->data_direction.
*/
hdr->flags &= ~ISCSI_FLAG_CMD_READ;
hdr->flags &= ~ISCSI_FLAG_CMD_WRITE;
pr_warn("ISCSI_FLAG_CMD_READ or ISCSI_FLAG_CMD_WRITE" " set when Expected Data Transfer Length is 0 for" " CDB: 0x%02x, Fixing up flags\n", hdr->cdb[0]);
}
if (!(hdr->flags & ISCSI_FLAG_CMD_READ) &&
!(hdr->flags & ISCSI_FLAG_CMD_WRITE) && (hdr->data_length != 0)) {
pr_err("ISCSI_FLAG_CMD_READ and/or ISCSI_FLAG_CMD_WRITE" " MUST be set if Expected Data Transfer Length is not 0." " Bad iSCSI Initiator\n"); return iscsit_add_reject_cmd(cmd,
ISCSI_REASON_BOOKMARK_INVALID, buf);
}
if ((hdr->flags & ISCSI_FLAG_CMD_READ) &&
(hdr->flags & ISCSI_FLAG_CMD_WRITE)) {
pr_err("Bidirectional operations not supported!\n"); return iscsit_add_reject_cmd(cmd,
ISCSI_REASON_BOOKMARK_INVALID, buf);
}
if (hdr->opcode & ISCSI_OP_IMMEDIATE) {
pr_err("Illegally set Immediate Bit in iSCSI Initiator" " Scsi Command PDU.\n"); return iscsit_add_reject_cmd(cmd,
ISCSI_REASON_BOOKMARK_INVALID, buf);
}
if (payload_length && !conn->sess->sess_ops->ImmediateData) {
pr_err("ImmediateData=No but DataSegmentLength=%u," " protocol error.\n", payload_length); return iscsit_add_reject_cmd(cmd,
ISCSI_REASON_PROTOCOL_ERROR, buf);
}
if ((be32_to_cpu(hdr->data_length) == payload_length) &&
(!(hdr->flags & ISCSI_FLAG_CMD_FINAL))) {
pr_err("Expected Data Transfer Length and Length of" " Immediate Data are the same, but ISCSI_FLAG_CMD_FINAL" " bit is not set protocol error\n"); return iscsit_add_reject_cmd(cmd,
ISCSI_REASON_PROTOCOL_ERROR, buf);
}
int iscsit_process_scsi_cmd(struct iscsit_conn *conn, struct iscsit_cmd *cmd, struct iscsi_scsi_req *hdr)
{ int cmdsn_ret = 0; /* * Check the CmdSN against ExpCmdSN/MaxCmdSN here if * the Immediate Bit is not set, and no Immediate * Data is attached. * * A PDU/CmdSN carrying Immediate Data can only * be processed after the DataCRC has passed. * If the DataCRC fails, the CmdSN MUST NOT * be acknowledged. (See below)
*/ if (!cmd->immediate_data) {
cmdsn_ret = iscsit_sequence_cmd(conn, cmd,
(unsignedchar *)hdr, hdr->cmdsn); if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) return -1; elseif (cmdsn_ret == CMDSN_LOWER_THAN_EXP) {
target_put_sess_cmd(&cmd->se_cmd); return 0;
}
}
/* * If no Immediate Data is attached, it's OK to return now.
*/ if (!cmd->immediate_data) { if (!cmd->sense_reason && cmd->unsolicited_data)
iscsit_set_unsolicited_dataout(cmd); if (!cmd->sense_reason) return 0;
target_put_sess_cmd(&cmd->se_cmd); return 0;
}
/* * Early CHECK_CONDITIONs with ImmediateData never make it to command * execution. These exceptions are processed in CmdSN order using * iscsit_check_received_cmdsn() in iscsit_get_immediate_data() below.
*/ if (cmd->sense_reason) return 1; /* * Call directly into transport_generic_new_cmd() to perform * the backend memory allocation.
*/
cmd->sense_reason = transport_generic_new_cmd(&cmd->se_cmd); if (cmd->sense_reason) return 1;
staticint
iscsit_get_immediate_data(struct iscsit_cmd *cmd, struct iscsi_scsi_req *hdr, bool dump_payload)
{ int cmdsn_ret = 0, immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; int rc;
/* * Special case for Unsupported SAM WRITE Opcodes and ImmediateData=Yes.
*/ if (dump_payload) {
u32 length = min(cmd->se_cmd.data_length - cmd->write_data_done,
cmd->first_burst_len);
if (immed_ret == IMMEDIATE_DATA_NORMAL_OPERATION) { /* * A PDU/CmdSN carrying Immediate Data passed * DataCRC, check against ExpCmdSN/MaxCmdSN if * Immediate Bit is not set.
*/
cmdsn_ret = iscsit_sequence_cmd(cmd->conn, cmd,
(unsignedchar *)hdr, hdr->cmdsn); if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) return -1;
if (cmd->sense_reason || cmdsn_ret == CMDSN_LOWER_THAN_EXP) {
target_put_sess_cmd(&cmd->se_cmd);
} elseif (immed_ret == IMMEDIATE_DATA_ERL1_CRC_FAILURE) { /* * Immediate Data failed DataCRC and ERL>=1, * silently drop this PDU and let the initiator * plug the CmdSN gap. * * FIXME: Send Unsolicited NOPIN with reserved * TTT here to help the initiator figure out * the missing CmdSN, although they should be * intelligent enough to determine the missing * CmdSN and issue a retry to plug the sequence.
*/
cmd->i_state = ISTATE_REMOVE;
iscsit_add_cmd_to_immediate_queue(cmd, cmd->conn, cmd->i_state);
} else/* immed_ret == IMMEDIATE_DATA_CANNOT_RECOVER */ return -1;
if (cmd->unsolicited_data) { int dump_unsolicited_data = 0;
if (conn->sess->sess_ops->InitialR2T) {
pr_err("Received unexpected unsolicited data" " while InitialR2T=Yes, protocol error.\n");
transport_send_check_condition_and_sense(&cmd->se_cmd,
TCM_UNEXPECTED_UNSOLICITED_DATA, 0); return -1;
} /* * Special case for dealing with Unsolicited DataOUT * and Unsupported SAM WRITE Opcodes and SE resource allocation * failures;
*/
/* Something's amiss if we're not in WRITE_PENDING state... */
WARN_ON(se_cmd->t_state != TRANSPORT_WRITE_PENDING); if (!(se_cmd->se_cmd_flags & SCF_SUPPORTED_SAM_OPCODE))
dump_unsolicited_data = 1;
if (dump_unsolicited_data) { /* * Check if a delayed TASK_ABORTED status needs to * be sent now if the ISCSI_FLAG_CMD_FINAL has been * received with the unsolicited data out.
*/ if (hdr->flags & ISCSI_FLAG_CMD_FINAL)
iscsit_stop_dataout_timer(cmd);
return iscsit_dump_data_payload(conn, payload_length, 1);
}
} else { /* * For the normal solicited data path: * * Check for a delayed TASK_ABORTED status and dump any * incoming data out payload if one exists. Also, when the * ISCSI_FLAG_CMD_FINAL is set to denote the end of the current * data out sequence, we decrement outstanding_r2ts. Once * outstanding_r2ts reaches zero, go ahead and send the delayed * TASK_ABORTED status.
*/ if (se_cmd->transport_state & CMD_T_ABORTED) { if (hdr->flags & ISCSI_FLAG_CMD_FINAL &&
--cmd->outstanding_r2ts < 1)
iscsit_stop_dataout_timer(cmd);
if (!(hdr->flags & ISCSI_FLAG_CMD_FINAL)) {
pr_err("NopOUT Flag's, Left Most Bit not set, protocol error.\n"); if (!cmd) return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR,
(unsignedchar *)hdr);
if (hdr->itt == RESERVED_ITT && !(hdr->opcode & ISCSI_OP_IMMEDIATE)) {
pr_err("NOPOUT ITT is reserved, but Immediate Bit is" " not set, protocol error.\n"); if (!cmd) return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR,
(unsignedchar *)hdr);
pr_debug("Got NOPOUT Ping %s ITT: 0x%08x, TTT: 0x%08x," " CmdSN: 0x%08x, ExpStatSN: 0x%08x, Length: %u\n",
hdr->itt == RESERVED_ITT ? "Response" : "Request",
hdr->itt, hdr->ttt, hdr->cmdsn, hdr->exp_statsn,
payload_length); /* * This is not a response to a Unsolicited NopIN, which means * it can either be a NOPOUT ping request (with a valid ITT), * or a NOPOUT not requesting a NOPIN (with a reserved ITT). * Either way, make sure we allocate an struct iscsit_cmd, as both * can contain ping data.
*/ if (hdr->ttt == cpu_to_be32(0xFFFFFFFF)) {
cmd->iscsi_opcode = ISCSI_OP_NOOP_OUT;
cmd->i_state = ISTATE_SEND_NOPIN;
cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ?
1 : 0);
conn->sess->init_task_tag = cmd->init_task_tag = hdr->itt;
cmd->targ_xfer_tag = 0xFFFFFFFF;
cmd->cmd_sn = be32_to_cpu(hdr->cmdsn);
cmd->exp_stat_sn = be32_to_cpu(hdr->exp_statsn);
cmd->data_direction = DMA_NONE;
}
return 0;
}
EXPORT_SYMBOL(iscsit_setup_nop_out);
int iscsit_process_nop_out(struct iscsit_conn *conn, struct iscsit_cmd *cmd, struct iscsi_nopout *hdr)
{ struct iscsit_cmd *cmd_p = NULL; int cmdsn_ret = 0; /* * Initiator is expecting a NopIN ping reply..
*/ if (hdr->itt != RESERVED_ITT) { if (!cmd) return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR,
(unsignedchar *)hdr);
cmdsn_ret = iscsit_sequence_cmd(conn, cmd,
(unsignedchar *)hdr, hdr->cmdsn); if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) return 0; if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) return -1;
return 0;
} /* * This was a response to a unsolicited NOPIN ping.
*/ if (hdr->ttt != cpu_to_be32(0xFFFFFFFF)) {
cmd_p = iscsit_find_cmd_from_ttt(conn, be32_to_cpu(hdr->ttt)); if (!cmd_p) return -EINVAL;
if ((function != ISCSI_TM_FUNC_ABORT_TASK) &&
((function != ISCSI_TM_FUNC_TASK_REASSIGN) &&
hdr->rtt != RESERVED_ITT)) {
pr_err("RefTaskTag should be set to 0xFFFFFFFF.\n");
hdr->rtt = RESERVED_ITT;
}
if ((function == ISCSI_TM_FUNC_TASK_REASSIGN) &&
!(hdr->opcode & ISCSI_OP_IMMEDIATE)) {
pr_err("Task Management Request TASK_REASSIGN not" " issued as immediate command, bad iSCSI Initiator" "implementation\n"); return iscsit_add_reject_cmd(cmd,
ISCSI_REASON_PROTOCOL_ERROR, buf);
} if ((function != ISCSI_TM_FUNC_ABORT_TASK) &&
be32_to_cpu(hdr->refcmdsn) != ISCSI_RESERVED_TAG)
hdr->refcmdsn = cpu_to_be32(ISCSI_RESERVED_TAG);
cmd->iscsi_opcode = ISCSI_OP_SCSI_TMFUNC;
cmd->i_state = ISTATE_SEND_TASKMGTRSP;
cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ? 1 : 0);
cmd->init_task_tag = hdr->itt;
cmd->targ_xfer_tag = 0xFFFFFFFF;
cmd->cmd_sn = be32_to_cpu(hdr->cmdsn);
cmd->exp_stat_sn = be32_to_cpu(hdr->exp_statsn);
se_tmr = cmd->se_cmd.se_tmr_req;
tmr_req = cmd->tmr_req; /* * Locate the struct se_lun for all TMRs not related to ERL=2 TASK_REASSIGN
*/ if (function != ISCSI_TM_FUNC_TASK_REASSIGN) {
ret = transport_lookup_tmr_lun(&cmd->se_cmd); if (ret < 0) {
se_tmr->response = ISCSI_TMF_RSP_NO_LUN; goto attach;
}
}
switch (function) { case ISCSI_TM_FUNC_ABORT_TASK:
se_tmr->response = iscsit_tmr_abort_task(cmd, buf); if (se_tmr->response) goto attach; break; case ISCSI_TM_FUNC_ABORT_TASK_SET: case ISCSI_TM_FUNC_CLEAR_ACA: case ISCSI_TM_FUNC_CLEAR_TASK_SET: case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: break; case ISCSI_TM_FUNC_TARGET_WARM_RESET: if (iscsit_tmr_task_warm_reset(conn, tmr_req, buf) < 0) {
se_tmr->response = ISCSI_TMF_RSP_AUTH_FAILED; goto attach;
} break; case ISCSI_TM_FUNC_TARGET_COLD_RESET: if (iscsit_tmr_task_cold_reset(conn, tmr_req, buf) < 0) {
se_tmr->response = ISCSI_TMF_RSP_AUTH_FAILED; goto attach;
} break; case ISCSI_TM_FUNC_TASK_REASSIGN:
se_tmr->response = iscsit_tmr_task_reassign(cmd, buf); /* * Perform sanity checks on the ExpDataSN only if the * TASK_REASSIGN was successful.
*/ if (se_tmr->response) break;
if (out_of_order_cmdsn || !(hdr->opcode & ISCSI_OP_IMMEDIATE)) return 0; /* * Found the referenced task, send to transport for processing.
*/ if (se_tmr->call_transport) return transport_generic_handle_tmr(&cmd->se_cmd);
/* * Could not find the referenced LUN, task, or Task Management * command not authorized or supported. Change state and * let the tx_thread send the response. * * For connection recovery, this is also the default action for * TMR TASK_REASSIGN.
*/
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
target_put_sess_cmd(&cmd->se_cmd); return 0;
}
EXPORT_SYMBOL(iscsit_handle_task_mgt_cmd);
/* #warning FIXME: Support Text Command parameters besides SendTargets */ int
iscsit_setup_text_cmd(struct iscsit_conn *conn, struct iscsit_cmd *cmd, struct iscsi_text *hdr)
{
u32 payload_length = ntoh24(hdr->dlength);
if (payload_length > conn->conn_ops->MaxXmitDataSegmentLength) {
pr_err("Unable to accept text parameter length: %u" "greater than MaxXmitDataSegmentLength %u.\n",
payload_length, conn->conn_ops->MaxXmitDataSegmentLength); return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR,
(unsignedchar *)hdr);
}
if (!(hdr->flags & ISCSI_FLAG_CMD_FINAL) ||
(hdr->flags & ISCSI_FLAG_TEXT_CONTINUE)) {
pr_err("Multi sequence text commands currently not supported\n"); return iscsit_reject_cmd(cmd, ISCSI_REASON_CMD_NOT_SUPPORTED,
(unsignedchar *)hdr);
}
pr_debug("Received logout request CLOSECONNECTION for CID:" " %hu on CID: %hu.\n", cmd->logout_cid, conn->cid);
/* * A Logout Request with a CLOSECONNECTION reason code for a CID * can arrive on a connection with a differing CID.
*/ if (conn->cid == cmd->logout_cid) {
spin_lock_bh(&conn->state_lock);
pr_debug("Moving to TARG_CONN_STATE_IN_LOGOUT.\n");
conn->conn_state = TARG_CONN_STATE_IN_LOGOUT;
spin_unlock_bh(&conn->state_lock);
} else { /* * Handle all different cid CLOSECONNECTION requests in * iscsit_logout_post_handler_diffcid() as to give enough * time for any non immediate command's CmdSN to be * acknowledged on the connection in question. * * Here we simply make sure the CID is still around.
*/
l_conn = iscsit_get_conn_from_cid(sess,
cmd->logout_cid); if (!l_conn) {
cmd->logout_response = ISCSI_LOGOUT_CID_NOT_FOUND;
iscsit_add_cmd_to_response_queue(cmd, conn,
cmd->i_state); return 0;
}
if (conn->conn_state != TARG_CONN_STATE_LOGGED_IN) {
pr_err("Received logout request on connection that" " is not in logged in state, ignoring request.\n");
iscsit_free_cmd(cmd, false); return 0;
}
/* * We need to sleep in these cases (by returning 1) until the Logout * Response gets sent in the tx thread.
*/ if ((reason_code == ISCSI_LOGOUT_REASON_CLOSE_SESSION) ||
((reason_code == ISCSI_LOGOUT_REASON_CLOSE_CONNECTION) &&
be16_to_cpu(hdr->cid) == conn->cid))
logout_remove = 1;
if (reason_code != ISCSI_LOGOUT_REASON_RECOVERY)
iscsit_ack_from_expstatsn(conn, be32_to_cpu(hdr->exp_statsn));
/* * Immediate commands are executed, well, immediately. * Non-Immediate Logout Commands are executed in CmdSN order.
*/ if (cmd->immediate_cmd) { int ret = iscsit_execute_cmd(cmd, 0);
/* #warning iscsi_build_conn_drop_async_message() only sends out on connections
with active network interface */ staticvoid iscsit_build_conn_drop_async_message(struct iscsit_conn *conn)
{ struct iscsit_cmd *cmd; struct iscsit_conn *conn_p; bool found = false;
lockdep_assert_held(&conn->sess->conn_lock);
/* * Only send a Asynchronous Message on connections whos network * interface is still functional.
*/
list_for_each_entry(conn_p, &conn->sess->sess_conn_list, conn_list) { if (conn_p->conn_state == TARG_CONN_STATE_LOGGED_IN) {
iscsit_inc_conn_usage_count(conn_p);
found = true; break;
}
}
if (!found) return;
cmd = iscsit_allocate_cmd(conn_p, TASK_RUNNING); if (!cmd) {
iscsit_dec_conn_usage_count(conn_p); return;
}
int
iscsit_build_logout_rsp(struct iscsit_cmd *cmd, struct iscsit_conn *conn, struct iscsi_logout_rsp *hdr)
{ struct iscsit_conn *logout_conn = NULL; struct iscsi_conn_recovery *cr = NULL; struct iscsit_session *sess = conn->sess; /* * The actual shutting down of Sessions and/or Connections * for CLOSESESSION and CLOSECONNECTION Logout Requests * is done in scsi_logout_post_handler().
*/ switch (cmd->logout_reason) { case ISCSI_LOGOUT_REASON_CLOSE_SESSION:
pr_debug("iSCSI session logout successful, setting" " logout response to ISCSI_LOGOUT_SUCCESS.\n");
cmd->logout_response = ISCSI_LOGOUT_SUCCESS; break; case ISCSI_LOGOUT_REASON_CLOSE_CONNECTION: if (cmd->logout_response == ISCSI_LOGOUT_CID_NOT_FOUND) break; /* * For CLOSECONNECTION logout requests carrying * a matching logout CID -> local CID, the reference * for the local CID will have been incremented in * iscsi_logout_closeconnection(). * * For CLOSECONNECTION logout requests carrying * a different CID than the connection it arrived * on, the connection responding to cmd->logout_cid * is stopped in iscsit_logout_post_handler_diffcid().
*/
pr_debug("iSCSI CID: %hu logout on CID: %hu" " successful.\n", cmd->logout_cid, conn->cid);
cmd->logout_response = ISCSI_LOGOUT_SUCCESS; break; case ISCSI_LOGOUT_REASON_RECOVERY: if ((cmd->logout_response == ISCSI_LOGOUT_RECOVERY_UNSUPPORTED) ||
(cmd->logout_response == ISCSI_LOGOUT_CLEANUP_FAILED)) break; /* * If the connection is still active from our point of view * force connection recovery to occur.
*/
logout_conn = iscsit_get_conn_from_cid_rcfr(sess,
cmd->logout_cid); if (logout_conn) {
iscsit_connection_reinstatement_rcfr(logout_conn);
iscsit_dec_conn_usage_count(logout_conn);
}
/* * NOPOUT Ping Data is attached to struct iscsit_cmd->buf_ptr. * NOPOUT DataSegmentLength is at struct iscsit_cmd->buf_ptr_size.
*/
pr_debug("Echoing back %u bytes of ping data.\n", cmd->buf_ptr_size);
iscsit_increment_maxcmdsn(cmd, conn->sess); /* * Reset maxcmdsn_inc in multi-part text payload exchanges to * correctly increment MaxCmdSN for each response answering a * non immediate text request with a valid CmdSN.
*/
cmd->maxcmdsn_inc = 0;
hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn);
hdr->max_cmdsn = cpu_to_be32((u32) atomic_read(&conn->sess->max_cmd_sn));
void iscsit_thread_get_cpumask(struct iscsit_conn *conn)
{ int ord, cpu;
cpumask_var_t conn_allowed_cpumask;
/* * bitmap_id is assigned from iscsit_global->ts_bitmap from * within iscsit_start_kthreads() * * Here we use bitmap_id to determine which CPU that this * iSCSI connection's RX/TX threads will be scheduled to * execute upon.
*/ if (!zalloc_cpumask_var(&conn_allowed_cpumask, GFP_KERNEL)) {
ord = conn->bitmap_id % cpumask_weight(cpu_online_mask);
for_each_online_cpu(cpu) { if (ord-- == 0) {
cpumask_set_cpu(cpu, conn->conn_cpumask); return;
}
}
} else {
cpumask_and(conn_allowed_cpumask, iscsit_global->allowed_cpumask,
cpu_online_mask);
cpumask_clear(conn->conn_cpumask);
ord = conn->bitmap_id % cpumask_weight(conn_allowed_cpumask);
for_each_cpu(cpu, conn_allowed_cpumask) { if (ord-- == 0) {
cpumask_set_cpu(cpu, conn->conn_cpumask);
free_cpumask_var(conn_allowed_cpumask); return;
}
}
free_cpumask_var(conn_allowed_cpumask);
} /* * This should never be reached..
*/
dump_stack();
cpumask_setall(conn->conn_cpumask);
}
void iscsit_thread_check_cpumask( struct iscsit_conn *conn, struct task_struct *p, int mode)
{ /* * The TX and RX threads maybe call iscsit_thread_check_cpumask() * at the same time. The RX thread might be faster and return from * iscsit_thread_reschedule() with conn_rx_reset_cpumask set to 0. * Then the TX thread sets it back to 1. * The next time the RX thread loops, it sees conn_rx_reset_cpumask * set to 1 and calls set_cpus_allowed_ptr() again and set it to 0.
*/
iscsit_thread_reschedule(conn);
/* * Update the CPU mask for this single kthread so that * both TX and RX kthreads are scheduled to run on the * same CPU.
*/
set_cpus_allowed_ptr(p, conn->conn_cpumask); if (mode == 1)
conn->conn_tx_reset_cpumask = 0; else
conn->conn_rx_reset_cpumask = 0;
}
EXPORT_SYMBOL(iscsit_thread_check_cpumask);
int
iscsit_immediate_queue(struct iscsit_conn *conn, struct iscsit_cmd *cmd, int state)
{ int ret;
switch (state) { case ISTATE_SEND_R2T:
ret = iscsit_send_r2t(cmd, conn); if (ret < 0) goto err; break; case ISTATE_REMOVE:
spin_lock_bh(&conn->cmd_lock);
list_del_init(&cmd->i_conn_node);
spin_unlock_bh(&conn->cmd_lock);
iscsit_free_cmd(cmd, false); break; case ISTATE_SEND_NOPIN_WANT_RESPONSE:
iscsit_mod_nopin_response_timer(conn);
ret = iscsit_send_unsolicited_nopin(cmd, conn, 1); if (ret < 0) goto err; break; case ISTATE_SEND_NOPIN_NO_RESPONSE:
ret = iscsit_send_unsolicited_nopin(cmd, conn, 0); if (ret < 0) goto err; break; default:
pr_err("Unknown Opcode: 0x%02x ITT:" " 0x%08x, i_state: %d on CID: %hu\n",
cmd->iscsi_opcode, cmd->init_task_tag, state,
conn->cid); goto err;
}
while ((qr = iscsit_get_cmd_from_immediate_queue(conn))) {
atomic_set(&conn->check_immediate_queue, 0);
cmd = qr->cmd;
state = qr->state;
kmem_cache_free(lio_qr_cache, qr);
ret = t->iscsit_immediate_queue(conn, cmd, state); if (ret < 0) return ret;
}
return 0;
}
int
iscsit_response_queue(struct iscsit_conn *conn, struct iscsit_cmd *cmd, int state)
{ int ret;
check_rsp_state: switch (state) { case ISTATE_SEND_DATAIN:
ret = iscsit_send_datain(cmd, conn); if (ret < 0) goto err; elseif (!ret) /* more drs */ goto check_rsp_state; elseif (ret == 1) { /* all done */
spin_lock_bh(&cmd->istate_lock);
cmd->i_state = ISTATE_SENT_STATUS;
spin_unlock_bh(&cmd->istate_lock);
if (atomic_read(&conn->check_immediate_queue)) return 1;
return 0;
} elseif (ret == 2) { /* Still must send status,
SCF_TRANSPORT_TASK_SENSE was set */
spin_lock_bh(&cmd->istate_lock);
cmd->i_state = ISTATE_SEND_STATUS;
spin_unlock_bh(&cmd->istate_lock);
state = ISTATE_SEND_STATUS; goto check_rsp_state;
}
break; case ISTATE_SEND_STATUS: case ISTATE_SEND_STATUS_RECOVERY:
ret = iscsit_send_response(cmd, conn); break; case ISTATE_SEND_LOGOUTRSP:
ret = iscsit_send_logout(cmd, conn); break; case ISTATE_SEND_ASYNCMSG:
ret = iscsit_send_conn_drop_async_message(
cmd, conn); break; case ISTATE_SEND_NOPIN:
ret = iscsit_send_nopin(cmd, conn); break; case ISTATE_SEND_REJECT:
ret = iscsit_send_reject(cmd, conn); break; case ISTATE_SEND_TASKMGTRSP:
ret = iscsit_send_task_mgt_rsp(cmd, conn); if (ret != 0) break;
ret = iscsit_tmr_post_handler(cmd, conn); if (ret != 0)
iscsit_fall_back_to_erl0(conn->sess); break; case ISTATE_SEND_TEXTRSP:
ret = iscsit_send_text_rsp(cmd, conn); break; default:
pr_err("Unknown Opcode: 0x%02x ITT:" " 0x%08x, i_state: %d on CID: %hu\n",
cmd->iscsi_opcode, cmd->init_task_tag,
state, conn->cid); goto err;
} if (ret < 0) goto err;
switch (state) { case ISTATE_SEND_LOGOUTRSP: if (!iscsit_logout_post_handler(cmd, conn)) return -ECONNRESET;
fallthrough; case ISTATE_SEND_STATUS: case ISTATE_SEND_ASYNCMSG: case ISTATE_SEND_NOPIN: case ISTATE_SEND_STATUS_RECOVERY: case ISTATE_SEND_TEXTRSP: case ISTATE_SEND_TASKMGTRSP: case ISTATE_SEND_REJECT:
spin_lock_bh(&cmd->istate_lock);
cmd->i_state = ISTATE_SENT_STATUS;
spin_unlock_bh(&cmd->istate_lock); break; default:
pr_err("Unknown Opcode: 0x%02x ITT:" " 0x%08x, i_state: %d on CID: %hu\n",
cmd->iscsi_opcode, cmd->init_task_tag,
cmd->i_state, conn->cid); goto err;
}
if (atomic_read(&conn->check_immediate_queue)) return 1;
while ((qr = iscsit_get_cmd_from_response_queue(conn))) {
cmd = qr->cmd;
state = qr->state;
kmem_cache_free(lio_qr_cache, qr);
ret = t->iscsit_response_queue(conn, cmd, state); if (ret == 1 || ret < 0) return ret;
}
return 0;
}
int iscsi_target_tx_thread(void *arg)
{ int ret = 0; struct iscsit_conn *conn = arg; bool conn_freed = false;
/* * Allow ourselves to be interrupted by SIGINT so that a * connection recovery / failure event can be triggered externally.
*/
allow_signal(SIGINT);
while (!kthread_should_stop()) { /* * Ensure that both TX and RX per connection kthreads * are scheduled to run on the same CPU.
*/
iscsit_thread_check_cpumask(conn, current, 1);
transport_err: /* * Avoid the normal connection failure code-path if this connection * is still within LOGIN mode, and iscsi_np process context is * responsible for cleaning up the early connection failure.
*/ if (conn->conn_state != TARG_CONN_STATE_IN_LOGIN)
iscsit_take_action_for_connection_exit(conn, &conn_freed);
out: if (!conn_freed) { while (!kthread_should_stop()) {
msleep(100);
}
} return 0;
}
buffer = kcalloc(ISCSI_HDR_LEN, sizeof(*buffer), GFP_KERNEL); if (!buffer) return;
while (!kthread_should_stop()) { /* * Ensure that both TX and RX per connection kthreads * are scheduled to run on the same CPU.
*/
iscsit_thread_check_cpumask(conn, current, 0);
ret = rx_data(conn, &iov, 1, iov.iov_len); if (ret != iov.iov_len) {
iscsit_rx_thread_wait_for_tcp(conn); break;
}
}
if (conn->conn_ops->HeaderDigest) {
iov.iov_base = &digest;
iov.iov_len = ISCSI_CRC_LEN;
ret = rx_data(conn, &iov, 1, ISCSI_CRC_LEN); if (ret != ISCSI_CRC_LEN) {
iscsit_rx_thread_wait_for_tcp(conn); break;
}
checksum = iscsit_crc_buf(buffer, ISCSI_HDR_LEN, 0,
NULL); if (digest != checksum) {
pr_err("HeaderDigest CRC32C failed," " received 0x%08x, computed 0x%08x\n",
digest, checksum); /* * Set the PDU to 0xff so it will intentionally * hit default in the switch below.
*/
memset(buffer, 0xff, ISCSI_HDR_LEN);
atomic_long_inc(&conn->sess->conn_digest_errors);
} else {
pr_debug("Got HeaderDigest CRC32C" " 0x%08x\n", checksum);
}
}
if (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT) break;
opcode = buffer[0] & ISCSI_OPCODE_MASK;
if (conn->sess->sess_ops->SessionType &&
((!(opcode & ISCSI_OP_TEXT)) ||
(!(opcode & ISCSI_OP_LOGOUT)))) {
pr_err("Received illegal iSCSI Opcode: 0x%02x" " while in Discovery Session, rejecting.\n", opcode);
iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR,
buffer); break;
}
ret = iscsi_target_rx_opcode(conn, buffer); if (ret < 0) break;
}
kfree(buffer);
}
int iscsi_target_rx_thread(void *arg)
{ int rc; struct iscsit_conn *conn = arg; bool conn_freed = false;
/* * Allow ourselves to be interrupted by SIGINT so that a * connection recovery / failure event can be triggered externally.
*/
allow_signal(SIGINT); /* * Wait for iscsi_post_login_handler() to complete before allowing * incoming iscsi/tcp socket I/O, and/or failing the connection.
*/
rc = wait_for_completion_interruptible(&conn->rx_login_comp); if (rc < 0 || iscsi_target_check_conn_state(conn)) goto out;
if (!conn->conn_transport->iscsit_get_rx_pdu) return 0;
conn->conn_transport->iscsit_get_rx_pdu(conn);
if (!signal_pending(current))
atomic_set(&conn->transport_failed, 1);
iscsit_take_action_for_connection_exit(conn, &conn_freed);
out: if (!conn_freed) { while (!kthread_should_stop()) {
msleep(100);
}
}
return 0;
}
staticvoid iscsit_release_commands_from_conn(struct iscsit_conn *conn)
{
LIST_HEAD(tmp_list); struct iscsit_cmd *cmd = NULL, *cmd_tmp = NULL; struct iscsit_session *sess = conn->sess; /* * We expect this function to only ever be called from either RX or TX * thread context via iscsit_close_connection() once the other context * has been reset -> returned sleeping pre-handler state.
*/
spin_lock_bh(&conn->cmd_lock);
list_splice_init(&conn->conn_cmd_list, &tmp_list);
spin_lock_irq(&se_cmd->t_state_lock); if (se_cmd->transport_state & CMD_T_ABORTED) { if (!(se_cmd->transport_state & CMD_T_TAS)) /* * LIO's abort path owns the cleanup for this, * so put it back on the list and let * aborted_task handle it.
*/
list_move_tail(&cmd->i_conn_node,
&conn->conn_cmd_list);
} else {
se_cmd->transport_state |= CMD_T_FABRIC_STOP;
}
if (cmd->se_cmd.t_state == TRANSPORT_WRITE_PENDING) { /* * We never submitted the cmd to LIO core, so we have * to tell LIO to perform the completion process.
*/
spin_unlock_irq(&se_cmd->t_state_lock);
target_complete_cmd(&cmd->se_cmd, SAM_STAT_TASK_ABORTED); continue;
}
spin_unlock_irq(&se_cmd->t_state_lock);
}
spin_unlock_bh(&conn->cmd_lock);
/* * Wait on commands that were cleaned up via the aborted_task path. * LLDs that implement iscsit_wait_conn will already have waited for * commands.
*/ if (!conn->conn_transport->iscsit_wait_conn) {
target_stop_cmd_counter(conn->cmd_cnt);
target_wait_for_cmds(conn->cmd_cnt);
}
}
int iscsit_close_connection( struct iscsit_conn *conn)
{ int conn_logout = (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT); struct iscsit_session *sess = conn->sess;
pr_debug("Closing iSCSI connection CID %hu on SID:" " %u\n", conn->cid, sess->sid); /* * Always up conn_logout_comp for the traditional TCP and HW_OFFLOAD * case just in case the RX Thread in iscsi_target_rx_opcode() is * sleeping and the logout response never got sent because the * connection failed. * * However for iser-target, isert_wait4logout() is using conn_logout_comp * to signal logout response TX interrupt completion. Go ahead and skip * this for iser since isert_rx_opcode() does not wait on logout failure, * and to avoid iscsit_conn pointer dereference in iser-target code.
*/ if (!conn->conn_transport->rdma_shutdown)
complete(&conn->conn_logout_comp);
if (conn->conn_transport->iscsit_wait_conn)
conn->conn_transport->iscsit_wait_conn(conn);
/* * During Connection recovery drop unacknowledged out of order * commands for this connection, and prepare the other commands * for reallegiance. * * During normal operation clear the out of order commands (but * do not free the struct iscsi_ooo_cmdsn's) and release all * struct iscsit_cmds.
*/ if (atomic_read(&conn->connection_recovery)) {
iscsit_discard_unacknowledged_ooo_cmdsns_for_conn(conn);
iscsit_prepare_cmds_for_reallegiance(conn);
} else {
iscsit_clear_ooo_cmdsns_for_conn(conn);
iscsit_release_commands_from_conn(conn);
}
iscsit_free_queue_reqs_for_conn(conn);
/* * Handle decrementing session or connection usage count if * a logout response was not able to be sent because the * connection failed. Fall back to Session Recovery here.
*/ if (atomic_read(&conn->conn_logout_remove)) { if (conn->conn_logout_reason == ISCSI_LOGOUT_REASON_CLOSE_SESSION) {
iscsit_dec_conn_usage_count(conn);
iscsit_dec_session_usage_count(sess);
} if (conn->conn_logout_reason == ISCSI_LOGOUT_REASON_CLOSE_CONNECTION)
iscsit_dec_conn_usage_count(conn);
/* * Attempt to let the Initiator know this connection failed by * sending an Connection Dropped Async Message on another * active connection.
*/ if (atomic_read(&conn->connection_recovery))
iscsit_build_conn_drop_async_message(conn);
spin_unlock_bh(&sess->conn_lock);
/* * If connection reinstatement is being performed on this connection, * up the connection reinstatement semaphore that is being blocked on * in iscsit_cause_connection_reinstatement().
*/
spin_lock_bh(&conn->state_lock); if (atomic_read(&conn->sleep_on_conn_wait_comp)) {
spin_unlock_bh(&conn->state_lock);
complete(&conn->conn_wait_comp);
wait_for_completion(&conn->conn_post_wait_comp);
spin_lock_bh(&conn->state_lock);
}
/* * If connection reinstatement is being performed on this connection * by receiving a REMOVECONNFORRECOVERY logout request, up the * connection wait rcfr semaphore that is being blocked on * an iscsit_connection_reinstatement_rcfr().
*/ if (atomic_read(&conn->connection_wait_rcfr)) {
spin_unlock_bh(&conn->state_lock);
complete(&conn->conn_wait_rcfr_comp);
wait_for_completion(&conn->conn_post_wait_comp);
spin_lock_bh(&conn->state_lock);
}
atomic_set(&conn->connection_reinstatement, 1);
spin_unlock_bh(&conn->state_lock);
/* * If any other processes are accessing this connection pointer we * must wait until they have completed.
*/
iscsit_check_conn_usage_count(conn);
if (conn->sock)
sock_release(conn->sock);
if (conn->conn_transport->iscsit_free_conn)
conn->conn_transport->iscsit_free_conn(conn);
pr_debug("Moving to TARG_CONN_STATE_FREE.\n");
conn->conn_state = TARG_CONN_STATE_FREE;
iscsit_free_conn(conn);
spin_lock_bh(&sess->conn_lock);
atomic_dec(&sess->nconn);
pr_debug("Decremented iSCSI connection count to %d from node:" " %s\n", atomic_read(&sess->nconn),
sess->sess_ops->InitiatorName); /* * Make sure that if one connection fails in an non ERL=2 iSCSI * Session that they all fail.
*/ if ((sess->sess_ops->ErrorRecoveryLevel != 2) && !conn_logout &&
!atomic_read(&sess->session_logout))
atomic_set(&sess->session_fall_back_to_erl0, 1);
/* * If this was not the last connection in the session, and we are * performing session reinstatement or falling back to ERL=0, call * iscsit_stop_session() without sleeping to shutdown the other * active connections.
*/ if (atomic_read(&sess->nconn)) { if (!atomic_read(&sess->session_reinstatement) &&
!atomic_read(&sess->session_fall_back_to_erl0)) {
spin_unlock_bh(&sess->conn_lock); return 0;
} if (!atomic_read(&sess->session_stop_active)) {
atomic_set(&sess->session_stop_active, 1);
spin_unlock_bh(&sess->conn_lock);
iscsit_stop_session(sess, 0, 0); return 0;
}
spin_unlock_bh(&sess->conn_lock); return 0;
}
/* * If this was the last connection in the session and one of the * following is occurring: * * Session Reinstatement is not being performed, and are falling back * to ERL=0 call iscsit_close_session(). * * Session Logout was requested. iscsit_close_session() will be called * elsewhere. * * Session Continuation is not being performed, start the Time2Retain * handler and check if sleep_on_sess_wait_sem is active.
*/ if (!atomic_read(&sess->session_reinstatement) &&
atomic_read(&sess->session_fall_back_to_erl0)) {
spin_unlock_bh(&sess->conn_lock);
complete_all(&sess->session_wait_comp);
iscsit_close_session(sess, true);
/* * If the iSCSI Session for the iSCSI Initiator Node exists, * forcefully shutdown the iSCSI NEXUS.
*/ int iscsit_close_session(struct iscsit_session *sess, bool can_sleep)
{ struct iscsi_portal_group *tpg = sess->tpg; struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
if (atomic_read(&sess->nconn)) {
pr_err("%d connection(s) still exist for iSCSI session" " to %s\n", atomic_read(&sess->nconn),
sess->sess_ops->InitiatorName);
BUG();
}
if (sess->sess_ops->ErrorRecoveryLevel == 2)
iscsit_free_connection_recovery_entries(sess);
/* * transport_deregister_session_configfs() will clear the * struct se_node_acl->nacl_sess pointer now as a iscsi_np process context * can be setting it again with __transport_register_session() in * iscsi_post_login_handler() again after the iscsit_stop_session() * completes in iscsi_np context.
*/
transport_deregister_session_configfs(sess->se_sess);
/* * If any other processes are accessing this session pointer we must * wait until they have completed. If we are in an interrupt (the * time2retain handler) and contain and active session usage count we * restart the timer and exit.
*/ if (iscsit_check_session_usage_count(sess, can_sleep)) {
atomic_set(&sess->session_logout, 0);
iscsit_start_time2retain_handler(sess); return 0;
}
transport_deregister_session(sess->se_sess);
iscsit_free_all_ooo_cmdsns(sess);
spin_lock_bh(&se_tpg->session_lock);
pr_debug("Moving to TARG_SESS_STATE_FREE.\n");
sess->session_state = TARG_SESS_STATE_FREE;
pr_debug("Released iSCSI session from node: %s\n",
sess->sess_ops->InitiatorName);
tpg->nsessions--; if (tpg->tpg_tiqn)
tpg->tpg_tiqn->tiqn_nsessions--;
pr_debug("Decremented number of active iSCSI Sessions on" " iSCSI TPG: %hu to %u\n", tpg->tpgt, tpg->nsessions);
staticvoid iscsit_logout_post_handler_closesession( struct iscsit_conn *conn)
{ struct iscsit_session *sess = conn->sess; int sleep = 1; /* * Traditional iscsi/tcp will invoke this logic from TX thread * context during session logout, so clear tx_thread_active and * sleep if iscsit_close_connection() has not already occured. * * Since iser-target invokes this logic from it's own workqueue, * always sleep waiting for RX/TX thread shutdown to complete * within iscsit_close_connection().
*/ if (!conn->conn_transport->rdma_shutdown) {
sleep = cmpxchg(&conn->tx_thread_active, true, false); if (!sleep) return;
}
/* * Return of 0 causes the TX thread to restart.
*/ int iscsit_logout_post_handler( struct iscsit_cmd *cmd, struct iscsit_conn *conn)
{ int ret = 0;
switch (cmd->logout_reason) { case ISCSI_LOGOUT_REASON_CLOSE_SESSION: switch (cmd->logout_response) { case ISCSI_LOGOUT_SUCCESS: case ISCSI_LOGOUT_CLEANUP_FAILED: default:
iscsit_logout_post_handler_closesession(conn); break;
} break; case ISCSI_LOGOUT_REASON_CLOSE_CONNECTION: if (conn->cid == cmd->logout_cid) { switch (cmd->logout_response) { case ISCSI_LOGOUT_SUCCESS: case ISCSI_LOGOUT_CLEANUP_FAILED: default:
iscsit_logout_post_handler_samecid(conn); break;
}
} else { switch (cmd->logout_response) { case ISCSI_LOGOUT_SUCCESS:
iscsit_logout_post_handler_diffcid(conn,
cmd->logout_cid); break; case ISCSI_LOGOUT_CID_NOT_FOUND: case ISCSI_LOGOUT_CLEANUP_FAILED: default: break;
}
ret = 1;
} break; case ISCSI_LOGOUT_REASON_RECOVERY: switch (cmd->logout_response) { case ISCSI_LOGOUT_SUCCESS: case ISCSI_LOGOUT_CID_NOT_FOUND: case ISCSI_LOGOUT_RECOVERY_UNSUPPORTED: case ISCSI_LOGOUT_CLEANUP_FAILED: default: break;
}
ret = 1; break; default: break;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.