From 098bc5a9a74d36f94bbff4abb7720595e50536e5 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Sun, 5 Dec 2010 08:24:57 +1100 Subject: [PATCH] Initial import of libiscsi --- INSTALL | 13 + Makefile | 56 +++ README | 12 + examples/iscsiclient.c | 459 +++++++++++++++++ include/iscsi-private.h | 171 +++++++ include/iscsi.h | 436 ++++++++++++++++ include/scsi-lowlevel.h | 369 ++++++++++++++ include/slist.h | 51 ++ lib/connect.c | 127 +++++ lib/crc32c.c | 99 ++++ lib/discovery.c | 189 +++++++ lib/init.c | 207 ++++++++ lib/login.c | 378 ++++++++++++++ lib/nop.c | 90 ++++ lib/pdu.c | 336 +++++++++++++ lib/scsi-command.c | 528 ++++++++++++++++++++ lib/scsi-lowlevel.c | 876 +++++++++++++++++++++++++++++++++ lib/socket.c | 351 +++++++++++++ lib/sync.c | 282 +++++++++++ packaging/RPM/libiscsi.spec.in | 87 ++++ packaging/RPM/makerpms.sh | 92 ++++ src/iscsi-inq.c | 218 ++++++++ src/iscsi-ls.c | 315 ++++++++++++ 23 files changed, 5742 insertions(+) create mode 100644 INSTALL create mode 100644 Makefile create mode 100644 README create mode 100644 examples/iscsiclient.c create mode 100644 include/iscsi-private.h create mode 100644 include/iscsi.h create mode 100644 include/scsi-lowlevel.h create mode 100644 include/slist.h create mode 100644 lib/connect.c create mode 100644 lib/crc32c.c create mode 100644 lib/discovery.c create mode 100644 lib/init.c create mode 100644 lib/login.c create mode 100644 lib/nop.c create mode 100644 lib/pdu.c create mode 100644 lib/scsi-command.c create mode 100644 lib/scsi-lowlevel.c create mode 100644 lib/socket.c create mode 100644 lib/sync.c create mode 100644 packaging/RPM/libiscsi.spec.in create mode 100755 packaging/RPM/makerpms.sh create mode 100644 src/iscsi-inq.c create mode 100644 src/iscsi-ls.c diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..7fe6711 --- /dev/null +++ b/INSTALL @@ -0,0 +1,13 @@ +This installs a new shared library under /usr/lib[64] +and executables under /usr/bin + + +From source: +============ +$ make +# make install + +Building RPM: +============= +./packaging/RPM/makerpms.sh + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d11de3e --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +LIBS="-lpopt" +CC=gcc +CFLAGS=-g -O0 -fPIC -Wall -W -I./include "-D_U_=__attribute__((unused))" +LIBISCSI_OBJ = lib/connect.o lib/crc32c.o lib/discovery.o lib/init.o lib/login.o lib/nop.o lib/pdu.o lib/scsi-command.o lib/scsi-lowlevel.o lib/socket.o lib/sync.o +INSTALLCMD = /usr/bin/install -c + +LIBISCSI_SO_NAME=libiscsi.so.1 +VERSION=1.0.0 +LIBISCSI_SO=libiscsi.so.$(VERSION) + +all: bin/iscsi-inq bin/iscsi-ls lib/$(LIBISCSI_SO) + +bin/iscsi-ls: src/iscsi-ls.c lib/libiscsi.a + mkdir -p bin + $(CC) $(CFLAGS) -o $@ src/iscsi-ls.c lib/libiscsi.a $(LIBS) + +bin/iscsi-inq: src/iscsi-inq.c lib/libiscsi.a + mkdir -p bin + $(CC) $(CFLAGS) -o $@ src/iscsi-inq.c lib/libiscsi.a $(LIBS) + +lib/$(LIBISCSI_SO): $(LIBISCSI_OBJ) + @echo Creating shared library $@ + $(CC) -shared -Wl,-soname=$(LIBISCSI_SO_NAME) -o $@ $(LIBISCSI_OBJ) + +lib/libiscsi.a: $(LIBISCSI_OBJ) + @echo Creating library $@ + ar r lib/libiscsi.a $(LIBISCSI_OBJ) + ranlib lib/libiscsi.a + +examples: bin/iscsiclient + +bin/iscsiclient: examples/iscsiclient.c lib/libiscsi.a + mkdir -p bin + $(CC) $(CFLAGS) -o $@ examples/iscsiclient.c lib/libiscsi.a $(LIBS) + +install: lib/libiscsi.a lib/$(LIBISCSI_SO) bin/iscsi-ls bin/iscsi-inq +ifeq ("$(LIBDIR)x","x") + $(INSTALLCMD) -m 755 lib/$(LIBISCSI_SO) /usr/lib + $(INSTALLCMD) -m 755 lib/libiscsi.a /usr/lib + ldconfig +else + $(INSTALLCMD) -m 755 lib/$(LIBISCSI_SO) $(LIBDIR) + $(INSTALLCMD) -m 755 lib/libiscsi.a $(LIBDIR) +endif + $(INSTALLCMD) -m 755 bin/iscsi-ls $(DESTDIR)/usr/bin + $(INSTALLCMD) -m 755 bin/iscsi-inq $(DESTDIR)/usr/bin + mkdir -p $(DESTDIR)/usr/include/iscsi + $(INSTALLCMD) -m 644 include/iscsi.h $(DESTDIR)/usr/include/iscsi + $(INSTALLCMD) -m 644 include/scsi-lowlevel.h $(DESTDIR)/usr/include/iscsi + +clean: + rm -f lib/*.o src/*.o examples/*.o + rm -f bin/* + rm -f lib/libiscsi.so* + rm -f lib/libiscsi.a + rm -f iscsi-inq iscsi-ls diff --git a/README b/README new file mode 100644 index 0000000..4160493 --- /dev/null +++ b/README @@ -0,0 +1,12 @@ +Libiscsi is a work in progress. +It aims to become a full async library for iscsi functionality, +including all features required to establish and maintain a iscsi +session, as well as a low level scsi library to create scsi cdb's +and parse/unmarshall data-in structures. + + +Installation +============ +make +make install + diff --git a/examples/iscsiclient.c b/examples/iscsiclient.c new file mode 100644 index 0000000..444342a --- /dev/null +++ b/examples/iscsiclient.c @@ -0,0 +1,459 @@ +/* This is an example of using libiscsi. + * It basically logs in to the the target and performs a discovery. + * It then selects the last target in the returned list and + * starts a normal login to that target. + * Once logged in it issues a REPORTLUNS call and selects the last returned lun in the list. + * This LUN is then used to send INQUIRY, READCAPACITY10 and READ10 test calls to. + */ +/* The reason why we have to specify an allocation length and sometimes probe, starting with a small value, probing how big the buffer + * should be, and asking again with a bigger buffer. + * Why not just always ask with a buffer that is big enough? + * The reason is that a lot of scsi targets are "sensitive" and ""buggy"" + * many targets will just fail the operation completely if they thing alloc len is unreasonably big. + */ + +/* This is the host/port we connect to.*/ +#define TARGET "127.0.0.1:3260" + +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "scsi-lowlevel.h" + +struct client_state { + int finished; + char *message; + int has_discovered_target; + char *target_name; + char *target_address; + int lun; + int block_size; +}; + +void synccache10_cb(struct iscsi_context *iscsi _U_, int status, void *command_data _U_, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + + printf("SYNCCACHE10 status:%d\n", status); + clnt->finished = 1; +} + +void nop_out_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct iscsi_data *data = command_data; + + printf("NOP-IN status:%d\n", status); + if (data->size > 0) { + printf("NOP-IN data:%s\n", data->data); + } + printf("Send SYNCHRONIZECACHE10\n"); + if (iscsi_synchronizecache10_async(iscsi, 2, 0, 0, 0, 0, synccache10_cb, private_data) != 0) { + printf("failed to send sync cache10\n"); + exit(10); + } +} + + +void write10_cb(struct iscsi_context *iscsi _U_, int status, void *command_data, void *private_data _U_) +{ + struct scsi_task *task = command_data; + + if (status == SCSI_STATUS_CHECK_CONDITION) { + + printf("Write10 failed with sense key:%d ascq:%04x\n", task->sense.key, task->sense.ascq); + exit(10); + } + + printf("Write successful\n"); + exit(10); +} + + +void read10_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct scsi_task *task = command_data; + int i; + + if (status == SCSI_STATUS_CHECK_CONDITION) { + printf("Read10 failed with sense key:%d ascq:%04x\n", task->sense.key, task->sense.ascq); + exit(10); + } + + printf("READ10 successful. Block content:\n"); + for (i=0;idatain.size;i++) { + printf("%02x ", task->datain.data[i]); + if (i%16==15) + printf("\n"); + if (i==69) + break; + } + printf("...\n"); + + printf("Finished, wont try to write data since that will likely destroy your LUN :-(\n"); + printf("Send NOP-OUT\n"); + if (iscsi_nop_out_async(iscsi, nop_out_cb, (unsigned char *)"Ping!", 6, private_data) != 0) { + printf("failed to send nop-out\n"); + exit(10); + } +// printf("write the block back\n"); +// if (iscsi_write10_async(iscsi, clnt->lun, task->data.datain, task->datain.size, 0, 0, 0, clnt->block_size, write10_cb, private_data) != 0) { +// printf("failed to send write10 command\n"); +// exit(10); +// } +} + +void readcapacity10_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + struct scsi_task *task = command_data; + struct scsi_readcapacity10 *rc10; + int full_size; + + if (status == SCSI_STATUS_CHECK_CONDITION) { + printf("Readcapacity10 failed with sense key:%d ascq:%04x\n", task->sense.key, task->sense.ascq); + exit(10); + } + + full_size = scsi_datain_getfullsize(task); + if (full_size < task->datain.size) { + printf("not enough data for full size readcapacity10\n"); + exit(10); + } + + rc10 = scsi_datain_unmarshall(task); + if (rc10 == NULL) { + printf("failed to unmarshall readcapacity10 data\n"); + exit(10); + } + clnt->block_size = rc10->block_size; + printf("READCAPACITY10 successful. Size:%d blocks blocksize:%d. Read first block\n", rc10->lba, rc10->block_size); + + if (iscsi_read10_async(iscsi, clnt->lun, 0, clnt->block_size, clnt->block_size, read10_cb, private_data) != 0) { + printf("failed to send read10 command\n"); + exit(10); + } +} + +void modesense6_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + struct scsi_task *task = command_data; + int full_size; + + if (status == SCSI_STATUS_CHECK_CONDITION) { + printf("Modesense6 failed with sense key:%d ascq:%04x\n", task->sense.key, task->sense.ascq); + } else { + full_size = scsi_datain_getfullsize(task); + if (full_size > task->datain.size) { + printf("did not get enough data for mode sense, sening modesense again asking for bigger buffer\n"); + if (iscsi_modesense6_async(iscsi, clnt->lun, 0, SCSI_MODESENSE_PC_CURRENT, SCSI_MODESENSE_PAGECODE_RETURN_ALL_PAGES, 0, full_size, modesense6_cb, private_data) != 0) { + printf("failed to send modesense6 command\n"); + exit(10); + } + return; + } + + printf("MODESENSE6 successful.\n"); + } + + printf("Send READCAPACITY10\n"); + if (iscsi_readcapacity10_async(iscsi, clnt->lun, 0, 0, readcapacity10_cb, private_data) != 0) { + printf("failed to send readcapacity command\n"); + exit(10); + } +} + +void inquiry_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + struct scsi_task *task = command_data; + struct scsi_inquiry_standard *inq; + + if (status == SCSI_STATUS_CHECK_CONDITION) { + printf("Inquiry failed with sense key:%d ascq:%04x\n", task->sense.key, task->sense.ascq); + exit(10); + } + + printf("INQUIRY successful for standard data.\n"); + inq = scsi_datain_unmarshall(task); + if (inq == NULL) { + printf("failed to unmarshall inquiry datain blob\n"); + exit(10); + } + + printf("Device Type is %d. VendorId:%s ProductId:%s\n", inq->periperal_device_type, inq->vendor_identification, inq->product_identification); + printf("Send MODESENSE6\n"); + if (iscsi_modesense6_async(iscsi, clnt->lun, 0, SCSI_MODESENSE_PC_CURRENT, SCSI_MODESENSE_PAGECODE_RETURN_ALL_PAGES, 0, 4, modesense6_cb, private_data) != 0) { + printf("failed to send modesense6 command\n"); + exit(10); + } + +} + +void testunitready_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + struct scsi_task *task = command_data; + + if (status == SCSI_STATUS_CHECK_CONDITION) { + printf("First testunitready failed with sense key:%d ascq:%04x\n", task->sense.key, task->sense.ascq); + if (task->sense.key == SCSI_SENSE_UNIT_ATTENTION && task->sense.ascq == SCSI_SENSE_ASCQ_BUS_RESET) { + printf("target device just came online, try again\n"); + + if (iscsi_testunitready_async(iscsi, clnt->lun, testunitready_cb, private_data) != 0) { + printf("failed to send testunitready command\n"); + exit(10); + } + } + return; + } + + printf("TESTUNITREADY successful, do an inquiry on lun:%d\n", clnt->lun); + if (iscsi_inquiry_async(iscsi, clnt->lun, 0, 0, 64, inquiry_cb, private_data) != 0) { + printf("failed to send inquiry command : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + + +void reportluns_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + struct scsi_task *task = command_data; + struct scsi_reportluns_list *list; + int full_report_size; + int i; + + if (status != SCSI_STATUS_GOOD) { + printf("Reportluns failed with : %s\n", iscsi_get_error(iscsi)); + return; + } + + full_report_size = scsi_datain_getfullsize(task); + + printf("REPORTLUNS status:%d data size:%d, full reports luns data size:%d\n", status, task->datain.size, full_report_size); + if (full_report_size > task->datain.size) { + printf("We did not get all the data we need in reportluns, ask again\n"); + if (iscsi_reportluns_async(iscsi, 0, full_report_size, reportluns_cb, private_data) != 0) { + printf("failed to send reportluns command\n"); + exit(10); + } + return; + } + + + list = scsi_datain_unmarshall(task); + if (list == NULL) { + printf("failed to unmarshall reportluns datain blob\n"); + exit(10); + } + for (i=0; i < (int)list->num; i++) { + printf("LUN:%d found\n", list->luns[i]); + clnt->lun = list->luns[i]; + } + + printf("Will use LUN:%d\n", clnt->lun); + printf("Send testunitready to lun %d\n", clnt->lun); + if (iscsi_testunitready_async(iscsi, clnt->lun, testunitready_cb, private_data) != 0) { + printf("failed to send testunitready command : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + + +void normallogin_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + if (status != 0) { + printf("Failed to log in to target : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + printf("Logged in normal session, send reportluns\n"); + if (iscsi_reportluns_async(iscsi, 0, 16, reportluns_cb, private_data) != 0) { + printf("failed to send reportluns command : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + + +void normalconnect_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + printf("Connected to iscsi socket\n"); + + if (status != 0) { + printf("normalconnect_cb: connection failed status:%d\n", status); + exit(10); + } + + printf("connected, send login command\n"); + iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C_NONE); + if (iscsi_login_async(iscsi, normallogin_cb, private_data) != 0) { + printf("iscsi_login_async failed\n"); + exit(10); + } +} + + + +void discoverylogout_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + + printf("discovery session logged out, Message from main() was:[%s]\n", clnt->message); + + if (status != 0) { + printf("Failed to logout from target. : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + printf("disconnect socket\n"); + if (iscsi_disconnect(iscsi) != 0) { + printf("Failed to disconnect old socket\n"); + exit(10); + } + + printf("reconnect with normal login to [%s]\n", clnt->target_address); + printf("Use targetname [%s] when connecting\n", clnt->target_name); + if (iscsi_set_targetname(iscsi, clnt->target_name)) { + printf("Failed to set target name\n"); + exit(10); + } + if (iscsi_set_alias(iscsi, "ronnie") != 0) { + printf("Failed to add alias\n"); + exit(10); + } + if (iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL) != 0) { + printf("Failed to set settion type to normal\n"); + exit(10); + } + + if (iscsi_connect_async(iscsi, clnt->target_address, normalconnect_cb, clnt) != 0) { + printf("iscsi_connect failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + +void discovery_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct client_state *clnt = (struct client_state *)private_data; + struct iscsi_discovery_address *addr; + + printf("discovery callback status:%04x\n", status); + + if (status != 0) { + printf("Failed to do discovery on target. : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + for(addr=command_data; addr; addr=addr->next) { + printf("Target:%s Address:%s\n", addr->target_name, addr->target_address); + } + + addr=command_data; + clnt->has_discovered_target = 1; + clnt->target_name = strdup(addr->target_name); + clnt->target_address = strdup(addr->target_address); + + + printf("discovery complete, send logout command\n"); + + if (iscsi_logout_async(iscsi, discoverylogout_cb, private_data) != 0) { + printf("iscsi_logout_async failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + + +void discoverylogin_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + if (status != 0) { + printf("Failed to log in to target. : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + printf("Logged in to target, send discovery command\n"); + if (iscsi_discovery_async(iscsi, discovery_cb, private_data) != 0) { + printf("failed to send discovery command : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + +} + +void discoveryconnect_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + printf("Connected to iscsi socket status:0x%08x\n", status); + + if (status != 0) { + printf("discoveryconnect_cb: connection failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + printf("connected, send login command\n"); + iscsi_set_session_type(iscsi, ISCSI_SESSION_DISCOVERY); + if (iscsi_login_async(iscsi, discoverylogin_cb, private_data) != 0) { + printf("iscsi_login_async failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + + +int main(int argc _U_, char *argv[] _U_) +{ + struct iscsi_context *iscsi; + struct pollfd pfd; + struct client_state clnt; + + printf("iscsi client\n"); + + bzero(&clnt, sizeof(clnt)); + + iscsi = iscsi_create_context("iqn.2002-10.com.ronnie:client"); + if (iscsi == NULL) { + printf("Failed to create context\n"); + exit(10); + } + + if (iscsi_set_alias(iscsi, "ronnie") != 0) { + printf("Failed to add alias\n"); + exit(10); + } + + clnt.message = "Hello iSCSI"; + clnt.has_discovered_target = 0; + if (iscsi_connect_async(iscsi, TARGET, discoveryconnect_cb, &clnt) != 0) { + printf("iscsi_connect failed. %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + while (clnt.finished == 0) { + pfd.fd = iscsi_get_fd(iscsi); + pfd.events = iscsi_which_events(iscsi); + + if (poll(&pfd, 1, -1) < 0) { + printf("Poll failed"); + exit(10); + } + if (iscsi_service(iscsi, pfd.revents) < 0) { + printf("iscsi_service failed with : %s\n", iscsi_get_error(iscsi)); + break; + } + } + + + iscsi_destroy_context(iscsi); + + if (clnt.target_name != NULL) { + free(clnt.target_name); + } + if (clnt.target_address != NULL) { + free(clnt.target_address); + } + + printf("ok\n"); + return 0; +} + diff --git a/include/iscsi-private.h b/include/iscsi-private.h new file mode 100644 index 0000000..b713014 --- /dev/null +++ b/include/iscsi-private.h @@ -0,0 +1,171 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include + +#ifndef discard_const +#define discard_const(ptr) ((void *)((intptr_t)(ptr))) +#endif + +struct iscsi_context { + const char *initiator_name; + const char *target_name; + const char *alias; + enum iscsi_session_type session_type; + unsigned char isid[6]; + uint32_t itt; + uint32_t cmdsn; + uint32_t statsn; + enum iscsi_header_digest want_header_digest; + enum iscsi_header_digest header_digest; + + char *error_string; + + int fd; + int is_connected; + int is_loggedin; + + iscsi_command_cb socket_status_cb; + void *connect_data; + + struct iscsi_pdu *outqueue; + struct iscsi_pdu *waitpdu; + + int insize; + int inpos; + unsigned char *inbuf; +}; + +#define ISCSI_RAW_HEADER_SIZE 48 + +#define ISCSI_HEADER_SIZE (ISCSI_RAW_HEADER_SIZE \ + + (iscsi->header_digest == ISCSI_HEADER_DIGEST_NONE?0:4)) + +#define ISCSI_PDU_IMMEDIATE 0x40 + +#define ISCSI_PDU_TEXT_FINAL 0x80 +#define ISCSI_PDU_TEXT_CONTINUE 0x40 + +#define ISCSI_PDU_LOGIN_TRANSIT 0x80 +#define ISCSI_PDU_LOGIN_CONTINUE 0x40 +#define ISCSI_PDU_LOGIN_CSG_SECNEG 0x00 +#define ISCSI_PDU_LOGIN_CSG_OPNEG 0x04 +#define ISCSI_PDU_LOGIN_CSG_FF 0x0c +#define ISCSI_PDU_LOGIN_NSG_SECNEG 0x00 +#define ISCSI_PDU_LOGIN_NSG_OPNEG 0x01 +#define ISCSI_PDU_LOGIN_NSG_FF 0x03 + +#define ISCSI_PDU_SCSI_FINAL 0x80 +#define ISCSI_PDU_SCSI_READ 0x40 +#define ISCSI_PDU_SCSI_WRITE 0x20 +#define ISCSI_PDU_SCSI_ATTR_UNTAGGED 0x00 +#define ISCSI_PDU_SCSI_ATTR_SIMPLE 0x01 +#define ISCSI_PDU_SCSI_ATTR_ORDERED 0x02 +#define ISCSI_PDU_SCSI_ATTR_HEADOFQUEUE 0x03 +#define ISCSI_PDU_SCSI_ATTR_ACA 0x04 + +#define ISCSI_PDU_DATA_FINAL 0x80 +#define ISCSI_PDU_DATA_ACK_REQUESTED 0x40 +#define ISCSI_PDU_DATA_BIDIR_OVERFLOW 0x10 +#define ISCSI_PDU_DATA_BIDIR_UNDERFLOW 0x08 +#define ISCSI_PDU_DATA_RESIDUAL_OVERFLOW 0x04 +#define ISCSI_PDU_DATA_RESIDUAL_UNDERFLOW 0x02 +#define ISCSI_PDU_DATA_CONTAINS_STATUS 0x01 + +enum iscsi_opcode { + ISCSI_PDU_NOP_OUT = 0x00, + ISCSI_PDU_SCSI_REQUEST = 0x01, + ISCSI_PDU_LOGIN_REQUEST = 0x03, + ISCSI_PDU_TEXT_REQUEST = 0x04, + ISCSI_PDU_LOGOUT_REQUEST = 0x06, + ISCSI_PDU_NOP_IN = 0x20, + ISCSI_PDU_SCSI_RESPONSE = 0x21, + ISCSI_PDU_LOGIN_RESPONSE = 0x23, + ISCSI_PDU_TEXT_RESPONSE = 0x24, + ISCSI_PDU_DATA_IN = 0x25, + ISCSI_PDU_LOGOUT_RESPONSE = 0x26 +}; + +struct iscsi_pdu { + struct iscsi_pdu *next; + + uint32_t itt; + uint32_t cmdsn; + enum iscsi_opcode response_opcode; + + iscsi_command_cb callback; + void *private_data; + + int written; + struct iscsi_data outdata; + struct iscsi_data indata; + + struct iscsi_scsi_cbdata *scsi_cbdata; +}; + +void iscsi_free_scsi_cbdata(struct iscsi_scsi_cbdata *scsi_cbdata); + +struct iscsi_pdu *iscsi_allocate_pdu(struct iscsi_context *iscsi, + enum iscsi_opcode opcode, + enum iscsi_opcode response_opcode); +void iscsi_free_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu); +void iscsi_pdu_set_pduflags(struct iscsi_pdu *pdu, unsigned char flags); +void iscsi_pdu_set_immediate(struct iscsi_pdu *pdu); +void iscsi_pdu_set_ttt(struct iscsi_pdu *pdu, uint32_t ttt); +void iscsi_pdu_set_cmdsn(struct iscsi_pdu *pdu, uint32_t cmdsn); +void iscsi_pdu_set_lun(struct iscsi_pdu *pdu, uint32_t lun); +void iscsi_pdu_set_expstatsn(struct iscsi_pdu *pdu, uint32_t expstatsnsn); +void iscsi_pdu_set_expxferlen(struct iscsi_pdu *pdu, uint32_t expxferlen); +int iscsi_pdu_add_data(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, + unsigned char *dptr, int dsize); +int iscsi_queue_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu); +int iscsi_add_data(struct iscsi_context *iscsi, struct iscsi_data *data, + unsigned char *dptr, int dsize, int pdualignment); + +struct scsi_task; +void iscsi_pdu_set_cdb(struct iscsi_pdu *pdu, struct scsi_task *task); + +int iscsi_get_pdu_size(struct iscsi_context *iscsi, const unsigned char *hdr); +int iscsi_process_pdu(struct iscsi_context *iscsi, const unsigned char *hdr, + int size); + +int iscsi_process_login_reply(struct iscsi_context *iscsi, + struct iscsi_pdu *pdu, + const unsigned char *hdr, int size); +int iscsi_process_text_reply(struct iscsi_context *iscsi, + struct iscsi_pdu *pdu, + const unsigned char *hdr, int size); +int iscsi_process_logout_reply(struct iscsi_context *iscsi, + struct iscsi_pdu *pdu, + const unsigned char *hdr, int size); +int iscsi_process_scsi_reply(struct iscsi_context *iscsi, + struct iscsi_pdu *pdu, + const unsigned char *hdr, int size); +int iscsi_process_scsi_data_in(struct iscsi_context *iscsi, + struct iscsi_pdu *pdu, + const unsigned char *hdr, int size, + int *is_finished); +int iscsi_process_nop_out_reply(struct iscsi_context *iscsi, + struct iscsi_pdu *pdu, + const unsigned char *hdr, int size); + +void iscsi_set_error(struct iscsi_context *iscsi, const char *error_string, + ...); + +unsigned long crc32c(char *buf, int len); + +void iscsi_cbdata_steal_scsi_task(struct scsi_task *task); diff --git a/include/iscsi.h b/include/iscsi.h new file mode 100644 index 0000000..5d92e5c --- /dev/null +++ b/include/iscsi.h @@ -0,0 +1,436 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +struct iscsi_context; +struct sockaddr; + + +const char *iscsi_get_error(struct iscsi_context *iscsi); + +/* + * Returns the file descriptor that libiscsi uses. + */ +int iscsi_get_fd(struct iscsi_context *iscsi); + +/* + * Returns which events that we need to poll for for the iscsi file descriptor. + */ +int iscsi_which_events(struct iscsi_context *iscsi); + +/* + * Called to process the events when events become available for the iscsi + * file descriptor. + */ +int iscsi_service(struct iscsi_context *iscsi, int revents); + + + +/* + * Create a context for an ISCSI session. + * Initiator_name is the iqn name we want to identify to the target as. + * + * Returns: + * 0: success + * <0: error + */ +struct iscsi_context *iscsi_create_context(const char *initiator_name); + +/* + * Destroy an existing ISCSI context and tear down any existing connection. + * Callbacks for any command in flight will be invoked with + * ISCSI_STATUS_CANCELLED. + * + * Returns: + * 0: success + * <0: error + */ +int iscsi_destroy_context(struct iscsi_context *iscsi); + +/* + * Set an optional alias name to identify with when connecting to the target + * + * Returns: + * 0: success + * <0: error + */ +int iscsi_set_alias(struct iscsi_context *iscsi, const char *alias); + +/* + * Set the iqn name of the taqget to login to. + * The target name must be set before a normal-login can be initiated. + * Only discovery-logins are possible without setting the target iqn name. + * + * Returns: + * 0: success + * <0: error + */ +int iscsi_set_targetname(struct iscsi_context *iscsi, const char *targetname); + + +/* Types of icsi sessions. Discovery sessions are used to query for what + * targets exist behin the portal connected to. Normal sessions are used to + * log in and do I/O to the SCSI LUNs + */ +enum iscsi_session_type { + ISCSI_SESSION_DISCOVERY = 1, + ISCSI_SESSION_NORMAL = 2 +}; + +/* + * Set the session type for a scsi context. + * Session type can only be set/changed while the iscsi context is not + * logged in to a target. + * + * Returns: + * 0: success + * <0: error + */ +int iscsi_set_session_type(struct iscsi_context *iscsi, + enum iscsi_session_type session_type); + + +/* + * Types of header digest we support. Default is NONE + */ +enum iscsi_header_digest { + ISCSI_HEADER_DIGEST_NONE = 0, + ISCSI_HEADER_DIGEST_NONE_CRC32C = 1, + ISCSI_HEADER_DIGEST_CRC32C_NONE = 2, + ISCSI_HEADER_DIGEST_CRC32C = 3 +}; + +/* + * Set the desired header digest for a scsi context. + * Header digest can only be set/changed while the iscsi context is not + * logged in to a target. + * + * Returns: + * 0: success + * <0: error + */ +int iscsi_set_header_digest(struct iscsi_context *iscsi, + enum iscsi_header_digest header_digest); + + +/* + * check if the context is logged in or not + */ +int iscsi_is_logged_in(struct iscsi_context *iscsi); + + +enum scsi_status { + SCSI_STATUS_GOOD = 0, + SCSI_STATUS_CHECK_CONDITION = 2, + SCSI_STATUS_CANCELLED = 0x0f000000, + SCSI_STATUS_ERROR = 0x0f000001 +}; + + +/* + * Generic callback for completion of iscsi_*_async(). + * command_data depends on status. + */ +typedef void (*iscsi_command_cb)(struct iscsi_context *iscsi, int status, + void *command_data, void *private_data); + + + +/* + * Asynchronous call to connect a TCP connection to the target-host/port + * + * Returns: + * 0 if the call was initiated and a connection will be attempted. Result of + * the connection will be reported through the callback function. + * <0 if there was an error. The callback function will not be invoked. + * + * This command is unique in that the callback can be invoked twice. + * + * Callback parameters : + * status can be either of : + * ISCSI_STATUS_GOOD : Connection was successful. Command_data is NULL. + * In this case the callback will be invoked a + * second time once the connection is torn down. + * + * ISCSI_STATUS_ERROR : Either failed to establish the connection, or + * an already established connection has failed + * with an error. + * + * The callback will NOT be invoked if the session is explicitely torn down + * through a call to iscsi_disconnect() or iscsi_destroy_context(). + */ +int iscsi_connect_async(struct iscsi_context *iscsi, const char *portal, + iscsi_command_cb cb, void *private_data); + +/* + * Synchronous call to connect a TCP connection to the target-host/port + * + * Returns: + * 0 if connected successfully. + * <0 if there was an error. + * + */ +int iscsi_connect_sync(struct iscsi_context *iscsi, const char *portal); + + +/* + * Asynchronous call to connect a lun + * This function will connect to the portal, login, and verify that the lun + * is available. + * + * Returns: + * 0 if the call was initiated and a connection will be attempted. Result + * of the connection will be reported through the callback function. + * <0 if there was an error. The callback function will not be invoked. + * + * This command is unique in that the callback can be invoked twice. + * + * Callback parameters : + * status can be either of : + * ISCSI_STATUS_GOOD : Connection was successful. Command_data is NULL. + * In this case the callback will be invoked a + * second time once the connection is torn down. + * + * ISCSI_STATUS_ERROR : Either failed to establish the connection, or + * an already established connection has failed + * with an error. + * + * The callback will NOT be invoked if the session is explicitely torn down + * through a call to iscsi_disconnect() or iscsi_destroy_context(). + */ +int iscsi_full_connect_async(struct iscsi_context *iscsi, const char *portal, + int lun, iscsi_command_cb cb, void *private_data); + +/* + * Synchronous call to connect a lun + * This function will connect to the portal, login, and verify that the lun + * is available. + * + * Returns: + * 0 if the cconnect was successful. + * <0 if there was an error. + */ +int iscsi_full_connect_sync(struct iscsi_context *iscsi, const char *portal, + int lun); + +/* + * Disconnect a connection to a target. + * You can not disconnect while being logged in to a target. + * + * Returns: + * 0 disconnect was successful + * <0 error + */ +int iscsi_disconnect(struct iscsi_context *iscsi); + +/* + * Asynchronous call to perform an ISCSI login. + * + * Returns: + * 0 if the call was initiated and a login will be attempted. Result of the + * login will be reported through the callback function. + * <0 if there was an error. The callback function will not be invoked. + * + * Callback parameters : + * status can be either of : + * ISCSI_STATUS_GOOD : login was successful. Command_data is always + * NULL. + * ISCSI_STATUS_CANCELLED: login was aborted. Command_data is NULL. + * ISCSI_STATUS_ERROR : login failed. Command_data is NULL. + */ +int iscsi_login_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data); + +/* + * Synchronous call to perform an ISCSI login. + * + * Returns: + * 0 if the login was successful + * <0 if there was an error. + */ +int iscsi_login_sync(struct iscsi_context *iscsi); + + +/* + * Asynchronous call to perform an ISCSI logout. + * + * Returns: + * 0 if the call was initiated and a logout will be attempted. Result of the + * logout will be reported through the callback function. + * <0 if there was an error. The callback function will not be invoked. + * + * Callback parameters : + * status can be either of : + * ISCSI_STATUS_GOOD : logout was successful. Command_data is always + * NULL. + * ISCSI_STATUS_CANCELLED: logout was aborted. Command_data is NULL. + */ +int iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data); + +/* + * Synchronous call to perform an ISCSI logout. + * + * Returns: + * 0 if the logout was successful + * <0 if there was an error. + */ +int iscsi_logout_sync(struct iscsi_context *iscsi); + + +/* + * Asynchronous call to perform an ISCSI discovery. + * + * discoveries can only be done on connected and logged in discovery sessions. + * + * Returns: + * 0 if the call was initiated and a discovery will be attempted. Result + * of the logout will be reported through the callback function. + * <0 if there was an error. The callback function will not be invoked. + * + * Callback parameters : + * status can be either of : + * ISCSI_STATUS_GOOD : Discovery was successful. Command_data is a + * pointer to a iscsi_discovery_address list of + * structures. + * This list of structures is only valid for the + * duration of the callback and all data will be + * freed once the callback returns. + * ISCSI_STATUS_CANCELLED: Discovery was aborted. Command_data is NULL. + */ +int iscsi_discovery_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data); + +struct iscsi_discovery_address { + struct iscsi_discovery_address *next; + const char *target_name; + const char *target_address; +}; + +/* + * Asynchronous call to perform an ISCSI NOP-OUT call + * + * Returns: + * 0 if the call was initiated and a nop-out will be attempted. Result will + * be reported through the callback function. + * <0 if there was an error. The callback function will not be invoked. + * + * Callback parameters : + * status can be either of : + * ISCSI_STATUS_GOOD : NOP-OUT was successful and the server responded + * with a NOP-IN callback_data is a iscsi_data + * structure containing the data returned from + * the server. + * ISCSI_STATUS_CANCELLED: Discovery was aborted. Command_data is NULL. + */ +int iscsi_nop_out_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + unsigned char *data, int len, void *private_data); + + +/* These are the possible status values for the callbacks for scsi commands. + * The content of command_data depends on the status type. + * + * status : + * ISCSI_STATUS_GOOD the scsi command completed successfullt on the target. + * If this scsi command returns DATA-IN, that data is stored in an scsi_task + * structure returned in the command_data parameter. This buffer will be + * automatically freed once the callback returns. + * + * ISCSI_STATUS_CHECK_CONDITION the scsi command failed with a scsi sense. + * Command_data contains a struct scsi_task. When the callback returns, + * this buffer will automatically become freed. + * + * ISCSI_STATUS_CANCELLED the scsi command was aborted. Command_data is + * NULL. + * + * ISCSI_STATUS_ERROR the command failed. Command_data is NULL. + */ + + + +struct iscsi_data { + int size; + unsigned char *data; +}; + + +/* + * Async commands for SCSI + */ +struct scsi_task; +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); + +int iscsi_reportluns_async(struct iscsi_context *iscsi, int report_type, + int alloc_len, iscsi_command_cb cb, + void *private_data); +int iscsi_testunitready_async(struct iscsi_context *iscsi, int lun, + iscsi_command_cb cb, void *private_data); +int iscsi_inquiry_async(struct iscsi_context *iscsi, int lun, int evpd, + int page_code, int maxsize, iscsi_command_cb cb, + void *private_data); +int iscsi_readcapacity10_async(struct iscsi_context *iscsi, int lun, int lba, + int pmi, iscsi_command_cb cb, + void *private_data); +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); + +int iscsi_read10_async(struct iscsi_context *iscsi, int lun, int lba, + int datalen, int blocksize, iscsi_command_cb cb, + void *private_data); +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); +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); + + + + +/* + * Sync commands for SCSI + */ +struct scsi_task * +iscsi_scsi_command_sync(struct iscsi_context *iscsi, int lun, + struct scsi_task *task, struct iscsi_data *data); + +struct scsi_task * +iscsi_reportluns_sync(struct iscsi_context *iscsi, int report_type, + int alloc_len); + +struct scsi_task * +iscsi_testunitready_sync(struct iscsi_context *iscsi, int lun); + +struct scsi_task * +iscsi_inquiry_sync(struct iscsi_context *iscsi, int lun, int evpd, + int page_code, int maxsize); + +struct scsi_task * +iscsi_readcapacity10_sync(struct iscsi_context *iscsi, int lun, int lba, + int pmi); + +struct scsi_task * +iscsi_synchronizecache10_sync(struct iscsi_context *iscsi, int lun, int lba, + int num_blocks, int syncnv, int immed); + +int +iscsi_set_isid_random(struct iscsi_context *iscsi, int rnd); diff --git a/include/scsi-lowlevel.h b/include/scsi-lowlevel.h new file mode 100644 index 0000000..f32faac --- /dev/null +++ b/include/scsi-lowlevel.h @@ -0,0 +1,369 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#define SCSI_CDB_MAX_SIZE 16 + +enum scsi_opcode { + SCSI_OPCODE_TESTUNITREADY = 0x00, + SCSI_OPCODE_INQUIRY = 0x12, + SCSI_OPCODE_MODESENSE6 = 0x1a, + SCSI_OPCODE_READCAPACITY10 = 0x25, + SCSI_OPCODE_READ10 = 0x28, + SCSI_OPCODE_WRITE10 = 0x2A, + SCSI_OPCODE_SYNCHRONIZECACHE10 = 0x35, + SCSI_OPCODE_REPORTLUNS = 0xA0 +}; + +/* sense keys */ +enum scsi_sense_key { + SCSI_SENSE_NO_SENSE = 0x00, + SCSI_SENSE_RECOVERED_ERROR = 0x01, + SCSI_SENSE_NOT_READY = 0x02, + SCSI_SENSE_MEDIUM_ERROR = 0x03, + SCSI_SENSE_HARDWARE_ERROR = 0x04, + SCSI_SENSE_ILLEGAL_REQUEST = 0x05, + SCSI_SENSE_UNIT_ATTENTION = 0x06, + SCSI_SENSE_DATA_PROTECTION = 0x07, + SCSI_SENSE_BLANK_CHECK = 0x08, + SCSI_SENSE_VENDOR_SPECIFIC = 0x09, + SCSI_SENSE_COPY_ABORTED = 0x0a, + SCSI_SENSE_COMMAND_ABORTED = 0x0b, + SCSI_SENSE_OBSOLETE_ERROR_CODE = 0x0c, + SCSI_SENSE_OVERFLOW_COMMAND = 0x0d, + SCSI_SENSE_MISCOMPARE = 0x0e +}; + +const char *scsi_sense_key_str(int key); + +/* ascq */ +#define SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB 0x2400 +#define SCSI_SENSE_ASCQ_LOGICAL_UNIT_NOT_SUPPORTED 0x2500 +#define SCSI_SENSE_ASCQ_BUS_RESET 0x2900 + +const char *scsi_sense_ascq_str(int ascq); + + +enum scsi_xfer_dir { + SCSI_XFER_NONE = 0, + SCSI_XFER_READ = 1, + SCSI_XFER_WRITE = 2 +}; + +struct scsi_reportluns_params { + int report_type; +}; +struct scsi_readcapacity10_params { + int lba; + int pmi; +}; +struct scsi_inquiry_params { + int evpd; + int page_code; +}; +struct scsi_modesense6_params { + int dbd; + int pc; + int page_code; + int sub_page_code; +}; + +struct scsi_sense { + unsigned char error_type; + enum scsi_sense_key key; + int ascq; +}; + +struct scsi_data { + int size; + unsigned char *data; +}; + +struct scsi_allocated_memory { + struct scsi_allocated_memory *next; + void *ptr; +}; + +struct scsi_task { + int status; + + int cdb_size; + int xfer_dir; + int expxferlen; + unsigned char cdb[SCSI_CDB_MAX_SIZE]; + union { + struct scsi_readcapacity10_params readcapacity10; + struct scsi_reportluns_params reportluns; + struct scsi_inquiry_params inquiry; + struct scsi_modesense6_params modesense6; + } params; + + struct scsi_sense sense; + struct scsi_data datain; + struct scsi_allocated_memory *mem; + + void *ptr; +}; + +void scsi_free_scsi_task(struct scsi_task *task); +void scsi_set_task_private_ptr(struct scsi_task *task, void *ptr); +void *scsi_get_task_private_ptr(struct scsi_task *task); + +/* + * TESTUNITREADY + */ +struct scsi_task *scsi_cdb_testunitready(void); + + +/* + * REPORTLUNS + */ +#define SCSI_REPORTLUNS_REPORT_ALL_LUNS 0x00 +#define SCSI_REPORTLUNS_REPORT_WELL_KNOWN_ONLY 0x01 +#define SCSI_REPORTLUNS_REPORT_AVAILABLE_LUNS_ONLY 0x02 + +struct scsi_reportluns_list { + uint32_t num; + uint16_t luns[0]; +}; + +struct scsi_task *scsi_reportluns_cdb(int report_type, int alloc_len); + +/* + * READCAPACITY10 + */ +struct scsi_readcapacity10 { + uint32_t lba; + uint32_t block_size; +}; +struct scsi_task *scsi_cdb_readcapacity10(int lba, int pmi); + + +/* + * INQUIRY + */ +enum scsi_inquiry_peripheral_qualifier { + SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED = 0x00, + SCSI_INQUIRY_PERIPHERAL_QUALIFIER_DISCONNECTED = 0x01, + SCSI_INQUIRY_PERIPHERAL_QUALIFIER_NOT_SUPPORTED = 0x03 +}; + +const char *scsi_devqualifier_to_str( + enum scsi_inquiry_peripheral_qualifier qualifier); + +enum scsi_inquiry_peripheral_device_type { + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS = 0x00, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SEQUENTIAL_ACCESS = 0x01, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_PRINTER = 0x02, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_PROCESSOR = 0x03, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_WRITE_ONCE = 0x04, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_MMC = 0x05, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SCANNER = 0x06, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OPTICAL_MEMORY = 0x07, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_MEDIA_CHANGER = 0x08, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_COMMUNICATIONS = 0x09, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_STORAGE_ARRAY_CONTROLLER = 0x0c, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_ENCLOSURE_SERVICES = 0x0d, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SIMPLIFIED_DIRECT_ACCESS = 0x0e, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OPTICAL_CARD_READER = 0x0f, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_BRIDGE_CONTROLLER = 0x10, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OSD = 0x11, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_AUTOMATION = 0x12, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SEQURITY_MANAGER = 0x13, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_WELL_KNOWN_LUN = 0x1e, + SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_UNKNOWN = 0x1f +}; + +const char *scsi_devtype_to_str(enum scsi_inquiry_peripheral_device_type type); + +enum scsi_version { + SCSI_VERSION_SPC = 0x03, + SCSI_VERSION_SPC2 = 0x04, + SCSI_VERSION_SPC3 = 0x05 +}; + +const char *scsi_version_to_str(enum scsi_version version); + +enum scsi_inquiry_tpgs { + SCSI_INQUIRY_TPGS_NO_SUPPORT = 0x00, + SCSI_INQUIRY_TPGS_IMPLICIT = 0x01, + SCSI_INQUIRY_TPGS_EXPLICIT = 0x02, + SCSI_INQUIRY_TPGS_IMPLICIT_AND_EXPLICIT = 0x03 +}; + +struct scsi_inquiry_standard { + enum scsi_inquiry_peripheral_qualifier periperal_qualifier; + enum scsi_inquiry_peripheral_device_type periperal_device_type; + int rmb; + int version; + int normaca; + int hisup; + int response_data_format; + + int sccs; + int acc; + int tpgs; + int threepc; + int protect; + + int encserv; + int multip; + int addr16; + int wbus16; + int sync; + int cmdque; + + int clocking; + int qas; + int ius; + + char vendor_identification[8+1]; + char product_identification[16+1]; + char product_revision_level[4+1]; +}; + +enum scsi_inquiry_pagecode { + SCSI_INQUIRY_PAGECODE_SUPPORTED_VPD_PAGES = 0x00, + SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER = 0x80, + SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION = 0x83, + SCSI_INQUIRY_PAGECODE_BLOCK_DEVICE_CHARACTERISTICS = 0xB1 +}; + +const char *scsi_inquiry_pagecode_to_str(int pagecode); + +struct scsi_inquiry_supported_pages { + enum scsi_inquiry_peripheral_qualifier periperal_qualifier; + enum scsi_inquiry_peripheral_device_type periperal_device_type; + enum scsi_inquiry_pagecode pagecode; + + int num_pages; + unsigned char *pages; +}; + +struct scsi_inquiry_block_device_characteristics { + enum scsi_inquiry_peripheral_qualifier periperal_qualifier; + enum scsi_inquiry_peripheral_device_type periperal_device_type; + enum scsi_inquiry_pagecode pagecode; + + int medium_rotation_rate; +}; + +struct scsi_task *scsi_cdb_inquiry(int evpd, int page_code, int alloc_len); + +struct scsi_inquiry_unit_serial_number { + enum scsi_inquiry_peripheral_qualifier periperal_qualifier; + enum scsi_inquiry_peripheral_device_type periperal_device_type; + enum scsi_inquiry_pagecode pagecode; + + char *usn; +}; + +enum scsi_protocol_identifier { + SCSI_PROTOCOL_IDENTIFIER_FIBRE_CHANNEL = 0x00, + SCSI_PROTOCOL_IDENTIFIER_PARALLEL_SCSI = 0x01, + SCSI_PROTOCOL_IDENTIFIER_SSA = 0x02, + SCSI_PROTOCOL_IDENTIFIER_IEEE_1394 = 0x03, + SCSI_PROTOCOL_IDENTIFIER_RDMA = 0x04, + SCSI_PROTOCOL_IDENTIFIER_ISCSI = 0x05, + SCSI_PROTOCOL_IDENTIFIER_SAS = 0x06, + SCSI_PROTOCOL_IDENTIFIER_ADT = 0x07, + SCSI_PROTOCOL_IDENTIFIER_ATA = 0x08 +}; + +const char *scsi_protocol_identifier_to_str(int identifier); + +enum scsi_codeset { + SCSI_CODESET_BINARY = 0x01, + SCSI_CODESET_ASCII = 0x02, + SCSI_CODESET_UTF8 = 0x03 +}; + +const char *scsi_codeset_to_str(int codeset); + +enum scsi_association { + SCSI_ASSOCIATION_LOGICAL_UNIT = 0x00, + SCSI_ASSOCIATION_TARGET_PORT = 0x01, + SCSI_ASSOCIATION_TARGET_DEVICE = 0x02 +}; + +const char *scsi_association_to_str(int association); + +enum scsi_designator_type { + SCSI_DESIGNATOR_TYPE_VENDOR_SPECIFIC = 0x00, + SCSI_DESIGNATOR_TYPE_T10_VENDORT_ID = 0x01, + SCSI_DESIGNATOR_TYPE_EUI_64 = 0x02, + SCSI_DESIGNATOR_TYPE_NAA = 0x03, + SCSI_DESIGNATOR_TYPE_RELATIVE_TARGET_PORT = 0x04, + SCSI_DESIGNATOR_TYPE_TARGET_PORT_GROUP = 0x05, + SCSI_DESIGNATOR_TYPE_LOGICAL_UNIT_GROUP = 0x06, + SCSI_DESIGNATOR_TYPE_MD5_LOGICAL_UNIT_IDENTIFIER = 0x07, + SCSI_DESIGNATOR_TYPE_SCSI_NAME_STRING = 0x08 +}; + +const char *scsi_designator_type_to_str(int association); + +struct scsi_inquiry_device_designator { + struct scsi_inquiry_device_designator *next; + + enum scsi_protocol_identifier protocol_identifier; + enum scsi_codeset code_set; + int piv; + enum scsi_association association; + enum scsi_designator_type designator_type; + int designator_length; + char *designator; +}; + +struct scsi_inquiry_device_identification { + enum scsi_inquiry_peripheral_qualifier periperal_qualifier; + enum scsi_inquiry_peripheral_device_type periperal_device_type; + enum scsi_inquiry_pagecode pagecode; + + struct scsi_inquiry_device_designator *designators; +}; + +/* + * MODESENSE6 + */ +enum scsi_modesense_page_control { + SCSI_MODESENSE_PC_CURRENT = 0x00, + SCSI_MODESENSE_PC_CHANGEABLE = 0x01, + SCSI_MODESENSE_PC_DEFAULT = 0x02, + SCSI_MODESENSE_PC_SAVED = 0x03 +}; + +enum scsi_modesense_page_code { + SCSI_MODESENSE_PAGECODE_RETURN_ALL_PAGES = 0x3f +}; + +struct scsi_task *scsi_cdb_modesense6(int dbd, + enum scsi_modesense_page_control pc, + enum scsi_modesense_page_code page_code, + int sub_page_code, + unsigned char alloc_len); + + + + +int scsi_datain_getfullsize(struct scsi_task *task); +void *scsi_datain_unmarshall(struct scsi_task *task); + +struct scsi_task *scsi_cdb_read10(int lba, int xferlen, int blocksize); +struct scsi_task *scsi_cdb_write10(int lba, int xferlen, int fua, int fuanv, + int blocksize); + +struct scsi_task *scsi_cdb_synchronizecache10(int lba, int num_blocks, + int syncnv, int immed); diff --git a/include/slist.h b/include/slist.h new file mode 100644 index 0000000..14a0127 --- /dev/null +++ b/include/slist.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#define SLIST_ADD(list, item) \ + do { \ + (item)->next = (*list); \ + (*list) = (item); \ + } while (0); + +#define SLIST_ADD_END(list, item) \ + if ((*list) == NULL) { \ + SLIST_ADD((list), (item)); \ + } else { \ + void *head = (*list); \ + while ((*list)->next) \ + (*list) = (*list)->next; \ + (*list)->next = (item); \ + (item)->next = NULL; \ + (*list) = head; \ + } + +#define SLIST_REMOVE(list, item) \ + if ((*list) == (item)) { \ + (*list) = (item)->next; \ + } else { \ + void *head = (*list); \ + while ((*list)->next && (*list)->next != (item)) \ + (*list) = (*list)->next; \ + if ((*list)->next != NULL) { \ + (*list)->next = (*list)->next->next; \ + } \ + (*list) = head; \ + } + + + + diff --git a/lib/connect.c b/lib/connect.c new file mode 100644 index 0000000..0705046 --- /dev/null +++ b/lib/connect.c @@ -0,0 +1,127 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "scsi-lowlevel.h" + +struct connect_task { + iscsi_command_cb cb; + void *private_data; + int lun; +}; + +static void +iscsi_testunitready_cb(struct iscsi_context *iscsi, int status, + void *command_data, void *private_data) +{ + struct connect_task *ct = private_data; + + if (status != 0) { + struct scsi_task *scsi = command_data; + + if (scsi->sense.key == SCSI_SENSE_UNIT_ATTENTION + && scsi->sense.ascq == SCSI_SENSE_ASCQ_BUS_RESET) { + /* This is just the normal unitattention/busreset + * you always get just after a fresh login. Try + * again. + */ + if (iscsi_testunitready_async(iscsi, ct->lun, + iscsi_testunitready_cb, + ct) != 0) { + iscsi_set_error(iscsi, "iscsi_testunitready " + "failed."); + ct->cb(iscsi, SCSI_STATUS_ERROR, NULL, + ct->private_data); + free(ct); + } + return; + } + } + + ct->cb(iscsi, status?SCSI_STATUS_ERROR:SCSI_STATUS_GOOD, NULL, + ct->private_data); + free(ct); +} + +static void +iscsi_login_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, + void *private_data) +{ + struct connect_task *ct = private_data; + + if (status != 0) { + iscsi_set_error(iscsi, "Failed to login to iSCSI target. " + "%s", iscsi_get_error(iscsi)); + ct->cb(iscsi, SCSI_STATUS_ERROR, NULL, ct->private_data); + free(ct); + return; + } + + if (iscsi_testunitready_async(iscsi, ct->lun, + iscsi_testunitready_cb, ct) != 0) { + iscsi_set_error(iscsi, "iscsi_testunitready_async failed."); + ct->cb(iscsi, SCSI_STATUS_ERROR, NULL, ct->private_data); + free(ct); + } +} + +static void +iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, + void *private_data) +{ + struct connect_task *ct = private_data; + + if (status != 0) { + iscsi_set_error(iscsi, "Failed to connect to iSCSI socket. " + "%s", iscsi_get_error(iscsi)); + ct->cb(iscsi, SCSI_STATUS_ERROR, NULL, ct->private_data); + free(ct); + return; + } + + if (iscsi_login_async(iscsi, iscsi_login_cb, ct) != 0) { + iscsi_set_error(iscsi, "iscsi_login_async failed."); + ct->cb(iscsi, SCSI_STATUS_ERROR, NULL, ct->private_data); + free(ct); + } +} + + +int +iscsi_full_connect_async(struct iscsi_context *iscsi, const char *portal, + int lun, iscsi_command_cb cb, void *private_data) +{ + struct connect_task *ct; + + ct = malloc(sizeof(struct connect_task)); + if (ct == NULL) { + iscsi_set_error(iscsi, "Out-of-memory. Failed to allocate " + "connect_task structure."); + return -ENOMEM; + } + ct->cb = cb; + ct->lun = lun; + ct->private_data = private_data; + if (iscsi_connect_async(iscsi, portal, iscsi_connect_cb, ct) != 0) { + free(ct); + return -ENOMEM; + } + return 0; +} diff --git a/lib/crc32c.c b/lib/crc32c.c new file mode 100644 index 0000000..3621e30 --- /dev/null +++ b/lib/crc32c.c @@ -0,0 +1,99 @@ +#include "iscsi.h" +#include "iscsi-private.h" + +/*****************************************************************/ +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +/*****************************************************************/ + +static unsigned long crctable[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L +}; + +unsigned long crc32c(char *buf, int len) +{ + unsigned long crc = 0xffffffff; + while (len-- > 0) { + crc = (crc>>8) ^ crctable[(crc ^ (*buf++)) & 0xFF]; + } + return crc^0xffffffff; +} + diff --git a/lib/discovery.c b/lib/discovery.c new file mode 100644 index 0000000..7c6b2b6 --- /dev/null +++ b/lib/discovery.c @@ -0,0 +1,189 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" + +int +iscsi_discovery_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data) +{ + struct iscsi_pdu *pdu; + char *str; + + if (iscsi->session_type != ISCSI_SESSION_DISCOVERY) { + iscsi_set_error(iscsi, "Trying to do discovery on " + "non-discovery session."); + return -1; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_TEXT_REQUEST, + ISCSI_PDU_TEXT_RESPONSE); + if (pdu == NULL) { + iscsi_set_error(iscsi, "Out-of-memory: Failed to allocate " + "text pdu."); + return -1; + } + + /* immediate */ + iscsi_pdu_set_immediate(pdu); + + /* flags */ + iscsi_pdu_set_pduflags(pdu, ISCSI_PDU_TEXT_FINAL); + + /* target transfer tag */ + iscsi_pdu_set_ttt(pdu, 0xffffffff); + + /* sendtargets */ + str = (char *)"SendTargets=All"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + pdu->callback = cb; + pdu->private_data = private_data; + + if (iscsi_queue_pdu(iscsi, pdu) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: failed to queue iscsi " + "text pdu."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + return 0; +} + +static void +iscsi_free_discovery_addresses(struct iscsi_discovery_address *addresses) +{ + while (addresses != NULL) { + struct iscsi_discovery_address *next = addresses->next; + + free(discard_const(addresses->target_name)); + addresses->target_name = NULL; + + free(discard_const(addresses->target_address)); + addresses->target_address = NULL; + + addresses->next = NULL; + free(addresses); + addresses = next; + } +} + +int +iscsi_process_text_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, + const unsigned char *hdr, int size) +{ + struct iscsi_discovery_address *targets = NULL; + + /* verify the response looks sane */ + if (hdr[1] != ISCSI_PDU_TEXT_FINAL) { + iscsi_set_error(iscsi, "unsupported flags in text " + "reply %02x", hdr[1]); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + return -1; + } + + /* skip past the header */ + hdr += ISCSI_HEADER_SIZE; + size -= ISCSI_HEADER_SIZE; + + while (size > 0) { + int len; + + len = strlen((char *)hdr); + + if (len == 0) { + break; + } + + if (len > size) { + iscsi_set_error(iscsi, "len > size when parsing " + "discovery data %d>%d", len, size); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + iscsi_free_discovery_addresses(targets); + return -1; + } + + /* parse the strings */ + if (!strncmp((char *)hdr, "TargetName=", 11)) { + struct iscsi_discovery_address *target; + + target = malloc(sizeof(struct iscsi_discovery_address)); + if (target == NULL) { + iscsi_set_error(iscsi, "Failed to allocate " + "data for new discovered " + "target"); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + iscsi_free_discovery_addresses(targets); + return -1; + } + bzero(target, sizeof(struct iscsi_discovery_address)); + target->target_name = strdup((char *)hdr+11); + if (target->target_name == NULL) { + iscsi_set_error(iscsi, "Failed to allocate " + "data for new discovered " + "target name"); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + free(target); + target = NULL; + iscsi_free_discovery_addresses(targets); + return -1; + } + target->next = targets; + targets = target; + } else if (!strncmp((char *)hdr, "TargetAddress=", 14)) { + targets->target_address = strdup((char *)hdr+14); + if (targets->target_address == NULL) { + iscsi_set_error(iscsi, "Failed to allocate " + "data for new discovered " + "target address"); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + iscsi_free_discovery_addresses(targets); + return -1; + } + } else { + iscsi_set_error(iscsi, "Dont know how to handle " + "discovery string : %s", hdr); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + iscsi_free_discovery_addresses(targets); + return -1; + } + + hdr += len + 1; + size -= len + 1; + } + + pdu->callback(iscsi, SCSI_STATUS_GOOD, targets, pdu->private_data); + iscsi_free_discovery_addresses(targets); + + return 0; +} diff --git a/lib/init.c b/lib/init.c new file mode 100644 index 0000000..5a3ab05 --- /dev/null +++ b/lib/init.c @@ -0,0 +1,207 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "slist.h" + + +struct iscsi_context * +iscsi_create_context(const char *initiator_name) +{ + struct iscsi_context *iscsi; + + iscsi = malloc(sizeof(struct iscsi_context)); + if (iscsi == NULL) { + return NULL; + } + + bzero(iscsi, sizeof(struct iscsi_context)); + + iscsi->initiator_name = strdup(initiator_name); + if (iscsi->initiator_name == NULL) { + free(iscsi); + return NULL; + } + + iscsi->fd = -1; + + /* initialize to a "random" isid */ + iscsi_set_isid_random(iscsi, getpid() ^ time(NULL)); + + return iscsi; +} + +int +iscsi_set_isid_random(struct iscsi_context *iscsi, int rnd) +{ + iscsi->isid[0] = 0x80; + iscsi->isid[1] = rnd&0xff; + iscsi->isid[2] = rnd&0xff; + iscsi->isid[3] = rnd&0xff; + iscsi->isid[4] = 0; + iscsi->isid[5] = 0; + + return 0; +} + +int +iscsi_set_alias(struct iscsi_context *iscsi, const char *alias) +{ + if (iscsi->is_loggedin != 0) { + iscsi_set_error(iscsi, "Already logged in when adding alias"); + return -1; + } + + free(discard_const(iscsi->alias)); + + iscsi->alias = strdup(alias); + if (iscsi->alias == NULL) { + iscsi_set_error(iscsi, "Failed to allocate alias name"); + return -1; + } + + return 0; +} + +int +iscsi_set_targetname(struct iscsi_context *iscsi, const char *target_name) +{ + if (iscsi->is_loggedin != 0) { + iscsi_set_error(iscsi, "Already logged in when adding " + "targetname"); + return -1; + } + + free(discard_const(iscsi->target_name)); + + iscsi->target_name = strdup(target_name); + if (iscsi->target_name == NULL) { + iscsi_set_error(iscsi, "Failed to allocate target name"); + return -1; + } + + return 0; +} + +int +iscsi_destroy_context(struct iscsi_context *iscsi) +{ + struct iscsi_pdu *pdu; + + if (iscsi == NULL) { + return 0; + } + + if (iscsi->fd != -1) { + iscsi_disconnect(iscsi); + } + + while ((pdu = iscsi->outqueue)) { + SLIST_REMOVE(&iscsi->outqueue, pdu); + pdu->callback(iscsi, SCSI_STATUS_CANCELLED, NULL, + pdu->private_data); + iscsi_free_pdu(iscsi, pdu); + } + while ((pdu = iscsi->waitpdu)) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + pdu->callback(iscsi, SCSI_STATUS_CANCELLED, NULL, + pdu->private_data); + iscsi_free_pdu(iscsi, pdu); + } + + free(discard_const(iscsi->initiator_name)); + iscsi->initiator_name = NULL; + + free(discard_const(iscsi->target_name)); + iscsi->target_name = NULL; + + free(discard_const(iscsi->alias)); + iscsi->alias = NULL; + + if (iscsi->inbuf != NULL) { + free(iscsi->inbuf); + iscsi->inbuf = NULL; + iscsi->insize = 0; + iscsi->inpos = 0; + } + + free(iscsi->error_string); + iscsi->error_string = NULL; + + free(iscsi); + + return 0; +} + + + +void +iscsi_set_error(struct iscsi_context *iscsi, const char *error_string, ...) +{ + va_list ap; + char *str; + + va_start(ap, error_string); + if (vasprintf(&str, error_string, ap) < 0) { + /* not much we can do here */ + str = NULL; + } + + free(iscsi->error_string); + + iscsi->error_string = str; + va_end(ap); +} + + +const char * +iscsi_get_error(struct iscsi_context *iscsi) +{ + return iscsi->error_string; +} + +int +iscsi_set_header_digest(struct iscsi_context *iscsi, + enum iscsi_header_digest header_digest) +{ + if (iscsi->is_loggedin) { + iscsi_set_error(iscsi, "trying to set header digest while " + "logged in"); + return -1; + } + + iscsi->want_header_digest = header_digest; + + return 0; +} + +int +iscsi_is_logged_in(struct iscsi_context *iscsi) +{ + return iscsi->is_loggedin; +} diff --git a/lib/login.c b/lib/login.c new file mode 100644 index 0000000..7b91ad8 --- /dev/null +++ b/lib/login.c @@ -0,0 +1,378 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" + +int +iscsi_login_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data) +{ + struct iscsi_pdu *pdu; + char *str; + + if (iscsi->is_loggedin != 0) { + iscsi_set_error(iscsi, "Trying to login while already logged " + "in."); + return -1; + } + + switch (iscsi->session_type) { + case ISCSI_SESSION_DISCOVERY: + case ISCSI_SESSION_NORMAL: + break; + default: + iscsi_set_error(iscsi, "trying to login without setting " + "session type."); + return -1; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_LOGIN_REQUEST, + ISCSI_PDU_LOGIN_RESPONSE); + if (pdu == NULL) { + iscsi_set_error(iscsi, "Out-of-memory: Failed to allocate " + "login pdu."); + return -1; + } + + /* login request */ + iscsi_pdu_set_immediate(pdu); + + /* flags */ + iscsi_pdu_set_pduflags(pdu, ISCSI_PDU_LOGIN_TRANSIT + | ISCSI_PDU_LOGIN_CSG_OPNEG + | ISCSI_PDU_LOGIN_NSG_FF); + + + /* initiator name */ + if (iscsi_pdu_add_data(iscsi, pdu, + (unsigned char *)"InitiatorName=", + 14) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + if (iscsi_pdu_add_data(iscsi, pdu, + (unsigned char *)iscsi->initiator_name, + strlen(iscsi->initiator_name) +1) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + /* optional alias */ + if (iscsi->alias) { + if (iscsi_pdu_add_data(iscsi, pdu, + (unsigned char *)"InitiatorAlias=", + 15) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + if (iscsi_pdu_add_data(iscsi, pdu, + (unsigned char *)iscsi->alias, + strlen(iscsi->alias) +1) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + } + + /* target name */ + if (iscsi->session_type == ISCSI_SESSION_NORMAL) { + if (iscsi->target_name == NULL) { + iscsi_set_error(iscsi, "Trying normal connect but " + "target name not set."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + if (iscsi_pdu_add_data(iscsi, pdu, + (unsigned char *)"TargetName=", + 11) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + if (iscsi_pdu_add_data(iscsi, pdu, + (unsigned char *)iscsi->target_name, + strlen(iscsi->target_name) +1) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + } + + /* session type */ + switch (iscsi->session_type) { + case ISCSI_SESSION_DISCOVERY: + str = (char *)"SessionType=Discovery"; + break; + case ISCSI_SESSION_NORMAL: + str = (char *)"SessionType=Normal"; + break; + default: + iscsi_set_error(iscsi, "Can not handle sessions %d yet.", + iscsi->session_type); + return -1; + } + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + switch (iscsi->want_header_digest) { + case ISCSI_HEADER_DIGEST_NONE: + str = (char *)"HeaderDigest=None"; + break; + case ISCSI_HEADER_DIGEST_NONE_CRC32C: + str = (char *)"HeaderDigest=None,CRC32C"; + break; + case ISCSI_HEADER_DIGEST_CRC32C_NONE: + str = (char *)"HeaderDigest=CRC32C,None"; + break; + case ISCSI_HEADER_DIGEST_CRC32C: + str = (char *)"HeaderDigest=CRC32C"; + break; + } + + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"DataDigest=None"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"InitialR2T=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"ImmediateData=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"MaxBurstLength=262144"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"FirstBurstLength=262144"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"MaxRecvDataSegmentLength=262144"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"DataPDUInOrder=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + str = (char *)"DataSequenceInOrder=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) + != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + + pdu->callback = cb; + pdu->private_data = private_data; + + if (iscsi_queue_pdu(iscsi, pdu) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: failed to queue iscsi " + "pdu."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + return 0; +} + +int +iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, + const unsigned char *hdr, int size) +{ + int status; + + if (size < ISCSI_HEADER_SIZE) { + iscsi_set_error(iscsi, "dont have enough data to read status " + "from login reply"); + return -1; + } + + status = ntohs(*(uint16_t *)&hdr[36]); + if (status != 0) { + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + return 0; + } + + iscsi->statsn = ntohs(*(uint16_t *)&hdr[24]); + + /* XXX here we should parse the data returned in case the target + * renegotiated some some parameters. + * we should also do proper handshaking if the target is not yet + * prepared to transition to the next stage + */ + /* skip past the header */ + hdr += ISCSI_HEADER_SIZE; + size -= ISCSI_HEADER_SIZE; + + while (size > 0) { + int len; + + len = strlen((char *)hdr); + + if (len == 0) { + break; + } + + if (len > size) { + iscsi_set_error(iscsi, "len > size when parsing " + "login data %d>%d", len, size); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + return -1; + } + + /* parse the strings */ + if (!strncmp((char *)hdr, "HeaderDigest=", 13)) { + if (!strcmp((char *)hdr + 13, "CRC32C")) { + iscsi->header_digest + = ISCSI_HEADER_DIGEST_CRC32C; + } else { + iscsi->header_digest + = ISCSI_HEADER_DIGEST_NONE; + } + } + + hdr += len + 1; + size -= len + 1; + } + + + iscsi->is_loggedin = 1; + pdu->callback(iscsi, SCSI_STATUS_GOOD, NULL, pdu->private_data); + + return 0; +} + + +int +iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data) +{ + struct iscsi_pdu *pdu; + + if (iscsi->is_loggedin == 0) { + iscsi_set_error(iscsi, "Trying to logout while not logged in."); + return -1; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_LOGOUT_REQUEST, + ISCSI_PDU_LOGOUT_RESPONSE); + if (pdu == NULL) { + iscsi_set_error(iscsi, "Out-of-memory: Failed to allocate " + "logout pdu."); + return -1; + } + + /* logout request has the immediate flag set */ + iscsi_pdu_set_immediate(pdu); + + /* flags : close the session */ + iscsi_pdu_set_pduflags(pdu, 0x80); + + + pdu->callback = cb; + pdu->private_data = private_data; + + if (iscsi_queue_pdu(iscsi, pdu) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: failed to queue iscsi " + "logout pdu."); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + return 0; +} + +int +iscsi_process_logout_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, +const unsigned char *hdr _U_, int size _U_) +{ + iscsi->is_loggedin = 0; + pdu->callback(iscsi, SCSI_STATUS_GOOD, NULL, pdu->private_data); + + return 0; +} + +int +iscsi_set_session_type(struct iscsi_context *iscsi, + enum iscsi_session_type session_type) +{ + if (iscsi->is_loggedin) { + iscsi_set_error(iscsi, "trying to set session type while " + "logged in"); + return -1; + } + + iscsi->session_type = session_type; + + return 0; +} diff --git a/lib/nop.c b/lib/nop.c new file mode 100644 index 0000000..010b742 --- /dev/null +++ b/lib/nop.c @@ -0,0 +1,90 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include "iscsi.h" +#include "iscsi-private.h" + +int +iscsi_nop_out_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + unsigned char *data, int len, void *private_data) +{ + struct iscsi_pdu *pdu; + + if (iscsi->is_loggedin == 0) { + iscsi_set_error(iscsi, "trying send nop-out while not logged " + "in"); + return -1; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_NOP_OUT, ISCSI_PDU_NOP_IN); + if (pdu == NULL) { + iscsi_set_error(iscsi, "Failed to allocate nop-out pdu"); + return -1; + } + + /* immediate flag */ + iscsi_pdu_set_immediate(pdu); + + /* flags */ + iscsi_pdu_set_pduflags(pdu, 0x80); + + /* ttt */ + iscsi_pdu_set_ttt(pdu, 0xffffffff); + + /* lun */ + iscsi_pdu_set_lun(pdu, 2); + + /* cmdsn is not increased if Immediate delivery*/ + iscsi_pdu_set_cmdsn(pdu, iscsi->cmdsn); + pdu->cmdsn = iscsi->cmdsn; + + pdu->callback = cb; + pdu->private_data = private_data; + + if (iscsi_pdu_add_data(iscsi, pdu, data, len) != 0) { + iscsi_set_error(iscsi, "Failed to add outdata to nop-out"); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + if (iscsi_queue_pdu(iscsi, pdu) != 0) { + iscsi_set_error(iscsi, "failed to queue iscsi nop-out pdu"); + iscsi_free_pdu(iscsi, pdu); + return -1; + } + + return 0; +} + +int +iscsi_process_nop_out_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, + const unsigned char *hdr, int size) +{ + struct iscsi_data data; + + data.data = NULL; + data.size = 0; + + if (size > ISCSI_HEADER_SIZE) { + data.data = discard_const(&hdr[ISCSI_HEADER_SIZE]); + data.size = size - ISCSI_HEADER_SIZE; + } + pdu->callback(iscsi, SCSI_STATUS_GOOD, &data, pdu->private_data); + + return 0; +} diff --git a/lib/pdu.c b/lib/pdu.c new file mode 100644 index 0000000..7388128 --- /dev/null +++ b/lib/pdu.c @@ -0,0 +1,336 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "scsi-lowlevel.h" +#include "slist.h" + +struct iscsi_pdu * +iscsi_allocate_pdu(struct iscsi_context *iscsi, enum iscsi_opcode opcode, + enum iscsi_opcode response_opcode) +{ + struct iscsi_pdu *pdu; + + pdu = malloc(sizeof(struct iscsi_pdu)); + if (pdu == NULL) { + iscsi_set_error(iscsi, "failed to allocate pdu"); + return NULL; + } + bzero(pdu, sizeof(struct iscsi_pdu)); + + pdu->outdata.size = ISCSI_HEADER_SIZE; + pdu->outdata.data = malloc(pdu->outdata.size); + + if (pdu->outdata.data == NULL) { + iscsi_set_error(iscsi, "failed to allocate pdu header"); + free(pdu); + return NULL; + } + bzero(pdu->outdata.data, pdu->outdata.size); + + /* opcode */ + pdu->outdata.data[0] = opcode; + pdu->response_opcode = response_opcode; + + /* isid */ + if (opcode == ISCSI_PDU_LOGIN_REQUEST) { + memcpy(&pdu->outdata.data[8], &iscsi->isid[0], 6); + } + + /* itt */ + *(uint32_t *)&pdu->outdata.data[16] = htonl(iscsi->itt); + pdu->itt = iscsi->itt; + + iscsi->itt++; + + return pdu; +} + +void +iscsi_free_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) +{ + if (pdu == NULL) { + iscsi_set_error(iscsi, "trying to free NULL pdu"); + return; + } + + free(pdu->outdata.data); + pdu->outdata.data = NULL; + + free(pdu->indata.data); + pdu->indata.data = NULL; + + if (pdu->scsi_cbdata) { + iscsi_free_scsi_cbdata(pdu->scsi_cbdata); + pdu->scsi_cbdata = NULL; + } + + free(pdu); +} + + +int +iscsi_add_data(struct iscsi_context *iscsi, struct iscsi_data *data, + unsigned char *dptr, int dsize, int pdualignment) +{ + int len, aligned; + unsigned char *buf; + + if (dsize == 0) { + iscsi_set_error(iscsi, "Trying to append zero size data to " + "iscsi_data"); + return -1; + } + + len = data->size + dsize; + aligned = len; + if (pdualignment) { + aligned = (aligned+3)&0xfffffffc; + } + buf = malloc(aligned); + if (buf == NULL) { + iscsi_set_error(iscsi, "failed to allocate buffer for %d " + "bytes", len); + return -1; + } + + if (data->size > 0) { + memcpy(buf, data->data, data->size); + } + memcpy(buf + data->size, dptr, dsize); + if (len != aligned) { + /* zero out any padding at the end */ + bzero(buf+len, aligned-len); + } + + free(data->data); + + data->data = buf; + data->size = len; + + return 0; +} + +int +iscsi_pdu_add_data(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, + unsigned char *dptr, int dsize) +{ + if (pdu == NULL) { + iscsi_set_error(iscsi, "trying to add data to NULL pdu"); + return -1; + } + if (dsize == 0) { + iscsi_set_error(iscsi, "Trying to append zero size data to " + "pdu"); + return -1; + } + + if (iscsi_add_data(iscsi, &pdu->outdata, dptr, dsize, 1) != 0) { + iscsi_set_error(iscsi, "failed to add data to pdu buffer"); + return -1; + } + + /* update data segment length */ + *(uint32_t *)&pdu->outdata.data[4] = htonl(pdu->outdata.size + - ISCSI_HEADER_SIZE); + + return 0; +} + +int +iscsi_get_pdu_size(struct iscsi_context *iscsi, const unsigned char *hdr) +{ + int size; + + size = (ntohl(*(uint32_t *)&hdr[4])&0x00ffffff) + ISCSI_HEADER_SIZE; + size = (size+3)&0xfffffffc; + + return size; +} + + +int +iscsi_process_pdu(struct iscsi_context *iscsi, const unsigned char *hdr, + int size) +{ + uint32_t itt; + enum iscsi_opcode opcode; + struct iscsi_pdu *pdu; + uint8_t ahslen; + + opcode = hdr[0] & 0x3f; + ahslen = hdr[4]; + itt = ntohl(*(uint32_t *)&hdr[16]); + + if (ahslen != 0) { + iscsi_set_error(iscsi, "cant handle expanded headers yet"); + return -1; + } + + for (pdu = iscsi->waitpdu; pdu; pdu = pdu->next) { + enum iscsi_opcode expected_response = pdu->response_opcode; + int is_finished = 1; + + if (pdu->itt != itt) { + continue; + } + + /* we have a special case with scsi-command opcodes, + * they are replied to by either a scsi-response + * or a data-in, or a combination of both. + */ + if (opcode == ISCSI_PDU_DATA_IN + && expected_response == ISCSI_PDU_SCSI_RESPONSE) { + expected_response = ISCSI_PDU_DATA_IN; + } + + if (opcode != expected_response) { + iscsi_set_error(iscsi, "Got wrong opcode back for " + "itt:%d got:%d expected %d", + itt, opcode, pdu->response_opcode); + return -1; + } + switch (opcode) { + case ISCSI_PDU_LOGIN_RESPONSE: + if (iscsi_process_login_reply(iscsi, pdu, hdr, size) + != 0) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + iscsi_set_error(iscsi, "iscsi login reply " + "failed"); + return -1; + } + break; + case ISCSI_PDU_TEXT_RESPONSE: + if (iscsi_process_text_reply(iscsi, pdu, hdr, size) + != 0) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + iscsi_set_error(iscsi, "iscsi text reply " + "failed"); + return -1; + } + break; + case ISCSI_PDU_LOGOUT_RESPONSE: + if (iscsi_process_logout_reply(iscsi, pdu, hdr, size) + != 0) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + iscsi_set_error(iscsi, "iscsi logout reply " + "failed"); + return -1; + } + break; + case ISCSI_PDU_SCSI_RESPONSE: + if (iscsi_process_scsi_reply(iscsi, pdu, hdr, size) + != 0) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + iscsi_set_error(iscsi, "iscsi response reply " + "failed"); + return -1; + } + break; + case ISCSI_PDU_DATA_IN: + if (iscsi_process_scsi_data_in(iscsi, pdu, hdr, size, + &is_finished) != 0) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + iscsi_set_error(iscsi, "iscsi data in " + "failed"); + return -1; + } + break; + case ISCSI_PDU_NOP_IN: + if (iscsi_process_nop_out_reply(iscsi, pdu, hdr, size) + != 0) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + iscsi_set_error(iscsi, "iscsi nop-in failed"); + return -1; + } + break; + default: + iscsi_set_error(iscsi, "Dont know how to handle " + "opcode %d", opcode); + return -1; + } + + if (is_finished) { + SLIST_REMOVE(&iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + } + return 0; + } + + return 0; +} + +void +iscsi_pdu_set_pduflags(struct iscsi_pdu *pdu, unsigned char flags) +{ + pdu->outdata.data[1] = flags; +} + +void +iscsi_pdu_set_immediate(struct iscsi_pdu *pdu) +{ + pdu->outdata.data[0] |= ISCSI_PDU_IMMEDIATE; +} + +void +iscsi_pdu_set_ttt(struct iscsi_pdu *pdu, uint32_t ttt) +{ + *(uint32_t *)&pdu->outdata.data[20] = htonl(ttt); +} + +void +iscsi_pdu_set_cmdsn(struct iscsi_pdu *pdu, uint32_t cmdsn) +{ + *(uint32_t *)&pdu->outdata.data[24] = htonl(cmdsn); +} + +void +iscsi_pdu_set_expstatsn(struct iscsi_pdu *pdu, uint32_t expstatsnsn) +{ + *(uint32_t *)&pdu->outdata.data[28] = htonl(expstatsnsn); +} + +void +iscsi_pdu_set_cdb(struct iscsi_pdu *pdu, struct scsi_task *task) +{ + bzero(&pdu->outdata.data[32], 16); + memcpy(&pdu->outdata.data[32], task->cdb, task->cdb_size); +} + +void +iscsi_pdu_set_lun(struct iscsi_pdu *pdu, uint32_t lun) +{ + pdu->outdata.data[9] = lun; +} + +void +iscsi_pdu_set_expxferlen(struct iscsi_pdu *pdu, uint32_t expxferlen) +{ + *(uint32_t *)&pdu->outdata.data[20] = htonl(expxferlen); +} diff --git a/lib/scsi-command.c b/lib/scsi-command.c new file mode 100644 index 0000000..bfcec59 --- /dev/null +++ b/lib/scsi-command.c @@ -0,0 +1,528 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include +#include +#include +#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, + 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 _U_, 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 -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 = 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; +} + diff --git a/lib/scsi-lowlevel.c b/lib/scsi-lowlevel.c new file mode 100644 index 0000000..591cc60 --- /dev/null +++ b/lib/scsi-lowlevel.c @@ -0,0 +1,876 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ +/* + * would be nice if this could grow into a full blown library for scsi to + * 1, build a CDB + * 2, check how big a complete data-in structure needs to be + * 3, unmarshall data-in into a real structure + * 4, marshall a real structure into a data-out blob + */ + +#include +#include +#include +#include +#include +#include +#include +#include "scsi-lowlevel.h" +#include "slist.h" + + +void +scsi_free_scsi_task(struct scsi_task *task) +{ + struct scsi_allocated_memory *mem; + + while ((mem = task->mem)) { + SLIST_REMOVE(&task->mem, mem); + free(mem->ptr); + free(mem); + } + + free(task->datain.data); + free(task); +} + +static void * +scsi_malloc(struct scsi_task *task, size_t size) +{ + struct scsi_allocated_memory *mem; + + mem = malloc(sizeof(struct scsi_allocated_memory)); + if (mem == NULL) { + return NULL; + } + bzero(mem, sizeof(struct scsi_allocated_memory)); + mem->ptr = malloc(size); + if (mem->ptr == NULL) { + free(mem); + return NULL; + } + bzero(mem->ptr, size); + SLIST_ADD(&task->mem, mem); + return mem->ptr; +} + +struct value_string { + int value; + const char *string; +}; + +static const char * +value_string_find(struct value_string *values, int value) +{ + for (; values->value; values++) { + if (value == values->value) { + return values->string; + } + } + return NULL; +} + +const char * +scsi_sense_key_str(int key) +{ + struct value_string keys[] = { + {SCSI_SENSE_ILLEGAL_REQUEST, + "ILLEGAL_REQUEST"}, + {SCSI_SENSE_UNIT_ATTENTION, + "UNIT_ATTENTION"}, + {0, NULL} + }; + + return value_string_find(keys, key); +} + +const char * +scsi_sense_ascq_str(int ascq) +{ + struct value_string ascqs[] = { + {SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB, + "INVALID_FIELD_IN_CDB"}, + {SCSI_SENSE_ASCQ_LOGICAL_UNIT_NOT_SUPPORTED, + "LOGICAL_UNIT_NOT_SUPPORTED"}, + {SCSI_SENSE_ASCQ_BUS_RESET, + "BUS_RESET"}, + {0, NULL} + }; + + return value_string_find(ascqs, ascq); +} + +/* + * TESTUNITREADY + */ +struct scsi_task * +scsi_cdb_testunitready(void) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_TESTUNITREADY; + + task->cdb_size = 6; + task->xfer_dir = SCSI_XFER_NONE; + task->expxferlen = 0; + + return task; +} + + +/* + * REPORTLUNS + */ +struct scsi_task * +scsi_reportluns_cdb(int report_type, int alloc_len) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_REPORTLUNS; + task->cdb[2] = report_type; + *(uint32_t *)&task->cdb[6] = htonl(alloc_len); + + task->cdb_size = 12; + task->xfer_dir = SCSI_XFER_READ; + task->expxferlen = alloc_len; + + task->params.reportluns.report_type = report_type; + + return task; +} + +/* + * parse the data in blob and calcualte the size of a full report luns + * datain structure + */ +static int +scsi_reportluns_datain_getfullsize(struct scsi_task *task) +{ + uint32_t list_size; + + list_size = htonl(*(uint32_t *)&(task->datain.data[0])) + 8; + + return list_size; +} + +/* + * unmarshall the data in blob for reportluns into a structure + */ +static struct scsi_reportluns_list * +scsi_reportluns_datain_unmarshall(struct scsi_task *task) +{ + struct scsi_reportluns_list *list; + int list_size; + int i, num_luns; + + if (task->datain.size < 4) { + return NULL; + } + + list_size = htonl(*(uint32_t *)&(task->datain.data[0])) + 8; + if (list_size < task->datain.size) { + return NULL; + } + + num_luns = list_size / 8 - 1; + list = scsi_malloc(task, offsetof(struct scsi_reportluns_list, luns) + + sizeof(uint16_t) * num_luns); + if (list == NULL) { + return NULL; + } + + list->num = num_luns; + for (i = 0; i < num_luns; i++) { + list->luns[i] = htons(*(uint16_t *) + &(task->datain.data[i*8+8])); + } + + return list; +} + + +/* + * READCAPACITY10 + */ +struct scsi_task * +scsi_cdb_readcapacity10(int lba, int pmi) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_READCAPACITY10; + + *(uint32_t *)&task->cdb[2] = htonl(lba); + + if (pmi) { + task->cdb[8] |= 0x01; + } + + task->cdb_size = 10; + task->xfer_dir = SCSI_XFER_READ; + task->expxferlen = 8; + + task->params.readcapacity10.lba = lba; + task->params.readcapacity10.pmi = pmi; + + return task; +} + +/* + * parse the data in blob and calcualte the size of a full + * readcapacity10 datain structure + */ +static int +scsi_readcapacity10_datain_getfullsize(struct scsi_task *task _U_) +{ + return 8; +} + +/* + * unmarshall the data in blob for readcapacity10 into a structure + */ +static struct scsi_readcapacity10 * +scsi_readcapacity10_datain_unmarshall(struct scsi_task *task) +{ + struct scsi_readcapacity10 *rc10; + + if (task->datain.size < 8) { + return NULL; + } + rc10 = scsi_malloc(task, sizeof(struct scsi_readcapacity10)); + if (rc10 == NULL) { + return NULL; + } + + rc10->lba = htonl(*(uint32_t *)&(task->datain.data[0])); + rc10->block_size = htonl(*(uint32_t *)&(task->datain.data[4])); + + return rc10; +} + + + + + +/* + * INQUIRY + */ +struct scsi_task * +scsi_cdb_inquiry(int evpd, int page_code, int alloc_len) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_INQUIRY; + + if (evpd) { + task->cdb[1] |= 0x01; + } + + task->cdb[2] = page_code; + + *(uint16_t *)&task->cdb[3] = htons(alloc_len); + + task->cdb_size = 6; + task->xfer_dir = SCSI_XFER_READ; + task->expxferlen = alloc_len; + + task->params.inquiry.evpd = evpd; + task->params.inquiry.page_code = page_code; + + return task; +} + +/* + * parse the data in blob and calcualte the size of a full + * inquiry datain structure + */ +static int +scsi_inquiry_datain_getfullsize(struct scsi_task *task) +{ + if (task->params.inquiry.evpd == 0) { + return task->datain.data[4] + 3; + } + + switch (task->params.inquiry.page_code) { + case SCSI_INQUIRY_PAGECODE_SUPPORTED_VPD_PAGES: + return task->datain.data[3] + 4; + case SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER: + return task->datain.data[3] + 4; + case SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION: + return ntohs(*(uint16_t *)&task->datain.data[2]) + 4; + case SCSI_INQUIRY_PAGECODE_BLOCK_DEVICE_CHARACTERISTICS: + return task->datain.data[3] + 4; + default: + return -1; + } +} + +/* + * unmarshall the data in blob for inquiry into a structure + */ +static void * +scsi_inquiry_datain_unmarshall(struct scsi_task *task) +{ + if (task->params.inquiry.evpd == 0) { + struct scsi_inquiry_standard *inq; + + /* standard inquiry */ + inq = scsi_malloc(task, sizeof(struct scsi_inquiry_standard)); + if (inq == NULL) { + return NULL; + } + + inq->periperal_qualifier = (task->datain.data[0]>>5)&0x07; + inq->periperal_device_type = task->datain.data[0]&0x1f; + inq->rmb = !!(task->datain.data[1]&0x80); + inq->version = task->datain.data[2]; + inq->normaca = !!(task->datain.data[3]&0x20); + inq->hisup = !!(task->datain.data[3]&0x10); + inq->response_data_format = task->datain.data[3]&0x0f; + + inq->sccs = !!(task->datain.data[5]&0x80); + inq->acc = !!(task->datain.data[5]&0x40); + inq->tpgs = (task->datain.data[5]>>4)&0x03; + inq->threepc = !!(task->datain.data[5]&0x08); + inq->protect = !!(task->datain.data[5]&0x01); + + inq->encserv = !!(task->datain.data[6]&0x40); + inq->multip = !!(task->datain.data[6]&0x10); + inq->addr16 = !!(task->datain.data[6]&0x01); + inq->wbus16 = !!(task->datain.data[7]&0x20); + inq->sync = !!(task->datain.data[7]&0x10); + inq->cmdque = !!(task->datain.data[7]&0x02); + + memcpy(&inq->vendor_identification[0], + &task->datain.data[8], 8); + memcpy(&inq->product_identification[0], + &task->datain.data[16], 16); + memcpy(&inq->product_revision_level[0], + &task->datain.data[32], 4); + + inq->clocking = (task->datain.data[56]>>2)&0x03; + inq->qas = !!(task->datain.data[56]&0x02); + inq->ius = !!(task->datain.data[56]&0x01); + + return inq; + } + + if (task->params.inquiry.page_code + == SCSI_INQUIRY_PAGECODE_SUPPORTED_VPD_PAGES) { + struct scsi_inquiry_supported_pages *inq; + + inq = scsi_malloc(task, + sizeof(struct scsi_inquiry_supported_pages)); + if (inq == NULL) { + return NULL; + } + inq->periperal_qualifier = (task->datain.data[0]>>5)&0x07; + inq->periperal_device_type = task->datain.data[0]&0x1f; + inq->pagecode = task->datain.data[1]; + + inq->num_pages = task->datain.data[3]; + inq->pages = scsi_malloc(task, inq->num_pages); + if (inq->pages == NULL) { + return NULL; + } + memcpy(inq->pages, &task->datain.data[4], inq->num_pages); + return inq; + } else if (task->params.inquiry.page_code + == SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER) { + struct scsi_inquiry_unit_serial_number *inq; + + inq = scsi_malloc(task, + sizeof(struct scsi_inquiry_unit_serial_number)); + if (inq == NULL) { + return NULL; + } + inq->periperal_qualifier = (task->datain.data[0]>>5)&0x07; + inq->periperal_device_type = task->datain.data[0]&0x1f; + inq->pagecode = task->datain.data[1]; + + inq->usn = scsi_malloc(task, task->datain.data[3]+1); + if (inq->usn == NULL) { + return NULL; + } + memcpy(inq->usn, &task->datain.data[4], task->datain.data[3]); + inq->usn[task->datain.data[3]] = 0; + return inq; + } else if (task->params.inquiry.page_code + == SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION) { + struct scsi_inquiry_device_identification *inq; + int remaining = ntohs(*(uint16_t *)&task->datain.data[2]); + unsigned char *dptr; + + inq = scsi_malloc(task, + sizeof(struct scsi_inquiry_device_identification)); + if (inq == NULL) { + return NULL; + } + inq->periperal_qualifier = (task->datain.data[0]>>5)&0x07; + inq->periperal_device_type = task->datain.data[0]&0x1f; + inq->pagecode = task->datain.data[1]; + + dptr = &task->datain.data[4]; + while (remaining > 0) { + struct scsi_inquiry_device_designator *dev; + + dev = scsi_malloc(task, + sizeof(struct scsi_inquiry_device_designator)); + if (dev == NULL) { + return NULL; + } + + dev->next = inq->designators; + inq->designators = dev; + + dev->protocol_identifier = (dptr[0]>>4) & 0x0f; + dev->code_set = dptr[0] & 0x0f; + dev->piv = !!(dptr[1]&0x80); + dev->association = (dptr[1]>>4)&0x03; + dev->designator_type = dptr[1]&0x0f; + + dev->designator_length = dptr[3]; + dev->designator = scsi_malloc(task, + dev->designator_length+1); + if (dev->designator == NULL) { + return NULL; + } + dev->designator[dev->designator_length] = 0; + memcpy(dev->designator, &dptr[4], + dev->designator_length); + + remaining -= 4; + remaining -= dev->designator_length; + + dptr += dev->designator_length + 4; + } + return inq; + } else if (task->params.inquiry.page_code + == SCSI_INQUIRY_PAGECODE_BLOCK_DEVICE_CHARACTERISTICS) { + struct scsi_inquiry_block_device_characteristics *inq; + + inq = scsi_malloc(task, + sizeof(struct scsi_inquiry_block_device_characteristics)); + if (inq == NULL) { + return NULL; + } + inq->periperal_qualifier = (task->datain.data[0]>>5)&0x07; + inq->periperal_device_type = task->datain.data[0]&0x1f; + inq->pagecode = task->datain.data[1]; + + inq->medium_rotation_rate = ntohs(*(uint16_t *) + &task->datain.data[4]); + return inq; + } + + return NULL; +} + +/* + * READ10 + */ +struct scsi_task * +scsi_cdb_read10(int lba, int xferlen, int blocksize) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_READ10; + + *(uint32_t *)&task->cdb[2] = htonl(lba); + *(uint16_t *)&task->cdb[7] = htons(xferlen/blocksize); + + task->cdb_size = 10; + task->xfer_dir = SCSI_XFER_READ; + task->expxferlen = xferlen; + + return task; +} + +/* + * WRITE10 + */ +struct scsi_task * +scsi_cdb_write10(int lba, int xferlen, int fua, int fuanv, int blocksize) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_WRITE10; + + if (fua) { + task->cdb[1] |= 0x08; + } + if (fuanv) { + task->cdb[1] |= 0x02; + } + + *(uint32_t *)&task->cdb[2] = htonl(lba); + *(uint16_t *)&task->cdb[7] = htons(xferlen/blocksize); + + task->cdb_size = 10; + task->xfer_dir = SCSI_XFER_WRITE; + task->expxferlen = xferlen; + + return task; +} + + + +/* + * MODESENSE6 + */ +struct scsi_task * +scsi_cdb_modesense6(int dbd, enum scsi_modesense_page_control pc, + enum scsi_modesense_page_code page_code, + int sub_page_code, unsigned char alloc_len) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_MODESENSE6; + + if (dbd) { + task->cdb[1] |= 0x08; + } + task->cdb[2] = pc<<6 | page_code; + task->cdb[3] = sub_page_code; + task->cdb[4] = alloc_len; + + task->cdb_size = 6; + task->xfer_dir = SCSI_XFER_READ; + task->expxferlen = alloc_len; + + task->params.modesense6.dbd = dbd; + task->params.modesense6.pc = pc; + task->params.modesense6.page_code = page_code; + task->params.modesense6.sub_page_code = sub_page_code; + + return task; +} + +/* + * parse the data in blob and calcualte the size of a full + * modesense6 datain structure + */ +static int +scsi_modesense6_datain_getfullsize(struct scsi_task *task) +{ + int len; + + len = task->datain.data[0] + 1; + + return len; +} + + +/* + * SYNCHRONIZECACHE10 + */ +struct scsi_task * +scsi_cdb_synchronizecache10(int lba, int num_blocks, int syncnv, int immed) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + return NULL; + } + + bzero(task, sizeof(struct scsi_task)); + task->cdb[0] = SCSI_OPCODE_SYNCHRONIZECACHE10; + + if (syncnv) { + task->cdb[1] |= 0x04; + } + if (immed) { + task->cdb[1] |= 0x02; + } + *(uint32_t *)&task->cdb[2] = htonl(lba); + *(uint16_t *)&task->cdb[7] = htons(num_blocks); + + task->cdb_size = 10; + task->xfer_dir = SCSI_XFER_NONE; + task->expxferlen = 0; + + return task; +} + + + +int +scsi_datain_getfullsize(struct scsi_task *task) +{ + switch (task->cdb[0]) { + case SCSI_OPCODE_TESTUNITREADY: + return 0; + case SCSI_OPCODE_INQUIRY: + return scsi_inquiry_datain_getfullsize(task); + case SCSI_OPCODE_MODESENSE6: + return scsi_modesense6_datain_getfullsize(task); + case SCSI_OPCODE_READCAPACITY10: + return scsi_readcapacity10_datain_getfullsize(task); + case SCSI_OPCODE_SYNCHRONIZECACHE10: + return 0; + case SCSI_OPCODE_REPORTLUNS: + return scsi_reportluns_datain_getfullsize(task); + } + return -1; +} + +void * +scsi_datain_unmarshall(struct scsi_task *task) +{ + switch (task->cdb[0]) { + case SCSI_OPCODE_TESTUNITREADY: + return NULL; + case SCSI_OPCODE_INQUIRY: + return scsi_inquiry_datain_unmarshall(task); + case SCSI_OPCODE_READCAPACITY10: + return scsi_readcapacity10_datain_unmarshall(task); + case SCSI_OPCODE_SYNCHRONIZECACHE10: + return NULL; + case SCSI_OPCODE_REPORTLUNS: + return scsi_reportluns_datain_unmarshall(task); + } + return NULL; +} + + +const char * +scsi_devtype_to_str(enum scsi_inquiry_peripheral_device_type type) +{ + switch (type) { + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS: + return "DIRECT_ACCESS"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SEQUENTIAL_ACCESS: + return "SEQUENTIAL_ACCESS"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_PRINTER: + return "PRINTER"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_PROCESSOR: + return "PROCESSOR"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_WRITE_ONCE: + return "WRITE_ONCE"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_MMC: + return "MMC"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SCANNER: + return "SCANNER"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OPTICAL_MEMORY: + return "OPTICAL_MEMORY"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_MEDIA_CHANGER: + return "MEDIA_CHANGER"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_COMMUNICATIONS: + return "COMMUNICATIONS"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_STORAGE_ARRAY_CONTROLLER: + return "STORAGE_ARRAY_CONTROLLER"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_ENCLOSURE_SERVICES: + return "ENCLOSURE_SERVICES"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SIMPLIFIED_DIRECT_ACCESS: + return "SIMPLIFIED_DIRECT_ACCESS"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OPTICAL_CARD_READER: + return "OPTICAL_CARD_READER"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_BRIDGE_CONTROLLER: + return "BRIDGE_CONTROLLER"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OSD: + return "OSD"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_AUTOMATION: + return "AUTOMATION"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SEQURITY_MANAGER: + return "SEQURITY_MANAGER"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_WELL_KNOWN_LUN: + return "WELL_KNOWN_LUN"; + case SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_UNKNOWN: + return "UNKNOWN"; + } + return "unknown"; +} + +const char * +scsi_devqualifier_to_str(enum scsi_inquiry_peripheral_qualifier qualifier) +{ + switch (qualifier) { + case SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED: + return "CONNECTED"; + case SCSI_INQUIRY_PERIPHERAL_QUALIFIER_DISCONNECTED: + return "DISCONNECTED"; + case SCSI_INQUIRY_PERIPHERAL_QUALIFIER_NOT_SUPPORTED: + return "NOT_SUPPORTED"; + } + return "unknown"; +} + +const char * +scsi_version_to_str(enum scsi_version version) +{ + switch (version) { + case SCSI_VERSION_SPC: + return "ANSI INCITS 301-1997 (SPC)"; + case SCSI_VERSION_SPC2: + return "ANSI INCITS 351-2001 (SPC-2)"; + case SCSI_VERSION_SPC3: + return "ANSI INCITS 408-2005 (SPC-3)"; + } + return "unknown"; +} + + +const char * +scsi_inquiry_pagecode_to_str(int pagecode) +{ + switch (pagecode) { + case SCSI_INQUIRY_PAGECODE_SUPPORTED_VPD_PAGES: + return "SUPPORTED_VPD_PAGES"; + case SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER: + return "UNIT_SERIAL_NUMBER"; + case SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION: + return "DEVICE_IDENTIFICATION"; + case SCSI_INQUIRY_PAGECODE_BLOCK_DEVICE_CHARACTERISTICS: + return "BLOCK_DEVICE_CHARACTERISTICS"; + } + return "unknown"; +} + + +const char * +scsi_protocol_identifier_to_str(int identifier) +{ + switch (identifier) { + case SCSI_PROTOCOL_IDENTIFIER_FIBRE_CHANNEL: + return "FIBRE_CHANNEL"; + case SCSI_PROTOCOL_IDENTIFIER_PARALLEL_SCSI: + return "PARALLEL_SCSI"; + case SCSI_PROTOCOL_IDENTIFIER_SSA: + return "SSA"; + case SCSI_PROTOCOL_IDENTIFIER_IEEE_1394: + return "IEEE_1394"; + case SCSI_PROTOCOL_IDENTIFIER_RDMA: + return "RDMA"; + case SCSI_PROTOCOL_IDENTIFIER_ISCSI: + return "ISCSI"; + case SCSI_PROTOCOL_IDENTIFIER_SAS: + return "SAS"; + case SCSI_PROTOCOL_IDENTIFIER_ADT: + return "ADT"; + case SCSI_PROTOCOL_IDENTIFIER_ATA: + return "ATA"; + } + return "unknown"; +} + +const char * +scsi_codeset_to_str(int codeset) +{ + switch (codeset) { + case SCSI_CODESET_BINARY: + return "BINARY"; + case SCSI_CODESET_ASCII: + return "ASCII"; + case SCSI_CODESET_UTF8: + return "UTF8"; + } + return "unknown"; +} + +const char * +scsi_association_to_str(int association) +{ + switch (association) { + case SCSI_ASSOCIATION_LOGICAL_UNIT: + return "LOGICAL_UNIT"; + case SCSI_ASSOCIATION_TARGET_PORT: + return "TARGET_PORT"; + case SCSI_ASSOCIATION_TARGET_DEVICE: + return "TARGET_DEVICE"; + } + return "unknown"; +} + +const char * +scsi_designator_type_to_str(int type) +{ + switch (type) { + case SCSI_DESIGNATOR_TYPE_VENDOR_SPECIFIC: + return "VENDOR_SPECIFIC"; + case SCSI_DESIGNATOR_TYPE_T10_VENDORT_ID: + return "T10_VENDORT_ID"; + case SCSI_DESIGNATOR_TYPE_EUI_64: + return "EUI_64"; + case SCSI_DESIGNATOR_TYPE_NAA: + return "NAA"; + case SCSI_DESIGNATOR_TYPE_RELATIVE_TARGET_PORT: + return "RELATIVE_TARGET_PORT"; + case SCSI_DESIGNATOR_TYPE_TARGET_PORT_GROUP: + return "TARGET_PORT_GROUP"; + case SCSI_DESIGNATOR_TYPE_LOGICAL_UNIT_GROUP: + return "LOGICAL_UNIT_GROUP"; + case SCSI_DESIGNATOR_TYPE_MD5_LOGICAL_UNIT_IDENTIFIER: + return "MD5_LOGICAL_UNIT_IDENTIFIER"; + case SCSI_DESIGNATOR_TYPE_SCSI_NAME_STRING: + return "SCSI_NAME_STRING"; + } + return "unknown"; +} + +void +scsi_set_task_private_ptr(struct scsi_task *task, void *ptr) +{ + task->ptr = ptr; +} + +void * +scsi_get_task_private_ptr(struct scsi_task *task) +{ + return task->ptr; +} diff --git a/lib/socket.c b/lib/socket.c new file mode 100644 index 0000000..e467a06 --- /dev/null +++ b/lib/socket.c @@ -0,0 +1,351 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "slist.h" + +static void set_nonblocking(int fd) +{ + unsigned v; + v = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, v | O_NONBLOCK); +} + +int +iscsi_connect_async(struct iscsi_context *iscsi, const char *portal, + iscsi_command_cb cb, void *private_data) +{ + int tpgt = -1; + int port = 3260; + char *str; + char *addr; + struct sockaddr_storage s; + struct sockaddr_in *sin = (struct sockaddr_in *)&s; + int socksize; + + if (iscsi->fd != -1) { + iscsi_set_error(iscsi, + "Trying to connect but already connected."); + return -1; + } + + addr = strdup(portal); + if (addr == NULL) { + iscsi_set_error(iscsi, "Out-of-memory: " + "Failed to strdup portal address."); + return -1; + } + + /* check if we have a target portal group tag */ + str = rindex(addr, ','); + if (str != NULL) { + tpgt = atoi(str+1); + str[0] = 0; + } + + /* XXX need handling for {ipv6 addresses} */ + /* for now, assume all is ipv4 */ + str = rindex(addr, ':'); + if (str != NULL) { + port = atoi(str+1); + str[0] = 0; + } + + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + if (inet_pton(AF_INET, addr, &sin->sin_addr) != 1) { + iscsi_set_error(iscsi, "Invalid target:%s " + "Failed to convert to ip address.", addr); + free(addr); + return -1; + } + free(addr); + + switch (s.ss_family) { + case AF_INET: + iscsi->fd = socket(AF_INET, SOCK_STREAM, 0); + socksize = sizeof(struct sockaddr_in); + break; + default: + iscsi_set_error(iscsi, "Unknown address family :%d. " + "Only IPv4 supported so far.", s.ss_family); + return -1; + + } + + if (iscsi->fd == -1) { + iscsi_set_error(iscsi, "Failed to open iscsi socket. " + "Errno:%s(%d).", strerror(errno), errno); + return -1; + + } + + iscsi->socket_status_cb = cb; + iscsi->connect_data = private_data; + + set_nonblocking(iscsi->fd); + + if (connect(iscsi->fd, (struct sockaddr *)&s, socksize) != 0 + && errno != EINPROGRESS) { + iscsi_set_error(iscsi, "Connect failed with errno : " + "%s(%d)", strerror(errno), errno); + close(iscsi->fd); + iscsi->fd = -1; + return -1; + } + + return 0; +} + +int +iscsi_disconnect(struct iscsi_context *iscsi) +{ + if (iscsi->fd == -1) { + iscsi_set_error(iscsi, "Trying to disconnect " + "but not connected"); + return -1; + } + + close(iscsi->fd); + iscsi->fd = -1; + iscsi->is_connected = 0; + + return 0; +} + +int +iscsi_get_fd(struct iscsi_context *iscsi) +{ + return iscsi->fd; +} + +int +iscsi_which_events(struct iscsi_context *iscsi) +{ + int events = POLLIN; + + if (iscsi->is_connected == 0) { + events |= POLLOUT; + } + + if (iscsi->outqueue) { + events |= POLLOUT; + } + return events; +} + +static int +iscsi_read_from_socket(struct iscsi_context *iscsi) +{ + int available; + int size; + unsigned char *buf; + ssize_t count; + + if (ioctl(iscsi->fd, FIONREAD, &available) != 0) { + iscsi_set_error(iscsi, "ioctl FIONREAD returned error : " + "%d", errno); + return -1; + } + if (available == 0) { + iscsi_set_error(iscsi, "no data readable in socket, " + "socket is closed"); + return -1; + } + size = iscsi->insize - iscsi->inpos + available; + buf = malloc(size); + if (buf == NULL) { + iscsi_set_error(iscsi, "failed to allocate %d bytes for " + "input buffer", size); + return -1; + } + if (iscsi->insize > iscsi->inpos) { + memcpy(buf, iscsi->inbuf + iscsi->inpos, + iscsi->insize - iscsi->inpos); + iscsi->insize -= iscsi->inpos; + iscsi->inpos = 0; + } + + count = read(iscsi->fd, buf + iscsi->insize, available); + if (count == -1) { + if (errno == EINTR) { + free(buf); + buf = NULL; + return 0; + } + iscsi_set_error(iscsi, "read from socket failed, " + "errno:%d", errno); + free(buf); + buf = NULL; + return -1; + } + + free(iscsi->inbuf); + + iscsi->inbuf = buf; + iscsi->insize += count; + + while (1) { + if (iscsi->insize - iscsi->inpos < ISCSI_RAW_HEADER_SIZE) { + return 0; + } + count = iscsi_get_pdu_size(iscsi, + iscsi->inbuf + iscsi->inpos); + if (iscsi->insize + iscsi->inpos < count) { + return 0; + } + if (iscsi_process_pdu(iscsi, iscsi->inbuf + iscsi->inpos, + count) != 0) { + iscsi->inpos += count; + return -1; + } + iscsi->inpos += count; + if (iscsi->inpos == iscsi->insize) { + free(iscsi->inbuf); + iscsi->inbuf = NULL; + iscsi->insize = 0; + iscsi->inpos = 0; + } + if (iscsi->inpos > iscsi->insize) { + iscsi_set_error(iscsi, "inpos > insize. bug!"); + return -1; + } + } + + return 0; +} + +static int +iscsi_write_to_socket(struct iscsi_context *iscsi) +{ + ssize_t count; + + if (iscsi->fd == -1) { + iscsi_set_error(iscsi, "trying to write but not connected"); + return -1; + } + + while (iscsi->outqueue != NULL) { + ssize_t total; + + total = iscsi->outqueue->outdata.size; + total = (total + 3) & 0xfffffffc; + + count = write(iscsi->fd, + iscsi->outqueue->outdata.data + + iscsi->outqueue->written, + total - iscsi->outqueue->written); + if (count == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + iscsi_set_error(iscsi, "Error when writing to " + "socket :%d", errno); + return -1; + } + + iscsi->outqueue->written += count; + if (iscsi->outqueue->written == total) { + struct iscsi_pdu *pdu = iscsi->outqueue; + + SLIST_REMOVE(&iscsi->outqueue, pdu); + SLIST_ADD_END(&iscsi->waitpdu, pdu); + } + } + return 0; +} + +int +iscsi_service(struct iscsi_context *iscsi, int revents) +{ + if (revents & POLLERR) { + iscsi_set_error(iscsi, "iscsi_service: POLLERR, " + "socket error."); + iscsi->socket_status_cb(iscsi, SCSI_STATUS_ERROR, NULL, + iscsi->connect_data); + return -1; + } + if (revents & POLLHUP) { + iscsi_set_error(iscsi, "iscsi_service: POLLHUP, " + "socket error."); + iscsi->socket_status_cb(iscsi, SCSI_STATUS_ERROR, NULL, + iscsi->connect_data); + return -1; + } + + if (iscsi->is_connected == 0 && iscsi->fd != -1 && revents&POLLOUT) { + iscsi->is_connected = 1; + iscsi->socket_status_cb(iscsi, SCSI_STATUS_GOOD, NULL, + iscsi->connect_data); + return 0; + } + + if (revents & POLLOUT && iscsi->outqueue != NULL) { + if (iscsi_write_to_socket(iscsi) != 0) { + return -1; + } + } + if (revents & POLLIN) { + if (iscsi_read_from_socket(iscsi) != 0) + return -1; + } + + return 0; +} + +int +iscsi_queue_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) +{ + if (pdu == NULL) { + iscsi_set_error(iscsi, "trying to queue NULL pdu"); + return -1; + } + + if (iscsi->header_digest != ISCSI_HEADER_DIGEST_NONE) { + unsigned long crc; + + if (pdu->outdata.size < ISCSI_RAW_HEADER_SIZE + 4) { + iscsi_set_error(iscsi, "PDU too small (%d) to contain header digest", + pdu->outdata.size); + return -1; + } + + crc = crc32c((char *)pdu->outdata.data, ISCSI_RAW_HEADER_SIZE); + + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+3] = (crc >> 24)&0xff; + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+2] = (crc >> 16)&0xff; + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+1] = (crc >> 8)&0xff; + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+0] = (crc) &0xff; + } + + SLIST_ADD_END(&iscsi->outqueue, pdu); + + return 0; +} + diff --git a/lib/sync.c b/lib/sync.c new file mode 100644 index 0000000..5c29bd5 --- /dev/null +++ b/lib/sync.c @@ -0,0 +1,282 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 3 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "scsi-lowlevel.h" + +struct scsi_sync_state { + int finished; + struct scsi_task *task; +}; + +struct iscsi_sync_state { + int finished; + int status; +}; + +static void +event_loop(struct iscsi_context *iscsi, struct scsi_sync_state *state) +{ + struct pollfd pfd; + + while (state->finished == 0) { + pfd.fd = iscsi_get_fd(iscsi); + pfd.events = iscsi_which_events(iscsi); + + if (poll(&pfd, 1, -1) < 0) { + iscsi_set_error(iscsi, "Poll failed"); + return; + } + if (iscsi_service(iscsi, pfd.revents) < 0) { + iscsi_set_error(iscsi, + "iscsi_service failed with : %s", + iscsi_get_error(iscsi)); + return; + } + } +} + +/* + * Synchronous iSCSI commands + */ +static void +iscsi_sync_cb(struct iscsi_context *iscsi _U_, int status, + void *command_data _U_, void *private_data) +{ + struct iscsi_sync_state *state = private_data; + + state->status = status; + state->finished = 1; +} + +int +iscsi_connect_sync(struct iscsi_context *iscsi, const char *portal) +{ + struct iscsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_connect_async(iscsi, portal, + iscsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, + "Failed to start connect() %s", + iscsi_get_error(iscsi)); + return -1; + } + + event_loop(iscsi, (struct scsi_sync_state *)&state); + + return state.status; +} + +int +iscsi_full_connect_sync(struct iscsi_context *iscsi, + const char *portal, int lun) +{ + struct iscsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_full_connect_async(iscsi, portal, lun, + iscsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, + "Failed to start full connect %s", + iscsi_get_error(iscsi)); + return -1; + } + + event_loop(iscsi, (struct scsi_sync_state *)&state); + + return state.status; +} + +int iscsi_login_sync(struct iscsi_context *iscsi) +{ + struct iscsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_login_async(iscsi, iscsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, "Failed to login. %s", + iscsi_get_error(iscsi)); + return -1; + } + + event_loop(iscsi, (struct scsi_sync_state *)&state); + + return state.status; +} + +int iscsi_logout_sync(struct iscsi_context *iscsi) +{ + struct iscsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_logout_async(iscsi, iscsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, "Failed to start logout() %s", + iscsi_get_error(iscsi)); + return -1; + } + + event_loop(iscsi, (struct scsi_sync_state *)&state); + + return state.status; +} + + + +/* + * Synchronous SCSI commands + */ +static void +scsi_sync_cb(struct iscsi_context *iscsi _U_, int status, void *command_data, + void *private_data) +{ + struct scsi_task *task = command_data; + struct scsi_sync_state *state = private_data; + + task->status = status; + state->finished = 1; + state->task = task; + iscsi_cbdata_steal_scsi_task(task); +} + +struct scsi_task * +iscsi_reportluns_sync(struct iscsi_context *iscsi, int report_type, + int alloc_len) +{ + struct scsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_reportluns_async(iscsi, report_type, alloc_len, + scsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, "Failed to send ReportLuns command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + + +struct scsi_task * +iscsi_testunitready_sync(struct iscsi_context *iscsi, int lun) +{ + struct scsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_testunitready_async(iscsi, lun, + scsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, + "Failed to send TestUnitReady command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + +struct scsi_task * +iscsi_inquiry_sync(struct iscsi_context *iscsi, int lun, int evpd, + int page_code, int maxsize) +{ + struct scsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_inquiry_async(iscsi, lun, evpd, page_code, maxsize, + scsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, "Failed to send Inquiry command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + +struct scsi_task * +iscsi_readcapacity10_sync(struct iscsi_context *iscsi, int lun, int lba, + int pmi) +{ + struct scsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_readcapacity10_async(iscsi, lun, lba, pmi, + scsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, + "Failed to send ReadCapacity10 command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + +struct scsi_task * +iscsi_synchronizecache10_sync(struct iscsi_context *iscsi, int lun, int lba, + int num_blocks, int syncnv, int immed) +{ + struct scsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_synchronizecache10_async(iscsi, lun, lba, num_blocks, + syncnv, immed, + scsi_sync_cb, &state) != 0) { + iscsi_set_error(iscsi, + "Failed to send SynchronizeCache10 command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + +struct scsi_task * +iscsi_scsi_command_sync(struct iscsi_context *iscsi, int lun, + struct scsi_task *task, struct iscsi_data *data) +{ + struct scsi_sync_state state; + + bzero(&state, sizeof(state)); + + if (iscsi_scsi_command_async(iscsi, lun, task, + scsi_sync_cb, data, &state) != 0) { + iscsi_set_error(iscsi, "Failed to send SCSI command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + + diff --git a/packaging/RPM/libiscsi.spec.in b/packaging/RPM/libiscsi.spec.in new file mode 100644 index 0000000..f5998bd --- /dev/null +++ b/packaging/RPM/libiscsi.spec.in @@ -0,0 +1,87 @@ +Name: libiscsi +Summary: iSCSI client library +Vendor: Ronnie Sahlberg +Packager: Ronnie Sahlberg +Version: 1.0.0 +Release: 1GITHASH +Epoch: 0 +License: GNU LGPL version 3 +Group: Libraries +URL: http://github.something/ + +Source: libiscsi-%{version}.tar.gz + +Prereq: fileutils + +Provides: libiscsi = %{version} + +Prefix: /usr +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description +libiscsi is a client library for attaching to iscsi resources across +a network and a set of assorted useful utilities + + +####################################################################### + + + +%prep +%setup -q +# setup the init script and sysconfig file +%setup -T -D -n libiscsi-%{version} -q + +%build + +CC="gcc" + +export CC + +%install +# Clean up in case there is trash left from a previous build +rm -rf $RPM_BUILD_ROOT + +# Create the target build directory hierarchy +mkdir -p $RPM_BUILD_ROOT%{_bindir} +mkdir -p $RPM_BUILD_ROOT%{_libdir} +mkdir -p $RPM_BUILD_ROOT%{_includedir} +mkdir -p $RPM_BUILD_ROOT%{_includedir}/iscsi + +# +make DESTDIR=$RPM_BUILD_ROOT LIBDIR=$RPM_BUILD_ROOT%{_libdir} install + +# Remove "*.old" files +find $RPM_BUILD_ROOT -name "*.old" -exec rm -f {} \; + +%clean +rm -rf $RPM_BUILD_ROOT + + +####################################################################### +## Files section ## +####################################################################### + +%files +%defattr(-,root,root) + +%{_bindir}/iscsi-ls +%{_bindir}/iscsi-inq + +%package devel +Summary: iSCSI client development libraries +Group: Development + +%description devel +development libraries for iSCSI + +%files devel +%defattr(-,root,root) +%{_includedir}/iscsi/iscsi.h +%{_includedir}/iscsi/scsi-lowlevel.h +%{_libdir}/libiscsi.a +%{_libdir}/libiscsi.so.1.0.0 + +%changelog +* Sat Dec 4 2010 : Version 1.0.0 + - Initial version diff --git a/packaging/RPM/makerpms.sh b/packaging/RPM/makerpms.sh new file mode 100755 index 0000000..2f1a879 --- /dev/null +++ b/packaging/RPM/makerpms.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# +# makerpms.sh - build RPM packages from the git sources +# +# Copyright (C) John H Terpstra 1998-2002 +# Copyright (C) Gerald (Jerry) Carter 2003 +# Copyright (C) Jim McDonough 2007 +# Copyright (C) Andrew Tridgell 2007 +# Copyright (C) Michael Adam 2008-2009 +# +# 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 3 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 . +# + +# +# The following allows environment variables to override the target directories +# the alternative is to have a file in your home directory calles .rpmmacros +# containing the following: +# %_topdir /home/mylogin/redhat +# +# Note: Under this directory rpm expects to find the same directories that are under the +# /usr/src/redhat directory +# + +EXTRA_OPTIONS="$1" + +DIRNAME=$(dirname $0) +TOPDIR=${DIRNAME}/../.. + +SPECDIR=`rpm --eval %_specdir` +SRCDIR=`rpm --eval %_sourcedir` + +SPECFILE="libiscsi.spec" +SPECFILE_IN="libiscsi.spec.in" +RPMBUILD="rpmbuild" + +GITHASH=".$(git log --pretty=format:%h -1)" + +if test "x$USE_GITHASH" = "xno" ; then + GITHASH="" +fi + +sed -e s/GITHASH/${GITHASH}/g \ + < ${DIRNAME}/${SPECFILE_IN} \ + > ${DIRNAME}/${SPECFILE} + +VERSION=$(grep ^Version ${DIRNAME}/${SPECFILE} | sed -e 's/^Version:\ \+//') +RELEASE=$(grep ^Release ${DIRNAME}/${SPECFILE} | sed -e 's/^Release:\ \+//') + +if echo | gzip -c --rsyncable - > /dev/null 2>&1 ; then + GZIP="gzip -9 --rsyncable" +else + GZIP="gzip -9" +fi + +pushd ${TOPDIR} +echo -n "Creating libiscsi-${VERSION}.tar.gz ... " +git archive --prefix=libiscsi-${VERSION}/ HEAD | ${GZIP} > ${SRCDIR}/libiscsi-${VERSION}.tar.gz +RC=$? +popd +echo "Done." +if [ $RC -ne 0 ]; then + echo "Build failed!" + exit 1 +fi + +# At this point the SPECDIR and SRCDIR vaiables must have a value! + +## +## copy additional source files +## +cp -p ${DIRNAME}/${SPECFILE} ${SPECDIR} + +## +## Build +## +echo "$(basename $0): Getting Ready to build release package" +${RPMBUILD} -ba --clean --rmsource ${EXTRA_OPTIONS} ${SPECDIR}/${SPECFILE} || exit 1 + +echo "$(basename $0): Done." + +exit 0 diff --git a/src/iscsi-inq.c b/src/iscsi-inq.c new file mode 100644 index 0000000..a786d2b --- /dev/null +++ b/src/iscsi-inq.c @@ -0,0 +1,218 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "scsi-lowlevel.h" + +char *initiator = "iqn.2010-11.ronnie:iscsi-inq"; + + +void inquiry_block_device_characteristics(struct scsi_inquiry_block_device_characteristics *inq) +{ + printf("Medium Rotation Rate:%dRPM\n", inq->medium_rotation_rate); +} + +void inquiry_device_identification(struct scsi_inquiry_device_identification *inq) +{ + struct scsi_inquiry_device_designator *dev; + int i; + + printf("Peripheral Qualifier:%s\n", + scsi_devqualifier_to_str(inq->periperal_qualifier)); + printf("Peripheral Device Type:%s\n", + scsi_devtype_to_str(inq->periperal_device_type)); + printf("Page Code:(0x%02x) %s\n", + inq->pagecode, scsi_inquiry_pagecode_to_str(inq->pagecode)); + + for (i=0, dev = inq->designators; dev; i++, dev = dev->next) { + printf("DEVICE DESIGNATOR #%d\n", i); + if (dev->piv != 0) { + printf("Device Protocol Identifier:(%d) %s\n", dev->protocol_identifier, scsi_protocol_identifier_to_str(dev->protocol_identifier)); + } + printf("Code Set:(%d) %s\n", dev->code_set, scsi_codeset_to_str(dev->code_set)); + printf("PIV:%d\n", dev->piv); + printf("Association:(%d) %s\n", dev->association, scsi_association_to_str(dev->association)); + printf("Designator Type:(%d) %s\n", dev->designator_type, scsi_designator_type_to_str(dev->designator_type)); + printf("Designator:[%s]\n", dev->designator); + } +} + +void inquiry_unit_serial_number(struct scsi_inquiry_unit_serial_number *inq) +{ + printf("Unit Serial Number:[%s]\n", inq->usn); +} + +void inquiry_supported_pages(struct scsi_inquiry_supported_pages *inq) +{ + int i; + + for (i = 0; i < inq->num_pages; i++) { + printf("Page:0x%02x %s\n", inq->pages[i], scsi_inquiry_pagecode_to_str(inq->pages[i])); + } +} + +void inquiry_standard(struct scsi_inquiry_standard *inq) +{ + printf("Peripheral Qualifier:%s\n", + scsi_devqualifier_to_str(inq->periperal_qualifier)); + printf("Peripheral Device Type:%s\n", + scsi_devtype_to_str(inq->periperal_device_type)); + printf("Removable:%d\n", inq->rmb); + printf("Version:%d %s\n", inq->version, scsi_version_to_str(inq->version)); + printf("NormACA:%d\n", inq->normaca); + printf("HiSup:%d\n", inq->hisup); + printf("ReponseDataFormat:%d\n", inq->response_data_format); + printf("SCCS:%d\n", inq->sccs); + printf("ACC:%d\n", inq->acc); + printf("TPGS:%d\n", inq->tpgs); + printf("3PC:%d\n", inq->threepc); + printf("Protect:%d\n", inq->protect); + printf("EncServ:%d\n", inq->encserv); + printf("MultiP:%d\n", inq->multip); + printf("SYNC:%d\n", inq->sync); + printf("CmdQue:%d\n", inq->cmdque); + printf("Vendor:%s\n", inq->vendor_identification); + printf("Product:%s\n", inq->product_identification); + printf("Revision:%s\n", inq->product_revision_level); +} + +void do_inquiry(struct iscsi_context *iscsi, int lun, int evpd, int pc) +{ + struct scsi_task *task; + int full_size; + void *inq; + + /* See how big this inquiry data is */ + task = iscsi_inquiry_sync(iscsi, lun, evpd, pc, 64); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, "Inquiry command failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + full_size = scsi_datain_getfullsize(task); + if (full_size > task->datain.size) { + scsi_free_scsi_task(task); + + /* we need more data for the full list */ + if ((task = iscsi_inquiry_sync(iscsi, lun, evpd, pc, full_size)) == NULL) { + fprintf(stderr, "Inquiry command failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + } + + inq = scsi_datain_unmarshall(task); + if (inq == NULL) { + fprintf(stderr, "failed to unmarshall inquiry datain blob\n"); + exit(10); + } + + if (evpd == 0) { + inquiry_standard(inq); + } else { + switch (pc) { + case SCSI_INQUIRY_PAGECODE_SUPPORTED_VPD_PAGES: + inquiry_supported_pages(inq); + break; + case SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER: + inquiry_unit_serial_number(inq); + break; + case SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION: + inquiry_device_identification(inq); + break; + case SCSI_INQUIRY_PAGECODE_BLOCK_DEVICE_CHARACTERISTICS: + inquiry_block_device_characteristics(inq); + break; + default: + fprintf(stderr, "Usupported pagecode:0x%02x\n", pc); + } + } + scsi_free_scsi_task(task); +} + + + +int main(int argc, const char *argv[]) +{ + poptContext pc; + struct iscsi_context *iscsi; + char *portal = NULL; + char *target = NULL; + int lun = -1, evpd = 0, pagecode = 0; + int res; + + struct poptOption popt_options[] = { + POPT_AUTOHELP + { "initiator-name", 'i', POPT_ARG_STRING, &initiator, 0, "Initiatorname to use", "iqn-name" }, + { "portal", 'p', POPT_ARG_STRING, &portal, 0, "Target portal", "address[:port]" }, + { "target", 't', POPT_ARG_STRING, &target, 0, "Target name", "iqn-name" }, + { "lun", 'l', POPT_ARG_INT, &lun, 0, "LUN", "integer" }, + { "evpd", 'e', POPT_ARG_INT, &evpd, 0, "evpd", "integer" }, + { "pagecode", 'c', POPT_ARG_INT, &pagecode, 0, "page code", "integer" }, + POPT_TABLEEND + }; + + pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_POSIXMEHARDER); + if ((res = poptGetNextOpt(pc)) < -1) { + fprintf(stderr, "Failed to parse option : %s %s\n", + poptBadOption(pc, 0), poptStrerror(res)); + exit(10); + } + poptFreeContext(pc); + + if (portal == NULL) { + fprintf(stderr, "You must specify target portal\n"); + exit(10); + } + + if (target == NULL) { + fprintf(stderr, "You must specify target name\n"); + exit(10); + } + + if (lun == -1) { + fprintf(stderr, "You must specify LUN\n"); + exit(10); + } + + iscsi = iscsi_create_context(initiator); + if (iscsi == NULL) { + fprintf(stderr, "Failed to create context\n"); + exit(10); + } + + iscsi_set_targetname(iscsi, target); + iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); + + if (iscsi_full_connect_sync(iscsi, portal, lun) != 0) { + fprintf(stderr, "Failed to log in to target %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + do_inquiry(iscsi, lun, evpd, pagecode); + + iscsi_logout_sync(iscsi); + iscsi_destroy_context(iscsi); + return 0; +} + diff --git a/src/iscsi-ls.c b/src/iscsi-ls.c new file mode 100644 index 0000000..6f4e0e4 --- /dev/null +++ b/src/iscsi-ls.c @@ -0,0 +1,315 @@ +/* + Copyright (C) 2010 by Ronnie Sahlberg + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "scsi-lowlevel.h" + +int showluns; +char *initiator = "iqn.2010-11.ronnie:iscsi-ls"; + +struct client_state { + int finished; + int status; + int lun; + int type; +}; + + +void event_loop(struct iscsi_context *iscsi, struct client_state *state) +{ + struct pollfd pfd; + + while (state->finished == 0) { + pfd.fd = iscsi_get_fd(iscsi); + pfd.events = iscsi_which_events(iscsi); + + if (poll(&pfd, 1, -1) < 0) { + fprintf(stderr, "Poll failed"); + exit(10); + } + if (iscsi_service(iscsi, pfd.revents) < 0) { + fprintf(stderr, "iscsi_service failed with : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + } +} + +void show_lun(struct iscsi_context *iscsi, int lun) +{ + struct scsi_task *task; + struct scsi_inquiry_standard *inq; + int type; + long long size = 0; + int size_pf = 0; + static const char sf[] = {' ', 'k', 'M', 'G', 'T' }; + + /* check we can talk to the lun */ +tur_try_again: + if ((task = iscsi_testunitready_sync(iscsi, lun)) == NULL) { + fprintf(stderr, "testunitready failed\n"); + exit(10); + } + if (task->status == SCSI_STATUS_CHECK_CONDITION) { + if (task->sense.key == SCSI_SENSE_UNIT_ATTENTION && task->sense.ascq == SCSI_SENSE_ASCQ_BUS_RESET) { + scsi_free_scsi_task(task); + goto tur_try_again; + } + } + if (task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, "TESTUNITREADY failed with %s\n", iscsi_get_error(iscsi)); + exit(10); + } + scsi_free_scsi_task(task); + + + + /* check what type of lun we have */ + task = iscsi_inquiry_sync(iscsi, lun, 0, 0, 64); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, "failed to send inquiry command : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + inq = scsi_datain_unmarshall(task); + if (inq == NULL) { + fprintf(stderr, "failed to unmarshall inquiry datain blob\n"); + exit(10); + } + type = inq->periperal_device_type; + scsi_free_scsi_task(task); + + + + if (type == SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) { + struct scsi_readcapacity10 *rc10; + + task = iscsi_readcapacity10_sync(iscsi, lun, 0, 0); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, "failed to send readcapacity command\n"); + exit(10); + } + + rc10 = scsi_datain_unmarshall(task); + if (rc10 == NULL) { + fprintf(stderr, "failed to unmarshall readcapacity10 data\n"); + exit(10); + } + + size = rc10->block_size; + size *= rc10->lba; + + for (size_pf=0; size_pf<4 && size > 1024; size_pf++) { + size /= 1024; + } + + scsi_free_scsi_task(task); + } + + + printf("Lun:%-4d Type:%s", lun, scsi_devtype_to_str(type)); + if (type == SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) { + printf(" (Size:%lld%c)", size, sf[size_pf]); + } + printf("\n"); +} + +void list_luns(const char *target, const char *portal) +{ + struct iscsi_context *iscsi; + struct scsi_task *task; + struct scsi_reportluns_list *list; + int full_report_size; + int i; + + iscsi = iscsi_create_context(initiator); + if (iscsi == NULL) { + printf("Failed to create context\n"); + exit(10); + } + if (iscsi_set_targetname(iscsi, target)) { + fprintf(stderr, "Failed to set target name\n"); + exit(10); + } + iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); + if (iscsi_connect_sync(iscsi, portal) != 0) { + printf("iscsi_connect failed. %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + if (iscsi_login_sync(iscsi) != 0) { + fprintf(stderr, "login failed :%s\n", iscsi_get_error(iscsi)); + exit(10); + } + + + /* get initial reportluns data, all targets can report 16 bytes but some + * fail if we ask for too much. + */ + if ((task = iscsi_reportluns_sync(iscsi, 0, 16)) == NULL) { + fprintf(stderr, "reportluns failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + full_report_size = scsi_datain_getfullsize(task); + if (full_report_size > task->datain.size) { + scsi_free_scsi_task(task); + + /* we need more data for the full list */ + if ((task = iscsi_reportluns_sync(iscsi, 0, full_report_size)) == NULL) { + fprintf(stderr, "reportluns failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + } + + list = scsi_datain_unmarshall(task); + if (list == NULL) { + fprintf(stderr, "failed to unmarshall reportluns datain blob\n"); + exit(10); + } + for (i=0; i < (int)list->num; i++) { + show_lun(iscsi, list->luns[i]); + } + + scsi_free_scsi_task(task); + iscsi_destroy_context(iscsi); +} + + + + +void discoverylogout_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + struct client_state *state = (struct client_state *)private_data; + + if (status != 0) { + fprintf(stderr, "Failed to logout from target. : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + if (iscsi_disconnect(iscsi) != 0) { + fprintf(stderr, "Failed to disconnect old socket\n"); + exit(10); + } + + state->finished = 1; +} + +void discovery_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct iscsi_discovery_address *addr; + + if (status != 0) { + fprintf(stderr, "Failed to do discovery on target. : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + for(addr=command_data; addr; addr=addr->next) { + printf("Target:%s Portal:%s\n", addr->target_name, addr->target_address); + if (showluns != 0) { + list_luns(addr->target_name, addr->target_address); + } + } + + if (iscsi_logout_async(iscsi, discoverylogout_cb, private_data) != 0) { + fprintf(stderr, "iscsi_logout_async failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + + +void discoverylogin_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + if (status != 0) { + fprintf(stderr, "Failed to log in to target. : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + if (iscsi_discovery_async(iscsi, discovery_cb, private_data) != 0) { + fprintf(stderr, "failed to send discovery command : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + +void discoveryconnect_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data) +{ + if (status != 0) { + fprintf(stderr, "discoveryconnect_cb: connection failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + if (iscsi_login_async(iscsi, discoverylogin_cb, private_data) != 0) { + fprintf(stderr, "iscsi_login_async failed : %s\n", iscsi_get_error(iscsi)); + exit(10); + } +} + + +int main(int argc, const char *argv[]) +{ + struct iscsi_context *iscsi; + struct client_state state; + char *portal = NULL; + poptContext pc; + int res; + + struct poptOption popt_options[] = { + POPT_AUTOHELP + { "initiator-name", 'i', POPT_ARG_STRING, &initiator, 0, "Initiatorname to use", "iqn-name" }, + { "portal", 'p', POPT_ARG_STRING, &portal, 0, "Target portal", "address[:port]" }, + { "show-luns", 's', POPT_ARG_NONE, &showluns, 0, "Show the luns for each target", NULL }, + POPT_TABLEEND + }; + + bzero(&state, sizeof(state)); + + pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_POSIXMEHARDER); + if ((res = poptGetNextOpt(pc)) < -1) { + fprintf(stderr, "Failed to parse option : %s %s\n", + poptBadOption(pc, 0), poptStrerror(res)); + exit(10); + } + poptFreeContext(pc); + + if (portal == NULL) { + fprintf(stderr, "You must specify target portal\n"); + exit(10); + } + + iscsi = iscsi_create_context(initiator); + if (iscsi == NULL) { + printf("Failed to create context\n"); + exit(10); + } + + iscsi_set_session_type(iscsi, ISCSI_SESSION_DISCOVERY); + + if (iscsi_connect_async(iscsi, portal, discoveryconnect_cb, &state) != 0) { + fprintf(stderr, "iscsi_connect failed. %s\n", iscsi_get_error(iscsi)); + exit(10); + } + + event_loop(iscsi, &state); + + iscsi_destroy_context(iscsi); + return 0; +} +