From cb522f25cada0d3429c69d0fbaa5bd337a422cdd Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 8 Nov 2010 19:49:41 +1030 Subject: [PATCH] iscsi: new module from Ronnie. --- ccan/iscsi/Makefile | 39 +++ ccan/iscsi/_info | 28 ++ ccan/iscsi/discovery.c | 172 ++++++++++++ ccan/iscsi/dlinklist.h | 181 +++++++++++++ ccan/iscsi/init.c | 164 ++++++++++++ ccan/iscsi/iscsi-private.h | 143 ++++++++++ ccan/iscsi/iscsi.h | 262 +++++++++++++++++++ ccan/iscsi/login.c | 292 +++++++++++++++++++++ ccan/iscsi/nop.c | 92 +++++++ ccan/iscsi/pdu.c | 302 +++++++++++++++++++++ ccan/iscsi/scsi-command.c | 436 +++++++++++++++++++++++++++++++ ccan/iscsi/scsi-lowlevel.c | 463 +++++++++++++++++++++++++++++++++ ccan/iscsi/scsi-lowlevel.h | 194 ++++++++++++++ ccan/iscsi/socket.c | 329 +++++++++++++++++++++++ ccan/iscsi/tools/iscsiclient.c | 430 ++++++++++++++++++++++++++++++ 15 files changed, 3527 insertions(+) create mode 100644 ccan/iscsi/Makefile create mode 100644 ccan/iscsi/_info create mode 100644 ccan/iscsi/discovery.c create mode 100644 ccan/iscsi/dlinklist.h create mode 100644 ccan/iscsi/init.c create mode 100644 ccan/iscsi/iscsi-private.h create mode 100644 ccan/iscsi/iscsi.h create mode 100644 ccan/iscsi/login.c create mode 100644 ccan/iscsi/nop.c create mode 100644 ccan/iscsi/pdu.c create mode 100644 ccan/iscsi/scsi-command.c create mode 100644 ccan/iscsi/scsi-lowlevel.c create mode 100644 ccan/iscsi/scsi-lowlevel.h create mode 100644 ccan/iscsi/socket.c create mode 100644 ccan/iscsi/tools/iscsiclient.c diff --git a/ccan/iscsi/Makefile b/ccan/iscsi/Makefile new file mode 100644 index 00000000..6995bfd7 --- /dev/null +++ b/ccan/iscsi/Makefile @@ -0,0 +1,39 @@ +LIBS= +CC=gcc +CFLAGS=-g -O0 -Wall -W -I../.. "-D_U_=__attribute__((unused))" +LIBISCSI_OBJ = socket.o init.o login.o nop.o pdu.o discovery.o scsi-command.o scsi-lowlevel.o + +all: tools/iscsiclient + +tools/iscsiclient: tools/iscsiclient.o libiscsi.a + $(CC) $(CFLAGS) -o $@ tools/iscsiclient.o libiscsi.a $(LIBS) + +libiscsi.a: $(LIBISCSI_OBJ) + @echo Creating library $@ + ar r libiscsi.a $(LIBISCSI_OBJ) + ranlib libiscsi.a + +tools/iscsiclient.o: tools/iscsiclient.c + @echo Compiling $@ + $(CC) $(CFLAGS) -c tools/iscsiclient.c -o $@ + +socket.o: socket.c iscsi.h iscsi-private.h + +init.o: init.c iscsi.h iscsi-private.h + +login.o: login.c iscsi.h iscsi-private.h + +pdu.o: pdu.c iscsi.h iscsi-private.h + +nop.o: nop.c iscsi.h iscsi-private.h + +discovery.o: discovery.c iscsi.h iscsi-private.h + +scsi-command.o: scsi-command.c iscsi.h iscsi-private.h + +scsi-lowlevel.o: scsi-lowlevel.c scsi-lowlevel.h + +clean: + rm -f tools/iscsiclient + rm -f *.o + rm -f libiscsi.a diff --git a/ccan/iscsi/_info b/ccan/iscsi/_info new file mode 100644 index 00000000..6e02fec0 --- /dev/null +++ b/ccan/iscsi/_info @@ -0,0 +1,28 @@ +#include +#include + +/** + * iscsi - async library for iscsi functionality + * + * The iscsi module 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. + * + * License: GPL (3 or any later version) + * Author: Ronnie Sahlberg + */ +int main(int argc, char *argv[]) +{ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + return 0; + } + + return 1; +} + diff --git a/ccan/iscsi/discovery.c b/ccan/iscsi/discovery.c new file mode 100644 index 00000000..05250429 --- /dev/null +++ b/ccan/iscsi/discovery.c @@ -0,0 +1,172 @@ +/* + 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 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 . +*/ + +#define _GNU_SOURCE +#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 == NULL) { + printf("trying to send text on NULL context\n"); + return -1; + } + + if (iscsi->session_type != ISCSI_SESSION_DISCOVERY) { + printf("Trying to do discovery on non-discovery session\n"); + return -2; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_TEXT_REQUEST, ISCSI_PDU_TEXT_RESPONSE); + if (pdu == NULL) { + printf("Failed to allocate text pdu\n"); + return -3; + } + + /* 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 = "SendTargets=All"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -4; + } + + pdu->callback = cb; + pdu->private_data = private_data; + + if (iscsi_queue_pdu(iscsi, pdu) != 0) { + printf("failed to queue iscsi text pdu\n"); + iscsi_free_pdu(iscsi, pdu); + return -5; + } + + return 0; +} + +static void iscsi_free_discovery_addresses(struct iscsi_discovery_address *addresses) +{ + while (addresses != NULL) { + struct iscsi_discovery_address *next = addresses->next; + + if (addresses->target_name != NULL) { + free(discard_const(addresses->target_name)); + addresses->target_name = NULL; + } + if (addresses->target_address != 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) { + printf("unsupported flags in text reply %02x\n", hdr[1]); + pdu->callback(iscsi, ISCSI_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) { + printf("len > size when parsing discovery data %d>%d\n", len, size); + pdu->callback(iscsi, ISCSI_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) { + printf("Failed to allocate data for new discovered target\n"); + pdu->callback(iscsi, ISCSI_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) { + printf("Failed to allocate data for new discovered target name\n"); + pdu->callback(iscsi, ISCSI_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) { + printf("Failed to allocate data for new discovered target address\n"); + pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data); + iscsi_free_discovery_addresses(targets); + return -1; + } + } else { + printf("Dont know how to handle discovery string : %s\n", hdr); + pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data); + iscsi_free_discovery_addresses(targets); + return -1; + } + + hdr += len + 1; + size -= len + 1; + } + + pdu->callback(iscsi, ISCSI_STATUS_GOOD, targets, pdu->private_data); + iscsi_free_discovery_addresses(targets); + + return 0; +} diff --git a/ccan/iscsi/dlinklist.h b/ccan/iscsi/dlinklist.h new file mode 100644 index 00000000..6d525f90 --- /dev/null +++ b/ccan/iscsi/dlinklist.h @@ -0,0 +1,181 @@ +/* + Unix SMB/CIFS implementation. + some simple double linked list macros + + Copyright (C) Andrew Tridgell 1998-2010 + + 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 . +*/ + +/* To use these macros you must have a structure containing a next and + prev pointer */ + +#ifndef _DLINKLIST_H +#define _DLINKLIST_H + +/* + February 2010 - changed list format to have a prev pointer from the + list head. This makes DLIST_ADD_END() O(1) even though we only have + one list pointer. + + The scheme is as follows: + + 1) with no entries in the list: + list_head == NULL + + 2) with 1 entry in the list: + list_head->next == NULL + list_head->prev == list_head + + 3) with 2 entries in the list: + list_head->next == element2 + list_head->prev == element2 + element2->prev == list_head + element2->next == NULL + + 4) with N entries in the list: + list_head->next == element2 + list_head->prev == elementN + elementN->prev == element{N-1} + elementN->next == NULL + + This allows us to find the tail of the list by using + list_head->prev, which means we can add to the end of the list in + O(1) time + + + Note that the 'type' arguments below are no longer needed, but + are kept for now to prevent an incompatible argument change + */ + + +/* + add an element at the front of a list +*/ +#define DLIST_ADD(list, p) \ +do { \ + if (!(list)) { \ + (p)->prev = (list) = (p); \ + (p)->next = NULL; \ + } else { \ + (p)->prev = (list)->prev; \ + (list)->prev = (p); \ + (p)->next = (list); \ + (list) = (p); \ + } \ +} while (0) + +/* + remove an element from a list + Note that the element doesn't have to be in the list. If it + isn't then this is a no-op +*/ +#define DLIST_REMOVE(list, p) \ +do { \ + if ((p) == (list)) { \ + if ((p)->next) (p)->next->prev = (p)->prev; \ + (list) = (p)->next; \ + } else if ((list) && (p) == (list)->prev) { \ + (p)->prev->next = NULL; \ + (list)->prev = (p)->prev; \ + } else { \ + if ((p)->prev) (p)->prev->next = (p)->next; \ + if ((p)->next) (p)->next->prev = (p)->prev; \ + } \ + if ((p) != (list)) (p)->next = (p)->prev = NULL; \ +} while (0) + +/* + find the head of the list given any element in it. + Note that this costs O(N), so you should avoid this macro + if at all possible! +*/ +#define DLIST_HEAD(p, result_head) \ +do { \ + (result_head) = (p); \ + while (DLIST_PREV(result_head)) (result_head) = (result_head)->prev; \ +} while(0) + +/* return the last element in the list */ +#define DLIST_TAIL(list) ((list)?(list)->prev:NULL) + +/* return the previous element in the list. */ +#define DLIST_PREV(p) (((p)->prev && (p)->prev->next != NULL)?(p)->prev:NULL) + +/* insert 'p' after the given element 'el' in a list. If el is NULL then + this is the same as a DLIST_ADD() */ +#define DLIST_ADD_AFTER(list, p, el) \ +do { \ + if (!(list) || !(el)) { \ + DLIST_ADD(list, p); \ + } else { \ + (p)->prev = (el); \ + (p)->next = (el)->next; \ + (el)->next = (p); \ + if ((p)->next) (p)->next->prev = (p); \ + if ((list)->prev == (el)) (list)->prev = (p); \ + }\ +} while (0) + + +/* + add to the end of a list. + Note that 'type' is ignored +*/ +#define DLIST_ADD_END(list, p, type) \ +do { \ + if (!(list)) { \ + DLIST_ADD(list, p); \ + } else { \ + DLIST_ADD_AFTER(list, p, (list)->prev); \ + } \ +} while (0) + +/* promote an element to the from of a list */ +#define DLIST_PROMOTE(list, p) \ +do { \ + DLIST_REMOVE(list, p); \ + DLIST_ADD(list, p); \ +} while (0) + +/* + demote an element to the end of a list. + Note that 'type' is ignored +*/ +#define DLIST_DEMOTE(list, p, type) \ +do { \ + DLIST_REMOVE(list, p); \ + DLIST_ADD_END(list, p, NULL); \ +} while (0) + +/* + concatenate two lists - putting all elements of the 2nd list at the + end of the first list. + Note that 'type' is ignored +*/ +#define DLIST_CONCATENATE(list1, list2, type) \ +do { \ + if (!(list1)) { \ + (list1) = (list2); \ + } else { \ + (list1)->prev->next = (list2); \ + if (list2) { \ + void *_tmplist = (void *)(list1)->prev; \ + (list1)->prev = (list2)->prev; \ + (list2)->prev = _tmplist; \ + } \ + } \ +} while (0) + +#endif /* _DLINKLIST_H */ diff --git a/ccan/iscsi/init.c b/ccan/iscsi/init.c new file mode 100644 index 00000000..096985e4 --- /dev/null +++ b/ccan/iscsi/init.c @@ -0,0 +1,164 @@ +/* + 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 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "dlinklist.h" + + +struct iscsi_context *iscsi_create_context(const char *initiator_name) +{ + struct iscsi_context *iscsi; + + iscsi = malloc(sizeof(struct iscsi_context)); + if (iscsi == NULL) { + printf("Failed to allocate iscsi context\n"); + return NULL; + } + + bzero(iscsi, sizeof(struct iscsi_context)); + + iscsi->initiator_name = strdup(initiator_name); + if (iscsi->initiator_name == NULL) { + printf("Failed to allocate initiator name context\n"); + free(iscsi); + return NULL; + } + + iscsi->fd = -1; + + /* use a "random" isid */ + srandom(getpid() ^ time(NULL)); + iscsi_set_random_isid(iscsi); + + return iscsi; +} + +int iscsi_set_random_isid(struct iscsi_context *iscsi) +{ + iscsi->isid[0] = 0x80; + iscsi->isid[1] = random()&0xff; + iscsi->isid[2] = random()&0xff; + iscsi->isid[3] = random()&0xff; + iscsi->isid[4] = 0; + iscsi->isid[5] = 0; + + return 0; +} + +int iscsi_set_alias(struct iscsi_context *iscsi, const char *alias) +{ + if (iscsi == NULL) { + printf("Context is NULL when adding alias\n"); + return -1; + } + if (iscsi->is_loggedin != 0) { + printf("Already logged in when adding alias\n"); + return -2; + } + + if (iscsi->alias != NULL) { + free(discard_const(iscsi->alias)); + iscsi->alias = NULL; + } + + iscsi->alias = strdup(alias); + if (iscsi->alias == NULL) { + printf("Failed to allocate alias name\n"); + return -3; + } + + return 0; +} + +int iscsi_set_targetname(struct iscsi_context *iscsi, const char *target_name) +{ + if (iscsi == NULL) { + printf("Context is NULL when adding targetname\n"); + return -1; + } + if (iscsi->is_loggedin != 0) { + printf("Already logged in when adding targetname\n"); + return -2; + } + + if (iscsi->target_name != NULL) { + free(discard_const(iscsi->target_name)); + iscsi->target_name= NULL; + } + + iscsi->target_name = strdup(target_name); + if (iscsi->target_name == NULL) { + printf("Failed to allocate target name\n"); + return -3; + } + + return 0; +} + +int iscsi_destroy_context(struct iscsi_context *iscsi) +{ + struct iscsi_pdu *pdu; + + if (iscsi == NULL) { + return 0; + } + if (iscsi->initiator_name != NULL) { + free(discard_const(iscsi->initiator_name)); + iscsi->initiator_name = NULL; + } + if (iscsi->alias != NULL) { + free(discard_const(iscsi->alias)); + iscsi->alias = NULL; + } + if (iscsi->is_loggedin != 0) { + printf("deswtroying context while logged in\n"); + } + if (iscsi->fd != -1) { + iscsi_disconnect(iscsi); + } + + if (iscsi->inbuf != NULL) { + free(iscsi->inbuf); + iscsi->inbuf = NULL; + iscsi->insize = 0; + iscsi->inpos = 0; + } + + while ((pdu = iscsi->outqueue)) { + DLIST_REMOVE(iscsi->outqueue, pdu); + pdu->callback(iscsi, ISCSI_STATUS_CANCELLED, NULL, pdu->private_data); + iscsi_free_pdu(iscsi, pdu); + } + while ((pdu = iscsi->waitpdu)) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + pdu->callback(iscsi, ISCSI_STATUS_CANCELLED, NULL, pdu->private_data); + iscsi_free_pdu(iscsi, pdu); + } + + free(iscsi); + + return 0; +} diff --git a/ccan/iscsi/iscsi-private.h b/ccan/iscsi/iscsi-private.h new file mode 100644 index 00000000..8c95b4d1 --- /dev/null +++ b/ccan/iscsi/iscsi-private.h @@ -0,0 +1,143 @@ +/* + 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 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 . +*/ + +#include + +#ifndef _U_ +#define _U_ +#endif + +#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; + + int fd; + int is_connected; + int is_loggedin; + + iscsi_command_cb connect_cb; + void *connect_data; + + struct iscsi_pdu *outqueue; + struct iscsi_pdu *waitpdu; + + int insize; + int inpos; + unsigned char *inbuf; +}; + +#define ISCSI_HEADER_SIZE 48 + +#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 *prev, *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_data *data, unsigned char *dptr, int dsize, int pdualignment); +int iscsi_set_random_isid(struct iscsi_context *iscsi); + +struct scsi_task; +void iscsi_pdu_set_cdb(struct iscsi_pdu *pdu, struct scsi_task *task); + +int iscsi_get_pdu_size(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); diff --git a/ccan/iscsi/iscsi.h b/ccan/iscsi/iscsi.h new file mode 100644 index 00000000..47c99169 --- /dev/null +++ b/ccan/iscsi/iscsi.h @@ -0,0 +1,262 @@ +/* + 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 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 . +*/ + +struct iscsi_context; +struct sockaddr; + + +/* + * 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 behind + * 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); + + +/* ISCSI_STATUS_GOOD must map to SCSI_STATUS_GOOD + * and ISCSI_STATUS_CHECK_CONDITION must map to SCSI_STATUS_CHECK_CONDITION + */ +enum icsi_status { ISCSI_STATUS_GOOD =0, + ISCSI_STATUS_CHECK_CONDITION =2, + ISCSI_STATUS_CANCELLED =0x0f000000, + ISCSI_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 *target, iscsi_command_cb cb, void *private_data); + +/* + * 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); + + +/* + * 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); + + +/* + * 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 + */ +int iscsi_reportluns_async(struct iscsi_context *iscsi, iscsi_command_cb cb, int report_type, int alloc_len, 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, iscsi_command_cb cb, int evpd, int page_code, int maxsize, void *private_data); +int iscsi_readcapacity10_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int lba, int pmi, void *private_data); +int iscsi_read10_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int lba, int datalen, int blocksize, void *private_data); +int iscsi_write10_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, unsigned char *data, int datalen, int lba, int fua, int fuanv, int blocksize, void *private_data); +int iscsi_modesense6_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int dbd, int pc, int page_code, int sub_page_code, unsigned char alloc_len, void *private_data); + + + diff --git a/ccan/iscsi/login.c b/ccan/iscsi/login.c new file mode 100644 index 00000000..ff0d4cce --- /dev/null +++ b/ccan/iscsi/login.c @@ -0,0 +1,292 @@ +/* + 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 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 . +*/ + +#define _GNU_SOURCE +#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; + int ret; + + if (iscsi == NULL) { + printf("trying to login on NULL context\n"); + return -1; + } + + if (iscsi->is_loggedin != 0) { + printf("trying to login while already logged in\n"); + return -2; + } + + switch (iscsi->session_type) { + case ISCSI_SESSION_DISCOVERY: + case ISCSI_SESSION_NORMAL: + break; + default: + printf("trying to login without setting session type\n"); + return -3; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_LOGIN_REQUEST, ISCSI_PDU_LOGIN_RESPONSE); + if (pdu == NULL) { + printf("Failed to allocate login pdu\n"); + return -4; + } + + /* 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 (asprintf(&str, "InitiatorName=%s", iscsi->initiator_name) == -1) { + printf("asprintf failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -5; + } + ret = iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1); + free(str); + if (ret != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -6; + } + + /* optional alias */ + if (iscsi->alias) { + if (asprintf(&str, "InitiatorAlias=%s", iscsi->alias) == -1) { + printf("asprintf failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -7; + } + ret = iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1); + free(str); + if (ret != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -8; + } + } + + /* target name */ + if (iscsi->session_type == ISCSI_SESSION_NORMAL) { + if (iscsi->target_name == NULL) { + printf("trying normal connect but target name not set\n"); + iscsi_free_pdu(iscsi, pdu); + return -9; + } + + if (asprintf(&str, "TargetName=%s", iscsi->target_name) == -1) { + printf("asprintf failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -10; + } + ret = iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1); + free(str); + if (ret != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -11; + } + } + + /* session type */ + switch (iscsi->session_type) { + case ISCSI_SESSION_DISCOVERY: + str = "SessionType=Discovery"; + break; + case ISCSI_SESSION_NORMAL: + str = "SessionType=Normal"; + break; + default: + printf("can not handle sessions %d yet\n", iscsi->session_type); + return -12; + } + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -13; + } + + str = "HeaderDigest=None"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -14; + } + str = "DataDigest=None"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -15; + } + str = "InitialR2T=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -16; + } + str = "ImmediateData=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -17; + } + str = "MaxBurstLength=262144"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -18; + } + str = "FirstBurstLength=262144"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -19; + } + str = "MaxRecvDataSegmentLength=262144"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -20; + } + str = "DataPDUInOrder=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -21; + } + str = "DataSequenceInOrder=Yes"; + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) { + printf("pdu add data failed\n"); + iscsi_free_pdu(iscsi, pdu); + return -22; + } + + + pdu->callback = cb; + pdu->private_data = private_data; + + if (iscsi_queue_pdu(iscsi, pdu) != 0) { + printf("failed to queue iscsi login pdu\n"); + iscsi_free_pdu(iscsi, pdu); + return -23; + } + + 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) { + printf("dont have enough data to read status from login reply\n"); + return -1; + } + + /* 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 + */ + status = ntohs(*(uint16_t *)&hdr[36]); + if (status != 0) { + pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data); + return 0; + } + + iscsi->statsn = ntohs(*(uint16_t *)&hdr[24]); + + iscsi->is_loggedin = 1; + pdu->callback(iscsi, ISCSI_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 == NULL) { + printf("trying to logout on NULL context\n"); + return -1; + } + + if (iscsi->is_loggedin == 0) { + printf("trying to logout while not logged in\n"); + return -2; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_LOGOUT_REQUEST, ISCSI_PDU_LOGOUT_RESPONSE); + if (pdu == NULL) { + printf("Failed to allocate logout pdu\n"); + return -3; + } + + /* 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) { + printf("failed to queue iscsi logout pdu\n"); + iscsi_free_pdu(iscsi, pdu); + return -4; + } + + return 0; +} + +int iscsi_process_logout_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size _U_) +{ + iscsi->is_loggedin = 0; + pdu->callback(iscsi, ISCSI_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 == NULL) { + printf("Trying to set sesssion type on NULL context\n"); + return -1; + } + if (iscsi->is_loggedin) { + printf("trying to set session type while logged in\n"); + return -2; + } + + iscsi->session_type = session_type; + + return 0; +} diff --git a/ccan/iscsi/nop.c b/ccan/iscsi/nop.c new file mode 100644 index 00000000..e12bd48a --- /dev/null +++ b/ccan/iscsi/nop.c @@ -0,0 +1,92 @@ +/* + 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 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 . +*/ + +#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 == NULL) { + printf("trying to send nop-out on NULL context\n"); + return -1; + } + + if (iscsi->is_loggedin == 0) { + printf("trying send nop-out while not logged in\n"); + return -2; + } + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_NOP_OUT, ISCSI_PDU_NOP_IN); + if (pdu == NULL) { + printf("Failed to allocate nop-out pdu\n"); + return -3; + } + + /* 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; +// iscsi->cmdsn++; + + pdu->callback = cb; + pdu->private_data = private_data; + + if (iscsi_pdu_add_data(iscsi, pdu, data, len) != 0) { + printf("Failed to add outdata to nop-out\n"); + iscsi_free_pdu(iscsi, pdu); + return -4; + } + + + if (iscsi_queue_pdu(iscsi, pdu) != 0) { + printf("failed to queue iscsi nop-out pdu\n"); + iscsi_free_pdu(iscsi, pdu); + return -5; + } + + 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, ISCSI_STATUS_GOOD, &data, pdu->private_data); + + return 0; +} diff --git a/ccan/iscsi/pdu.c b/ccan/iscsi/pdu.c new file mode 100644 index 00000000..e073ebab --- /dev/null +++ b/ccan/iscsi/pdu.c @@ -0,0 +1,302 @@ +/* + 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 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 . +*/ + +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "scsi-lowlevel.h" +#include "dlinklist.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) { + printf("failed to allocate pdu\n"); + 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) { + printf("failed to allocate pdu header\n"); + free(pdu); + pdu = NULL; + 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) { + printf("trying to free NULL pdu\n"); + return; + } + if (pdu->outdata.data) { + free(pdu->outdata.data); + pdu->outdata.data = NULL; + } + if (pdu->indata.data) { + 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_data *data, unsigned char *dptr, int dsize, int pdualignment) +{ + int len, aligned; + unsigned char *buf; + + if (dsize == 0) { + printf("Trying to append zero size data to iscsi_data\n"); + return -1; + } + + len = data->size + dsize; + aligned = len; + if (pdualignment) { + aligned = (aligned+3)&0xfffffffc; + } + buf = malloc(aligned); + if (buf == NULL) { + printf("failed to allocate buffer for %d bytes\n", len); + return -2; + } + + 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) +{ + int len, aligned; + unsigned char *buf; + + if (pdu == NULL) { + printf("trying to add data to NULL pdu\n"); + return -1; + } + if (dsize == 0) { + printf("Trying to append zero size data to pdu\n"); + return -2; + } + + if (iscsi_add_data(&pdu->outdata, dptr, dsize, 1) != 0) { + printf("failed to add data to pdu buffer\n"); + return -3; + } + + /* update data segment length */ + *(uint32_t *)&pdu->outdata.data[4] = htonl(pdu->outdata.size-ISCSI_HEADER_SIZE); + + return 0; +} + +int iscsi_get_pdu_size(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) { + printf("cant handle expanded headers yet\n"); + 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, the 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) { + printf("Got wrong opcode back for itt:%d got:%d expected %d\n", itt, opcode, pdu->response_opcode); + return -1; + } + switch (opcode) { + case ISCSI_PDU_LOGIN_RESPONSE: + if (iscsi_process_login_reply(iscsi, pdu, hdr, size) != 0) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + printf("iscsi login reply failed\n"); + return -2; + } + break; + case ISCSI_PDU_TEXT_RESPONSE: + if (iscsi_process_text_reply(iscsi, pdu, hdr, size) != 0) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + printf("iscsi text reply failed\n"); + return -2; + } + break; + case ISCSI_PDU_LOGOUT_RESPONSE: + if (iscsi_process_logout_reply(iscsi, pdu, hdr, size) != 0) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + printf("iscsi logout reply failed\n"); + return -3; + } + break; + case ISCSI_PDU_SCSI_RESPONSE: + if (iscsi_process_scsi_reply(iscsi, pdu, hdr, size) != 0) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + printf("iscsi response reply failed\n"); + return -4; + } + break; + case ISCSI_PDU_DATA_IN: + if (iscsi_process_scsi_data_in(iscsi, pdu, hdr, size, &is_finished) != 0) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + printf("iscsi data in failed\n"); + return -4; + } + break; + case ISCSI_PDU_NOP_IN: + if (iscsi_process_nop_out_reply(iscsi, pdu, hdr, size) != 0) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + printf("iscsi nop-in failed\n"); + return -5; + } + break; + default: + printf("Dont know how to handle opcode %d\n", opcode); + return -2; + } + + if (is_finished) { + DLIST_REMOVE(iscsi->waitpdu, pdu); + iscsi_free_pdu(iscsi, pdu); + } else { + printf("pdu is not yet finished, let it remain\n"); + } + 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/ccan/iscsi/scsi-command.c b/ccan/iscsi/scsi-command.c new file mode 100644 index 00000000..15782eb6 --- /dev/null +++ b/ccan/iscsi/scsi-command.c @@ -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 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 . +*/ + +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "scsi-lowlevel.h" +#include "dlinklist.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); +} + +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 ISCSI_STATUS_CHECK_CONDITION: + scsi_cbdata->callback(iscsi, ISCSI_STATUS_CHECK_CONDITION, task, scsi_cbdata->private_data); + return; + + + case ISCSI_STATUS_GOOD: + scsi_cbdata->callback(iscsi, ISCSI_STATUS_GOOD, task, scsi_cbdata->private_data); + return; + default: + printf("Cant handle scsi status %d yet\n", status); + scsi_cbdata->callback(iscsi, ISCSI_STATUS_ERROR, task, scsi_cbdata->private_data); + } +} + + +static 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 == NULL) { + printf("trying to send command on NULL context\n"); + scsi_free_scsi_task(task); + return -1; + } + + if (iscsi->session_type != ISCSI_SESSION_NORMAL) { + printf("Trying to send command on discovery session\n"); + scsi_free_scsi_task(task); + return -2; + } + + if (iscsi->is_loggedin == 0) { + printf("Trying to send command while not logged in\n"); + scsi_free_scsi_task(task); + return -3; + } + + scsi_cbdata = malloc(sizeof(struct iscsi_scsi_cbdata)); + if (scsi_cbdata == NULL) { + printf("failed to allocate scsi cbdata\n"); + scsi_free_scsi_task(task); + return -4; + } + bzero(scsi_cbdata, sizeof(struct iscsi_scsi_cbdata)); + scsi_cbdata->task = task; + scsi_cbdata->callback = cb; + scsi_cbdata->private_data = private_data; + + pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_SCSI_REQUEST, ISCSI_PDU_SCSI_RESPONSE); + if (pdu == NULL) { + printf("Failed to allocate text pdu\n"); + iscsi_free_scsi_cbdata(scsi_cbdata); + return -5; + } + 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) { + printf("DATA-OUT command but data == NULL\n"); + iscsi_free_pdu(iscsi, pdu); + return -5; + } + if (data->size != task->expxferlen) { + printf("data size:%d is not same as expected data transfer length:%d\n", data->size, task->expxferlen); + iscsi_free_pdu(iscsi, pdu); + return -7; + } + if (iscsi_pdu_add_data(iscsi, pdu, data->data, data->size) != 0) { + printf("Failed to add outdata to the pdu\n"); + iscsi_free_pdu(iscsi, pdu); + return -6; + } + + 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) { + printf("failed to queue iscsi scsi pdu\n"); + iscsi_free_pdu(iscsi, pdu); + return -6; + } + + 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) { + printf("scsi response pdu but Final bit is not set: 0x%02x\n", flags); + pdu->callback(iscsi, ISCSI_STATUS_ERROR, task, pdu->private_data); + return -1; + } + if ((flags&ISCSI_PDU_DATA_ACK_REQUESTED) != 0) { + printf("scsi response asked for ACK 0x%02x\n", flags); + pdu->callback(iscsi, ISCSI_STATUS_ERROR, task, pdu->private_data); + return -1; + } + /* for now, we ignore all overflow/underflow flags. We just print/log them so we can tweak + * libiscsi to not generate under/over flows + */ + if ((flags&ISCSI_PDU_DATA_BIDIR_OVERFLOW) != 0) { + printf("scsi response contains bidir overflow 0x%02x\n", flags); + } + if ((flags&ISCSI_PDU_DATA_BIDIR_UNDERFLOW) != 0) { + printf("scsi response contains bidir underflow 0x%02x\n", flags); + } + if ((flags&ISCSI_PDU_DATA_RESIDUAL_OVERFLOW) != 0) { + printf("scsi response contains residual overflow 0x%02x\n", flags); + } + if ((flags&ISCSI_PDU_DATA_RESIDUAL_UNDERFLOW) != 0) { + printf("scsi response contains residual underflow 0x%02x\n", flags); + } + + + response = hdr[2]; + if (response != 0x00) { + printf("scsi reply response field:%d\n", response); + } + + status = hdr[3]; + + switch (status) { + case ISCSI_STATUS_GOOD: + task->datain.data = pdu->indata.data; + task->datain.size = pdu->indata.size; + + pdu->callback(iscsi, ISCSI_STATUS_GOOD, &pdu->indata, pdu->private_data); + break; + case ISCSI_STATUS_CHECK_CONDITION: + task->datain.data = discard_const(hdr + ISCSI_HEADER_SIZE); + task->datain.size = size - ISCSI_HEADER_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])); + + pdu->callback(iscsi, ISCSI_STATUS_CHECK_CONDITION, task, pdu->private_data); + break; + default: + printf("Unknown status :%d\n", status); + + pdu->callback(iscsi, ISCSI_STATUS_ERROR, task, pdu->private_data); + return -1; + } + + return 0; +} + +int iscsi_process_scsi_data_in(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size, int *is_finished) +{ + int statsn, flags, status; + struct iscsi_scsi_cbdata *scsi_cbdata = pdu->scsi_cbdata; + struct scsi_task *task = scsi_cbdata->task; + int dsl; + + statsn = ntohl(*(uint32_t *)&hdr[24]); + if (statsn > (int)iscsi->statsn) { + iscsi->statsn = statsn; + } + + flags = hdr[1]; + if ((flags&ISCSI_PDU_DATA_ACK_REQUESTED) != 0) { + printf("scsi response asked for ACK 0x%02x\n", flags); + pdu->callback(iscsi, ISCSI_STATUS_ERROR, task, pdu->private_data); + return -1; + } + /* for now, we ignore all overflow/underflow flags. We just print/log them so we can tweak + * libiscsi to not generate under/over flows + */ + if ((flags&ISCSI_PDU_DATA_RESIDUAL_OVERFLOW) != 0) { + printf("scsi response contains residual overflow 0x%02x\n", flags); + } + if ((flags&ISCSI_PDU_DATA_RESIDUAL_UNDERFLOW) != 0) { + printf("scsi response contains residual underflow 0x%02x\n", flags); + } + + dsl = ntohl(*(uint32_t *)&hdr[4])&0x00ffffff; + + if (dsl > size - ISCSI_HEADER_SIZE) { + printf ("dsl is :%d, while buffser size if %d\n", dsl, size - ISCSI_HEADER_SIZE); + } + + if (iscsi_add_data(&pdu->indata, discard_const(hdr + ISCSI_HEADER_SIZE), dsl, 0) != 0) { + printf("failed to add data to pdu in buffer\n"); + return -3; + } + + + if ((flags&ISCSI_PDU_DATA_FINAL) == 0) { + printf("scsi data-in without Final bit: 0x%02x\n", flags); + *is_finished = 0; + } + if ((flags&ISCSI_PDU_DATA_CONTAINS_STATUS) == 0) { + printf("scsi data-in without Status bit: 0x%02x\n", flags); + *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->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; + + if ((task = scsi_cdb_testunitready()) == NULL) { + printf("Failed to create testunitready cdb\n"); + return -1; + } + ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL, private_data); + + return ret; +} + + +int iscsi_reportluns_async(struct iscsi_context *iscsi, iscsi_command_cb cb, int report_type, int alloc_len, void *private_data) +{ + struct scsi_task *task; + int ret; + + if (alloc_len < 16) { + printf("Minimum allowed alloc len for reportluns is 16. You specified %d\n", alloc_len); + return -1; + } + + if ((task = scsi_reportluns_cdb(report_type, alloc_len)) == NULL) { + printf("Failed to create reportluns cdb\n"); + return -2; + } + /* report luns are always sent to lun 0 */ + ret = iscsi_scsi_command_async(iscsi, 0, task, cb, NULL, private_data); + + return ret; +} + +int iscsi_inquiry_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int evpd, int page_code, int maxsize, void *private_data) +{ + struct scsi_task *task; + int ret; + + if ((task = scsi_cdb_inquiry(evpd, page_code, maxsize)) == NULL) { + printf("Failed to create inquiry cdb\n"); + 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, iscsi_command_cb cb, int lba, int pmi, void *private_data) +{ + struct scsi_task *task; + int ret; + + if ((task = scsi_cdb_readcapacity10(lba, pmi)) == NULL) { + printf("Failed to create readcapacity10 cdb\n"); + 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, iscsi_command_cb cb, int lba, int datalen, int blocksize, void *private_data) +{ + struct scsi_task *task; + int ret; + + if (datalen % blocksize != 0) { + printf("datalen:%d is not a multiple of the blocksize:%d\n", datalen, blocksize); + return -1; + } + + if ((task = scsi_cdb_read10(lba, datalen, blocksize)) == NULL) { + printf("Failed to create read10 cdb\n"); + return -2; + } + ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL, private_data); + + return ret; +} + + +int iscsi_write10_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, unsigned char *data, int datalen, int lba, int fua, int fuanv, int blocksize, void *private_data) +{ + struct scsi_task *task; + struct iscsi_data outdata; + int ret; + + if (datalen % blocksize != 0) { + printf("datalen:%d is not a multiple of the blocksize:%d\n", datalen, blocksize); + return -1; + } + + if ((task = scsi_cdb_write10(lba, datalen, fua, fuanv, blocksize)) == NULL) { + printf("Failed to create read10 cdb\n"); + return -2; + } + + outdata.data = data; + outdata.size = datalen; + + ret = iscsi_scsi_command_async(iscsi, lun, task, cb, &outdata, private_data); + + return ret; +} + +int iscsi_modesense6_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int dbd, int pc, int page_code, int sub_page_code, unsigned char alloc_len, void *private_data) +{ + struct scsi_task *task; + int ret; + + if ((task = scsi_cdb_modesense6(dbd, pc, page_code, sub_page_code, alloc_len)) == NULL) { + printf("Failed to create modesense6 cdb\n"); + return -2; + } + ret = iscsi_scsi_command_async(iscsi, lun, task, cb, NULL, private_data); + + return ret; +} + diff --git a/ccan/iscsi/scsi-lowlevel.c b/ccan/iscsi/scsi-lowlevel.c new file mode 100644 index 00000000..7199bf82 --- /dev/null +++ b/ccan/iscsi/scsi-lowlevel.c @@ -0,0 +1,463 @@ +/* + 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 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 . +*/ +/* + * 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 "dlinklist.h" + + +void scsi_free_scsi_task(struct scsi_task *task) +{ + struct scsi_allocated_memory *mem; + + while((mem = task->mem)) { + DLIST_REMOVE(task->mem, mem); + free(mem); + } + free(task); +} + +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) { + printf("Failed to allocate memory to scsi task\n"); + return NULL; + } + bzero(mem, sizeof(struct scsi_allocated_memory)); + mem->ptr = malloc(size); + if (mem->ptr == NULL) { + printf("Failed to allocate memory buffer for scsi task\n"); + free(mem); + return NULL; + } + bzero(mem->ptr, size); + DLIST_ADD(task->mem, mem); + return mem->ptr; +} + +/* + * TESTUNITREADY + */ +struct scsi_task *scsi_cdb_testunitready(void) +{ + struct scsi_task *task; + + task = malloc(sizeof(struct scsi_task)); + if (task == NULL) { + printf("Failed to allocate scsi task structure\n"); + 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) { + printf("Failed to allocate scsi task structure\n"); + 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) { + printf("not enough data for reportluns list length\n"); + return NULL; + } + + list_size = htonl(*(uint32_t *)&(task->datain.data[0])) + 8; + if (list_size < task->datain.size) { + printf("not enough data to unmarshall reportluns data\n"); + 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) { + printf("Failed to allocate reportluns structure\n"); + return NULL; + } + + list->num = num_luns; + for (i=0; iluns[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) { + printf("Failed to allocate scsi task structure\n"); + 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) { + printf("Not enough data to unmarshall readcapacity10\n"); + return NULL; + } + rc10 = malloc(sizeof(struct scsi_readcapacity10)); + if (rc10 == NULL) { + printf("Failed to allocate readcapacity10 structure\n"); + 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) { + printf("Failed to allocate scsi task structure\n"); + 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) { + printf("Can not handle extended inquiry yet\n"); + return -1; + } + + /* standard inquiry*/ + return task->datain.data[4] + 3; +} + +/* + * unmarshall the data in blob for inquiry into a structure + */ +void *scsi_inquiry_datain_unmarshall(struct scsi_task *task) +{ + struct scsi_inquiry_standard *inq; + + if (task->params.inquiry.evpd != 0) { + printf("Can not handle extended inquiry yet\n"); + return NULL; + } + + /* standard inquiry */ + inq = scsi_malloc(task, sizeof(struct scsi_inquiry_standard)); + if (inq == NULL) { + printf("Failed to allocate standard inquiry structure\n"); + 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; + + 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); + + return inq; +} + +/* + * 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) { + printf("Failed to allocate scsi task structure\n"); + 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) { + printf("Failed to allocate scsi task structure\n"); + 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) { + printf("Failed to allocate scsi task structure\n"); + 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 report luns datain structure + */ +static int scsi_modesense6_datain_getfullsize(struct scsi_task *task) +{ + int len; + + len = task->datain.data[0] + 1; + + return len; +} + + + +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_READ10: +// case SCSI_OPCODE_WRITE10: + case SCSI_OPCODE_REPORTLUNS: + return scsi_reportluns_datain_getfullsize(task); + } + printf("Unknown opcode:%d for datain get full size\n", task->cdb[0]); + 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_READ10: +// case SCSI_OPCODE_WRITE10: + case SCSI_OPCODE_REPORTLUNS: + return scsi_reportluns_datain_unmarshall(task); + } + printf("Unknown opcode:%d for datain unmarshall\n", task->cdb[0]); + return NULL; +} + diff --git a/ccan/iscsi/scsi-lowlevel.h b/ccan/iscsi/scsi-lowlevel.h new file mode 100644 index 00000000..9417f42f --- /dev/null +++ b/ccan/iscsi/scsi-lowlevel.h @@ -0,0 +1,194 @@ +/* + 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 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 . +*/ + +#ifndef _U_ +#define _U_ +#endif + +#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_REPORTLUNS=0xA0}; + +/* sense keys */ +#define SCSI_SENSE_KEY_ILLEGAL_REQUEST 0x05 +#define SCSI_SENSE_KEY_UNIT_ATTENTION 0x06 + +/* ascq */ +#define SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB 0x2400 +#define SCSI_SENSE_ASCQ_BUS_RESET 0x2900 + +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; + unsigned char key; + int ascq; +}; + +struct scsi_data { + int size; + unsigned char *data; +}; + +struct scsi_allocated_memory { + struct scsi_allocated_memory *prev, *next; + void *ptr; +}; + +struct scsi_task { + 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 scsi_free_scsi_task(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}; + +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}; + +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; + + char vendor_identification[8+1]; + char product_identification[16+1]; + char product_revision_level[4+1]; +}; + +struct scsi_task *scsi_cdb_inquiry(int evpd, int page_code, int alloc_len); + + + +/* + * 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); + diff --git a/ccan/iscsi/socket.c b/ccan/iscsi/socket.c new file mode 100644 index 00000000..6f1eedda --- /dev/null +++ b/ccan/iscsi/socket.c @@ -0,0 +1,329 @@ +/* + 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 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "iscsi-private.h" +#include "dlinklist.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 *target, 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 == NULL) { + printf("Trying to connect NULL context\n"); + return -1; + } + if (iscsi->fd != -1) { + printf("Trying to connect but already connected\n"); + return -2; + } + + addr = strdup(target); + if (addr == NULL) { + printf("failed to strdup target address\n"); + return -3; + } + + /* check if we have a target portal group tag */ + if ((str = rindex(addr, ',')) != NULL) { + tpgt = atoi(str+1); + str[0] = 0; + } + + /* XXX need handling for {ipv6 addresses} */ + /* for now, assume all is ipv4 */ + if ((str = rindex(addr, ':')) != 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) { + printf("failed to convert to ip address\n"); + free(addr); + return -4; + } + free(addr); + + switch (s.ss_family) { + case AF_INET: + iscsi->fd = socket(AF_INET, SOCK_STREAM, 0); + socksize = sizeof(struct sockaddr_in); + break; + default: + printf("Unknown family :%d\n", s.ss_family); + return -5; + + } + + if (iscsi->fd == -1) { + printf("Failed to open socket\n"); + return -6; + + } + + iscsi->connect_cb = cb; + iscsi->connect_data = private_data; + + set_nonblocking(iscsi->fd); + + if (connect(iscsi->fd, (struct sockaddr *)&s, socksize) != 0 && errno != EINPROGRESS) { + printf("Connect failed errno : %s (%d)\n", strerror(errno), errno); + return -7; + } + + return 0; +} + +int iscsi_disconnect(struct iscsi_context *iscsi) +{ + if (iscsi == NULL) { + printf("Trying to disconnect NULL context\n"); + return -1; + } + if (iscsi->is_loggedin != 0) { + printf("Trying to disconnect while logged in\n"); + return -2; + } + if (iscsi->fd == -1) { + printf("Trying to disconnect but not connected\n"); + return -3; + } + + close(iscsi->fd); + iscsi->fd = -1; + + iscsi->is_connected = 0; + + return 0; +} + +int iscsi_get_fd(struct iscsi_context *iscsi) +{ + if (iscsi == NULL) { + printf("Trying to get fd for NULL context\n"); + return -1; + } + + 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) { + printf("ioctl FIONREAD returned error : %d\n", errno); + return -1; + } + if (available == 0) { + printf("no data readable in socket, socket is closed\n"); + return -2; + } + size = iscsi->insize - iscsi->inpos + available; + buf = malloc(size); + if (buf == NULL) { + printf("failed to allocate %d bytes for input buffer\n", size); + return -3; + } + 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; + } + printf("read from socket failed, errno:%d\n", errno); + free(buf); + buf = NULL; + return -4; + } + + if (iscsi->inbuf != NULL) { + free(iscsi->inbuf); + } + iscsi->inbuf = buf; + iscsi->insize += count; + + while (1) { + if (iscsi->insize - iscsi->inpos < 48) { + return 0; + } + count = iscsi_get_pdu_size(iscsi->inbuf + iscsi->inpos); + if (iscsi->insize + iscsi->inpos < count) { + return 0; + } + if (iscsi_process_pdu(iscsi, iscsi->inbuf + iscsi->inpos, count) != 0) { + printf("failed to process pdu\n"); + return -5; + } + 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) { + printf("inpos > insize. bug!\n"); + return -6; + } + } + + return 0; +} + +static int iscsi_write_to_socket(struct iscsi_context *iscsi) +{ + ssize_t count; + + if (iscsi == NULL) { + printf("trying to write to socket for NULL context\n"); + return -1; + } + if (iscsi->fd == -1) { + printf("trying to write but not connected\n"); + return -2; + } + + 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) { + printf("socket would block, return from write to socket\n"); + return 0; + } + printf("Error when writing to socket :%d\n", errno); + return -3; + } + + iscsi->outqueue->written += count; + if (iscsi->outqueue->written == total) { + struct iscsi_pdu *pdu = iscsi->outqueue; + + DLIST_REMOVE(iscsi->outqueue, pdu); + DLIST_ADD_END(iscsi->waitpdu, pdu, NULL); + } + } + return 0; +} + +int iscsi_service(struct iscsi_context *iscsi, int revents) +{ + if (revents & POLLERR) { + printf("iscsi_service: POLLERR, socket error\n"); + iscsi->connect_cb(iscsi, ISCSI_STATUS_ERROR, NULL, iscsi->connect_data); + return -1; + } + if (revents & POLLHUP) { + printf("iscsi_service: POLLHUP, socket error\n"); + iscsi->connect_cb(iscsi, ISCSI_STATUS_ERROR, NULL, iscsi->connect_data); + return -2; + } + + if (iscsi->is_connected == 0 && iscsi->fd != -1 && revents&POLLOUT) { + iscsi->is_connected = 1; + iscsi->connect_cb(iscsi, ISCSI_STATUS_GOOD, NULL, iscsi->connect_data); + return 0; + } + + if (revents & POLLOUT && iscsi->outqueue != NULL) { + if (iscsi_write_to_socket(iscsi) != 0) { + printf("write to socket failed\n"); + return -3; + } + } + if (revents & POLLIN) { + if (iscsi_read_from_socket(iscsi) != 0) { + printf("read from socket failed\n"); + return -4; + } + } + + return 0; +} + +int iscsi_queue_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) +{ + if (iscsi == NULL) { + printf("trying to queue to NULL context\n"); + return -1; + } + if (pdu == NULL) { + printf("trying to queue NULL pdu\n"); + return -2; + } + DLIST_ADD_END(iscsi->outqueue, pdu, NULL); + + return 0; +} + + + diff --git a/ccan/iscsi/tools/iscsiclient.c b/ccan/iscsi/tools/iscsiclient.c new file mode 100644 index 00000000..28df8c23 --- /dev/null +++ b/ccan/iscsi/tools/iscsiclient.c @@ -0,0 +1,430 @@ +/* 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 "10.1.1.27:3260" + +#include +#include +#include +#include +#include +#include +#include + +struct client_state { + char *message; + int has_discovered_target; + char *target_name; + char *target_address; + int lun; + int block_size; +}; + +void nop_out_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + struct client_state *clnt = (struct client_state *)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); + } + exit(10); +} + + +void write10_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 i; + + if (status == ISCSI_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 client_state *clnt = (struct client_state *)private_data; + struct scsi_task *task = command_data; + int i; + + if (status == ISCSI_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, "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, write10_cb, task->data.datain, task->datain.size, 0, 0, 0, clnt->block_size, 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 == ISCSI_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); + free(rc10); + + if (iscsi_read10_async(iscsi, clnt->lun, read10_cb, 0, clnt->block_size, clnt->block_size, 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 == ISCSI_STATUS_CHECK_CONDITION) { + printf("Modesense6 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("did not get enough data for mode sense, sening modesense again asking for bigger buffer\n"); + if (iscsi_modesense6_async(iscsi, clnt->lun, modesense6_cb, 0, SCSI_MODESENSE_PC_CURRENT, SCSI_MODESENSE_PAGECODE_RETURN_ALL_PAGES, 0, full_size, 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, readcapacity10_cb, 0, 0, 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 == ISCSI_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, modesense6_cb, 0, SCSI_MODESENSE_PC_CURRENT, SCSI_MODESENSE_PAGECODE_RETURN_ALL_PAGES, 0, 4, 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 == ISCSI_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_KEY_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, inquiry_cb, 0, 0, 64, private_data) != 0) { + printf("failed to send inquiry command\n"); + 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; + uint32_t full_report_size; + int i; + + if (status != ISCSI_STATUS_GOOD) { + printf("Reportluns failed with unknown status code :%d\n", status); + 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, reportluns_cb, 0, full_report_size, 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 < 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\n"); + exit(10); + } +} + + +void normallogin_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + if (status != 0) { + printf("Failed to log in to target. status :0x%04x\n", status); + exit(10); + } + + printf("Logged in normal session, send reportluns\n"); + if (iscsi_reportluns_async(iscsi, reportluns_cb, 0, 16, private_data) != 0) { + printf("failed to send reportluns command\n"); + exit(10); + } +} + + +void normalconnect_cb(struct iscsi_context *iscsi, int status, void *command_data, 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); + 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, 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); + + 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\n"); + 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); + 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\n"); + exit(10); + } +} + + +void discoverylogin_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + if (status != 0) { + printf("Failed to log in to target. status :0x%04x\n", status); + 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\n"); + exit(10); + } + +} + +void discoveryconnect_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) +{ + printf("Connected to iscsi socket status:0x%08x\n", status); + + if (status != 0) { + printf("discoveryconnect_cb: connection failed status:%d\n", status); + 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\n"); + exit(10); + } +} + + +int main(int argc, char *argv[]) +{ + struct iscsi_context *iscsi; + struct pollfd pfd; + struct client_state clnt; + + printf("iscsi client\n"); + + 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\n"); + exit(10); + } + + for (;;) { + 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\n"); + break; + } + } + +printf("STOP\n"); +exit(10); + + printf("ok\n"); + return 0; +} + -- 2.39.2