Patchwork [08/14] ./block/iscsi/sscsi-command.c

login
register
mail settings
Submitter ronniesahlberg@gmail.com
Date Dec. 3, 2010, 11:09 a.m.
Message ID <1291374593-17448-9-git-send-email-ronniesahlberg@gmail.com>
Download mbox | patch
Permalink /patch/74123/
State New
Headers show

Comments

ronniesahlberg@gmail.com - Dec. 3, 2010, 11:09 a.m.
From: Ronnie Sahlberg <ronniesahlberg@gmail.com>

iscsi  client library  : scsi-command.c
This file contains functions for providing a fully async and fully
nonblocking interface to send scsi commands to a target and
handle replies coming back.

This layer is fully async and nonblocking.
Client applications can send and keep arbitrary number of commands in flight
and will be notified of the scsi command completions through a callback
mechanism.

...

./block/iscsi/ contains a copy of a general purpose iscsi client
library which is aimed at providing a clientside api for iscsi
for both qemu/kvm as well as otther scsi related utilities.

As such, there is need to make merging across various consumers,
qemu/kvm being one of many here, as easy as possible when features
are added to the library.
As such, no consumer/qemu specific code is used in this library as well
as coding guidelined might not be adhered to 100%

It is the intention that this library will be useful for many
and that iscsi use spawned from this will flourish.

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
---
 block/iscsi/scsi-command.c |  528 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 528 insertions(+), 0 deletions(-)
 create mode 100644 block/iscsi/scsi-command.c

Patch

