Files
libiscsi/lib/scsi-command.c
Ronnie Sahlberg 1c024d6bc4 Input processing:
Input processing used to keep all data in one single input buffer, which
makes it hard to handle nested events as well as reading directly from the
socket into the application buffer without an extra copy.

Create a new iscsi_in_pdu structure where we store the header, and any data
for the recevied pdu and store them in a proper input queue.

Change the signature for all processing functions to tahe a iscsi_in_pdu
structure for the received pdu instead of just a pointer to a buffer.
2010-12-11 15:15:51 +11:00

528 lines
13 KiB
C

/*
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 Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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 -1;
}
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 -1;
}
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 -1;
}
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 -1;
}
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 -1;
}
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 -1;
}
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 -1;
}
return 0;
}
int
iscsi_process_scsi_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu,
struct iscsi_in_pdu *in)
{
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 *)&in->hdr[24]);
if (statsn > (int)iscsi->statsn) {
iscsi->statsn = statsn;
}
flags = in->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 = in->hdr[2];
status = in->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 = in->data_pos;
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, in->data, 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,
struct iscsi_in_pdu *in, 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 *)&in->hdr[24]);
if (statsn > (int)iscsi->statsn) {
iscsi->statsn = statsn;
}
flags = in->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 *)&in->hdr[4])&0x00ffffff;
if (iscsi_add_data(iscsi, &pdu->indata,
in->data, dsl, 0)
!= 0) {
iscsi_set_error(iscsi, "Out-of-memory: failed to add data "
"to pdu in buffer.");
return -1;
}
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 = in->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 -1;
}
/* 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 -1;
}
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 -1;
}
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 -1;
}
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;
}