diff --git a/block/iscsi/scsi-command.c b/block/iscsi/scsi-command.c
new file mode 100644
index 0000000..f699c3f
--- /dev/null
+++ b/block/iscsi/scsi-command.c
@@ -0,0 +1,528 @@ 
+/*
+   Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include "iscsi.h"
+#include "iscsi-private.h"
+#include "scsi-lowlevel.h"
+
+struct iscsi_scsi_cbdata {
+	struct iscsi_scsi_cbdata *prev, *next;
+	iscsi_command_cb          callback;
+	void                     *private_data;
+	struct scsi_task         *task;
+};
+
+void
+iscsi_free_scsi_cbdata(struct iscsi_scsi_cbdata *scsi_cbdata)
+{
+	if (scsi_cbdata == NULL) {
+		return;
+	}
+	if (scsi_cbdata->task != NULL) {
+		scsi_free_scsi_task(scsi_cbdata->task);
+		scsi_cbdata->task = NULL;
+	}
+	free(scsi_cbdata);
+}
+
+void
+iscsi_cbdata_steal_scsi_task(struct scsi_task *task)
+{
+	struct iscsi_scsi_cbdata *scsi_cbdata =
+	  scsi_get_task_private_ptr(task);
+
+	if (scsi_cbdata != NULL) {
+		scsi_cbdata->task = NULL;
+	}
+}
+
+static void
+iscsi_scsi_response_cb(struct iscsi_context *iscsi, int status,
+		       void *command_data, void *private_data)
+{
+	struct iscsi_scsi_cbdata *scsi_cbdata =
+	  (struct iscsi_scsi_cbdata *)private_data;
+	struct scsi_task *task = command_data;
+
+	switch (status) {
+	case SCSI_STATUS_GOOD:
+		scsi_cbdata->callback(iscsi, SCSI_STATUS_GOOD, task,
+				      scsi_cbdata->private_data);
+		return;
+	case SCSI_STATUS_CHECK_CONDITION:
+		scsi_cbdata->callback(iscsi, SCSI_STATUS_CHECK_CONDITION, task,
+				      scsi_cbdata->private_data);
+		return;
+	default:
+		iscsi_set_error(iscsi, "Cant handle  scsi status %d yet.",
+				status);
+		scsi_cbdata->callback(iscsi, SCSI_STATUS_ERROR, task,
+				      scsi_cbdata->private_data);
+	}
+}
+
+
+int
+iscsi_scsi_command_async(struct iscsi_context *iscsi, int lun,
+			    struct scsi_task *task, iscsi_command_cb cb,
+			    struct iscsi_data *data, void *private_data)
+{
+	struct iscsi_pdu *pdu;
+	struct iscsi_scsi_cbdata *scsi_cbdata;
+	int flags;
+
+	if (iscsi->session_type != ISCSI_SESSION_NORMAL) {
+		iscsi_set_error(iscsi, "Trying to send command on "
+				"discovery session.");
+		scsi_free_scsi_task(task);
+		return -1;
+	}
+
+	if (iscsi->is_loggedin == 0) {
+		iscsi_set_error(iscsi, "Trying to send command while "
+				"not logged in.");
+		scsi_free_scsi_task(task);
+		return -2;
+	}
+
+	scsi_cbdata = malloc(sizeof(struct iscsi_scsi_cbdata));
+	if (scsi_cbdata == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: failed to allocate "
+				"scsi cbdata.");
+		scsi_free_scsi_task(task);
+		return -3;
+	}
+	bzero(scsi_cbdata, sizeof(struct iscsi_scsi_cbdata));
+	scsi_cbdata->task         = task;
+	scsi_cbdata->callback     = cb;
+	scsi_cbdata->private_data = private_data;
+
+	scsi_set_task_private_ptr(task, scsi_cbdata);
+
+	pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_SCSI_REQUEST,
+				 ISCSI_PDU_SCSI_RESPONSE);
+	if (pdu == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory, Failed to allocate "
+				"scsi pdu.");
+		iscsi_free_scsi_cbdata(scsi_cbdata);
+		return -4;
+	}
+	pdu->scsi_cbdata = scsi_cbdata;
+
+	/* flags */
+	flags = ISCSI_PDU_SCSI_FINAL|ISCSI_PDU_SCSI_ATTR_SIMPLE;
+	switch (task->xfer_dir) {
+	case SCSI_XFER_NONE:
+		break;
+	case SCSI_XFER_READ:
+		flags |= ISCSI_PDU_SCSI_READ;
+		break;
+	case SCSI_XFER_WRITE:
+		flags |= ISCSI_PDU_SCSI_WRITE;
+		if (data == NULL) {
+			iscsi_set_error(iscsi, "DATA-OUT command but data "
+					"== NULL.");
+			iscsi_free_pdu(iscsi, pdu);
+			return -5;
+		}
+		if (data->size != task->expxferlen) {
+			iscsi_set_error(iscsi, "Data size:%d is not same as "
+					"expected data transfer "
+					"length:%d.", data->size,
+					task->expxferlen);
+			iscsi_free_pdu(iscsi, pdu);
+			return -6;
+		}
+		if (iscsi_pdu_add_data(iscsi, pdu, data->data, data->size)
+		    != 0) {
+			iscsi_set_error(iscsi, "Out-of-memory: Failed to "
+					"add outdata to the pdu.");
+			iscsi_free_pdu(iscsi, pdu);
+			return -7;
+		}
+
+		break;
+	}
+	iscsi_pdu_set_pduflags(pdu, flags);
+
+	/* lun */
+	iscsi_pdu_set_lun(pdu, lun);
+
+	/* expxferlen */
+	iscsi_pdu_set_expxferlen(pdu, task->expxferlen);
+
+	/* cmdsn */
+	iscsi_pdu_set_cmdsn(pdu, iscsi->cmdsn);
+	pdu->cmdsn = iscsi->cmdsn;
+	iscsi->cmdsn++;
+
+	/* exp statsn */
+	iscsi_pdu_set_expstatsn(pdu, iscsi->statsn+1);
+
+	/* cdb */
+	iscsi_pdu_set_cdb(pdu, task);
+
+	pdu->callback     = iscsi_scsi_response_cb;
+	pdu->private_data = scsi_cbdata;
+
+	if (iscsi_queue_pdu(iscsi, pdu) != 0) {
+		iscsi_set_error(iscsi, "Out-of-memory: failed to queue iscsi "
+				"scsi pdu.");
+		iscsi_free_pdu(iscsi, pdu);
+		return -8;
+	}
+
+	return 0;
+}
+
+
+int
+iscsi_process_scsi_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu,
+			 const unsigned char *hdr, int size)
+{
+	int statsn, flags, response, status;
+	struct iscsi_scsi_cbdata *scsi_cbdata = pdu->scsi_cbdata;
+	struct scsi_task *task = scsi_cbdata->task;
+
+	statsn = ntohl(*(uint32_t *)&hdr[24]);
+	if (statsn > (int)iscsi->statsn) {
+		iscsi->statsn = statsn;
+	}
+
+	flags = hdr[1];
+	if ((flags&ISCSI_PDU_DATA_FINAL) == 0) {
+		iscsi_set_error(iscsi, "scsi response pdu but Final bit is "
+				"not set: 0x%02x.", flags);
+		pdu->callback(iscsi, SCSI_STATUS_ERROR, task,
+			      pdu->private_data);
+		return -1;
+	}
+	if ((flags&ISCSI_PDU_DATA_ACK_REQUESTED) != 0) {
+		iscsi_set_error(iscsi, "scsi response asked for ACK "
+				"0x%02x.", flags);
+		pdu->callback(iscsi, SCSI_STATUS_ERROR, task,
+			      pdu->private_data);
+		return -1;
+	}
+
+	response = hdr[2];
+
+	status = hdr[3];
+
+	switch (status) {
+	case SCSI_STATUS_GOOD:
+		task->datain.data = pdu->indata.data;
+		task->datain.size = pdu->indata.size;
+
+		pdu->indata.data = NULL;
+		pdu->indata.size = 0;
+
+		pdu->callback(iscsi, SCSI_STATUS_GOOD, task,
+			      pdu->private_data);
+		break;
+	case SCSI_STATUS_CHECK_CONDITION:
+		task->datain.size = size - ISCSI_HEADER_SIZE;
+		task->datain.data = malloc(task->datain.size);
+		if (task->datain.data == NULL) {
+			iscsi_set_error(iscsi, "failed to allocate blob for "
+					"sense data");
+		}
+		memcpy(task->datain.data, hdr + ISCSI_HEADER_SIZE,
+		       task->datain.size);
+
+		task->sense.error_type = task->datain.data[2] & 0x7f;
+		task->sense.key        = task->datain.data[4] & 0x0f;
+		task->sense.ascq       = ntohs(*(uint16_t *)
+					       &(task->datain.data[14]));
+
+		iscsi_set_error(iscsi, "SENSE KEY:%s(%d) ASCQ:%s(0x%04x)",
+				scsi_sense_key_str(task->sense.key),
+				task->sense.key,
+				scsi_sense_ascq_str(task->sense.ascq),
+				task->sense.ascq);
+		pdu->callback(iscsi, SCSI_STATUS_CHECK_CONDITION, task,
+			      pdu->private_data);
+		break;
+	default:
+		iscsi_set_error(iscsi, "Unknown SCSI status :%d.", status);
+
+		pdu->callback(iscsi, SCSI_STATUS_ERROR, task,
+			      pdu->private_data);
+		return -1;
+	}
+
+	return 0;
+}
+
+int
+iscsi_process_scsi_data_in(struct iscsi_context *iscsi, struct iscsi_pdu *pdu,
+			   const unsigned char *hdr, int size, int *is_finished)
+{
+	int statsn, flags, status;
+	struct iscsi_scsi_cbdata *scsi_cbdata = pdu->scsi_cbdata;
+	struct scsi_task *task = scsi_cbdata->task;
+	int dsl;
+
+	statsn = ntohl(*(uint32_t *)&hdr[24]);
+	if (statsn > (int)iscsi->statsn) {
+		iscsi->statsn = statsn;
+	}
+
+	flags = hdr[1];
+	if ((flags&ISCSI_PDU_DATA_ACK_REQUESTED) != 0) {
+		iscsi_set_error(iscsi, "scsi response asked for ACK "
+				"0x%02x.", flags);
+		pdu->callback(iscsi, SCSI_STATUS_ERROR, task,
+			      pdu->private_data);
+		return -1;
+	}
+	dsl = ntohl(*(uint32_t *)&hdr[4])&0x00ffffff;
+
+	if (iscsi_add_data(iscsi, &pdu->indata,
+			   discard_const(hdr + ISCSI_HEADER_SIZE), dsl, 0)
+	    != 0) {
+		iscsi_set_error(iscsi, "Out-of-memory: failed to add data "
+				"to pdu in buffer.");
+		return -2;
+	}
+
+
+	if ((flags&ISCSI_PDU_DATA_FINAL) == 0) {
+		*is_finished = 0;
+	}
+	if ((flags&ISCSI_PDU_DATA_CONTAINS_STATUS) == 0) {
+		*is_finished = 0;
+	}
+
+	if (*is_finished == 0) {
+		return 0;
+	}
+
+
+	/* this was the final data-in packet in the sequence and it has
+	 * the s-bit set, so invoke the callback.
+	 */
+	status = hdr[3];
+	task->datain.data = pdu->indata.data;
+	task->datain.size = pdu->indata.size;
+
+	pdu->indata.data = NULL;
+	pdu->indata.size = 0;
+
+	pdu->callback(iscsi, status, task, pdu->private_data);
+
+	return 0;
+}
+
+
+
+
+/*
+ * SCSI commands
+ */
+
+int
+iscsi_testunitready_async(struct iscsi_context *iscsi, int lun,
+			  iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	int ret;
+
+	task = scsi_cdb_testunitready();
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"testunitready cdb.");
+		return -1;
+	}
+	ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL,
+				       private_data);
+
+	return ret;
+}
+
+
+int
+iscsi_reportluns_async(struct iscsi_context *iscsi, int report_type,
+		       int alloc_len, iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	int ret;
+
+	if (alloc_len < 16) {
+		iscsi_set_error(iscsi, "Minimum allowed alloc len for "
+				"reportluns is 16. You specified %d.",
+				alloc_len);
+		return -1;
+	}
+
+	task = scsi_reportluns_cdb(report_type, alloc_len);
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"reportluns cdb.");
+		return -2;
+	}
+	/* report luns are always sent to lun 0 */
+	ret = iscsi_scsi_command_async(iscsi, 0, task, cb, NULL,
+				       private_data);
+
+	return ret;
+}
+
+int
+iscsi_inquiry_async(struct iscsi_context *iscsi, int lun, int evpd,
+		    int page_code, int maxsize,
+		    iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	int ret;
+
+	task = scsi_cdb_inquiry(evpd, page_code, maxsize);
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"inquiry cdb.");
+		return -1;
+	}
+	ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL,
+				       private_data);
+
+	return ret;
+}
+
+int
+iscsi_readcapacity10_async(struct iscsi_context *iscsi, int lun, int lba,
+			   int pmi, iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	int ret;
+
+	task = scsi_cdb_readcapacity10(lba, pmi);
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"readcapacity10 cdb.");
+		return -1;
+	}
+	ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL,
+				       private_data);
+
+	return ret;
+}
+
+int
+iscsi_read10_async(struct iscsi_context *iscsi, int lun, int lba,
+		   int datalen, int blocksize,
+		   iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	int ret;
+
+	if (datalen % blocksize != 0) {
+		iscsi_set_error(iscsi, "Datalen:%d is not a multiple of "
+				"the blocksize:%d.", datalen, blocksize);
+		return -1;
+	}
+
+	task = scsi_cdb_read10(lba, datalen, blocksize);
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"read10 cdb.");
+		return -2;
+	}
+	ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL,
+				       private_data);
+
+	return ret;
+}
+
+
+int
+iscsi_write10_async(struct iscsi_context *iscsi, int lun, unsigned char *data,
+		    int datalen, int lba, int fua, int fuanv, int blocksize,
+		    iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	struct iscsi_data outdata;
+	int ret;
+
+	if (datalen % blocksize != 0) {
+		iscsi_set_error(iscsi, "Datalen:%d is not a multiple of the "
+				"blocksize:%d.", datalen, blocksize);
+		return -1;
+	}
+
+	task = scsi_cdb_write10(lba, datalen, fua, fuanv, blocksize);
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"read10 cdb.");
+		return -2;
+	}
+
+	outdata.data = data;
+	outdata.size = datalen;
+
+	ret = iscsi_scsi_command_async(iscsi, lun, task, cb, &outdata,
+				       private_data);
+
+	return ret;
+}
+
+int
+iscsi_modesense6_async(struct iscsi_context *iscsi, int lun, int dbd, int pc,
+		       int page_code, int sub_page_code,
+		       unsigned char alloc_len,
+		       iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	int ret;
+
+	task = scsi_cdb_modesense6(dbd, pc, page_code, sub_page_code,
+				   alloc_len);
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"modesense6 cdb.");
+		return -2;
+	}
+	ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL,
+				       private_data);
+
+	return ret;
+}
+
+int
+iscsi_synchronizecache10_async(struct iscsi_context *iscsi, int lun, int lba,
+			       int num_blocks, int syncnv, int immed,
+			       iscsi_command_cb cb, void *private_data)
+{
+	struct scsi_task *task;
+	int ret;
+
+	task = scsi_cdb_synchronizecache10(lba, num_blocks, syncnv,
+					   immed);
+	if (task == NULL) {
+		iscsi_set_error(iscsi, "Out-of-memory: Failed to create "
+				"synchronizecache10 cdb.");
+		return -1;
+	}
+	ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL,
+				       private_data);
+
+	return ret;
+}
+