diff options
author | mcneal <none@none> | 2006-06-30 17:02:49 -0700 |
---|---|---|
committer | mcneal <none@none> | 2006-06-30 17:02:49 -0700 |
commit | 36c5fee33fa8b822175d410202aebcf592c8d342 (patch) | |
tree | 6898ca550b2d4a40197087cb8a1c93ba44c834ae | |
parent | 0ce6acb835f8cec2ed5d14a190db6e2f98ed6d69 (diff) | |
download | illumos-joyent-36c5fee33fa8b822175d410202aebcf592c8d342.tar.gz |
PSARC 2005/441 iSCSI Target
6392513 Need iSCSI Target for Solaris
87 files changed, 34472 insertions, 31 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint index 6c03f00518..09f6025dda 100644 --- a/usr/src/Makefile.lint +++ b/usr/src/Makefile.lint @@ -154,6 +154,7 @@ COMMON_SUBDIRS = \ cmd/ipcs \ cmd/isaexec \ cmd/isalist \ + cmd/iscsi \ cmd/join \ cmd/kbd \ cmd/killall \ @@ -410,7 +411,7 @@ sparc_SUBDIRS= \ cmd/datadm \ cmd/dcs \ cmd/drd \ - cmd/fruadm \ +'ve cmd/fruadm \ cmd/prtdscp \ cmd/prtfru \ cmd/sckmd \ diff --git a/usr/src/Targetdirs b/usr/src/Targetdirs index 121a668fea..a91cde3c53 100644 --- a/usr/src/Targetdirs +++ b/usr/src/Targetdirs @@ -83,6 +83,7 @@ ROOT.SYS= \ /etc/fs/nfs \ /etc/fs/zfs \ /etc/ftpd \ + /etc/iscsi \ /etc/rpcsec \ /etc/security \ /etc/gss \ diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile index cb3c89f635..d9b0d5c657 100644 --- a/usr/src/cmd/Makefile +++ b/usr/src/cmd/Makefile @@ -199,6 +199,7 @@ COMMON_SUBDIRS= \ ipf \ isainfo \ isalist \ + iscsi \ java \ join \ kbd \ @@ -555,6 +556,7 @@ MSGSUBDIRS= \ hostname \ id \ isaexec \ + iscsi \ join \ krb5 \ kstat \ diff --git a/usr/src/cmd/iscsi/Makefile b/usr/src/cmd/iscsi/Makefile new file mode 100644 index 0000000000..79aac24e7c --- /dev/null +++ b/usr/src/cmd/iscsi/Makefile @@ -0,0 +1,70 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# Makefile definitions for volume management +# +# +# cmd/iscsi/Makefile +# + +include ../Makefile.cmd + +SUBDIRS = iscsitgtd iscsitadm +POSUBDIRS = iscsitgtd iscsitadm +POFILE = SUNW_ISCSI.po +POFILES = $(POSUBDIRS:%=%/%.po) + +CAT= cat + +all := TARGET= all +install := TARGET= install +clean := TARGET= clean +clobber := TARGET= clobber +lint := TARGET= lint +cstyle := TARGET= cstyle +_msg := TARGET= catalog + +.KEEP_STATE: + +all install cstyle lint _msg: $(SUBDIRS) + +clean: $(SUBDIRS) + $(RM) $(POFILES) + +clobber: $(SUBDIRS) + $(RM) $(POFILE) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +$(POFILE): $(POFILES) + $(BUILDPO.pofiles) + +_msg: $(POSUBDIRS) .WAIT $(MSGDOMAINPOFILE) + +FRC: + +include $(SRC)/Makefile.msg.targ diff --git a/usr/src/cmd/iscsi/Makefile.iscsi b/usr/src/cmd/iscsi/Makefile.iscsi new file mode 100644 index 0000000000..ba44fd2201 --- /dev/null +++ b/usr/src/cmd/iscsi/Makefile.iscsi @@ -0,0 +1,34 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I/usr/include/libxml2 + +ISCSISRC = $(SRC)/cmd/iscsi +ISCSICOMMONDIR = $(ISCSISRC)/common + +COMMON_OBJS = xml.o +COMMON_SRCS = $(COMMON_OBJS:%.o=$(ISCSICOMMONDIR)/%.c) diff --git a/usr/src/cmd/iscsi/common/iscsi_door.h b/usr/src/cmd/iscsi/common/iscsi_door.h new file mode 100644 index 0000000000..327c0fa104 --- /dev/null +++ b/usr/src/cmd/iscsi/common/iscsi_door.h @@ -0,0 +1,159 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ISCSI_DOOR_H +#define _ISCSI_DOOR_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISCSI_TARGET_MGMT_DOOR "/var/run/iscsi_tgt_door" +#define ISCSI_DOOR_REQ_SIGNATURE 0x53435349 +#define ISCSI_DOOR_REQ_VERSION_1 1 +#define ISCSI_DOOR_MAX_DATA_SIZE 8192 + + +#define ISCSI_DOOR_GETIPNODEBYNAME_REQ 0x0000 +#define ISCSI_DOOR_GETIPNODEBYNAME_CNF 0x4000 +#define ISCSI_DOOR_ERROR_IND 0x8000 + +#define ISCSI_DOOR_STATUS_SUCCESS 0x00000000 +#define ISCSI_DOOR_STATUS_REQ_LENGTH 0x00000001 +#define ISCSI_DOOR_STATUS_REQ_FORMAT 0x00000002 +#define ISCSI_DOOR_STATUS_REQ_INVALID 0x00000003 +#define ISCSI_DOOR_STATUS_REQ_VERSION 0x00000004 +#define ISCSI_DOOR_STATUS_MORE 0x00000005 + +typedef struct _iscsi_door_msg_hdr { + uint32_t signature; + uint32_t version; + uint32_t opcode; + uint32_t status; +} iscsi_door_msg_hdr_t; + +typedef struct _getipnodebyname_req { + iscsi_door_msg_hdr_t hdr; + uint32_t name_offset; + uint32_t name_length; + uint32_t af; + uint32_t flags; +} getipnodebyname_req_t; + +typedef struct _getipnodebyname_cnf { + iscsi_door_msg_hdr_t hdr; + uint32_t h_size_needed; + uint32_t h_addr_list_offset; + uint32_t h_addr_list_length; + uint32_t h_addrtype; + uint32_t h_addrlen; + uint32_t h_name_offset; + uint32_t h_name_len; + uint32_t h_alias_list_offset; + uint32_t h_alias_list_length; + int32_t error_num; +} getipnodebyname_cnf_t; + +typedef union _iscsi_door_req { + iscsi_door_msg_hdr_t hdr; + getipnodebyname_req_t ginbn_req; +} iscsi_door_req_t; + +typedef union _iscsi_door_cnf { + iscsi_door_msg_hdr_t hdr; + getipnodebyname_cnf_t ginbn_cnf; +} iscsi_door_cnf_t; + +typedef union _iscsi_door_ind { + iscsi_door_msg_hdr_t hdr; + iscsi_door_msg_hdr_t error_ind; +} iscsi_door_ind_t; + +typedef union _iscsi_door_msg { + iscsi_door_msg_hdr_t hdr; + iscsi_door_req_t req; + iscsi_door_cnf_t cnf; + iscsi_door_ind_t ind; +} iscsi_door_msg_t; + +#ifdef _KERNEL + +/* Defines copied from netdb.h */ +#define HOST_NOT_FOUND 1 /* Authoritive Answer Host not found */ +#define TRY_AGAIN 2 /* Non-Authoritive Host not found, or SERVERFAIL */ +#define NO_RECOVERY 3 /* Non recoverable errors,FORMERR,REFUSED,NOTIMP */ +#define NO_DATA 4 /* Valid name, no data record of requested type */ +#define NO_ADDRESS NO_DATA /* no address, look for MX record */ + +#define AI_V4MAPPED 0x0001 /* IPv4 mapped addresses if no IPv6 */ +#define AI_ALL 0x0002 /* IPv6 and IPv4 mapped addresses */ +#define AI_ADDRCONFIG 0x0004 /* AAAA or A records only if IPv6/IPv4 cnfgd */ + +struct hostent { + char *h_name; /* official name of host */ + char **h_aliases; /* alias list */ + int h_addrtype; /* host address type */ + int h_length; /* length of address */ + char **h_addr_list; /* list of addresses from name server */ +}; + +boolean_t +iscsi_door_ini(void); + +boolean_t +iscsi_door_term(void); + +boolean_t +iscsi_door_bind( + int did +); + +void +kfreehostent( + struct hostent *hptr +); + +struct hostent * +kgetipnodebyname( + const char *name, + int af, + int flags, + int *error_num +); + +#else /* !_KERNEL */ + +#define kfreehostent freehostent +#define kgetipnodebyname getipnodebyname + +#endif /* _KERNEL */ + +#ifdef __cplusplus +} +#endif + +#endif /* _ISCSI_DOOR_H */ diff --git a/usr/src/cmd/iscsi/common/local_types.h b/usr/src/cmd/iscsi/common/local_types.h new file mode 100644 index 0000000000..46e614266f --- /dev/null +++ b/usr/src/cmd/iscsi/common/local_types.h @@ -0,0 +1,66 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LOCAL_TYPES_H +#define _LOCAL_TYPES_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +/* + * Solaris typedefs boolean_t to be an enum with B_TRUE and B_FALSE. + * MacOS X typedefs boolean_t to be an int with #defines for TRUE & FALSE + * I like the use of enum's for return codes so that compilers can catch + * sloppy coding practices so I've defined a Boolean_t which is unique here. + */ +typedef enum { + False = 0, + True = 1 +} Boolean_t; + +#ifndef DTYPE_OSD +#define DTYPE_OSD 0x11 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _LOCAL_TYPES_H */ diff --git a/usr/src/cmd/iscsi/common/xml.c b/usr/src/cmd/iscsi/common/xml.c new file mode 100644 index 0000000000..7f4e88d2e1 --- /dev/null +++ b/usr/src/cmd/iscsi/common/xml.c @@ -0,0 +1,968 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <inttypes.h> +#include <assert.h> +#include <libxml/xmlreader.h> +#include <strings.h> +#include <ctype.h> +#include <stdlib.h> +#include <syslog.h> +#include <sys/stat.h> + +#include "xml.h" + +/* + * Forward declarations + */ +static char *strip_space(char *value); +static xml_node_t *node_alloc(); +static void node_free(xml_node_t *x); +static Boolean_t node_name(xml_node_t *x, const xmlChar *n); +static Boolean_t node_value(xml_node_t *x, const xmlChar *n, Boolean_t s); +static xml_node_t *node_parent(xml_node_t *x); +static xml_node_t *node_child(xml_node_t *x); +static xml_node_t *node_alloc_attr(xml_node_t *x); + +#define XML_COMMENT_STR "!--" +#define XML_COMMENT_END "--" + +void +xml_tree_free(xml_node_t *n) +{ + xml_node_t *c, + *c1; + if (n == NULL) + return; + for (c = n->x_child; c; ) { + c1 = c->x_sibling; + xml_tree_free(c); + c = c1; + } + for (c = n->x_attr; c; ) { + c1 = c->x_sibling; + node_free(c); + c = c1; + } + node_free(n); + +} + +void +xml_walk(xml_node_t *n, int depth) +{ + int i; + xml_node_t *c; + + if (n == NULL) + return; + for (i = 0; i < depth; i++) + (void) printf(" "); + if (n->x_name && n->x_value) { + if (strcmp(n->x_name, XML_COMMENT_STR) == 0) { + (void) printf("%s%s%s\n", XML_COMMENT_STR, n->x_value, + XML_COMMENT_END); + } else if (n->x_attr != NULL) { + (void) printf("%s(", n->x_name); + for (c = n->x_attr; c; c = c->x_sibling) + (void) printf("%s='%s'%c", c->x_name, + c->x_value, + (c->x_sibling != NULL) ? ',' : '\0'); + (void) printf(")=%s\n", n->x_value); + } else { + (void) printf("%s=%s\n", n->x_name, n->x_value); + } + } else if (n->x_name) { + if (n->x_attr != NULL) { + (void) printf("%s (", n->x_name); + for (c = n->x_attr; c; c = c->x_sibling) { + if (c != n->x_attr) + (void) printf(" "); + (void) printf("%s='%s'", c->x_name, + c->x_value); + } + (void) printf(")...\n"); + } else + (void) printf("%s...\n", n->x_name); + } + for (c = n->x_child; c; c = c->x_sibling) + xml_walk(c, depth + 1); +} + +void +xml_walk_to_buf(xml_node_t *n, char **buf) +{ + xml_node_t *c; + + if (n == NULL) + return; + if (strcmp(n->x_name, XML_COMMENT_STR) == 0) { + xml_add_comment(buf, n->x_value); + return; + } + buf_add_node_attr(buf, n); + if (n->x_value != NULL) + buf_add_tag(buf, n->x_value, Tag_String); + for (c = n->x_child; c; c = c->x_sibling) + xml_walk_to_buf(c, buf); + buf_add_tag(buf, n->x_name, Tag_End); +} + +/* + * []---- + * | xml_update_config -- dump out in core node tree to XML parsable file + * | + * | The depth argument is only used to pad the output with white space + * | for indentation. This is meant to help the readability of the file + * | for humans. + * []---- + */ +void +xml_update_config(xml_node_t *t, int depth, FILE *output) +{ + int i; + xml_node_t *c; + + if (t == NULL) + return; + + for (i = 0; i < depth; i++) + (void) fprintf(output, " "); + if (strcmp(t->x_name, XML_COMMENT_STR) == 0) { + (void) fprintf(output, "<%s%s%s>\n", XML_COMMENT_STR, + t->x_value, XML_COMMENT_END); + return; + } + if ((t->x_child == NULL) && (t->x_value != NULL) && + (t->x_attr == NULL) && + (((depth * 4) + ((strlen(t->x_name) * 2) + 5) + + strlen(t->x_value)) < 80)) { + + (void) fprintf(output, "<%s>%s</%s>\n", t->x_name, t->x_value, + t->x_name); + + } else { + (void) fprintf(output, "<%s", t->x_name); + for (c = t->x_attr; c; c = c->x_sibling) + (void) fprintf(output, " %s='%s'", c->x_name, + c->x_value); + (void) fprintf(output, ">\n"); + if (t->x_value) { + for (i = 0; i < depth + 1; i++) + (void) fprintf(output, " "); + (void) fprintf(output, "%s\n", t->x_value); + } + for (c = t->x_child; c; c = c->x_sibling) + xml_update_config(c, depth + 1, output); + for (i = 0; i < depth; i++) + (void) fprintf(output, " "); + (void) fprintf(output, "</%s>\n", t->x_name); + } +} + +Boolean_t +xml_dump2file(xml_node_t *root, char *path) +{ + FILE *output; + + if ((output = fopen(path, "wb")) == NULL) { + syslog(LOG_ERR, "Cannot open the file: %s\n", path); + return (False); + } else { + xml_update_config(root, 0, output); + (void) fclose(output); + + /* + * Make sure only root can access the file since we're + * optionally keeping CHAP secrets located here. + */ + (void) chmod(path, 0600); + + return (True); + } +} + +char *common_attr_list[] = { + "name", + "version", + 0 +}; + +Boolean_t +xml_process_node(xmlTextReaderPtr r, xml_node_t **node) +{ + const xmlChar *name, + *value; + char **ap; + xmlElementType node_type; + xml_node_t *n, + *an; + + n = *node; + if (n == NULL) { + n = node_alloc(); + if (n == NULL) + return (False); + *node = n; + } + + name = (xmlChar *)xmlTextReaderConstName(r); + if (name == NULL) { + node_free(n); + return (False); + } + + node_type = (xmlElementType)xmlTextReaderNodeType(r); + + if (xmlTextReaderAttributeCount(r) > 0) { + + for (ap = common_attr_list; *ap; ap++) { + value = xmlTextReaderGetAttribute(r, (xmlChar *)*ap); + if (value != NULL) { + + if ((an = node_alloc_attr(n)) == NULL) + return (False); + if (node_name(an, (xmlChar *)*ap) == False) { + node_free(an); + return (False); + } + if (node_value(an, value, True) == False) { + node_free(an); + return (False); + } + free((char *)value); + } + } + } + value = (xmlChar *)xmlTextReaderConstValue(r); + + if (node_type == XML_ELEMENT_NODE) { + if (n->x_state != NodeAlloc) { + n = node_child(n); + *node = n; + if (n == NULL) + return (False); + } + if (node_name(n, name) == False) { + node_free(n); + return (False); + } + } else if ((value != NULL) && (node_type == XML_TEXT_NODE)) { + if (node_value(n, value, True) == False) { + node_free(n); + return (False); + } + } else if (node_type == XML_ELEMENT_DECL) { + n = node_parent(n); + if (n == NULL) + return (False); + *node = n; + } else if (node_type == XML_COMMENT_NODE) { + n = node_child(n); + if (node_name(n, (xmlChar *)XML_COMMENT_STR) == False) { + node_free(n); + return (False); + } + if (node_value(n, (xmlChar *)value, False) == False) { + node_free(n); + return (False); + } + } else if (node_type != XML_DTD_NODE) { + node_free(n); + return (False); + } + return (True); +} + +Boolean_t +xml_find_attr_str(xml_node_t *n, char *attr, char **value) +{ + xml_node_t *a; + + if ((n == NULL) || (n->x_attr == NULL)) + return (False); + + for (a = n->x_attr; a; a = a->x_sibling) + if (strcmp(a->x_name, attr) == 0) { + *value = a->x_value ? strdup(a->x_value) : NULL; + return (True); + } + return (False); +} + +Boolean_t +xml_find_value_str(xml_node_t *n, char *name, char **value) +{ + xml_node_t *c; + + if ((n == NULL) || (n->x_name == NULL)) + return (False); + + if (strcmp(n->x_name, name) == 0) { + *value = n->x_value ? strdup(n->x_value) : NULL; + return (True); + } + for (c = n->x_child; c; c = c->x_sibling) { + if (xml_find_value_str(c, name, value) == True) + return (True); + } + return (False); +} + +Boolean_t +xml_find_value_int(xml_node_t *n, char *name, int *value) +{ + xml_node_t *c; + + if ((n == NULL) || (n->x_name == NULL)) + return (False); + + if (strcmp(n->x_name, name) == 0) { + if (n->x_value == NULL) + return (False); + *value = strtol(n->x_value, NULL, 0); + return (True); + } + for (c = n->x_child; c; c = c->x_sibling) { + if (xml_find_value_int(c, name, value) == True) + return (True); + } + return (False); +} + +/* + * []---- + * | xml_find_value_intchk -- if node exists, check to see if value is okay + * []---- + */ +Boolean_t +xml_find_value_intchk(xml_node_t *n, char *name, int *value) +{ + char *str, + chk[32]; + Boolean_t rval; + + if (xml_find_value_str(n, name, &str) == True) { + + *value = strtol(str, NULL, 0); + /* + * Validate that the input string hasn't overrun what + * what an integer can handle. This is done by simply + * printing out the result of the conversion into a buffer + * and comparing it to the incoming string. That way when + * someone enters 4294967296 which strtol returns as 0 + * we'll catch it. + */ + if ((str[0] == '0') && (str[1] != '\0')) { + if (str[1] == 'x') + snprintf(chk, sizeof (chk), "0x%x", *value); + else if (str[1] == 'X') + snprintf(chk, sizeof (chk), "0X%x", *value); + else + snprintf(chk, sizeof (chk), "0%o", *value); + } else + snprintf(chk, sizeof (chk), "%d", *value); + if (strcmp(chk, str) == 0) + rval = True; + else + rval = False; + free(str); + return (rval); + } else + return (True); +} + +Boolean_t +xml_find_value_boolean(xml_node_t *n, char *name, Boolean_t *value) +{ + xml_node_t *c; + + if ((n == NULL) || (n->x_name == NULL)) + return (False); + + if (strcmp(n->x_name, name) == 0) { + if (n->x_value == NULL) + return (False); + *value = strcmp(n->x_value, "true") == 0 ? True : False; + return (True); + } + for (c = n->x_child; c; c = c->x_sibling) { + if (xml_find_value_boolean(c, name, value) == True) + return (True); + } + return (False); +} + +xml_node_t * +xml_node_next(xml_node_t *n, char *name, xml_node_t *cur) +{ + xml_node_t *x, + *p; + + if (n == NULL) + return (NULL); + + if (cur != NULL) { + for (x = cur->x_sibling; x; x = x->x_sibling) + if (strcmp(x->x_name, name) == 0) + return (x); + return (NULL); + } + + if (n->x_name == NULL) + return (NULL); + + if (strcmp(n->x_name, name) == 0) + return (n); + for (x = n->x_child; x; x = x->x_sibling) + if ((p = xml_node_next(x, name, 0)) != NULL) + return (p); + return (NULL); +} + +xml_node_t * +xml_node_next_child(xml_node_t *n, char *name, xml_node_t *cur) +{ + if (cur != NULL) { + n = cur->x_sibling; + } else { + if (n != NULL) + n = n->x_child; + else + return (NULL); + } + while (n) { + if (strcmp(n->x_name, name) == 0) + return (n); + n = n->x_sibling; + } + return (NULL); +} + +Boolean_t +xml_add_child(xml_node_t *p, xml_node_t *c) +{ + xml_node_t *s; + if ((p == NULL) || (c == NULL)) + return (False); + + if (p->x_child == NULL) + p->x_child = c; + else { + for (s = p->x_child; s->x_sibling; s = s->x_sibling) + ; + s->x_sibling = c; + } + return (True); +} + +xml_node_t * +xml_alloc_node(char *name, xml_val_type_t type, void *value) +{ + xml_node_t *d = node_alloc(); + int value_len; + char *value_str; + + if (d == NULL) + return (NULL); + switch (type) { + case String: + value_len = strlen((char *)value) + 1; + break; + case Int: + value_len = sizeof (int) * 2 + 3; + break; + case Uint64: + value_len = sizeof (uint64_t) * 2 + 3; + break; + } + if ((value_str = (char *)calloc(sizeof (char), value_len)) == NULL) + return (NULL); + if (node_name(d, (xmlChar *)name) == False) { + free(value_str); + return (NULL); + } + switch (type) { + case String: + (void) snprintf(value_str, value_len, "%s", (char *)value); + break; + case Int: + (void) snprintf(value_str, value_len, "0x%x", *(int *)value); + break; + case Uint64: + (void) snprintf(value_str, value_len, "0x%llx", + *(uint64_t *)value); + break; + } + (void) node_value(d, (xmlChar *)value_str, True); + + return (d); +} + +Boolean_t +xml_encode_bytes(uint8_t *ip, size_t ip_size, char **buf, size_t *buf_size) +{ + char *bp; + *buf_size = (ip_size * 2) + 1; + + if ((*buf = (char *)malloc(*buf_size)) == NULL) { + *buf_size = 0; + return (False); + } + + for (bp = *buf; ip_size; ip_size--) { + (void) sprintf(bp, "%.2x", *ip); + ip++; + bp += 2; + } + + return (True); +} + +Boolean_t +xml_decode_bytes(char *buf, uint8_t **ip, size_t *ip_size) +{ + uint8_t *i; + size_t buf_size = strlen(buf); + *ip_size = buf_size / 2; + + if ((*ip = (uint8_t *)malloc(*ip_size)) == NULL) { + *ip_size = 0; + return (False); + } + + for (i = *ip; buf_size; buf_size -= 2) { + char x[3]; + bcopy(buf, x, 2); + x[2] = 0; + *i++ = strtol(x, NULL, 16); + buf += 2; + } + return (True); +} + +void +xml_free_node(xml_node_t *node) +{ + node_free(node); +} + +Boolean_t +xml_remove_child(xml_node_t *parent, xml_node_t *child, match_type_t m) +{ + xml_node_t *s, + *c = NULL; + + if ((parent == NULL) || (child == NULL)) + return (False); + + for (s = parent->x_child; s; c = s, s = s->x_sibling) { + + /* + * See if the new child node matches one of the children + * in the parent. + */ + if ((strcmp(s->x_name, child->x_name) == 0) && + ((m == MatchName) || (strcmp(s->x_value, + child->x_value) == 0))) { + + if (parent->x_child == s) { + parent->x_child = s->x_sibling; + } else { + c->x_sibling = s->x_sibling; + } + xml_tree_free(s); + break; + } + } + if (s == NULL) + return (False); + else + return (True); +} + +void +xml_replace_child(xml_node_t *parent, xml_node_t *child, match_type_t m) +{ + xml_node_t *s, + *c; + + if ((parent == NULL) || (child == NULL)) + return; + + for (s = parent->x_child; s; s = s->x_sibling) { + + /* + * See if the new child node matches one of the children + * in the parent. + */ + if ((strcmp(s->x_name, child->x_name) == 0) && + ((m == MatchName) || (strcmp(s->x_value, + child->x_value) == 0))) { + + /* + * We have a match. Now save the values of the new + * child in this current node. + */ + free(s->x_name); + free(s->x_value); + s->x_name = strdup(child->x_name); + s->x_value = strdup(child->x_value); + if (s->x_child) { + xml_tree_free(s->x_child); + s->x_child = NULL; + } + for (c = child->x_child; c; c = c->x_sibling) + (void) xml_add_child(s, xml_node_dup(c)); + break; + } + } + + if (s == NULL) { + /* + * Never found the child so add it + */ + (void) xml_add_child(parent, xml_node_dup(child)); + } +} + +#ifdef notused +/* + * Update node value when value type is usigned long long + */ +Boolean_t +xml_update_value_ull(xml_node_t *node, char *name, uint64_t value) +{ + if (node == NULL || strcmp(name, node->x_name)) + return (False); + + if (strtoll(node->x_value, NULL, 0) == value) + return (True); + (void) snprintf(node->x_value, 64, "0x%llx", value); + return (True); +} +#endif + +Boolean_t +xml_update_value_str(xml_node_t *node, char *name, char *str) +{ + if ((node == NULL) || (strcmp(name, node->x_name) != 0)) + return (False); + if (node->x_value != NULL) + free(node->x_value); + node->x_value = strdup(str); + node->x_state = NodeValue; + return (True); +} + +xml_node_t * +xml_find_child(xml_node_t *n, char *name) +{ + xml_node_t *rval; + + for (rval = n->x_child; rval; rval = rval->x_sibling) + if (strcmp(rval->x_name, name) == 0) + break; + return (rval); +} + +xml_node_t * +xml_node_dup(xml_node_t *n) +{ + xml_node_t *d = node_alloc(), + *c; + + if (d == NULL) + return (NULL); + if (node_name(d, (xmlChar *)n->x_name) == False) + return (NULL); + if (node_value(d, (xmlChar *)n->x_value, True) == False) + return (NULL); + for (c = n->x_child; c; c = c->x_sibling) + (void) xml_add_child(d, xml_node_dup(c)); + return (d); +} + +/* + * []---- + * | Utility functions + * []---- + */ +static xml_node_t * +node_alloc() +{ + xml_node_t *x = (xml_node_t *)calloc(sizeof (xml_node_t), 1); + + if (x == NULL) + return (NULL); + + x->x_state = NodeAlloc; + return (x); +} + +static void +node_free(xml_node_t *x) +{ + x->x_state = NodeFree; + if (x->x_name) + free(x->x_name); + if (x->x_value) + free(x->x_value); + free(x); +} + +static Boolean_t +node_name(xml_node_t *x, const xmlChar *n) +{ + assert(x->x_state == NodeAlloc); + if ((n == NULL) || (strlen((char *)n) == 0)) + return (False); + + x->x_state = NodeName; + x->x_name = strip_space((char *)n); + return (True); +} + +static Boolean_t +node_value(xml_node_t *x, const xmlChar *n, Boolean_t do_strip) +{ + assert(x->x_state == NodeName); + if ((n == NULL) || (strlen((char *)n) == NULL)) + return (False); + + x->x_state = NodeValue; + x->x_value = (do_strip == True) ? + strip_space((char *)n) : strdup((char *)n); + return (True); +} + +static xml_node_t * +node_parent(xml_node_t *x) +{ + return (x->x_parent); +} + +static xml_node_t * +node_child(xml_node_t *x) +{ + xml_node_t *n, + *next; + + n = node_alloc(); + if (x->x_child == NULL) { + x->x_child = n; + } else { + for (next = x->x_child; next->x_sibling; next = next->x_sibling) + ; + next->x_sibling = n; + } + if (n != NULL) + n->x_parent = x; + return (n); +} + +static xml_node_t * +node_alloc_attr(xml_node_t *x) +{ + xml_node_t *n, + *next; + + n = node_alloc(); + if (x->x_attr == NULL) { + x->x_attr = n; + } else { + for (next = x->x_attr; next->x_sibling; next = next->x_sibling) + ; + next->x_sibling = n; + } + if (n != NULL) + n->x_parent = x; + return (n); +} + +void +buf_add_str(char **b, char *str) +{ + int len, + olen = 0; + char *p = *b; + + /* + * Make sure we have enough room for the string and tag characters + * plus a NULL byte. + */ + if (str == NULL) + return; + + len = strlen(str) + 1; + if (p == NULL) { + p = malloc(len); + } else { + olen = strlen(p); + p = realloc(p, olen + len); + } + (void) strcpy(p + olen, str); + *b = p; +} + +/* + * []---- + * | buf_add_tag -- adds string to buffer allocating space, sets up tags too + * | + * | Helper function to build a string by allocating memory as we go. + * | If the string argument 'str' is defined to be a start or end tag + * | as declared by 'type' argument add the appropriate characters. + * []---- + */ +void +buf_add_tag(char **b, char *str, val_type_t type) +{ + char *buf; + int len; + + /* + * We will add potentially up to 3 extra characters plus the NULL byte + */ + len = strlen(str) + 4; + if ((buf = malloc(len)) == NULL) + return; + + (void) snprintf(buf, len, "%s%s%s%s", type == Tag_String ? "" : "<", + type == Tag_End ? "/" : "", str, type == Tag_String ? "" : ">"); + buf_add_str(b, buf); + free(buf); +} + +/* + * []---- + * | buf_add_tag_and_attr -- variant on buf_add_tag which also gives attr + * []---- + */ +void +buf_add_tag_and_attr(char **b, char *str, char *attr) +{ + char *buf; + int len; + + /* + * In addition to the 'str' and 'attr' strings the code will add + * three characters plus a null byte. + */ + len = strlen(str) + strlen(attr) + 4; + if ((buf = malloc(len)) == NULL) + return; + + (void) snprintf(buf, len, "<%s %s>", str, attr); + buf_add_str(b, buf); + free(buf); +} + +void +buf_add_node_attr(char **b, xml_node_t *x) +{ + char *buf; + xml_node_t *n; + int len; + + /* ---- null byte and starting '<' character ---- */ + len = strlen(x->x_name) + 2; + if ((buf = malloc(len)) == NULL) + return; + (void) snprintf(buf, len, "<%s", x->x_name); + buf_add_str(b, buf); + free(buf); + + for (n = x->x_attr; n; n = n->x_sibling) { + len = strlen(n->x_name) + strlen(n->x_value) + 5; + if ((buf = malloc(len)) == NULL) + return; + (void) snprintf(buf, len, " %s='%s'", n->x_name, n->x_value); + buf_add_str(b, buf); + free(buf); + } + buf_add_str(b, ">"); +} + +void +xml_add_comment(char **b, char *comment) +{ + char *p = *b; + int len, + olen; + + if (comment == NULL) + return; + + /* + * Room for the strings, plus the brackets and NULL byte + */ + len = strlen(comment) + strlen(XML_COMMENT_STR) + + strlen(XML_COMMENT_END) + 3; + + if (p == NULL) + p = malloc(len); + else { + olen = strlen(p); + p = realloc(p, olen + len); + } + (void) snprintf(p + olen, len, "<%s%s%s>", XML_COMMENT_STR, comment, + XML_COMMENT_END); + *b = p; +} + +void +xml_add_tag(char **b, char *element, char *cdata) +{ + buf_add_tag(b, element, Tag_Start); + if (cdata != NULL) + buf_add_tag(b, cdata, Tag_String); + buf_add_tag(b, element, Tag_End); +} + +static char * +strip_space(char *value) +{ + char *p, + *n; + + for (p = value; p && *p; p++) + if (!isspace(*p)) + break; + if ((p == NULL) || (*p == '\0')) + return (NULL); + + p = strdup(p); + for (n = (p + strlen(p) - 1); n >= p; n--) + if (!isspace(*n)) { + n++; + break; + } + *n = '\0'; + return (p); +} diff --git a/usr/src/cmd/iscsi/common/xml.h b/usr/src/cmd/iscsi/common/xml.h new file mode 100644 index 0000000000..df373b111a --- /dev/null +++ b/usr/src/cmd/iscsi/common/xml.h @@ -0,0 +1,179 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _TARGET_XML_H +#define _TARGET_XML_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libxml/xmlreader.h> + +#include "local_types.h" + +/* + * XML element defines. + */ +#define XML_ELEMENT_ERROR "error" +#define XML_ELEMENT_CODE "code" +#define XML_ELEMENT_MESSAGE "message" +#define XML_ELEMENT_TRANSVERS "transport-version" +#define XML_ELEMENT_PROPS "props" +#define XML_ELEMENT_DATAOUT "data-out-size" +#define XML_ELEMENT_BASEDIR "base-directory" +#define XML_ELEMENT_CHAPSECRET "chap-secret" +#define XML_ELEMENT_CHAPNAME "chap-name" +#define XML_ELEMENT_RAD_ACCESS "radius-access" +#define XML_ELEMENT_RAD_SERV "radius-server" +#define XML_ELEMENT_RAD_SECRET "radius-secret" +#define XML_ELEMENT_ISNS_ACCESS "isns-access" +#define XML_ELEMENT_FAST "fast-write-ack" +#define XML_ELEMENT_NAME "name" +#define XML_ELEMENT_ACL "acl" +#define XML_ELEMENT_ACLLIST "acl-list" +#define XML_ELEMENT_TPGT "tpgt" +#define XML_ELEMENT_TPGTLIST "tpgt-list" +#define XML_ELEMENT_SIZE "size" +#define XML_ELEMENT_LUN "lun" +#define XML_ELEMENT_LUNLIST "lun-list" +#define XML_ELEMENT_TYPE "type" +#define XML_ELEMENT_ALIAS "alias" +#define XML_ELEMENT_BACK "backing-store" +#define XML_ELEMENT_DELETE_BACK "delete-backing-store" +#define XML_ELEMENT_TARG "target" +#define XML_ELEMENT_INIT "initiator" +#define XML_ELEMENT_ADMIN "admin" +#define XML_ELEMENT_INAME "iscsi-name" +#define XML_ELEMENT_MAXRECV "maxrecv" +#define XML_ELEMENT_IPADDR "ip-address" +#define XML_ELEMENT_ALL "all" +#define XML_ELEMENT_VERBOSE "verbose" +#define XML_ELEMENT_LIST "list" +#define XML_ELEMENT_RESULT "result" +#define XML_ELEMENT_TIMECON "time-connected" +#define XML_ELEMENT_READCMDS "read-commands" +#define XML_ELEMENT_WRITECMDS "write-commands" +#define XML_ELEMENT_READBLKS "read-blks" +#define XML_ELEMENT_WRITEBLKS "write-blks" +#define XML_ELEMENT_STATS "statistics" +#define XML_ELEMENT_CONN "connection" +#define XML_ELEMENT_LUNINFO "lun-information" +#define XML_ELEMENT_VID "vid" +#define XML_ELEMENT_PID "pid" +#define XML_ELEMENT_GUID "guid" +#define XML_ELEMENT_DTYPE "dtype" +#define XML_ELEMENT_IOSTAT "iostat" +#define XML_ELEMENT_MACADDR "mac-addr" +#define XML_ELEMENT_MGMTPORT "mgmt-port" +#define XML_ELEMENT_ISCSIPORT "iscsi-port" +#define XML_ELEMENT_TARGLOG "target-log" +#define XML_ELEMENT_DBGLVL "dbg-lvl" +#define XML_ELEMENT_LOGLVL "qlog-lvl" +#define XML_ELEMENT_DBGDAEMON "daemonize" +#define XML_ELEMENT_ENFORCE "enforce-strict-guid" +#define XML_ELEMENT_VERS "version" +#define XML_ELEMENT_MMAP_LUN "mmap-lun" +#define XML_ELEMENT_RPM "rpm" +#define XML_ELEMENT_HEADS "heads" +#define XML_ELEMENT_CYLINDERS "cylinders" +#define XML_ELEMENT_SPT "spt" +#define XML_ELEMENT_BPS "bps" +#define XML_ELEMENT_INTERLEAVE "interleave" +#define XML_ELEMENT_PARAMS "params" +#define XML_ELEMENT_MAXCMDS "max-outstanding-cmds" +#define XML_ELEMENT_THIN_PROVO "thin-provisioning" +#define XML_ELEMENT_DISABLE_TPGS "disable-tpgs" +#define XML_ELEMENT_STATUS "status" +#define XML_ELEMENT_PROGRESS "progress" +#define XML_ELEMENT_TIMESTAMPS "time-stamps" + +typedef enum { + NodeFree, + NodeAlloc, + NodeName, + NodeValue +} xml_node_state; + +typedef enum { MatchName, MatchBoth } match_type_t; + +typedef struct xml_node { + struct xml_node *x_parent, + *x_child, + *x_sibling, + *x_attr; + char *x_name, + *x_value; + xml_node_state x_state; +} xml_node_t; + +typedef enum val_type { Tag_String, Tag_Start, Tag_End } val_type_t; +typedef enum xml_val_type { String, Int, Uint64 } xml_val_type_t; + +void xml_tree_free(xml_node_t *x); +void xml_walk(xml_node_t *x, int depth); +void xml_walk_to_buf(xml_node_t *n, char **buf); +void xml_update_config(xml_node_t *t, int depth, FILE *output); +void buf_add_str(char **b, char *str); +void buf_add_tag(char **b, char *str, val_type_t type); +void buf_add_tag_and_attr(char **b, char *str, char *attr); +void buf_add_node_attr(char **b, xml_node_t *x); +void xml_add_tag(char **b, char *element, char *cdata); +void xml_add_comment(char **b, char *comment); +void xml_replace_child(xml_node_t *parent, xml_node_t *child, match_type_t m); +Boolean_t xml_remove_child(xml_node_t *parent, xml_node_t *child, + match_type_t m); +Boolean_t xml_encode_bytes(uint8_t *ip, size_t ip_size, char **buf, + size_t *buf_size); +Boolean_t xml_decode_bytes(char *buf, uint8_t **ip, size_t *ip_size); +Boolean_t xml_find_value_str(xml_node_t *n, char *name, char **value); +Boolean_t xml_find_value_int(xml_node_t *n, char *name, int *value); +Boolean_t xml_find_value_intchk(xml_node_t *n, char *name, int *value); +Boolean_t xml_update_value_ull(xml_node_t *root, char *name, uint64_t value); +Boolean_t xml_dump2file(xml_node_t *root, char *path); +Boolean_t xml_find_value_boolean(xml_node_t *n, char *name, Boolean_t *value); +Boolean_t xml_find_attr_str(xml_node_t *n, char *attr, char **value); +Boolean_t xml_process_node(xmlTextReaderPtr r, xml_node_t **node); +Boolean_t xml_add_child(xml_node_t *p, xml_node_t *c); +xml_node_t *xml_alloc_node(char *name, xml_val_type_t type, void *value); +void xml_free_node(xml_node_t *node); +xml_node_t *xml_node_next(xml_node_t *n, char *name, xml_node_t *cur); +xml_node_t *xml_node_next_child(xml_node_t *n, char *name, xml_node_t *cur); +xml_node_t *xml_node_dup(xml_node_t *n); +xml_node_t *xml_find_child(xml_node_t *n, char *name); +Boolean_t xml_update_value_str(xml_node_t *node, char *name, char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* _TARGET_XML_H */ diff --git a/usr/src/cmd/iscsi/iscsitadm/Makefile b/usr/src/cmd/iscsi/iscsitadm/Makefile new file mode 100644 index 0000000000..a5a19dbc3b --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitadm/Makefile @@ -0,0 +1,81 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# + +PROG = iscsitadm +OBJS = main.o helper.o cmdparse.o +SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS) +POFILES= $(OBJS:%.o=%.po) + +include $(SRC)/cmd/Makefile.cmd +include $(SRC)/cmd/iscsi/Makefile.iscsi + +LDLIBS += -lxml2 -lsocket -lnsl -ldoor -lscf +CFLAGS += $(CCVERBOSE) -I${ISCSICOMMONDIR} +FILEMODE= 0555 +GROUP= bin + +# +# We can't use the default lint target because there is not a libxml2 +# lint library and we'll get warnings. So, we'll just lint the local +# source and not do the cross checks. +# +#lint := LINTFLAGS = -muxs -I$(ISCSICOMMONDIR) + +SUFFIX_LINT = .ln + +.KEEP_STATE: + +all: $(PROG) + +install: all $(ROOTUSRSBINPROG) + +$(PROG): $(OBJS) $(COMMON_OBJS) + $(LINK.c) -o $(PROG) $(OBJS) $(COMMON_OBJS) $(LDLIBS) + $(POST_PROCESS) + +catalog: $(POFILE) + +lint := LINTFLAGS += -u +lint := LINTFLAGS64 += -u + +lint: $(SRCS:%=%$(SUFFIX_LINT)) + +%$(SUFFIX_LINT): % + ${LINT.c} -I. -I$(ISCSICOMMONDIR) -y -c $< && touch $@ + +$(POFILE): $(POFILES) + $(RM) $@ + cat $(POFILES) > $@ + +%.o : $(ISCSICOMMONDIR)/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +clean: + -$(RM) $(OBJS) $(COMMON_OBJS) *$(SUFFIX_LINT) + +include $(SRC)/cmd/Makefile.targ diff --git a/usr/src/cmd/iscsi/iscsitadm/cmdparse.c b/usr/src/cmd/iscsi/iscsitadm/cmdparse.c new file mode 100644 index 0000000000..d94e54538c --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitadm/cmdparse.c @@ -0,0 +1,934 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <strings.h> +#include <sys/types.h> +#include <unistd.h> +#include <libintl.h> +#include <errno.h> +#include <string.h> +#include <assert.h> +#include <getopt.h> +#include "cmdparse.h" + +#pragma ident "%Z%%M% %I% %E% SMI" + + +/* Usage types */ +#define GENERAL_USAGE 1 +#define HELP_USAGE 2 +#define DETAIL_USAGE 3 + +/* printable ascii character set len */ +#define MAXOPTIONS (uint_t)('~' - '!' + 1) + +/* + * MAXOPTIONSTRING is the max length of the options string used in getopt and + * will be the printable character set + ':' for each character, + * providing for options with arguments. e.g. "t:Cs:hglr:" + */ +#define MAXOPTIONSTRING MAXOPTIONS * 2 + +/* standard command options table to support -?, -V */ +struct option standardCmdOptions[] = { + {"help", no_argument, NULL, '?'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} +}; + +/* standard subcommand options table to support -? */ +struct option standardSubCmdOptions[] = { + {"help", no_argument, NULL, '?'}, + {NULL, 0, NULL, 0} +}; + +/* forward declarations */ +static int getSubcommand(char *, subcommand_t **); +static char *getExecBasename(char *); +static void usage(uint_t); +static void subUsage(uint_t, subcommand_t *); +static void subUsageObject(uint_t, subcommand_t *, object_t *); +static int getObject(char *, object_t **); +static int getObjectRules(uint_t, objectRules_t **); +static char *getLongOption(int); +static optionProp_t *getOptions(uint_t, uint_t); +static char *getOptionArgDesc(int); + +/* global data */ +static struct option *_longOptions; +static subcommand_t *_subcommands; +static object_t *_objects; +static objectRules_t *_objectRules; +static optionRules_t *_optionRules; +static optionTbl_t *_clientOptionTbl; +static char *commandName; + + +/* + * input: + * object - object value + * output: + * opCmd - pointer to opCmd_t structure allocated by caller + * + * On successful return, opCmd contains the rules for the value in + * object. On failure, the contents of opCmd is unspecified. + * + * Returns: + * zero on success + * non-zero on failure + * + */ +static int +getObjectRules(uint_t object, objectRules_t **objectRules) +{ + objectRules_t *sp; + + for (sp = _objectRules; sp->value; sp++) { + if (sp->value == object) { + *objectRules = sp; + return (0); + } + } + return (1); +} + +/* + * input: + * arg - pointer to array of char containing object string + * + * output: + * object - pointer to object_t structure pointer + * on success, contains the matching object structure based on + * input object name + * + * Returns: + * zero on success + * non-zero otherwise + * + */ +static int +getObject(char *arg, object_t **object) +{ + + object_t *op; + int len; + + for (op = _objects; op->name; op++) { + len = strlen(arg); + if (len == strlen(op->name) && + strncasecmp(arg, op->name, len) == 0) { + *object = op; + return (0); + } + } + return (1); +} + +/* + * input: + * arg - pointer to array of char containing subcommand string + * output: + * subcommand - pointer to subcommand_t pointer + * on success, contains the matching subcommand structure based on + * input subcommand name + * + * Returns: + * zero on success + * non-zero on failure + */ +static int +getSubcommand(char *arg, subcommand_t **subcommand) +{ + subcommand_t *sp; + int len; + + for (sp = _subcommands; sp->name; sp++) { + len = strlen(arg); + if (len == strlen(sp->name) && + strncasecmp(arg, sp->name, len) == 0) { + *subcommand = sp; + return (0); + } + } + return (1); +} + +/* + * input: + * object - object for which to get options + * subcommand - subcommand for which to get options + * + * Returns: + * on success, optionsProp_t pointer to structure matching input object + * value + * on failure, NULL is returned + */ +static optionProp_t * +getOptions(uint_t object, uint_t subcommand) +{ + uint_t currObject; + optionRules_t *op = _optionRules; + while (op && ((currObject = op->objectValue) != 0)) { + if ((currObject == object) && + (op->subcommandValue == subcommand)) { + return (&(op->optionProp)); + } + op++; + } + return (NULL); +} + +/* + * input: + * shortOption - short option character for which to return the + * associated long option string + * + * Returns: + * on success, long option name + * on failure, NULL + */ +static char * +getLongOption(int shortOption) +{ + struct option *op; + for (op = _longOptions; op->name; op++) { + if (shortOption == op->val) { + return (op->name); + } + } + return (NULL); +} + +/* + * input + * shortOption - short option character for which to return the + * option argument + * Returns: + * on success, argument string + * on failure, NULL + */ +static char * +getOptionArgDesc(int shortOption) +{ + optionTbl_t *op; + for (op = _clientOptionTbl; op->name; op++) { + if (op->val == shortOption && + op->has_arg == required_argument) { + return (op->argDesc); + } + } + return (NULL); +} + + +/* + * Print usage for a subcommand. + * + * input: + * usage type - GENERAL_USAGE, HELP_USAGE, DETAIL_USAGE + * subcommand - pointer to subcommand_t structure + * + * Returns: + * none + * + */ +static void +subUsage(uint_t usageType, subcommand_t *subcommand) +{ + int i; + object_t *objp; + + + (void) fprintf(stdout, "%s:\t%s %s [", + gettext("Usage"), commandName, subcommand->name); + + for (i = 0; standardSubCmdOptions[i].name; i++) { + (void) fprintf(stdout, "-%c", + standardSubCmdOptions[i].val); + if (standardSubCmdOptions[i+1].name) + (void) fprintf(stdout, ","); + } + + (void) fprintf(stdout, "] %s [", "<OBJECT>"); + + for (i = 0; standardSubCmdOptions[i].name; i++) { + (void) fprintf(stdout, "-%c", + standardSubCmdOptions[i].val); + if (standardSubCmdOptions[i+1].name) + (void) fprintf(stdout, ","); + } + + (void) fprintf(stdout, "] %s", "[<OPERAND>]"); + (void) fprintf(stdout, "\n"); + + if (usageType == GENERAL_USAGE) { + return; + } + + (void) fprintf(stdout, "%s:\n", gettext("Usage by OBJECT")); + + /* + * iterate through object table + * For each object, print appropriate usage + * based on rules tables + */ + for (objp = _objects; objp->value; objp++) { + subUsageObject(usageType, subcommand, objp); + } +} + +/* + * Print usage for a subcommand and object. + * + * input: + * usage type - GENERAL_USAGE, HELP_USAGE, DETAIL_USAGE + * subcommand - pointer to subcommand_t structure + * objp - pointer to a object_t structure + * + * Returns: + * none + * + */ +static void +subUsageObject(uint_t usageType, subcommand_t *subcommand, object_t *objp) +{ + int i; + objectRules_t *objRules = NULL; + opCmd_t *opCmd = NULL; + optionProp_t *options; + char *optionArgDesc; + char *longOpt; + + + if (getObjectRules(objp->value, &objRules) != 0) { + /* + * internal subcommand rules table error + * no object entry in object + */ + assert(0); + } + + opCmd = &(objRules->opCmd); + + if (opCmd->invOpCmd & subcommand->value) { + return; + } + + options = getOptions(objp->value, subcommand->value); + + /* print generic subcommand usage */ + (void) fprintf(stdout, "\t%s %s ", commandName, subcommand->name); + + /* print object */ + (void) fprintf(stdout, "%s ", objp->name); + + /* print options if applicable */ + if (options != NULL) { + if (options->required) { + (void) fprintf(stdout, "%s", gettext("<")); + } else { + (void) fprintf(stdout, "%s", gettext("[")); + } + (void) fprintf(stdout, "%s", gettext("OPTIONS")); + if (options->required) { + (void) fprintf(stdout, "%s ", gettext(">")); + } else { + (void) fprintf(stdout, "%s ", gettext("]")); + } + } + + /* print operand requirements */ + if (opCmd->optOpCmd & subcommand->value) { + (void) fprintf(stdout, gettext("[")); + } + if (!(opCmd->noOpCmd & subcommand->value)) { + (void) fprintf(stdout, gettext("<")); + if (objRules->operandDefinition) { + (void) fprintf(stdout, "%s", + objRules->operandDefinition); + } else { + /* + * Missing operand description + * from table + */ + assert(0); + } + } + if (opCmd->multOpCmd & subcommand->value) { + (void) fprintf(stdout, gettext(" ...")); + } + if (!(opCmd->noOpCmd & subcommand->value)) { + (void) fprintf(stdout, gettext(">")); + } + if (opCmd->optOpCmd & subcommand->value) { + (void) fprintf(stdout, gettext("]")); + } + + if (usageType == HELP_USAGE) { + (void) fprintf(stdout, "\n"); + return; + } + + /* print options for subcommand, object */ + if (options != NULL && options->optionString != NULL) { + (void) fprintf(stdout, "\n\t%s:", gettext("OPTIONS")); + for (i = 0; i < strlen(options->optionString); i++) { + if ((longOpt = getLongOption( + options->optionString[i])) + == NULL) { + /* no long option exists for short option */ + assert(0); + } + (void) fprintf(stdout, "\n\t\t-%c, --%s ", + options->optionString[i], longOpt); + optionArgDesc = + getOptionArgDesc(options->optionString[i]); + if (optionArgDesc != NULL) { + (void) fprintf(stdout, "<%s>", optionArgDesc); + } + if (options->exclusive && + strchr(options->exclusive, + options->optionString[i])) { + (void) fprintf(stdout, " (%s)", + gettext("exclusive")); + } + } + } + (void) fprintf(stdout, "\n"); +} + +/* + * input: + * type of usage statement to print + * + * Returns: + * return value of subUsage + */ +static void +usage(uint_t usageType) +{ + int i; + subcommand_t subcommand; + subcommand_t *sp; + + /* print general command usage */ + (void) fprintf(stdout, "%s:\t%s ", + gettext("Usage"), commandName); + + for (i = 0; standardCmdOptions[i].name; i++) { + (void) fprintf(stdout, "-%c", + standardCmdOptions[i].val); + if (standardCmdOptions[i+1].name) + (void) fprintf(stdout, ","); + } + + if (usageType == HELP_USAGE || usageType == GENERAL_USAGE) { + for (i = 0; standardSubCmdOptions[i].name; i++) { + (void) fprintf(stdout, ",--%s", + standardSubCmdOptions[i].name); + if (standardSubCmdOptions[i+1].name) + (void) fprintf(stdout, ","); + } + } + + (void) fprintf(stdout, "\n"); + + + /* print all subcommand usage */ + for (sp = _subcommands; sp->name; sp++) { + subcommand.name = sp->name; + subcommand.value = sp->value; + if (usageType == HELP_USAGE) { + (void) fprintf(stdout, "\n"); + } + subUsage(usageType, &subcommand); + } +} + +/* + * input: + * execFullName - exec name of program (argv[0]) + * + * Returns: + * command name portion of execFullName + */ +static char * +getExecBasename(char *execFullname) +{ + char *lastSlash, *execBasename; + + /* guard against '/' at end of command invocation */ + for (;;) { + lastSlash = strrchr(execFullname, '/'); + if (lastSlash == NULL) { + execBasename = execFullname; + break; + } else { + execBasename = lastSlash + 1; + if (*execBasename == '\0') { + *lastSlash = '\0'; + continue; + } + break; + } + } + return (execBasename); +} + +/* + * cmdParse is a parser that checks syntax of the input command against + * various rules tables. + * + * It provides usage feedback based upon the passed rules tables by calling + * two usage functions, usage, subUsage, and subUsageObject handling command, + * subcommand and object usage respectively. + * + * When syntax is successfully validated, the associated function is called + * using the subcommands table functions. + * + * Syntax is as follows: + * command subcommand object [<options>] [<operand>] + * + * There are two standard short and long options assumed: + * -?, --help Provides usage on a command or subcommand + * and stops further processing of the arguments + * + * -V, --version Provides version information on the command + * and stops further processing of the arguments + * + * These options are loaded by this function. + * + * input: + * argc, argv from main + * syntax rules tables (synTables_t structure) + * callArgs - void * passed by caller to be passed to subcommand function + * + * output: + * funcRet - pointer to int that holds subcommand function return value + * + * Returns: + * + * zero on successful syntax parse and function call + * + * 1 on unsuccessful syntax parse (no function has been called) + * This could be due to a version or help call or simply a + * general usage call. + * + * -1 check errno, call failed + * + * This module is not MT-safe. + * + */ +int +cmdParse(int argc, char *argv[], synTables_t synTable, void *callArgs, + int *funcRet) +{ + int getoptargc; + char **getoptargv; + int opt; + int operInd; + int i, j; + int len; + char *versionString; + char optionStringAll[MAXOPTIONSTRING + 1]; + optionProp_t *availOptions; + objectRules_t *objRules = NULL; + opCmd_t *opCmd = NULL; + subcommand_t *subcommand; + object_t *object; + cmdOptions_t cmdOptions[MAXOPTIONS + 1]; + struct option *lp; + optionTbl_t *optionTbl; + struct option intLongOpt[MAXOPTIONS + 1]; + + /* + * Check for NULLs on mandatory input arguments + * + * Note: longOptionTbl and optionRulesTbl can be NULL in the case + * where there is no caller defined options + * + */ + if (synTable.versionString == NULL || + synTable.subcommandTbl == NULL || + synTable.objectRulesTbl == NULL || + synTable.objectTbl == NULL || + funcRet == NULL) { + assert(0); + } + + + versionString = synTable.versionString; + + /* set global command name */ + commandName = getExecBasename(argv[0]); + + /* Set unbuffered output */ + setbuf(stdout, NULL); + + /* load globals */ + _subcommands = synTable.subcommandTbl; + _objectRules = synTable.objectRulesTbl; + _optionRules = synTable.optionRulesTbl; + _objects = synTable.objectTbl; + _clientOptionTbl = synTable.longOptionTbl; + + /* There must be at least two arguments */ + if (argc < 2) { + usage(GENERAL_USAGE); + return (1); + } + + bzero(&intLongOpt[0], sizeof (intLongOpt)); + + /* + * load standard subcommand options to internal long options table + * Two separate getopt_long(3C) tables are used. + */ + for (i = 0; standardSubCmdOptions[i].name; i++) { + intLongOpt[i].name = standardSubCmdOptions[i].name; + intLongOpt[i].has_arg = standardSubCmdOptions[i].has_arg; + intLongOpt[i].flag = standardSubCmdOptions[i].flag; + intLongOpt[i].val = standardSubCmdOptions[i].val; + } + + /* + * copy caller's long options into internal long options table + * We do this for two reasons: + * 1) We need to use the getopt_long option structure internally + * 2) We need to prepend the table with the standard option + * for all subcommands (currently -?) + */ + for (optionTbl = synTable.longOptionTbl; + optionTbl && optionTbl->name; optionTbl++, i++) { + if (i > MAXOPTIONS - 1) { + /* option table too long */ + assert(0); + } + intLongOpt[i].name = optionTbl->name; + intLongOpt[i].has_arg = optionTbl->has_arg; + intLongOpt[i].flag = NULL; + intLongOpt[i].val = optionTbl->val; + } + + /* set option table global */ + _longOptions = &intLongOpt[0]; + + + /* + * Check for help/version request immediately following command + * '+' in option string ensures POSIX compliance in getopt_long() + * which means that processing will stop at first non-option + * argument. + */ + while ((opt = getopt_long(argc, argv, "+?V", standardCmdOptions, + NULL)) != EOF) { + switch (opt) { + case '?': + /* + * getopt can return a '?' when no + * option letters match string. Check for + * the 'real' '?' in optopt. + */ + if (optopt == '?') { + usage(HELP_USAGE); + return (1); + } else { + usage(GENERAL_USAGE); + return (1); + } + case 'V': + (void) fprintf(stdout, "%s: %s %s\n", + commandName, gettext("Version"), + versionString); + return (1); + default: + break; + } + } + + /* + * subcommand is always in the second argument. If there is no + * recognized subcommand in the second argument, print error, + * general usage and then return. + */ + if (getSubcommand(argv[1], &subcommand) != 0) { + (void) fprintf(stderr, "%s: %s\n", + commandName, gettext("invalid subcommand")); + usage(GENERAL_USAGE); + return (1); + } + + if (argc == 2) { + (void) fprintf(stderr, "%s: %s\n", + commandName, gettext("missing object")); + subUsage(GENERAL_USAGE, subcommand); + return (1); + } + + getoptargv = argv; + getoptargv++; + getoptargc = argc; + getoptargc -= 1; + + while ((opt = getopt_long(getoptargc, getoptargv, "+?", + standardSubCmdOptions, NULL)) != EOF) { + switch (opt) { + case '?': + /* + * getopt can return a '?' when no + * option letters match string. Check for + * the 'real' '?' in optopt. + */ + if (optopt == '?') { + subUsage(HELP_USAGE, subcommand); + return (1); + } else { + subUsage(GENERAL_USAGE, subcommand); + return (1); + } + default: + break; + } + } + + + /* + * object is always in the third argument. If there is no + * recognized object in the third argument, print error, + * help usage for the subcommand and then return. + */ + if (getObject(argv[2], &object) != 0) { + (void) fprintf(stderr, "%s: %s\n", + commandName, gettext("invalid object")); + subUsage(HELP_USAGE, subcommand); + return (1); + } + + if (getObjectRules(object->value, &objRules) != 0) { + /* + * internal subcommand rules table error + * no object entry in object table + */ + assert(0); + } + + opCmd = &(objRules->opCmd); + + /* + * Is command valid for this object? + */ + if (opCmd->invOpCmd & subcommand->value) { + (void) fprintf(stderr, "%s: %s %s\n", commandName, + gettext("invalid subcommand for"), object->name); + subUsage(HELP_USAGE, subcommand); + return (1); + } + + /* + * offset getopt arg begin since + * getopt(3C) assumes options + * follow first argument + */ + getoptargv = argv; + getoptargv++; + getoptargv++; + getoptargc = argc; + getoptargc -= 2; + + bzero(optionStringAll, sizeof (optionStringAll)); + bzero(&cmdOptions[0], sizeof (cmdOptions)); + + j = 0; + /* + * Build optionStringAll from long options table + */ + for (lp = _longOptions; lp->name; lp++, j++) { + /* sanity check on string length */ + if (j + 1 >= sizeof (optionStringAll)) { + /* option table too long */ + assert(0); + } + optionStringAll[j] = lp->val; + if (lp->has_arg == required_argument) { + optionStringAll[++j] = ':'; + } + } + + i = 0; + /* + * Run getopt for all arguments against all possible options + * Store all options/option arguments in an array for retrieval + * later. + * Once all options are retrieved, check against object + * and subcommand (option rules table) for validity. + * This is done later. + */ + while ((opt = getopt_long(getoptargc, getoptargv, optionStringAll, + _longOptions, NULL)) != EOF) { + switch (opt) { + case '?': + if (optopt == '?') { + subUsageObject(DETAIL_USAGE, + subcommand, object); + return (1); + } else { + subUsage(GENERAL_USAGE, subcommand); + return (1); + } + default: + cmdOptions[i].optval = opt; + if (optarg) { + len = strlen(optarg); + if (len > sizeof (cmdOptions[i].optarg) + - 1) { + (void) fprintf(stderr, + "%s: %s\n", + commandName, + gettext("option too long")); + errno = EINVAL; + return (-1); + } + (void) strncpy(cmdOptions[i].optarg, + optarg, len); + } + i++; + break; + } + } + + /* + * increment past last option + */ + operInd = optind + 2; + + /* + * Check validity of given options, if any were given + */ + + /* get option string for this object and subcommand */ + availOptions = getOptions(object->value, subcommand->value); + + if (cmdOptions[0].optval != 0) { /* options were input */ + if (availOptions == NULL) { /* no options permitted */ + (void) fprintf(stderr, "%s: %s\n", + commandName, gettext("no options permitted")); + subUsageObject(HELP_USAGE, subcommand, object); + return (1); + } + for (i = 0; cmdOptions[i].optval; i++) { + /* Check for invalid options */ + if (availOptions->optionString == NULL) { + /* + * internal option table error + * There must be an option string if + * there is an entry in the table + */ + assert(0); + } + /* is the option in the available option string? */ + + if (!(strchr(availOptions->optionString, + cmdOptions[i].optval))) { + (void) fprintf(stderr, + "%s: '-%c': %s\n", + commandName, cmdOptions[i].optval, + gettext("invalid option")); + subUsageObject(DETAIL_USAGE, subcommand, + object); + return (1); + + /* Check for exclusive options */ + } else if (cmdOptions[1].optval != 0 && + availOptions->exclusive && + strchr(availOptions->exclusive, + cmdOptions[i].optval)) { + (void) fprintf(stderr, + "%s: '-%c': %s\n", + commandName, cmdOptions[i].optval, + gettext("is an exclusive option")); + subUsageObject(DETAIL_USAGE, subcommand, + object); + return (1); + } + } + } else { /* no options were input */ + if (availOptions != NULL && (availOptions->required)) { + (void) fprintf(stderr, "%s: %s\n", commandName, + gettext("at least one option required")); + subUsageObject(DETAIL_USAGE, subcommand, + object); + return (1); + } + } + + /* + * If there are no more arguments (operands), + * check to see if this is okay + */ + if ((operInd == argc) && + (opCmd->reqOpCmd & subcommand->value)) { + (void) fprintf(stderr, "%s: %s %s %s\n", + commandName, subcommand->name, object->name, + gettext("requires an operand")); + subUsageObject(HELP_USAGE, subcommand, object); + return (1); + } + + /* + * If there are more operands, + * check to see if this is okay + */ + if ((argc > operInd) && (opCmd->noOpCmd & subcommand->value)) { + (void) fprintf(stderr, "%s: %s %s %s\n", commandName, + subcommand->name, object->name, + gettext("takes no operands")); + subUsageObject(HELP_USAGE, subcommand, object); + return (1); + } + + /* + * If there is more than one more operand, + * check to see if this is okay + */ + if ((argc > operInd) && ((argc - operInd) != 1) && + !(opCmd->multOpCmd & subcommand->value)) { + (void) fprintf(stderr, "%s: %s %s %s\n", commandName, + subcommand->name, object->name, + gettext("accepts only a single operand")); + subUsageObject(HELP_USAGE, subcommand, object); + return (1); + } + + /* Finished syntax checks */ + + + /* Call appropriate function */ + *funcRet = subcommand->handler(argc - operInd, &argv[operInd], + object->value, &cmdOptions[0], callArgs); + + return (0); +} diff --git a/usr/src/cmd/iscsi/iscsitadm/cmdparse.h b/usr/src/cmd/iscsi/iscsitadm/cmdparse.h new file mode 100644 index 0000000000..6a8fe56c1d --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitadm/cmdparse.h @@ -0,0 +1,283 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _CMDPARSE_H +#define _CMDPARSE_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "getopt.h" + +/* subcommands must have a single bit on and must have exclusive values */ +#define SUBCOMMAND_BASE 1 +#define SUBCOMMAND(x) (SUBCOMMAND_BASE << x) + +#define OBJECT_BASE 1 +#define OBJECT(x) (OBJECT_BASE << x) + +/* maximum length of an option argument */ +#define MAXOPTARGLEN 256 + + +/* + * Add objects here + * + * EXAMPLE: + * object_t object[] = { + * {"target", TARGET}, + * {NULL, 0} + * }; + */ +typedef struct _object { + char *name; + uint_t value; +} object_t; + +/* + * This structure is passed into the caller's callback function and + * will contain a list of all options entered and their associated + * option arguments if applicable + */ +typedef struct _cmdOptions { + int optval; + char optarg[MAXOPTARGLEN + 1]; +} cmdOptions_t; + + +/* + * list of objects, subcommands, valid short options, required flag and + * exlusive option string + * + * objectValue -> object + * subcommandValue -> subcommand value + * optionProp.optionString -> short options that are valid + * optionProp.required -> flag indicating whether at least one option is + * required + * optionProp.exclusive -> short options that are required to be exclusively + * entered + * + * + * If it's not here, there are no options for that object. + * + * The long options table specifies whether an option argument is required. + * + * + * EXAMPLE: + * + * Based on DISCOVERY entry below: + * + * MODIFY DISCOVERY accepts -i, -s, -t and -l + * MODIFY DISCOVERY requires at least one option + * MODIFY DISCOVERY has no exclusive options + * + * + * optionRules_t optionRules[] = { + * {DISCOVERY, MODIFY, "istl", B_TRUE, NULL}, + * {0, 0, NULL, 0, NULL} + * }; + */ +typedef struct _optionProp { + char *optionString; + boolean_t required; + char *exclusive; +} optionProp_t; + +typedef struct _optionRules { + uint_t objectValue; + uint_t subcommandValue; + optionProp_t optionProp; +} optionRules_t; + +/* + * Rules for subcommands and object operands + * + * Every object requires an entry + * + * value, reqOpCmd, optOpCmd, noOpCmd, invCmd, multOpCmd + * + * value -> numeric value of object + * + * The following five fields are comprised of values that are + * a bitwise OR of the subcommands related to the object + * + * reqOpCmd -> subcommands that must have an operand + * optOpCmd -> subcommands that may have an operand + * noOpCmd -> subcommands that will have no operand + * invCmd -> subcommands that are invalid + * multOpCmd -> subcommands that can accept multiple operands + * operandDefinition -> Usage definition for the operand of this object + * + * + * EXAMPLE: + * + * based on TARGET entry below: + * MODIFY and DELETE subcomamnds require an operand + * LIST optionally requires an operand + * There are no subcommands that requires that no operand is specified + * ADD and REMOVE are invalid subcommands for this operand + * DELETE can accept multiple operands + * + * objectRules_t objectRules[] = { + * {TARGET, MODIFY|DELETE, LIST, 0, ADD|REMOVE, DELETE, + * "target-name"}, + * {0, 0, 0, 0, 0, NULL} + * }; + */ +typedef struct _opCmd { + uint_t reqOpCmd; + uint_t optOpCmd; + uint_t noOpCmd; + uint_t invOpCmd; + uint_t multOpCmd; +} opCmd_t; + +typedef struct _objectRules { + uint_t value; + opCmd_t opCmd; + char *operandDefinition; +} objectRules_t; + + +/* + * subcommand callback function + * + * argc - number of arguments in argv + * argv - operand arguments + * options - options entered on command line + * callData - pointer to caller data to be passed to subcommand function + */ +typedef int (*handler_t)(int argc, char *argv[], int, cmdOptions_t *options, + void *callData); + +/* + * Add new subcommands here + * + * EXAMPLE: + * subcommand_t subcommands[] = { + * {"add", ADD, addFunc}, + * {NULL, 0, NULL} + * }; + */ +typedef struct _subcommand { + char *name; + uint_t value; + handler_t handler; +} subcommand_t; + +#define required_arg required_argument +#define no_arg no_argument + +/* + * Add short options and long options here + * + * name -> long option name + * has_arg -> required_arg, no_arg + * val -> short option character + * argDesc -> description of option argument + * + * Note: This structure may not be used if your CLI has no + * options. However, -?, --help and -V, --version will still be supported + * as they are standard for every CLI. + * + * EXAMPLE: + * + * optionTbl_t options[] = { + * {"filename", arg_required, 'f', "out-filename"}, + * {NULL, 0, 0} + * }; + * + */ +typedef struct _optionTbl { + char *name; + int has_arg; + int val; + char *argDesc; +} optionTbl_t; + +/* + * After tables are set, assign them to this structure + * for passing into cmdparse() + */ +typedef struct _synTables { + char *versionString; + optionTbl_t *longOptionTbl; + subcommand_t *subcommandTbl; + object_t *objectTbl; + objectRules_t *objectRulesTbl; + optionRules_t *optionRulesTbl; +} synTables_t; + +/* + * cmdParse is a parser that checks syntax of the input command against + * various rules tables. + * + * When syntax is successfully validated, the function associated with the + * subcommand is called using the subcommands table functions. + * + * Syntax for the command is as follows: + * + * command subcommand [<options>] object [<operand ...>] + * + * + * There are two standard short and long options assumed: + * -?, --help Provides usage on a command or subcommand + * and stops further processing of the arguments + * + * -V, --version Provides version information on the command + * and stops further processing of the arguments + * + * These options are loaded by this function. + * + * input: + * argc, argv from main + * syntax rules tables (synTables_t structure) + * callArgs - void * passed by caller to be passed to subcommand function + * + * output: + * funcRet - pointer to int that holds subcommand function return value + * + * Returns: + * + * zero on successful syntax parse and function call + * + * 1 on unsuccessful syntax parse (no function has been called) + * This could be due to a version or help call or simply a + * general usage call. + * + * -1 check errno, call failed + * + */ +int cmdParse(int numOperands, char *operands[], synTables_t synTables, + void *callerArgs, int *funcRet); + +#ifdef __cplusplus +} +#endif + +#endif /* _CMDPARSE_H */ diff --git a/usr/src/cmd/iscsi/iscsitadm/helper.c b/usr/src/cmd/iscsi/iscsitadm/helper.c new file mode 100644 index 0000000000..cf3768f348 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitadm/helper.c @@ -0,0 +1,679 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <stdio.h> +#include <wchar.h> +#include <widec.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <unistd.h> +#include <libintl.h> +#include <limits.h> +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <errno.h> +#include <netinet/in.h> +#include <sys/iscsi_protocol.h> +#include <door.h> +#include <iscsi_door.h> +#include <sys/scsi/generic/inquiry.h> +#include <sys/mman.h> +#include <sys/filio.h> +#include <libxml/xmlreader.h> +#include <libscf.h> +#include <fcntl.h> + +#include "local_types.h" +#include "cmdparse.h" +#include "utility.h" +#include "xml.h" +#include "helper.h" + +extern char *cmdName; + +#define WAIT_FOR_SERVICE 15 +#define WAIT_FOR_DOOR 15 +static char *service = "system/iscsitgt:default"; +static stat_delta_t *stat_head; + +static Boolean_t +is_online() +{ + char *s; + Boolean_t rval = False; + + if ((s = smf_get_state(service)) != NULL) { + if (strcmp(s, SCF_STATE_STRING_ONLINE) == 0) + rval = True; + free(s); + } + + return (rval); +} + +Boolean_t +check_and_online() +{ + int i, + fd; + + if (is_online() == False) { + if (smf_enable_instance(service, 0) != 0) { + return (False); + } + for (i = 0; i < WAIT_FOR_SERVICE; i++) { + if (is_online() == True) + break; + (void) sleep(1); + } + if (i == WAIT_FOR_SERVICE) { + return (False); + } + } + + for (i = 0; i < WAIT_FOR_DOOR; i++) { + if ((fd = open(ISCSI_TARGET_MGMT_DOOR, 0)) >= 0) { + (void) close(fd); + return (True); + } + (void) sleep(1); + } + return (False); +} + +/* + * []---- + * | buffer_xml -- buffer incoming XML response until complete + * | + * | Incoming data from target may not be a complete XML message. So, + * | we need to wait until we've got everything otherwise the XML routines + * | will generate a parsing error for a short buffer. + * []---- + */ +Boolean_t +buffer_xml(char *s, char **storage, xml_node_t **np) +{ + xml_node_t *node = NULL; + xmlTextReaderPtr r; + char *p, + *e, + *end_tag, + hold_ch; + + p = *storage; + if (s != NULL) { + if (p == NULL) { + p = strdup(s); + } else { + p = realloc(p, strlen(p) + strlen(s) + 1); + (void) strcat(p, s); + } + } + if (p == NULL) { + return (False); + } + + if (*p != '<') { + return (False); + } + + if ((e = strchr(p, '>')) == NULL) { + return (False); + } + + /* + * The +3 is for the slash, closing tag character and null + * For example if p is pointing at a string which starts with + * "<foo>...." + * p will point at '<' and e will point at '>'. e - p is 4, yet + * the tag length is really 5 characters. We will need to create + * the end tag which also has a slash and NULL byte. + */ + if ((end_tag = malloc(e - p + 3)) == NULL) { + return (False); + } + + end_tag[0] = '<'; + end_tag[1] = '/'; + + /* + * Copy in the tag value and the closing tag character '>'. + */ + bcopy(p + 1, &end_tag[2], e - p); + + /* + * Add the null byte + */ + end_tag[e - p + 2] = '\0'; + + /* + * Do we have the closing string yet? If not, just return + */ + if ((e = strstr(p, end_tag)) == NULL) { + *storage = p; + return (False); + } + + /* + * Move past the closing tag and free the end_tag memory + */ + e += strlen(end_tag); + free(end_tag); + + /* + * NULL terminate the string and remember to save that character + * so that we can restore it later. + */ + hold_ch = *e; + *e = '\0'; + + if ((r = (xmlTextReaderPtr)xmlReaderForMemory(p, strlen(p), NULL, + NULL, 0)) == NULL) + return (False); + + while (xmlTextReaderRead(r) == 1) { + if (xml_process_node(r, &node) == False) + break; + } + + *np = node; + + xmlFreeTextReader(r); + + *e = hold_ch; + for (; isspace(*e); e++) + ; + if (*e != '\0') { + *storage = strdup(e); + } else + *storage = NULL; + free(p); + return (True); +} + +xml_node_t * +send_data(char *hostname, char *first_str) +{ + struct sockaddr_in sin; + struct hostent *hp; + int s, + min, + nmsgs, + nbytes, + error_num, + port = 3269; + struct pollfd fds[1]; + nfds_t nfds = 1; + char input[128], + *storage = NULL; + xml_node_t *node = NULL; + + if (hostname != NULL) { + if ((hp = getipnodebyname(hostname, AF_INET, + AI_ALL | AI_ADDRCONFIG | AI_V4MAPPED, + &error_num)) == NULL) { + (void) printf("Can't find IP addr for %s\n", hostname); + exit(1); + } + + bzero(&sin, sizeof (sin)); + sin.sin_family = hp->h_addrtype; + sin.sin_port = ntohs(port); + bcopy(hp->h_addr_list[0], &sin.sin_addr, hp->h_length); + + if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + (void) printf("Failed to open socket\n"); + exit(1); + } + + if (connect(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) { + (void) printf("Failed to connect\n"); + exit(1); + } + + fds[0].fd = s; + fds[0].events = POLLIN; + fds[0].revents = 0; + + if (write(s, first_str, strlen(first_str)) != + strlen(first_str)) { + perror("Failed to send request"); + exit(1); + } + while (poll(fds, nfds, -1) != -1) { + if ((nmsgs = ioctl(s, FIONREAD, &nbytes)) < 0) + exit(1); + if ((nmsgs == 0) && (nbytes == 0)) + exit(1); + min = MIN(nbytes, sizeof (input) - 1); + if (read(s, input, min) != min) + exit(1); + input[min] = '\0'; + + if (buffer_xml(input, &storage, &node) == True) { + break; + } + } + } else { + door_arg_t d; + xmlTextReaderPtr r; + + d.data_ptr = first_str; + d.data_size = strlen(first_str) + 1; + d.desc_ptr = NULL; + d.desc_num = 0; + d.rbuf = NULL; + d.rsize = 0; + + if (((s = open(ISCSI_TARGET_MGMT_DOOR, 0)) < 0) || + (door_call(s, &d) < 0)) { + if (s != -1) + (void) close(s); + + if (check_and_online() == False) { + (void) fprintf(stderr, + "iscsitadm: SMF service unavailable\n"); + exit(1); + } + + if ((s = open(ISCSI_TARGET_MGMT_DOOR, 0)) < 0) { + (void) fprintf(stderr, + "iscsitadm: Failed to open iSCSI target" + " management door\n"); + exit(1); + } + if (door_call(s, &d) < 0) { + perror("door_call"); + exit(1); + } + } + + if ((r = (xmlTextReaderPtr)xmlReaderForMemory(d.rbuf, + strlen(d.rbuf), NULL, NULL, 0)) == NULL) { + perror("xmlReaderForMemory"); + exit(1); + } + while (xmlTextReaderRead(r) == 1) + if (xml_process_node(r, &node) == False) + break; + xmlFreeTextReader(r); + (void) munmap(d.rbuf, d.rsize); + } + (void) close(s); + return (node); +} + +/* + * Retrieve CHAP secret from input + */ +int +getSecret(char *secret, int *secretLen, int minSecretLen, int maxSecretLen) +{ + char *chapSecret; + + /* XXX Should we prompt for hex or ascii printable input? */ + + /* get password */ + chapSecret = getpassphrase(gettext("Enter secret:")); + + if (strlen(chapSecret) > maxSecretLen) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("secret too long")); + *secret = NULL; + return (1); + } + + if (strlen(chapSecret) < minSecretLen) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("secret too short")); + *secret = NULL; + return (1); + } + + (void) strcpy(secret, chapSecret); + + chapSecret = getpassphrase(gettext("Re-enter secret:")); + if (strcmp(secret, chapSecret) != 0) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("secret not changed")); + *secret = NULL; + return (1); + } + *secretLen = strlen(chapSecret); + return (0); +} + +void +iSCSINameCheckStatusDisplay(iSCSINameCheckStatusType status) +{ + switch (status) { + case iSCSINameLenZero: + (void) fprintf(stderr, "%s: %s\n", + cmdName, gettext("empty iSCSI name.")); + break; + case iSCSINameLenExceededMax: + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("iSCSI name exceeded maximum length.")); + break; + case iSCSINameUnknownType: + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("unknown iSCSI name type.")); + break; + case iSCSINameIqnFormatError: + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("iqn formatting error.")); + break; + case iSCSINameEUIFormatError: + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("eui formatting error.")); + break; + } +} + +/* + * This helper function could go into a utility module for general use. + */ +int +parseAddress(char *address_port_str, + uint16_t defaultPort, + char *address_str, + size_t address_str_len, + uint16_t *port, + boolean_t *isIpv6) +{ + char port_str[64]; + int tmp_port; + + if (address_port_str[0] == '[') { + /* IPv6 address */ + char *close_bracket_pos; + close_bracket_pos = strchr(address_port_str, ']'); + if (!close_bracket_pos) { + syslog(LOG_USER|LOG_DEBUG, + "IP address format error: %s\n", address_str); + return (PARSE_ADDR_MISSING_CLOSING_BRACKET); + } + + *close_bracket_pos = NULL; + (void) strlcpy(address_str, &address_port_str[1], + address_str_len); + + /* Extract the port number */ + close_bracket_pos++; + if (*close_bracket_pos == ':') { + close_bracket_pos++; + if (*close_bracket_pos != NULL) { + (void) strlcpy(port_str, close_bracket_pos, + 64); + tmp_port = atoi(port_str); + if (((tmp_port > 0) && + (tmp_port > USHRT_MAX)) || + (tmp_port < 0)) { + /* Port number out of range */ + syslog(LOG_USER|LOG_DEBUG, + "Specified port out of range: %d", + tmp_port); + return (PARSE_ADDR_PORT_OUT_OF_RANGE); + } else { + *port = (uint16_t)tmp_port; + } + } else { + *port = defaultPort; + } + } else { + *port = defaultPort; + } + + *isIpv6 = B_TRUE; + } else { + /* IPv4 address */ + char *colon_pos; + colon_pos = strchr(address_port_str, ':'); + if (!colon_pos) { + /* No port number specified. */ + *port = defaultPort; + (void) strlcpy(address_str, address_port_str, + address_str_len); + } else { + *colon_pos = (char)NULL; + (void) strlcpy(address_str, address_port_str, + address_str_len); + + /* Extract the port number */ + colon_pos++; + if (*colon_pos != NULL) { + (void) strlcpy(port_str, colon_pos, 64); + tmp_port = atoi(port_str); + if (((tmp_port > 0) && + (tmp_port > USHRT_MAX)) || + (tmp_port < 0)) { + /* Port number out of range */ + syslog(LOG_USER|LOG_DEBUG, + "Specified port out of range: %d", + tmp_port); + return (PARSE_ADDR_PORT_OUT_OF_RANGE); + } else { + *port = (uint16_t)tmp_port; + } + } else { + *port = defaultPort; + } + } + + *isIpv6 = B_FALSE; + } + + return (PARSE_ADDR_OK); +} + +/* + * []---- + * | Following routine (number_to_scaled_string) is lifted + * | from usr/src/cmd/fs.d/df.c + * []---- + */ +/* + * Convert an unsigned long long to a string representation and place the + * result in the caller-supplied buffer. + * The given number is in units of "unit_from" size, + * this will first be converted to a number in 1024 or 1000 byte size, + * depending on the scaling factor. + * Then the number is scaled down until it is small enough to be in a good + * human readable format i.e. in the range 0 thru scale-1. + * If it's smaller than 10 there's room enough to provide one decimal place. + * The value "(unsigned long long)-1" is a special case and is always + * converted to "-1". + * Returns a pointer to the caller-supplied buffer. + */ +char * +number_to_scaled_string( + char *buf, /* put the result here */ + unsigned long long number, /* convert this number */ + int unit_from, + int scale) +{ + unsigned long long save = 0; + char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */ + char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */ + + if ((long long)number == (long long)-1) { + (void) strcpy(buf, "-1"); + return (buf); + } + + if ((number < scale) && (unit_from == 1)) { + (void) sprintf(buf, "%4llu", number); + return (buf); + } + /* + * Convert number from unit_from to given scale (1024 or 1000). + * This means multiply number by unit_from and divide by scale. + * + * Would like to multiply by unit_from and then divide by scale, + * but if the first multiplication would overflow, then need to + * divide by scale and then multiply by unit_from. + */ + if (number > (UINT64_MAX / (unsigned long long)unit_from)) { + number = (number / (unsigned long long)scale) * + (unsigned long long)unit_from; + } else { + number = (number * (unsigned long long)unit_from) / + (unsigned long long)scale; + } + + /* + * Now we have number as a count of scale units. + * Stop scaling when we reached exa bytes, then something is + * probably wrong with our number. + */ + + while ((number >= scale) && (*uom != 'E')) { + uom++; /* next unit of measurement */ + save = number; + number = (number + (scale / 2)) / scale; + } + /* check if we should output a decimal place after the point */ + if (save && ((save / scale) < 10)) { + /* sprintf() will round for us */ + float fnum = (float)save / scale; + (void) sprintf(buf, "%2.1f%c", fnum, *uom); + } else { + (void) sprintf(buf, "%4llu%c", number, *uom); + } + return (buf); +} + +void +stats_load_counts(xml_node_t *n, stat_delta_t *d) +{ + xml_node_t *conn = NULL, + *lun; + char *val; + + bzero(d, sizeof (*d)); + d->device = n->x_value; + + while (conn = xml_node_next(n, XML_ELEMENT_CONN, conn)) { + lun = NULL; + while (lun = xml_node_next(conn, XML_ELEMENT_LUN, lun)) { + if (xml_find_value_str(lun, XML_ELEMENT_READCMDS, + &val) == True) { + d->read_cmds += strtoll(val, NULL, 0); + free(val); + } + if (xml_find_value_str(lun, XML_ELEMENT_WRITECMDS, + &val) == True) { + d->write_cmds += strtoll(val, NULL, 0); + free(val); + } + if (xml_find_value_str(lun, XML_ELEMENT_READBLKS, + &val) == True) { + d->read_blks += strtoll(val, NULL, 0); + free(val); + } + if (xml_find_value_str(lun, XML_ELEMENT_WRITEBLKS, + &val) == True) { + d->write_blks += strtoll(val, NULL, 0); + free(val); + } + } + } +} + +stat_delta_t * +stats_prev_counts(stat_delta_t *cp) +{ + stat_delta_t *n; + + for (n = stat_head; n; n = n->next) { + if (strcmp(n->device, cp->device) == 0) + return (n); + } + if ((n = calloc(1, sizeof (*n))) == NULL) + return (NULL); + n->device = strdup(cp->device); + if (stat_head == NULL) + stat_head = n; + else { + n->next = stat_head; + stat_head = n; + } + return (n); +} + +void +stats_update_counts(stat_delta_t *p, stat_delta_t *c) +{ + p->read_cmds += c->read_cmds - p->read_cmds; + p->write_cmds += c->write_cmds - p->write_cmds; + p->read_blks += c->read_blks - p->read_blks; + p->write_blks += c->write_blks - p->write_blks; +} + +void +stats_free() +{ + stat_delta_t *n; + + /* CSTYLED */ + for (;stat_head;) { + n = stat_head->next; + free(stat_head->device); + free(stat_head); + stat_head = n; + } +} + +static char spaces[128]; + +/* + * []---- + * | dospace -- generate a string which has the appropriate number of spaces + * | + * | NOTE: Since this function modifies a static buffer usage of this + * | function may not be what's expected. For example: + * | printf("%sfoo%sbar\n", dospace(1), dospace(2)); would produce + * | ' foo bar' + * | instead of + * | ' foo bar' + * []---- + */ +char * +dospace(int n) +{ + (void) memset(spaces, ' ', sizeof (spaces)); + spaces[sizeof (spaces) - 1] = '\0'; + + if (n < sizeof (spaces)) + spaces[n * 4] = '\0'; + return (spaces); +} diff --git a/usr/src/cmd/iscsi/iscsitadm/helper.h b/usr/src/cmd/iscsi/iscsitadm/helper.h new file mode 100644 index 0000000000..543e44d259 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitadm/helper.h @@ -0,0 +1,91 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _HELPER_H +#define _HELPER_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_ISCSI_NAME_LEN 223 +#define MAX_ADDRESS_LEN 255 +#define MIN_CHAP_SECRET_LEN 12 +#define MAX_CHAP_SECRET_LEN 16 +#define DEFAULT_ISCSI_PORT 3260 +#define DEFAULT_RADIUS_PORT 1812 +#define MAX_CHAP_NAME_LEN 512 + +/* forward declarations */ +#define PARSE_ADDR_OK 0 +#define PARSE_ADDR_MISSING_CLOSING_BRACKET 1 +#define PARSE_ADDR_PORT_OUT_OF_RANGE 2 +#define PARSE_TARGET_OK 0 +#define PARSE_TARGET_INVALID_TPGT 1 +#define PARSE_TARGET_INVALID_ADDR 2 + +typedef enum iSCSINameCheckStatus { + iSCSINameCheckOK, + iSCSINameLenZero, + iSCSINameLenExceededMax, + iSCSINameUnknownType, + iSCSINameIqnFormatError, + iSCSINameEUIFormatError +} iSCSINameCheckStatusType; + +typedef struct stat_delta { + struct stat_delta *next; + char *device; + size_t read_cmds, + write_cmds, + read_blks, + write_blks; +} stat_delta_t; + +/* helper functions */ +int getSecret(char *, int *, int, int); +xml_node_t *send_data(char *hostname, char *first_str); +int parseAddress(char *address_port_str, uint16_t defaultPort, + char *address_str, size_t address_str_len, + uint16_t *port, boolean_t *isIpv6); +char *number_to_scaled_string( + char *buf, + unsigned long long number, + int unit_from, + int scale); +void stats_load_counts(xml_node_t *n, stat_delta_t *d); +stat_delta_t *stats_prev_counts(stat_delta_t *cp); +void stats_update_counts(stat_delta_t *p, stat_delta_t *c); +void stats_free(); +char *dospace(int n); + +#ifdef __cplusplus +} +#endif + +#endif /* _HELPER_H */ diff --git a/usr/src/cmd/iscsi/iscsitadm/main.c b/usr/src/cmd/iscsi/iscsitadm/main.c new file mode 100644 index 0000000000..60e010dddc --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitadm/main.c @@ -0,0 +1,1584 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/param.h> +#include <unistd.h> +#include <libintl.h> +#include <limits.h> +#include <string.h> +#include <syslog.h> +#include <errno.h> +#include <sys/stat.h> +#include <zone.h> + +#include "cmdparse.h" +#include "xml.h" +#include "helper.h" + +#define CREATE SUBCOMMAND(0) +#define LIST SUBCOMMAND(1) +#define MODIFY SUBCOMMAND(2) +#define DELETE SUBCOMMAND(3) +#define SHOW SUBCOMMAND(4) + +#define TARGET OBJECT(0) +#define INITIATOR OBJECT(1) +#define ADMIN OBJECT(2) +#define TPGT OBJECT(3) +#define STATS OBJECT(4) + +#define VERSION_STRING_MAX_LEN 10 +#define MAX_IPADDRESS_LEN 128 + +/* + * Version number: + * MAJOR - This should only change when there is an incompatible change made + * to the interfaces or the output. + * + * MINOR - This should change whenever there is a new command or new feature + * with no incompatible change. + */ +#define VERSION_STRING_MAJOR "1" +#define VERSION_STRING_MINOR "0" + +#define OPT_ENABLE "enable" +#define OPT_DISABLE "disable" +#define OPT_TRUE "true" +#define OPT_FALSE "false" + +/* subcommand functions */ +static int createFunc(int, char **, int, cmdOptions_t *, void *); +static int listFunc(int, char **, int, cmdOptions_t *, void *); +static int modifyFunc(int, char **, int, cmdOptions_t *, void *); +static int deleteFunc(int, char **, int, cmdOptions_t *, void *); +static int showFunc(int, char **, int, cmdOptions_t *, void *); + +/* object functions per subcommand */ +static int createTarget(int, char *[], cmdOptions_t *); +static int createInitiator(int, char *[], cmdOptions_t *); +static int createTpgt(int, char *[], cmdOptions_t *); +static int modifyTarget(int, char *[], cmdOptions_t *); +static int modifyInitiator(int, char *[], cmdOptions_t *); +static int modifyTpgt(int, char *[], cmdOptions_t *); +static int modifyAdmin(int, char *[], cmdOptions_t *); +static int deleteTarget(int, char *[], cmdOptions_t *); +static int deleteInitiator(int, char *[], cmdOptions_t *); +static int deleteTpgt(int, char *[], cmdOptions_t *); +static int listTarget(int, char *[], cmdOptions_t *); +static int listInitiator(int, char *[], cmdOptions_t *); +static int listTpgt(int, char *[], cmdOptions_t *); +static int showAdmin(int, char *[], cmdOptions_t *); +static int showStats(int, char *[], cmdOptions_t *); + +/* globals */ +char *cmdName; +static char *hostname = NULL; + +/* + * Add new options here + */ +optionTbl_t longOptions[] = { + {"size", required_arg, 'z', "size k/m/g/t"}, + {"type", required_arg, 't', "disk/tape/osd/raw"}, + {"lun", required_arg, 'u', "number"}, + {"alias", required_arg, 'a', "value"}, + {"backing-store", required_arg, 'b', "pathname"}, + {"tpgt", required_arg, 'p', "tpgt number"}, + {"acl", required_arg, 'l', "local initiator"}, + {"maxrecv", required_arg, 'm', "max recv data segment length"}, + {"chap-secret", no_arg, 'C', NULL}, + {"chap-name", required_arg, 'H', "chap username"}, + {"iqn", required_arg, 'n', "iSCSI node name"}, + {"ip-address", required_arg, 'i', "ip address"}, + {"base-directory", required_arg, 'd', "directory"}, + {"radius-access", required_arg, 'R', "enable/disable"}, + {"radius-server", required_arg, 'r', "hostname[:port]"}, + {"radius-secret", no_arg, 'P', NULL}, + {"isns-access", required_arg, 'S', "enable/disable"}, + {"fast-write-ack", required_arg, 'f', "enable/disable"}, + {"verbose", no_arg, 'v', NULL}, + {"interval", required_arg, 'I', "seconds"}, + {"count", required_arg, 'N', "number"}, + {"all", no_arg, 'A', NULL}, + {NULL, 0, 0, 0} +}; + +/* + * Add new subcommands here + */ +subcommand_t subcommands[] = { + {"create", CREATE, createFunc}, + {"list", LIST, listFunc}, + {"modify", MODIFY, modifyFunc}, + {"delete", DELETE, deleteFunc}, + {"show", SHOW, showFunc}, + {NULL, 0, NULL} +}; + +/* + * Add objects here + */ +object_t objects[] = { + {"target", TARGET}, + {"initiator", INITIATOR}, + {"admin", ADMIN}, + {"tpgt", TPGT}, + {"stats", STATS}, + {NULL, 0} +}; + +/* + * Rules for subcommands and objects + * ReqiredOp, OptioalOp, NoOp, InvalidOp, MultiOp + */ +objectRules_t objectRules[] = { + /* + * create/modify/delete subcmd requires an operand + * list subcmd optionally requires an operand + * no subcmd requires no operand + * no subcmd is invalid for this operand + * no subcmd can accept multiple operands + */ + {TARGET, CREATE|MODIFY|DELETE, LIST, 0, SHOW, 0, "local-target"}, + /* + * create/modify/delete subcmd requires an operand + * list subcmd optionally requires an operand + * no subcmd requires no operand + * no subcmd is invalid for this operand + * no subcmd can accept multiple operands + */ + {INITIATOR, CREATE|MODIFY|DELETE, LIST, 0, SHOW, 0, "local-initiator"}, + /* + * no subcmd requires an operand + * no subcmd optionally requires an operand + * modify/list subcmd requires no operand + * create/delete subcmd are invlaid for this operand + * no subcmd can accept multiple operands + */ + {ADMIN, 0, 0, MODIFY|SHOW, CREATE|DELETE|LIST, 0, NULL}, + /* + * create/modify/delete subcmd requires an operand + * list subcmd optionally requires an operand + * no subcmd requires no operand + * no subcmd is invalid for this operand + * no subcmd can accept multiple operands + */ + {TPGT, CREATE|MODIFY|DELETE, LIST, 0, SHOW, 0, "local-tpgt"}, + /* + * no subcmd requires an operand + * list subcmd optionally requires an operand + * no subcmd requires no operand + * create/delete/modify subcmd are invalid for this operand + * no subcmd can accept multiple operands + */ + {STATS, 0, SHOW, 0, CREATE|MODIFY|DELETE|LIST, 0, "local-target"}, + {0, 0, 0, 0, 0, NULL} +}; + +/* + * list of objects, subcommands, valid short options, required flag and + * exclusive option string + * + * If it's not here, there are no options for that object. + */ +optionRules_t optionRules[] = { + {TARGET, CREATE, "tuzab", B_TRUE, NULL}, + {TARGET, MODIFY, "plamzu", B_TRUE, NULL}, + {TARGET, DELETE, "ulp", B_TRUE, NULL}, + {TARGET, LIST, "v", B_FALSE, NULL}, + {INITIATOR, CREATE, "n", B_TRUE, NULL}, + {INITIATOR, MODIFY, "CH", B_TRUE, NULL}, + {INITIATOR, DELETE, "A", B_TRUE, NULL}, + {INITIATOR, LIST, "v", B_FALSE, NULL}, + {TPGT, MODIFY, "i", B_TRUE, NULL}, + {TPGT, DELETE, "Ai", B_TRUE, NULL}, + {TPGT, LIST, "v", B_FALSE, NULL}, + {ADMIN, MODIFY, "dHCRrPSf", B_TRUE, NULL}, + {STATS, SHOW, "vIN", B_FALSE, NULL}, +}; + + + +/*ARGSUSED*/ +static int +createFunc(int operandLen, char *operand[], int object, cmdOptions_t *options, + void *addArgs) +{ + int ret; + + switch (object) { + case TARGET: + ret = createTarget(operandLen, operand, options); + break; + case INITIATOR: + ret = createInitiator(operandLen, operand, options); + break; + case TPGT: + ret = createTpgt(operandLen, operand, options); + break; + default: + (void) fprintf(stderr, "%s: %s\n", + cmdName, gettext("unknown object")); + ret = 1; + break; + } + return (ret); +} + +/*ARGSUSED*/ +static int +listFunc(int operandLen, char *operand[], int object, cmdOptions_t *options, + void *addArgs) +{ + int ret; + + switch (object) { + case TARGET: + ret = listTarget(operandLen, operand, options); + break; + case INITIATOR: + ret = listInitiator(operandLen, operand, options); + break; + case TPGT: + ret = listTpgt(operandLen, operand, options); + break; + default: + (void) fprintf(stderr, "%s: %s\n", + cmdName, gettext("unknown object")); + ret = 1; + break; + } + return (ret); +} + +/*ARGSUSED*/ +static int +showFunc(int operandLen, char *operand[], int object, cmdOptions_t *options, + void *addArgs) +{ + int ret; + + switch (object) { + case STATS: + ret = showStats(operandLen, operand, options); + break; + case ADMIN: + ret = showAdmin(operandLen, operand, options); + break; + default: + (void) fprintf(stderr, "%s: %s\n", + cmdName, gettext("unknown object")); + ret = 1; + break; + } + return (ret); +} + +/*ARGSUSED*/ +static int +modifyFunc(int operandLen, char *operand[], int object, cmdOptions_t *options, + void *addArgs) +{ + int ret; + + switch (object) { + case TARGET: + ret = modifyTarget(operandLen, operand, options); + break; + case INITIATOR: + ret = modifyInitiator(operandLen, operand, options); + break; + case TPGT: + ret = modifyTpgt(operandLen, operand, options); + break; + case ADMIN: + ret = modifyAdmin(operandLen, operand, options); + break; + default: + (void) fprintf(stderr, "%s: %s\n", + cmdName, gettext("unknown object")); + ret = 1; + break; + } + return (ret); +} + +/*ARGSUSED*/ +static int +deleteFunc(int operandLen, char *operand[], int object, cmdOptions_t *options, + void *addArgs) +{ + int ret; + + switch (object) { + case TARGET: + ret = deleteTarget(operandLen, operand, options); + break; + case INITIATOR: + ret = deleteInitiator(operandLen, operand, options); + break; + case TPGT: + ret = deleteTpgt(operandLen, operand, options); + break; + default: + (void) fprintf(stderr, "%s: %s\n", + cmdName, gettext("unknown object")); + ret = 1; + break; + } + return (ret); +} + +static int +formatErrString(xml_node_t *node) +{ + int code = 0, + rtn = 0; + char *msg = NULL; + + if ((strcmp(node->x_name, XML_ELEMENT_ERROR) == 0) && + (xml_find_value_int(node, XML_ELEMENT_CODE, &code) == B_TRUE) && + (xml_find_value_str(node, XML_ELEMENT_MESSAGE, &msg) == B_TRUE)) { + + /* + * 1000 is the success code, so we don't need to display + * the success message. + */ + if (code != 1000) { + (void) fprintf(stderr, "%s: %s %s\n", + cmdName, gettext("Error"), msg); + rtn = 1; + } + } else { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Bad XML response")); + rtn = 1; + } + if (msg) + free(msg); + return (rtn); +} + +/*ARGSUSED*/ +static int +createTarget(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + + if (operand == NULL) + return (1); + + buf_add_tag(&first_str, "create", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + for (; optionList->optval; optionList++) { + switch (optionList->optval) { + case 't': /* type */ + if ((strcmp(optionList->optarg, "disk")) && + (strcmp(optionList->optarg, "tape")) && + (strcmp(optionList->optarg, "raw")) && + (strcmp(optionList->optarg, "osd"))) { + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown type")); + free(first_str); + return (1); + } else { + xml_add_tag(&first_str, + XML_ELEMENT_TYPE, + optionList->optarg); + } + break; + case 'z': /* size */ + xml_add_tag(&first_str, XML_ELEMENT_SIZE, + optionList->optarg); + break; + case 'u': /* lun number */ + xml_add_tag(&first_str, XML_ELEMENT_LUN, + optionList->optarg); + break; + case 'a': /* alias */ + xml_add_tag(&first_str, XML_ELEMENT_ALIAS, + optionList->optarg); + break; + case 'b': /* backing store */ + xml_add_tag(&first_str, XML_ELEMENT_BACK, + optionList->optarg); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + } + + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End); + buf_add_tag(&first_str, "create", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +createInitiator(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + + if (operand == NULL) + return (1); + if (options == NULL) + return (1); + + buf_add_tag(&first_str, "create", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + switch (optionList->optval) { + case 'n': /* iqn */ + xml_add_tag(&first_str, XML_ELEMENT_INAME, optionList->optarg); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End); + buf_add_tag(&first_str, "create", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +createTpgt(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + + if (operand == NULL) + return (1); + + buf_add_tag(&first_str, "create", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End); + buf_add_tag(&first_str, "create", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +modifyTarget(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + + if (operand == NULL) + return (1); + + buf_add_tag(&first_str, "modify", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + for (; optionList->optval; optionList++) { + switch (optionList->optval) { + case 'p': /* tpgt number */ + xml_add_tag(&first_str, XML_ELEMENT_TPGT, + optionList->optarg); + break; + case 'l': /* acl */ + xml_add_tag(&first_str, XML_ELEMENT_ACL, + optionList->optarg); + break; + case 'a': /* alias */ + xml_add_tag(&first_str, XML_ELEMENT_ALIAS, + optionList->optarg); + break; + case 'm': /* max recv */ + xml_add_tag(&first_str, XML_ELEMENT_MAXRECV, + optionList->optarg); + break; + case 'z': /* grow lun size */ + xml_add_tag(&first_str, XML_ELEMENT_SIZE, + optionList->optarg); + break; + case 'u': + xml_add_tag(&first_str, XML_ELEMENT_LUN, + optionList->optarg); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + } + + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End); + buf_add_tag(&first_str, "modify", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +modifyInitiator(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + char chapSecret[MAX_CHAP_SECRET_LEN]; + int secretLen = 0; + int ret = 0; + + if (operand == NULL) + return (1); + if (options == NULL) + return (1); + + buf_add_tag(&first_str, "modify", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + for (; optionList->optval; optionList++) { + switch (optionList->optval) { + case 'H': /* chap-name */ + xml_add_tag(&first_str, XML_ELEMENT_CHAPNAME, + optionList->optarg); + break; + case 'C': /* chap-secret */ + ret = getSecret((char *)&chapSecret[0], &secretLen, + MIN_CHAP_SECRET_LEN, MAX_CHAP_SECRET_LEN); + if (ret != 0) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Cannot read CHAP secret")); + return (ret); + } + chapSecret[secretLen] = '\0'; + xml_add_tag(&first_str, XML_ELEMENT_CHAPSECRET, + chapSecret); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + } + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End); + buf_add_tag(&first_str, "modify", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +modifyTpgt(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + boolean_t isIpv6 = B_FALSE; + uint16_t port; + char IpAddress[MAX_IPADDRESS_LEN]; + + if (operand == NULL) + return (1); + if (optionList == NULL) + return (1); + + buf_add_tag(&first_str, "modify", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + switch (optionList->optval) { + case 'i': /* ip address */ + if (parseAddress(optionList->optarg, 0, + IpAddress, 256, &port, &isIpv6) != + PARSE_ADDR_OK) { + return (1); + } + xml_add_tag(&first_str, XML_ELEMENT_IPADDR, IpAddress); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End); + buf_add_tag(&first_str, "modify", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +modifyAdmin(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + char chapSecret[MAX_CHAP_SECRET_LEN], + olddir[MAXPATHLEN], + newdir[MAXPATHLEN]; + int secretLen = 0; + int ret = 0; + + if (operand == NULL) + return (1); + if (options == NULL) + return (1); + + buf_add_tag(&first_str, "modify", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + for (; optionList->optval; optionList++) { + switch (optionList->optval) { + case 'd': /* base directory */ + (void) getcwd(olddir, sizeof (olddir)); + + /* + * Attempt to create the new base directory. + * This may fail for one of two reasons. + * (a) The path given is invalid or (b) it + * already exists. If (a) is true then then + * following chdir() will fail and the user + * notified. If (b) is true, then chdir() will + * succeed. + */ + (void) mkdir(optionList->optarg, 0700); + + if (chdir(optionList->optarg) == -1) { + (void) fprintf(stderr, "%s: %s\n", + cmdName, gettext("Invalid path")); + free(first_str); + return (1); + } + (void) getcwd(newdir, sizeof (newdir)); + xml_add_tag(&first_str, XML_ELEMENT_BASEDIR, + newdir); + chdir(olddir); + break; + case 'H': /* chap name */ + xml_add_tag(&first_str, XML_ELEMENT_CHAPNAME, + optionList->optarg); + break; + case 'C': /* chap secert */ + ret = getSecret((char *)&chapSecret[0], + &secretLen, + MIN_CHAP_SECRET_LEN, + MAX_CHAP_SECRET_LEN); + if (ret != 0) { + (void) fprintf(stderr, "%s: %s\n", + cmdName, + gettext("Cannot read CHAP secret")); + free(first_str); + return (ret); + } + chapSecret[secretLen] = '\0'; + xml_add_tag(&first_str, XML_ELEMENT_CHAPSECRET, + chapSecret); + break; + case 'R': /* radius access */ + if (strcmp(optionList->optarg, + OPT_ENABLE) == 0) { + xml_add_tag(&first_str, + XML_ELEMENT_RAD_ACCESS, OPT_TRUE); + } else + if (strcmp(optionList->optarg, + OPT_DISABLE) == 0) { + xml_add_tag(&first_str, + XML_ELEMENT_RAD_ACCESS, OPT_FALSE); + } else { + (void) fprintf(stderr, "%s: %s\n", + cmdName, + gettext("Option value should be" + "enable/disable")); + free(first_str); + return (1); + } + break; + case 'r': /* radius server */ + xml_add_tag(&first_str, XML_ELEMENT_RAD_SERV, + optionList->optarg); + break; + case 'P': /* radius secret */ + ret = getSecret((char *)&chapSecret[0], + &secretLen, MIN_CHAP_SECRET_LEN, + MAX_CHAP_SECRET_LEN); + if (ret != 0) { + (void) fprintf(stderr, "%s: %s\n", + cmdName, + gettext("Cannot read RADIUS " + "secret")); + free(first_str); + return (ret); + } + chapSecret[secretLen] = '\0'; + xml_add_tag(&first_str, XML_ELEMENT_RAD_SECRET, + chapSecret); + break; + case 'S': /* iSNS access */ + if (strcmp(optionList->optarg, + OPT_ENABLE) == 0) { + xml_add_tag(&first_str, + XML_ELEMENT_ISNS_ACCESS, OPT_TRUE); + } else + if (strcmp(optionList->optarg, + OPT_DISABLE) == 0) { + xml_add_tag(&first_str, + XML_ELEMENT_ISNS_ACCESS, OPT_FALSE); + } else { + (void) fprintf(stderr, "%s: %s\n", + cmdName, + gettext("Option value should be" + "enable/disable")); + free(first_str); + return (1); + } + break; + case 'f': /* fast write back */ + if (strcmp(optionList->optarg, + OPT_ENABLE) == 0) { + xml_add_tag(&first_str, + XML_ELEMENT_FAST, OPT_TRUE); + } else + if (strcmp(optionList->optarg, + OPT_DISABLE) == 0) { + xml_add_tag(&first_str, + XML_ELEMENT_FAST, OPT_FALSE); + } else { + (void) fprintf(stderr, "%s: %s\n", + cmdName, + gettext("Option value should be" + "enable/disable")); + free(first_str); + return (1); + } + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + } + + buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_End); + buf_add_tag(&first_str, "modify", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +deleteTarget(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + + if (operand == NULL) + return (1); + if (options == NULL) + return (1); + + buf_add_tag(&first_str, "delete", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + switch (optionList->optval) { + case 'u': /* all */ + xml_add_tag(&first_str, XML_ELEMENT_LUN, optionList->optarg); + break; + case 'l': /* acl */ + xml_add_tag(&first_str, XML_ELEMENT_ACL, optionList->optarg); + break; + case 'p': /* tpgt number */ + xml_add_tag(&first_str, XML_ELEMENT_TPGT, optionList->optarg); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End); + buf_add_tag(&first_str, "delete", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +deleteInitiator(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + + if (operand == NULL) + return (1); + if (options == NULL) + return (1); + + buf_add_tag(&first_str, "delete", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + switch (optionList->optval) { + case 'A': /* all */ + xml_add_tag(&first_str, XML_ELEMENT_ALL, optionList->optarg); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End); + buf_add_tag(&first_str, "delete", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +/*ARGSUSED*/ +static int +deleteTpgt(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + cmdOptions_t *optionList = options; + boolean_t isIpv6 = B_FALSE; + uint16_t port; + char IpAddress[MAX_IPADDRESS_LEN]; + + if (operand == NULL) + return (1); + if (options == NULL) + return (1); + + buf_add_tag(&first_str, "delete", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start); + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + switch (optionList->optval) { + case 'A': /* all */ + xml_add_tag(&first_str, XML_ELEMENT_ALL, optionList->optarg); + break; + case 'i': /* ip address */ + if (parseAddress(optionList->optarg, 0, + IpAddress, 256, &port, &isIpv6) != + PARSE_ADDR_OK) { + return (1); + } + xml_add_tag(&first_str, XML_ELEMENT_IPADDR, IpAddress); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End); + buf_add_tag(&first_str, "delete", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + return (formatErrString(node)); +} + +static int +listTarget(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node = NULL; + xml_node_t *n1 = NULL; /* pointer to node (depth=1) */ + xml_node_t *n2 = NULL; /* pointer to node (depth=2) */ + xml_node_t *n3 = NULL; /* pointer to node (depth=3) */ + xml_node_t *n4 = NULL; /* pointer to node (depth=4) */ + int conns; + char buf[32]; + Boolean_t verbose = False; + + if (operand == NULL) + return (1); + + buf_add_tag(&first_str, "list", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start); + + if (operandLen) + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + /* + * Always retrieve the iostats which will give us the + * connection count information even if we're not doing + * a verbose output. + */ + xml_add_tag(&first_str, XML_ELEMENT_IOSTAT, OPT_TRUE); + + if (options) { + switch (options->optval) { + case 0: + break; + case 'v': + xml_add_tag(&first_str, XML_ELEMENT_LUNINFO, OPT_TRUE); + verbose = True; + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", cmdName, + options->optval, gettext("unknown option")); + free(first_str); + return (1); + } + } + + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End); + buf_add_tag(&first_str, "list", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + + if (strcmp(node->x_name, XML_ELEMENT_RESULT)) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Bad XML response")); + return (1); + } + + n1 = NULL; + while ((n1 = xml_node_next_child(node, XML_ELEMENT_TARG, n1)) != NULL) { + (void) printf("%s: %s\n", gettext("Target"), n1->x_value); + n2 = xml_node_next_child(n1, XML_ELEMENT_INAME, NULL); + (void) printf("%s%s: %s\n", dospace(1), gettext("iSCSI Name"), + n2 ? n2->x_value : gettext("Not set")); + + if ((n2 = xml_node_next_child(n1, XML_ELEMENT_ALIAS, NULL)) != + NULL) + (void) printf("%s%s: %s\n", dospace(1), + gettext("Alias"), n2->x_value); + + if ((n2 = xml_node_next_child(n1, XML_ELEMENT_MAXRECV, NULL)) != + NULL) + (void) printf("%s%s: %s\n", dospace(1), + gettext("MaxRecv"), n2->x_value); + + /* + * Count the number of connections available. + */ + n2 = NULL; + conns = 0; + while (n2 = xml_node_next_child(n1, XML_ELEMENT_CONN, n2)) + conns++; + (void) printf("%s%s: %d\n", dospace(1), gettext("Connections"), + conns); + + if (verbose == False) + continue; + + /* + * Displaying the individual connections must be done + * first when verbose is turned on because you'll notice + * above that we've left the output hanging with a label + * indicating connections are coming next. + */ + n2 = NULL; + while (n2 = xml_node_next_child(n1, XML_ELEMENT_CONN, n2)) { + (void) printf("%s%s:\n", dospace(2), + gettext("Initiator")); + (void) printf("%s%s: %s\n", dospace(3), + gettext("iSCSI Name"), n2->x_value); + n3 = xml_node_next_child(n2, XML_ELEMENT_ALIAS, NULL); + (void) printf("%s%s: %s\n", dospace(3), + gettext("Alias"), + n3 ? n3->x_value : gettext("unknown")); + } + + (void) printf("%s%s:\n", dospace(1), gettext("ACL list")); + n2 = xml_node_next_child(n1, XML_ELEMENT_ACLLIST, NULL); + n3 = NULL; + while (n3 = xml_node_next_child(n2, XML_ELEMENT_INIT, n3)) { + (void) printf("%s%s: %s\n", dospace(2), + gettext("Initiator"), + n3->x_value); + } + + (void) printf("%s%s:\n", dospace(1), gettext("TPGT list")); + n2 = xml_node_next_child(n1, XML_ELEMENT_TPGTLIST, NULL); + n3 = NULL; + while (n3 = xml_node_next_child(n2, XML_ELEMENT_TPGT, n3)) { + (void) printf("%s%s: %s\n", dospace(2), + gettext("TPGT"), + n3->x_value); + } + + (void) printf("%s%s:\n", dospace(1), + gettext("LUN information")); + n2 = xml_node_next_child(n1, XML_ELEMENT_LUNINFO, NULL); + n3 = NULL; + while (n3 = xml_node_next_child(n2, XML_ELEMENT_LUN, n3)) { + (void) printf("%s%s: %s\n", dospace(2), gettext("LUN"), + n3->x_value); + + n4 = xml_node_next_child(n3, XML_ELEMENT_GUID, NULL); + (void) printf("%s%s: %s\n", dospace(3), gettext("GUID"), + n4 ? n4->x_value : gettext("unknown")); + + n4 = xml_node_next_child(n3, XML_ELEMENT_VID, NULL); + (void) printf("%s%s: %s\n", dospace(3), gettext("VID"), + n4 ? n4->x_value : gettext("unknown")); + + n4 = xml_node_next_child(n3, XML_ELEMENT_PID, NULL); + (void) printf("%s%s: %s\n", dospace(3), gettext("PID"), + n4 ? n4->x_value : gettext("unknown")); + + n4 = xml_node_next_child(n3, XML_ELEMENT_DTYPE, NULL); + (void) printf("%s%s: %s\n", dospace(3), gettext("Type"), + n4 ? n4->x_value : gettext("unknown")); + + n4 = xml_node_next_child(n3, XML_ELEMENT_SIZE, NULL); + if (n4 && (strtol(n4->x_value, NULL, 0) != 0)) { + (void) printf("%s%s: %s\n", dospace(3), + gettext("Size"), + number_to_scaled_string(buf, + strtoll(n4->x_value, + NULL, 0), 512, 1024)); + } else { + (void) printf("%s%s: %s\n", dospace(3), + gettext("Size"), gettext("unknown")); + } + + n4 = xml_node_next_child(n3, XML_ELEMENT_BACK, NULL); + if (n4) { + (void) printf("%s%s: %s\n", dospace(3), + gettext("Backing store"), n4->x_value); + } + + n4 = xml_node_next_child(n3, XML_ELEMENT_STATUS, NULL); + (void) printf("%s%s: %s\n", dospace(3), + gettext("Status"), + n4 ? n4->x_value : gettext("unknown")); + } + } + + return (0); +} + +static int +listInitiator(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node; + xml_node_t *n1 = NULL; /* pointer to node (depth=1) */ + xml_node_t *n2 = NULL; /* pointer to node (depth=2) */ + Boolean_t verbose = False; + cmdOptions_t *optionList = options; + + if (operand == NULL) + return (1); + + buf_add_tag(&first_str, "list", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start); + + if (operandLen) { + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + } + if (optionList) { + switch (optionList->optval) { + case 0: + break; + case 'v': + verbose = True; + xml_add_tag(&first_str, + XML_ELEMENT_VERBOSE, OPT_TRUE); + break; + + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + } + + buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End); + buf_add_tag(&first_str, "list", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + + if (strcmp(node->x_name, XML_ELEMENT_RESULT)) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Bad XML response")); + return (1); + } + + n1 = NULL; + while (n1 = xml_node_next_child(node, XML_ELEMENT_INIT, n1)) { + (void) printf("%s: %s\n", gettext("Initiator"), n1->x_value); + + n2 = xml_node_next_child(n1, XML_ELEMENT_INAME, NULL); + (void) printf("%s%s: %s\n", dospace(1), gettext("iSCSI Name"), + n2 ? n2->x_value : gettext("Not set")); + + n2 = xml_node_next_child(n1, XML_ELEMENT_CHAPNAME, NULL); + (void) printf("%s%s: %s\n", dospace(1), gettext("CHAP Name"), + n2 ? n2->x_value : gettext("Not set")); + + if (verbose == True) { + n2 = xml_node_next_child(n1, XML_ELEMENT_CHAPSECRET, + NULL); + (void) printf("%s%s: %s\n", dospace(1), + gettext("CHAP Secret"), + n2 ? gettext("Set") : gettext("Not set")); + } + + } + + return (0); +} + +static int +listTpgt(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node = NULL; + xml_node_t *n1 = NULL; /* pointer to node (depth=1) */ + xml_node_t *n2 = NULL; /* pointer to node (depth=2) */ + cmdOptions_t *optionList = options; + Boolean_t verbose = False; + int addrs; + + if (operand == NULL) + return (1); + + buf_add_tag(&first_str, "list", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start); + + if (operandLen) + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + if (optionList) { + switch (optionList->optval) { + case 0: /* no options, treat as --verbose */ + break; + case 'v': + verbose = True; + xml_add_tag(&first_str, + XML_ELEMENT_VERBOSE, OPT_TRUE); + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", + cmdName, optionList->optval, + gettext("unknown option")); + free(first_str); + return (1); + } + } + + buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End); + buf_add_tag(&first_str, "list", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + + if (strcmp(node->x_name, XML_ELEMENT_RESULT)) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Bad XML response")); + return (1); + } + + n1 = NULL; + while (n1 = xml_node_next_child(node, XML_ELEMENT_TPGT, n1)) { + (void) printf("%s: %s\n", gettext("TPGT"), n1->x_value); + n2 = NULL; + addrs = 0; + while (n2 = xml_node_next_child(n1, XML_ELEMENT_IPADDR, n2)) { + if (verbose == True) + (void) printf("%s%s: %s\n", dospace(1), + gettext("IP Address"), + n2 ? n2->x_value : gettext("Not set")); + addrs++; + } + + if (verbose == False) { + (void) printf("%s%s: %d\n", dospace(1), + gettext("IP Address count"), addrs); + } else if (addrs == 0) { + + /* + * Verbose is true, but there where no addresses + * for this TPGT. To keep the output consistent + * dump a "Not set" string out. + */ + (void) printf("%s%s: %s\n", dospace(1), + gettext("IP Address"), gettext("Not set")); + } + } + + return (0); +} + +/*ARGSUSED*/ +static int +showAdmin(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL; + xml_node_t *node = NULL; + xml_node_t *n1 = NULL; /* pointer to node (depth=1) */ + xml_node_t *n2 = NULL; /* pointer to node (depth=2) */ + + if (operand == NULL) + return (1); + + buf_add_tag(&first_str, "list", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_End); + buf_add_tag(&first_str, "list", Tag_End); + + node = send_data(hostname, first_str); + free(first_str); + + if (strcmp(node->x_name, XML_ELEMENT_RESULT)) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Bad XML response")); + return (1); + } + + (void) printf("%s:\n", cmdName); + + n1 = xml_node_next_child(node, XML_ELEMENT_ADMIN, NULL); + if (n1 == NULL) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Bad XML response")); + return (1); + } + + n2 = xml_node_next_child(n1, XML_ELEMENT_BASEDIR, NULL); + (void) printf("%s%s: %s\n", dospace(1), gettext("Base Directory"), + n2 ? n2->x_value : gettext("Not set")); + + n2 = xml_node_next_child(n1, XML_ELEMENT_CHAPNAME, NULL); + (void) printf("%s%s: %s\n", dospace(1), gettext("CHAP Name"), + n2 ? n2->x_value : gettext("Not set")); + + n2 = xml_node_next_child(n1, XML_ELEMENT_RAD_ACCESS, NULL); + (void) printf("%s%s: ", dospace(1), gettext("RADIUS Access")); + if (n2) { + if (strcmp(n2->x_value, OPT_TRUE) == 0) + (void) printf("%s\n", gettext("Enabled")); + else + (void) printf("%s\n", gettext("Disabled")); + } else + (void) printf("%s\n", gettext("Not set")); + + n2 = xml_node_next_child(n1, XML_ELEMENT_RAD_SERV, NULL); + (void) printf("%s%s: %s\n", dospace(1), gettext("RADIUS Server"), + n2 ? n2->x_value : gettext("Not set")); + + n2 = xml_node_next_child(n1, XML_ELEMENT_ISNS_ACCESS, NULL); + (void) printf("%s%s: ", dospace(1), gettext("iSNS Access")); + if (n2) { + if (strcmp(n2->x_value, OPT_TRUE) == 0) + (void) printf("%s\n", gettext("Enabled")); + else + (void) printf("%s\n", gettext("Disabled")); + } else + (void) printf("%s\n", gettext("Not set")); + + n2 = xml_node_next_child(n1, XML_ELEMENT_FAST, NULL); + (void) printf("%s%s: ", dospace(1), gettext("Fast Write ACK")); + if (n2) { + if (strcmp(n2->x_value, OPT_TRUE) == 0) + (void) printf("%s\n", gettext("Enabled")); + else + (void) printf("%s\n", gettext("Disabled")); + } else + (void) printf("%s\n", gettext("Not set")); + + return (0); +} + +static int +showStats(int operandLen, char *operand[], cmdOptions_t *options) +{ + char *first_str = NULL, + scale_buf[16]; + xml_node_t *node, + *n1; + int interval = -1, + count = -1, + header; + stat_delta_t cur_data, + *pd; + + buf_add_tag(&first_str, "list", Tag_Start); + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start); + + xml_add_tag(&first_str, XML_ELEMENT_IOSTAT, OPT_TRUE); + if (operandLen) + xml_add_tag(&first_str, XML_ELEMENT_NAME, operand[0]); + + for (; options->optval; options++) { + switch (options->optval) { + case 0: + break; + case 'I': /* optarg = refresh interval */ + interval = atoi(options->optarg); + if (interval == 0) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("interval must be non-zero")); + free(first_str); + return (1); + } + break; + case 'N': + count = atoi(options->optarg); + if (count == 0) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("count must be non-zero")); + free(first_str); + return (1); + } + break; + default: + (void) fprintf(stderr, "%s: %c: %s\n", cmdName, + options->optval, gettext("unknown option")); + free(first_str); + return (1); + } + } + + buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End); + buf_add_tag(&first_str, "list", Tag_End); + + header = 1; + /*CONSTANTCONDITION*/ + while (1) { + if (--header == 0) { + (void) printf("%20s %12s %12s\n", " ", + gettext("operations"), gettext("bandwidth ")); + (void) printf("%-20s %5s %5s %5s %5s\n", + gettext("device"), gettext("read"), + gettext("write"), gettext("read"), + gettext("write")); + (void) printf("%-20s %5s %5s %5s %5s\n", + "--------------------", "-----", "-----", + "-----", "-----"); + header = 20; + } + node = send_data(hostname, first_str); + + if (strcmp(node->x_name, XML_ELEMENT_RESULT)) { + (void) fprintf(stderr, "%s: %s\n", cmdName, + gettext("Bad XML response")); + free(first_str); + xml_tree_free(node); + stats_free(); + return (1); + } + + n1 = NULL; + while (n1 = xml_node_next_child(node, XML_ELEMENT_TARG, n1)) { + stats_load_counts(n1, &cur_data); + if ((pd = stats_prev_counts(&cur_data)) == NULL) { + free(first_str); + xml_tree_free(node); + return (1); + } + (void) printf("%-20s ", pd->device); + (void) printf("%5s ", + number_to_scaled_string(scale_buf, + cur_data.read_cmds - pd->read_cmds, 1, 1024)); + (void) printf("%5s ", + number_to_scaled_string(scale_buf, + cur_data.write_cmds - pd->write_cmds, 1, 1024)); + (void) printf("%5s ", + number_to_scaled_string(scale_buf, + cur_data.read_blks - pd->read_blks, 512, 1024)); + (void) printf("%5s\n", + number_to_scaled_string(scale_buf, + cur_data.write_blks - pd->write_blks, 512, 1024)); + stats_update_counts(pd, &cur_data); + } + xml_tree_free(node); + + if (count == -1) { + if (interval == -1) + /* No count or internal, do it just once */ + break; + else + (void) sleep(interval); + } else if (--count) { + if (interval == -1) + break; + else + (void) sleep(interval); + } else + break; + } + + stats_free(); + free(first_str); + return (0); +} + +/* + * input: + * execFullName - exec name of program (argv[0]) + * + * Returns: + * command name portion of execFullName + */ +static char * +getExecBasename(char *execFullname) +{ + char *lastSlash, *execBasename; + + /* guard against '/' at end of command invocation */ + for (;;) { + lastSlash = strrchr(execFullname, '/'); + if (lastSlash == NULL) { + execBasename = execFullname; + break; + } else { + execBasename = lastSlash + 1; + if (*execBasename == '\0') { + *lastSlash = '\0'; + continue; + } + break; + } + } + return (execBasename); +} + +/* + * main calls a parser that checks syntax of the input command against + * various rules tables. + * + * The parser provides usage feedback based upon same tables by calling + * two usage functions, usage and subUsage, handling command and subcommand + * usage respectively. + * + * The parser handles all printing of usage syntactical errors + * + * When syntax is successfully validated, the parser calls the associated + * function using the subcommands table functions. + * + * Syntax is as follows: + * command subcommand [options] resource-type [<object>] + * + * The return value from the function is placed in funcRet + */ +int +main(int argc, char *argv[]) +{ + synTables_t synTables; + char versionString[VERSION_STRING_MAX_LEN]; + int ret; + int funcRet; + void *subcommandArgs = NULL; + + /* set global command name */ + cmdName = getExecBasename(argv[0]); + + if (getzoneid() != GLOBAL_ZONEID) { + (void) fprintf(stderr, + "%s: this command is only available in the 'global' " + "zone\n", cmdName); + exit(1); + } + + (void) snprintf(versionString, sizeof (versionString), "%s.%s", + VERSION_STRING_MAJOR, VERSION_STRING_MINOR); + synTables.versionString = versionString; + synTables.longOptionTbl = &longOptions[0]; + synTables.subcommandTbl = &subcommands[0]; + synTables.objectTbl = &objects[0]; + synTables.objectRulesTbl = &objectRules[0]; + synTables.optionRulesTbl = &optionRules[0]; + + /* call the CLI parser */ + ret = cmdParse(argc, argv, synTables, subcommandArgs, &funcRet); + if (ret == 1) { + (void) printf("%s %s(1M)\n", + gettext("For more information, please see"), cmdName); + return (1); + } else if (ret == -1) { + perror(cmdName); + return (1); + } + + return (funcRet); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/Makefile b/usr/src/cmd/iscsi/iscsitgtd/Makefile new file mode 100644 index 0000000000..da0edb9e7e --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/Makefile @@ -0,0 +1,83 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +#ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# cmd/iscsi/iscsitgtd/Makefile + +PROG= iscsitgtd +OBJS = main.o mgmt.o mgmt_create.o mgmt_list.o mgmt_modify.o mgmt_remove.o +OBJS += iscsi_authclient.o iscsi_authglue.o iscsi_cmd.o iscsi_conn.o +OBJS += iscsi_crc.o iscsi_ffp.o iscsi_login.o iscsi_sess.o radius.o +OBJS += t10_sam.o t10_spc.o t10_sbc.o t10_raw_if.o t10_ssc.o t10_osd.o +OBJS += util.o util_err.o util_ifname.o util_port.o util_queue.o +POFILE= iscsitgtd.po +POFILES = $(OBJS:%.o=%.po) + +include ../../Makefile.cmd +include $(SRC)/cmd/iscsi/Makefile.iscsi + +$(64ONLY)SUBDIRS= $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +MANIFEST = iscsi_target.xml + +ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) +$(ROOTSVCSYSTEM)/iscsi_target.xml := OWNER = root +$(ROOTSVCSYSTEM)/iscsi_target.xml := GROUP = bin +$(ROOTSVCSYSTEM)/iscsi_target.xml := FILEMODE = 0444 + +CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I${ISCSICOMMONDIR} + +all := TARGET = all +install := TARGET = install +clean := TARGET = clean +clobber := TARGET = clobber +lint := TARGET = lint + +.KEEP_STATE: + +all: $(SUBDIRS) + +clean clobber lint: $(SUBDIRS) + +install: $(SUBDIRS) $(ROOTMANIFEST) + -$(RM) $(ROOTUSRSBINPROG) + -$(LN) $(ISAEXEC) $(ROOTUSRSBINPROG) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +catalog: $(POFILE) + +$(POFILE): $(POFILES) + $(RM) $@ + cat $(POFILES) > $@ + +check: $(CHKMANIFEST) + +FRC: + +include ../../Makefile.targ + diff --git a/usr/src/cmd/iscsi/iscsitgtd/Makefile.com b/usr/src/cmd/iscsi/iscsitgtd/Makefile.com new file mode 100644 index 0000000000..ff7064d4da --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/Makefile.com @@ -0,0 +1,77 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# cmd/iscsi/iscsitgtd/Makefile.com + +PROG= iscsitgtd +OBJS = main.o mgmt.o mgmt_create.o mgmt_list.o mgmt_modify.o mgmt_remove.o +OBJS += iscsi_authclient.o iscsi_authglue.o iscsi_cmd.o iscsi_conn.o +OBJS += iscsi_crc.o iscsi_ffp.o iscsi_login.o iscsi_sess.o radius.o +OBJS += t10_sam.o t10_spc.o t10_sbc.o t10_raw_if.o t10_ssc.o t10_osd.o +OBJS += util.o util_err.o util_ifname.o util_port.o util_queue.o +SRCS= $(OBJS:%.o=../%.c) $(COMMON_SRCS) + +include ../../../Makefile.cmd +include $(SRC)/cmd/iscsi/Makefile.iscsi + +SUFFIX_LINT = .ln + +CFLAGS += $(CCVERBOSE) +CPPFLAGS += -D_LARGEFILE64_SOURCE=1 -I$(ISCSICOMMONDIR) -I/usr/include/libxml2 +CFLAGS64 += $(CCVERBOSE) + +GROUP=sys + +CLEANFILES += $(OBJS) + +.KEEP_STATE: + +all: $(PROG) + +LDLIBS += -luuid -lxml2 -lsocket -lnsl -ldoor -lavl -lmd5 -ladm -lefi +$(PROG): $(OBJS) $(COMMON_OBJS) + $(LINK.c) $(OBJS) $(COMMON_OBJS) -o $@ $(LDLIBS) + $(POST_PROCESS) + +lint := LINTFLAGS += -u +lint := LINTFLAGS64 += -u + +# lint: lint_SRCS +lint: $(SRCS:../%=%$(SUFFIX_LINT)) + +%$(SUFFIX_LINT): ../% + ${LINT.c} -I.. ${INCLUDES} -y -c $< && touch $@ + +%.o: $(ISCSICOMMONDIR)/%.c + $(COMPILE.c) $< + +%.o: ../%.c + $(COMPILE.c) $< + +clean: + $(RM) $(CLEANFILES) $(COMMON_OBJS) *$(SUFFIX_LINT) + +include ../../../Makefile.targ diff --git a/usr/src/cmd/iscsi/iscsitgtd/amd64/Makefile b/usr/src/cmd/iscsi/iscsitgtd/amd64/Makefile new file mode 100644 index 0000000000..cee9d7ffbe --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/amd64/Makefile @@ -0,0 +1,31 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.cmd.64 + +install: all $(ROOTUSRSBINPROG64) diff --git a/usr/src/cmd/iscsi/iscsitgtd/errcode.h b/usr/src/cmd/iscsi/iscsitgtd/errcode.h new file mode 100644 index 0000000000..cf3a38e52e --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/errcode.h @@ -0,0 +1,118 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _TARGET_ERRCODE_H +#define _TARGET_ERRCODE_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ERR_SUCCESS = 1000, + ERR_NULL_XML_MESSAGE, + ERR_SYNTAX_EMPTY, + ERR_SYNTAX_MISSING_ALL, + ERR_SYNTAX_MISSING_BACKING_STORE, + ERR_SYNTAX_MISSING_INAME, + ERR_SYNTAX_MISSING_IPADDR, + ERR_SYNTAX_MISSING_NAME, + ERR_SYNTAX_MISSING_OBJECT, + ERR_SYNTAX_MISSING_OPERAND, + ERR_SYNTAX_MISSING_SIZE, + ERR_SYNTAX_MISSING_TYPE, + ERR_SYNTAX_EMPTY_ACL, + ERR_SYNTAX_EMPTY_ALIAS, + ERR_SYNTAX_EMPTY_CHAPNAME, + ERR_SYNTAX_EMPTY_CHAPSECRET, + ERR_SYNTAX_EMPTY_IPADDR, + ERR_SYNTAX_EMPTY_MAXRECV, + ERR_SYNTAX_EMPTY_TPGT, + ERR_SYNTAX_INVALID_NAME, + ERR_INVALID_COMMAND, + ERR_INVALID_OBJECT, + ERR_INVALID_IP, + ERR_INVALID_BASEDIR, + ERR_INVALID_TPGT, + ERR_INVALID_MAXRECV, + ERR_INVALID_RADSRV, + ERR_INVALID_SIZE, + ERR_INIT_EXISTS, + ERR_NAME_TO_LONG, + ERR_LUN_EXISTS, + ERR_TPGT_EXISTS, + ERR_ACL_NOT_FOUND, + ERR_INIT_NOT_FOUND, + ERR_TARG_NOT_FOUND, + ERR_LUN_NOT_FOUND, + ERR_LUN_INVALID_RANGE, + ERR_TPGT_NOT_FOUND, + ERR_ACCESS_RAW_DEVICE_FAILED, + ERR_CREATE_METADATA_FAILED, + ERR_CREATE_SYMLINK_FAILED, + ERR_CREATE_NAME_TO_LONG, + ERR_DISK_BACKING_MUST_BE_REGULAR_FILE, + ERR_DISK_BACKING_NOT_VALID_RAW, + ERR_DISK_BACKING_SIZE_OR_FILE, + ERR_STAT_BACKING_FAILED, + ERR_RAW_PART_NOT_CAP, + ERR_CREATE_TARGET_DIR_FAILED, + ERR_ENCODE_GUID_FAILED, + ERR_INIT_XML_READER_FAILED, + ERR_OPEN_PARAM_FILE_FAILED, + ERR_UPDATE_MAINCFG_FAILED, + ERR_UPDATE_TARGCFG_FAILED, + ERR_VALID_TARG_EXIST, + ERR_TARGCFG_MISSING_INAME, + ERR_NO_MATCH, + ERR_NO_MEM, + ERR_LUN_ZERO_NOT_LAST, + ERR_LUN_ZERO_NOT_FIRST, + ERR_SIZE_MOD_BLOCK, + ERR_CANT_SHRINK_LU, + ERR_RESIZE_WRONG_TYPE, + ERR_RESIZE_WRONG_DTYPE, + ERR_LUN_NOT_GROWN, + ERR_FILE_TO_BIG, + ERR_FAILED_TO_CREATE_LU, + ERR_TAPE_NOT_SUPPORTED_IN_32BIT, + ERR_INTERNAL_ERROR +} err_code_t; + +char * +errcode_to_str(err_code_t err_code); + +#ifdef __cplusplus +} +#endif + +#endif /* _TARGET_ERRCODE_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/i386/Makefile b/usr/src/cmd/iscsi/iscsitgtd/i386/Makefile new file mode 100644 index 0000000000..fde14781e6 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/i386/Makefile @@ -0,0 +1,31 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# cmd/iscsi/iscsitgt/i386/Makefile + +include ../Makefile.com + +install: all $(ROOTUSRSBINPROG32) diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_authclient.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_authclient.c new file mode 100644 index 0000000000..8aa279ed0e --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_authclient.c @@ -0,0 +1,3042 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2000 by Cisco Systems, Inc. All rights reserved. + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * FIXME: If this is true we have some problems. draft 15!? + * + * This file implements the iSCSI CHAP authentication method based on + * draft-ietf-ips-iscsi-15.txt. The code in this file is meant + * to be platform independent, and makes use of only limited library + * functions, presently only string.h. Platform dependent routines + * are defined in iscsi_authclient.h, but implemented in another file. + * + * This code in this files assumes a single thread of execution + * for each IscsiAuthClient structure, and does no locking. + */ +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef _KERNEL + +#include "iscsi.h" + +#else + +#include <strings.h> +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#endif + +#include <sys/iscsi_authclient.h> + +struct iscsiAuthKeyInfo_t { + const char *name; +}; +typedef struct iscsiAuthKeyInfo_t IscsiAuthKeyInfo; + + +IscsiAuthClientGlobalStats iscsiAuthClientGlobalStats; + +/* + * Note: The ordering of this table must match the order + * defined by IscsiAuthKeyType in iscsiAuthClient.h. + */ +static IscsiAuthKeyInfo iscsiAuthClientKeyInfo[iscsiAuthKeyTypeMaxCount] = { + {"AuthMethod"}, + {"CHAP_A"}, + {"CHAP_N"}, + {"CHAP_R"}, + {"CHAP_I"}, + {"CHAP_C"} +}; + +static const char iscsiAuthClientHexString[] = "0123456789abcdefABCDEF"; +static const char iscsiAuthClientBase64String[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char iscsiAuthClientAuthMethodChapOptionName[] = "CHAP"; + + +static int +iscsiAuthClientCheckString(const char *s, + unsigned int maxLength, unsigned int *pOutLength) +{ + unsigned int length; + + if (!s) { + return (TRUE); + } + + for (length = 0; length < maxLength; length++) { + if (*s++ == '\0') { + if (pOutLength) { + *pOutLength = length; + } + return (FALSE); + } + } + + return (TRUE); +} + + +static int +iscsiAuthClientStringCopy(char *stringOut, const char *stringIn, + unsigned int length) +{ + if (!stringOut || !stringIn || length == 0) { + return (TRUE); + } + + while ((*stringOut++ = *stringIn++) != '\0') { + if (--length == 0) { + stringOut--; + *stringOut = '\0'; + return (TRUE); + } + } + + return (FALSE); +} + + +static int +iscsiAuthClientStringAppend(char *stringOut, const char *stringIn, + unsigned int length) +{ + if (!stringOut || !stringIn || length == 0) { + return (TRUE); + } + + while (*stringOut++ != '\0') { + if (--length == 0) { + stringOut--; + *stringOut = '\0'; + return (TRUE); + } + } + + stringOut--; + + while ((*stringOut++ = *stringIn++) != '\0') { + if (--length == 0) { + stringOut--; + *stringOut = '\0'; + return (TRUE); + } + } + + return (FALSE); +} + + +static int +iscsiAuthClientStringIndex(const char *s, int c) +{ + int n = 0; + + while (*s != '\0') { + if (*s++ == c) { + return (n); + } + n++; + } + + return (-1); +} + + +static int +iscsiAuthClientCheckNodeType(int nodeType) +{ + if (nodeType == iscsiAuthNodeTypeInitiator || + nodeType == iscsiAuthNodeTypeTarget) { + return (FALSE); + } + + return (TRUE); +} + + +static int +iscsiAuthClientCheckVersion(int value) +{ + if (value == iscsiAuthVersionDraft8 || value == iscsiAuthVersionRfc) { + + return (FALSE); + } + + return (TRUE); +} + +static int +iscsiAuthClientCheckAuthMethodOption(int value) +{ + if (value == iscsiAuthOptionNone || value == iscsiAuthMethodChap) { + + return (FALSE); + } + + return (TRUE); +} + + +static const char * +iscsiAuthClientAuthMethodOptionToText(IscsiAuthClient * client, int value) +{ + const char *s; + + switch (value) { + case iscsiAuthOptionReject: + s = client->rejectOptionName; + break; + + case iscsiAuthOptionNone: + s = client->noneOptionName; + break; + + case iscsiAuthMethodChap: + s = iscsiAuthClientAuthMethodChapOptionName; + break; + + default: + s = 0; + } + + return (s); +} + + +static int +iscsiAuthClientCheckChapAlgorithmOption(int chapAlgorithm) +{ + if (chapAlgorithm == iscsiAuthOptionNone || + chapAlgorithm == iscsiAuthChapAlgorithmMd5) { + return (FALSE); + } + + return (TRUE); +} + + +static int +iscsiAuthClientDataToHex(unsigned char *data, unsigned int dataLength, + char *text, unsigned int textLength) +{ + unsigned long n; + + if (!text || textLength == 0) { + return (TRUE); + } + + if (!data || dataLength == 0) { + *text = '\0'; + return (TRUE); + } + + if (textLength < 3) { + *text = '\0'; + return (TRUE); + } + + *text++ = '0'; + *text++ = 'x'; + + textLength -= 2; + + while (dataLength > 0) { + + if (textLength < 3) { + *text = '\0'; + return (TRUE); + } + + n = *data++; + dataLength--; + + *text++ = iscsiAuthClientHexString[(n >> 4) & 0xf]; + *text++ = iscsiAuthClientHexString[n & 0xf]; + + textLength -= 2; + } + + *text = '\0'; + + return (FALSE); +} + + +static int +iscsiAuthClientDataToBase64(unsigned char *data, unsigned int dataLength, + char *text, unsigned int textLength) +{ + unsigned long n; + + if (!text || textLength == 0) { + return (TRUE); + } + + if (!data || dataLength == 0) { + *text = '\0'; + return (TRUE); + } + + if (textLength < 3) { + *text = '\0'; + return (TRUE); + } + + *text++ = '0'; + *text++ = 'b'; + + textLength -= 2; + + while (dataLength >= 3) { + + if (textLength < 5) { + *text = '\0'; + return (TRUE); + } + + n = *data++; + n = (n << 8) | *data++; + n = (n << 8) | *data++; + dataLength -= 3; + + *text++ = iscsiAuthClientBase64String[(n >> 18) & 0x3f]; + *text++ = iscsiAuthClientBase64String[(n >> 12) & 0x3f]; + *text++ = iscsiAuthClientBase64String[(n >> 6) & 0x3f]; + *text++ = iscsiAuthClientBase64String[n & 0x3f]; + + textLength -= 4; + } + + if (dataLength == 1) { + + if (textLength < 5) { + *text = '\0'; + return (TRUE); + } + + n = *data++; + n = n << 4; + + *text++ = iscsiAuthClientBase64String[(n >> 6) & 0x3f]; + *text++ = iscsiAuthClientBase64String[n & 0x3f]; + *text++ = '='; + *text++ = '='; + + } else if (dataLength == 2) { + + if (textLength < 5) { + return (TRUE); + } + + n = *data++; + n = (n << 8) | *data++; + n = n << 2; + + *text++ = iscsiAuthClientBase64String[(n >> 12) & 0x3f]; + *text++ = iscsiAuthClientBase64String[(n >> 6) & 0x3f]; + *text++ = iscsiAuthClientBase64String[n & 0x3f]; + *text++ = '='; + } + + *text = '\0'; + + return (FALSE); +} + + +static int +iscsiAuthClientDataToText(int base64, unsigned char *data, + unsigned int dataLength, char *text, unsigned int textLength) +{ + int status; + + if (base64) { + status = iscsiAuthClientDataToBase64( + data, dataLength, text, textLength); + } else { + status = iscsiAuthClientDataToHex( + data, dataLength, text, textLength); + } + + return (status); +} + + +static int +iscsiAuthClientHexToData(const char *text, unsigned int textLength, + unsigned char *data, unsigned int *pDataLength) +{ + int i; + unsigned int n1; + unsigned int n2; + unsigned int dataLength = *pDataLength; + + if ((textLength % 2) == 1) { + i = iscsiAuthClientStringIndex(iscsiAuthClientHexString, + *text++); + if (i < 0) { + return (TRUE); /* error, bad character */ + } + + if (i > 15) + i -= 6; + n2 = i; + + if (dataLength < 1) { + return (TRUE); /* error, too much data */ + } + + *data++ = n2; + dataLength--; + } + + while (*text != '\0') { + + i = iscsiAuthClientStringIndex( + iscsiAuthClientHexString, *text++); + if (i < 0) { + return (TRUE); /* error, bad character */ + } + + if (i > 15) + i -= 6; + n1 = i; + + if (*text == '\0') { + return (TRUE); /* error, odd string length */ + } + + i = iscsiAuthClientStringIndex( + iscsiAuthClientHexString, *text++); + if (i < 0) { + return (TRUE); /* error, bad character */ + } + + if (i > 15) + i -= 6; + n2 = i; + + if (dataLength < 1) { + return (TRUE); /* error, too much data */ + } + + *data++ = (n1 << 4) | n2; + dataLength--; + } + + if (dataLength >= *pDataLength) { + return (TRUE); /* error, no data */ + } + + *pDataLength = *pDataLength - dataLength; + + return (FALSE); /* no error */ +} + + +static int +iscsiAuthClientBase64ToData(const char *text, unsigned int textLength, + unsigned char *data, unsigned int *pDataLength) +{ + int i; + unsigned int n; + unsigned int count; + unsigned int dataLength = *pDataLength; + + textLength = textLength; /* not used */ + + n = 0; + count = 0; + + while (*text != '\0' && *text != '=') { + + i = iscsiAuthClientStringIndex( + iscsiAuthClientBase64String, *text++); + if (i < 0) { + return (TRUE); /* error, bad character */ + } + + n = (n << 6 | (unsigned int)i); + count++; + + if (count >= 4) { + if (dataLength < 3) { + return (TRUE); /* error, too much data */ + } + *data++ = n >> 16; + *data++ = n >> 8; + *data++ = n; + dataLength -= 3; + n = 0; + count = 0; + } + } + + while (*text != '\0') { + if (*text++ != '=') { + return (TRUE); /* error, bad pad */ + } + } + + if (count == 0) { + /* + * do nothing + */ + /* EMPTY */ + } else if (count == 2) { + if (dataLength < 1) { + return (TRUE); /* error, too much data */ + } + n = n >> 4; + *data++ = n; + dataLength--; + } else if (count == 3) { + if (dataLength < 2) { + return (TRUE); /* error, too much data */ + } + n = n >> 2; + *data++ = n >> 8; + *data++ = n; + dataLength -= 2; + } else { + return (TRUE); /* bad encoding */ + } + + if (dataLength >= *pDataLength) { + return (TRUE); /* error, no data */ + } + + *pDataLength = *pDataLength - dataLength; + + return (FALSE); /* no error */ +} + + +static int +iscsiAuthClientTextToData(const char *text, unsigned char *data, + unsigned int *dataLength) +{ + int status; + unsigned int textLength; + + status = iscsiAuthClientCheckString(text, + 2 + 2 * iscsiAuthLargeBinaryMaxLength + 1, &textLength); + + if (status) { + return (status); + } + + if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) { + /* + * skip prefix + */ + text += 2; + textLength -= 2; + status = iscsiAuthClientHexToData(text, + textLength, data, dataLength); + } else if (text[0] == '0' && (text[1] == 'b' || text[1] == 'B')) { + /* + * skip prefix + */ + text += 2; + textLength -= 2; + status = iscsiAuthClientBase64ToData(text, + textLength, data, dataLength); + } else { + status = TRUE; /* prefix not recognized. */ + } + + return (status); +} + + +static IscsiAuthDebugStatus +iscsiAuthClientChapComputeResponse(IscsiAuthClient * client, + int remoteAuthentication, unsigned int id, + unsigned char *challengeData, unsigned int challengeLength, + unsigned char *responseData) +{ + unsigned char idData[1]; + IscsiAuthMd5Context context; + unsigned char outData[iscsiAuthStringMaxLength]; + unsigned int outLength = iscsiAuthStringMaxLength; + + if (!client->passwordPresent) { + return (iscsiAuthDebugStatusLocalPasswordNotSet); + } + + iscsiAuthMd5Init(&context); + + /* + * id byte + */ + idData[0] = id; + iscsiAuthMd5Update(&context, idData, 1); + + /* + * decrypt password + */ + if (iscsiAuthClientData(outData, &outLength, + client->passwordData, client->passwordLength)) { + + return (iscsiAuthDebugStatusPasswordDecryptFailed); + } + + if (!remoteAuthentication && !client->ipSec && outLength < 12) { + return (iscsiAuthDebugStatusPasswordTooShortWithNoIpSec); + } + + /* + * shared secret + */ + iscsiAuthMd5Update(&context, outData, outLength); + + /* + * clear decrypted password + */ + bzero(outData, iscsiAuthStringMaxLength); + + /* + * challenge value + */ + iscsiAuthMd5Update(&context, challengeData, challengeLength); + + iscsiAuthMd5Final(responseData, &context); + + return (iscsiAuthDebugStatusNotSet); /* no error */ +} + + +static void +iscsiAuthClientInitKeyBlock(IscsiAuthKeyBlock * keyBlock) +{ + char *stringBlock = keyBlock->stringBlock; + + bzero(keyBlock, sizeof (*keyBlock)); + keyBlock->stringBlock = stringBlock; +} + + +static void +iscsiAuthClientSetKeyValue(IscsiAuthKeyBlock * keyBlock, + int keyType, const char *keyValue) +{ + unsigned int length; + char *string; + + if (keyBlock->key[keyType].valueSet) { + keyBlock->duplicateSet = TRUE; + return; + } + + keyBlock->key[keyType].valueSet = TRUE; + + if (!keyValue) { + return; + } + + if (iscsiAuthClientCheckString(keyValue, + iscsiAuthStringMaxLength, &length)) { + keyBlock->stringTooLong = TRUE; + return; + } + + length += 1; + + if ((keyBlock->blockLength + length) > iscsiAuthStringBlockMaxLength) { + keyBlock->tooMuchData = TRUE; + return; + } + + string = &keyBlock->stringBlock[keyBlock->blockLength]; + + if (iscsiAuthClientStringCopy(string, keyValue, length)) { + keyBlock->tooMuchData = TRUE; + return; + } + keyBlock->blockLength += length; + + keyBlock->key[keyType].string = string; + keyBlock->key[keyType].present = TRUE; +} + + +static const char * +iscsiAuthClientGetKeyValue(IscsiAuthKeyBlock * keyBlock, int keyType) +{ + keyBlock->key[keyType].processed = TRUE; + + if (!keyBlock->key[keyType].present) { + return (0); + } + + return (keyBlock->key[keyType].string); +} + + +static void +iscsiAuthClientCheckKey(IscsiAuthClient * client, + int keyType, + int *negotiatedOption, + unsigned int optionCount, + int *optionList, const char *(*valueToText) (IscsiAuthClient *, int)) +{ + const char *keyValue; + int length; + unsigned int i; + + keyValue = iscsiAuthClientGetKeyValue(&client->recvKeyBlock, keyType); + if (!keyValue) { + *negotiatedOption = iscsiAuthOptionNotPresent; + return; + } + + while (*keyValue != '\0') { + + length = 0; + + while (*keyValue != '\0' && *keyValue != ',') { + client->scratchKeyValue[length++] = *keyValue++; + } + + if (*keyValue == ',') + keyValue++; + client->scratchKeyValue[length++] = '\0'; + + for (i = 0; i < optionCount; i++) { + const char *s = (*valueToText) (client, optionList[i]); + + if (!s) + continue; + + if (strcmp(client->scratchKeyValue, s) == 0) { + *negotiatedOption = optionList[i]; + return; + } + } + } + + *negotiatedOption = iscsiAuthOptionReject; +} + + +static void +iscsiAuthClientSetKey(IscsiAuthClient * client, + int keyType, + unsigned int optionCount, + int *optionList, const char *(*valueToText) (IscsiAuthClient *, int)) +{ + unsigned int i; + + if (optionCount == 0) { + /* + * No valid options to send, but we always want to + * send something. + */ + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, keyType, + client->noneOptionName); + return; + } + + if (optionCount == 1 && optionList[0] == iscsiAuthOptionNotPresent) { + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, keyType, 0); + return; + } + + for (i = 0; i < optionCount; i++) { + const char *s = (*valueToText) (client, optionList[i]); + + if (!s) + continue; + + if (i == 0) { + (void) iscsiAuthClientStringCopy(client->scratchKeyValue, + s, iscsiAuthStringMaxLength); + } else { + (void) iscsiAuthClientStringAppend(client->scratchKeyValue, + ",", iscsiAuthStringMaxLength); + (void) iscsiAuthClientStringAppend(client->scratchKeyValue, + s, iscsiAuthStringMaxLength); + } + } + + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + keyType, client->scratchKeyValue); +} + + +static void +iscsiAuthClientCheckAuthMethodKey(IscsiAuthClient * client) +{ + iscsiAuthClientCheckKey(client, + iscsiAuthKeyTypeAuthMethod, + &client->negotiatedAuthMethod, + client->authMethodValidCount, + client->authMethodValidList, iscsiAuthClientAuthMethodOptionToText); +} + + +static void +iscsiAuthClientSetAuthMethodKey(IscsiAuthClient * client, + unsigned int authMethodCount, int *authMethodList) +{ + iscsiAuthClientSetKey(client, iscsiAuthKeyTypeAuthMethod, + authMethodCount, authMethodList, + iscsiAuthClientAuthMethodOptionToText); +} + + +static void +iscsiAuthClientCheckChapAlgorithmKey(IscsiAuthClient * client) +{ + const char *keyValue; + int length; + unsigned long number; + unsigned int i; + + keyValue = iscsiAuthClientGetKeyValue(&client->recvKeyBlock, + iscsiAuthKeyTypeChapAlgorithm); + if (!keyValue) { + client->negotiatedChapAlgorithm = iscsiAuthOptionNotPresent; + return; + } + + while (*keyValue != '\0') { + length = 0; + + while (*keyValue != '\0' && *keyValue != ',') { + client->scratchKeyValue[length++] = *keyValue++; + } + + if (*keyValue == ',') + keyValue++; + client->scratchKeyValue[length++] = '\0'; + + if (iscsiAuthClientTextToNumber(client->scratchKeyValue, + &number)) { + continue; + } + + for (i = 0; i < client->chapAlgorithmCount; i++) { + + if (number == (unsigned long)client-> + chapAlgorithmList[i]) { + client->negotiatedChapAlgorithm = number; + return; + } + } + } + + client->negotiatedChapAlgorithm = iscsiAuthOptionReject; +} + + +static void +iscsiAuthClientSetChapAlgorithmKey(IscsiAuthClient * client, + unsigned int chapAlgorithmCount, int *chapAlgorithmList) +{ + unsigned int i; + + if (chapAlgorithmCount == 0) { + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapAlgorithm, 0); + return; + } + + if (chapAlgorithmCount == 1 && + chapAlgorithmList[0] == iscsiAuthOptionNotPresent) { + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapAlgorithm, 0); + return; + } + + if (chapAlgorithmCount == 1 && + chapAlgorithmList[0] == iscsiAuthOptionReject) { + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapAlgorithm, client->rejectOptionName); + return; + } + + for (i = 0; i < chapAlgorithmCount; i++) { + char s[20]; + + iscsiAuthClientNumberToText(chapAlgorithmList[i], + s, sizeof (s)); + + if (i == 0) { + (void) iscsiAuthClientStringCopy(client->scratchKeyValue, s, + iscsiAuthStringMaxLength); + } else { + (void) iscsiAuthClientStringAppend(client->scratchKeyValue, + ",", iscsiAuthStringMaxLength); + (void) iscsiAuthClientStringAppend(client->scratchKeyValue, + s, iscsiAuthStringMaxLength); + } + } + + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapAlgorithm, client->scratchKeyValue); +} + + +static void +iscsiAuthClientNextPhase(IscsiAuthClient * client) +{ + switch (client->phase) { + case iscsiAuthPhaseConfigure: + client->phase = iscsiAuthPhaseNegotiate; + break; + + case iscsiAuthPhaseNegotiate: + client->phase = iscsiAuthPhaseAuthenticate; + + if (client->negotiatedAuthMethod == + iscsiAuthOptionReject || + client->negotiatedAuthMethod == + iscsiAuthOptionNotPresent || + client->negotiatedAuthMethod == iscsiAuthOptionNone) { + + client->localState = iscsiAuthLocalStateDone; + client->remoteState = iscsiAuthRemoteStateDone; + + if (client->authRemote) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + } else { + client->remoteAuthStatus = iscsiAuthStatusPass; + } + + switch (client->negotiatedAuthMethod) { + case iscsiAuthOptionReject: + client->debugStatus = + iscsiAuthDebugStatusAuthMethodReject; + break; + + case iscsiAuthOptionNotPresent: + client->debugStatus = + iscsiAuthDebugStatusAuthMethodNotPresent; + break; + + case iscsiAuthOptionNone: + client->debugStatus = + iscsiAuthDebugStatusAuthMethodNone; + } + + } else if (client->negotiatedAuthMethod == + iscsiAuthMethodChap) { + client->localState = iscsiAuthLocalStateSendAlgorithm; + client->remoteState = iscsiAuthRemoteStateSendAlgorithm; + } else { + client->localState = iscsiAuthLocalStateDone; + client->remoteState = iscsiAuthRemoteStateDone; + client->remoteAuthStatus = iscsiAuthStatusFail; + client->debugStatus = iscsiAuthDebugStatusAuthMethodBad; + } + + break; + + case iscsiAuthPhaseAuthenticate: + client->phase = iscsiAuthPhaseDone; + break; + + case iscsiAuthPhaseDone: + case iscsiAuthPhaseError: + default: + client->phase = iscsiAuthPhaseError; + } +} + + +static void +iscsiAuthClientLocalAuthentication(IscsiAuthClient * client) +{ + unsigned int chapIdentifier; + unsigned char responseData[iscsiAuthChapResponseLength]; + unsigned long number; + int status; + IscsiAuthDebugStatus debugStatus; + const char *chapIdentifierKeyValue; + const char *chapChallengeKeyValue; + + switch (client->localState) { + case iscsiAuthLocalStateSendAlgorithm: + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + iscsiAuthClientSetChapAlgorithmKey( + client, client->chapAlgorithmCount, + client->chapAlgorithmList); + client->localState = iscsiAuthLocalStateRecvAlgorithm; + break; + } + + /* FALLTHRU */ + + case iscsiAuthLocalStateRecvAlgorithm: + iscsiAuthClientCheckChapAlgorithmKey(client); + + if (client->nodeType == iscsiAuthNodeTypeTarget) { + + iscsiAuthClientSetChapAlgorithmKey(client, 1, + &client->negotiatedChapAlgorithm); + } + + /* + * Make sure only supported CHAP algorithm is used. + */ + if (client->negotiatedChapAlgorithm == + iscsiAuthOptionNotPresent) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapAlgorithmExpected; + break; + + } else if (client->negotiatedChapAlgorithm == + iscsiAuthOptionReject) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapAlgorithmReject; + break; + + } else if (client->negotiatedChapAlgorithm != + iscsiAuthChapAlgorithmMd5) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapAlgorithmBad; + break; + } + + if (client->nodeType == iscsiAuthNodeTypeTarget) { + + client->localState = iscsiAuthLocalStateRecvChallenge; + break; + } + + /* FALLTHRU */ + + case iscsiAuthLocalStateRecvChallenge: + chapIdentifierKeyValue = iscsiAuthClientGetKeyValue( + &client->recvKeyBlock, iscsiAuthKeyTypeChapIdentifier); + chapChallengeKeyValue = iscsiAuthClientGetKeyValue( + &client->recvKeyBlock, iscsiAuthKeyTypeChapChallenge); + + if (client->nodeType == iscsiAuthNodeTypeTarget) { + if (!chapIdentifierKeyValue && !chapChallengeKeyValue) { + client->localState = iscsiAuthLocalStateDone; + break; + } + } + + if (!chapIdentifierKeyValue) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapIdentifierExpected; + break; + } + + if (!chapChallengeKeyValue) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapChallengeExpected; + break; + } + + status = iscsiAuthClientTextToNumber( + chapIdentifierKeyValue, &number); + + if (status || (255 < number)) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapIdentifierBad; + break; + } + chapIdentifier = number; + + if (client->recvChapChallengeStatus) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapChallengeBad; + break; + } + + if (client->nodeType == iscsiAuthNodeTypeTarget && + client->recvChapChallenge.length == + client->sendChapChallenge.length && + bcmp(client->recvChapChallenge.largeBinary, + client->sendChapChallenge.largeBinary, + client->sendChapChallenge.length) == 0) { + + client->localState = iscsiAuthLocalStateError; + client->debugStatus = + iscsiAuthDebugStatusChapChallengeReflected; + break; + } + + debugStatus = iscsiAuthClientChapComputeResponse(client, + FALSE, + chapIdentifier, + client->recvChapChallenge.largeBinary, + client->recvChapChallenge.length, responseData); + + if (debugStatus != iscsiAuthDebugStatusNotSet) { + client->localState = iscsiAuthLocalStateError; + client->debugStatus = debugStatus; + break; + } + + (void) iscsiAuthClientDataToText(client->base64, + responseData, iscsiAuthChapResponseLength, + client->scratchKeyValue, iscsiAuthStringMaxLength); + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapResponse, client->scratchKeyValue); + + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapUsername, client->username); + + client->localState = iscsiAuthLocalStateDone; + break; + + case iscsiAuthLocalStateDone: + break; + + case iscsiAuthLocalStateError: + default: + client->phase = iscsiAuthPhaseError; + } +} + + +static void +iscsiAuthClientRemoteAuthentication(IscsiAuthClient * client) +{ + unsigned char idData[1]; + unsigned char responseData[iscsiAuthStringMaxLength]; + unsigned int responseLength = iscsiAuthStringMaxLength; + unsigned char myResponseData[iscsiAuthChapResponseLength]; + int status; + IscsiAuthDebugStatus debugStatus; + const char *chapResponseKeyValue; + const char *chapUsernameKeyValue; + + switch (client->remoteState) { + case iscsiAuthRemoteStateSendAlgorithm: + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + client->remoteState = iscsiAuthRemoteStateSendChallenge; + break; + } + + /* FALLTHRU */ + + case iscsiAuthRemoteStateSendChallenge: + if (!client->authRemote) { + client->remoteAuthStatus = iscsiAuthStatusPass; + client->debugStatus = + iscsiAuthDebugStatusAuthRemoteFalse; + client->remoteState = iscsiAuthRemoteStateDone; + break; + } + + iscsiAuthRandomSetData(idData, 1); + client->sendChapIdentifier = idData[0]; + + iscsiAuthClientNumberToText(client->sendChapIdentifier, + client->scratchKeyValue, iscsiAuthStringMaxLength); + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapIdentifier, client->scratchKeyValue); + + client->sendChapChallenge.length = client->chapChallengeLength; + iscsiAuthRandomSetData(client->sendChapChallenge.largeBinary, + client->sendChapChallenge.length); + + iscsiAuthClientSetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeChapChallenge, ""); + + client->remoteState = iscsiAuthRemoteStateRecvResponse; + break; + + case iscsiAuthRemoteStateRecvResponse: + chapResponseKeyValue = iscsiAuthClientGetKeyValue( + &client->recvKeyBlock, iscsiAuthKeyTypeChapResponse); + + chapUsernameKeyValue = iscsiAuthClientGetKeyValue( + &client->recvKeyBlock, iscsiAuthKeyTypeChapUsername); + + if (!chapResponseKeyValue) { + client->remoteState = iscsiAuthRemoteStateError; + client->debugStatus = + iscsiAuthDebugStatusChapResponseExpected; + break; + } + + if (!chapUsernameKeyValue) { + client->remoteState = iscsiAuthRemoteStateError; + client->debugStatus = + iscsiAuthDebugStatusChapUsernameExpected; + break; + } + + status = iscsiAuthClientTextToData(chapResponseKeyValue, + responseData, &responseLength); + + if (status) { + client->remoteState = iscsiAuthRemoteStateError; + client->debugStatus = + iscsiAuthDebugStatusChapResponseBad; + break; + } + + if (responseLength == iscsiAuthChapResponseLength) { + debugStatus = iscsiAuthClientChapComputeResponse( + client, TRUE, client->sendChapIdentifier, + client->sendChapChallenge.largeBinary, + client->sendChapChallenge.length, myResponseData); + + /* + * Check if the same CHAP secret is being used for + * authentication in both directions. + */ + if (debugStatus == iscsiAuthDebugStatusNotSet && + bcmp(myResponseData, responseData, + iscsiAuthChapResponseLength) == 0) { + + client->remoteState = + iscsiAuthRemoteStateError; + client->debugStatus = + iscsiAuthDebugStatusPasswordIdentical; + break; + } + } + + (void) iscsiAuthClientStringCopy(client->chapUsername, + chapUsernameKeyValue, iscsiAuthStringMaxLength); + + /* To verify the target's response. */ + status = iscsiAuthClientChapAuthRequest( + client, client->chapUsername, client->sendChapIdentifier, + client->sendChapChallenge.largeBinary, + client->sendChapChallenge.length, responseData, + responseLength); + + if (status == iscsiAuthStatusInProgress) { + iscsiAuthClientGlobalStats.requestSent++; + client->remoteState = iscsiAuthRemoteStateAuthRequest; + break; + } + + client->remoteAuthStatus = (IscsiAuthStatus) status; + client->authResponseFlag = TRUE; + + /* FALLTHRU */ + + case iscsiAuthRemoteStateAuthRequest: + /* + * client->remoteAuthStatus already set + */ + if (client->authServerErrorFlag) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->debugStatus = + iscsiAuthDebugStatusAuthServerError; + } else if (client->remoteAuthStatus == iscsiAuthStatusPass) { + client->debugStatus = iscsiAuthDebugStatusAuthPass; + } else if (client->remoteAuthStatus == iscsiAuthStatusFail) { + client->debugStatus = iscsiAuthDebugStatusAuthFail; + } else { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->debugStatus = iscsiAuthDebugStatusAuthStatusBad; + } + client->remoteState = iscsiAuthRemoteStateDone; + + /* FALLTHRU */ + + case iscsiAuthRemoteStateDone: + break; + + case iscsiAuthRemoteStateError: + default: + client->phase = iscsiAuthPhaseError; + } +} + + +static void +iscsiAuthClientHandshake(IscsiAuthClient * client) +{ + if (client->phase == iscsiAuthPhaseDone) { + /* + * Should only happen if authentication + * protocol error occured. + */ + return; + } + + if (client->remoteState == iscsiAuthRemoteStateAuthRequest) { + /* + * Defer until authentication response received + * from internal authentication service. + */ + return; + } + + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + + /* + * Target should only have set T bit on response if + * initiator set it on previous message. + */ + if (client->recvKeyBlock.transitBit && + client->transitBitSentFlag == 0) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusTbitSetIllegal; + return; + } + } + + if (client->phase == iscsiAuthPhaseNegotiate) { + /* + * Should only happen if waiting for peer + * to send AuthMethod key or set Transit Bit. + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + client->sendKeyBlock.transitBit = TRUE; + } + return; + } + + if (client->remoteState == iscsiAuthRemoteStateRecvResponse || + client->remoteState == iscsiAuthRemoteStateDone) { + + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + if (client->recvKeyBlock.transitBit) { + if (client->remoteState != + iscsiAuthRemoteStateDone) { + goto recvTransitBitError; + } + iscsiAuthClientNextPhase(client); + } else { + client->sendKeyBlock.transitBit = TRUE; + } + } else { + if (client->remoteState == iscsiAuthRemoteStateDone && + client->remoteAuthStatus != iscsiAuthStatusPass) { + + /* + * Authentication failed, don't + * do T bit handshake. + */ + iscsiAuthClientNextPhase(client); + } else { + + /* + * Target can only set T bit on response if + * initiator set it on current message. + */ + if (client->recvKeyBlock.transitBit) { + client->sendKeyBlock.transitBit = TRUE; + iscsiAuthClientNextPhase(client); + } + } + } + } else { + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + if (client->recvKeyBlock.transitBit) { + goto recvTransitBitError; + } + } + } + + return; + +recvTransitBitError: + /* + * Target set T bit on response but + * initiator was not done with authentication. + */ + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = iscsiAuthDebugStatusTbitSetPremature; +} + + +static int +iscsiAuthClientRecvEndStatus(IscsiAuthClient * client) +{ + int authStatus; + int keyType; + + if (client->phase == iscsiAuthPhaseError) { + return (iscsiAuthStatusError); + } + + if (client->phase == iscsiAuthPhaseDone) { + + /* + * Perform sanity check against configured parameters. + */ + + if (client->authRemote && !client->authResponseFlag && + client->remoteAuthStatus == iscsiAuthStatusPass) { + + client->remoteAuthStatus = iscsiAuthStatusFail; + client->debugStatus = + iscsiAuthDebugStatusAuthPassNotValid; + } + + authStatus = client->remoteAuthStatus; + } else if (client->remoteState == iscsiAuthRemoteStateAuthRequest) { + authStatus = iscsiAuthStatusInProgress; + } else { + authStatus = iscsiAuthStatusContinue; + } + + if (authStatus != iscsiAuthStatusInProgress) { + client->recvInProgressFlag = FALSE; + } + + if (authStatus == iscsiAuthStatusContinue || + authStatus == iscsiAuthStatusPass) { + if (client->sendKeyBlock.duplicateSet) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusSendDuplicateSetKeyValue; + authStatus = iscsiAuthStatusFail; + } else if (client->sendKeyBlock.stringTooLong) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusSendStringTooLong; + authStatus = iscsiAuthStatusFail; + } else if (client->sendKeyBlock.tooMuchData) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusSendTooMuchData; + authStatus = iscsiAuthStatusFail; + } else { + /* + * Check that all incoming keys have been processed. + */ + for (keyType = iscsiAuthKeyTypeFirst; + keyType < iscsiAuthKeyTypeMaxCount; keyType++) { + if (client->recvKeyBlock.key[keyType].present && + client->recvKeyBlock.key[keyType]. + processed == 0) { + break; + } + } + + if (keyType < iscsiAuthKeyTypeMaxCount) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusUnexpectedKeyPresent; + authStatus = iscsiAuthStatusFail; + } + } + } + + if (authStatus != iscsiAuthStatusPass && + authStatus != iscsiAuthStatusContinue && + authStatus != iscsiAuthStatusInProgress) { + int authMethodKeyPresent = FALSE; + int chapAlgorithmKeyPresent = FALSE; + + /* + * Suppress send keys on error, except + * for AuthMethod and CHAP_A. + */ + if (client->nodeType == iscsiAuthNodeTypeTarget) { + if (iscsiAuthClientGetKeyValue(&client->sendKeyBlock, + iscsiAuthKeyTypeAuthMethod)) { + authMethodKeyPresent = TRUE; + } else if (iscsiAuthClientGetKeyValue( + &client->sendKeyBlock, + iscsiAuthKeyTypeChapAlgorithm)) { + chapAlgorithmKeyPresent = TRUE; + } + } + + iscsiAuthClientInitKeyBlock(&client->sendKeyBlock); + + if (client->nodeType == iscsiAuthNodeTypeTarget) { + if (authMethodKeyPresent && + client->negotiatedAuthMethod == + iscsiAuthOptionReject) { + iscsiAuthClientSetKeyValue( + &client->sendKeyBlock, + iscsiAuthKeyTypeAuthMethod, + client->rejectOptionName); + } else if (chapAlgorithmKeyPresent && + client->negotiatedChapAlgorithm == + iscsiAuthOptionReject) { + iscsiAuthClientSetKeyValue( + &client->sendKeyBlock, + iscsiAuthKeyTypeChapAlgorithm, + client->rejectOptionName); + } + } + } + + return (authStatus); +} + + +int +iscsiAuthClientRecvBegin(IscsiAuthClient * client) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase == iscsiAuthPhaseError) { + return (iscsiAuthStatusError); + } + + if (client->phase == iscsiAuthPhaseDone) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (client->recvInProgressFlag) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->recvInProgressFlag = TRUE; + + if (client->phase == iscsiAuthPhaseConfigure) { + iscsiAuthClientNextPhase(client); + } + + client->transitBitSentFlag = client->sendKeyBlock.transitBit; + + iscsiAuthClientInitKeyBlock(&client->recvKeyBlock); + iscsiAuthClientInitKeyBlock(&client->sendKeyBlock); + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientRecvEnd(IscsiAuthClient * client, + IscsiAuthClientCallback * callback, void *userHandle, void *messageHandle) +{ + int nextPhaseFlag = FALSE; + + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase == iscsiAuthPhaseError) { + return (iscsiAuthStatusError); + } + + if (!callback || !client->recvInProgressFlag) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (client->recvEndCount > iscsiAuthRecvEndMaxCount) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusRecvMessageCountLimit; + } else if (client->recvKeyBlock.duplicateSet) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusRecvDuplicateSetKeyValue; + } else if (client->recvKeyBlock.stringTooLong) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = iscsiAuthDebugStatusRecvStringTooLong; + } else if (client->recvKeyBlock.tooMuchData) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = iscsiAuthDebugStatusRecvTooMuchData; + } + + client->recvEndCount++; + + client->callback = callback; + client->userHandle = userHandle; + client->messageHandle = messageHandle; + + switch (client->phase) { + case iscsiAuthPhaseNegotiate: + iscsiAuthClientCheckAuthMethodKey(client); + + if (client->authMethodValidNegRole == + iscsiAuthNegRoleResponder) { + if (client->negotiatedAuthMethod == + iscsiAuthOptionNotPresent) { + if (client->authRemote || + client->recvKeyBlock.transitBit == 0) { + /* + * No AuthMethod key from peer + * on first message, try moving + * the process along by sending + * the AuthMethod key. + */ + + client->authMethodValidNegRole = + iscsiAuthNegRoleOriginator; + + iscsiAuthClientSetAuthMethodKey(client, + client->authMethodValidCount, + client->authMethodValidList); + break; + } + + /* + * Special case if peer sent no + * AuthMethod key, but did set Transit + * Bit, allowing this side to do a + * null authentication, and compelete + * the iSCSI security phase without + * either side sending the AuthMethod + * key. + */ + } else { + /* + * Send response to AuthMethod key. + */ + + iscsiAuthClientSetAuthMethodKey(client, 1, + &client->negotiatedAuthMethod); + } + + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + iscsiAuthClientNextPhase(client); + } else { + nextPhaseFlag = TRUE; + } + + } else { + if (client->negotiatedAuthMethod == + iscsiAuthOptionNotPresent) { + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + client->debugStatus = + iscsiAuthDebugStatusAuthMethodExpected; + break; + } + + iscsiAuthClientNextPhase(client); + } + break; + + case iscsiAuthPhaseAuthenticate: + case iscsiAuthPhaseDone: + break; + + default: + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + switch (client->phase) { + case iscsiAuthPhaseNegotiate: + if (nextPhaseFlag) { + iscsiAuthClientNextPhase(client); + } + break; + + case iscsiAuthPhaseAuthenticate: + /* + * Must call iscsiAuthClientLocalAuthentication() + * before iscsiAuthClientRemoteAuthentication() + * to insure processing of the CHAP algorithm key, + * and to avoid leaving an in progress request to the + * authentication service. + */ + iscsiAuthClientLocalAuthentication(client); + + if (client->localState != iscsiAuthLocalStateError) { + iscsiAuthClientRemoteAuthentication(client); + } + + if (client->localState == iscsiAuthLocalStateError || + client->remoteState == iscsiAuthRemoteStateError) { + + client->remoteAuthStatus = iscsiAuthStatusFail; + client->phase = iscsiAuthPhaseDone; + /* + * client->debugStatus should already be set. + */ + } + break; + + case iscsiAuthPhaseDone: + break; + + default: + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + iscsiAuthClientHandshake(client); + + return (iscsiAuthClientRecvEndStatus(client)); +} + + +#ifdef notused +void +iscsiAuthClientAuthResponse(IscsiAuthClient * client, int authStatus) +{ + iscsiAuthClientGlobalStats.responseReceived++; + + if (!client || client->signature != iscsiAuthClientSignature) { + return; + } + + if (!client->recvInProgressFlag || + client->phase != iscsiAuthPhaseAuthenticate || + client->remoteState != iscsiAuthRemoteStateAuthRequest) { + + client->phase = iscsiAuthPhaseError; + return; + } + + client->remoteAuthStatus = (IscsiAuthStatus) authStatus; + client->authResponseFlag = TRUE; + + iscsiAuthClientRemoteAuthentication(client); + + iscsiAuthClientHandshake(client); + + authStatus = iscsiAuthClientRecvEndStatus(client); + + client->callback(client->userHandle, client->messageHandle, authStatus); +} +#endif + + +const char * +iscsiAuthClientGetKeyName(int keyType) +{ + if (keyType < iscsiAuthKeyTypeFirst || keyType > iscsiAuthKeyTypeLast) { + return (0); + } + return (iscsiAuthClientKeyInfo[keyType].name); +} + + +int +iscsiAuthClientGetNextKeyType(int *pKeyType) +{ + int keyType = *pKeyType; + + if (keyType >= iscsiAuthKeyTypeLast) { + return (iscsiAuthStatusError); + } + + if (keyType < iscsiAuthKeyTypeFirst) { + keyType = iscsiAuthKeyTypeFirst; + } else { + keyType++; + } + + *pKeyType = keyType; + + return (iscsiAuthStatusNoError); +} + + +#ifdef notused +int +iscsiAuthClientKeyNameToKeyType(const char *keyName) +{ + int keyType = iscsiAuthKeyTypeNone; + + while (iscsiAuthClientGetNextKeyType(&keyType) == + iscsiAuthStatusNoError) { + const char *keyName2 = iscsiAuthClientGetKeyName(keyType); + + if (!keyName2) { + return (iscsiAuthKeyTypeNone); + } + + if (strcmp(keyName, keyName2) == 0) { + return (keyType); + } + } + + return (iscsiAuthKeyTypeNone); +} +#endif + + +int +iscsiAuthClientRecvKeyValue(IscsiAuthClient * client, int keyType, + const char *userKeyValue) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseNegotiate && + client->phase != iscsiAuthPhaseAuthenticate) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (keyType < iscsiAuthKeyTypeFirst || keyType > iscsiAuthKeyTypeLast) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (keyType == iscsiAuthKeyTypeChapChallenge) { + client->recvChapChallenge.length = + iscsiAuthLargeBinaryMaxLength; + client->recvChapChallengeStatus = + iscsiAuthClientTextToData(userKeyValue, + client->recvChapChallenge.largeBinary, + &client->recvChapChallenge.length); + userKeyValue = ""; + } + + iscsiAuthClientSetKeyValue(&client->recvKeyBlock, + keyType, userKeyValue); + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientSendKeyValue(IscsiAuthClient * client, int keyType, + int *keyPresent, char *userKeyValue, unsigned int maxLength) +{ + const char *keyValue; + + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure && + client->phase != iscsiAuthPhaseNegotiate && + client->phase != iscsiAuthPhaseAuthenticate && + client->phase != iscsiAuthPhaseDone) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (keyType < iscsiAuthKeyTypeFirst || keyType > iscsiAuthKeyTypeLast) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + keyValue = iscsiAuthClientGetKeyValue(&client->sendKeyBlock, keyType); + if (keyValue) { + if (keyType == iscsiAuthKeyTypeChapChallenge) { + if (iscsiAuthClientDataToText(client->base64, + client->sendChapChallenge.largeBinary, + client->sendChapChallenge.length, + userKeyValue, maxLength)) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + } else { + if (iscsiAuthClientStringCopy(userKeyValue, + keyValue, maxLength)) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + } + *keyPresent = TRUE; + } else { + *keyPresent = FALSE; + } + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientRecvTransitBit(IscsiAuthClient * client, int value) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseNegotiate && + client->phase != iscsiAuthPhaseAuthenticate) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (value) { + client->recvKeyBlock.transitBit = TRUE; + } else { + client->recvKeyBlock.transitBit = FALSE; + } + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientSendTransitBit(IscsiAuthClient * client, int *value) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure && + client->phase != iscsiAuthPhaseNegotiate && + client->phase != iscsiAuthPhaseAuthenticate && + client->phase != iscsiAuthPhaseDone) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + *value = client->sendKeyBlock.transitBit; + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientInit(int nodeType, int bufferDescCount, + IscsiAuthBufferDesc * bufferDesc) +{ + IscsiAuthClient *client; + IscsiAuthStringBlock *recvStringBlock; + IscsiAuthStringBlock *sendStringBlock; + IscsiAuthLargeBinary *recvChapChallenge; + IscsiAuthLargeBinary *sendChapChallenge; + int valueList[2]; + + if (bufferDescCount != 5 || + bufferDesc == 0) { + return (iscsiAuthStatusError); + } + + if (!bufferDesc[0].address || + bufferDesc[0].length != sizeof (*client)) { + return (iscsiAuthStatusError); + } + client = (IscsiAuthClient *) bufferDesc[0].address; + + if (bufferDesc[1].address == 0 || + bufferDesc[1].length != sizeof (*recvStringBlock)) { + return (iscsiAuthStatusError); + } + recvStringBlock = (IscsiAuthStringBlock *) bufferDesc[1].address; + + if (bufferDesc[2].address == 0 || + bufferDesc[2].length != sizeof (*sendStringBlock)) { + return (iscsiAuthStatusError); + } + sendStringBlock = (IscsiAuthStringBlock *) bufferDesc[2].address; + + if (bufferDesc[3].address == 0 || + bufferDesc[3].length != sizeof (*recvChapChallenge)) { + return (iscsiAuthStatusError); + } + recvChapChallenge = (IscsiAuthLargeBinary *) bufferDesc[3].address; + + if (bufferDesc[4].address == 0 || + bufferDesc[4].length != sizeof (*sendChapChallenge)) { + return (iscsiAuthStatusError); + } + sendChapChallenge = (IscsiAuthLargeBinary *) bufferDesc[4].address; + + bzero(client, sizeof (*client)); + bzero(recvStringBlock, sizeof (*recvStringBlock)); + bzero(sendStringBlock, sizeof (*sendStringBlock)); + bzero(recvChapChallenge, sizeof (*recvChapChallenge)); + bzero(sendChapChallenge, sizeof (*sendChapChallenge)); + + client->recvKeyBlock.stringBlock = recvStringBlock->stringBlock; + client->sendKeyBlock.stringBlock = sendStringBlock->stringBlock; + client->recvChapChallenge.largeBinary = recvChapChallenge->largeBinary; + client->sendChapChallenge.largeBinary = sendChapChallenge->largeBinary; + + if (iscsiAuthClientCheckNodeType(nodeType)) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->signature = iscsiAuthClientSignature; + client->nodeType = (IscsiAuthNodeType) nodeType; + /* Assume bi-directional authentication enabled. */ + client->authRemote = TRUE; + client->passwordPresent = FALSE; + client->version = iscsiAuthVersionRfc; + client->chapChallengeLength = iscsiAuthChapResponseLength; + client->ipSec = TRUE; + client->base64 = FALSE; + + client->phase = iscsiAuthPhaseConfigure; + client->negotiatedAuthMethod = iscsiAuthOptionNotPresent; + client->negotiatedChapAlgorithm = iscsiAuthOptionNotPresent; + + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + client->authMethodNegRole = iscsiAuthNegRoleOriginator; + } else { + /* + * Initial value ignored for Target. + */ + client->authMethodNegRole = iscsiAuthNegRoleResponder; + } + + /* All supported authentication methods */ + valueList[0] = iscsiAuthMethodChap; + valueList[1] = iscsiAuthOptionNone; + + /* + * Must call after setting authRemote, password, + * version and authMethodNegRole + */ + if (iscsiAuthClientSetAuthMethodList(client, 2, valueList) != + iscsiAuthStatusNoError) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + valueList[0] = iscsiAuthChapAlgorithmMd5; + + if (iscsiAuthClientSetChapAlgorithmList(client, 1, valueList) != + iscsiAuthStatusNoError) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + return (iscsiAuthStatusNoError); +} + + +#ifdef notused +int +iscsiAuthClientFinish(IscsiAuthClient * client) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + iscsiAuthClientChapAuthCancel(client); + + bzero(client, sizeof (*client)); + + return (iscsiAuthStatusNoError); +} +#endif + + +static int +iscsiAuthClientSetOptionList(IscsiAuthClient * client, + unsigned int optionCount, + const int *optionList, + unsigned int *clientOptionCount, + int *clientOptionList, + unsigned int optionMaxCount, + int (*checkOption) (int), + int (*checkList) (unsigned int optionCount, const int *optionList)) +{ + unsigned int i; + unsigned int j; + + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure || + optionCount > optionMaxCount) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + for (i = 0; i < optionCount; i++) { + if ((*checkOption) (optionList[i])) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + } + + /* + * Check for duplicate entries. + */ + for (i = 0; i < optionCount; i++) { + for (j = 0; j < optionCount; j++) { + if (j == i) + continue; + if (optionList[i] == optionList[j]) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + } + } + + /* + * Check for key specific constraints. + */ + if (checkList) { + if ((*checkList) (optionCount, optionList)) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + } + + for (i = 0; i < optionCount; i++) { + clientOptionList[i] = optionList[i]; + } + + *clientOptionCount = optionCount; + + return (iscsiAuthStatusNoError); +} + + +static void +iscsiAuthClientSetAuthMethodValid(IscsiAuthClient * client) +{ + static const char rejectOptionNameDraft8[] = "reject"; + static const char rejectOptionNameRfc[] = "Reject"; + static const char noneOptionNameDraft8[] = "none"; + static const char noneOptionNameRfc[] = "None"; + unsigned int i; + unsigned int j = 0; + int option = 0; + + if (client->version == iscsiAuthVersionDraft8) { + client->rejectOptionName = rejectOptionNameDraft8; + client->noneOptionName = noneOptionNameDraft8; + } else { + client->rejectOptionName = rejectOptionNameRfc; + client->noneOptionName = noneOptionNameRfc; + } + + /* + * Following checks may need to be revised if + * authentication options other than CHAP and none + * are supported. + */ + + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + + if (client->authRemote) { + /* + * If initiator doing authentication, + * don't offer authentication option none. + */ + option = 1; + } else if (!client->passwordPresent) { + /* + * If initiator password not set, + * only offer authentication option none. + */ + option = 2; + } + } + + if (client->nodeType == iscsiAuthNodeTypeTarget) { + + if (client->authRemote) { + /* + * If target doing authentication, + * don't accept authentication option none. + */ + option = 1; + } else { + /* + * If target not doing authentication, + * only accept authentication option none. + */ + option = 2; + } + } + + for (i = 0; i < client->authMethodCount; i++) { + + if (option == 1) { + if (client->authMethodList[i] == iscsiAuthOptionNone) { + continue; + } + } else if (option == 2) { + if (client->authMethodList[i] != iscsiAuthOptionNone) { + continue; + } + } + + client->authMethodValidList[j++] = client->authMethodList[i]; + } + + client->authMethodValidCount = j; + + iscsiAuthClientInitKeyBlock(&client->sendKeyBlock); + + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + if (client->authRemote) { + /* + * Initiator wants to authenticate target, + * always send AuthMethod key. + */ + client->sendKeyBlock.transitBit = FALSE; + client->authMethodValidNegRole = + iscsiAuthNegRoleOriginator; + } else { + client->sendKeyBlock.transitBit = TRUE; + client->authMethodValidNegRole = + client->authMethodNegRole; + } + } else { + client->sendKeyBlock.transitBit = FALSE; + client->authMethodValidNegRole = iscsiAuthNegRoleResponder; + } + + if (client->authMethodValidNegRole == iscsiAuthNegRoleOriginator) { + iscsiAuthClientSetAuthMethodKey(client, + client->authMethodValidCount, + client->authMethodValidList); + } else { + int value = iscsiAuthOptionNotPresent; + iscsiAuthClientSetAuthMethodKey(client, 1, &value); + } +} + + +static int +iscsiAuthClientCheckAuthMethodList(unsigned int optionCount, + const int *optionList) +{ + unsigned int i; + + if (!optionList || optionCount < 2) { + return (TRUE); + } + + if (optionList[optionCount - 1] != iscsiAuthOptionNone) { + return (TRUE); + } + + for (i = 0; i < (optionCount - 1); i++) { + if (optionList[i] != iscsiAuthOptionNone) { + return (FALSE); + } + } + + return (FALSE); +} + + +int +iscsiAuthClientSetAuthMethodList(IscsiAuthClient * client, + unsigned int optionCount, const int *optionList) +{ + int status; + + status = iscsiAuthClientSetOptionList( + client, optionCount, optionList, &client->authMethodCount, + client->authMethodList, iscsiAuthMethodMaxCount, + iscsiAuthClientCheckAuthMethodOption, + iscsiAuthClientCheckAuthMethodList); + + if (status != iscsiAuthStatusNoError) { + return (status); + } + + /* + * Setting authMethod affects authMethodValid. + */ + iscsiAuthClientSetAuthMethodValid(client); + + return (iscsiAuthStatusNoError); +} + +#ifdef notused +int +iscsiAuthClientSetAuthMethodNegRole(IscsiAuthClient * client, int negRole) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure || + iscsiAuthClientCheckNegRole(negRole) || + client->nodeType != iscsiAuthNodeTypeInitiator) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->authMethodNegRole = (IscsiAuthNegRole) negRole; + + /* + * Setting negRole affects authMethodValid. + */ + iscsiAuthClientSetAuthMethodValid(client); + + return (iscsiAuthStatusNoError); +} +#endif + + +static int +iscsiAuthClientCheckChapAlgorithmList(unsigned int optionCount, + const int *optionList) +{ + if (!optionList || optionCount < 1) { + return (TRUE); + } + + return (FALSE); +} + + +int +iscsiAuthClientSetChapAlgorithmList(IscsiAuthClient * client, + unsigned int optionCount, const int *optionList) +{ + return (iscsiAuthClientSetOptionList(client, + optionCount, + optionList, + &client->chapAlgorithmCount, + client->chapAlgorithmList, + iscsiAuthChapAlgorithmMaxCount, + iscsiAuthClientCheckChapAlgorithmOption, + iscsiAuthClientCheckChapAlgorithmList)); +} + + +int +iscsiAuthClientSetUsername(IscsiAuthClient * client, const char *username) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure || + iscsiAuthClientCheckString(username, iscsiAuthStringMaxLength, 0)) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (iscsiAuthClientStringCopy(client->username, username, + iscsiAuthStringMaxLength)) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientSetPassword(IscsiAuthClient * client, + const unsigned char *passwordData, unsigned int passwordLength) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure || + passwordLength > iscsiAuthStringMaxLength) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + bcopy(passwordData, client->passwordData, passwordLength); + client->passwordLength = passwordLength; + if (client->passwordLength > 0) { + client->passwordPresent = TRUE; + } else { + client->passwordPresent = FALSE; + } + + /* + * Setting password may affect authMethodValid. + */ + iscsiAuthClientSetAuthMethodValid(client); + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientSetAuthRemote(IscsiAuthClient * client, int authRemote) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->authRemote = authRemote; + + /* + * Setting authRemote may affect authMethodValid. + */ + iscsiAuthClientSetAuthMethodValid(client); + + return (iscsiAuthStatusNoError); +} + +#ifdef notused +int +iscsiAuthClientSetGlueHandle(IscsiAuthClient * client, void *glueHandle) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure && + client->phase != iscsiAuthPhaseNegotiate && + client->phase != iscsiAuthPhaseAuthenticate) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->glueHandle = glueHandle; + + return (iscsiAuthStatusNoError); +} + +int +iscsiAuthClientSetMethodListName(IscsiAuthClient *client, + const char *methodListName) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure || + iscsiAuthClientCheckString(methodListName, + iscsiAuthStringMaxLength, 0)) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (iscsiAuthClientStringCopy(client->methodListName, methodListName, + iscsiAuthStringMaxLength)) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + return (iscsiAuthStatusNoError); +} +#endif + + +int +iscsiAuthClientSetVersion(IscsiAuthClient * client, int version) +{ + if (client == 0 || + client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure || + iscsiAuthClientCheckVersion(version)) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->version = (IscsiAuthVersion) version; + + iscsiAuthClientSetAuthMethodValid(client); + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientSetIpSec(IscsiAuthClient * client, int ipSec) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->ipSec = ipSec; + + return (iscsiAuthStatusNoError); +} + +#ifdef notused +int +iscsiAuthClientSetBase64(IscsiAuthClient * client, int base64) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->base64 = base64; + + return (iscsiAuthStatusNoError); +} + +int +iscsiAuthClientSetChapChallengeLength(IscsiAuthClient * client, + unsigned int chapChallengeLength) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure || + chapChallengeLength < iscsiAuthChapResponseLength || + chapChallengeLength > iscsiAuthLargeBinaryMaxLength) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + client->chapChallengeLength = chapChallengeLength; + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientCheckPasswordNeeded(IscsiAuthClient *client, int *passwordNeeded) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + if (client->authRemote && !client->passwordPresent) { + *passwordNeeded = TRUE; + } else { + *passwordNeeded = FALSE; + } + } else { + *passwordNeeded = FALSE; + } + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientGetAuthPhase(IscsiAuthClient * client, int *value) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + *value = client->phase; + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientGetAuthStatus(IscsiAuthClient * client, int *value) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseDone) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + *value = client->remoteAuthStatus; + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientAuthStatusPass(int authStatus) +{ + if (authStatus == iscsiAuthStatusPass) { + return (TRUE); + } + + return (FALSE); +} + + +int +iscsiAuthClientGetAuthMethod(IscsiAuthClient * client, int *value) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseDone && + client->phase != iscsiAuthPhaseAuthenticate) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + *value = client->negotiatedAuthMethod; + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientGetChapAlgorithm(IscsiAuthClient * client, int *value) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseDone) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + *value = client->negotiatedChapAlgorithm; + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientGetChapUsername(IscsiAuthClient * client, + char *value, unsigned int maxLength) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseDone) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (iscsiAuthClientStringCopy(value, client->chapUsername, maxLength)) { + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + return (iscsiAuthStatusNoError); +} + + +int +iscsiAuthClientSendStatusCode(IscsiAuthClient * client, int *statusCode) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseConfigure && + client->phase != iscsiAuthPhaseNegotiate && + client->phase != iscsiAuthPhaseAuthenticate && + client->phase != iscsiAuthPhaseDone) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseDone) { + *statusCode = 0x0000; + return (iscsiAuthStatusNoError); + } + + switch (client->remoteAuthStatus) { + case iscsiAuthStatusPass: + *statusCode = 0x0000; /* no error */ + break; + + case iscsiAuthStatusFail: + switch (client->debugStatus) { + case iscsiAuthDebugStatusAuthFail: + /* + * Authentication error with peer. + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + *statusCode = 0x0300; + /* + * iSCSI Target error + */ + } else { + *statusCode = 0x0201; + /* + * iSCSI Initiator error + */ + } + break; + + case iscsiAuthDebugStatusAuthMethodExpected: + case iscsiAuthDebugStatusChapAlgorithmExpected: + case iscsiAuthDebugStatusChapIdentifierExpected: + case iscsiAuthDebugStatusChapChallengeExpected: + case iscsiAuthDebugStatusChapResponseExpected: + case iscsiAuthDebugStatusChapUsernameExpected: + /* + * Missing parameter with peer. + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + *statusCode = 0x0300; + /* + * iSCSI Target error + */ + } else { + *statusCode = 0x0207; + /* + * iSCSI Initiator error + */ + } + break; + + case iscsiAuthDebugStatusAuthMethodNotPresent: + case iscsiAuthDebugStatusAuthMethodReject: + case iscsiAuthDebugStatusAuthMethodNone: + case iscsiAuthDebugStatusChapAlgorithmReject: + case iscsiAuthDebugStatusChapChallengeReflected: + case iscsiAuthDebugStatusPasswordIdentical: + /* + * Could not authenticate with peer. + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + *statusCode = 0x0300; + /* + * iSCSI Target error + */ + } else { + *statusCode = 0x0201; + /* + * iSCSI Initiator error + */ + } + break; + + case iscsiAuthDebugStatusLocalPasswordNotSet: + /* + * Local password not set. + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + *statusCode = 0x0200; + /* + * iSCSI Initiator error + */ + } else { + *statusCode = 0x0201; + /* + * iSCSI Target error + */ + } + break; + + case iscsiAuthDebugStatusChapIdentifierBad: + case iscsiAuthDebugStatusChapChallengeBad: + case iscsiAuthDebugStatusChapResponseBad: + case iscsiAuthDebugStatusUnexpectedKeyPresent: + case iscsiAuthDebugStatusTbitSetIllegal: + case iscsiAuthDebugStatusTbitSetPremature: + case iscsiAuthDebugStatusRecvMessageCountLimit: + case iscsiAuthDebugStatusRecvDuplicateSetKeyValue: + case iscsiAuthDebugStatusRecvStringTooLong: + case iscsiAuthDebugStatusRecvTooMuchData: + /* + * Other error with peer. + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + *statusCode = 0x0300; + /* + * iSCSI Target error + */ + } else { + *statusCode = 0x0200; + /* + * iSCSI Initiator error + */ + } + break; + + case iscsiAuthDebugStatusNotSet: + case iscsiAuthDebugStatusAuthPass: + case iscsiAuthDebugStatusAuthRemoteFalse: + case iscsiAuthDebugStatusAuthMethodBad: + case iscsiAuthDebugStatusChapAlgorithmBad: + case iscsiAuthDebugStatusPasswordDecryptFailed: + case iscsiAuthDebugStatusPasswordTooShortWithNoIpSec: + case iscsiAuthDebugStatusAuthServerError: + case iscsiAuthDebugStatusAuthStatusBad: + case iscsiAuthDebugStatusAuthPassNotValid: + case iscsiAuthDebugStatusSendDuplicateSetKeyValue: + case iscsiAuthDebugStatusSendStringTooLong: + case iscsiAuthDebugStatusSendTooMuchData: + default: + /* + * Error on this side. + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + *statusCode = 0x0200; + /* + * iSCSI Initiator error + */ + } else { + *statusCode = 0x0300; + /* + * iSCSI Target error + */ + } + + } + break; + + case iscsiAuthStatusNoError: + case iscsiAuthStatusError: + case iscsiAuthStatusContinue: + case iscsiAuthStatusInProgress: + default: + /* + * Bad authStatus + */ + if (client->nodeType == iscsiAuthNodeTypeInitiator) { + *statusCode = 0x0200; + /* + * iSCSI Initiator error + */ + } else { + *statusCode = 0x0300; + /* + * iSCSI Target error + */ + } + } + + return (iscsiAuthStatusNoError); +} +#endif + + +int +iscsiAuthClientGetDebugStatus(IscsiAuthClient * client, int *value) +{ + if (!client || client->signature != iscsiAuthClientSignature) { + return (iscsiAuthStatusError); + } + + if (client->phase != iscsiAuthPhaseDone) { + + client->phase = iscsiAuthPhaseError; + return (iscsiAuthStatusError); + } + + *value = client->debugStatus; + + return (iscsiAuthStatusNoError); +} + + +const char * +iscsiAuthClientDebugStatusToText(int debugStatus) +{ + const char *s; + + switch (debugStatus) { + case iscsiAuthDebugStatusNotSet: + s = "Debug status not set"; + break; + + case iscsiAuthDebugStatusAuthPass: + s = "Authentication request passed"; + break; + + case iscsiAuthDebugStatusAuthRemoteFalse: + s = "Authentication not enabled"; + break; + + case iscsiAuthDebugStatusAuthFail: + s = "Authentication request failed"; + break; + + case iscsiAuthDebugStatusAuthMethodBad: + s = "AuthMethod bad"; + break; + + case iscsiAuthDebugStatusChapAlgorithmBad: + s = "CHAP algorithm bad"; + break; + + case iscsiAuthDebugStatusPasswordDecryptFailed: + s = "Decrypt password failed"; + break; + + case iscsiAuthDebugStatusPasswordTooShortWithNoIpSec: + s = "Local password too short with no IPSec"; + break; + + case iscsiAuthDebugStatusAuthServerError: + s = "Unexpected error from authentication server"; + break; + + case iscsiAuthDebugStatusAuthStatusBad: + s = "Authentication request status bad"; + break; + + case iscsiAuthDebugStatusAuthPassNotValid: + s = "Authentication pass status not valid"; + break; + + case iscsiAuthDebugStatusSendDuplicateSetKeyValue: + s = "Same key set more than once on send"; + break; + + case iscsiAuthDebugStatusSendStringTooLong: + s = "Key value too long on send"; + break; + + case iscsiAuthDebugStatusSendTooMuchData: + s = "Too much data on send"; + break; + + case iscsiAuthDebugStatusAuthMethodExpected: + s = "AuthMethod key expected"; + break; + + case iscsiAuthDebugStatusChapAlgorithmExpected: + s = "CHAP algorithm key expected"; + break; + + case iscsiAuthDebugStatusChapIdentifierExpected: + s = "CHAP identifier expected"; + break; + + case iscsiAuthDebugStatusChapChallengeExpected: + s = "CHAP challenge expected"; + break; + + case iscsiAuthDebugStatusChapResponseExpected: + s = "CHAP response expected"; + break; + + case iscsiAuthDebugStatusChapUsernameExpected: + s = "CHAP username expected"; + break; + + case iscsiAuthDebugStatusAuthMethodNotPresent: + s = "AuthMethod key not present"; + break; + + case iscsiAuthDebugStatusAuthMethodReject: + s = "AuthMethod negotiation failed"; + break; + + case iscsiAuthDebugStatusAuthMethodNone: + s = "AuthMethod negotiated to none"; + break; + + case iscsiAuthDebugStatusChapAlgorithmReject: + s = "CHAP algorithm negotiation failed"; + break; + + case iscsiAuthDebugStatusChapChallengeReflected: + s = "CHAP challange reflected"; + break; + + case iscsiAuthDebugStatusPasswordIdentical: + s = "Local password same as remote"; + break; + + case iscsiAuthDebugStatusLocalPasswordNotSet: + s = "Local password not set"; + break; + + case iscsiAuthDebugStatusChapIdentifierBad: + s = "CHAP identifier bad"; + break; + + case iscsiAuthDebugStatusChapChallengeBad: + s = "CHAP challenge bad"; + break; + + case iscsiAuthDebugStatusChapResponseBad: + s = "CHAP response bad"; + break; + + case iscsiAuthDebugStatusUnexpectedKeyPresent: + s = "Unexpected key present"; + break; + + case iscsiAuthDebugStatusTbitSetIllegal: + s = "T bit set on response, but not on previous message"; + break; + + case iscsiAuthDebugStatusTbitSetPremature: + s = "T bit set on response, but authenticaton not complete"; + break; + + case iscsiAuthDebugStatusRecvMessageCountLimit: + s = "Message count limit reached on receive"; + break; + + case iscsiAuthDebugStatusRecvDuplicateSetKeyValue: + s = "Same key set more than once on receive"; + break; + + case iscsiAuthDebugStatusRecvStringTooLong: + s = "Key value too long on receive"; + break; + + case iscsiAuthDebugStatusRecvTooMuchData: + s = "Too much data on receive"; + break; + + default: + s = "Unknown error"; + } + + return (s); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_authglue.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_authglue.c new file mode 100644 index 0000000000..fe0f5b4188 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_authglue.c @@ -0,0 +1,374 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2000 by Cisco Systems, Inc. All rights reserved. + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * iSCSI Pseudo HBA Driver + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/random.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include <sys/iscsi_protocol.h> +#include <sys/iscsi_authclient.h> +#include <sys/types.h> +#include "radius.h" +#include "queue.h" +#include "iscsi_sess.h" +#include "xml.h" +#include "target.h" + +#define DEFAULT_RADIUS_PORT 1812 + +boolean_t +persistent_radius_get(iscsi_radius_props_t *radius) +{ + Boolean_t bRadiusAccess = False; + char *szRadiusServer = NULL; + char *szRadiusSecret = NULL; + char *szRadiusPort = NULL; + int ret = 0; + struct addrinfo *res; + + bzero(radius, sizeof (radius)); + radius->r_radius_config_valid = B_FALSE; + + /* Load RADIUS access option: enable/disable */ + if (xml_find_value_boolean(main_config, XML_ELEMENT_RAD_ACCESS, + &bRadiusAccess) == False) { + return (B_FALSE); + } + if (bRadiusAccess == False) { + return (B_FALSE); + } + + /* Load RADIUS server: ipaddr[:port] */ + if (xml_find_value_str(main_config, XML_ELEMENT_RAD_SERV, + &szRadiusServer) == False) { + return (B_FALSE); + } + + szRadiusPort = strchr(szRadiusServer, ':'); + if (szRadiusPort == NULL) { + radius->r_port = DEFAULT_RADIUS_PORT; + } else { + radius->r_port = strtoul(szRadiusPort + 1, NULL, 0); + if (radius->r_port == 0) { + radius->r_port = DEFAULT_RADIUS_PORT; + } + *szRadiusPort = '\0'; + } + + ret = getaddrinfo(szRadiusServer, NULL, NULL, &res); + free(szRadiusServer); + if (ret != 0) { + return (B_FALSE); + } + if (res->ai_family == PF_INET) { + struct sockaddr_in sa_tmp; + + bcopy(res->ai_addr, &sa_tmp, sizeof (sa_tmp)); + radius->r_insize = sizeof (in_addr_t); + radius->r_addr.u_in4 = sa_tmp.sin_addr; + } + /* + * We don't handle IPV6 currently. + */ + + /* Load RADIUS shared secret */ + if (xml_find_value_str(main_config, XML_ELEMENT_RAD_SECRET, + &szRadiusSecret) == False) { + freeaddrinfo(res); + return (B_FALSE); + } + (void) strncpy((char *)radius->r_shared_secret, szRadiusSecret, + MAX_RAD_SHARED_SECRET_LEN); + radius->r_shared_secret_len = strlen((char *)radius->r_shared_secret); + free(szRadiusSecret); + freeaddrinfo(res); + + /* Set RADIUS config flag */ + radius->r_radius_access = B_TRUE; + radius->r_radius_config_valid = B_TRUE; + return (B_TRUE); +} + +/* + * Authenticate a target's CHAP response. + * + * username - Incoming username from the the target. + * responseData - Incoming response data from the target. + */ +int +iscsiAuthClientChapAuthRequest(IscsiAuthClient *client, + char *username, unsigned int id, uchar_t *challengeData, + unsigned int challengeLength, uchar_t *responseData, + unsigned int responseLength) +{ + iscsi_sess_t *isp = (iscsi_sess_t *)client->userHandle; + IscsiAuthMd5Context context; + iscsi_radius_props_t p_radius_cfg; + uchar_t verifyData[iscsiAuthChapResponseLength]; + char debug[128]; + + if (isp == NULL) { + return (iscsiAuthStatusFail); + } + + /* + * the expected credentials are in the session + */ + if (isp->sess_auth.username_in == NULL) { + (void) snprintf(debug, sizeof (debug), + "SES%x iscsi session(%u) failed authentication, " + "no incoming username configured to authenticate initiator", + isp->s_num); + + queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug); + return (iscsiAuthStatusFail); + } + if (strcmp(username, isp->sess_auth.username_in) != 0) { + (void) snprintf(debug, sizeof (debug), + "SES%x iscsi session(%u) failed authentication, " + "received incorrect username from initiator", + isp->s_num); + queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug); + return (iscsiAuthStatusFail); + } + + /* Check if RADIUS access is enabled */ + if (persistent_radius_get(&p_radius_cfg) == B_TRUE && + p_radius_cfg.r_radius_access == B_TRUE) { + chap_validation_status_type chap_valid_status; + int authStatus; + RADIUS_CONFIG radius_cfg; + + if (p_radius_cfg.r_radius_config_valid == B_FALSE) { + /* + * Radius enabled but configuration invalid - + * invalid condition + */ + return (iscsiAuthStatusFail); + } + + /* Use RADIUS server to authentication target */ + if (p_radius_cfg.r_insize == sizeof (in_addr_t)) { + /* IPv4 */ + radius_cfg.rad_svr_addr.i_addr.in4.s_addr = + p_radius_cfg.r_addr.u_in4.s_addr; + radius_cfg.rad_svr_addr.i_insize + = sizeof (in_addr_t); + } else if (p_radius_cfg.r_insize == sizeof (in6_addr_t)) { + /* IPv6 */ + bcopy(p_radius_cfg.r_addr.u_in6.s6_addr, + radius_cfg.rad_svr_addr.i_addr.in6.s6_addr, + 16); + radius_cfg.rad_svr_addr.i_insize = sizeof (in6_addr_t); + } else { + return (iscsiAuthStatusFail); + } + + radius_cfg.rad_svr_port = p_radius_cfg.r_port; + bcopy(p_radius_cfg.r_shared_secret, + radius_cfg.rad_svr_shared_secret, + MAX_RAD_SHARED_SECRET_LEN); + radius_cfg.rad_svr_shared_secret_len = + p_radius_cfg.r_shared_secret_len; + + chap_valid_status = radius_chap_validate( + isp->sess_auth.username_in, + isp->sess_auth.username, + challengeData, + challengeLength, + responseData, + responseLength, + id, + radius_cfg.rad_svr_addr, + radius_cfg.rad_svr_port, + radius_cfg.rad_svr_shared_secret, + radius_cfg.rad_svr_shared_secret_len); + + + switch (chap_valid_status) { + case CHAP_VALIDATION_PASSED: + authStatus = iscsiAuthStatusPass; + break; + case CHAP_VALIDATION_INVALID_RESPONSE: + authStatus = iscsiAuthStatusFail; + break; + case CHAP_VALIDATION_DUP_SECRET: + authStatus = iscsiAuthStatusFail; + break; + case CHAP_VALIDATION_RADIUS_ACCESS_ERROR: + authStatus = iscsiAuthStatusFail; + break; + case CHAP_VALIDATION_BAD_RADIUS_SECRET: + authStatus = iscsiAuthStatusFail; + break; + default: + authStatus = iscsiAuthStatusFail; + break; + } + return (authStatus); + } else { + /* Use target secret (if defined) to authenticate target */ + if ((isp->sess_auth.password_length_in < 1) || + (isp->sess_auth.password_in == NULL) || + (isp->sess_auth.password_in[0] == '\0')) { + /* No target secret defined - invalid condition */ + return (iscsiAuthStatusFail); + } + + /* + * challenge length is I->T, and shouldn't need to + * be checked + */ + if (responseLength != sizeof (verifyData)) { + (void) snprintf(debug, sizeof (debug), + "SES%x iscsi session(%u) failed " + "authentication, received incorrect CHAP response " + "from initiator", isp->s_num); + queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug); + return (iscsiAuthStatusFail); + } + + iscsiAuthMd5Init(&context); + + /* + * id byte + */ + verifyData[0] = id; + iscsiAuthMd5Update(&context, verifyData, 1); + + /* + * shared secret + */ + iscsiAuthMd5Update(&context, + (uchar_t *)isp->sess_auth.password_in, + isp->sess_auth.password_length_in); + + /* + * challenge value + */ + iscsiAuthMd5Update(&context, + (uchar_t *)challengeData, + challengeLength); + + iscsiAuthMd5Final(verifyData, &context); + + if (bcmp(responseData, verifyData, + sizeof (verifyData)) == 0) { + return (iscsiAuthStatusPass); + } + + (void) snprintf(debug, sizeof (debug), + "SES%x iscsi session(%u) failed authentication, " + "received incorrect CHAP response from initiator", + isp->s_num); + queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug); + } + return (iscsiAuthStatusFail); +} + +int +iscsiAuthClientTextToNumber(const char *text, unsigned long *pNumber) +{ + char *pEnd; + unsigned long number; + + number = strtoul(text, &pEnd, 0); + if (*text != '\0' && *pEnd == '\0') { + *pNumber = number; + return (0); /* No error */ + } else { + return (1); /* Error */ + } +} + +/* ARGSUSED */ +void +iscsiAuthClientNumberToText(unsigned long number, char *text, + unsigned int length) +{ + (void) snprintf(text, length, "%lu", number); +} + + +void +iscsiAuthRandomSetData(uchar_t *data, unsigned int length) +{ + int fd; + fd = open("/dev/random", O_RDONLY); + if (fd == -1) + return; + (void) read(fd, data, length); + (void) close(fd); +} + + +void +iscsiAuthMd5Init(IscsiAuthMd5Context * context) +{ + MD5Init(context); +} + + +void +iscsiAuthMd5Update(IscsiAuthMd5Context *context, uchar_t *data, + unsigned int length) +{ + MD5Update(context, data, length); +} + + +void +iscsiAuthMd5Final(uchar_t *hash, IscsiAuthMd5Context *context) +{ + MD5Final(hash, context); +} + + +int +iscsiAuthClientData(uchar_t *outData, unsigned int *outLength, + uchar_t *inData, unsigned int inLength) +{ + if (*outLength < inLength) { + return (1); /* error */ + } + bcopy(inData, outData, inLength); + *outLength = inLength; + return (0); /* no error */ +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_cmd.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_cmd.c new file mode 100644 index 0000000000..e6822af1c2 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_cmd.c @@ -0,0 +1,337 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <pthread.h> +#include <stdlib.h> +#include <assert.h> +#include <syslog.h> +#include <synch.h> +#include <sys/time.h> +#include <sys/asynch.h> + +#include "iscsi_conn.h" +#include "iscsi_cmd.h" +#include "utility.h" + +static pthread_mutex_t cmd_mutex; +static int cmd_ttt; + +/* + * []---- + * | iscsi_cmd_init -- called at the beginning of time to initialize locks + * []---- + */ +void +iscsi_cmd_init() +{ + (void) pthread_mutex_init(&cmd_mutex, NULL); + cmd_ttt = 0; +} + +/* + * []---- + * | iscsi_cmd_alloc -- allocate space for new command + * []---- + */ +iscsi_cmd_t * +iscsi_cmd_alloc(iscsi_conn_t *c, int op) +{ + iscsi_cmd_t *cmd = (iscsi_cmd_t *)calloc(sizeof (iscsi_cmd_t), 1); + + if (cmd == NULL) + return (NULL); + + (void) pthread_mutex_lock(&cmd_mutex); + cmd->c_ttt = cmd_ttt++; + (void) pthread_mutex_unlock(&cmd_mutex); + + (void) pthread_mutex_lock(&c->c_mutex); + cmd->c_opcode = op; + cmd->c_statsn = c->c_statsn; + cmd->c_state = CmdAlloc; + if (c->c_cmd_head == NULL) { + c->c_cmd_head = cmd; + assert(c->c_cmd_tail == NULL); + c->c_cmd_tail = cmd; + } else { + c->c_cmd_tail->c_next = cmd; + cmd->c_prev = c->c_cmd_tail; + c->c_cmd_tail = cmd; + } + cmd->c_allegiance = c; + cmd->c_t_start = gethrtime(); + c->c_cmds_active++; + (void) pthread_mutex_unlock(&c->c_mutex); + + return (cmd); +} + +/* + * []---- + * | iscsi_cmd_find -- search for a specific command and return it + * | + * | XXX Need to switch to use an AVL tree. + * []---- + */ +iscsi_cmd_t * +iscsi_cmd_find(iscsi_conn_t *c, uint32_t val, find_type_t type) +{ + iscsi_cmd_t *cmd = NULL; + + (void) pthread_mutex_lock(&c->c_mutex); + for (cmd = c->c_cmd_head; cmd; cmd = cmd->c_next) { + + /* + * Depending on type determine correct matching value. + * Only return a hit if the command hasn't already been + * freed. + */ + if ((((type == FindTTT) && (cmd->c_ttt == val)) || + ((type == FindITT) && (cmd->c_itt == val))) && + (cmd->c_state != CmdFree)) + break; + } + (void) pthread_mutex_unlock(&c->c_mutex); + + return (cmd); +} + +/* + * []---- + * | iscsi_cmd_free -- mark a command as freed. + * []---- + */ +void +iscsi_cmd_free(iscsi_conn_t *c, iscsi_cmd_t *cmd) +{ + hrtime_t h = gethrtime(); + + cmd->c_state = CmdFree; + cmd->c_t_completion = h - cmd->c_t_start; + c->c_cmds_avg_sum += cmd->c_t_completion; + c->c_cmds_avg_cnt++; +} + +/* + * []---- + * | iscsi_cmd_cancel -- mark a command as canceled + * | + * | We don't actually stop commands in flight. We only prevent the canceled + * | commands from returning status and/or data to the initiator. At the + * | connection layer if a command is canceled nothing will be sent on the + * | wire and at that point the command is marked CmdFree so that future calls + * | to cmd_remove will actually free the space. + * []---- + */ +void +iscsi_cmd_cancel(iscsi_conn_t *c, iscsi_cmd_t *cmd) +{ + (void) pthread_mutex_lock(&c->c_mutex); + if (cmd->c_state == CmdAlloc) { + cmd->c_state = CmdCanceled; + if (cmd->c_t10_cmd != NULL) + t10_cmd_state(cmd->c_t10_cmd, T10_Cmd_Event_Canceled); + } + (void) pthread_mutex_unlock(&c->c_mutex); +} + +/* + * []---- + * | iscsi_cmd_remove -- actually free space allocated to commands + * | + * | According to the iSCSI specification the target must kept resources + * | around until the initiator sends a command with a status serial + * | number higher than the held resource. This is so that an initiator + * | can request data again if needed. During the processing of each new + * | command this routine is called to free old commands. + * []---- + */ +void +iscsi_cmd_remove(iscsi_conn_t *c, uint32_t statsn) +{ + iscsi_cmd_t *cmd, + *n; + + (void) pthread_mutex_lock(&c->c_mutex); + for (cmd = c->c_cmd_head; cmd; ) { + + /* + * If the StatusSN for this command is less than the incoming + * StatusSN and the command has been freed remove it from + * list. Don't bother with commands that are in the state of + * CmdCanceled. Once the I/O has been completed the command + * is passed back to the connection handler where the state + * will be noticed and then the command will be freed. At that + * point the next incoming command with a valid expected + * status serial number will free the memory. + */ + if (sna_lt(cmd->c_statsn, statsn) && + (cmd->c_state == CmdFree)) { + + if (c->c_cmd_head == cmd) { + c->c_cmd_head = cmd->c_next; + if (c->c_cmd_head == NULL) + c->c_cmd_tail = NULL; + } else { + n = cmd->c_prev; + n->c_next = cmd->c_next; + if (cmd->c_next != NULL) + cmd->c_next->c_prev = n; + else { + assert(c->c_cmd_tail == cmd); + c->c_cmd_tail = n; + } + } + + n = cmd->c_next; + if (cmd->c_scb_extended) + free(cmd->c_scb_extended); + if (cmd->c_data_alloc == True) { + free(cmd->c_data); + cmd->c_data = NULL; + } + c->c_cmds_active--; + free(cmd); + cmd = n; + } else if (sna_lte(statsn, cmd->c_statsn)) { + break; + } else { + cmd = cmd->c_next; + } + } + (void) pthread_mutex_unlock(&c->c_mutex); + +} + +/* + * []---- + * | iscsi_cmd_window -- return the number of available commands + * | + * | There are currently 7 different places where this routine is called. + * | In some cases and command is allocated which will be freed shortly and + * | in others no command is held. This is why the number of commands found + * | will be decremented if larger than 0. Since the daemon doesn't have + * | any hard limits on the number of commands being supported this is more + * | arbitrary and the command window size is used for debugging other + * | initiators. + * | + * | NOTE: connection mutex must be held during this call. + * []---- + */ +int +iscsi_cmd_window(iscsi_conn_t *c) +{ + int cnt; + + assert(pthread_mutex_trylock(&c->c_mutex) != 0); + if (c->c_cmds_avg_cnt == 0) { + + /* + * If there are no outstanding commands clear the averages + * so that the initiator can start again. + */ + c->c_cmds_avg_sum = 0; + c->c_cmds_avg_cnt = 0; + cnt = c->c_maxcmdsn - c->c_cmds_active; + + } else if ((c->c_cmds_avg_sum / c->c_cmds_avg_cnt) >= NANOSEC) { + + /* + * It would appear things are taking a real long time to + * complete on our end. Close down the command window to + * prevent the initiator from timing out commands. + */ + cnt = (c->c_cmds_active > 5) ? 0 : 5 - c->c_cmds_active; + + } else { + cnt = (c->c_cmds_active >= c->c_maxcmdsn) ? 0 : + c->c_maxcmdsn - c->c_cmds_active; + } + + return (cnt); +} + +void +iscsi_cmd_delayed_store(iscsi_cmd_t *cmd, t10_cmd_t *t) +{ + iscsi_delayed_t *d, + *n, + *l = NULL; + + if ((d = (iscsi_delayed_t *)calloc(1, sizeof (*d))) == NULL) { + syslog(LOG_ERR, "Failed to allocate space for delayed I/O"); + queue_prt(cmd->c_allegiance->c_mgmtq, Q_CONN_ERRS, + "CON%x Failed calloc for delayed I/O", + cmd->c_allegiance->c_num); + t10_cmd_state(t, T10_Cmd_Event_Release); + return; + } + + d->id_offset = T10_DATA_OFFSET(t); + d->id_t10_cmd = t; + + for (n = cmd->c_t10_delayed; n; n = n->id_next) { + l = n; + if (d->id_offset < n->id_offset) { + if (n->id_prev == NULL) { + d->id_next = n; + n->id_prev = d; + cmd->c_t10_delayed = d; + } else { + d->id_prev = n->id_prev; + d->id_prev->id_next = d; + n->id_prev = d; + d->id_next = n; + } + return; + } + } + + if (l == NULL) { + cmd->c_t10_delayed = d; + } else { + l->id_next = d; + d->id_prev = l; + } +} + +void +iscsi_cmd_delayed_remove(iscsi_cmd_t *cmd, iscsi_delayed_t *d) +{ + if (cmd->c_t10_delayed == d) { + cmd->c_t10_delayed = d->id_next; + if (d->id_next) + d->id_next->id_prev = NULL; + } else { + d->id_prev->id_next = d->id_next; + if (d->id_next != NULL) + d->id_next->id_prev = d->id_prev; + } + free(d); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_cmd.h b/usr/src/cmd/iscsi/iscsitgtd/iscsi_cmd.h new file mode 100644 index 0000000000..f9c20c82ba --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_cmd.h @@ -0,0 +1,167 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _TARGET_CMD_H +#define _TARGET_CMD_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <sys/avl.h> +#include <aio.h> + +#include "local_types.h" +#include "t10.h" + +#define CMD_MAXOUTSTANDING 64 + +typedef enum { + FindTTT, + FindITT +} find_type_t; + +typedef enum { + CmdAlloc, + CmdCanceled, + CmdFree +} cmd_state_t; + +typedef struct iscsi_delayed { + struct iscsi_delayed *id_prev, + *id_next; + t10_cmd_t *id_t10_cmd; + size_t id_offset; +} iscsi_delayed_t; + +typedef struct iscsi_cmd { + struct iscsi_cmd *c_next; + struct iscsi_cmd *c_prev; + + /* + * Always kept in network byte order since we only + * store this field + */ + uint32_t c_itt; + + uint32_t c_opcode; + uint32_t c_ttt; + uint32_t c_cmdsn; + uint32_t c_datasn; + uint32_t c_statsn; + + Boolean_t c_writeop; + uint32_t c_lun; + + /* + * Default storage for SCB which is the most common size to day. + */ + uint8_t c_scb_default[16]; + + /* + * If the CDB is larger than 16 bytes an Alternate Header Segment + * is used and memory allocated which is pointed to by the following + */ + uint8_t *c_scb_extended; + + /* + * Points at the appropriate memory area for the SCB + */ + uint8_t *c_scb; + uint32_t c_scb_len; + + cmd_state_t c_state; + uint32_t c_status; + + /* + * When ImmediateData==Yes it'll be read in to a buffer + * allocated by the connection. This will be free'd when the + * callback is done which means the SAM-3 layer is finished with + * the data. + */ + char *c_data; + size_t c_data_len; + off_t c_offset_out; + + /* + * Arrange for the PDUs to always be sent in order. If DataPDUInOrder + * is True this is a *must* according to the specification. There's + * also a need that the final flag bit not be sent unless all other + * packets have been, regardless of order. Without keeping a complicated + * bitmap of which packets have been sent for this second case we + * just order things always. + */ + off_t c_offset_in; + iscsi_delayed_t *c_t10_delayed; + + Boolean_t c_data_alloc; + + void (*c_dataout_cb)(t10_cmd_t *cmd, char *data, + size_t *xfer); + + /* + * Used to hold the interface pointer when we've got an R2T + * for this command. This is needed because memory is allocated + * normally by the emulation layer and we can copy directly to that + * instead of allocating our own buffer. + */ + t10_cmd_t *c_t10_cmd; + + /* + * Used by the session layer to send packets out the same + * connection. + */ + struct iscsi_conn *c_allegiance; + + hrtime_t c_t_start, + c_t_completion; + +} iscsi_cmd_t; + + +void iscsi_cmd_init(); +iscsi_cmd_t *iscsi_cmd_alloc(struct iscsi_conn *c, int opcode); +iscsi_cmd_t *iscsi_cmd_find(struct iscsi_conn *c, uint32_t x, + find_type_t type); +void iscsi_cmd_free(struct iscsi_conn *c, iscsi_cmd_t *cmd); +void iscsi_cmd_cancel(struct iscsi_conn *c, iscsi_cmd_t *cmd); +void iscsi_cmd_remove(struct iscsi_conn *c, uint32_t statsn); +int iscsi_cmd_window(struct iscsi_conn *c); +void iscsi_cmd_delayed_store(iscsi_cmd_t *cmd, t10_cmd_t *t); +void iscsi_cmd_delayed_remove(iscsi_cmd_t *cmd, iscsi_delayed_t *d); + +#ifdef __cplusplus +} +#endif + +#endif /* _TARGET_CMD_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_conn.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_conn.c new file mode 100644 index 0000000000..5252832d99 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_conn.c @@ -0,0 +1,1226 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <signal.h> +#include <pthread.h> +#include <assert.h> +#include <sys/select.h> +#include <stdlib.h> +#include <poll.h> +#include <strings.h> +#include <sys/filio.h> +#include <errno.h> +#include <utility.h> +#include <unistd.h> +#include <sys/stropts.h> +#include <syslog.h> +#include <sys/iscsi_protocol.h> + +#include "local_types.h" +#include "iscsi_conn.h" +#include "iscsi_sess.h" +#include "iscsi_login.h" +#include "iscsi_ffp.h" +#include "utility.h" +#include "target.h" +#include "port.h" +#include "t10.h" + +/* + * defined here so that pad_text is initialized to zero's. It's + * never modified. + */ +static const char pad_text[ISCSI_PAD_WORD_LEN] = { 0 }; + +static void iscsi_conn_data_rqst(t10_cmd_t *cmd); +static void iscsi_conn_cmdcmplt(t10_cmd_t *cmd); +static void iscsi_conn_data_in(t10_cmd_t *); +static void iscsi_conn_pkt(iscsi_conn_t *c, iscsi_rsp_hdr_t *in); + +static void send_datain_pdu(iscsi_conn_t *c, t10_cmd_t *cmd, + uint8_t final_flag); +static void send_scsi_rsp(iscsi_conn_t *c, t10_cmd_t *cmd); +static void queue_noop_in(iscsi_conn_t *c); +static char *state_to_str(iscsi_state_t s); +static void send_async_logout(iscsi_conn_t *c); +static void send_async_scsi(iscsi_conn_t *c, int key, int asc, int ascq); + +void * +conn_poller(void *v) +{ + iscsi_conn_t *c = (iscsi_conn_t *)v; + int nbytes, + pval; + nfds_t nfds = 1; + struct pollfd fds[1]; + iscsi_state_t state; + target_queue_t *mgmtq = c->c_mgmtq; + + fds[0].fd = c->c_fd; + fds[0].events = POLLIN; + + util_title(c->c_mgmtq, Q_CONN_LOGIN, c->c_num, "Start Poller"); + while ((pval = poll(fds, nfds, 30 * 1000)) != -1) { + + /* + * The true asynchronous events are when we're in S5_LOGGED_IN + * state. In the iscsi_full_feature() code the state is + * locked and checked before sending any messages along. The + * mutex is grabbed here only to prevent a collision between + * some thread setting the state and our reading of the value. + * There's no harm in us grabbing the state which might + * change right after we unlock the mutex. + */ + (void) pthread_mutex_lock(&c->c_state_mutex); + state = c->c_state; + (void) pthread_mutex_unlock(&c->c_state_mutex); + + switch (state) { + case S1_FREE: + /* + * If we moved to the free state. The session + * was sent a message to shutdown. Once it + * completes it will reply with a shutdown + * response which will close the main + * connection thread. So, this thread just + * returns a stop processing incoming packets. + */ + goto error; + + case S3_XPT_UP: + if (ioctl(c->c_fd, FIONREAD, &nbytes) < 0) { + queue_message_set(c->c_dataq, 0, msg_shutdown, + 0); + goto error; + } + + /* + * To be fully compliant the code should use + * ioctl(fd, I_PEEK, (struct strpeek v)); and + * look to see if the header is indeed a login + * packet. If not, just close the connection. + */ + if (nbytes < sizeof (iscsi_login_hdr_t)) { + queue_message_set(c->c_dataq, 0, + msg_shutdown, 0); + goto error; + } else { + /* + * Change the state to S4_IN_LOGIN. + * Since we haven't touched the data + * waiting on the stream when the + * sema_post() occurs below the poller + * will find data again and send + * another packet ready message at + * which point we deal with the + * login. + */ + conn_state(c, T4); + } + break; + + case S4_IN_LOGIN: + if (iscsi_handle_login_pkt(c) == False) + goto error; + break; + + case S7_LOGOUT_REQUESTED: + case S5_LOGGED_IN: + if (fds[0].revents & POLLIN) { + + if (iscsi_full_feature(c) == False) + goto error; + + } else if (c->c_sess->s_type == SessionNormal) { + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x Send a NOP request", c->c_num); + queue_noop_in(c); + } + break; + + case S6_IN_LOGOUT: + goto error; + + case S8_CLEANUP_WAIT: + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "Haven't handled state S8"); + queue_message_set(c->c_dataq, 0, + msg_shutdown_rsp, 0); + goto error; + } + + } + +error: + /* + * Only when we're logged in would we have an active session + * which needs to be shut down. In the case of S4_IN_LOGIN we could + * transition to either S1_FREE in which case a shutdown message + * was sent to the session which in turn will reply with a shutdown + * response causing the conn_process to exit. + */ + if (c->c_state == S5_LOGGED_IN) + conn_state(c, T8); + + /* + * If a msg_conn_lost was already sent it's invalid to reference + * the management queue from the connection structure at this point. + */ + util_title(mgmtq, Q_CONN_LOGIN, c->c_num, "End Poller"); + + if (pval == -1) + queue_message_set(c->c_dataq, 0, msg_conn_lost, 0); + return (NULL); +} + +/* + * conn_process -- thread which runs a connection + */ +void * +conn_process(void *v) +{ + iscsi_conn_t *c = (iscsi_conn_t *)v; + iscsi_cmd_t *cmd; + Boolean_t process = True, + is_last = False; + msg_t *m; + void *thr_status; + int i; + mgmt_request_t *mgmt; + char debug[80]; + time_t tval = time((time_t *)0); + + c->c_dataq = queue_alloc(); + c->c_maxcmdsn = CMD_MAXOUTSTANDING; + if (sema_init(&c->c_datain, 0, USYNC_THREAD, NULL) != 0) { + port_conn_remove(c); + free(c); + return (NULL); + } + + util_title(c->c_mgmtq, Q_CONN_LOGIN, c->c_num, "Start Receiver"); + util_title(c->c_mgmtq, Q_CONN_LOGIN, c->c_num, + ctime_r(&tval, debug, sizeof (debug))); + + (void) pthread_create(&c->c_thr_id_poller, NULL, + conn_poller, (void *)c); + c->c_thr_id_process = pthread_self(); + + assert(c->c_state == S1_FREE); + conn_state(c, T3); + + do { + m = queue_message_get(c->c_dataq); + switch (m->msg_type) { + case msg_mgmt_rqst: + mgmt = (mgmt_request_t *)m->msg_data; + if (c->c_state == S5_LOGGED_IN) { + if (mgmt->m_request == mgmt_logout) { + conn_state(c, T11); + queue_message_set(mgmt->m_q, 0, + msg_mgmt_rply, 0); + } else { + queue_message_set(c->c_sessq, 0, + msg_mgmt_rqst, m->msg_data); + } + } else { + /* + * Corner case which can occur when the + * connection has just started and is in the + * process of logging in and we get a + * mangement request. There's no session + * information or even a queue setup. Just + * show an empty connection. + * + * For the mgmt_logout, it's possible that + * we sent the T11 state change causing the + * connection to enter the S7 state. If we + * get a logout request again the specification + * says to just drop the connection. + */ + if (mgmt->m_request == mgmt_logout) + conn_state(c, T18); + else { + (void) pthread_mutex_lock( + &mgmt->m_resp_mutex); + xml_add_tag(mgmt->m_u.m_resp, + "connection", NULL); + (void) pthread_mutex_unlock( + &mgmt->m_resp_mutex); + } + queue_message_set(mgmt->m_q, 0, + msg_mgmt_rply, 0); + } + m->msg_data = NULL; + break; + + case msg_conn_lost: + queue_prt(c->c_mgmtq, Q_CONN_LOGIN, + "CON%x Shutdown: connection", c->c_num); + + if (c->c_state == S5_LOGGED_IN) + conn_state(c, T8); + break; + + case msg_shutdown_rsp: + if (c->c_state == S6_IN_LOGOUT) + conn_state(c, T13); + (void) pthread_join(c->c_thr_id_poller, &thr_status); + is_last = (Boolean_t)m->msg_data; + m->msg_data = NULL; + process = False; + break; + + case msg_shutdown: + if (c->c_state == S5_LOGGED_IN) { + conn_state(c, T8); + } else if (c->c_state == S4_IN_LOGIN) + conn_state(c, T7); + else { + process = False; + } + break; + + case msg_targ_inventory_change: + if (c->c_state == S5_LOGGED_IN) { + send_async_scsi(c, KEY_UNIT_ATTENTION, 0x3f, + 0x0e); + } + break; + + case msg_send_pkt: + iscsi_conn_pkt(c, (iscsi_rsp_hdr_t *)m->msg_data); + break; + + case msg_cmd_data_rqst: + /* + * The STE needs more data to complete + * the write command. + */ + iscsi_conn_data_rqst((t10_cmd_t *)m->msg_data); + break; + + case msg_cmd_data_in: + /* + * Data is available to satisfy the READ command + */ + iscsi_conn_data_in((t10_cmd_t *)m->msg_data); + break; + + case msg_cmd_cmplt: + /* + * Status is available for a previous STEOut. + * The status may be good and the previous STEOut data + * wasn't sent so we phase collapse. + */ + iscsi_conn_cmdcmplt((t10_cmd_t *)m->msg_data); + break; + + default: + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x Didn't handle msg_type %d", c->c_num, + m->msg_type); + break; + } + + queue_message_free(m); + } while (process == True); + + /* + * Free any resources used. + */ + if (c->c_text_area) + free(c->c_text_area); + if (c->c_fd != -1) + (void) close(c->c_fd); + + /* + * It's possible, but very unlikely that c_sessq is NULL at this + * point. I saw one case where the system had problems causing the + * poller routine to exit real early so that the session was never + * setup causing the daemon to get a SEGV in queue_free when a NULL + * was passed in. + */ + if ((is_last == True) && (c->c_sessq != NULL)) + queue_free(c->c_sessq, sess_queue_data_remove); + + /* + * See if there are any commands outstanding and free them. + * NOTE: Should walk through the data_ptr list and find data structure + * who have alligence to this connection and free them as well. + */ + (void) pthread_mutex_lock(&c->c_mutex); + + for (i = 0, cmd = c->c_cmd_head; cmd; i++) + cmd = cmd->c_next; /* debug count of lost ttt's */ + + (void) snprintf(debug, sizeof (debug), "CON%x %d Lost TTTs: ", + c->c_num, i); + + for (i = 0, cmd = c->c_cmd_head; cmd; i++) { + iscsi_cmd_t *n = cmd->c_next; + if (cmd->c_state != CmdCanceled) { + (void) snprintf(debug + strlen(debug), + sizeof (debug) - strlen(debug), + "0x%x ", cmd->c_ttt); + } + free(cmd); + cmd = n; + } + (void) pthread_mutex_unlock(&c->c_mutex); + + if (i) { + /* + * If there where any found send a message indicating + * which ones. This message is purely for information + * and is not indicative of an error. + */ + queue_prt(c->c_mgmtq, Q_CONN_LOGIN, debug); + } + + if (c->c_cmds_avg_cnt != 0) + queue_prt(c->c_mgmtq, Q_CONN_LOGIN, + "CON%x Average completion %lldms", c->c_num, + (c->c_cmds_avg_sum / c->c_cmds_avg_cnt) / (1000 * 1000)); + + (void) sema_destroy(&c->c_datain); + if (c->c_targ_alias) + free(c->c_targ_alias); + + util_title(c->c_mgmtq, Q_CONN_LOGIN, c->c_num, "End Receiver"); + + /* + * Remove this connection from linked list of current connections. + * This will also free the connection queue. Must not hold the + * q here because port_conn_remove-->queue_free->conn_queue_data + * will possible grab the mutex. + */ + port_conn_remove(c); + free(c); + return (NULL); +} + +/* + * []---- + * | iscsi_conn_pkt -- send out PDU from receive thread + * | + * | (1) This PDU could be either: + * | (a) A NOP request was sent from the initiator which the recieve + * | side processed and is requesting to be sent back. + * | (b) Nothing has been received in N seconds and we're looking + * | to see if the connection is still alive. + * | (c) A task management request was processed by the receive side + * | and the response must be sent. + * | (2) Fields to be filled in + * | Need to delay filling in several of the fields until + * | now to avoid using sequence number which would be out of + * | order. + * []---- + */ +static void +iscsi_conn_pkt(iscsi_conn_t *c, iscsi_rsp_hdr_t *in) +{ + if (c->c_state != S5_LOGGED_IN) { + free(in); + return; + } + (void) pthread_mutex_lock(&c->c_mutex); + in->statsn = htonl(c->c_statsn++); + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + in->expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + in->maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + + send_iscsi_pkt(c, (iscsi_hdr_t *)in, 0); + free(in); +} + +/* + * []---- + * | iscsi_conn_data_rqst -- request that data be sent from the initiator + * []---- + */ +static void +iscsi_conn_data_rqst(t10_cmd_t *t) +{ + iscsi_cmd_t *cmd = T10_TRANS_ID(t); + iscsi_conn_t *c; + iscsi_rtt_hdr_t rtt; + + bzero(&rtt, sizeof (rtt)); + + c = cmd->c_allegiance; + (void) pthread_mutex_lock(&c->c_mutex); + (void) pthread_mutex_lock(&c->c_state_mutex); + if ((c->c_state != S5_LOGGED_IN) || + (cmd->c_state == CmdCanceled)) { + t10_cmd_state(t, T10_Cmd_Event_Release); + (void) pthread_mutex_unlock(&c->c_state_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + return; + } + (void) pthread_mutex_unlock(&c->c_state_mutex); + + /* + * Save the data pointer from the emulation code. It's their + * responsibility to allocate space for the data which the + * initiator will return. When we receive a DATAOUT packet + * we'll copy data from the socket directly to this buffer. + */ + cmd->c_data = T10_DATA(t); + + /* + * RFC3270.10.8.3 + * The statsn field will contain the next statsn. The statsn for this + * connection is not advanced after this PDU is sent. + */ + rtt.statsn = htonl(c->c_statsn); + + rtt.opcode = ISCSI_OP_RTT_RSP; + rtt.flags = ISCSI_FLAG_FINAL; + rtt.itt = cmd->c_itt; + rtt.ttt = cmd->c_ttt; + rtt.data_offset = htonl(T10_DATA_OFFSET(t)); + rtt.data_length = htonl(T10_DATA_LEN(t)); + + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + rtt.maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn); + rtt.expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + +#ifdef FULL_DEBUG + queue_prt(c->c_mgmtq, Q_CONN_IO, + "CON%x TTT 0x%x R2T offset 0x%x, len 0x%x", + c->c_num, cmd->c_ttt, T10_DATA_OFFSET(t), T10_DATA_LEN(t)); +#endif + + t10_cmd_state(t, T10_Cmd_Event_DataOut_Sent); + (void) pthread_mutex_unlock(&c->c_mutex); + send_iscsi_pkt(c, (iscsi_hdr_t *)&rtt, 0); +} + +/* + * []---- + * | iscsi_conn_data_in -- Send data to initiator + * []---- + */ +void +iscsi_conn_data_in(t10_cmd_t *t) +{ + iscsi_cmd_t *cmd = (iscsi_cmd_t *)T10_TRANS_ID(t); + iscsi_conn_t *c; + + c = cmd->c_allegiance; + (void) pthread_mutex_lock(&c->c_mutex); + (void) pthread_mutex_lock(&c->c_state_mutex); + if ((c->c_state != S5_LOGGED_IN) || + (cmd->c_state == CmdCanceled)) { + + t10_cmd_state(t, T10_Cmd_Event_Release); + while (cmd->c_t10_delayed) { + t10_cmd_state(cmd->c_t10_delayed->id_t10_cmd, + T10_Cmd_Event_Release); + iscsi_cmd_delayed_remove(cmd, cmd->c_t10_delayed); + } + (void) pthread_mutex_unlock(&c->c_state_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + return; + } + (void) pthread_mutex_unlock(&c->c_state_mutex); + + /* + * Need to deal with out of order data PDUs. RFC3720 allows + * the initiator to indicate if it can handle out-of-order + * PDUs. + */ + if (cmd->c_offset_in != T10_DATA_OFFSET(t)) { + iscsi_cmd_delayed_store(cmd, t); + (void) pthread_mutex_unlock(&c->c_mutex); + return; + } + + while (t != NULL) { + cmd->c_offset_in += T10_DATA_LEN(t); + if (T10_CMD_LAST(t) == True) { + send_datain_pdu(c, t, + ISCSI_FLAG_FINAL | ISCSI_FLAG_DATA_STATUS); + iscsi_cmd_free(c, cmd); + } else { + send_datain_pdu(c, t, 0); + } + t10_cmd_state(t, T10_Cmd_Event_Release); + cmd->c_t10_cmd = NULL; + + if (cmd->c_t10_delayed && + (cmd->c_t10_delayed->id_offset == cmd->c_offset_in)) { + t = cmd->c_t10_delayed->id_t10_cmd; + iscsi_cmd_delayed_remove(cmd, cmd->c_t10_delayed); + } else { + t = NULL; + } + } + (void) pthread_mutex_unlock(&c->c_mutex); +} + +/* + * []---- + * | iscsi_conn_cmdcmplt -- Send out appropriate completion PDU + * []---- + */ +void +iscsi_conn_cmdcmplt(t10_cmd_t *t) +{ + iscsi_cmd_t *cmd = (iscsi_cmd_t *)T10_TRANS_ID(t); + iscsi_conn_t *c; + + c = cmd->c_allegiance; + (void) pthread_mutex_lock(&c->c_mutex); + (void) pthread_mutex_lock(&c->c_state_mutex); + if ((c->c_state != S5_LOGGED_IN) || + (cmd->c_state == CmdCanceled)) { + + t10_cmd_state(t, T10_Cmd_Event_Release); + (void) pthread_mutex_unlock(&c->c_state_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + return; + } + (void) pthread_mutex_unlock(&c->c_state_mutex); + + if (T10_SENSE_LEN(t) || (T10_DATA(t) == 0)) { + + /* + * If d_sense_len is set there's a problem and we need to send + * a SCSI response packet. Or if there's no data buffer then + * this is an acknowledgement that a SCSI Write completed + * successfully. + */ + send_scsi_rsp(c, t); + + } else { + + /* + * send data out with final bit. Last packet of a SCSI + * READ Op and we'll send it out with the final/status + * bits set. + */ + send_datain_pdu(c, t, + ISCSI_FLAG_FINAL | ISCSI_FLAG_DATA_STATUS); + + } + + t10_cmd_state(t, T10_Cmd_Event_Release); + cmd->c_t10_cmd = NULL; + + if (cmd->c_scb_extended != NULL) + free(cmd->c_scb_extended); + iscsi_cmd_free(c, cmd); + (void) pthread_mutex_unlock(&c->c_mutex); +} + +/* + * []---- + * | send_datain_pdu -- Send DataIn PDU with READ data + * | + * | If this is the last read operation and it completed successfully + * | the final flag will be set along with the status bit which indicates + * | successful completion. This is known as a phase collapse for iSCSI. + * | + * | NOTE: connection mutex must be held. + * []---- + */ +static void +send_datain_pdu(iscsi_conn_t *c, t10_cmd_t *t, uint8_t final_flag) +{ + iscsi_cmd_t *cmd = (iscsi_cmd_t *)T10_TRANS_ID(t); + iscsi_data_rsp_hdr_t rsp; + + assert(pthread_mutex_trylock(&c->c_mutex) != 0); + bzero(&rsp, sizeof (rsp)); + + rsp.opcode = ISCSI_OP_SCSI_DATA_RSP; + rsp.flags = final_flag; + rsp.cmd_status = cmd->c_status; + rsp.itt = cmd->c_itt; + rsp.ttt = ISCSI_RSVD_TASK_TAG; + rsp.datasn = htonl(cmd->c_datasn++); + rsp.offset = htonl(T10_DATA_OFFSET(t)); + rsp.lun[1] = (uint8_t)cmd->c_lun; + + hton24(rsp.dlength, T10_DATA_LEN(t)); + + rsp.statsn = htonl(c->c_statsn); + /* + * The statsn is only incremented when the Status bit is set + * for a DataIn PDU. This must be done *after* the value + * was stored in the PDU. + */ + if (final_flag & ISCSI_FLAG_DATA_STATUS) + c->c_statsn++; + + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + rsp.maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn); + rsp.expcmdsn = htonl(c->c_sess->s_seencmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + + send_iscsi_pkt(c, (iscsi_hdr_t *)&rsp, T10_DATA(t)); +} + +/* + * []---- + * | send_scsi_rsp -- Send SCSI reponse PDU + * | + * | NOTE: connection mutex must be held. + * []---- + */ +static void +send_scsi_rsp(iscsi_conn_t *c, t10_cmd_t *t) +{ + iscsi_cmd_t *cmd = (iscsi_cmd_t *)T10_TRANS_ID(t); + iscsi_scsi_rsp_hdr_t rsp; + void *auto_sense = NULL; + + assert(pthread_mutex_trylock(&c->c_mutex) != 0); + bzero(&rsp, sizeof (rsp)); + + rsp.opcode = ISCSI_OP_SCSI_RSP; + rsp.flags = ISCSI_FLAG_FINAL; + rsp.itt = cmd->c_itt; + rsp.statsn = htonl(c->c_statsn++); + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + rsp.expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + rsp.maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + rsp.cmd_status = T10_CMD_STATUS(t); + + if (rsp.cmd_status) { + rsp.response = ISCSI_STATUS_CMD_COMPLETED; + rsp.residual_count = htonl(T10_CMD_RESID(t)); + if (cmd->c_writeop == True) + rsp.flags |= ISCSI_FLAG_CMD_OVERFLOW; + else + rsp.flags |= ISCSI_FLAG_CMD_UNDERFLOW; + + if (T10_SENSE_LEN(t) != 0) { + /* + * Need to handle autosense stuff. The data should + * be store in the d_sense area + */ + auto_sense = (void *)T10_SENSE_DATA(t); + hton24(rsp.dlength, T10_SENSE_LEN(t)); + } + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x LUN%d SCSI Error Status: %d", + c->c_num, t->c_lu->l_common->l_num, rsp.cmd_status); + } else { + rsp.response = ISCSI_STATUS_CMD_COMPLETED; + rsp.expdatasn = htonl(cmd->c_datasn); + } + + send_iscsi_pkt(c, (iscsi_hdr_t *)&rsp, auto_sense); +} + +static void +send_async_scsi(iscsi_conn_t *c, int key, int asc, int ascq) +{ + iscsi_async_evt_hdr_t a; + struct scsi_extended_sense s; + char *buf; + + bzero(&a, sizeof (a)); + bzero(&s, sizeof (s)); + + s.es_class = CLASS_EXTENDED_SENSE; + s.es_code = CODE_FMT_FIXED_CURRENT; + s.es_key = key; + s.es_valid = 1; + s.es_add_code = asc; + s.es_qual_code = ascq; + + if ((buf = malloc(sizeof (s) + 2)) == NULL) + return; + + buf[0] = (sizeof (s) >> 8) & 0xff; + buf[1] = sizeof (s) & 0xff; + bcopy(&s, &buf[2], sizeof (s)); + + hton24(a.dlength, sizeof (s) + 2); + a.opcode = ISCSI_OP_ASYNC_EVENT; + a.flags = ISCSI_FLAG_FINAL; + a.async_event = ISCSI_ASYNC_EVENT_SCSI_EVENT; + (void) pthread_mutex_lock(&c->c_mutex); + a.statsn = htonl(c->c_statsn++); + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + a.expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + a.maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x Sending async scsi sense", c->c_num); + + send_iscsi_pkt(c, (iscsi_hdr_t *)&a, buf); +} + +/* + * []---- + * | send_async_logout -- request logout from initiator + * []---- + */ +static void +send_async_logout(iscsi_conn_t *c) +{ + iscsi_async_evt_hdr_t a; + + bzero(&a, sizeof (a)); + + a.opcode = ISCSI_OP_ASYNC_EVENT; + a.flags = ISCSI_FLAG_FINAL; + a.async_event = ISCSI_ASYNC_EVENT_REQUEST_LOGOUT; + (void) pthread_mutex_lock(&c->c_mutex); + a.statsn = htonl(c->c_statsn++); + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + a.expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + a.maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn); + a.param3 = htons(ASYNC_LOGOUT_TIMEOUT); + a.rsvd4[0] = 0xff; + a.rsvd4[1] = 0xff; /* According to the spec these four */ + a.rsvd4[2] = 0xff; /* values must be 0xff */ + a.rsvd4[3] = 0xff; + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x Sending async logout request", c->c_num); + + send_iscsi_pkt(c, (iscsi_hdr_t *)&a, 0); +} + +/* + * []---- + * | queue_noop_in -- generate a NOP request and queue it to be sent. + * []---- + */ +static void +queue_noop_in(iscsi_conn_t *c) +{ + iscsi_nop_in_hdr_t *in; + iscsi_cmd_t *cmd = iscsi_cmd_alloc(c, ISCSI_OP_NOOP_IN); + + if (cmd == NULL) + return; + + in = (iscsi_nop_in_hdr_t *)calloc(sizeof (*in), 1); + if (in == NULL) + return; + + in->opcode = ISCSI_OP_NOOP_IN | ISCSI_OP_IMMEDIATE; + in->flags = ISCSI_FLAG_FINAL; + in->ttt = cmd->c_ttt; + in->itt = ISCSI_RSVD_TASK_TAG; + + iscsi_cmd_free(c, cmd); + queue_message_set(c->c_dataq, 0, msg_send_pkt, (void *)in); +} + +void +iscsi_capacity_change(char *targ_name, int lun) +{ + iscsi_conn_t *conn; + extern pthread_mutex_t port_mutex; + + /* + * SBC-2 revision 16, section 4.6 -- Initialization + * Any time the parameter data returned by the READ CAPACITY(10) + * (see 5.10) or the READ CAPACITY(16) command (see 5.11) changes, + * the device server should establish a unit attention condition for + * the initiator port associated with each I_T nexus. + * Since the transport knows which initiators are currently accessing + * the target the message will be sent from here. + */ + (void) pthread_mutex_lock(&port_mutex); + for (conn = conn_head; conn; conn = conn->c_next) { + (void) pthread_mutex_lock(&conn->c_state_mutex); + if ((conn->c_state == S5_LOGGED_IN) && + (conn->c_sess->s_type == SessionNormal) && + (strcmp(conn->c_sess->s_t_name, targ_name) == 0)) { + + queue_message_set(conn->c_sessq, 0, + msg_lu_capacity_change, (void *)(uintptr_t)lun); + } + (void) pthread_mutex_unlock(&conn->c_state_mutex); + } + (void) pthread_mutex_unlock(&port_mutex); +} + +/* + * []---- + * | iscsi_inventory_change -- Send notice to initiator that something changed. + * []---- + */ +void +iscsi_inventory_change(char *targ_name) +{ + iscsi_conn_t *c; + extern pthread_mutex_t port_mutex; + + /* + * SPC-3 revision 21c, Section 6.21 REPORT_LUNS + * If the logical unit inventory changes for any reason + * (e.g. completion of initialization, removal of a logical unit, + * or create of a logical unit), then the device server shall generate + * a unit attention condition for all I_T nexuses, with the additional + * sense code set to REPORTED LUNS DATA HAS CHANGED. + */ + (void) pthread_mutex_lock(&port_mutex); + for (c = conn_head; c; c = c->c_next) { + (void) pthread_mutex_lock(&c->c_state_mutex); + if ((c->c_state == S5_LOGGED_IN) && + (c->c_sess->s_type == SessionNormal) && + (strcmp(c->c_sess->s_t_name, targ_name) == 0)) { + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x Sending Inventory change out", c->c_num); + /* + * Send a message indicating that the logical unit + * inventory has changed. 1) This message is sent + * to the session level which will pass it onto + * the SAM layer causing a UNIT_ATTENTION during + * the next command. 2) This message is also sent + * directly to the outgoing side of the connection + * which will send an asynchronous event message + * to the initiator. + */ + queue_message_set(c->c_sessq, 0, + msg_targ_inventory_change, 0); + queue_message_set(c->c_dataq, 0, + msg_targ_inventory_change, 0); + } + (void) pthread_mutex_unlock(&c->c_state_mutex); + } + (void) pthread_mutex_unlock(&port_mutex); +} + +/* + * []---- + * | state_to_str -- return string for given state, used for debug + * []---- + */ +static char * +state_to_str(iscsi_state_t s) +{ + switch (s) { + case S1_FREE: return ("FREE"); + case S3_XPT_UP: return ("XPT_UP"); + case S4_IN_LOGIN: return ("IN_LOGIN"); + case S5_LOGGED_IN: return ("LOGGED_IN"); + case S6_IN_LOGOUT: return ("IN_LOGOUT"); + case S7_LOGOUT_REQUESTED: return ("LOGOUT_REQUEST"); + case S8_CLEANUP_WAIT: return ("CLEANUP_WAIT"); + } + return ("Unknown"); +} + +/* + * []---- + * | event_to_str -- return string for given event, used for debug + * []---- + */ +static char * +event_to_str(iscsi_transition_t t) +{ + switch (t) { + case T3: return ("T3"); + case T4: return ("T4"); + case T5: return ("T5"); + case T6: return ("T6"); + case T7: return ("T7"); + case T8: return ("T8"); + case T9: return ("T9"); + case T10: return ("T10"); + case T11: return ("T11"); + case T12: return ("T12"); + case T13: return ("T13"); + case T15: return ("T15"); + case T16: return ("T16"); + case T17: return ("T17"); + case T18: return ("T18"); + } + return ("Unknown"); +} + +/* + * []---- + * | conn_state -- Attempt to change from one state to the next + * []---- + */ +void +conn_state(iscsi_conn_t *c, iscsi_transition_t t) +{ + iscsi_state_t old_state = c->c_state; + + (void) pthread_mutex_lock(&c->c_state_mutex); + switch (c->c_state) { + case S1_FREE: + switch (t) { + case T3: + c->c_state = S3_XPT_UP; + break; + + } + break; + + case S3_XPT_UP: + switch (t) { + case T6: + c->c_state = S1_FREE; + break; + + case T4: + c->c_statsn = 1; + c->c_state = S4_IN_LOGIN; + break; + + } + break; + + case S4_IN_LOGIN: + switch (t) { + case T7: + /* + * When there's a session a shutdown messages is + * sent giving the opportunity to free resources + * used by the session code and STE. Very early + * on a session might not exist when a failure + * occurs like getting a bad opcode. The connection + * process routine is going to sit around waiting + * for a message which will never come so fake + * a completion message here if there's no session. + */ + if (c->c_sessq == NULL) { + queue_message_set(c->c_dataq, 0, + msg_shutdown_rsp, (void *)True); + } else { + queue_message_set(c->c_sessq, 0, msg_shutdown, + (void *)c); + } + c->c_state = S1_FREE; + break; + case T5: + c->c_state = S5_LOGGED_IN; + if (strncmp(c->c_sess->s_i_name, + "iqn.1991-05.com.microsoft", + strlen("iqn.1991-05.com.microsoft")) == 0) + c->c_sess->s_cmdsn++; + break; + } + break; + + case S5_LOGGED_IN: + switch (t) { + case T8: + queue_message_set(c->c_sessq, 0, msg_shutdown, + (void *)c); + c->c_state = S1_FREE; + + break; + case T9: + queue_message_set(c->c_sessq, 0, msg_shutdown, + (void *)c); + c->c_state = S6_IN_LOGOUT; + break; + case T11: + c->c_state = S7_LOGOUT_REQUESTED; + send_async_logout(c); + break; + case T15: + c->c_state = S8_CLEANUP_WAIT; + break; + } + break; + + case S6_IN_LOGOUT: + switch (t) { + case T13: + if (c->c_last_pkg) { + send_iscsi_pkt(c, c->c_last_pkg, NULL); + free(c->c_last_pkg); + } + c->c_state = S1_FREE; + break; + case T17: + c->c_state = S8_CLEANUP_WAIT; + break; + } + break; + + case S7_LOGOUT_REQUESTED: + switch (t) { + case T18: + queue_message_set(c->c_sessq, 0, msg_shutdown, + (void *)c); + c->c_state = S1_FREE; + break; + case T10: + queue_message_set(c->c_sessq, 0, msg_shutdown, + (void *)c); + c->c_state = S6_IN_LOGOUT; + break; + case T12: + c->c_state = S7_LOGOUT_REQUESTED; + break; + case T16: + c->c_state = S8_CLEANUP_WAIT; + break; + } + break; + + case S8_CLEANUP_WAIT: + default: + break; + } + queue_prt(c->c_mgmtq, Q_CONN_NONIO, "CON%x ---- %s(%s) -> %s", + c->c_num, state_to_str(old_state), event_to_str(t), + state_to_str(c->c_state)); + (void) pthread_mutex_unlock(&c->c_state_mutex); +} + +/* + * []---- + * | send_iscsi_pkt -- output PDU header, data, and alignment bytes if needed + * | + * | NOTE: This routine may be called with the connection mutex held. This + * | is done to prevent a state change being made to a command pointer. This + * | routine is currently written so that it doesn't need to have this mutex + * | held or calls a routine which needs it to be held. + * []---- + */ +void +send_iscsi_pkt(iscsi_conn_t *c, iscsi_hdr_t *h, char *opt_text) +{ + int dlen = ntoh24(h->dlength), + pad_len; + uint32_t crc; + + /* + * Sanity check. If there's a length in the header we must + * have text to send or if the length is zero there better not + * be any text. + */ + if (((dlen == 0) && (opt_text != NULL)) || + ((dlen != 0) && (opt_text == NULL))) + return; + + if (write(c->c_fd, h, sizeof (*h)) < 0) { + if (errno == EPIPE) { + + /* + * For some reason the initiator has closed the + * socket on us. This is most likely caused because + * of some network related condition + * (e.g. broken cable). We'll shutdown our side and + * wait for a reconnect from the initiator. + */ + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x iscsi_pkt -- initiator closed socket", + c->c_num); + } else { + + /* + * This is not good. + */ + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x iscsi_pkt write failed, errno %d", + c->c_num, errno); + } + conn_state(c, T8); + return; + } + + /* + * Only start generating digest values once we've completed the + * login phase. If the state is not checked here and during login + * header or data digests have been enabled we would generate + * a digest value during the Login RSP PDU which the initiator + * is not expecting. + */ + if ((c->c_state == S5_LOGGED_IN) && (c->c_header_digest == True)) { + crc = iscsi_crc32c((void *)h, sizeof (*h)); + if (write(c->c_fd, &crc, sizeof (crc)) != sizeof (crc)) { + conn_state(c, T8); + return; + } + } + + if (dlen) { + if (write(c->c_fd, opt_text, dlen) != dlen) { + conn_state(c, T8); + return; + } + + /* + * Find out how many pad bytes we need to send out. + */ + pad_len = (ISCSI_PAD_WORD_LEN - + (dlen & (ISCSI_PAD_WORD_LEN - 1))) & + (ISCSI_PAD_WORD_LEN - 1); + if (pad_len) { + if (write(c->c_fd, pad_text, pad_len) != pad_len) { + conn_state(c, T8); + return; + } + } + + if ((c->c_state == S5_LOGGED_IN) && + (c->c_data_digest == True)) { + + crc = iscsi_crc32c((void *)opt_text, + (unsigned long)dlen); + + /* + * Include the pad information in the calculation of + * the CRC for the data. + */ + crc = iscsi_crc32c_continued((void *)pad_text, + (unsigned long)pad_len, crc); + + if (write(c->c_fd, &crc, sizeof (crc)) != + sizeof (crc)) { + conn_state(c, T8); + return; + } + } + } +#ifdef FULL_DEBUG + if (dlen != 0) { + queue_prt(c->c_mgmtq, Q_CONN_IO, + "CON%x Response(0x%x), Data: len=%d addr=0x%llx", + c->c_num, h->opcode, dlen, opt_text); + } +#endif +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_conn.h b/usr/src/cmd/iscsi/iscsitgtd/iscsi_conn.h new file mode 100644 index 0000000000..851757200d --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_conn.h @@ -0,0 +1,216 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _TARGET_CONN_H +#define _TARGET_CONN_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/iscsi_protocol.h> +#include <sys/socket.h> +#include "queue.h" +#include "iscsi_sess.h" +#include "iscsi_cmd.h" + +#define LBUFSIZE 80 + +#define TARGET_LOCATION "targets" +/* + * Currently I'm having some problems with network reads/write when the + * data size is larger than 8k. To work around this problem I set the + * various negotiation parameters during login to limit things to 8k. + */ + +#define NETWORK_SNDRCV 65536 +#define NETWORK_SNDRCV_STR "65536" + +typedef enum iscsi_state { + S1_FREE, + /* S2_XPT_WAIT, Not possible for target */ + S3_XPT_UP, + S4_IN_LOGIN, + S5_LOGGED_IN, + S6_IN_LOGOUT, + S7_LOGOUT_REQUESTED, + S8_CLEANUP_WAIT +} iscsi_state_t; + +typedef enum iscsi_transition { + T3, T4, T5, T6, T7, T8, + T9, T10, T11, T12, T13, T15, + T16, T17, T18 +} iscsi_transition_t; + +/* + * When grabbing mutex's make sure to grab c_mutex before c_mutex_state + * if you need to grab both. + */ +typedef struct iscsi_conn { + int c_fd; + + /* + * This is a linked list of all connections. Not just the connections + * associated with a particular session. + */ + struct iscsi_conn *c_next, + *c_prev; + + target_queue_t *c_mgmtq; + + /* + * Time as reported by time(2) when this connection was started. + */ + time_t c_up_at; + + /* + * This queue is used to accept notification that incoming packets + * are available and command completion status from Session. + */ + target_queue_t *c_dataq; + + /* + * Messages are sent to Session, and from there onto STE, using + * this queue. + */ + target_queue_t *c_sessq; + + iscsi_sess_t *c_sess; + + pthread_mutex_t c_state_mutex; + iscsi_state_t c_state; + + /* + * Protected by c_mutex + */ + int c_statsn; + + int c_cid; + + /* + * Pointer to data buffer used to store text messages which have + * the 'C' bit set. Since the text data separates name/value pairs + * with a '\0' strlen can't be used to determine the amount of space + * used so we keep the length in c_text_len; + */ + char *c_text_area; + int c_text_len; + int c_text_sent; + + sema_t c_datain; + pthread_t c_thr_id_poller, + c_thr_id_process; + + pthread_mutex_t c_mutex; + iscsi_cmd_t *c_cmd_head; + iscsi_cmd_t *c_cmd_tail; + + struct sockaddr_storage c_initiator_sockaddr; + struct sockaddr_storage c_target_sockaddr; + + int c_num; + + int c_auth_pass : 1; + + int c_cmds_active; + + /* + * A performance issue has been found when the backing store + * is UFS. Because of the indirect blocks used by UFS large files + * (many GBs in size) perform poorly. This in turn can cause the + * initiator to issue commands which don't complete in time. So, + * we'll monitor the completion times for commands if if it's + * increasing the command window will be reduced. + * The avg_sum is in nanoseconds. This will wrap once every 584 + * years. + */ + uint64_t c_cmds_avg_cnt; + hrtime_t c_cmds_avg_sum; + + /* + * During an orderly shutdown the logout response is created when + * we receive the logout request. We must however wait for all I/O + * to complete before processing the data else we'll loose data + * which the initiator believes was successfully transferred. + * Once the STE and sessions have closed they will send a shutdown + * complete message. At that point the transmit side of the connection + * will set the state to T13 which pushes this message out. + * Unfortunately we need information from the Logout Request PDU + * to create the Logout Response PDU. Otherwise the response could + * be generated on the fly in the T13 state handler. By creating + * the response PDU and saving a pointer gives us some flexibility + * in the future if the final outgoing packet needs to change. + * Otherwise, storing that one bit of information from the request + * PDU might become dated. + */ + iscsi_hdr_t *c_last_pkg; + + /* + * Connection parameters + */ + Boolean_t c_header_digest, + c_data_digest, + c_ifmarker, + c_ofmarker, + c_initialR2T, + c_immediate_data, + c_data_pdu_in_order, + c_data_sequence_in_order; + int c_tpgt, + c_maxcmdsn, + c_max_recv_data, + c_default_time_2_wait, + c_default_time_2_retain, + c_erl, + c_max_burst_len, + c_first_burst_len, + c_max_outstanding_r2t, + c_max_connections; + char *c_targ_alias, + *auth_text; + int auth_text_length; + +} iscsi_conn_t; + +void *conn_process(void *v); +void conn_state(iscsi_conn_t *c, iscsi_transition_t t); +void send_iscsi_pkt(iscsi_conn_t *c, iscsi_hdr_t *h, char *opt_text); +int read_retry(int fd, char *buf, int count); +void iscsi_inventory_change(char *targ_name); +void iscsi_capacity_change(char *targ_name, int lun); + +#ifdef __cplusplus +} +#endif + +#endif /* _TARGET_CONN_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_crc.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_crc.c new file mode 100644 index 0000000000..417839d918 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_crc.c @@ -0,0 +1,178 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2000 by Cisco Systems, Inc. All rights reserved. + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * This file contains CRC-32C code use to verify + * iSCSI HeaderDigests and DataDigests. + */ +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> /* standard types */ +#include <sys/iscsi_protocol.h> /* contains prototypes */ + +/* + * This is the CRC-32C table + * Generated with: + * width = 32 bits + * poly = 0x1EDC6F41 + * reflect input bytes = true + * reflect output bytes = true + */ + +uint32_t iscsi_crc32c_table[256] = +{ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, + 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, + 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, + 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, + 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, + 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, + 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, + 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, + 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, + 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, + 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, + 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, + 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, + 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, + 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, + 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, + 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, + 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, + 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, + 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, + 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, + 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 +}; + +/* + * iscsi_crc32c - Steps through buffer one byte at at time, calculates + * reflected crc using table. + */ +uint32_t +iscsi_crc32c(void *address, unsigned long length) +{ + uint8_t *buffer = address; + uint32_t crc = 0xffffffff, result; +#ifdef _BIG_ENDIAN + uint8_t byte0, byte1, byte2, byte3; +#endif + + while (length--) { + crc = iscsi_crc32c_table[(crc ^ *buffer++) & 0xFFL] ^ + (crc >> 8); + } + result = crc ^ 0xffffffff; + +#ifdef _BIG_ENDIAN + byte0 = (uint8_t)(result & 0xFF); + byte1 = (uint8_t)((result >> 8) & 0xFF); + byte2 = (uint8_t)((result >> 16) & 0xFF); + byte3 = (uint8_t)((result >> 24) & 0xFF); + result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); +#endif /* _BIG_ENDIAN */ + + return (result); +} + + +/* + * iscsi_crc32c_continued - Continues stepping through buffer one + * byte at at time, calculates reflected crc using table. + */ +uint32_t +iscsi_crc32c_continued(void *address, unsigned long length, uint32_t crc) +{ + uint8_t *buffer = address; + uint32_t result; +#ifdef _BIG_ENDIAN + uint8_t byte0, byte1, byte2, byte3; +#endif + +#ifdef _BIG_ENDIAN + byte0 = (uint8_t)((crc >> 24) & 0xFF); + byte1 = (uint8_t)((crc >> 16) & 0xFF); + byte2 = (uint8_t)((crc >> 8) & 0xFF); + byte3 = (uint8_t)(crc & 0xFF); + crc = ((byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0); +#endif + + crc = crc ^ 0xffffffff; + while (length--) { + crc = iscsi_crc32c_table[(crc ^ *buffer++) & 0xFFL] ^ + (crc >> 8); + } + result = crc ^ 0xffffffff; + +#ifdef _BIG_ENDIAN + byte0 = (uint8_t)(result & 0xFF); + byte1 = (uint8_t)((result >> 8) & 0xFF); + byte2 = (uint8_t)((result >> 16) & 0xFF); + byte3 = (uint8_t)((result >> 24) & 0xFF); + result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); +#endif + return (result); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_ffp.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_ffp.c new file mode 100644 index 0000000000..3eaaea7fde --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_ffp.c @@ -0,0 +1,953 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This file contains methods to handle the iSCSI Full Feature Phase aspects + * of the protocol. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <unistd.h> +#include <poll.h> +#include <strings.h> +#include <stdlib.h> +#include <sys/types.h> +#include <assert.h> +#include <stdio.h> +#include <errno.h> +#include <utility.h> +#include <netinet/in.h> +#include <inttypes.h> +#include <sys/iscsi_protocol.h> + +#include "iscsi_ffp.h" +#include "iscsi_cmd.h" +#include "t10_spc.h" +#include "utility.h" + +static Boolean_t handle_text_msg(iscsi_conn_t *, iscsi_hdr_t *, char *, int); +static Boolean_t handle_logout_msg(iscsi_conn_t *, iscsi_hdr_t *, char *, int); +static Boolean_t handle_scsi_cmd(iscsi_conn_t *, iscsi_hdr_t *, char *, int); +static Boolean_t handle_noop_cmd(iscsi_conn_t *, iscsi_hdr_t *, char *, int); +static Boolean_t handle_scsi_data(iscsi_conn_t *, iscsi_hdr_t *, char *, int); +static Boolean_t handle_task_mgt(iscsi_conn_t *, iscsi_hdr_t *, char *, int); +static Boolean_t dataout_delayed(iscsi_cmd_t *cmd, msg_type_t type); +void dataout_callback(t10_cmd_t *t, char *data, size_t *xfer); + +Boolean_t +iscsi_full_feature(iscsi_conn_t *c) +{ + iscsi_hdr_t h; + Boolean_t rval = False; + char debug[128], + *ahs = NULL; + int cc, + ahslen; + + if ((cc = read_retry(c->c_fd, (char *)&h, + sizeof (h))) != sizeof (h)) { + if (errno == ECONNRESET) { + (void) snprintf(debug, sizeof (debug), + "CON%x full_feature -- initiator reset socket", + c->c_num); + } else { + (void) snprintf(debug, sizeof (debug), + "CON%x full_feature(got-%d, expect-%d), errno=%d", + c->c_num, cc, sizeof (h), errno); + } + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T8); + return (False); + } + + /* + * Look to see if there's an Additional Header Segment available. + * If so, read it in. + */ + if ((ahslen = (h.hlength * sizeof (uint32_t))) != 0) { + if ((ahs = malloc(ahslen)) == NULL) + return (False); + if (read_retry(c->c_fd, ahs, ahslen) != ahslen) { + (void) snprintf(debug, sizeof (debug), + "CON%x Failed to read in AHS", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + return (False); + } + } + + if ((c->c_state == S5_LOGGED_IN) && (c->c_header_digest == True)) { + uint32_t crc_actual, + crc_calculated; + + (void) read_retry(c->c_fd, (char *)&crc_actual, + sizeof (crc_actual)); + crc_calculated = iscsi_crc32c((void *)&h, sizeof (h)); + if (ahslen) + crc_calculated = iscsi_crc32c_continued(ahs, + ahslen, crc_calculated); + if (crc_actual != crc_calculated) { + (void) snprintf(debug, sizeof (debug), + "CON%x CRC error: actual 0x%x v. calc 0x%x", + c->c_num, crc_actual, crc_calculated); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + return (False); + } + } + + if (c->c_sess->s_type == SessionDiscovery) { + switch (h.opcode & ISCSI_OPCODE_MASK) { + default: + /* + * Need to handle the error case here. + */ + (void) snprintf(debug, sizeof (debug), + "CON%x Wrong opcode for Discovery, %d", + c->c_num, h.opcode & ISCSI_OPCODE_MASK); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + rval = False; + break; + + case ISCSI_OP_LOGOUT_CMD: + /* + * This will transition from S5_LOGGED_IN + * to S6_IN_LOGOUT to S1_FREE; + */ + rval = handle_logout_msg(c, &h, ahs, ahslen); + break; + + case ISCSI_OP_TEXT_CMD: + rval = handle_text_msg(c, &h, ahs, ahslen); + break; + } + } else { + iscsi_cmd_remove(c, htonl(h.expstatsn)); + switch (h.opcode & ISCSI_OPCODE_MASK) { + case ISCSI_OP_NOOP_OUT: + rval = handle_noop_cmd(c, &h, ahs, ahslen); + break; + + case ISCSI_OP_SCSI_CMD: + rval = handle_scsi_cmd(c, &h, ahs, ahslen); + break; + + case ISCSI_OP_SCSI_TASK_MGT_MSG: + rval = handle_task_mgt(c, &h, ahs, ahslen); + break; + + case ISCSI_OP_LOGIN_CMD: + /* + * This is an illegal state transition. Should + * we drop the connection? + */ + break; + + case ISCSI_OP_TEXT_CMD: + rval = handle_text_msg(c, &h, ahs, ahslen); + break; + + case ISCSI_OP_SCSI_DATA: + rval = handle_scsi_data(c, &h, ahs, ahslen); + break; + + case ISCSI_OP_LOGOUT_CMD: + /* + * This will transition from S5_LOGGED_IN + * to S6_IN_LOGOUT. + */ + rval = handle_logout_msg(c, &h, ahs, ahslen); + break; + + case ISCSI_OP_SNACK_CMD: + default: + (void) snprintf(debug, sizeof (debug), + "CON%x Opcode: %d not handled", + c->c_num, h.opcode & ISCSI_OPCODE_MASK); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T8); + rval = True; + break; + } + } + + if (ahs != NULL) + free(ahs); + return (rval); +} + +/*ARGSUSED*/ +static Boolean_t +handle_task_mgt(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen) +{ + iscsi_scsi_task_mgt_hdr_t *hp = (iscsi_scsi_task_mgt_hdr_t *)p; + iscsi_scsi_task_mgt_rsp_hdr_t *rsp; + iscsi_cmd_t *cmd; + uint32_t lun; + Boolean_t lu_reset = False; + + rsp = (iscsi_scsi_task_mgt_rsp_hdr_t *)calloc(sizeof (*rsp), 1); + if (rsp == NULL) + return (False); + + if (spc_decode_lu_addr(&hp->lun[0], 8, &lun) == False) + return (False); + + rsp->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP; + rsp->flags = ISCSI_FLAG_FINAL; + rsp->itt = hp->itt; + + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn) + c->c_sess->s_seencmdsn = ntohl(hp->cmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x PDU(Task Mgt): %s, cmdsn 0x%x", + c->c_num, + task_to_str(hp->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK), + ntohl(hp->cmdsn)); + + switch (hp->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) { + case ISCSI_TM_FUNC_ABORT_TASK: + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x Abort ITT 0x%x", c->c_num, hp->rtt); + if ((cmd = iscsi_cmd_find(c, hp->rtt, FindITT)) == NULL) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x Invalid AbortTask rtt 0x%x\n", + c->c_num, hp->rtt); + rsp->response = SCSI_TCP_TM_RESP_NO_TASK; + } else { + iscsi_cmd_cancel(c, cmd); + rsp->response = SCSI_TCP_TM_RESP_COMPLETE; + } + break; + + case ISCSI_TM_FUNC_ABORT_TASK_SET: + /* ---- This is actually "Function not support" ---- */ + rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS; + break; + + case ISCSI_TM_FUNC_CLEAR_ACA: + /* ---- This is actually "Function not support" ---- */ + rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS; + break; + + case ISCSI_TM_FUNC_CLEAR_TASK_SET: + /* ---- This is actually "Function not support" ---- */ + rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS; + break; + + case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: + lu_reset = True; + /*FALLTHRU*/ + case ISCSI_TM_FUNC_TARGET_WARM_RESET: + (void) pthread_mutex_lock(&c->c_mutex); + for (cmd = c->c_cmd_head; cmd; cmd = cmd->c_next) { + if (((hp->function & + ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) == + ISCSI_TM_FUNC_TARGET_WARM_RESET) || + (lun == cmd->c_lun)) { + + /* + * Can't call cmd_cancel() here because it + * will attempt to grab the lock which we + * already have held. + */ + if (cmd->c_state == CmdAlloc) { + cmd->c_state = CmdCanceled; + + /* + * It's possible that the session + * queue hasn't had a chance to run + * yet and get a T10 command structure. + */ + if (cmd->c_t10_cmd != NULL) { + t10_cmd_state(cmd->c_t10_cmd, + T10_Cmd_Event_Canceled); + } + } + + } + } + (void) pthread_mutex_unlock(&c->c_mutex); + + if (lu_reset == True) + queue_message_set(c->c_sessq, 0, msg_reset_lu, + (void *)(uintptr_t)lun); + else + queue_message_set(c->c_sessq, 0, msg_reset_targ, 0); + rsp->response = SCSI_TCP_TM_RESP_COMPLETE; + break; + + case ISCSI_TM_FUNC_TARGET_COLD_RESET: + /* + * According to the specification a cold reset should + * close *all* connections on the target, not just those + * for this current session. + */ + queue_message_set(c->c_sessq, 0, msg_reset_targ, (void *)1); + conn_state(c, T8); + break; + + case ISCSI_TM_FUNC_TASK_REASSIGN: + default: + /* ---- This is actually "Function not support" ---- */ + rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS; + break; + } + + (void) pthread_mutex_lock(&c->c_state_mutex); + if (c->c_state == S5_LOGGED_IN) + queue_message_set(c->c_dataq, + hp->opcode & ISCSI_OP_IMMEDIATE ? Q_HIGH : 0, + msg_send_pkt, rsp); + (void) pthread_mutex_unlock(&c->c_state_mutex); + return (True); +} + +/*ARGSUSED*/ +static Boolean_t +handle_noop_cmd(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen) +{ + iscsi_nop_out_hdr_t *hp = (iscsi_nop_out_hdr_t *)p; + iscsi_nop_in_hdr_t *in; + + in = (iscsi_nop_in_hdr_t *)calloc(sizeof (*in), 1); + if (in == NULL) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x NopIn -- failed to malloc space for header", + c->c_num); + return (False); + } + + /* + * Just an answer to our ping + */ + if (hp->ttt != ISCSI_RSVD_TASK_TAG) { + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x NopIn -- answer to our call", c->c_num); + return (True); + } + + in->opcode = ISCSI_OP_NOOP_IN; + in->flags = ISCSI_FLAG_FINAL; + /* + * Need to handle possible data associated with NOP-Out + */ + bcopy(hp->lun, in->lun, 8); + in->itt = hp->itt; + in->ttt = ISCSI_RSVD_TASK_TAG; + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn) + c->c_sess->s_seencmdsn = ntohl(hp->cmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + + (void) pthread_mutex_lock(&c->c_state_mutex); + if (c->c_state == S5_LOGGED_IN) + queue_message_set(c->c_dataq, + hp->opcode & ISCSI_OP_IMMEDIATE ? Q_HIGH : 0, + msg_send_pkt, in); + (void) pthread_mutex_unlock(&c->c_state_mutex); + return (True); +} + +/*ARGSUSED*/ +static Boolean_t +handle_scsi_data(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen) +{ + iscsi_data_hdr_t *hp = (iscsi_data_hdr_t *)p; + int dlen = ntoh24(hp->dlength); + iscsi_cmd_t *cmd; + + if ((cmd = iscsi_cmd_find(c, hp->ttt, FindTTT)) == NULL) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x failed to find ttt 0x%x", c->c_num, hp->ttt); + /* + * Need to handle error case. + */ + return (False); + } + cmd->c_opcode = hp->opcode & ISCSI_OPCODE_MASK; + + /* + * assert(cmd->c_lun == hp->lun[1]); + * Previously this check was done, but is caused a problem with + * the RedHat initiator. There was a discussion on the IPS alias + * around this very topic. Even though section 10.7.4 states: + * "If the Target Transfer Tag is provided, then the LUN field + * MUST hold a valid value and be consistent with whatever was + * specified with the command; otherwise, the LUN field is + * reserved." + * Everyone agreed though that for a DataOut command the LUN field + * wasn't required to be valid because the TTT gives the Target + * enough information to complete the command. + */ + assert(cmd->c_allegiance == c); + assert(cmd->c_itt == hp->itt); + + cmd->c_offset_out = ntohl(hp->offset); + cmd->c_data_len = dlen; + (void) pthread_mutex_lock(&c->c_mutex); + (void) pthread_mutex_lock(&c->c_state_mutex); + if (c->c_state == S5_LOGGED_IN) { + if (cmd->c_state != CmdCanceled) { + t10_cmd_state(cmd->c_t10_cmd, + T10_Cmd_Event_DataIn_Recv); + } + } + (void) pthread_mutex_unlock(&c->c_state_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + +#ifdef FULL_DEBUG + queue_prt(c->c_mgmtq, Q_CONN_IO, + "CON%x PDU(DataOut) TTT 0x%x, offset=0x%x, len=0x%x", + c->c_num, cmd->c_ttt, cmd->c_t10_cmd->c_offset, dlen); +#endif + + return (dataout_delayed(cmd, msg_cmd_data_out)); +} + +static Boolean_t +handle_scsi_cmd(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen) +{ + iscsi_scsi_cmd_hdr_t *hp = (iscsi_scsi_cmd_hdr_t *)p; + int dlen = ntoh24(hp->dlength); + iscsi_cmd_t *cmd; + + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn) + c->c_sess->s_seencmdsn = ntohl(hp->cmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + + if ((cmd = iscsi_cmd_alloc(c, hp->opcode & ISCSI_OPCODE_MASK)) == NULL) + return (False); + + bcopy(hp->scb, cmd->c_scb_default, sizeof (cmd->c_scb_default)); + cmd->c_scb = cmd->c_scb_default; + cmd->c_scb_len = sizeof (cmd->c_scb_default); + cmd->c_data_len = dlen; + + if (ahslen) { + + /* + * Additional Header Section ---- + * + * For Object Storage Devices the SCB is quite large. On + * the order of 140 bytes which means the data must be + * found in the AHS. + */ + uint16_t hslen, + next_seg; + uint8_t hstyp; + + do { + /* + * Find this header segment's length and type + */ + bcopy(ahs, &hslen, sizeof (hslen)); + hslen = ntohs(hslen); + hstyp = ahs[2]; + + switch (hstyp) { + /* ---- Extended CDB ---- */ + case 1: + /* + * The hslen accounts for the reserved + * data byte in the segment. So the first + * sixteen bytes are in hp->scb with the + * remainder here. By only adding 15 bytes + * we allocate the correct amount of space + */ + cmd->c_scb_extended = malloc(hslen + 15); + cmd->c_scb_len = hslen + 15; + if (cmd->c_scb_extended == NULL) + return (False); + + /* + * First 16 bytes of extended SCB are + * found in the normal location. + */ + bcopy(hp->scb, cmd->c_scb_extended, 16); + bcopy(&ahs[4], &cmd->c_scb_extended[16], + hslen - 16); + cmd->c_scb = cmd->c_scb_extended; + break; + + /* ---- Expected bidirectional read data len ---- */ + case 2: + /* + * We shouldn't need this since we're + * not prealloc'ing resources. If that should + * change or the need for error checking + * here's the spot to locate the data. + */ + break; + } + + /* + * hslen contains the effective length in bytes of + * segment, excluding type and length (not including + * padding). Each segment is padded to a 4 byte + * boundary. + */ + next_seg = ((hslen + sizeof (hslen) + + sizeof (hstyp) + 3) & ~3); + ahs += next_seg; + ahslen -= next_seg; + + } while (ahslen); + } + + /* + * XXX Need to handle error case better. + */ + if (spc_decode_lu_addr(&hp->lun[0], sizeof (hp->lun), &cmd->c_lun) == + False) { + return (False); + } + + cmd->c_itt = hp->itt; + cmd->c_cmdsn = ntohl(hp->cmdsn); + cmd->c_writeop = hp->flags & ISCSI_FLAG_CMD_WRITE ? + True : False; + +#ifdef FULL_DEBUG + queue_prt(c->c_mgmtq, Q_CONN_IO, + "CON%x PDU(SCSI Cmd) CmdSN 0x%x ITT 0x%x TTT 0x%x LUN[%02x] " + "SCSI Op", c->c_num, cmd->c_cmdsn, cmd->c_itt, cmd->c_ttt, + cmd->c_lun); +#endif + if (dlen && (hp->flags & ISCSI_FLAG_CMD_WRITE)) { + /* + * NOTE: This should only occur if ImmediateData==Yes. + * We can handle this even if the initiator violates + * the specification so no need to worry. Use the rule + * of "Be strict in what is sent, but lenient in what + * is accepted." + */ + return (dataout_delayed(cmd, msg_cmd_send)); + } else { + (void) pthread_mutex_lock(&c->c_state_mutex); + if (c->c_state == S5_LOGGED_IN) + queue_message_set(c->c_sessq, 0, + msg_cmd_send, (void *)cmd); + (void) pthread_mutex_unlock(&c->c_state_mutex); + return (True); + } +} + +/* + * []---- + * | handle_text_msg -- process incoming test parameters + * | + * | NOTE: Need to handle continuation packets sent by the initiator. + * []---- + */ +/*ARGSUSED*/ +static Boolean_t +handle_text_msg(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen) +{ + iscsi_text_rsp_hdr_t rsp; + iscsi_text_hdr_t *hp = (iscsi_text_hdr_t *)p; + char *text = NULL; + int text_length = 0; + Boolean_t release_at_end = False; + + bzero(&rsp, sizeof (rsp)); + rsp.opcode = ISCSI_OP_TEXT_RSP; + rsp.itt = hp->itt; + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, "CON%x PDU(Text Message)", + c->c_num); + + /* + * Need to determine if this incoming text PDU is an initial message + * or a continuation. + */ + if (hp->ttt == ISCSI_RSVD_TASK_TAG) { + + /* ---- Initial text PDU, so parse the incoming data ---- */ + if (parse_text(c, ntoh24(hp->dlength), &text, &text_length, + NULL) == False) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "Failed to parse Text"); + if (text) { + /* + * It's possible that we started to create + * a response, but yet an error occurred. + * Release the partial text response if that + * occurred. + */ + free(text); + } + return (False); + } + + /* + * 10.11.4 -- + * When the target receives a Text Request with the Target + * Transfer Tag set to the reserved value of 0xffffffff, it + * resets its internal information (resets state) associated + * with the given Initiator Task Tag (restarts the negotiation). + */ + if (c->c_text_area != NULL) + free(c->c_text_area); + + c->c_text_area = text; + if (text_length > c->c_max_recv_data) { + + /* + * Too much data to send at once, break it up into + * multiple transfers. + */ + rsp.flags = ISCSI_FLAG_TEXT_CONTINUE; + rsp.ttt = 1; + c->c_text_len = text_length; + text_length = c->c_max_recv_data; + c->c_text_sent = text_length; + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x Text PDU: %d PDUs required (len=%d)", + c->c_num, + (c->c_text_len + c->c_max_recv_data - 1) / + c->c_max_recv_data, c->c_text_len); + + } else { + rsp.flags = ISCSI_FLAG_FINAL; + rsp.ttt = ISCSI_RSVD_TASK_TAG; + release_at_end = True; + } + } else { + + /* ---- Continuation of previous text request ---- */ + text_length = c->c_text_len - c->c_text_sent; + text = c->c_text_area + c->c_text_sent; + if (text_length > c->c_max_recv_data) { + rsp.flags = ISCSI_FLAG_TEXT_CONTINUE; + rsp.ttt = 1; + text_length = c->c_max_recv_data; + c->c_text_sent += text_length; + } else { + rsp.flags = ISCSI_FLAG_FINAL; + rsp.ttt = ISCSI_RSVD_TASK_TAG; + release_at_end = True; + } + } + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, + "CON%x Text PDU: flags=0x%02x, ttt=0x%08x, len=%d", + c->c_num, rsp.flags, rsp.ttt, text_length); + + hton24(rsp.dlength, text_length); + (void) pthread_mutex_lock(&c->c_mutex); + rsp.statsn = htonl(c->c_statsn++); + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn) + c->c_sess->s_seencmdsn = ntohl(hp->cmdsn); + rsp.maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn); + rsp.expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + + send_iscsi_pkt(c, (iscsi_hdr_t *)&rsp, text); + + if (release_at_end == True) { + free(c->c_text_area); + c->c_text_area = NULL; + } + return (True); +} + +/*ARGSUSED*/ +static Boolean_t +handle_logout_msg(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen) +{ + iscsi_logout_rsp_hdr_t *rsp; + iscsi_logout_hdr_t *hp = (iscsi_logout_hdr_t *)p; + char debug[80]; + + rsp = (iscsi_logout_rsp_hdr_t *)calloc(sizeof (*rsp), 1); + if (rsp) { + + (void) snprintf(debug, sizeof (debug), + "CON%x PDU(Logout Request)", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_NONIO, msg_log, debug); + + (void) pthread_mutex_lock(&c->c_mutex); + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + if (hp->cmdsn > c->c_sess->s_seencmdsn) + c->c_sess->s_seencmdsn = hp->cmdsn; + rsp->expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + rsp->maxcmdsn = htonl(iscsi_cmd_window(c) + + c->c_sess->s_seencmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + + rsp->opcode = ISCSI_OP_LOGOUT_RSP; + rsp->flags = ISCSI_FLAG_FINAL; + rsp->itt = hp->itt; + (void) pthread_mutex_lock(&c->c_mutex); + rsp->statsn = htonl(c->c_statsn++); + (void) pthread_mutex_unlock(&c->c_mutex); + + c->c_last_pkg = (iscsi_hdr_t *)rsp; + + /* + * Call the state transition last. This will send out + * an asynchronous message to shutdown the session and STE. + * Once that's complete a shutdown reply will be sent to + * the transmit connection thread. That will cause another + * transition to T13 which expects to send out this logout + * response. + */ + if (c->c_state == S7_LOGOUT_REQUESTED) + conn_state(c, T10); + else + conn_state(c, T9); + + return (True); + } else + return (False); +} + +/* + * dataou_delayed -- possibly copy data from initiator + * + * If DataDigests are enabled copy the data from the socket into a buffer + * and perform the CRC check now. + * + * If MaxConnections==1 don't copy the data now and wait until the STE is + * ready to copy the data directly from the socket to it's final location. + * This is extremely beneficial when using mmap'd data. + * NOTE: + * (1) For this to work we must not use the queues and instead + * call the STE functions directly. If the queues are used + * this routine must pause until STE processes the data to + * prevent this thread from attempting to read data from + * the socket as if it's the next PDU header. + * (2) Currently we don't call STE directly. To prevent a performance + * issue we'll have the code in place to support calling + * STE directly, but any time MaxConnections is greater than 0 + * we'll copy the buffer. This will be removed at some future + * point. + */ +static Boolean_t +dataout_delayed(iscsi_cmd_t *cmd, msg_type_t type) +{ + iscsi_conn_t *c = cmd->c_allegiance; + int dlen = cmd->c_data_len, + cc; + uint32_t crc_calc, + crc_actual; + char pad_buf[ISCSI_PAD_WORD_LEN - 1]; + char pad_len; + char debug[80]; + + cmd->c_dataout_cb = dataout_callback; + + if (cmd->c_data == NULL) { + if ((cmd->c_data = (char *)malloc(dlen)) == NULL) { + (void) pthread_mutex_lock(&c->c_mutex); + iscsi_cmd_free(c, cmd); + (void) pthread_mutex_unlock(&c->c_mutex); + return (False); + } + cmd->c_data_alloc = True; + } + + if ((cc = read_retry(c->c_fd, cmd->c_data, dlen)) != dlen) { + if (errno == ECONNRESET) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x dataout_delayed -- " + "initiator reset socket", c->c_num); + } else { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x read_retry(got-%d, expect-%d), " + "errno=%d", c->c_num, cc, dlen, errno); + } + + (void) pthread_mutex_lock(&c->c_mutex); + iscsi_cmd_free(c, cmd); + (void) pthread_mutex_unlock(&c->c_mutex); + conn_state(c, T8); + return (True); + } + + pad_len = ((ISCSI_PAD_WORD_LEN - + (dlen & (ISCSI_PAD_WORD_LEN - 1))) & (ISCSI_PAD_WORD_LEN - 1)); + + if (pad_len) { + if (read_retry(c->c_fd, pad_buf, pad_len) != pad_len) { + if (errno == ECONNRESET) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x dataout_delayed -- " + "initiator reset socket", c->c_num); + } else { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x Pad Word read errno=%d", c->c_num, + errno); + } + + (void) pthread_mutex_lock(&c->c_mutex); + iscsi_cmd_free(c, cmd); + (void) pthread_mutex_unlock(&c->c_mutex); + conn_state(c, T8); + return (True); + } + } + + if (c->c_data_digest == True) { + if (read_retry(c->c_fd, (char *)&crc_actual, + sizeof (crc_actual)) != sizeof (crc_actual)) { + if (errno == ECONNRESET) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x dataout_delayed -- " + "initiator reset socket", c->c_num); + } else { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x CRC32 read errno=%d", c->c_num, + errno); + } + + (void) pthread_mutex_lock(&c->c_mutex); + iscsi_cmd_free(c, cmd); + (void) pthread_mutex_unlock(&c->c_mutex); + conn_state(c, T8); + return (True); + } + crc_calc = iscsi_crc32c((void *)cmd->c_data, dlen); + if (crc_calc != crc_actual) { + + (void) snprintf(debug, sizeof (debug), + "CON%x CRC Error: actual %x vs. calc 0x%x", + c->c_num, crc_actual, crc_calc); + + /* + * NOTE: Need to think about this one some more. + * Just because we get a data error doesn't mean + * we should drop the connection. Look at the + * spec and determine what's the appropriate + * error recovery for this issue. + */ + (void) pthread_mutex_lock(&c->c_mutex); + iscsi_cmd_free(c, cmd); + (void) pthread_mutex_unlock(&c->c_mutex); + conn_state(c, T8); + return (True); + } + } + + (void) pthread_mutex_lock(&c->c_mutex); + (void) pthread_mutex_lock(&c->c_state_mutex); + if (c->c_state == S5_LOGGED_IN) { + if ((cmd->c_state == CmdCanceled) && + (type == msg_cmd_data_out)) { + t10_cmd_state(cmd->c_t10_cmd, T10_Cmd_Event_Release); + cmd->c_t10_cmd = NULL; + } else + queue_message_set(c->c_sessq, 0, type, (void *)cmd); + } else if (cmd->c_state == CmdCanceled) { + t10_cmd_state(cmd->c_t10_cmd, T10_Cmd_Event_Release); + cmd->c_t10_cmd = NULL; + } + (void) pthread_mutex_unlock(&c->c_state_mutex); + (void) pthread_mutex_unlock(&c->c_mutex); + + /* + * The else case here is if we're calling STE directly and the data + * will be read from the socket when STE is ready for it. + */ + + return (True); +} + +/* + * []---- + * | dataout_callback -- copy data from socket to emulation buffer + * []---- + */ +void +dataout_callback(t10_cmd_t *t, char *data, size_t *xfer) +{ + iscsi_cmd_t *cmd = (iscsi_cmd_t *)T10_TRANS_ID(t); + iscsi_conn_t *c = cmd->c_allegiance; + int dlen = cmd->c_data_len, + cc; + char pad_buf[ISCSI_PAD_WORD_LEN - 1]; + char pad_len = 0; + + pad_len = ((ISCSI_PAD_WORD_LEN - + (dlen & (ISCSI_PAD_WORD_LEN - 1))) & + (ISCSI_PAD_WORD_LEN - 1)); + + + if (T10_DATA(t) != NULL) { + assert(T10_DATA(t) == cmd->c_data); + assert(cmd->c_data_alloc == True); + free(T10_DATA(t)); + T10_DATA(t) = NULL; + cmd->c_data = NULL; + cmd->c_data_alloc = False; + return; + } + + if ((cc = read_retry(c->c_fd, data, dlen)) != dlen) { + if (errno == ECONNRESET) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x data_callback -- initiator reset socket", + c->c_num); + } else { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x read_retry(got-%d, expect-%d) errno=%d", + c->c_num, cc, dlen, errno); + } + + conn_state(c, T8); + goto finish; + } + + if (pad_len) { + if (read_retry(c->c_fd, pad_buf, pad_len) + != pad_len) { + if (errno == ECONNRESET) { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x data_callback -- " + "initiator reset socket", c->c_num); + } else { + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x data_callback -- " + "pad read errno=%d", c->c_num, errno); + } + conn_state(c, T8); + goto finish; + } + } + +finish: + *xfer = cc; + /* ---- Send msg that receive side of the connection can go ---- */ + (void) sema_post(&c->c_datain); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_ffp.h b/usr/src/cmd/iscsi/iscsitgtd/iscsi_ffp.h new file mode 100644 index 0000000000..ae84910a99 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_ffp.h @@ -0,0 +1,43 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _FEATURE_H +#define _FEATURE_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include "iscsi_conn.h" + +/* + * The number of seconds that an initiator has to respond to our + * asynchronous logout request before we just drop the connection. + */ +#define ASYNC_LOGOUT_TIMEOUT 10 + +Boolean_t iscsi_full_feature(iscsi_conn_t *c); + +#endif /* _FEATURE_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_login.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_login.c new file mode 100644 index 0000000000..7e7fd6a808 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_login.c @@ -0,0 +1,843 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <strings.h> +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/iscsi_protocol.h> + +#include "queue.h" +#include "iscsi_conn.h" +#include "iscsi_sess.h" +#include "iscsi_login.h" +#include "utility.h" +#include "xml.h" +#include "target.h" + +typedef enum auth_action { + LOGIN_NO_AUTH, + LOGIN_AUTH, + LOGIN_DROP +} auth_action_t; + +/* + * Forward declarations + */ +static iscsi_login_rsp_hdr_t *make_login_response(iscsi_conn_t *c, + iscsi_login_hdr_t *lhp); +static void send_login_reject(iscsi_conn_t *c, iscsi_login_hdr_t *lhp, + int err_code); +static Boolean_t check_for_valid_I_T(iscsi_conn_t *c); +static auth_action_t login_set_auth(iscsi_sess_t *s); + +/* + * iscsi_null_callback - This callback may be used under certain + * conditions when authenticating a target, but I'm not sure what + * we need to do here. + */ +/* ARGSUSED */ +static void +iscsi_null_callback(void *user_handle, void *message_handle, int auth_status) +{ +} + +/* + * iscsi_find_key_value - + */ +static int +iscsi_find_key_value(char *param, char *ihp, char *pdu_end, + char **value_start, char **value_end) +{ + char *str = param; + char *text = ihp; + char *value = NULL; + + if (value_start) + *value_start = NULL; + if (value_end) + *value_end = NULL; + + /* + * make sure they contain the same bytes + */ + while (*str) { + if (text >= pdu_end) { + return (0); + } + if (*text == '\0') { + return (0); + } + if (*str != *text) { + return (0); + } + str++; + text++; + } + + if ((text >= pdu_end) || + (*text == '\0') || + (*text != ISCSI_TEXT_SEPARATOR)) { + return (0); + } + + /* + * find the value + */ + value = text + 1; + + /* + * find the end of the value + */ + while ((text < pdu_end) && (*text)) + text++; + + if (value_start) + *value_start = value; + if (value_end) + *value_end = text; + + return (1); +} + +Boolean_t +iscsi_handle_login_pkt(iscsi_conn_t *c) +{ + iscsi_login_hdr_t lh; + iscsi_login_rsp_hdr_t *rsp = NULL; + Boolean_t rval = False; + IscsiAuthClient *auth_client = NULL; + char *text = NULL, + *end = NULL, + *text_rsp = NULL, + debug[128]; + int debug_status = 0, + errcode = 0, + text_length = 0, + keytype = 0, + transit = 0, + tpgt, + rc = 0; + auth_action_t auth_action = LOGIN_DROP; + xml_node_t *tnode = NULL; + + if (read(c->c_fd, &lh, sizeof (lh)) != sizeof (lh)) { + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, + "Header to small"); + return (False); + } + + if ((lh.opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN_CMD) { + (void) snprintf(debug, sizeof (debug), + "CON%x Wrong OP code for state (Got 0x%x, Expected 0x%x)", + c->c_num, lh.opcode, ISCSI_OP_LOGIN_CMD); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_INVALID_REQUEST); + conn_state(c, T7); + return (True); + } + + if ((rval = session_alloc(c, lh.isid)) == False) { + conn_state(c, T7); + return (True); + } + + connection_parameters_default(c); + + c->c_cid = ntohl(lh.cid); + + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + c->c_sess->s_cmdsn = ntohl(lh.cmdsn); + c->c_sess->s_seencmdsn = ntohl(lh.cmdsn); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + + /* + * Is this a new session or an attempt to add a connection to + * an existing session. + */ + if (ntohs(lh.tsid) != 0) { + + /* Multiple connections per session not handled right now */ + conn_state(c, T7); + return (True); + } + + if ((rsp = make_login_response(c, &lh)) == NULL) { + (void) snprintf(debug, sizeof (debug), + "CON%x Failed make_login_response", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + return (False); + } + /* default is ISCSI_FLAG_LOGIN_TRANSIT, not good for login */ + rsp->flags = 0; + + if ((rsp->active_version < lh.min_version) || + (rsp->active_version > lh.max_version)) { + (void) snprintf(debug, sizeof (debug), + "CON%x Version: Active %d, min %d, max %d", c->c_num, + rsp->active_version, lh.min_version, lh.max_version); + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_NO_VERSION); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T7); + return (True); + } + + if (lh.flags & ISCSI_FLAG_LOGIN_CONTINUE) { + (void) snprintf(debug, sizeof (debug), + "CON%x Continuation pkt", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_LOGIN, msg_log, debug); + } + + auth_client = + (c->c_sess->sess_auth.auth_buffers && + c->c_sess->sess_auth.num_auth_buffers) ? + (IscsiAuthClient *) c->c_sess->sess_auth.auth_buffers[0].address : + NULL; + + if (c->auth_text != NULL) + free(c->auth_text); + c->auth_text_length = 0; + + transit = lh.flags & ISCSI_FLAG_LOGIN_TRANSIT; + + switch (ISCSI_LOGIN_CURRENT_STAGE(lh.flags)) { + case ISCSI_SECURITY_NEGOTIATION_STAGE: + + /* + * Grab the parameters and create the response + * text. + */ + rval = parse_text(c, ntoh24(lh.dlength), + &text_rsp, &text_length, &errcode); + if (rval == False) { + send_login_reject(c, &lh, errcode); + (void) snprintf(debug, sizeof (debug), + "CON%x SecurityNegotiation: parse_text" + " failed", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T7); + break; + } + + if ((rval = check_for_valid_I_T(c)) == False) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_INIT_ERR); + (void) snprintf(debug, sizeof (debug), + "CON%x SecurityNegotiation: invalid I " + "or T", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T7); + break; + } + + auth_action = login_set_auth(c->c_sess); + + if (auth_action == LOGIN_NO_AUTH) { + rsp->flags |= ISCSI_FLAG_LOGIN_TRANSIT; + rsp->flags |= ISCSI_OP_PARMS_NEGOTIATION_STAGE; + rval = add_text(&text_rsp, &text_length, "AuthMethod", + "None"); + if (rval == False) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_TARGET_ERR << 8) | + ISCSI_LOGIN_STATUS_TARGET_ERROR); + (void) snprintf(debug, sizeof (debug), + "CON%x Norm: Failed to add AuthMethod=None", + c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, + debug); + conn_state(c, T7); + } + break; + } + + if (auth_action == LOGIN_DROP) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_TGT_FORBIDDEN); + (void) snprintf(debug, sizeof (debug), + "CON%x SecurityNegotiation: access denied", + c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T7); + rval = False; + break; + } + + if (iscsiAuthClientRecvBegin(auth_client) != + iscsiAuthStatusNoError) { + (void) snprintf(debug, sizeof (debug), "CON%x " + "login failed - authentication receive failed", + c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + break; + } + + if (iscsiAuthClientRecvTransitBit(auth_client, + transit) != iscsiAuthStatusNoError) { + (void) snprintf(debug, sizeof (debug), + "iscsi connection(%u) login failed - " + "authentication transmit failed", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + break; + } + + /* + * scan the text data + */ + text = c->auth_text; + end = text + c->auth_text_length; +more_text: + while (text && (text < end)) { + char *value = NULL; + char *value_end = NULL; + keytype = iscsiAuthKeyTypeNone; + + /* + * skip any NULs separating each text key=value pair + */ + while ((text < end) && (*text == '\0')) { + text++; + } + if (text >= end) { + break; + } + + while (iscsiAuthClientGetNextKeyType(&keytype) == + iscsiAuthStatusNoError) { + char *key = + (char *)iscsiAuthClientGetKeyName(keytype); + if ((key) && + (iscsi_find_key_value(key, text, end, + &value, &value_end))) { + (void) snprintf(debug, sizeof (debug), + "%s=%s", key, value); + queue_str(c->c_mgmtq, Q_CONN_ERRS, + msg_log, debug); + if (iscsiAuthClientRecvKeyValue( + auth_client, keytype, value) + != iscsiAuthStatusNoError) { + (void) snprintf(debug, + sizeof (debug), + "iscsi connection(%u) login" + "failed - can't accept " + "%s in security stage", + c->c_num, text); + queue_str(c->c_mgmtq, + Q_CONN_ERRS, + msg_log, debug); + } + text = value_end; + goto more_text; + } + } + } + + switch (iscsiAuthClientRecvEnd(auth_client, iscsi_null_callback, + (void *)c->c_sess, NULL)) { + case iscsiAuthStatusContinue: + /* + * continue sending PDUs + */ + break; + + case iscsiAuthStatusPass: + c->c_auth_pass = 1; + break; + + case iscsiAuthStatusInProgress: + /* + * this should only occur if we were authenticating the + * target, which we don't do yet, so treat this as an + * error. + */ + case iscsiAuthStatusNoError: + /* + * treat this as an error, since we should get a + * different code + */ + case iscsiAuthStatusError: + case iscsiAuthStatusFail: + default: + debug_status = 0; + + (void) iscsiAuthClientGetDebugStatus(auth_client, + &debug_status); + (void) snprintf(debug, sizeof (debug), + "iscsi connection(%u) authentication failed (%s)", + c->c_num, + iscsiAuthClientDebugStatusToText( + debug_status)); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, + debug); + + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_AUTH_FAILED); + conn_state(c, T7); + rval = False; + break; + } + + if (rval == False) + break; + + keytype = iscsiAuthKeyTypeNone; + rc = iscsiAuthClientSendTransitBit(auth_client, &transit); + + /* + * see if we're ready for a stage change + */ + if (rc == iscsiAuthStatusNoError) { + if (transit) { + rsp->flags = lh.flags; + } + + } else { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_AUTH_FAILED); + (void) snprintf(debug, sizeof (debug), + "CON%x SecurityNegotiation: wants", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T7); + rval = False; + } + + /* + * enumerate all the keys the auth code might want to send + */ + while (iscsiAuthClientGetNextKeyType(&keytype) == + iscsiAuthStatusNoError) { + int present = 0; + char *key = (char *)iscsiAuthClientGetKeyName(keytype); + int key_length = key ? strlen(key) : 0; + int pdu_length = ntoh24(rsp->dlength); + char *auth_value = NULL; + unsigned int max_length = ISCSI_DEFAULT_MAX_XMIT_SEG_LEN + - (pdu_length + key_length + 1); /* FIXME: check */ + + /* + * add the key/value pairs the auth code wants to + * send directly to the PDU, since they could in + * theory be large. + */ + if ((auth_value = (char *)malloc(max_length)) == + NULL) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_TARGET_ERR << 8) | + ISCSI_LOGIN_STATUS_TARGET_ERROR); + + (void) snprintf(debug, sizeof (debug), + "CON%x Norm: Failed alloc auth_key %S=%s", + c->c_num, key, auth_value); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, + debug); + conn_state(c, T7); + rval = False; + break; + } + rc = iscsiAuthClientSendKeyValue(auth_client, keytype, + &present, auth_value, max_length); + if ((rc == iscsiAuthStatusNoError) && present) { + (void) snprintf(debug, sizeof (debug), + "key:%s, auth_value:%s\n", key, auth_value); + queue_str(c->c_mgmtq, Q_CONN_LOGIN, msg_log, + debug); + + rval = add_text(&text_rsp, &text_length, + key, auth_value); + if (rval == False) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_TARGET_ERR << + 8) | + ISCSI_LOGIN_STATUS_TARGET_ERROR); + (void) snprintf(debug, sizeof (debug), + "CON%x Norm: Failed to add %S=%s", + c->c_num, key, auth_value); + queue_str(c->c_mgmtq, Q_CONN_ERRS, + msg_log, debug); + conn_state(c, T7); + } + } + if (auth_value != NULL) + free(auth_value); + } + + break; + + case ISCSI_OP_PARMS_NEGOTIATION_STAGE: + + /* + * Gather up the parameters sent across and build a response + * based on any selection required. + */ + if ((rval = parse_text(c, ntoh24(lh.dlength), &text_rsp, + &text_length, &errcode)) == False) { + + send_login_reject(c, &lh, errcode); + (void) snprintf(debug, sizeof (debug), + "CON%x Norm: parse_text failed", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T7); + break; + } + + /* + * If the connection hasn't passed authentication and + * it's a normal session see if this connection MUST + * have gone through authentication first. If the + * initiator has a CHAP secret stored that means we + * want to validate. + */ + if ((c->c_auth_pass == 0) && + (c->c_sess->s_type == SessionNormal)) { + if ((tnode = find_target_node(c->c_sess->s_t_name)) == + NULL) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_TARGET_ERR << 8) | + ISCSI_LOGIN_STATUS_TARGET_ERROR); + (void) snprintf(debug, sizeof (debug), + "CON%x No target node in login", + c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, + debug); + conn_state(c, T7); + } + + /* + * check_access will return True if the initiator + * is required to use CHAP authentication. So if + * true and we're here it means that the initiator + * is trying to skip the authentication step. + */ + if (check_access(tnode, c->c_sess->s_i_name, True) == + False) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_AUTH_FAILED); + (void) snprintf(debug, sizeof (debug), + "CON%x Authentication required for %s", + c->c_num, c->c_sess->s_i_name); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, + debug); + conn_state(c, T7); + } + } + + if ((rval = check_for_valid_I_T(c)) == False) { + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_INIT_ERR); + + (void) snprintf(debug, sizeof (debug), + "CON%x Norm: bad I or T", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + conn_state(c, T7); + break; + } + + /* + * We accept transition and stage information as is + * and echo it back because at this point there's no need + * to send a parameter to the Initiator and expect a + * reply. + */ + rsp->flags = lh.flags; + + break; + + case ISCSI_FULL_FEATURE_PHASE: + /* can't jump directly to full feature phase */ + (void) snprintf(debug, sizeof (debug), + "CON%x Protocol error: wrong stage", c->c_num); + queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug); + send_login_reject(c, &lh, + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_INIT_ERR); + conn_state(c, T7); + rval = False; + break; + + default: + /* just drop the connection since we don't know what's up */ + rval = False; + break; + } + + hton24(rsp->dlength, text_length); + if ((rval == True) && (session_validate(c->c_sess) == True)) { + + send_iscsi_pkt(c, (iscsi_hdr_t *)rsp, text_rsp); + + if ((lh.flags & ISCSI_FLAG_LOGIN_TRANSIT) && + (ISCSI_LOGIN_NEXT_STAGE(lh.flags) == + ISCSI_FULL_FEATURE_PHASE)) { + + conn_state(c, T5); + + /* + * At this point we've completed the negotiation + * of all login parameters. Now we need to perform + * some quick boundary checks and then send a couple + * pieces of information to STE for it's use. + */ + c->c_max_burst_len = MIN(c->c_max_burst_len, + c->c_max_recv_data); + /* + * XXX Need to get this information passed up + * to the SAM-3 layer so that the SCSI TPG stuff + * works again. + */ + tpgt = find_main_tpgt(&(c->c_target_sockaddr)); + if (tpgt == 0) + tpgt = T10_DEFAULT_TPG; + c->c_tpgt = tpgt; + } + } + + if (text_rsp != NULL) + free(text_rsp); + if (rsp != NULL) + free(rsp); + + return (rval); +} + +/* + * check_for_valid_I_T -- check to see if we have valid names + * + * This routine checks to see if we have received a valid InitiatorName + * and TargetName which is the bare minimum which an Initiator must send + * across during the login phase. + */ +static Boolean_t +check_for_valid_I_T(iscsi_conn_t *c) +{ + iscsi_sess_t *s = c->c_sess; + if (s->s_type == SessionDiscovery) + return ((s->s_i_name == NULL) ? False : True); + else + return (((s->s_t_name == NULL) || + (s->s_i_name == NULL)) ? False : True); +} + +static iscsi_login_rsp_hdr_t * +make_login_response(iscsi_conn_t *c, iscsi_login_hdr_t *lhp) +{ + iscsi_login_rsp_hdr_t *r; + + if (lhp->tsid != 0) + /* don't except existing sessions for now */ + return (NULL); + + r = (iscsi_login_rsp_hdr_t *)calloc(sizeof (*r), sizeof (char)); + if (r == NULL) + return (NULL); + + bcopy(lhp->isid, r->isid, 6); /* 6 is defined by protocol */ + r->opcode = ISCSI_OP_LOGIN_RSP; + r->flags = ISCSI_FLAG_LOGIN_TRANSIT; + r->max_version = ISCSI_MAX_VERSION; + r->active_version = ISCSI_MIN_VERSION; + r->itt = lhp->itt; + r->tsid = htons(c->c_sess->s_tsid); + (void) pthread_mutex_lock(&c->c_mutex); + r->statsn = htonl(c->c_statsn++); + (void) pthread_mutex_unlock(&c->c_mutex); + /* ---- cmdsn is not advanced during login ---- */ + (void) pthread_mutex_lock(&c->c_sess->s_mutex); + r->expcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + r->maxcmdsn = htonl(c->c_sess->s_seencmdsn + 1); + (void) pthread_mutex_unlock(&c->c_sess->s_mutex); + + + return (r); +} + +static void +send_login_reject(iscsi_conn_t *c, iscsi_login_hdr_t *lhp, int err_code) +{ + iscsi_login_rsp_hdr_t *r; + + if ((r = make_login_response(c, lhp)) == NULL) + return; + + r->status_class = (err_code >> 8) & 0xff; + r->status_detail = err_code & 0xff; + + (void) write(c->c_fd, r, sizeof (*r)); + free(r); +} + +static auth_action_t +login_set_auth(iscsi_sess_t *s) +{ + xml_node_t *xnInitiator = NULL; + xml_node_t *xnTarget = NULL; + xml_node_t *xnAcl = NULL; + char *szIniAlias = NULL; + char *szIscsiName = NULL; + char *szChapName = NULL; + char *szChapSecret = NULL; + char *possible = NULL; + iscsi_auth_t *sess_auth = &(s->sess_auth); + int comp = 0; + + bzero(sess_auth->username_in, sizeof (sess_auth->username_in)); + bzero(sess_auth->password_in, sizeof (sess_auth->password_in)); + sess_auth->password_length_in = 0; + + /* Load alias, iscsi-name, chap-name, chap-secret from config file */ + while ((xnInitiator = xml_node_next(main_config, XML_ELEMENT_INIT, + xnInitiator)) != NULL) { + + (void) xml_find_value_str(xnInitiator, XML_ELEMENT_INIT, + &szIniAlias); + + if (xml_find_value_str(xnInitiator, XML_ELEMENT_INAME, + &szIscsiName) == True) { + + comp = strcmp(s->s_i_name, szIscsiName); + free(szIscsiName); + szIscsiName = NULL; + + if (comp == 0) { + + if (xml_find_value_str(xnInitiator, + XML_ELEMENT_CHAPNAME, + &szChapName) == True) { + /*CSTYLED*/ + (void) strcpy((char *)sess_auth->username_in, + szChapName); + free(szChapName); + } + + if (xml_find_value_str(xnInitiator, + XML_ELEMENT_CHAPSECRET, + &szChapSecret) == True) { + /*CSTYLED*/ + (void) strcpy((char *)sess_auth->password_in, + szChapSecret); + sess_auth->password_length_in = + strlen(szChapSecret); + free(szChapSecret); + } + break; + } + } + } + + if (s->s_type == SessionDiscovery) { + return (LOGIN_NO_AUTH); + } + + if (s->s_t_name == NULL) { + /* + * Should not happen for non-discovery session + */ + return (LOGIN_DROP); + } + + /* + * If no acc_list for current target, transit. + * If acc_list exists for the target, and + * If the initiator not in the list, drop it. + * If the initiator in the list, and + * If no CHAP secret for the initiator, transit. + * If a CHAP secret exists for the initiator, it must be authed. + */ + + while ((xnTarget = xml_node_next(targets_config, XML_ELEMENT_TARG, + xnTarget)) != NULL) { + + if ((xml_find_value_str(xnTarget, XML_ELEMENT_INAME, + &szIscsiName) == False) || (szIscsiName == NULL)) { + return (LOGIN_DROP); + } + + comp = strcmp(szIscsiName, s->s_t_name); + free(szIscsiName); + szIscsiName = NULL; + + if (comp == 0) { + + if ((xnAcl = xml_node_next(xnTarget, + XML_ELEMENT_ACLLIST, 0)) == NULL) { + /* + * No acl_list found, return True for no auth + */ + return (LOGIN_NO_AUTH); + } + + /* + * This target has an access_list. Now compare + * those entries against the initiator who started + * this session. + */ + xnInitiator = NULL; + while ((xnInitiator = xml_node_next(xnAcl, + XML_ELEMENT_INIT, xnInitiator)) != NULL) { + + if ((xml_find_value_str(xnInitiator, + XML_ELEMENT_INIT, &possible) == False) || + (possible == NULL)) + continue; + + if (strcmp(szIniAlias, possible) == 0) { + /* + * Found the initiator in acl-list, + * authentication needed + */ + free(possible); + if (sess_auth->password_length_in == 0) + return (LOGIN_NO_AUTH); + else + return (LOGIN_AUTH); + } + + free(possible); + possible = NULL; + } + /* + * Acl-list exists, while the initiator is not found in + * the list, we should drop the connection + */ + return (LOGIN_DROP); + } + } + + /* False means Need authentication */ + return (LOGIN_AUTH); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_login.h b/usr/src/cmd/iscsi/iscsitgtd/iscsi_login.h new file mode 100644 index 0000000000..846b23521e --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_login.h @@ -0,0 +1,42 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LOGIN_H +#define _LOGIN_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +Boolean_t iscsi_handle_login_pkt(iscsi_conn_t *c); + +#ifdef __cplusplus +} +#endif + +#endif /* _LOGIN_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_sess.c b/usr/src/cmd/iscsi/iscsitgtd/iscsi_sess.c new file mode 100644 index 0000000000..a2d119886a --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_sess.c @@ -0,0 +1,713 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <assert.h> +#include <sys/types.h> +#include <stdlib.h> +#include <strings.h> +#include <syslog.h> + +#include "iscsi_conn.h" +#include "iscsi_sess.h" +#include "t10.h" +#include "utility.h" +#include "xml.h" +#include "target.h" + +pthread_mutex_t sess_mutex; +int sess_num; +iscsi_sess_t *sess_head; + +static void session_free(struct iscsi_sess *s); +static void sess_set_auth(iscsi_sess_t *isp); +static void *sess_from_t10(void *v); +static void *sess_process(void *v); + +/* + * []---- + * | session_init -- initialize global variables and mutexs + * []---- + */ +void +session_init() +{ + (void) pthread_mutex_init(&sess_mutex, NULL); + sess_num = 0; + sess_head = 0; +} + +/* + * []---- + * | session_alloc -- create a new session attached to the lead connection + * []---- + */ +Boolean_t +session_alloc(iscsi_conn_t *c, uint8_t *isid) +{ + iscsi_sess_t *s, + *n; + + if (c->c_sess != NULL) + return (True); + + s = (iscsi_sess_t *)calloc(sizeof (iscsi_sess_t), 1); + if (s == NULL) + return (False); + + (void) pthread_mutex_lock(&sess_mutex); + s->s_num = sess_num++; + s->s_state = SS_STARTED; + + if (sess_head == NULL) + sess_head = s; + else { + for (n = sess_head; n->s_next; n = n->s_next) + ; + n->s_next = s; + } + (void) pthread_mutex_unlock(&sess_mutex); + + bcopy(isid, s->s_isid, 6); + + (void) pthread_mutex_init(&s->s_mutex, NULL); + c->c_sess = s; + s->s_conn_head = c; + s->s_sessq = queue_alloc(); + s->s_t10q = queue_alloc(); + c->c_sessq = s->s_sessq; + s->s_mgmtq = c->c_mgmtq; + s->s_type = SessionNormal; + s->s_tsid = s->s_num; + + sess_set_auth(s); + + (void) pthread_create(&s->s_thr_id_t10, NULL, sess_from_t10, s); + (void) pthread_create(&s->s_thr_id_conn, NULL, sess_process, s); + + util_title(s->s_mgmtq, Q_SESS_LOGIN, s->s_num, "Start Session"); + + return (True); +} + +/* + * []---- + * | session_free -- remove connection from session + * []---- + */ +static void +session_free(iscsi_sess_t *s) +{ + iscsi_sess_t *n; + + /* + * Early errors in connection setup can still call this routine + * which means the session hasn't been called. + */ + if (s == NULL) + return; + + if (s->s_i_name) + free(s->s_i_name); + if (s->s_t_name) + free(s->s_t_name); + if (s->s_i_alias) + free(s->s_i_alias); + + (void) pthread_mutex_lock(&sess_mutex); + if (sess_head == s) + sess_head = s->s_next; + else { + for (n = sess_head; n; n = n->s_next) { + if (n->s_next == s) { + n->s_next = s->s_next; + break; + } + } + if (n == NULL) { + queue_prt(s->s_mgmtq, Q_SESS_ERRS, + "SES%x NOT IN SESSION LIST!", s->s_num); + } + } + (void) pthread_mutex_unlock(&sess_mutex); +} + +/* + * []---- + * | session_remove_connection -- removes conn from sess list + * | + * | Returns True if this was the last connection which is always the case + * | for now. In the future with multiple connections per session it'll be + * | different. + * []---- + */ +/*ARGSUSED*/ +static Boolean_t +session_remove_connection(iscsi_sess_t *s, iscsi_conn_t *c) +{ + bzero(s->s_isid, 6); + return (True); +} + +/* + * []---- + * | convert_i_local -- Return a local name for the initiator if avilable. + * | + * | NOTE: If this routine returns true, it's the callers responsibility + * | to free the memory. + * []---- + */ +Boolean_t +convert_i_local(char *ip, char **rtn) +{ + xml_node_t *inode = NULL; + char *iname, + *name; + + while ((inode = xml_node_next(main_config, XML_ELEMENT_INIT, inode)) != + NULL) { + if (xml_find_value_str(inode, XML_ELEMENT_INAME, &iname) == + False) { + continue; + } + if (strcmp(iname, ip) == 0) { + if (xml_find_value_str(inode, XML_ELEMENT_INIT, + &name) == False) { + free(iname); + return (False); + } else + free(iname); + *rtn = name; + return (True); + } else + free(iname); + } + return (False); +} + +/* + * []---- + * | sess_from_t10 -- handle messages from the T10 layer + * []---- + */ +void * +sess_from_t10(void *v) +{ + iscsi_sess_t *s = (iscsi_sess_t *)v; + msg_t *m; + Boolean_t process = True; + + while (process == True) { + m = queue_message_get(s->s_t10q); + switch (m->msg_type) { + case msg_cmd_data_rqst: + queue_message_set(s->s_conn_head->c_dataq, 0, + msg_cmd_data_rqst, m->msg_data); + break; + + case msg_cmd_data_in: + queue_message_set(s->s_conn_head->c_dataq, 0, + msg_cmd_data_in, m->msg_data); + break; + + case msg_cmd_cmplt: + queue_message_set(s->s_conn_head->c_dataq, 0, + msg_cmd_cmplt, m->msg_data); + break; + + case msg_shutdown_rsp: + + if (s->s_t10) { + t10_handle_destroy(s->s_t10); + s->s_t10 = NULL; + } + + (void) pthread_mutex_lock(&s->s_mutex); + s->s_state = SS_SHUTDOWN_CMPLT; + (void) pthread_mutex_unlock(&s->s_mutex); + + session_free(s); + + /* + * Let the connection, which is the last, know + * about our completion of the shutdown. + */ + queue_message_set(s->s_conn_head->c_dataq, 0, + msg_shutdown_rsp, (void *)True); + process = False; + s->s_state = SS_FREE; + break; + + default: + queue_prt(s->s_mgmtq, Q_SESS_ERRS, + "SES%x Unknown msg type (%d) from T10 ", + s->s_num, m->msg_type); + queue_message_set(s->s_conn_head->c_dataq, 0, + m->msg_type, m->msg_data); + break; + } + queue_message_free(m); + } + queue_free(s->s_t10q, NULL); + util_title(s->s_mgmtq, Q_SESS_LOGIN, s->s_num, "End Session"); + free(s); + return (NULL); +} + +/* + * []---- + * | sess_process -- handle messages from the connection(s) + * []---- + */ +static void * +sess_process(void *v) +{ + iscsi_sess_t *s = (iscsi_sess_t *)v; + iscsi_conn_t *c; + iscsi_cmd_t *cmd; + msg_t *m; + Boolean_t process = True; + mgmt_request_t *mgmt; + name_request_t *nr; + t10_cmd_t *t10_cmd; + char **buf, + local_buf[16]; + int lun; + extern void dataout_callback(t10_cmd_t *t, char *data, size_t *xfer); + + (void) pthread_mutex_lock(&s->s_mutex); + s->s_state = SS_RUNNING; + (void) pthread_mutex_unlock(&s->s_mutex); + do { + m = queue_message_get(s->s_sessq); + switch (m->msg_type) { + case msg_cmd_send: + cmd = (iscsi_cmd_t *)m->msg_data; + if (s->s_t10 == NULL) { + + /* + * The value of 0x960 comes from T10. + * See SPC-4, revision 1a, section 6.4.2, + * table 87 + * + * XXX Need to rethink how I should do + * the callback. + */ + s->s_t10 = t10_handle_create(s->s_t_name, + T10_TRANS_ISCSI, s->s_conn_head->c_tpgt, + s->s_conn_head->c_max_burst_len, + s->s_t10q, dataout_callback); + } + if (t10_cmd_create(s->s_t10, cmd->c_lun, cmd->c_scb, + cmd->c_scb_len, (transport_t)cmd, + &t10_cmd) == False) { + + queue_prt(s->s_mgmtq, Q_SESS_ERRS, + "SES%x FAILED to create cmd", s->s_num); + /* + * If the command create failed, the T10 layer + * will attempt to create a sense buffer + * telling the initiator what went wrong. If + * that layer was unable to accomplish that + * things are really bad and we need to just + * close the connection. + */ + if (cmd->c_t10_cmd != NULL) { + queue_message_set( + cmd->c_allegiance->c_dataq, + 0, msg_cmd_cmplt, t10_cmd); + } else + conn_state(cmd->c_allegiance, T11); + } else { + (void) pthread_mutex_lock( + &cmd->c_allegiance->c_mutex); + if (cmd->c_state != CmdCanceled) { + cmd->c_t10_cmd = t10_cmd; + (void) t10_cmd_send(s->s_t10, + cmd->c_t10_cmd, cmd->c_data, + cmd->c_data_len); + } else { + t10_cmd_state(t10_cmd, + T10_Cmd_Event_Canceled); + } + (void) pthread_mutex_unlock( + &cmd->c_allegiance->c_mutex); + } + break; + + case msg_cmd_data_out: + cmd = (iscsi_cmd_t *)m->msg_data; + if (s->s_t10 != NULL) + (void) t10_cmd_data(s->s_t10, cmd->c_t10_cmd, + cmd->c_offset_out, cmd->c_data, + cmd->c_data_len); + break; + + case msg_targ_inventory_change: + if (s->s_t10 != NULL) + (void) t10_task_mgmt(s->s_t10, InventoryChange, + 0, 0); + break; + + case msg_lu_capacity_change: + lun = (int)(uintptr_t)m->msg_data; + if (s->s_t10 != NULL) + (void) t10_task_mgmt(s->s_t10, CapacityChange, + lun, 0); + break; + + case msg_reset_targ: + if (s->s_t10 != NULL) + (void) t10_task_mgmt(s->s_t10, ResetTarget, + 0, 0); + break; + + case msg_reset_lu: + if (s->s_t10 != NULL) + (void) t10_task_mgmt(s->s_t10, ResetLun, + (int)(uintptr_t)m->msg_data, 0); + break; + + case msg_shutdown: + (void) pthread_mutex_lock(&s->s_mutex); + s->s_state = SS_SHUTDOWN_START; + (void) pthread_mutex_unlock(&s->s_mutex); + + /* + * Shutdown rquest comming from a connection. Only + * shutdown the STE if this is the last connection + * for this session. + */ + c = (iscsi_conn_t *)m->msg_data; + if (session_remove_connection(s, c) == True) { + queue_prt(s->s_mgmtq, Q_SESS_NONIO, + "SES%x Starting shutdown", s->s_num); + + /* + * If this is the last connection for this + * session send a message to the SAM-3 layer to + * shutdown. + */ + if (s->s_t10 != NULL) { + t10_handle_disable(s->s_t10); + } + queue_message_set(s->s_t10q, 0, + msg_shutdown_rsp, 0); + process = False; + } else { + + /* + * Since this isn't the last connection for + * the session, acknowledge the connection + * request now since it's references from + * this session have been removed. + */ + queue_message_set(c->c_dataq, 0, + msg_shutdown_rsp, (void *)False); + } + break; + + case msg_initiator_name: + nr = (name_request_t *)m->msg_data; + s->s_i_name = strdup(nr->nr_name); + + /* + * Acknowledge the request by sending back an empty + * message. + */ + queue_message_set(nr->nr_q, 0, msg_initiator_name, 0); + break; + + case msg_initiator_alias: + nr = (name_request_t *)m->msg_data; + s->s_i_alias = strdup(nr->nr_name); + + /* + * Acknowledge the request by sending back an empty + * message. + */ + queue_message_set(nr->nr_q, 0, msg_initiator_alias, 0); + break; + + case msg_target_name: + nr = (name_request_t *)m->msg_data; + s->s_t_name = strdup(nr->nr_name); + + /* + * Acknowledge the request by sending back an empty + * message. + */ + queue_message_set(nr->nr_q, 0, msg_target_name, 0); + break; + + case msg_mgmt_rqst: + mgmt = (mgmt_request_t *)m->msg_data; + m->msg_data = NULL; + + (void) pthread_mutex_lock(&mgmt->m_resp_mutex); + buf = mgmt->m_u.m_resp; + + if ((s->s_type == SessionNormal) && + (mgmt->m_request == mgmt_full_phase_statistics) && + (strcmp(s->s_t_name, mgmt->m_targ_name) == 0)) { + + buf_add_tag(buf, XML_ELEMENT_CONN, Tag_Start); + buf_add_tag(buf, s->s_i_name, Tag_String); + if (s->s_i_alias != NULL) { + xml_add_tag(buf, XML_ELEMENT_ALIAS, + s->s_i_alias); + } + + /* + * Need to loop through the connections + * and create one time_connected tag for + * each. This will be needed once MC/S support + * is added. + */ + (void) snprintf(local_buf, sizeof (local_buf), + "%d", + mgmt->m_time - s->s_conn_head->c_up_at); + xml_add_tag(buf, XML_ELEMENT_TIMECON, + local_buf); + + buf_add_tag(buf, XML_ELEMENT_STATS, Tag_Start); + + t10_targ_stat(s->s_t10, buf); + + buf_add_tag(buf, XML_ELEMENT_STATS, Tag_End); + buf_add_tag(buf, XML_ELEMENT_CONN, Tag_End); + } + + (void) pthread_mutex_unlock(&mgmt->m_resp_mutex); + + queue_message_set(mgmt->m_q, 0, msg_mgmt_rply, 0); + + break; + + default: + queue_prt(s->s_mgmtq, Q_SESS_ERRS, + "SES%x Unknown msg type (%d) from Connection", + s->s_num, m->msg_type); + break; + } + queue_message_free(m); + } while (process == True); + + return (NULL); +} + +/* + * []---- + * | session_validate -- do what the name says + * | + * | At this point the connection has processed the login command so that + * | we have InitiatorName and ISID at a minimum. Check to see if there + * | are other sessions which match. If so, log that one out and proceed with + * | this session. If nothing matches, then link this into a global list. + * | + * | Once we support multiple connections per session need to scan list + * | to see if other connection have the same CID. If so, log out that + * | connection. + * []---- + */ +Boolean_t +session_validate(iscsi_sess_t *s) +{ + iscsi_sess_t *check; + + queue_prt(s->s_mgmtq, Q_SESS_NONIO, + "SES%x %s ISID[%02x%02x%02x%02x%02x%02x]", + s->s_num, s->s_i_alias == NULL ? s->s_i_name : s->s_i_alias, + s->s_isid[0], s->s_isid[1], s->s_isid[2], + s->s_isid[3], s->s_isid[4], s->s_isid[5]); + + + /* + * SessionType=Discovery which means no target name and therefore + * this is okay. + */ + if (s->s_t_name == NULL) + return (True); + + (void) pthread_mutex_lock(&sess_mutex); + for (check = sess_head; check; check = check->s_next) { + /* + * Ignore ourselves in this check. + */ + if (check == s) + continue; + if ((check->s_t_name == NULL) || + (strcmp(check->s_t_name, s->s_t_name) != 0)) + continue; + if (strcmp(check->s_i_name, s->s_i_name) != 0) + continue; + if (check->s_conn_head->c_tpgt != s->s_conn_head->c_tpgt) + continue; + /* + * Section 5.3.5 + * Session reinstatement is the process of the initiator + * logging in with an ISID that is possible active from + * the target's perspective. Thus implicitly logging out + * the session that corresponds to the ISID and + * reinstating a new iSCSI session in its place (with the + * same ISID). + */ + if (bcmp(check->s_isid, s->s_isid, 6) == 0) { + queue_prt(s->s_mgmtq, Q_SESS_NONIO, + "SES%x Implicit shutdown", check->s_num); + if (check->s_conn_head->c_state == S5_LOGGED_IN) + conn_state(check->s_conn_head, T8); + else + conn_state(check->s_conn_head, T7); + break; + } + } + (void) pthread_mutex_unlock(&sess_mutex); + + return (True); +} + +/* + * []---- + * | static iscsi_sess_set_auth - + * []---- + */ +static void +sess_set_auth(iscsi_sess_t *isp) +{ + IscsiAuthClient *auth_client = NULL; + xml_node_t *node = NULL; + + if (isp == (iscsi_sess_t *)NULL) + return; + + /* Zero out the session authentication structure */ + bzero(&isp->sess_auth, sizeof (iscsi_auth_t)); + isp->sess_auth.auth_enabled = B_TRUE; + + /* Load CHAP name */ + node = xml_node_next_child(main_config, XML_ELEMENT_CHAPNAME, NULL); + if (node == NULL) + return; + (void) strcpy(isp->sess_auth.username, node->x_value); + + /* Load CHAP secret */ + node = xml_node_next_child(main_config, XML_ELEMENT_CHAPSECRET, NULL); + if (node == NULL) + return; + (void) strcpy((char *)isp->sess_auth.password, node->x_value); + isp->sess_auth.password_length = strlen(node->x_value); + + /* Set up authentication buffers only if configured */ + if ((isp->sess_auth.password_length != 0) || + (isp->sess_auth.password_length_in != 0)) { + isp->sess_auth.num_auth_buffers = 5; + isp->sess_auth.auth_buffers[0].address = + &(isp->sess_auth.auth_client_block); + isp->sess_auth.auth_buffers[0].length = + sizeof (isp->sess_auth.auth_client_block); + isp->sess_auth.auth_buffers[1].address = + &(isp->sess_auth.auth_recv_string_block); + isp->sess_auth.auth_buffers[1].length = + sizeof (isp->sess_auth.auth_recv_string_block); + isp->sess_auth.auth_buffers[2].address = + &(isp->sess_auth.auth_send_string_block); + isp->sess_auth.auth_buffers[2].length = + sizeof (isp->sess_auth.auth_send_string_block); + isp->sess_auth.auth_buffers[3].address = + &(isp->sess_auth.auth_recv_binary_block); + isp->sess_auth.auth_buffers[3].length = + sizeof (isp->sess_auth.auth_recv_binary_block); + isp->sess_auth.auth_buffers[4].address = + &(isp->sess_auth.auth_send_binary_block); + isp->sess_auth.auth_buffers[4].length = + sizeof (isp->sess_auth.auth_send_binary_block); + } + + if (isp->sess_auth.auth_buffers && + isp->sess_auth.num_auth_buffers) { + + auth_client = (IscsiAuthClient *)isp-> + sess_auth.auth_buffers[0].address; + + /* + * prepare for authentication + */ + if (iscsiAuthClientInit(iscsiAuthNodeTypeTarget, + isp->sess_auth.num_auth_buffers, + isp->sess_auth.auth_buffers) != + iscsiAuthStatusNoError) { + syslog(LOG_ERR, "iscsi connection login failed - " + "unable to initialize authentication\n"); + return; + } + + if (iscsiAuthClientSetVersion(auth_client, + iscsiAuthVersionRfc) != iscsiAuthStatusNoError) { + syslog(LOG_ERR, "iscsi connection login failed - " + "unable to set version\n"); + return; + } + + if (isp->sess_auth.username && + (iscsiAuthClientSetUsername(auth_client, + isp->sess_auth.username) != + iscsiAuthStatusNoError)) { + syslog(LOG_ERR, "iscsi connection login failed - " + "unable to set username\n"); + return; + } + + if (isp->sess_auth.password && + (iscsiAuthClientSetPassword(auth_client, + isp->sess_auth.password, isp->sess_auth.password_length) != + iscsiAuthStatusNoError)) { + syslog(LOG_ERR, "iscsi connection login failed - " + "unable to set password\n"); + return; + } + + /* + * FIXME: we disable the minimum size check for now + */ + if (iscsiAuthClientSetIpSec(auth_client, 1) != + iscsiAuthStatusNoError) { + syslog(LOG_ERR, "iscsi connection login failed - " + "unable to set ipsec\n"); + return; + } + + if (iscsiAuthClientSetAuthRemote(auth_client, + isp->sess_auth.auth_enabled) != + iscsiAuthStatusNoError) { + syslog(LOG_ERR, "iscsi connection login failed - " + "unable to set remote authentication\n"); + return; + } + } +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_sess.h b/usr/src/cmd/iscsi/iscsitgtd/iscsi_sess.h new file mode 100644 index 0000000000..4426046117 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_sess.h @@ -0,0 +1,142 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SESSION_H +#define _SESSION_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <sys/iscsi_authclient.h> +#include "iscsi_cmd.h" +#include "t10.h" + +/* + * iSCSI Auth Information + */ +typedef struct iscsi_auth { + IscsiAuthStringBlock auth_recv_string_block; + IscsiAuthStringBlock auth_send_string_block; + IscsiAuthLargeBinary auth_recv_binary_block; + IscsiAuthLargeBinary auth_send_binary_block; + IscsiAuthClient auth_client_block; + int num_auth_buffers; + IscsiAuthBufferDesc auth_buffers[5]; + + /* + * To indicate if authentication is enabled. + * 0 means authentication disabled. + * 1 means authentication enabled. + */ + int auth_enabled; + + /* Initiator's authentication information. */ + char username[iscsiAuthStringMaxLength]; + uint8_t password[iscsiAuthStringMaxLength]; + int password_length; + + /* Target's authentication information. */ + char username_in[iscsiAuthStringMaxLength]; + uint8_t password_in[iscsiAuthStringMaxLength]; + int password_length_in; +} iscsi_auth_t; + +typedef enum iscsi_session_type { + SessionDiscovery, SessionNormal +} iscsi_session_type_t; + +typedef enum iscsi_session_state { + SS_FREE, SS_STARTED, SS_RUNNING, SS_SHUTDOWN_START, SS_SHUTDOWN_CMPLT +} iscsi_sess_state_t; + +typedef struct iscsi_sess { + struct iscsi_sess *s_next; + + iscsi_sess_state_t s_state; + + /* + * Set during login + * mutex isn't held. + */ + char *s_i_name, + *s_i_alias, + *s_t_name; + uint8_t s_isid[6]; + /* + * This is the highest packet number we've seen and is + * used during replies. + */ + int s_seencmdsn; + + /* + * To keep the correct order of PDU's submitted to the SCSI + * layer we check that the incoming cmdsn matches this value. + * Otherwise, we're missing a packet and need to wait. This + * is particularly important with multiple connections per + * session. + */ + int s_cmdsn; + + iscsi_session_type_t s_type; + + /* + * Set during allocation of this struct and only referenced + */ + int s_tsid; + + target_queue_t *s_sessq, + *s_t10q, + *s_mgmtq; + + t10_targ_handle_t s_t10; + + struct iscsi_conn *s_conn_head; + + int s_num; + + pthread_mutex_t s_mutex; + iscsi_auth_t sess_auth; + pthread_t s_thr_id_conn, + s_thr_id_t10; +} iscsi_sess_t; + +void session_init(); +Boolean_t session_alloc(struct iscsi_conn *c, uint8_t *isid); +Boolean_t session_validate(struct iscsi_sess *s); + +#ifdef __cplusplus +} +#endif + +#endif /* _SESSION_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/iscsi_target.xml b/usr/src/cmd/iscsi/iscsitgtd/iscsi_target.xml new file mode 100644 index 0000000000..e0bd20b7a5 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/iscsi_target.xml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> +<!-- + Copyright 2006 Sun Microsystems, Inc. All rights reserved. + Use is subject to license terms. + + CDDL HEADER START + + The contents of this file are subject to the terms of the + Common Development and Distribution License (the "License"). + You may not use this file except in compliance with the License. + + You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + or http://www.opensolaris.org/os/licensing. + See the License for the specific language governing permissions + and limitations under the License. + + When distributing Covered Code, include this CDDL HEADER in each + file and include the License file at usr/src/OPENSOLARIS.LICENSE. + If applicable, add the following below this CDDL HEADER, with the + fields enclosed by brackets "[]" replaced with your own identifying + information: Portions Copyright [yyyy] [name of copyright owner] + + CDDL HEADER END + + ident "%Z%%M% %I% %E% SMI" + + + NOTE: This service manifest is not editable; its contents will + be overwritten by package or patch operations, including + operating system upgrade. Make customizations in a different + file. +--> + +<service_bundle type='manifest' name='SUNWiscsitgtu:iscsitgt'> + +<service + name='system/iscsitgt' + type='service' + version='1'> + + <create_default_instance enabled='false' /> + + <single_instance/> + + <!-- We need name resolution and full filesystem access --> + <dependency + name='milestone' + grouping='require_all' + restart_on='none' + type='service'> + <service_fmri value='svc:/milestone/multi-user' /> + </dependency> + + + <exec_method + type='method' + name='start' + exec='/usr/sbin/iscsitgtd' + timeout_seconds='60'> + <method_context> + <method_credential + user='root' + group='sys' + privileges='basic,net_rawaccess,sys_mount,file_dac_write,sys_devices' /> + </method_context> + </exec_method> + + <exec_method + type='method' + name='stop' + exec=':kill' + timeout_seconds='60'> + <method_context> + <method_credential + user='root' + group='sys' + privileges='basic,net_rawaccess,sys_mount,file_dac_write,sys_devices' /> + </method_context> + </exec_method> + + <stability value='Unstable' /> + + <template> + <common_name> + <loctext xml:lang='C'> Solaris iSCSI Target + </loctext> + </common_name> + <documentation> + <manpage title='iscsitgtd' section='1M' + manpath='/usr/share/man' /> + <manpage title='iscsitadm' section='1M' + manpath='/usr/share/man' /> + </documentation> + </template> +</service> + +</service_bundle> diff --git a/usr/src/cmd/iscsi/iscsitgtd/main.c b/usr/src/cmd/iscsi/iscsitgtd/main.c new file mode 100644 index 0000000000..3769262ea2 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/main.c @@ -0,0 +1,975 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#define FD_SETSIZE 65536 +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> +#include <pthread.h> +#include <sys/conf.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <assert.h> +#include <errno.h> +#include <door.h> +#include <iscsi_door.h> +#include <signal.h> +#include <siginfo.h> +#include <sys/ethernet.h> +#include <libscf.h> +#include <syslog.h> +#include <synch.h> +#include <libxml/xmlreader.h> +#include <sys/resource.h> +#include <syslog.h> +#include <sys/select.h> + +#include "queue.h" +#include "port.h" +#include "iscsi_conn.h" +#include "target.h" +#include "xml.h" +#include "utility.h" +#include "iscsi_ffp.h" +#include "errcode.h" +#include "t10.h" + +#define EMPTY_CONFIG "<config version='1.0'>\n</config>\n" + +/* ---- Forward declarations ---- */ +static void variable_handler(xml_node_t *, target_queue_t *, target_queue_t *); + + +/* ---- Global configuration data. ---- */ +char *target_basedir = NULL; +char *target_log = DEFAULT_TARGET_LOG; +char *config_file = DEFAULT_CONFIG_LOCATION; +int iscsi_port = 3260; /* defined by the spec */ +xml_node_t *main_config, + *targets_config; +Boolean_t enforce_strict_guid = True, + thin_provisioning = False, + disable_tpgs = False, + dbg_timestamps = False; +int targets_vers_maj, + targets_vers_min, + main_vers_maj, + main_vers_min; +pthread_mutex_t targ_config_mutex; + +int dbg_lvl = 0; + +/* + * door_return doesn't have a means to free the memory that's passed in + * which means the program must either use the buffer space provided as + * the argument to the service routine or allocate it's own and create + * a leak. Since the argument to the service routine is likely to be a + * fairly small buffer it may not be able to hold the results. So, the + * daemon which already has the results supplies that value to the door_return + * and sets up a request to collect that memory later. This structure + * empty_garbage(), and delayed_free() are used to recoupe the memory. + */ +typedef struct garbage_can { + char *g_buf; + int g_timo; +} garbage_can_t; + +typedef struct var_table { + char *v_name; + int *v_value; +} var_table_t; + +typedef struct cmd_table { + char *c_name; + void (*c_func)(xml_node_t *, target_queue_t *, target_queue_t *); +} cmd_table_t; + +admin_table_t admin_prop_list[] = { + {XML_ELEMENT_BASEDIR, update_basedir}, + {XML_ELEMENT_CHAPSECRET, 0}, + {XML_ELEMENT_CHAPNAME, 0}, + {XML_ELEMENT_RAD_ACCESS, 0}, + {XML_ELEMENT_RAD_SERV, valid_radius_srv}, + {XML_ELEMENT_RAD_SECRET, 0}, + {XML_ELEMENT_ISNS_ACCESS, 0}, + {XML_ELEMENT_FAST, 0}, + {0, 0} +}; + +/* + * Global variables which can be set via the management XML interface + * with the syntax of "<variable><dbg_lvl>0x033</dbg_lvl></variable>" + */ +var_table_t var_table[] = { + { "dbg_lvl", &dbg_lvl }, + { "qlog_lvl", &qlog_lvl }, + /* ---- End of Table marker ---- */ + { NULL, 0 } +}; + +/* + * Commands which are run via the management XML interface + */ +cmd_table_t cmd_table[] = { + { "variable", variable_handler }, + { "create", create_func }, + { "modify", modify_func }, + { "delete", remove_func }, + { "list", list_func }, + /* ---- End of Table marker ---- */ + { NULL, NULL } +}; + +/* + * []---- + * | scan_for_luns -- scan target for missing luns + * | + * | Checks to see if there are any LUs not in the target node which + * | exist in the target directory and adds them. Also checks to see + * | if there are LUs in the target node which are not present in the + * | directory and removes them. + * | + * | NOTE: This routine expects that the targ_dir has already been + * | verified to exist and is a directory + * []---- + */ +static void +scan_for_luns(xml_node_t *targ, char *targ_dir) +{ + xml_node_t *lun_list = NULL, + *lun = NULL; + Boolean_t changes_made = False; + DIR *dp; + struct dirent *de; + fd_set lu_bits; + int l; + + FD_ZERO(&lu_bits); + + if ((lun_list = xml_node_next(targ, XML_ELEMENT_LUNLIST, NULL)) == + NULL) { + /* + * Most likely have an older configuration file that + * didn't list the LUs assocated with each target. No problem. + * Create a node and add it to the target. + */ + lun_list = xml_alloc_node(XML_ELEMENT_LUNLIST, String, ""); + (void) xml_add_child(targ, lun_list); + changes_made = True; + } else { + /* + * Build up a bit mask of the current LU set. This will be + * used to see if a LU already exists or not. + * When a LU is found the bit will be checked. If it's + * set then we know the current tree is correct for that + * LU and the bit will be cleared. If it's not set we're + * missing that LU and it will be added. + * Once we've got through the list any bits that are still + * set means that LU node in the tree should be removed. + */ + while ((lun = xml_node_next(lun_list, XML_ELEMENT_LUN, lun)) != + NULL) { + (void) xml_find_value_int(lun, XML_ELEMENT_LUN, &l); + FD_SET(l, &lu_bits); + } + } + + dp = opendir(targ_dir); + assert(dp != NULL); + while ((de = readdir(dp)) != NULL) { + if ((strcmp(de->d_name, ".") == 0) || + (strcmp(de->d_name, "..") == 0)) + continue; + /* ---- Look for the LU backing store files ---- */ + if (strncmp(de->d_name, LUNBASE, sizeof (LUNBASE) - 1) != 0) + continue; + + l = atoi(&de->d_name[sizeof (LUNBASE) - 1]); + if (FD_ISSET(l, &lu_bits) == 0) { + lun = xml_alloc_node(XML_ELEMENT_LUN, Int, &l); + (void) xml_add_child(lun_list, lun); + changes_made = True; + } else + FD_CLR(l, &lu_bits); + } + closedir(dp); + for (l = 0; l < FD_SETSIZE; l++) { + if (FD_ISSET(l, &lu_bits)) { + lun = xml_alloc_node(XML_ELEMENT_LUN, Int, &l); + (void) xml_remove_child(lun_list, lun, MatchBoth); + xml_tree_free(lun); + changes_made = True; + } + } + if (changes_made == True) + (void) update_config_targets(NULL); +} +/* + * []---- + * | process_target_config -- Load up the targets into memory + * []---- + */ +Boolean_t +process_target_config() +{ + xmlTextReaderPtr r = NULL; + char path[MAXPATHLEN], + *target = NULL; + struct stat ss; + xml_node_t *node = NULL, + *next = NULL; + int xml_fd = -1; + + if (target_basedir != NULL) { + if (access(target_basedir, R_OK) != 0) { + + /* + * The target base directory has been set, but no + * longer exists which means someone has removed it + * behind our back. Obviously something bad has + * occurred, but we should attempt to start anyway and + * do so with an empty configuration. + */ + r = (xmlTextReaderPtr)xmlReaderForMemory(EMPTY_CONFIG, + strlen(EMPTY_CONFIG), NULL, NULL, 0); + syslog(LOG_WARNING, + "Previous target directory (%s) has been removed", + target_basedir); + + } else { + + (void) snprintf(path, MAXPATHLEN, "%s/%s", + target_basedir, "config.xml"); + if (access(path, R_OK) != 0) { + + /* + * No existing configuration, but we have + * the target directory so attempt to create + * an empty configuration file. + * If the open or write fail there's a + * serious problem which needs attention. + */ + if ((xml_fd = open(path, + O_RDWR | O_CREAT, 0600)) < 0) { + + syslog(LOG_ERR, + "Can not create empty " + "configuration file in %s", + target_basedir); + return (False); + } + + if (write(xml_fd, EMPTY_CONFIG, + strlen(EMPTY_CONFIG)) != + strlen(EMPTY_CONFIG)) { + syslog(LOG_ERR, "Failed to write empty " + "configuration file in %s", path); + return (False); + } + + (void) close(xml_fd); + } + + if ((xml_fd = open(path, O_RDONLY)) >= 0) + r = (xmlTextReaderPtr)xmlReaderForFd(xml_fd, + NULL, NULL, 0); + } + } else { + r = (xmlTextReaderPtr)xmlReaderForMemory(EMPTY_CONFIG, + strlen(EMPTY_CONFIG), NULL, NULL, 0); + } + + if (r != NULL) { + while (xmlTextReaderRead(r) == 1) { + if (xml_process_node(r, &node) == False) + break; + } + + /* + * Validate the configuration file has the appropriate + * version number. + */ + targets_vers_maj = XML_VERS_TARG_MAJ; + targets_vers_min = XML_VERS_TARG_MIN; + if (validate_version(node, &targets_vers_maj, + &targets_vers_min) == False) { + syslog(LOG_ERR, "Target config(%s/config.xml) invalid", + target_basedir); + return (False); + } + + targets_config = node; + while ((next = xml_node_next(node, XML_ELEMENT_TARG, + next)) != NULL) { + if (xml_find_value_str(next, XML_ELEMENT_INAME, + &target) == False) { + continue; + } + (void) snprintf(path, MAXPATHLEN, "%s/%s", + target_basedir, target); + if (stat(path, &ss) < 0) { + continue; + } + if ((ss.st_mode & S_IFDIR) == 0) { + continue; + } + scan_for_luns(next, path); + free(target); + } + if (xml_fd != -1) + (void) close(xml_fd); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + xmlCleanupParser(); + return (True); + } else { + + if (xml_fd != -1) + (void) close(xml_fd); + /* + * NOTE: Look at sending a syslog message or maybe + * something to FMA. + */ + return (False); + } +} + +/* + * []---- + * | process_config -- parse the main configuration file + * | + * | Everything in the configuratin file is optional. That's because + * | the management CLI can set the value to everything and update + * | the configuration. + * []---- + */ +static Boolean_t +process_config(char *file) +{ + xmlTextReaderPtr r; + int ret, + xml_fd = -1; + xml_node_t *node = NULL; + +#ifndef lint + LIBXML_TEST_VERSION; +#endif + + if (access(file, R_OK) != 0) { + if ((xml_fd = open(file, O_RDWR | O_CREAT, 0600)) < 0) + return (False); + if (write(xml_fd, EMPTY_CONFIG, strlen(EMPTY_CONFIG)) != + strlen(EMPTY_CONFIG)) + return (False); + (void) close(xml_fd); + } + + if ((xml_fd = open(file, O_RDONLY)) >= 0) + r = (xmlTextReaderPtr)xmlReaderForFd(xml_fd, NULL, NULL, 0); + + if (r != NULL) { + ret = xmlTextReaderRead(r); + while (ret == 1) { + if (xml_process_node(r, &node) == False) { + break; + } + ret = xmlTextReaderRead(r); + } + + /* + * Validate the configuration file has the appropriate + * version number. + */ + main_vers_maj = XML_VERS_MAIN_MAJ; + main_vers_min = XML_VERS_MAIN_MIN; + if (validate_version(node, &main_vers_maj, &main_vers_min) == + False) { + syslog(LOG_ERR, "Target main config invalid"); + return (False); + } + + /* + * The base directory is optional in the sense that the daemon + * can start without it, but the daemon can't really do + * anything until the administrator sets the value. + */ + (void) xml_find_value_str(node, XML_ELEMENT_BASEDIR, + &target_basedir); + + /* + * These are optional settings for the target. Each of + * these has a default value which can be overwritten in + * the configuration file. + */ + (void) xml_find_value_str(node, XML_ELEMENT_TARGLOG, + &target_log); + (void) xml_find_value_int(node, XML_ELEMENT_ISCSIPORT, + &iscsi_port); + (void) xml_find_value_int(node, XML_ELEMENT_DBGLVL, &dbg_lvl); + (void) xml_find_value_boolean(node, XML_ELEMENT_ENFORCE, + &enforce_strict_guid); + (void) xml_find_value_boolean(node, XML_ELEMENT_THIN_PROVO, + &thin_provisioning); + (void) xml_find_value_boolean(node, XML_ELEMENT_DISABLE_TPGS, + &disable_tpgs); + (void) xml_find_value_boolean(node, XML_ELEMENT_TIMESTAMPS, + &dbg_timestamps); + if (xml_find_value_int(node, XML_ELEMENT_LOGLVL, + &qlog_lvl) == True) + queue_log(True); + + main_config = node; + if (xml_fd != -1) + (void) close(xml_fd); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + xmlCleanupParser(); + + return (True); + } else { + if (xml_fd != -1) + (void) close(xml_fd); + return (False); + } +} + +/* + * []---- + * | logout_cleanup -- see if the initiator did what was requested + * | + * | When a target issues an asynchrouns event with the code set to + * | "logout requested" the initiator is supposed to respond with + * | a LogoutRequested PDU within a certain amount of time. If it + * | fails to do so, it's the targets responsibility to clean up. + * | After ASYNC_LOGOUT_TIMEOUT seconds (currently 10) we look to + * | see if any connections are still in the S7_LOGOUT_REQUESTED + * | state. If so, reissue the management request to logout which + * | will cause the connections to close. + * []---- + */ +static void * +logout_cleanup(void *v) +{ + int msg_sent, + i; + char *targ = (char *)v; + mgmt_request_t m; + iscsi_conn_t *conn; + extern pthread_mutex_t port_mutex; + + bzero(&m, sizeof (m)); + m.m_request = mgmt_logout; + m.m_q = queue_alloc(); + msg_sent = 0; + + (void) sleep(ASYNC_LOGOUT_TIMEOUT); + (void) pthread_mutex_lock(&port_mutex); + for (conn = conn_head; conn; conn = conn->c_next) { + if ((conn->c_state == S7_LOGOUT_REQUESTED) && + (strcmp(conn->c_sess->s_t_name, targ) == 0)) { + + queue_message_set(conn->c_dataq, 0, + msg_mgmt_rqst, &m); + msg_sent++; + } + } + (void) pthread_mutex_unlock(&port_mutex); + + /* + * Wait to see if they received the message. + */ + for (i = 0; i < msg_sent; i++) + queue_message_free(queue_message_get(m.m_q)); + queue_free(m.m_q, NULL); + free(targ); + + return ((void *)0); +} + +void +logout_targ(char *targ) +{ + mgmt_request_t m; + iscsi_conn_t *conn; + int i, + msg_sent; + pthread_t junk; + extern pthread_mutex_t port_mutex; + + /* + * Now we look for connections to this target and issue + * a request to asynchronously logout. + */ + bzero(&m, sizeof (m)); + m.m_request = mgmt_logout; + m.m_q = queue_alloc(); + msg_sent = 0; + + (void) pthread_mutex_lock(&port_mutex); + for (conn = conn_head; conn; conn = conn->c_next) { + if ((conn->c_state == S5_LOGGED_IN) && + (strcmp(conn->c_sess->s_t_name, targ) == 0)) { + + queue_message_set(conn->c_dataq, 0, msg_mgmt_rqst, &m); + msg_sent++; + } + } + (void) pthread_mutex_unlock(&port_mutex); + + /* ---- Wait to see if they received the message. ---- */ + for (i = 0; i < msg_sent; i++) + queue_message_free(queue_message_get(m.m_q)); + + queue_free(m.m_q, NULL); + + /* ---- Start housecleaning thread ---- */ + (void) pthread_create(&junk, NULL, logout_cleanup, + (void *)strdup(targ)); +} + +/* + * [] ---- XML Management Handlers ---- [] + */ + +/* + * []---- + * | variable_handler -- used to set a couple of internal global variables + * []---- + */ +/*ARGSUSED*/ +void +variable_handler(xml_node_t *x, target_queue_t *reply, target_queue_t *mgmt) +{ + char *reply_buf = NULL; + var_table_t *v; + xml_node_t *c; + + for (c = x->x_child; c; c = c->x_sibling) { + + for (v = var_table; v->v_name; v++) { + if (strcmp(c->x_name, v->v_name) == 0) { + *v->v_value = strtol(c->x_value, NULL, 0); + if (strcmp(v->v_name, "qlog_lvl") == 0) + queue_log(True); + xml_rtn_msg(&reply_buf, ERR_SUCCESS); + break; + } + } + if (v->v_name == NULL) + xml_rtn_msg(&reply_buf, ERR_NO_MATCH); + + queue_str(reply, 0, msg_mgmt_rply, reply_buf); + } +} + +/* + * []---- + * | parse_xml -- incoming management requests are sent here for processing + * []---- + */ +static void +parse_xml(xml_node_t *x, target_queue_t *reply, target_queue_t *mgmt) +{ + char *reply_msg = NULL; + cmd_table_t *c; + + if ((x->x_name == NULL) || (x->x_state == NodeFree)) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_EMPTY); + queue_message_set(reply, 0, msg_mgmt_rply, reply_msg); + return; + } + + for (c = cmd_table; c->c_name != NULL; c++) + if (strcmp(c->c_name, x->x_name) == 0) + break; + if (c->c_name == NULL) { + xml_rtn_msg(&reply_msg, ERR_INVALID_COMMAND); + queue_message_set(reply, 0, msg_mgmt_rply, reply_msg); + } else { + (c->c_func)(x, reply, mgmt); + } +} + +/* + * []---- + * | empty_garbage -- sleep for the requested time and then release the memory + * []---- + */ +static void * +empty_garbage(void *v) +{ + garbage_can_t *g = (garbage_can_t *)v; + + (void) sleep(g->g_timo); + free(g->g_buf); + free(g); + return (NULL); +} + +/* + * []---- + * | delayed_free -- set things up to free a chunk of memory later + * []---- + */ +static void +delayed_free(char *buf, int timeout) +{ + garbage_can_t *g; + pthread_t junk; + + /* + * if we can't get memory we're pretty much sunk. + */ + if ((g = (garbage_can_t *)calloc(1, sizeof (*g))) == NULL) + return; + + g->g_buf = buf; + g->g_timo = timeout; + + (void) pthread_create(&junk, NULL, empty_garbage, g); +} + +/*ARGSUSED*/ +static void +server_for_door(void *cookie, char *argp, size_t arg_size, door_desc_t *dp, + uint_t n_desc) +{ + target_queue_t *mgmtq = (target_queue_t *)cookie; + mgmt_request_t m; + msg_t *msg = NULL; + xml_node_t *node = NULL; + xmlTextReaderPtr r; + char *err_rply = NULL; + + bzero(&m, sizeof (m)); + + if ((r = (xmlTextReaderPtr)xmlReaderForMemory(argp, strlen(argp), + NULL, NULL, 0)) != NULL) { + while (xmlTextReaderRead(r)) { + if (xml_process_node(r, &node) == False) + break; + } + if (node != NULL) { + m.m_q = queue_alloc(); + m.m_request = mgmt_parse_xml; + m.m_time = time(NULL); + m.m_targ_name = NULL; + m.m_u.m_node = node; + + queue_message_set(mgmtq, 0, msg_mgmt_rqst, &m); + if ((msg = queue_message_get(m.m_q)) == NULL) { + xmlFreeTextReader(r); + xmlCleanupParser(); + door_return("", 1, NULL, 0); + } + + /* + * Check to see if the response can fit into the + * incoming argument buffer. If so, copy the response + * to that buffer so that we can free the data. + * If it's not big enough we'll return the pointer to + * the message response and have to free the data + * at another time. + */ + if (strlen(msg->msg_data) < arg_size) { + (void) strcpy(argp, msg->msg_data); + free(msg->msg_data); + } else { + delayed_free(msg->msg_data, 2); + argp = msg->msg_data; + } + queue_message_free(msg); + } else { + xml_rtn_msg(&err_rply, ERR_NULL_XML_MESSAGE); + } + + xmlFreeTextReader(r); + xmlCleanupParser(); + + } else { + xml_rtn_msg(&err_rply, ERR_INIT_XML_READER_FAILED); + } + + if (node != NULL) + xml_tree_free(node); + if (err_rply != NULL) { + strcpy(argp, err_rply); + free(err_rply); + } + if (m.m_q != NULL) + queue_free(m.m_q, NULL); + + (void) door_return(argp, strlen(argp) + 1, NULL, 0); +} + +/* + * []---- + * | setup_door -- Create a door portal for management requests + * | + * | First check to see if another daemon is already running by attempting + * | to send an empty request to the door. If successful it means this + * | daemon should exit. + * []---- + */ +static void +setup_door(target_queue_t *q, char *door_name) +{ + int did, + fd; + struct stat s; + door_arg_t d; + + if ((fd = open(door_name, 0)) >= 0) { + + /* + * There's at least a file with the same name as our + * door. Let's see if someone is currently answering + * by sending an empty XML request. + */ + d.data_ptr = "<config></config>"; + d.data_size = strlen(d.data_ptr) + 1; + d.desc_ptr = NULL; + d.desc_num = 0; + d.rbuf = NULL; + d.rsize = 0; + + if (door_call(fd, &d) == 0) { + + /* + * If the door_call succeeds that means another + * daemon is already running so let's just exit. + */ + exit(0); + } + (void) close(fd); + } + + if ((did = door_create(server_for_door, (void *)q, 0)) < 0) { + syslog(LOG_ERR, "door_create"); + exit(1); + } + + if (stat(door_name, &s) < 0) { + int newfd; + if ((newfd = creat(door_name, 0400)) < 0) { + syslog(LOG_ERR, "creat failed"); + exit(1); + } + (void) close(newfd); + } + + (void) fdetach(door_name); + if (fattach(did, door_name) < 0) { + syslog(LOG_ERR, "fattach failed errno=%d", errno); + exit(2); + } +} + +int +main(int argc, char **argv) +{ + char c, + *p, + *door_name; + msg_t *msg; + target_queue_t *q; + port_args_t port1, + port2; + Boolean_t mgmt_up = False, + daemonize = True, + console_output = True; + pthread_t junk; + mgmt_request_t *mgmt; + struct sigaction act; + struct rlimit rl; + + door_name = ISCSI_TARGET_MGMT_DOOR; + + while ((c = getopt(argc, argv, "c:d:")) != EOF) { + switch (c) { + case 'c': + config_file = optarg; + break; + case 'd': + door_name = optarg; + break; + } + } + + /* + * If the initiator closes the socket because of a protocol error + * or bad digest on the header packet we'll receive a SIGPIPE if we're + * in the middle of a write operation. There's no need to receive + * a signal when a -1 from the write will handle things correctly. + * So, ignore SIGPIPE's. + */ + (void) sigignore(SIGPIPE); + + /* + * Look at the function lu_buserr_handler() above to see the details + * of why we need to handle segmentation violations. + */ + bzero(&act, sizeof (act)); + act.sa_sigaction = lu_buserr_handler; + act.sa_flags = SA_SIGINFO; + + if (sigaction(SIGBUS, &act, NULL) == -1) { + perror("sigaction"); + exit(SMF_EXIT_ERR_CONFIG); + } + + if (process_config(config_file) == False) + exit(SMF_EXIT_ERR_CONFIG); + + if (process_target_config() == False) + exit(SMF_EXIT_ERR_CONFIG); + + (void) xml_find_value_boolean(main_config, XML_ELEMENT_DBGDAEMON, + &daemonize); + if (daemonize == True) { + switch (fork()) { + case 0: + /* ---- I'm the daemon, continue running ---- */ + break; + + case -1: + /* ---- Failed to fork!. Trouble ---- */ + exit(SMF_EXIT_ERR_CONFIG); + + default: + exit(SMF_EXIT_OK); + } + closefrom(0); + } + + q = queue_alloc(); + + /* + * Initialize the various subsystems. In most cases these are + * just initializing mutexs. + */ + (void) pthread_mutex_init(&targ_config_mutex, NULL); + iscsi_cmd_init(); + session_init(); + t10_init(q); + port_init(); + queue_init(); + util_init(); + + /* + * If there's no MAC address currently available don't worry about + * it. The first time an initiator connects the SAM-3 layer will + * attempt to create a GUID and force another look for a MAC address. + */ + if (if_find_mac(q) == False) + queue_prt(q, Q_GEN_DETAILS, "MAIN: No MAC address available"); + + /* + * At a minimum we need two file descriptors for each target, one for + * the socket and one for the backing store. If there's more than one + * initiator attached to a given target than that number goes up by 1. + * Once we have multiple sessions per connection that to will cause + * an increase. + */ + if ((getrlimit(RLIMIT_NOFILE, &rl) == 0) && + (rl.rlim_cur < TARGET_NOFILE)) { + rl.rlim_cur = TARGET_NOFILE; + if (setrlimit(RLIMIT_NOFILE, &rl) != 0) + syslog(LOG_NOTICE, + "Can't set new limit for open files"); + } + + port1.port_mgmtq = q; + port1.port_num = iscsi_port; + (void) pthread_create(&junk, NULL, port_watcher, &port1); + + setup_door(q, door_name); + if ((xml_find_value_int(main_config, XML_ELEMENT_MGMTPORT, + &port2.port_num) == True) && (port2.port_num != -1)) { + port2.port_mgmtq = q; + port2.port_dataq = queue_alloc(); + (void) pthread_create(&junk, NULL, port_management, &port2); + } + + do { + msg = queue_message_get(q); + + switch (msg->msg_type) { + case msg_log: + if ((p = strchr(msg->msg_data, '\n')) != NULL) + *p = '\0'; + p = (char *)msg->msg_data; + if ((msg->msg_pri_level & dbg_lvl) == 0) + break; + + if (mgmt_up == True) + queue_str(port2.port_dataq, Q_GEN_DETAILS, + msg_log, p); + if (console_output == True) + (void) printf("%s\n", p); + break; + + case msg_mgmt_rqst: + mgmt = (mgmt_request_t *)msg->msg_data; + if (mgmt->m_request == mgmt_parse_xml) + parse_xml(mgmt->m_u.m_node, mgmt->m_q, q); + msg->msg_data = NULL; + break; + + case msg_status: + p = (char *)msg->msg_data; + /* + * NOTE: + * These are real error conditons being sent from + * the other threads and should be logged in + * some manner, either syslog() or using a FMA + * interface. + */ + (void) printf("STATUS: %s\n", p); + break; + + default: + break; + } + + if (msg->msg_data != NULL) + free(msg->msg_data); + queue_message_free(msg); + /*CONSTANTCONDITION*/ + } while (1); + + return (0); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/mgmt.c b/usr/src/cmd/iscsi/iscsitgtd/mgmt.c new file mode 100644 index 0000000000..13bcc97105 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/mgmt.c @@ -0,0 +1,259 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <unistd.h> +#include <strings.h> +#include <stdlib.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <netinet/in.h> +#include <sys/filio.h> +#include <utility.h> +#include <synch.h> +#include <sys/stropts.h> +#include <libxml/xmlreader.h> + +#include "queue.h" +#include "port.h" +#include "utility.h" +#include "xml.h" + +static void +mgmt_monitor_queue(port_args_t *p) +{ + target_queue_t *in = p->port_dataq; + msg_t *m; + int process = True; + char *data, + *output; + + do { + m = queue_message_get(in); + switch (m->msg_type) { + case msg_conn_lost: + process = False; + break; + + case msg_log: + data = (char *)m->msg_data; + output = NULL; + xml_add_tag(&output, "log", data); + (void) write(p->port_socket, output, strlen(output)); + free(output); + break; + + case msg_mgmt_rply: + data = (char *)m->msg_data; + output = NULL; + xml_add_tag(&output, "mgmt", data); + (void) write(p->port_socket, output, strlen(output)); + free(output); + free(data); + m->msg_data = NULL; + break; + + default: + break; + } + + if (m->msg_data) + free(m->msg_data); + queue_message_free(m); + + } while (process == True); +} + +static void * +mgmt_process(void *v) +{ + port_args_t *p = (port_args_t *)v; + int nbytes, + nmsgs, + pval, + ret; + char *buf; + nfds_t nfds = 1; + struct pollfd fds[1]; + xmlTextReaderPtr r; + xml_node_t *node = NULL; + mgmt_request_t m; + + fds[0].fd = p->port_socket; + fds[0].events = POLLIN; + + m.m_q = p->port_dataq; + m.m_request = mgmt_parse_xml; + m.m_time = time(NULL); + m.m_targ_name = NULL; + + while ((pval = poll(fds, nfds, -1)) != -1) { + if ((nmsgs = ioctl(p->port_socket, FIONREAD, &nbytes)) == -1) { + + queue_message_set(p->port_dataq, 0, msg_conn_lost, 0); + break; + + } else if ((nmsgs == 0) && (nbytes == 0)) { + + queue_message_set(p->port_dataq, 0, msg_conn_lost, 0); + break; + + } else if ((buf = malloc(nbytes)) == NULL) { + + queue_message_set(p->port_dataq, 0, msg_conn_lost, 0); + break; + + } else if (read(p->port_socket, buf, nbytes) != nbytes) { + + queue_message_set(p->port_dataq, 0, msg_conn_lost, 0); + break; + + } + + buf[nbytes] = '\0'; + r = (xmlTextReaderPtr)xmlReaderForMemory(buf, nbytes, + NULL, NULL, 0); + if (r != NULL) { + ret = xmlTextReaderRead(r); + while (ret == 1) { + if (xml_process_node(r, &node) == False) + break; + ret = xmlTextReaderRead(r); + } + if (node != NULL) { + m.m_u.m_node = node; + queue_message_set(p->port_mgmtq, 0, + msg_mgmt_rqst, &m); + } + xmlFreeTextReader(r); + node = NULL; + } + + } + + if (pval == -1) + queue_message_set(p->port_dataq, 0, msg_conn_lost, 0); + (void) close(p->port_socket); + p->port_socket = -1; + return (NULL); +} + +void * +port_management(void *v) +{ + int s, + fd, + on = 1; + struct sockaddr_in sin_ip; + struct sockaddr_in6 sin6_ip; + socklen_t fromlen; + struct sockaddr_storage from; + port_args_t *p = (port_args_t *)v; + target_queue_t *q = p->port_mgmtq; + int l; + pthread_t junk; + char debug[80]; + + if ((s = socket(PF_INET6, SOCK_STREAM, 0)) == -1) { + if ((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) { + queue_str(q, Q_GEN_ERRS, msg_status, + "Can't open socket"); + return (NULL); + } else { + + bzero(&sin_ip, sizeof (sin_ip)); + sin_ip.sin_family = AF_INET; + sin_ip.sin_port = htons(p->port_num); + sin_ip.sin_addr.s_addr = INADDR_ANY; + + (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof (on)); + + if ((bind(s, (struct sockaddr *)&sin_ip, + sizeof (sin_ip))) < 0) { + (void) snprintf(debug, sizeof (debug), + "bind on port %d failed\n", p->port_num); + queue_str(q, Q_GEN_ERRS, msg_status, debug); + return (NULL); + } + } + } else { + + bzero(&sin6_ip, sizeof (sin6_ip)); + sin6_ip.sin6_family = AF_INET6; + sin6_ip.sin6_port = htons(p->port_num); + sin6_ip.sin6_addr = in6addr_any; + + (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, + sizeof (on)); + + if ((bind(s, (struct sockaddr *)&sin6_ip, sizeof (sin6_ip))) + < 0) { + (void) snprintf(debug, sizeof (debug), + "bind on port %d failed\n", + p->port_num); + queue_str(q, Q_GEN_ERRS, msg_status, debug); + return (NULL); + } + } + + if (listen(s, 5) < 0) { + queue_str(q, Q_GEN_ERRS, msg_status, "listen failed"); + return (NULL); + } + + /*CONSTANTCONDITION*/ + while (1) { + fromlen = sizeof (from); + if ((fd = accept(s, (struct sockaddr *)&from, + &fromlen)) < 0) { + queue_str(q, Q_GEN_ERRS, msg_status, "accept failed"); + return (NULL); + } + + l = 128 * 1024; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&l, + sizeof (l)) < 0) + queue_str(q, Q_GEN_ERRS, msg_status, + "setsockopt failed"); + + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&l, + sizeof (l)) < 0) + queue_str(q, Q_GEN_ERRS, msg_status, + "setsockopt failed"); + + + p->port_socket = fd; + (void) pthread_create(&junk, NULL, mgmt_process, p); + + mgmt_monitor_queue(p); + } + return (NULL); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/mgmt_create.c b/usr/src/cmd/iscsi/iscsitgtd/mgmt_create.c new file mode 100644 index 0000000000..e492c2b853 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/mgmt_create.c @@ -0,0 +1,1000 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <ctype.h> +#include <sys/types.h> +#include <time.h> +#include <sys/utsname.h> +#include <unistd.h> +#include <sys/param.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <errno.h> +#include <strings.h> +#include <sys/vtoc.h> +#include <sys/efi_partition.h> +#include <uuid/uuid.h> +#include <sys/scsi/impl/uscsi.h> +#include <sys/scsi/generic/commands.h> +#include <sys/scsi/impl/commands.h> + +#include "xml.h" +#include "queue.h" +#include "target.h" +#include "iscsi_cmd.h" +#include "utility.h" +#include "errcode.h" +#include "t10_spc.h" + +extern char *getfullrawname(); + +static char *create_target(xml_node_t *); +static char *create_initiator(xml_node_t *); +static char *create_tpgt(xml_node_t *); +static Boolean_t create_target_dir(char *targ_name, char *local_name); +static char *create_node_name(char *local_nick, char *alias); +static Boolean_t create_lun(char *targ_name, char *type, int lun, + char *size_str, char *backing, err_code_t *code); +static Boolean_t create_lun_common(char *targ_name, int lun, uint64_t size, + err_code_t *code); +static Boolean_t setup_disk_backing(err_code_t *code, char *path, char *backing, + FILE *fp, uint64_t *size); +static Boolean_t setup_raw_backing(err_code_t *code, char *path, char *backing, + uint64_t *size); + + +/* + * []---- + * | create_func -- Branch out to appropriate object create function + * []---- + */ +/*ARGSUSED*/ +void +create_func(xml_node_t *p, target_queue_t *reply, target_queue_t *mgmt) +{ + xml_node_t *x; + char msgbuf[80], + *reply_msg = NULL; + + if (p->x_child == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + } else { + x = p->x_child; + + if (x->x_name == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + } else if (strcmp(x->x_name, XML_ELEMENT_TARG) == 0) { + reply_msg = create_target(x); + } else if (strcmp(x->x_name, XML_ELEMENT_INIT) == 0) { + reply_msg = create_initiator(x); + } else if (strcmp(x->x_name, XML_ELEMENT_TPGT) == 0) { + reply_msg = create_tpgt(x); + } else { + (void) snprintf(msgbuf, sizeof (msgbuf), + "Unknown object '%s' for create element", + x->x_name); + xml_rtn_msg(&reply_msg, ERR_INVALID_OBJECT); + } + } + queue_message_set(reply, 0, msg_mgmt_rply, reply_msg); +} + +static char * +create_target(xml_node_t *x) +{ + char *msg = NULL, + *name = NULL, + *alias = NULL, + *size = NULL, + *type = NULL, + *backing = NULL, + *node_name = NULL, + path[MAXPATHLEN]; + int lun = 0, /* default to LUN 0 */ + i; + xml_node_t *n, + *c, + *l; + err_code_t code; + + (void) xml_find_value_str(x, XML_ELEMENT_BACK, &backing); + (void) xml_find_value_str(x, XML_ELEMENT_ALIAS, &alias); + if (xml_find_value_intchk(x, XML_ELEMENT_LUN, &lun) == False) { + xml_rtn_msg(&msg, ERR_LUN_INVALID_RANGE); + goto error; + } + + /* + * We've got to have a name element or all bets are off. + */ + if (xml_find_value_str(x, XML_ELEMENT_NAME, &name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + goto error; + } + + /* + * RFC3722 states that names must be one of: + * (1) a..z + * (2) A..Z + * (3) 0-9 + * or + * (4) ':', '.', '-' + * If it's an upper case character is must be made lower + * case. + */ + for (i = 0; i < strlen(name); i++) { + if (!isalnum(name[i]) && + (name[i] != ':') && (name[i] != '.') && (name[i] != '-')) { + xml_rtn_msg(&msg, ERR_SYNTAX_INVALID_NAME); + goto error; + } else if (isupper(name[i])) + name[i] = tolower(name[i]); + } + + if (xml_find_value_str(x, XML_ELEMENT_TYPE, &type) == False) { + /* + * If a type hasn't been specified default to disk emulation. + * We use strdup() since at the end of this routine the code + * is expecting to free 'type' along with other strings. + */ + type = strdup(TGT_TYPE_DISK); + } + + if ((strcmp(type, "raw") == 0) && (backing == NULL)) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_BACKING_STORE); + goto error; + } + + if (xml_find_value_str(x, XML_ELEMENT_SIZE, &size) == False) { + if (backing != NULL) { + + /* + * If a backing store has been provided we don't + * need the size since we can determine that from + * a READ_CAPACITY command which everyone issues. + * + * NOTE: strdup is used here, since at the end + * of this routine any of the string pointers which + * are non-NULL get freed. + */ + size = strdup("0"); + } else { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_SIZE); + goto error; + } + } + if ((lun < 0) || (lun > T10_MAX_LUNS)) { + xml_rtn_msg(&msg, ERR_LUN_INVALID_RANGE); + goto error; + } + + /* + * See if we already have a local target name created. If so, + * the user is most likely wanting to create another LUN for this + * target. Checking to see if there's a duplicate LUN will be + * done later. + */ + for (n = targets_config->x_child; n; n = n->x_sibling) { + if (strcmp(n->x_value, name) == 0) + break; + } + + if (n == NULL) { + if (lun != 0) { + xml_rtn_msg(&msg, ERR_LUN_ZERO_NOT_FIRST); + goto error; + } + if ((node_name = create_node_name(name, alias)) == NULL) { + xml_rtn_msg(&msg, ERR_CREATE_NAME_TO_LONG); + goto error; + } + if (create_target_dir(node_name, name) == False) { + xml_rtn_msg(&msg, ERR_CREATE_TARGET_DIR_FAILED); + goto error; + } + n = xml_alloc_node(XML_ELEMENT_TARG, String, name); + c = xml_alloc_node(XML_ELEMENT_INAME, String, node_name); + (void) xml_add_child(n, c); + c = xml_alloc_node(XML_ELEMENT_LUNLIST, String, ""); + l = xml_alloc_node(XML_ELEMENT_LUN, Int, &lun); + (void) xml_add_child(c, l); + (void) xml_add_child(n, c); + if (alias != NULL) { + c = xml_alloc_node(XML_ELEMENT_ALIAS, String, alias); + (void) xml_add_child(n, c); + } + (void) xml_add_child(targets_config, n); + + } else { + if (xml_find_value_str(n, XML_ELEMENT_INAME, + &node_name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_INAME); + goto error; + } + if ((c = xml_node_next(n, XML_ELEMENT_LUNLIST, NULL)) == NULL) { + xml_rtn_msg(&msg, ERR_INTERNAL_ERROR); + goto error; + } + l = xml_alloc_node(XML_ELEMENT_LUN, Int, &lun); + (void) xml_add_child(c, l); + } + + if (create_lun(node_name, type, lun, size, backing, &code) == True) { + + if (update_config_targets(&msg) == False) + goto error; + + } else if ((lun == 0) && (code != ERR_LUN_EXISTS)) { + + /* + * The first LU will have created the directory and + * symbolic link. Remove those on error. + */ + (void) snprintf(path, sizeof (path), "%s/%s", + target_basedir, node_name); + rmdir(path); + (void) snprintf(path, sizeof (path), "%s/%s", + target_basedir, name); + unlink(path); + xml_remove_child(targets_config, n, MatchBoth); + xml_tree_free(n); + } else + xml_remove_child(c, l, MatchBoth); + + xml_rtn_msg(&msg, code); + +error: + if (name != NULL) + free(name); + if (size != NULL) + free(size); + if (alias != NULL) + free(name); + if (backing != NULL) + free(backing); + if (node_name != NULL) + free(node_name); + if (type != NULL) + free(type); + + return (msg); +} + +static char * +create_initiator(xml_node_t *x) +{ + char *msg = NULL, + *name = NULL, + *iscsi_name = NULL; + xml_node_t *inode = NULL, + *n, + *c; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + goto error; + } + if (xml_find_value_str(x, XML_ELEMENT_INAME, &iscsi_name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_INAME); + goto error; + } + if (strlen(iscsi_name) >= ISCSI_MAX_NAME_LEN) { + xml_rtn_msg(&msg, ERR_NAME_TO_LONG); + goto error; + } + + while ((inode = xml_node_next(main_config, XML_ELEMENT_INIT, + inode)) != NULL) { + if (strcmp(inode->x_value, name) == 0) { + xml_rtn_msg(&msg, ERR_INIT_EXISTS); + goto error; + } + } + + n = xml_alloc_node(XML_ELEMENT_INIT, String, name); + c = xml_alloc_node(XML_ELEMENT_INAME, String, iscsi_name); + (void) xml_add_child(n, c); + (void) xml_add_child(main_config, n); + + if (update_config_main(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + +error: + if (name) + free(name); + if (iscsi_name) + free(iscsi_name); + + return (msg); +} + +static char * +create_tpgt(xml_node_t *x) +{ + char *msg = NULL, + *tpgt = NULL, + *extra = NULL; + xml_node_t *tnode = NULL, + *n; + int tpgt_val; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &tpgt) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + goto error; + } + + /* ---- Validation checks ---- */ + tpgt_val = strtol(tpgt, &extra, 0); + if ((extra && (*extra != '\0')) || + (tpgt_val < TPGT_MIN) || (tpgt_val > TPGT_MAX)) { + xml_rtn_msg(&msg, ERR_INVALID_TPGT); + goto error; + } + + while ((tnode = xml_node_next(main_config, XML_ELEMENT_TPGT, + tnode)) != NULL) { + if (strcmp(tnode->x_value, tpgt) == 0) { + xml_rtn_msg(&msg, ERR_TPGT_EXISTS); + goto error; + } + } + + n = xml_alloc_node(XML_ELEMENT_TPGT, String, tpgt); + (void) xml_add_child(main_config, n); + + if (update_config_main(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + +error: + if (tpgt) + free(tpgt); + + return (msg); +} + +/* + * []------------------------------------------------------------------[] + * | Utility functions used by the routines above. | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | create_node_name -- Creates the IQN that adhears to RFC3270 + * []---- + */ +static char * +create_node_name(char *local_nick, char *alias) +{ + uuid_t id; + char id_str[37], + *p; + + if ((p = (char *)malloc(ISCSI_MAX_NAME_LEN)) == NULL) + return (NULL); + + /* + * Originally we we going to use the machines MAC address and + * timestamp in hex format. This would be consistent with the + * Solaris iSCSI initiator and NAS5310. Unfortunately, someone + * pointed out that there's no requirement that the network + * interfaces be plumbed before someone attempts to create + * targets. If the networks aren't plumbed there are no MAC + * addresses available and we can't use 0 for the MAC address + * since that would introduce the probability of non-unique + * IQN names. + */ + uuid_generate(id); + uuid_unparse(id, id_str); + if (snprintf(p, ISCSI_MAX_NAME_LEN, "iqn.1986-03.com.sun:%02d:%s.%s", + TARGET_NAME_VERS, id_str, alias != NULL ? alias : local_nick) > + ISCSI_MAX_NAME_LEN) { + free(p); + return (NULL); + } + + return (p); +} + +/* + * []---- + * | create_target_dir -- create the target directory + * []---- + */ +static Boolean_t +create_target_dir(char *targ_name, char *local_name) +{ + char path[MAXPATHLEN], + sympath[MAXPATHLEN]; + + if ((mkdir(target_basedir, 0777) == -1) && (errno != EEXIST)) + return (False); + + (void) snprintf(path, sizeof (path), "%s/%s", target_basedir, + targ_name); + (void) snprintf(sympath, sizeof (sympath), "%s/%s", target_basedir, + local_name); + + if ((mkdir(path, 0777) == -1) && (errno != EEXIST)) + return (False); + + /* + * This symbolic link is here for convenience and nothing more, so if + * if fails. Oh well. + */ + (void) symlink(path, sympath); + return (True); +} + +/* + * []---- + * | create_lun -- given type, lun, size, backing create LU and params + * []---- + */ +static Boolean_t +create_lun(char *targ_name, char *type, int lun, char *size_str, char *backing, + err_code_t *code) +{ + uint64_t size; + int fd = -1, + rpm = DEFAULT_RPM, + heads = DEFAULT_HEADS, + cylinders = DEFAULT_CYLINDERS, + spt = DEFAULT_SPT, + bytes_sect = DEFAULT_BYTES_PER, + interleave = DEFAULT_INTERLEAVE; + char *vid = DEFAULT_VID, + *pid = DEFAULT_PID, + path[MAXPATHLEN]; + FILE *fp = NULL; + + /* + * after calling stroll_multipler it's an error for size to be + * 0, if and only if, the size_str doesn't equal "0". The administrator + * may want the code to determine the size. This would be the case + * when the administrator has provide a backing store which exists. + */ + if ((strtoll_multiplier(size_str, &size) == False) || + ((size == 0) && (size_str != NULL) && strcmp(size_str, "0"))) { + *code = ERR_INVALID_SIZE; + return (False); + } + + if ((size % 512) != 0) { + *code = ERR_SIZE_MOD_BLOCK; + return (False); + } + + /* + * Make sure we're not trying to recreate an existing LU. + */ + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + targ_name, PARAMBASE, lun); + if (access(path, F_OK) == 0) { + *code = ERR_LUN_EXISTS; + return (False); + } + + if ((fp = fopen(path, "w+")) == NULL) { + *code = ERR_OPEN_PARAM_FILE_FAILED; + return (False); + } + + (void) fprintf(fp, "<%s version='1.0'>\n", XML_ELEMENT_PARAMS); + (void) fprintf(fp, "\t<%s>0</%s>\n", XML_ELEMENT_GUID, + XML_ELEMENT_GUID); + (void) fprintf(fp, "\t<%s>%s</%s>\n", XML_ELEMENT_PID, pid, + XML_ELEMENT_PID); + (void) fprintf(fp, "\t<%s>%s</%s>\n", XML_ELEMENT_VID, vid, + XML_ELEMENT_VID); + (void) fprintf(fp, "\t<%s>%s</%s>\n", XML_ELEMENT_DTYPE, type, + XML_ELEMENT_DTYPE); + + if (strcmp(type, TGT_TYPE_DISK) == 0) { + + (void) snprintf(path, sizeof (path), "%s/%s/lun.%d", + target_basedir, targ_name, lun); + if (setup_disk_backing(code, path, backing, fp, &size) == False) + goto error; + + create_geom(size, &cylinders, &heads, &spt); + + (void) fprintf(fp, "\t<%s>%d</%s>\n", XML_ELEMENT_RPM, rpm, + XML_ELEMENT_RPM); + (void) fprintf(fp, "\t<%s>%d</%s>\n", XML_ELEMENT_HEADS, heads, + XML_ELEMENT_HEADS); + (void) fprintf(fp, "\t<%s>%d</%s>\n", XML_ELEMENT_CYLINDERS, + cylinders, XML_ELEMENT_CYLINDERS); + (void) fprintf(fp, "\t<%s>%d</%s>\n", XML_ELEMENT_SPT, spt, + XML_ELEMENT_SPT); + (void) fprintf(fp, "\t<%s>%d</%s>\n", XML_ELEMENT_BPS, + bytes_sect, XML_ELEMENT_BPS); + (void) fprintf(fp, "\t<%s>%d</%s>\n", XML_ELEMENT_INTERLEAVE, + interleave, XML_ELEMENT_INTERLEAVE); + (void) fprintf(fp, "\t<%s>%s</%s>\n", XML_ELEMENT_STATUS, + TGT_STATUS_OFFLINE, XML_ELEMENT_STATUS); + + } else if (strcmp(type, TGT_TYPE_TAPE) == 0) { +#ifndef _LP64 + *code = ERR_TAPE_NOT_SUPPORTED_IN_32BIT; + goto error; +#else + (void) fprintf(fp, "\t<%s>%s</%s>\n", XML_ELEMENT_STATUS, + TGT_STATUS_OFFLINE, XML_ELEMENT_STATUS); + (void) snprintf(path, sizeof (path), "%s/%s/lun.%d", + target_basedir, targ_name, lun); + if (setup_disk_backing(code, path, backing, fp, &size) == False) + goto error; +#endif + + } else if (strcmp(type, TGT_TYPE_RAW) == 0) { + + (void) fprintf(fp, "\t<%s>%s</%s>\n", XML_ELEMENT_STATUS, + TGT_STATUS_ONLINE, XML_ELEMENT_STATUS); + backing = getfullrawname(backing); + if (setup_raw_backing(code, path, backing, &size) == False) + goto error; + + (void) fprintf(fp, "\t<%s>false</%s>\n", XML_ELEMENT_MMAP_LUN, + XML_ELEMENT_MMAP_LUN); + + (void) snprintf(path, sizeof (path), "%s/%s/lun.%d", + target_basedir, targ_name, lun); + if (symlink(backing, path)) { + *code = ERR_CREATE_SYMLINK_FAILED; + goto error; + } + + iscsi_inventory_change(targ_name); + + } else if (strcmp(type, TGT_TYPE_OSD) == 0) { + + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", + target_basedir, targ_name, OSDBASE, lun); + if (mkdir(path, 0700) != 0) + goto error; + } + + + /* + * Wait to set the size until here because it may be unknown until + * the possible backing store has been setup. + */ + (void) fprintf(fp, "\t<%s>0x%llx</%s>\n", XML_ELEMENT_SIZE, + size / 512LL, XML_ELEMENT_SIZE); + if (backing != NULL) { + (void) fprintf(fp, "\t<%s>%s</%s>\n", XML_ELEMENT_BACK, + backing, XML_ELEMENT_BACK); + } + + (void) fprintf(fp, "</%s>\n", XML_ELEMENT_PARAMS); + (void) fclose(fp); + fp = NULL; + + if ((strcmp(type, TGT_TYPE_DISK) == 0) || + (strcmp(type, TGT_TYPE_TAPE) == 0)) { + if (create_lun_common(targ_name, lun, size, code) == False) + goto error; + } + + *code = ERR_SUCCESS; + return (True); + +error: + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + targ_name, PARAMBASE, lun); + (void) unlink(path); + if (fd == -1) + (void) close(fd); + if (fp != NULL) + (void) fclose(fp); + return (False); +} + +/* + * []---- + * | create_lun_common -- create LU and start provisioning if needed + * | + * | This function is common to both the tape and disk emulation + * | code. + * []---- + */ +static Boolean_t +create_lun_common(char *targ_name, int lun, uint64_t size, err_code_t *code) +{ + struct stat s; + int fd = -1; + char path[MAXPATHLEN], + buf[512]; + struct statvfs fs; + xml_node_t *node = NULL, + *c; + xmlTextReaderPtr r; + + /* + * Touch the last block of the file which will cause file systems + * to understand the intent of the file to be a certain size. The + * space isn't allocated, but the daemon can then mmap in this file + * and start writing to it. + */ + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", + target_basedir, targ_name, LUNBASE, lun); + if ((fd = open(path, O_RDWR|O_CREAT|O_LARGEFILE, 0600)) < 0) + goto error; + + (void) lseek(fd, size - 512LL, 0); + if (write(fd, buf, sizeof (buf)) != sizeof (buf)) { + unlink(path); + if (errno == EFBIG) + *code = ERR_FILE_TO_BIG; + else + *code = ERR_FAILED_TO_CREATE_LU; + goto error; + } + (void) close(fd); + + /* + * Set the fd back to -1 so that if an error occurs we don't + * attempt to close this device twice. This could be an issue + * if another thread opened a file right after we closed this + * one and the system reused the file descriptor. During an + * error we would then close another threads file which would + * be ugly, not to mention difficult to track down. + */ + fd = -1; + + if (stat(path, &s) != 0) { + *code = ERR_FAILED_TO_CREATE_LU; + goto error; + } + + /* + * If the backing store is a regular file and the default is + * used which initializes the file instead of sparse allocation + * go ahead a set things up. + */ + if ((thin_provisioning == False) && ((s.st_mode & S_IFMT) == S_IFREG)) { + thick_provo_t *tp; + pthread_t junk; + + /* + * Attempt to see if there is enough space currently + * for the LU. The initialization might still fail + * with "out of space" because someone else is + * consuming space while the initialization is occuring. + * Nothing we can do about that. + */ + if (statvfs(path, &fs) != 0) { + queue_prt(mgmtq, Q_GEN_ERRS, + "GEN statvfs failed for %s", path); + *code = ERR_FAILED_TO_CREATE_LU; + goto error; + } else if ((fs.f_frsize * fs.f_bfree) < size) { + queue_prt(mgmtq, Q_STE_ERRS, + "GEN Not enough space for LU"); + *code = ERR_FILE_TO_BIG; + goto error; + } + + /* + * Run the initialization thread in the background so that + * the administrator doesn't have to wait which for UFS could + * be a long time on a large LU. + */ + if ((tp = calloc(1, sizeof (*tp))) != NULL) { + tp->targ_name = strdup(targ_name); + tp->lun = lun; + tp->q = queue_alloc(); + (void) pthread_create(&junk, NULL, + thick_provo_start, tp); + + /* + * As soon as the thread starts it will send a simple + * ACK to it's own queue that we can look for. When + * we see this message we know that the thread has + * started and it's been added to the provisioning + * list. If this were not done it's possible for someone + * to create and delete a target within a script and + * have the delete run and fail to find the provision + * thread in the list. + */ + queue_message_free(queue_message_get(tp->q)); + } + } else { + + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", + target_basedir, targ_name, PARAMBASE, lun); + if (r = (xmlTextReaderPtr)xmlReaderForFile(path, NULL, 0)) { + while (xmlTextReaderRead(r) == 1) + if (xml_process_node(r, &node) == False) + break; + c = xml_alloc_node(XML_ELEMENT_STATUS, String, + TGT_STATUS_ONLINE); + xml_replace_child(node, c, MatchName); + xml_tree_free(c); + if (xml_dump2file(node, path) == False) { + queue_prt(mgmtq, Q_STE_ERRS, + "GEN%d failed to dump out params", lun); + goto error; + } + xml_tree_free(node); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + xmlCleanupParser(); + + /* + * The thick_provo_start will issue an inventory + * change once it's finished. + */ + iscsi_inventory_change(targ_name); + } else + return (False); + } + + return (True); + +error: + if (fd != -1) + (void) close(fd); + return (False); +} + +static Boolean_t +readvtoc(int fd, struct vtoc *v, int *slice) +{ + if ((*slice = read_vtoc(fd, v)) >= 0) + return (True); + else + return (False); +} + +static Boolean_t +readefi(int fd, struct dk_gpt **efi, int *slice) +{ + if ((*slice = efi_alloc_and_read(fd, efi)) >= 0) + return (True); + else + return (False); +} + +/* + * []---- + * | setup_alt_backing -- use backing store link for regular file lun + * | + * | If the size is zero, then the administrator MUST have + * | specified a backing store to use. + * | If the size is non-zero and the backing store doesn't exist it will + * | be created. Also a tag will be added indicating that during removal + * | the backing store should be deleted as well. + * []---- + */ +static Boolean_t +setup_disk_backing(err_code_t *code, char *path, char *backing, FILE *fp, + uint64_t *size) +{ + struct stat s; + char *raw_name, + buf[512]; + struct vtoc vtoc; + struct dk_gpt *efi; + int slice, + fd; + + /* + * Error checking regarding size and backing store has already + * been done. If the backing store is null at this point everything + * is okay so just return True. + */ + if (backing == NULL) + return (True); + + if (stat(backing, &s) == -1) { + if (*size == 0) { + *code = ERR_STAT_BACKING_FAILED; + return (False); + } else { + (void) fprintf(fp, "\t<%s>true</%s>\n", + XML_ELEMENT_DELETE_BACK, XML_ELEMENT_DELETE_BACK); + if ((fd = open(backing, O_RDWR|O_CREAT|O_LARGEFILE, + 0600)) < 0) { + *code = ERR_FAILED_TO_CREATE_LU; + return (False); + } + lseek(fd, *size - 512LL, 0); + (void) write(fd, buf, sizeof (buf)); + close(fd); + } + } else if (*size != 0) { + *code = ERR_DISK_BACKING_SIZE_OR_FILE; + return (False); + } else if (((s.st_mode & S_IFMT) == S_IFCHR) || + ((s.st_mode & S_IFMT) == S_IFBLK)) { + raw_name = getfullrawname(backing); + if ((raw_name == NULL) || + ((fd = open(raw_name, O_NONBLOCK|O_RDONLY)) < 0)) { + *code = ERR_DISK_BACKING_NOT_VALID_RAW; + (void) close(fd); + return (False); + } + if (readvtoc(fd, &vtoc, &slice) == True) { + *size = (long long)vtoc.v_part[slice].p_size * 512; + + } else if (readefi(fd, &efi, &slice) == True) { + *size = efi->efi_parts[slice].p_size * 512; + efi_free(efi); + } else { + *code = ERR_DISK_BACKING_NOT_VALID_RAW; + (void) close(fd); + return (False); + } + (void) close(fd); + + } else if ((s.st_mode & S_IFMT) == S_IFREG) { + *size = s.st_size; + } else { + *code = ERR_DISK_BACKING_MUST_BE_REGULAR_FILE; + return (False); + } + + if (symlink(backing, path)) { + *code = ERR_CREATE_SYMLINK_FAILED; + return (False); + } + + return (True); +} + +/* + * []---- + * | validate_raw_backing -- check that device is full partition + * | + * | The size of the device will be returned in rtn_size in bytes. + * | + * | Need to guarantee that the backing store for a raw device is: + * | (a) character device + * | (b) Not buffered + * | Don't want this host to have data which is not flushed + * | out during a write since a multiple path access to + * | the backing store would be possible meaning we'd have + * | cache issue. + * | (c) read/write will access entire device. + * | To speed things up we use asynchronous I/O which means + * | the path has to have access to the entire device through + * | the partition table. If not, some client will issue a + * | READ_CAPACITY command, but not be able to access all of + * | the data. + * []---- + */ +static Boolean_t +setup_raw_backing(err_code_t *code, char *path, char *backing, + uint64_t *rtn_size) +{ + struct stat s; + char buf[512]; + int fd; + uint64_t size; + struct uscsi_cmd u; + struct scsi_extended_sense sense; + union scsi_cdb cdb; + struct scsi_capacity cap; + struct scsi_capacity_16 cap16; + int cap_len = sizeof (cap16); + size_t cc; + Boolean_t rval = False; + + if (stat(backing, &s) == -1) { + *code = ERR_ACCESS_RAW_DEVICE_FAILED; + return (False); + } else if (((s.st_mode & S_IFMT) != S_IFCHR) && + ((s.st_mode & S_IFMT) != S_IFBLK)) { + *code = ERR_DISK_BACKING_NOT_VALID_RAW; + return (False); + } + + if ((backing == NULL) || + ((fd = open(backing, O_NDELAY|O_RDONLY|O_LARGEFILE)) < 0)) { + *code = ERR_DISK_BACKING_NOT_VALID_RAW; + (void) close(fd); + return (False); + } + + bzero(&u, sizeof (u)); + bzero(&cdb, sizeof (cdb)); + bzero(&cap, sizeof (cap)); + bzero(&sense, sizeof (sense)); + + cdb.scc_cmd = 0x25; /* ---- READ_CAPACITY(10) ---- */ + + u.uscsi_cdb = (caddr_t)&cdb; + u.uscsi_cdblen = CDB_GROUP1; + u.uscsi_bufaddr = (caddr_t)∩ + u.uscsi_buflen = sizeof (cap); + u.uscsi_flags = USCSI_READ | USCSI_RQENABLE; + u.uscsi_rqbuf = (char *)&sense; + u.uscsi_rqlen = sizeof (sense); + + if ((ioctl(fd, USCSICMD, &u) != 0) || (u.uscsi_status != 0)) { + queue_prt(mgmtq, Q_GEN_DETAILS, "GEN0 uscsi(READ_CAP) failed"); + *code = ERR_DISK_BACKING_NOT_VALID_RAW; + rval = False; + goto error; + } + + if (cap.capacity == 0xffffffff) { + + bzero(&u, sizeof (u)); + bzero(&cdb, sizeof (cdb)); + bzero(&sense, sizeof (sense)); + /* + * The device is to large for the 10byte CDB. + * Using the larger 16byte read capacity command + */ + cdb.scc_cmd = 0x9E; + cdb.g4_reladdr = 0x10; + cdb.g4_count3 = hibyte(hiword(cap_len)); + cdb.g4_count2 = lobyte(hiword(cap_len)); + cdb.g4_count1 = hibyte(loword(cap_len)); + cdb.g4_count0 = lobyte(loword(cap_len)); + + u.uscsi_cdb = (caddr_t)&cdb; + u.uscsi_cdblen = CDB_GROUP4; + u.uscsi_bufaddr = (caddr_t)&cap16; + u.uscsi_buflen = sizeof (cap16); + u.uscsi_flags = USCSI_READ | USCSI_RQENABLE; + u.uscsi_rqbuf = (char *)&sense; + u.uscsi_rqlen = sizeof (sense); + + if ((ioctl(fd, USCSICMD, &u) != 0) || (u.uscsi_status != 0)) { + queue_prt(mgmtq, Q_GEN_DETAILS, + "GEN0 uscsi(READ_CAP16) failed"); + *code = ERR_DISK_BACKING_NOT_VALID_RAW; + rval = False; + goto error; + } + + size = ntohll(cap16.sc_capacity) - 1; + } else + size = (uint64_t)ntohl(cap.capacity) - 1; + + if ((cc = pread(fd, buf, sizeof (buf), size * 512LL)) != sizeof (buf)) { + queue_prt(mgmtq, Q_GEN_DETAILS, + "GEN0 Partition size != capacity(0x%llx), cc=%d, errno=%d", + size, cc, errno); + *code = ERR_RAW_PART_NOT_CAP; + rval = False; + goto error; + } else { + *rtn_size = size * 512LL; + rval = True; + } + +error: + (void) close(fd); + return (rval); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/mgmt_list.c b/usr/src/cmd/iscsi/iscsitgtd/mgmt_list.c new file mode 100644 index 0000000000..fef771b514 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/mgmt_list.c @@ -0,0 +1,447 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <time.h> +#include <sys/utsname.h> +#include <unistd.h> +#include <sys/param.h> +#include <fcntl.h> +#include <errno.h> +#include <strings.h> +#include <dirent.h> + +#include "utility.h" +#include <xml.h> +#include "queue.h" +#include "target.h" +#include "iscsi_cmd.h" +#include "iscsi_conn.h" +#include "port.h" +#include "errcode.h" + +static char *list_targets(xml_node_t *x); +static char *list_initiator(xml_node_t *x); +static char *list_tpgt(xml_node_t *x); +static char *list_admin(xml_node_t *x); +static void target_info(char **msg, char *targ_name, xml_node_t *tnode); +static void target_stat(char **msg, char *iname, mgmt_type_t type); + +/*ARGSUSED*/ +void +list_func(xml_node_t *p, target_queue_t *reply, target_queue_t *mgmt) +{ + xml_node_t *x; + char msgbuf[80], + *reply_msg = NULL; + + if (p->x_child == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + } else { + x = p->x_child; + + if (x->x_name == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + } else if (strcmp(x->x_name, XML_ELEMENT_TARG) == 0) { + reply_msg = list_targets(x); + } else if (strcmp(x->x_name, XML_ELEMENT_INIT) == 0) { + reply_msg = list_initiator(x); + } else if (strcmp(x->x_name, XML_ELEMENT_TPGT) == 0) { + reply_msg = list_tpgt(x); + } else if (strcmp(x->x_name, XML_ELEMENT_ADMIN) == 0) { + reply_msg = list_admin(x); + } else { + (void) snprintf(msgbuf, sizeof (msgbuf), + "Unknown object '%s' for list element", + x->x_name); + xml_rtn_msg(&reply_msg, ERR_INVALID_OBJECT); + } + } + queue_message_set(reply, 0, msg_mgmt_rply, reply_msg); +} + +static char * +list_targets(xml_node_t *x) +{ + char *msg = NULL, + *prop = NULL, + *iname = NULL; + xml_node_t *targ = NULL; + Boolean_t luninfo = False, + dostat = False; + + /* + * It's okay to not supply a "name" element. That just means the + * administrator wants a complete list of targets. However if a + * "name" is supplied then there must be a value for that element. + */ + if ((xml_find_value_str(x, XML_ELEMENT_NAME, &prop) == True) && + (prop == NULL)) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + + /* ---- optional arguments ---- */ + (void) xml_find_value_boolean(x, XML_ELEMENT_LUNINFO, &luninfo); + (void) xml_find_value_boolean(x, XML_ELEMENT_IOSTAT, &dostat); + + buf_add_tag_and_attr(&msg, XML_ELEMENT_RESULT, "version='1.0'"); + while ((targ = xml_node_next(targets_config, XML_ELEMENT_TARG, + targ)) != NULL) { + if (targ->x_value == NULL) { + xml_add_tag(&msg, XML_ELEMENT_TARG, + "bogus entry"); + continue; + } + if (xml_find_value_str(targ, XML_ELEMENT_INAME, &iname) == + False) { + xml_add_tag(&msg, XML_ELEMENT_TARG, + "missing iscsi-name"); + continue; + } + if (prop != NULL) { + if (strcmp(prop, targ->x_value) == 0) { + buf_add_tag(&msg, XML_ELEMENT_TARG, Tag_Start); + buf_add_tag(&msg, targ->x_value, Tag_String); + xml_add_tag(&msg, XML_ELEMENT_INAME, iname); + if (luninfo == True) + target_info(&msg, iname, targ); + if (dostat == True) + target_stat(&msg, iname, + mgmt_full_phase_statistics); + buf_add_tag(&msg, XML_ELEMENT_TARG, Tag_End); + } + } else { + buf_add_tag(&msg, XML_ELEMENT_TARG, Tag_Start); + buf_add_tag(&msg, targ->x_value, Tag_String); + xml_add_tag(&msg, XML_ELEMENT_INAME, iname); + if (dostat == True) + target_stat(&msg, iname, + mgmt_full_phase_statistics); + if (luninfo == True) + target_info(&msg, iname, targ); + buf_add_tag(&msg, XML_ELEMENT_TARG, Tag_End); + } + free(iname); + } + buf_add_tag(&msg, XML_ELEMENT_RESULT, Tag_End); + free(prop); + + return (msg); +} + +static char * +list_initiator(xml_node_t *x) +{ + char *msg = NULL, + *attr, + *prop = NULL; + Boolean_t verbose = False; + xml_node_t *init = NULL; + + /* ---- Optional arguments ---- */ + if ((xml_find_value_str(x, XML_ELEMENT_NAME, &prop) == True) && + (prop == NULL)) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + (void) xml_find_value_boolean(x, XML_ELEMENT_VERBOSE, &verbose); + + buf_add_tag_and_attr(&msg, XML_ELEMENT_RESULT, "version='1.0'"); + while ((init = xml_node_next(main_config, XML_ELEMENT_INIT, init)) != + NULL) { + if ((prop == NULL) || + ((prop != NULL) && (strcmp(prop, init->x_value) == 0))) { + + buf_add_tag(&msg, XML_ELEMENT_INIT, Tag_Start); + buf_add_tag(&msg, init->x_value, Tag_String); + + if (xml_find_value_str(init, XML_ELEMENT_INAME, + &attr) == True) { + xml_add_tag(&msg, XML_ELEMENT_INAME, attr); + free(attr); + } + + if (xml_find_value_str(init, XML_ELEMENT_CHAPSECRET, + &attr) == True) { + xml_add_tag(&msg, XML_ELEMENT_CHAPSECRET, + attr); + free(attr); + } + + if (xml_find_value_str(init, XML_ELEMENT_CHAPNAME, + &attr) == True) { + xml_add_tag(&msg, XML_ELEMENT_CHAPNAME, attr); + free(attr); + } + + buf_add_tag(&msg, XML_ELEMENT_INIT, Tag_End); + } + } + + if (prop != NULL) + free(prop); + + buf_add_tag(&msg, XML_ELEMENT_RESULT, Tag_End); + + return (msg); +} + +static char * +list_tpgt(xml_node_t *x) +{ + char *msg = NULL, + *prop = NULL; + Boolean_t verbose = False; + xml_node_t *tpgt = NULL, + *ip = NULL; + + /* ---- Optional arguments ---- */ + if ((xml_find_value_str(x, XML_ELEMENT_NAME, &prop) == True) && + (prop == NULL)) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + (void) xml_find_value_boolean(x, XML_ELEMENT_VERBOSE, &verbose); + + buf_add_tag_and_attr(&msg, XML_ELEMENT_RESULT, "version='1.0'"); + while ((tpgt = xml_node_next(main_config, XML_ELEMENT_TPGT, tpgt)) != + NULL) { + if ((prop == NULL) || + ((prop != NULL) && (strcmp(prop, tpgt->x_value) == 0))) { + + buf_add_tag(&msg, XML_ELEMENT_TPGT, Tag_Start); + buf_add_tag(&msg, tpgt->x_value, Tag_String); + + while ((ip = xml_node_next(tpgt, XML_ELEMENT_IPADDR, + ip)) != NULL) { + xml_add_tag(&msg, ip->x_name, ip->x_value); + } + + buf_add_tag(&msg, XML_ELEMENT_TPGT, Tag_End); + } + } + buf_add_tag(&msg, XML_ELEMENT_RESULT, Tag_End); + + if (prop != NULL) + free(prop); + return (msg); +} + +/*ARGSUSED*/ +static char * +list_admin(xml_node_t *x) +{ + char *msg = NULL; + admin_table_t *p; + xml_node_t *node = NULL; + + buf_add_tag_and_attr(&msg, XML_ELEMENT_RESULT, "version='1.0'"); + buf_add_tag(&msg, XML_ELEMENT_ADMIN, Tag_Start); + + node = NULL; + for (p = admin_prop_list; p->name != NULL; p++) { + node = xml_node_next_child(main_config, p->name, NULL); + if (node) { + xml_add_tag(&msg, p->name, node->x_value); + } + } + + buf_add_tag(&msg, XML_ELEMENT_ADMIN, Tag_End); + buf_add_tag(&msg, XML_ELEMENT_RESULT, Tag_End); + + return (msg); +} + +static void +target_stat(char **msg, char *targ_name, mgmt_type_t type) +{ + iscsi_conn_t *c; + msg_t *m; + target_queue_t *q = queue_alloc(); + mgmt_request_t mgmt_rqst; + int msg_sent, + i; + extern pthread_mutex_t port_mutex; + + mgmt_rqst.m_q = q; + mgmt_rqst.m_u.m_resp = msg; + mgmt_rqst.m_time = time(NULL); + mgmt_rqst.m_request = type; + (void) pthread_mutex_init(&mgmt_rqst.m_resp_mutex, NULL); + + (void) pthread_mutex_lock(&port_mutex); + mgmt_rqst.m_targ_name = targ_name; + msg_sent = 0; + for (c = conn_head; c; c = c->c_next) { + if (c->c_state == S5_LOGGED_IN) { + /* + * Only send requests for statistics to + * connections that are up. Could even + * go further and only look at connections + * which are S5_LOGGED_IN, but there may + * be statistics, such as connection time, + * which we'd like to have. + */ + queue_message_set(c->c_dataq, 0, msg_mgmt_rqst, + &mgmt_rqst); + msg_sent++; + } + } + (void) pthread_mutex_unlock(&port_mutex); + + /* + * Comment: main.c:list_targets:1 + * We wait for the responses without the port_mutex + * being held. There is a small window between when the + * connection last listens for a message and when the + * queue is freed. During that time the connection will + * attempt to grab the port_mutex lock so that it + * can unlink itself and call queueu_free(). If we sent + * the message with the lock held and then wait for a response + * it's possible that the connection will deadlock waiting + * to get the port_mutex. + */ + for (i = 0; i < msg_sent; i++) { + m = queue_message_get(q); + queue_message_free(m); + } + queue_free(q, NULL); +} + +static void +target_info(char **msg, char *targ_name, xml_node_t *tnode) +{ + char path[MAXPATHLEN], + lun_buf[16], + *prop; + xmlTextReaderPtr r; + xml_node_t *lnode, /* list node */ + *lnp, /* list node pointer */ + *lun, + *params; + int xml_fd, + lun_num; + + if ((lnode = xml_node_next(tnode, XML_ELEMENT_ACLLIST, NULL)) != + NULL) { + lnp = NULL; + buf_add_tag(msg, XML_ELEMENT_ACLLIST, Tag_Start); + while ((lnp = xml_node_next(lnode, XML_ELEMENT_INIT, lnp)) != + NULL) + xml_add_tag(msg, XML_ELEMENT_INIT, lnp->x_value); + buf_add_tag(msg, XML_ELEMENT_ACLLIST, Tag_End); + } + + if ((lnode = xml_node_next(tnode, XML_ELEMENT_TPGTLIST, NULL)) != + NULL) { + lnp = NULL; + buf_add_tag(msg, XML_ELEMENT_TPGTLIST, Tag_Start); + while ((lnp = xml_node_next(lnode, XML_ELEMENT_TPGT, lnp)) != + NULL) + xml_add_tag(msg, XML_ELEMENT_TPGT, lnp->x_value); + buf_add_tag(msg, XML_ELEMENT_TPGTLIST, Tag_End); + } + + if ((lnode = xml_node_next(tnode, XML_ELEMENT_ALIAS, NULL)) != NULL) + xml_add_tag(msg, XML_ELEMENT_ALIAS, lnode->x_value); + + if ((lnode = xml_node_next(tnode, XML_ELEMENT_MAXRECV, NULL)) != NULL) + xml_add_tag(msg, XML_ELEMENT_MAXRECV, lnode->x_value); + + if ((lnode = xml_node_next(tnode, XML_ELEMENT_LUNLIST, NULL)) == NULL) + return; + + buf_add_tag(msg, XML_ELEMENT_LUNINFO, Tag_Start); + lun = NULL; + while ((lun = xml_node_next(lnode, XML_ELEMENT_LUN, lun)) != NULL) { + if ((xml_find_value_int(lun, XML_ELEMENT_LUN, &lun_num)) == + False) + continue; + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", + target_basedir, targ_name, PARAMBASE, lun_num); + if ((xml_fd = open(path, O_RDONLY)) < 0) + continue; + if ((r = (xmlTextReaderPtr)xmlReaderForFd(xml_fd, + NULL, NULL, 0)) == NULL) + continue; + + params = NULL; + while (xmlTextReaderRead(r) == 1) { + if (xml_process_node(r, ¶ms) == False) + break; + } + (void) close(xml_fd); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + + buf_add_tag(msg, XML_ELEMENT_LUN, Tag_Start); + snprintf(lun_buf, sizeof (lun_buf), "%d", lun_num); + buf_add_tag(msg, lun_buf, Tag_String); + + if (xml_find_value_str(params, XML_ELEMENT_GUID, &prop) == + True) { + xml_add_tag(msg, XML_ELEMENT_GUID, prop); + free(prop); + } + if (xml_find_value_str(params, XML_ELEMENT_VID, &prop) == + True) { + xml_add_tag(msg, XML_ELEMENT_VID, prop); + free(prop); + } + if (xml_find_value_str(params, XML_ELEMENT_PID, &prop) == + True) { + xml_add_tag(msg, XML_ELEMENT_PID, prop); + free(prop); + } + if (xml_find_value_str(params, XML_ELEMENT_DTYPE, &prop) == + True) { + xml_add_tag(msg, XML_ELEMENT_DTYPE, prop); + free(prop); + } + if (xml_find_value_str(params, XML_ELEMENT_SIZE, &prop) == + True) { + xml_add_tag(msg, XML_ELEMENT_SIZE, prop); + free(prop); + } + if (xml_find_value_str(params, XML_ELEMENT_STATUS, &prop) == + True) { + xml_add_tag(msg, XML_ELEMENT_STATUS, prop); + free(prop); + } + if (xml_find_value_str(params, XML_ELEMENT_BACK, &prop) == + True) { + xml_add_tag(msg, XML_ELEMENT_BACK, prop); + free(prop); + } + buf_add_tag(msg, XML_ELEMENT_LUN, Tag_End); + + xml_tree_free(params); + } + buf_add_tag(msg, XML_ELEMENT_LUNINFO, Tag_End); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/mgmt_modify.c b/usr/src/cmd/iscsi/iscsitgtd/mgmt_modify.c new file mode 100644 index 0000000000..ffc9384f11 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/mgmt_modify.c @@ -0,0 +1,692 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <time.h> +#include <sys/utsname.h> +#include <unistd.h> +#include <sys/param.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <errno.h> +#include <strings.h> +#include <assert.h> +#include <sys/socket.h> +#include <netdb.h> + +#include "xml.h" +#include "queue.h" +#include "utility.h" +#include "iscsi_cmd.h" +#include "target.h" +#include "errcode.h" + +static char *modify_target(xml_node_t *x); +static char *modify_initiator(xml_node_t *x); +static char *modify_admin(xml_node_t *x); +static char *modify_tpgt(xml_node_t *x); +static Boolean_t modify_element(char *, char *, xml_node_t *, match_type_t); + +/* + * []---- + * | modify_func -- dispatch routine for objects + * []---- + */ +/*ARGSUSED*/ +void +modify_func(xml_node_t *p, target_queue_t *reply, target_queue_t *mgmt) +{ + xml_node_t *x; + char *reply_msg = NULL; + + if (p->x_child == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + + } else { + x = p->x_child; + + if (x->x_name == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + } else if (strcmp(x->x_name, XML_ELEMENT_TARG) == 0) { + reply_msg = modify_target(x); + } else if (strcmp(x->x_name, XML_ELEMENT_INIT) == 0) { + reply_msg = modify_initiator(x); + } else if (strcmp(x->x_name, XML_ELEMENT_ADMIN) == 0) { + reply_msg = modify_admin(x); + } else if (strcmp(x->x_name, XML_ELEMENT_TPGT) == 0) { + reply_msg = modify_tpgt(x); + } else { + xml_rtn_msg(&reply_msg, ERR_INVALID_OBJECT); + } + } + queue_message_set(reply, 0, msg_mgmt_rply, reply_msg); +} + +/* + * []---- + * | modify_target -- updates one or more properties for a target + * []---- + */ +static char * +modify_target(xml_node_t *x) +{ + char *msg = NULL, + *name = NULL, + *iscsi, + *prop = NULL, + size_str[16], + path[MAXPATHLEN], + *m, + buf[512]; /* one sector size block */ + xml_node_t *t = NULL, + *list = NULL, + *c = NULL, + *node; + Boolean_t change_made = False; + int lun = 0, + fd, + xml_fd; + uint64_t val, + new_lu_size, + cur_lu_size; + struct stat st; + xmlTextReaderPtr r; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + + while ((t = xml_node_next(targets_config, XML_ELEMENT_TARG, + t)) != NULL) { + if (strcmp(t->x_value, name) == 0) { + break; + } + } + + /* ---- Finished with these so go ahead and release the memory ---- */ + free(name); + + if (t == NULL) { + xml_rtn_msg(&msg, ERR_TARG_NOT_FOUND); + return (msg); + } + + /* + * Grow the LU. We currently do not support shrinking the LU and + * that is only because it's unknown if any applications could support + * that type of data loss. To support shrinking all that would be + * needed is to remove the new/old size check and perform a truncation. + * The actually truncation request should be shipped off to the T10 + * layer so that the LU thread can remap the smaller size without + * anyone accessing the data. + */ + if (xml_find_value_str(x, XML_ELEMENT_SIZE, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_TPGT); + return (msg); + } + if (strtoll_multiplier(prop, &new_lu_size) == False) { + free(prop); + xml_rtn_msg(&msg, ERR_INVALID_SIZE); + return (msg); + } + free(prop); + if ((new_lu_size % 512LL) != 0) { + xml_rtn_msg(&msg, ERR_SIZE_MOD_BLOCK); + return (msg); + } + new_lu_size /= 512LL; + + if (xml_find_value_str(x, XML_ELEMENT_INAME, &iscsi) == False) { + xml_rtn_msg(&msg, ERR_TARGCFG_MISSING_INAME); + return (msg); + } + + /* ---- default to LUN 0 ---- */ + (void) xml_find_value_int(x, XML_ELEMENT_LUN, &lun); + + /* ---- read in current paramaters ---- */ + snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + iscsi, PARAMBASE, lun); + if ((xml_fd = open(path, O_RDONLY)) < 0) { + xml_rtn_msg(&msg, ERR_OPEN_PARAM_FILE_FAILED); + return (msg); + } + if ((r = (xmlTextReaderPtr)xmlReaderForFd(xml_fd, NULL, NULL, + 0)) != NULL) { + node = NULL; + while (xmlTextReaderRead(r) == 1) + if (xml_process_node(r, &node) == False) + break; + } else { + xml_rtn_msg(&msg, ERR_INIT_XML_READER_FAILED); + return (msg); + } + + (void) close(xml_fd); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + + /* ---- validate that we're indeed growing the LU ---- */ + if (xml_find_value_str(node, XML_ELEMENT_SIZE, &prop) == + False) { + xml_rtn_msg(&msg, ERR_INIT_XML_READER_FAILED); + return (msg); + } + if (strtoll_multiplier(prop, &cur_lu_size) == False) { + free(prop); + xml_rtn_msg(&msg, ERR_INVALID_SIZE); + return (msg); + } + free(prop); + + if (new_lu_size < cur_lu_size) { + xml_rtn_msg(&msg, ERR_CANT_SHRINK_LU); + return (msg); + } + + /* ---- check that this LU is of type 'disk' or 'tape' ---- */ + if (xml_find_value_str(node, XML_ELEMENT_DTYPE, &prop) == + False) { + xml_rtn_msg(&msg, ERR_INIT_XML_READER_FAILED); + return (msg); + } + if ((strcmp(prop, TGT_TYPE_DISK) != 0) && + (strcmp(prop, TGT_TYPE_TAPE) != 0)) { + xml_rtn_msg(&msg, ERR_RESIZE_WRONG_DTYPE); + return (msg); + } + free(prop); + + /* ---- validate the backing store is a regular file ---- */ + snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + iscsi, LUNBASE, lun); + if (stat(path, &st) == -1) { + xml_rtn_msg(&msg, ERR_STAT_BACKING_FAILED); + return (msg); + } + if ((st.st_mode & S_IFMT) != S_IFREG) { + xml_rtn_msg(&msg, + ERR_DISK_BACKING_MUST_BE_REGULAR_FILE); + return (msg); + } + + /* ---- update the parameter node with new size ---- */ + snprintf(size_str, sizeof (size_str), "0x%llx", new_lu_size); + if ((c = xml_alloc_node(XML_ELEMENT_SIZE, Uint64, size_str)) == + False) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + xml_replace_child(node, c, MatchName); + xml_tree_free(c); + + /* ---- now update params file ---- */ + snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + iscsi, PARAMBASE, lun); + if (xml_dump2file(node, path) == False) { + xml_rtn_msg(&msg, ERR_UPDATE_TARGCFG_FAILED); + return (msg); + } + + /* ---- grow lu backing store ---- */ + snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + iscsi, LUNBASE, lun); + if ((fd = open(path, O_RDWR|O_CREAT|O_LARGEFILE, 0600)) < 0) { + xml_rtn_msg(&msg, ERR_LUN_NOT_FOUND); + return (msg); + } + (void) lseek(fd, (new_lu_size * 512LL) - 512LL, 0); + bzero(buf, sizeof (buf)); + if (write(fd, buf, sizeof (buf)) != sizeof (buf)) { + xml_rtn_msg(&msg, ERR_LUN_NOT_GROWN); + return (msg); + } + (void) close(fd); + + /* ---- send updates to current initiators via ASC/ASCQ ---- */ + iscsi_capacity_change(iscsi, lun); + + free(iscsi); + prop = NULL; + xml_tree_free(node); + } + + if (xml_find_value_str(x, XML_ELEMENT_TPGT, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_ACL); + return (msg); + } + + /* + * Validate that the Target Portal Group Tag is reasonable. + */ + val = strtoll(prop, &m, 0); + if ((val < TPGT_MIN) || (val > TPGT_MAX) || + ((m != NULL) && (*m != '\0'))) { + xml_rtn_msg(&msg, ERR_INVALID_TPGT); + free(prop); + return (msg); + } + + if ((c = xml_alloc_node(XML_ELEMENT_TPGT, String, prop)) == + NULL) { + free(prop); + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + + if ((list = xml_node_next(t, XML_ELEMENT_TPGTLIST, + NULL)) != NULL) { + xml_replace_child(list, c, MatchBoth); + /* + * xml_replace_child will duplicate the child node + * xml_add_child which is used below just links it + * into the tree. + */ + xml_tree_free(c); + } else { + list = xml_alloc_node(XML_ELEMENT_TPGTLIST, String, ""); + if (list == NULL) { + free(prop); + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + (void) xml_add_child(list, c); + (void) xml_add_child(t, list); + } + free(prop); + prop = NULL; + change_made = True; + } + + if (xml_find_value_str(x, XML_ELEMENT_ACL, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_ACL); + return (msg); + } + + c = xml_alloc_node(XML_ELEMENT_INIT, String, prop); + if (c == NULL) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + if ((list = xml_node_next(t, XML_ELEMENT_ACLLIST, + NULL)) != NULL) { + xml_replace_child(list, c, MatchBoth); + /* ---- See above usage ---- */ + xml_tree_free(c); + } else { + list = xml_alloc_node(XML_ELEMENT_ACLLIST, String, ""); + if (list == NULL) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + (void) xml_add_child(list, c); + (void) xml_add_child(t, list); + } + free(prop); + prop = NULL; + change_made = True; + } + + if (xml_find_value_str(x, XML_ELEMENT_ALIAS, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_ALIAS); + return (msg); + } + + if (modify_element(XML_ELEMENT_ALIAS, prop, t, MatchName) == + False) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + free(prop); + prop = NULL; + change_made = True; + } + + if (xml_find_value_str(x, XML_ELEMENT_MAXRECV, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_MAXRECV); + return (msg); + } + + if ((strtoll_multiplier(prop, &val) == False) || + (val < MAXRCVDATA_MIN) || (val > MAXRCVDATA_MAX)) { + free(prop); + xml_rtn_msg(&msg, ERR_INVALID_MAXRECV); + return (msg); + } + free(prop); + if ((prop = malloc(32)) == NULL) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + snprintf(prop, 32, "%d", val); + + if (modify_element(XML_ELEMENT_MAXRECV, prop, t, MatchName) == + False) { + free(prop); + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + free(prop); + prop = NULL; + change_made = True; + } + + if (change_made == True) { + if (update_config_targets(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + } else { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_OPERAND); + } + + return (msg); +} + +/* + * []---- + * | modify_initiator -- store the CHAP information for an initiator + * []---- + */ +static char * +modify_initiator(xml_node_t *x) +{ + char *msg = NULL, + *name = NULL, + *prop = NULL; + xml_node_t *inode = NULL; + Boolean_t changes_made = False; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + + while ((inode = xml_node_next(main_config, XML_ELEMENT_INIT, + inode)) != NULL) { + if (strcmp(inode->x_value, name) == 0) + break; + } + + /* + * We no longer need the name since we should have found the node + * it refers to and this way we don't have to worry about freeing + * the storage later. + */ + free(name); + + if (inode == NULL) { + xml_rtn_msg(&msg, ERR_INIT_NOT_FOUND); + return (msg); + } + + if (xml_find_value_str(x, XML_ELEMENT_CHAPSECRET, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_CHAPSECRET); + return (msg); + } + + if (modify_element(XML_ELEMENT_CHAPSECRET, prop, inode, + MatchName) == False) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + free(prop); + changes_made = True; + } + + if (xml_find_value_str(x, XML_ELEMENT_CHAPNAME, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_CHAPNAME); + return (msg); + } + + if (modify_element(XML_ELEMENT_CHAPNAME, prop, inode, + MatchName) == False) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + free(prop); + changes_made = True; + } + + if (changes_made == True) { + if (update_config_main(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + } else { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_OPERAND); + } + + return (msg); +} + +/* + * []---- + * | modify_admin -- modify one or more of the admin related props + * []---- + */ +static char * +modify_admin(xml_node_t *x) +{ + char *msg = NULL, + *prop; + Boolean_t changes_made = False; + admin_table_t *ap; + + for (ap = admin_prop_list; ap->name; ap++) { + if (xml_find_value_str(x, ap->name, &prop) == True) { + + if ((prop == NULL) || (strlen(prop) == 0)) + break; + + /* + * Do the function call first if it exists which + * will allow possible checking to be done first. + */ + if (ap->func) { + if ((msg = (*ap->func)(ap->name, prop)) != NULL) + return (msg); + } + if (modify_element(ap->name, prop, main_config, + MatchName) == False) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + free(prop); + changes_made = True; + } + } + + if (changes_made == True) { + if (update_config_main(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + } else { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_OPERAND); + } + + return (msg); +} + +/* + * []---- + * | modify_tpgt -- add an IP-address to a target portal group + * []---- + */ +static char * +modify_tpgt(xml_node_t *x) +{ + struct addrinfo *res = NULL; + char *msg = NULL, + *name = NULL, + *ip_str = NULL; + xml_node_t *tnode = NULL; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + goto error; + } + if (xml_find_value_str(x, XML_ELEMENT_IPADDR, &ip_str) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_IPADDR); + goto error; + } + if ((getaddrinfo(ip_str, NULL, NULL, &res) != 0) || (res == NULL)) { + xml_rtn_msg(&msg, ERR_INVALID_IP); + goto error; + } + while ((tnode = xml_node_next(main_config, XML_ELEMENT_TPGT, + tnode)) != NULL) { + if (strcmp(tnode->x_value, name) == 0) + break; + } + if (tnode == NULL) { + xml_rtn_msg(&msg, ERR_TPGT_NOT_FOUND); + goto error; + } + if (modify_element(XML_ELEMENT_IPADDR, ip_str, tnode, MatchBoth) == + False) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } + + if (update_config_main(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + +error: + if (name) + free(name); + if (ip_str) + free(ip_str); + return (msg); +} + +/* + * []---- + * | modify_element -- helper function to create node and add it to parent + * | + * | A False return value indicates a failure to allocate enough memory. + * []---- + */ +static Boolean_t +modify_element(char *name, char *value, xml_node_t *p, match_type_t m) +{ + xml_node_t *c; + + if ((c = xml_alloc_node(name, String, value)) == NULL) + return (False); + else { + xml_replace_child(p, c, m); + xml_tree_free(c); + return (True); + } +} + +/* + * []---- + * | update_basedir -- update the global target directory + * | + * | Most of the properties when updated require no futher processing. The + * | target base directory however must be updated if it hasn't been set. + * | On a new system the daemon will not have any location to place the + * | backing store and target configuration files. On a live system we would + * | screw things up if we changed the global variable if it was already + * | in use, so we only allow the updating to occur if there are no targets. + * []---- + */ +/*ARGSUSED*/ +char * +update_basedir(char *name, char *prop) +{ + xml_node_t *targ = NULL; + int count = 0; + char *msg = NULL; + + if ((prop == NULL) || (strlen(prop) == 0) || (prop[0] != '/')) { + xml_rtn_msg(&msg, ERR_INVALID_BASEDIR); + return (msg); + } + + while ((targ = xml_node_next(targets_config, XML_ELEMENT_TARG, + targ)) != NULL) { + count++; + } + + if (target_basedir == NULL) { + target_basedir = strdup(prop); + } else if (count == 0) { + free(target_basedir); + target_basedir = strdup(prop); + if ((mkdir(target_basedir, 0700) != 0) && (errno != EEXIST)) { + xml_rtn_msg(&msg, ERR_CREATE_TARGET_DIR_FAILED); + } else { + if (process_target_config() == False) { + xml_rtn_msg(&msg, ERR_CREATE_TARGET_DIR_FAILED); + } + } + } else { + xml_rtn_msg(&msg, ERR_VALID_TARG_EXIST); + } + return (msg); +} + +/* + * []---- + * | validate_radius -- validate that server[:port] are valid + * []---- + */ +char * +valid_radius_srv(char *name, char *prop) +{ + struct addrinfo *res = NULL; + char *msg = NULL, + *sp, + *p; + int port; + + if ((sp = strdup(prop)) == NULL) { + xml_rtn_msg(&msg, ERR_NO_MEM); + return (msg); + } else if ((p = strrchr(sp, ':')) != NULL) { + *p++ = '\0'; + port = atoi(p); + if ((port < 1) || (port > 65535)) { + xml_rtn_msg(&msg, ERR_INVALID_RADSRV); + free(sp); + return (msg); + } + } + if ((getaddrinfo(sp, NULL, NULL, &res) != 0) || (res == NULL)) + xml_rtn_msg(&msg, ERR_INVALID_RADSRV); + else + freeaddrinfo(res); + free(sp); + return (msg); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/mgmt_remove.c b/usr/src/cmd/iscsi/iscsitgtd/mgmt_remove.c new file mode 100644 index 0000000000..47a1e269b3 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/mgmt_remove.c @@ -0,0 +1,288 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file deals with XML data for removing various configuration data. + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <errno.h> +#include <strings.h> +#include <unistd.h> + +#include "utility.h" +#include "xml.h" +#include "queue.h" +#include "target.h" +#include "iscsi_cmd.h" +#include "errcode.h" + +static char *remove_target(xml_node_t *x); +static char *remove_initiator(xml_node_t *x); +static char *remove_tpgt(xml_node_t *x); + +/*ARGSUSED*/ +void +remove_func(xml_node_t *p, target_queue_t *reply, target_queue_t *mgmt) +{ + xml_node_t *x; + char msgbuf[80], + *reply_msg = NULL; + + if (p->x_child == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + } else { + x = p->x_child; + + if (x->x_name == NULL) { + xml_rtn_msg(&reply_msg, ERR_SYNTAX_MISSING_OBJECT); + } else if (strcmp(x->x_name, XML_ELEMENT_TARG) == 0) { + reply_msg = remove_target(x); + } else if (strcmp(x->x_name, XML_ELEMENT_INIT) == 0) { + reply_msg = remove_initiator(x); + } else if (strcmp(x->x_name, XML_ELEMENT_TPGT) == 0) { + reply_msg = remove_tpgt(x); + } else { + (void) snprintf(msgbuf, sizeof (msgbuf), + "Unknown object '%s' for delete element", + x->x_name); + xml_rtn_msg(&reply_msg, ERR_INVALID_OBJECT); + } + } + queue_message_set(reply, 0, msg_mgmt_rply, reply_msg); +} + +static char * +remove_target(xml_node_t *x) +{ + char *msg = NULL, + *prop = NULL; + xml_node_t *targ = NULL, + *list, + *c = NULL; + Boolean_t change_made = False; + int lun_num; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &prop) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + + while ((targ = xml_node_next(targets_config, XML_ELEMENT_TARG, targ)) != + NULL) { + if (strcmp(targ->x_value, prop) == 0) + break; + } + free(prop); + if (targ == NULL) { + xml_rtn_msg(&msg, ERR_TARG_NOT_FOUND); + return (msg); + } + if (xml_find_value_str(x, XML_ELEMENT_ACL, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_ACL); + return (msg); + } + if ((list = xml_node_next(targ, XML_ELEMENT_ACLLIST, NULL)) == + NULL) { + free(prop); + xml_rtn_msg(&msg, ERR_ACL_NOT_FOUND); + return (msg); + } + c = xml_alloc_node(XML_ELEMENT_INIT, String, prop); + if (xml_remove_child(list, c, MatchBoth) == False) { + xml_rtn_msg(&msg, ERR_INIT_NOT_FOUND); + goto error; + } + xml_free_node(c); + if (list->x_child == NULL) + (void) xml_remove_child(targ, list, MatchName); + free(prop); + change_made = True; + } + if (xml_find_value_str(x, XML_ELEMENT_TPGT, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_TPGT); + return (msg); + } + if ((list = xml_node_next(targ, XML_ELEMENT_TPGTLIST, NULL)) == + NULL) { + free(prop); + xml_rtn_msg(&msg, ERR_ACL_NOT_FOUND); + return (msg); + } + c = xml_alloc_node(XML_ELEMENT_TPGT, String, prop); + if (xml_remove_child(list, c, MatchBoth) == False) { + xml_rtn_msg(&msg, ERR_TPGT_NOT_FOUND); + goto error; + } + xml_free_node(c); + if (list->x_child == NULL) + (void) xml_remove_child(targ, list, MatchName); + free(prop); + change_made = True; + } + if (xml_find_value_int(x, XML_ELEMENT_LUN, &lun_num) == True) { + + if (xml_find_value_intchk(x, XML_ELEMENT_LUN, &lun_num) == + False) { + xml_rtn_msg(&msg, ERR_LUN_INVALID_RANGE); + return (msg); + } + + /* + * Save the iscsi-name which we'll need to remove LUNs. + */ + if (xml_find_value_str(targ, XML_ELEMENT_INAME, &prop) == + False) { + xml_rtn_msg(&msg, ERR_TARGCFG_MISSING_INAME); + return (msg); + } + + logout_targ(prop); + thick_provo_stop(prop, lun_num); + + remove_target_common(targ->x_value, lun_num, &msg); + if (msg != NULL) + goto error; + + iscsi_inventory_change(prop); + free(prop); + change_made = True; + } + + if (change_made == True) { + if (update_config_targets(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + } else { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_OPERAND); + } + + return (msg); + +error: + if (c != NULL) + xml_free_node(c); + if (prop != NULL) + free(prop); + return (msg); +} + +static char * +remove_initiator(xml_node_t *x) +{ + char *msg = NULL, + *name; + xml_node_t *node = NULL; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + while ((node = xml_node_next(main_config, XML_ELEMENT_INIT, node)) != + NULL) { + if (strcmp(node->x_value, name) == 0) + break; + } + free(name); + if (node == NULL) { + xml_rtn_msg(&msg, ERR_INIT_NOT_FOUND); + return (msg); + } + if (xml_find_value_str(x, XML_ELEMENT_ALL, &name) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_ALL); + return (msg); + } + (void) xml_remove_child(main_config, node, MatchBoth); + + if (update_config_main(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + + return (msg); +} + +static char * +remove_tpgt(xml_node_t *x) +{ + char *msg = NULL, + *prop = NULL; + xml_node_t *node = NULL, + *c = NULL; + Boolean_t change_made = False; + + if (xml_find_value_str(x, XML_ELEMENT_NAME, &prop) == False) { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_NAME); + return (msg); + } + while ((node = xml_node_next(main_config, XML_ELEMENT_TPGT, node)) != + NULL) { + if (strcmp(node->x_value, prop) == 0) + break; + } + free(prop); + if (node == NULL) { + xml_rtn_msg(&msg, ERR_TPGT_NOT_FOUND); + return (msg); + } + if (xml_find_value_str(x, XML_ELEMENT_IPADDR, &prop) == True) { + if (prop == NULL) { + xml_rtn_msg(&msg, ERR_SYNTAX_EMPTY_IPADDR); + return (msg); + } + c = xml_alloc_node(XML_ELEMENT_IPADDR, String, prop); + if (xml_remove_child(node, c, MatchBoth) == False) { + xml_rtn_msg(&msg, ERR_INVALID_IP); + goto error; + } + xml_free_node(c); + free(prop); + change_made = True; + } + if ((change_made != True) && + (xml_find_value_str(x, XML_ELEMENT_ALL, &prop) == True)) { + xml_remove_child(main_config, node, MatchBoth); + change_made = True; + } + + if (change_made == True) { + if (update_config_main(&msg) == True) + xml_rtn_msg(&msg, ERR_SUCCESS); + } else { + xml_rtn_msg(&msg, ERR_SYNTAX_MISSING_OPERAND); + } + + return (msg); + +error: + if (c != NULL) + xml_free_node(c); + if (prop != NULL) + free(prop); + return (msg); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/port.h b/usr/src/cmd/iscsi/iscsitgtd/port.h new file mode 100644 index 0000000000..e1cc377fee --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/port.h @@ -0,0 +1,48 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef PORT_H +#define PORT_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include "iscsi_conn.h" + +typedef struct port_args { + target_queue_t *port_mgmtq, /* management queue */ + *port_dataq; /* incoming data for thread */ + int port_num, /* port number to monitor */ + port_socket; +} port_args_t; + +void port_init(); +void *port_watcher(void *v); +void *port_management(void *v); +void port_conn_remove(iscsi_conn_t *c); + +extern iscsi_conn_t *conn_head; + +#endif /* PORT_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/queue.h b/usr/src/cmd/iscsi/iscsitgtd/queue.h new file mode 100644 index 0000000000..92e823fc80 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/queue.h @@ -0,0 +1,268 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _TARGET_QUEUE_H +#define _TARGET_QUEUE_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pthread.h> +#include <sys/time.h> +#include <stdarg.h> +#include <xml.h> +#include <synch.h> + +#include "local_types.h" + +#define Q_CONN_ERRS 0x00001 +#define Q_CONN_LOGIN 0x00002 +#define Q_CONN_NONIO 0x00004 +#define Q_CONN_IO 0x00008 + +#define Q_SESS_ERRS 0x00010 +#define Q_SESS_LOGIN 0x00020 +#define Q_SESS_NONIO 0x00040 +#define Q_SESS_IO 0x00080 + +#define Q_STE_ERRS 0x00100 +#define Q_STE_NONIO 0x00200 +#define Q_STE_IO 0x00400 + +#define Q_GEN_ERRS 0x01000 +#define Q_GEN_DETAILS 0x02000 + +/* + * When used the queue request will be place at the head of the queue. + */ +#define Q_HIGH 0x10000 + +extern int qlog_lvl; + +typedef enum { + /* + * []---------------------------------------------------------------- + * | Messages internal to the SAM-3 portion. When the transport calls + * | the SAM-3 interfaces messages are enqueued to the LU. The LU + * | thread then dequeues these messages and calls the appropriate + * | function for the emulator. + */ + + /* ---- from transport ---- */ + msg_cmd_send, + msg_cmd_data_out, + + /* ---- from emulation ---- */ + msg_cmd_data_in, + msg_cmd_data_rqst, + msg_cmd_cmplt, + + /* ---- Internal SAM-3 messages ---- */ + msg_lu_add, + msg_lu_remove, + msg_lu_online, + + /* + * | End of SAM-3 messages + * []---------------------------------------------------------------- + */ + + msg_reset_lu, + msg_reset_targ, + msg_targ_inventory_change, + msg_lu_capacity_change, + + /* + * The ConnectionReader will send packet ready messages when + * data is available. If the socket has an error or is closed + * a conn_lost message will be sent. Packet ready will have the + * number of bytes currently available on the connection. Don't + * free. + */ + msg_conn_lost, + msg_packet_ready, + + /* + * Shutdowns happen from the bottom up. The replies are in place + * so that threads can wait for the top end to disappear, at least + * they must no longer reference any common structures such as + * message queues. + */ + msg_drain_complete, + msg_shutdown, + msg_shutdown_rsp, + + /* + * Here's a special error condition for STE. When using mmap + * to access the backing store of a LUN which is larger than + * the underlying storage it's possible to run out of room + * on the device (no duh). When that happens the OS will send + * the daemon a SIGSBUS. The STE thread catches that signal, + * sends a UNIT ATTENTION to the other side, and closes down + * the STE thread in a special manner. The transport layer + * can then restart another STE thread with the same queues + * which mean outstanding I/O restarts. + */ + msg_ste_media_error, + + /* + * A NopIn request could be sent on the connection receive thread + * except for one little issue. Since both the receive and transmit + * threads could be issuing packets and data to the socket at the + * same time we must protect those writes so that all of the data + * for a single PDU (hdr, checksum, data, checksum) go out together. + * It's possible for the socket to receive so much incoming data + * that writes will be blocked until some of that data has been + * read. If the transmit grabs the lock, attempts to write, and is + * blocked we find a condition where the receiver is also blocked + * processing a nop command because it can't get the lock. So, instead + * we build up the packet and queue it. + * + * This will also occur with Task Management Requests. + */ + msg_send_pkt, + + /* + * During login when the TargetName name/value pair is processed + * the value will be sent to STE through the session layer. + * STE can use the information however it sees fit. + * The InitiatorName will also be sent which STE can use to + * validate login properties. + */ + msg_target_name, + msg_initiator_name, + msg_initiator_alias, + + /* + * Issued when causing full allocation of backing store. + * This is an internal message used by t10_sam.c + */ + msg_thick_provo, + + /* + * ---------------- Debug/Management type messages ---------------- + */ + /* + * Requests from and replys to the management host will be done using + * these messages. + */ + msg_mgmt_rqst, + msg_mgmt_rply, + + /* + * General debug messages. + */ + msg_log, + + /* + * Problem message by some of the auxiliary threads indication + * problems. + */ + msg_status + +} msg_type_t; + +typedef struct msg { + struct msg *msg_next, + *msg_prev; + struct msg *msg_all_next; + + msg_type_t msg_type; + void *msg_data; + + /* + * This can be used either to insert a message higher into the queue + * or as debug level flags. + */ + uint32_t msg_pri_level; +} msg_t; + +typedef struct target_queue { + msg_t *q_head, + *q_tail; + pthread_mutex_t q_mutex; + sema_t q_sema; + int q_num; +} target_queue_t; + +typedef enum mgmt_type { + mgmt_full_phase_statistics, + mgmt_discovery_statistics, + mgmt_lun_information, + mgmt_parse_xml, + mgmt_logout +} mgmt_type_t; + +typedef struct mgmt_request { + target_queue_t *m_q; + mgmt_type_t m_request; + time_t m_time; + char *m_targ_name; + + /* + * This mutex protects the m_buf pointer from multiple connections + * attempting to update the response at the same time. One management + * request structure is sent to possible multiple connections when + * gathering statistics. The connections/sessions will lock access + * to the buffer. + */ + pthread_mutex_t m_resp_mutex; + union { + char **m_resp; + xml_node_t *m_node; + } m_u; +} mgmt_request_t; + +typedef struct name_request { + target_queue_t *nr_q; + char *nr_name; +} name_request_t; + +void queue_init(); +target_queue_t *queue_alloc(); +void queue_message_set(target_queue_t *, uint32_t lvl, msg_type_t, void *); +msg_t *queue_message_get(target_queue_t *); +msg_t *queue_message_try_get(target_queue_t *q); +void queue_message_free(msg_t *); +void queue_walker_free(target_queue_t *q, + Boolean_t (*func)(msg_t *, void *v), void *v1); +void queue_free(target_queue_t *, void (*free_func)(msg_t *)); +void queue_reset(target_queue_t *q); +void queue_prt(target_queue_t *q, int type, char *fmt, ...); +void queue_str(target_queue_t *, uint32_t lvl, msg_type_t, char *); +void queue_log(Boolean_t on_off); +void ste_queue_data_remove(msg_t *m); +void conn_queue_data_remove(msg_t *m); +void sess_queue_data_remove(msg_t *m); + +#ifdef __cplusplus +} +#endif + +#endif /* _TARGET_QUEUE_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/radius.c b/usr/src/cmd/iscsi/iscsitgtd/radius.c new file mode 100644 index 0000000000..a6432ae451 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/radius.c @@ -0,0 +1,514 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/random.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <stdlib.h> + +#include <netinet/in.h> +#include <sys/socket.h> + +#include <md5.h> +#include "target.h" +#include "radius.h" + +/* Forward declaration */ + +/* + * Encode a CHAP-Password attribute. This function basically prepends + * the identifier in front of chap_passwd and copy the results to + * *result. + */ +static +void +encode_chap_password(int identifier, + int chap_passwd_len, + uint8_t *chap_passwd, + uint8_t *result); + +int +snd_radius_request(int sd, + iscsi_ipaddr_t rsvr_ip_addr, + uint32_t rsvr_port, + radius_packet_data_t *req_data); + +int +rcv_radius_response(int sd, + uint8_t *shared_secret, + uint32_t shared_secret_len, + uint8_t *req_authenticator, + radius_packet_data_t *resp_data); + +/* + * Annotate the radius_attr_t objects with authentication data. + */ +static +void +set_radius_attrs(radius_packet_data_t *req, + char *target_chap_name, + unsigned char *target_response, + uint32_t responseLength, + uint8_t *challenge, +uint32_t challengeLength); + +/* + * See radius_auth.h. + */ +/* ARGSUSED */ +chap_validation_status_type +radius_chap_validate(char *target_chap_name, + char *initiator_chap_name, + uint8_t *challenge, + uint32_t challengeLength, + uint8_t *target_response, + uint32_t responseLength, + uint8_t identifier, + iscsi_ipaddr_t rad_svr_ip_addr, + uint32_t rad_svr_port, + uint8_t *rad_svr_shared_secret, + uint32_t rad_svr_shared_secret_len) +{ + chap_validation_status_type validation_status; + int rcv_status; + int sd; + int rc; + struct sockaddr_in sockaddr; + radius_packet_data_t req; + radius_packet_data_t resp; + MD5_CTX context; + uint8_t md5_digest[16]; /* MD5 digest length 16 */ + uint8_t random_number[16]; + int fd; + + if (rad_svr_shared_secret_len == 0) { + /* The secret must not be empty (section 3, RFC 2865) */ + return (CHAP_VALIDATION_BAD_RADIUS_SECRET); + } + + bzero(&req, sizeof (radius_packet_data_t)); + + req.identifier = identifier; + req.code = RAD_ACCESS_REQ; + set_radius_attrs(&req, + target_chap_name, + target_response, + responseLength, + challenge, + challengeLength); + + /* Prepare the request authenticator */ + MD5Init(&context); + bzero(&md5_digest, 16); + /* First, the shared secret */ + MD5Update(&context, rad_svr_shared_secret, rad_svr_shared_secret_len); + /* Then a unique number - use a random number */ + fd = open("/dev/random", O_RDONLY); + if (fd == -1) + return (CHAP_VALIDATION_INTERNAL_ERROR); + (void) read(fd, &random_number, sizeof (random_number)); + (void) close(fd); + MD5Update(&context, random_number, sizeof (random_number)); + MD5Final(md5_digest, &context); + bcopy(md5_digest, &req.authenticator, RAD_AUTHENTICATOR_LEN); + + /* Create UDP socket */ + sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); + } + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); + sockaddr.sin_port = htons(0); + rc = bind(sd, (struct sockaddr *)&sockaddr, sizeof (sockaddr)); + if (rc < 0) { + return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); + } + + /* Send the authentication access request to the RADIUS server */ + if (snd_radius_request(sd, + rad_svr_ip_addr, + rad_svr_port, + &req) == -1) { + (void) close(sd); + return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); + } + + bzero(&resp, sizeof (radius_packet_data_t)); + /* Analyze the response coming through from the same socket. */ + rcv_status = rcv_radius_response(sd, + rad_svr_shared_secret, + rad_svr_shared_secret_len, + req.authenticator, &resp); + if (rcv_status == RAD_RSP_RCVD_SUCCESS) { + if (resp.code == RAD_ACCESS_ACPT) { + validation_status = CHAP_VALIDATION_PASSED; + } else if (resp.code == RAD_ACCESS_REJ) { + validation_status = CHAP_VALIDATION_INVALID_RESPONSE; + } else { + validation_status = + CHAP_VALIDATION_UNKNOWN_RADIUS_CODE; + } + } else if (rcv_status == RAD_RSP_RCVD_AUTH_FAILED) { + validation_status = CHAP_VALIDATION_BAD_RADIUS_SECRET; + } else { + validation_status = CHAP_VALIDATION_RADIUS_ACCESS_ERROR; + } + + (void) close(sd); + return (validation_status); +} + +/* See forward declaration. */ +static void +set_radius_attrs(radius_packet_data_t *req, + char *target_chap_name, + unsigned char *target_response, + uint32_t responseLength, + uint8_t *challenge, + uint32_t challengeLength) +{ + req->attrs[0].attr_type_code = RAD_USER_NAME; + (void) strncpy((char *)req->attrs[0].attr_value, + (const char *)target_chap_name, + strlen(target_chap_name)); + req->attrs[0].attr_value_len = strlen(target_chap_name); + + req->attrs[1].attr_type_code = RAD_CHAP_PASSWORD; + bcopy(target_response, + (char *)req->attrs[1].attr_value, + min(responseLength, sizeof (req->attrs[1].attr_value))); + /* A target response is an MD5 hash thus its length has to be 16. */ + req->attrs[1].attr_value_len = responseLength; + + req->attrs[2].attr_type_code = RAD_CHAP_CHALLENGE; + bcopy(challenge, + (char *)req->attrs[2].attr_value, + min(challengeLength, sizeof (req->attrs[2].attr_value))); + req->attrs[2].attr_value_len = challengeLength; + + /* 3 attributes associated with each RADIUS packet. */ + req->num_of_attrs = 3; +} + +/* + * See radius_packet.h. + */ +int +snd_radius_request(int sd, + iscsi_ipaddr_t rsvr_ip_addr, + uint32_t rsvr_port, + radius_packet_data_t *req_data) +{ + int i; /* Loop counter. */ + int data_len; + int len; + ushort_t total_length; /* Has to be 2 octets in size */ + uint8_t *ptr; /* Pointer to RADIUS packet data */ + uint8_t *length_ptr; /* Points to the Length field of the */ + /* packet. */ + uint8_t *data; /* RADIUS data to be sent */ + radius_attr_t *req_attr; /* Request attributes */ + radius_packet_t *packet; /* Outbound RADIUS packet */ + union { + struct sockaddr_in s_in4; + struct sockaddr_in6 s_in6; + } sa_rsvr; /* Socket address of the server */ + + /* + * Create a RADIUS packet with minimal length for now. + */ + total_length = MIN_RAD_PACKET_LEN; + data = (uint8_t *)malloc(MAX_RAD_PACKET_LEN); + packet = (radius_packet_t *)data; + packet->code = req_data->code; + packet->identifier = req_data->identifier; + bcopy(req_data->authenticator, packet->authenticator, + RAD_AUTHENTICATOR_LEN); + ptr = packet->data; + + /* Loop over all attributes of the request. */ + for (i = 0; i < req_data->num_of_attrs; i++) { + if (total_length > MAX_RAD_PACKET_LEN) { + /* The packet has exceed its maximum size. */ + free(data); + return (-1); + } + + req_attr = &req_data->attrs[i]; + *ptr++ = (req_attr->attr_type_code & 0xFF); + length_ptr = ptr; + /* Length is 2 octets - RFC 2865 section 3 */ + *ptr++ = 2; + total_length += 2; + + /* If the attribute is CHAP-Password, encode it. */ + if (req_attr->attr_type_code == RAD_CHAP_PASSWORD) { + /* + * Identifier plus CHAP response. RFC 2865 + * section 5.3. + */ + uint8_t encoded_chap_passwd[RAD_CHAP_PASSWD_STR_LEN + + RAD_IDENTIFIER_LEN + + 1]; + encode_chap_password + (req_data->identifier, + req_attr->attr_value_len, + req_attr->attr_value, + encoded_chap_passwd); + + req_attr->attr_value_len = RAD_CHAP_PASSWD_STR_LEN + + RAD_IDENTIFIER_LEN; + + bcopy(encoded_chap_passwd, + req_attr->attr_value, + req_attr->attr_value_len); + } + + len = req_attr->attr_value_len; + *length_ptr += len; + + bcopy(req_attr->attr_value, ptr, req_attr->attr_value_len); + ptr += req_attr->attr_value_len; + + total_length += len; + } /* Done looping over all attributes */ + + data_len = total_length; + total_length = htons(total_length); + bcopy(&total_length, packet->length, sizeof (ushort_t)); + + /* + * Send the packet to the RADIUS server. + */ + bzero((char *)&sa_rsvr, sizeof (sa_rsvr)); + if (rsvr_ip_addr.i_insize == sizeof (in_addr_t)) { + int ret; + + /* IPv4 */ + sa_rsvr.s_in4.sin_family = AF_INET; + sa_rsvr.s_in4.sin_addr.s_addr = + rsvr_ip_addr.i_addr.in4.s_addr; + /* + * sin_port is of type u_short (or ushort_t - POSIX compliant). + */ + sa_rsvr.s_in4.sin_port = htons((ushort_t)rsvr_port); + + ret = sendto(sd, data, data_len, 0, + (struct sockaddr *)&sa_rsvr.s_in4, + sizeof (struct sockaddr_in)); + free(data); + return (ret); + } else if (rsvr_ip_addr.i_insize == sizeof (in6_addr_t)) { + /* IPv6 */ + sa_rsvr.s_in6.sin6_family = AF_INET6; + bcopy(sa_rsvr.s_in6.sin6_addr.s6_addr, + rsvr_ip_addr.i_addr.in6.s6_addr, 16); + /* + * sin6_port is of type in_port_t (i.e., uint16_t). + */ + sa_rsvr.s_in6.sin6_port = htons((in_port_t)rsvr_port); + + free(data); + /* No IPv6 support for now. */ + return (-1); + } else { + /* Invalid IP address for RADIUS server. */ + free(data); + return (-1); + } +} + +/* + * See radius_packet.h. + */ +int +rcv_radius_response(int sd, + uint8_t *shared_secret, + uint32_t shared_secret_len, + uint8_t *req_authenticator, + radius_packet_data_t *resp_data) +{ + int poll_cnt = 0; + int rcv_len = 0; + radius_packet_t *packet; + MD5_CTX context; + uint8_t *tmp_data; + uint8_t md5_digest[16]; /* MD5 Digest Length 16 */ + uint16_t declared_len = 0; + ushort_t len; + + fd_set fdset; + struct timeval timeout; + + tmp_data = (uint8_t *)malloc(MAX_RAD_PACKET_LEN); + + /* + * Poll and receive RADIUS packet. + */ + poll_cnt = 0; + do { + timeout.tv_sec = RAD_RCV_TIMEOUT; + timeout.tv_usec = 0; + + FD_ZERO(&fdset); + FD_SET(sd, &fdset); + + if (select(sd+1, &fdset, NULL, NULL, &timeout) < 0) { + free(tmp_data); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + if (FD_ISSET(sd, &fdset)) { + rcv_len = recv(sd, tmp_data, MAX_RAD_PACKET_LEN, 0); + break; + } else { + poll_cnt++; + } + + } while (poll_cnt < RAD_RETRY_MAX); + + if (poll_cnt >= RAD_RETRY_MAX) { + free(tmp_data); + return (RAD_RSP_RCVD_TIMEOUT); + } + + if (rcv_len < 0) { + /* Socket error. */ + free(tmp_data); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + packet = (radius_packet_t *)tmp_data; + bcopy(packet->length, &len, sizeof (ushort_t)); + declared_len = ntohs(len); + + /* + * Check if the received packet length is within allowable range. + * RFC 2865 section 3. + */ + if (rcv_len < MIN_RAD_PACKET_LEN) { + free(tmp_data); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } else if (rcv_len > MAX_RAD_PACKET_LEN) { + free(tmp_data); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + /* + * Check if the declared packet length is within allowable range. + * RFC 2865 section 3. + */ + if (declared_len < MIN_RAD_PACKET_LEN) { + free(tmp_data); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } else if (declared_len > MAX_RAD_PACKET_LEN) { + free(tmp_data); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + /* + * Discard packet with received length shorter than declared + * length. RFC 2865 section 3. + */ + if (rcv_len < declared_len) { + free(tmp_data); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + /* + * Authenticate the incoming packet, using the following algorithm + * (RFC 2865 section 3): + * + * MD5(Code+ID+Length+RequestAuth+Attributes+Secret) + * + * Code = RADIUS packet code + * ID = RADIUS packet identifier + * Length = Declared length of the packet + * RequestAuth = The request authenticator + * Attributes = The response attributes + * Secret = The shared secret + */ + MD5Init(&context); + bzero(&md5_digest, 16); + MD5Update(&context, &packet->code, 1); + MD5Update(&context, &packet->identifier, 1); + MD5Update(&context, packet->length, 2); + MD5Update(&context, req_authenticator, RAD_AUTHENTICATOR_LEN); + /* Include response attributes only if there is a payload */ + if (declared_len > RAD_PACKET_HDR_LEN) { + /* Response Attributes */ + MD5Update(&context, packet->data, + declared_len - RAD_PACKET_HDR_LEN); + } + MD5Update(&context, shared_secret, shared_secret_len); + MD5Final(md5_digest, &context); + + if (bcmp(md5_digest, packet->authenticator, RAD_AUTHENTICATOR_LEN) + != 0) { + free(tmp_data); + return (RAD_RSP_RCVD_AUTH_FAILED); + } + + /* + * If the received length is greater than the declared length, + * trust the declared length and shorten the packet (i.e., to + * treat the octets outside the range of the Length field as + * padding - RFC 2865 section 3). + */ + if (rcv_len > declared_len) { + /* Clear the padding data. */ + bzero(tmp_data + declared_len, rcv_len - declared_len); + rcv_len = declared_len; + } + + /* + * Annotate the RADIUS packet data with the data we received from + * the server. + */ + resp_data->code = packet->code; + resp_data->identifier = packet->identifier; + + free(tmp_data); + return (RAD_RSP_RCVD_SUCCESS); +} + +static +void +encode_chap_password(int identifier, + int chap_passwd_len, + uint8_t *chap_passwd, + uint8_t *result) +{ + result[0] = (uint8_t)identifier; + bcopy(chap_passwd, &result[1], chap_passwd_len); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/radius.h b/usr/src/cmd/iscsi/iscsitgtd/radius.h new file mode 100644 index 0000000000..fdb9684475 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/radius.h @@ -0,0 +1,243 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _RADIUS_H +#define _RADIUS_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <netinet/in.h> +#include <sys/int_types.h> + +/* Packet type. RFC 2865 section 4. */ +#define RAD_ACCESS_REQ 1 /* Authentication Request */ +#define RAD_ACCESS_ACPT 2 /* Authentication Accepted */ +#define RAD_ACCESS_REJ 3 /* Authentication Rejected */ + +/* RADIUS Attribute Types. RFC 2865 section 5. */ +#define RAD_USER_NAME 1 +#define RAD_CHAP_PASSWORD 3 +#define RAD_CHAP_CHALLENGE 60 + +/* RFC 2865 Section 3. The Identifier field is one octet. */ +#define RAD_IDENTIFIER_LEN 1 + +/* RFC 2865 Section 5.3. The String field is 16 octets. */ +#define RAD_CHAP_PASSWD_STR_LEN 16 + +/* RFC 2865 Section 3. Authenticator field is 16 octets. */ +#define RAD_AUTHENTICATOR_LEN 16 + +/* RFC 2865 Section 5: 1-253 octets */ +#define MAX_RAD_ATTR_VALUE_LEN 253 + +/* RFC 2865 Section 3. Minimum length 20 octets. */ +#define MIN_RAD_PACKET_LEN 20 + +/* RFC 2865 Section 3. Maximum length 4096 octets. */ +#define MAX_RAD_PACKET_LEN 4096 + +/* Maximum RADIUS shared secret length (in fact there is no defined limit) */ +#define MAX_RAD_SHARED_SECRET_LEN 128 + +/* RFC 2865 Section 3. Minimum RADIUS shared secret length */ +#define MIN_RAD_SHARED_SECRET_LEN 16 + +/* Raw RADIUS packet. RFC 2865 section 3. */ +typedef struct radius_packet { + uint8_t code; /* RADIUS code, section 3, RFC 2865 */ + uint8_t identifier; /* 1 octet in length. RFC 2865 section 3 */ + uint8_t length[2]; /* 2 octets, or sizeof (u_short) */ + uint8_t authenticator[RAD_AUTHENTICATOR_LEN]; + uint8_t data[1]; +} radius_packet_t; + +/* Length of a RADIUS packet minus the payload */ +#define RAD_PACKET_HDR_LEN 20 + + +typedef enum chap_validation_status_type { + CHAP_VALIDATION_PASSED, /* CHAP validation passed */ + CHAP_VALIDATION_INVALID_RESPONSE, /* Invalid CHAP response */ + CHAP_VALIDATION_DUP_SECRET, /* Same CHAP secret used */ + /* for authentication in the */ + /* other direction */ + CHAP_VALIDATION_UNKNOWN_AUTH_METHOD, /* Unknown authentication */ + /* method */ + CHAP_VALIDATION_INTERNAL_ERROR, /* MISC internal error */ + CHAP_VALIDATION_RADIUS_ACCESS_ERROR, /* Problem accessing RADIUS */ + CHAP_VALIDATION_BAD_RADIUS_SECRET, /* Invalid RADIUS shared */ + /* secret */ + CHAP_VALIDATION_UNKNOWN_RADIUS_CODE /* Irrelevant or unknown */ + /* RADIUS packet code */ + /* returned */ +} chap_validation_status_type; + +typedef enum authentication_method_type { + RADIUS_AUTHENTICATION, + DIRECT_AUTHENTICATION +} authentication_method_type; + +typedef struct _IPAddress { + union { + struct in_addr in4; + struct in6_addr in6; + } i_addr; + /* i_insize determines which is valid in the union above */ + int i_insize; +} iscsi_ipaddr_t; + +typedef struct radius_config { + iscsi_ipaddr_t rad_svr_addr; /* IPv6 enabled */ + uint32_t rad_svr_port; + uint8_t rad_svr_shared_secret[MAX_RAD_SHARED_SECRET_LEN]; + uint32_t rad_svr_shared_secret_len; +} RADIUS_CONFIG; + +/* A total of RAD_RCV_TIMEOUT * RAD_RETRY_MAX seconds timeout. */ +#define RAD_RCV_TIMEOUT 5 /* Timeout for receiving RADIUS packet in */ + /* sec. */ +#define RAD_RETRY_MAX 2 /* Max. # of times to retry receiving */ + /* packet. */ + +/* Describes a RADIUS attribute */ +typedef struct radius_attr { + int attr_type_code; /* RADIUS attribute type code, */ + /* e.g. RAD_USER_PASSWORD, etc. */ + int attr_value_len; + uint8_t attr_value[MAX_RAD_ATTR_VALUE_LEN]; +} radius_attr_t; + +/* Describes data fields of a RADIUS packet. */ +typedef struct radius_packet_data { + uint8_t code; /* RADIUS code, section 3, RFC 2865. */ + uint8_t identifier; + uint8_t authenticator[RAD_AUTHENTICATOR_LEN]; + int num_of_attrs; + radius_attr_t attrs[4]; /* For this implementation each */ + /* outbound RADIUS packet will only */ + /* have 3 attributes associated with */ + /* it thus the chosen size should be */ + /* good enough. */ +} radius_packet_data_t; + +/* + * Data in this structure is set by the user agent and consumed by + * the driver. + */ +#define MAX_RAD_SHARED_SECRET_LEN 128 +typedef struct radius_props { + uint32_t r_vers; + uint32_t r_oid; + union { + struct in_addr u_in4; + struct in6_addr u_in6; + } r_addr; + /* + * r_insize indicates which of the previous structs is valid. + */ + int r_insize; + + uint32_t r_port; + uint8_t r_shared_secret[MAX_RAD_SHARED_SECRET_LEN]; + boolean_t r_radius_access; + boolean_t r_radius_config_valid; + uint32_t r_shared_secret_len; +} iscsi_radius_props_t; + +/* + * Send a request to a RADIUS server. + * + * Returns > 0 on success, <= 0 on failure . + * + */ +int +snd_radius_request(int sd, + iscsi_ipaddr_t rsvr_ip_addr, + uint32_t rsvr_port, + radius_packet_data_t *packet_data); + +#define RAD_RSP_RCVD_SUCCESS 0 +#define RAD_RSP_RCVD_NO_DATA 1 +#define RAD_RSP_RCVD_TIMEOUT 2 +#define RAD_RSP_RCVD_PROTOCOL_ERR 3 +#define RAD_RSP_RCVD_AUTH_FAILED 4 +/* + * Receives a response from a RADIUS server. + * + * Return receive status. + */ +int +rcv_radius_response(int sd, + uint8_t *shared_secret, + uint32_t shared_secret_len, + uint8_t *req_authenticator, + radius_packet_data_t *resp_data); + +/* + * Function: radius_chap_validate + * + * Description: To validate a target response given the + * associated challenge via the specified + * RADIUS server. + * + * Arguments: + * target_chap_name - The CHAP name of the target being authenticated. + * initiator_chap_name - The CHAP name of the authenticating initiator. + * challenge - The CHAP challenge to which the target responded. + * target_response - The target's CHAP response to be validated. + * identifier - The identifier associated with the CHAP challenge. + * radius_server_ip_address - The IP address of the RADIUS server. + * radius_server_port - The port number of the RADIUS server. + * radius_shared_secret - The shared secret for accessing the RADIUS server. + * radius_shared_secret_len - The length of the shared secret. + * + * Return: See chap_validation_status_type. + */ +chap_validation_status_type +radius_chap_validate(char *target_chap_name, + char *initiator_chap_name, + uint8_t *challenge, + uint32_t challengeLength, + uint8_t *target_response, + uint32_t responseLength, + uint8_t identifier, + iscsi_ipaddr_t rad_svr_ip_addr, + uint32_t rad_svr_port, + uint8_t *rad_svr_shared_secret, + uint32_t rad_svr_shared_secret_len); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _RADIUS_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/sparcv9/Makefile b/usr/src/cmd/iscsi/iscsitgtd/sparcv9/Makefile new file mode 100644 index 0000000000..69b08dbca6 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/sparcv9/Makefile @@ -0,0 +1,32 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# cmd/iscsi/iscsitgt/sparcv9/Makefile + +include ../Makefile.com +include ../../../Makefile.cmd.64 + +install: all $(ROOTUSRSBINPROG64) diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10.h b/usr/src/cmd/iscsi/iscsitgtd/t10.h new file mode 100644 index 0000000000..bbcb97e00c --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10.h @@ -0,0 +1,623 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _T10_H +#define _T10_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This header file describes the service level between the transport + * layer and the emulation portion. These procedure calls can be thought + * of as part of the T10 SAM-3 specification. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Here are the header files which are required to define references found + * in this file. No other header files are to be included. + */ +#include <pthread.h> +#include <sys/avl.h> +#include <signal.h> +#include <sys/scsi/generic/sense.h> + +#include "queue.h" + +#ifdef lint +/* + * lints sees aio_return64, but can't find it in the aio structure. To keep + * lint happy this define is used. + */ +#define aio_return64 aio_return +#endif + +typedef void *transport_t; +typedef void *t10_targ_handle_t; + +typedef void *t10_lun_handle_t; + +typedef void *emul_handle_t; +typedef void *emul_cmd_t; + +typedef enum { + ClearSet, + ResetTarget, + ResetLun, + InventoryChange, + CapacityChange, + DeviceOnline, + DeviceOffline +} TaskOp_t; + +typedef enum { + T10_Cmd_Free = 1, + T10_Cmd_Alloc, + T10_Cmd_DataIn, + T10_Cmd_DataOut, + T10_Cmd_DataOut_Sent, + T10_Cmd_Complete, + T10_Cmd_Canceled, + T10_Cmd_Errored +} t10_cmd_state_t; + +typedef enum { + T10_Cmd_Event_DataOut_Sent, + T10_Cmd_Event_DataIn_Recv, + T10_Cmd_Event_Release, + T10_Cmd_Event_Canceled +} t10_cmd_event_t; + +typedef enum { + lu_online, + lu_offline, + lu_errored +} t10_lu_state_t; + +/* + * The t10_cmd_t structure bridges the gap between the transport and + * emulation services. At certain times either the transport or emulation + * service needs to access the data stored within this structure. + * For now we'll just use macros which hide the reference, but in the + * future when the transport and emulation services are loadable modules + * these macros will become functions so that the structure can change + * inside of the T10 space and not cause compatibility issues. + */ +#define T10_MAX_OUT(cmd) (cmd->c_lu->l_targ->s_maxout) +#define T10_MMAP_AREA(cmd) (cmd->c_lu->l_common->l_mmap) +#define T10_PARAMS_AREA(cmd) trans_params_area(cmd) +#define T10_TRANS_ID(cmd) (cmd->c_trans_id) +#define T10_DATA(cmd) (cmd->c_data) +#define T10_DATA_LEN(cmd) (cmd->c_data_len) +#define T10_DATA_OFFSET(cmd) (cmd->c_offset) +#define T10_CMD_LAST(cmd) (cmd->c_last) +#define T10_CMD_STATUS(cmd) (cmd->c_cmd_status) +#define T10_CMD_RESID(cmd) (cmd->c_resid) +#define T10_SENSE_LEN(cmd) (cmd->c_cmd_sense_len) +#define T10_SENSE_DATA(cmd) (cmd->c_cmd_sense) + +#define T10_DEFAULT_TPG 1 + +/* + * []------------------------------------------------------------------[] + * | SAM-3 revision 14, section 4.9 -- Logical Unit Numbers | + * | The specification allows for 64-bit LUNs, but at this point | + * | most OSes don't support that many. Section 4.9.7, table 9 gives | + * | the Flat Space Addressing Method which allows for 16,383 LUNs. | + * | This will be the imposed maximum even though the code can support | + * | more. Raise this number if needed. | + * []------------------------------------------------------------------[] + */ +#define T10_MAX_LUNS 16383 + +/* + * SPC-3 Revision 21c, Section 6.4.2 Table 85 + * Version Descriptor Values + */ +#define T10_TRANS_ISCSI 0x960 /* iSCSI (no version claimed) */ +#define T10_TRANS_FC 0x8c0 /* FCP (no version claimed) */ + +typedef struct t10_aio { + /* + * This must be the first member of the structure. aioread/aiowrite + * take as one of the arguments an pointer to a aio_result_t + * structure. When the operation is complete the aio_return and + * aio_errno of that structure are updated. When aiowait() is + * called the address of that aio_result_t is returned. By having + * this structure at the beginning we can pass in the data_ptr + * structure address. The ste_aio_process thread will get everything + * it needs from the aiowait to send a message to the correct + * STE thread. Clear as mud? + */ + aio_result_t a_aio; + + void (*a_aio_cmplt)(emul_cmd_t id); + emul_cmd_t a_id; +} t10_aio_t; + +/* + * Bidirectional structure used to track requests from the transport + * and send reponse data from the emulation. + * + * The glue logic for t10_send_cmd will allocate this structure, fill in + * in with the provided data and put it on the LUN queue. The LUN thread + * will dequeue this request and call the appropriate LUN command interpreter. + */ +typedef struct t10_cmd { + /* + * Transport specific tracking value. If this value is non-zero it + * means this command was part of a previous command that wasn't + * completed. Currently this is only used for DATA_OUT (SCSI write op) + * commands. + */ + transport_t c_trans_id; + + t10_cmd_state_t c_state; + + /* + * Emulation specific tracking value. + */ + emul_cmd_t c_emul_id; + + /* + * Per I_T_L structure used to determine which command + * interpreter to call and which transport queue to send the response. + */ + struct t10_lu_impl *c_lu; + + /* + * Pointer to command buffer. No interpretation of data is + * done by the glue logic. Interpretation is done by the LUN + * emulation code. + */ + uint8_t *c_cdb; + size_t c_cdb_len; + + /* + * Optional offset into the command. If more than one response + * is required this value indicates where the data belongs. + */ + off_t c_offset; + + /* + * Data for transfer. + */ + char *c_data; + size_t c_data_len; + size_t c_resid; + + /* + * Indicates if this response is the last to be sent + * and will be followed closely by a complete message. Enables + * transports to phase collapse the final READ data PDU with + * completion PDU if possible. + */ + Boolean_t c_last; + + /* + * When the transport is finished sending the data it will + * call t10_cmd_destroy() which will cause the SAM-3 layer to + * call the emulation function stored here with this command + * pointer. The emulation code is responsible for freeing any + * memory it allocated. + */ + void (*c_emul_complete)(emul_handle_t id); + + /* + * SCSI sense information. + */ + int c_cmd_status; + char *c_cmd_sense; + size_t c_cmd_sense_len; + + /* + * List of active commands at the ITL level. + */ + avl_tree_t c_cmd_avl; +} t10_cmd_t; + +/* + * Each LU has a structure which contains common data for all I_T's who + * access this LU. + */ +typedef struct t10_lu_common { + /* + * Logic Unit Number + */ + int l_num; + + /* + * state of device + */ + t10_lu_state_t l_state; + + /* + * Internal ID which will be unique for all LUs. This will be + * used for log messages to help tracking details. + */ + int l_internal_num; + + /* + * Thread ID which is running this logical unit. This is currently + * used for only one purpose which is to locate this structure + * in case of a SIGBUS. It's possible for the underlying file system + * to run out of space for an mmap'd LU. The only means of notification + * the OS has is to send a SIGBUS. The thread only receives the memory + * address, so we look for our thread ID amongst all of the LU + * available. + */ + pthread_t l_thr_id; + + /* + * If we receive a SIGBUS the initiator needs to be notified that + * something bad has occurred. This means we need to know which + * command was being emulated so that we can find the appropriate + * transport. + * Special handling needs to be done if the thread is initializing + * the LU so we need a flag to indicate that fact. + */ + t10_cmd_t *l_curr; + Boolean_t l_curr_provo; + + /* + * The implementation uses a 16 byte EUI value for the GUID. + * Not only is this value used for SCSI INQUIRY data, but it + * is used to distinquish this common LUN from other LUNs in + * the AVL tree. + */ + uint8_t *l_guid; + size_t l_guid_len; + + /* + * Other common information which is needed for ever device + * type. + */ + int l_dtype; + char *l_pid, + *l_vid; + + /* + * Each dtype has different parameters that it uses. This + * is a place holder for storing a pointer to some structure which + * contains that information. + */ + void *l_dtype_params; + + /* + * Parameter information in XML format. + */ + xml_node_t *l_root; + + /* + * File descriptor for the open file which is the backing store + * for this device. This can be a regular file or a character + * special device if we're acting as a bridge between transports. + */ + int l_fd; + + void *l_mmap; + off64_t l_size; + + Boolean_t l_fast_write_ack; + + /* + * AVL tree containing all I_T_Q nexus' which are actively using + * this LUN. + */ + avl_tree_t l_all_open; + + /* + * Each I_T will place requests for command emulation on this + * queue. Common requests are msg_ste_cmd and msg_ste_shutdown + */ + target_queue_t *l_from_transports; + + /* + * Mutex used to lock access to the AVL tree. + */ + pthread_mutex_t l_common_mutex; + + /* + * When a target is looking to see if an existing LUN is opened + * a search of all LUNs needs to be done and will use this + * AVL node. This field is modified only by the AVL code. + */ + avl_node_t l_all_luns; +} t10_lu_common_t; + +/* + * Each I_T_Q has a LU structure associated with it. + */ +typedef struct t10_lu_impl { + /* + * pointer to common area of LUN. + */ + t10_lu_common_t *l_common; + pthread_mutex_t l_mutex; + + /* + * Mutex to protect access to active commands + */ + pthread_mutex_t l_cmd_mutex; + pthread_cond_t l_cmd_cond; + Boolean_t l_wait_for_drain; + + avl_tree_t l_cmds; + + /* + * Queue for sending command results and R2T results back + * to the transport. + */ + target_queue_t *l_to_transport; + + /* + * Back pointer to target structure who created this LUN reference. + */ + struct t10_targ_impl *l_targ; + + struct scsi_cmd_table *l_cmd_table; + + /* + * Per LU methods for issuing commands and data to the + * DTYPE emulator. + */ + void (*l_cmd)(t10_cmd_t *cmd, uint8_t *cdb, + size_t cdb_len); + void (*l_data)(t10_cmd_t *cmd, emul_handle_t e, + size_t offset, char *data, size_t data_len); + + /* + * AVL node information for all other I_T nexus' who are referencing + * this LUN. This is used by the AVL code and *not* modified by + * this daemon directly. + */ + avl_node_t l_open_lu_node; + + /* + * AVL node information for all LUN's being access by this I_T nexus. + * This is used by the AVL code and *not* modified by this daemon + * directly. + */ + avl_node_t l_open_targ_node; + + /* + * Logical Unit Number. This value is used as the comparision value + * for the AVL search at the per target level. + */ + int l_targ_lun; + + Boolean_t l_dsense_enabled; + + /* + * Statistics on a per ITL basis + */ + uint64_t l_cmds_read, + l_cmds_write, + l_sects_read, + l_sects_write; + + /* + * Each time a command is run the value of l_status is checked. + * If non-zero the command isn't executed and instead a transport + * complete message is sent with these values. This is commonly + * used to send UNIT ATTENTION for things like power on. + * -- Do we need some sort of stack to push and pop these values? + */ + int l_status, + l_asc, + l_ascq; +} t10_lu_impl_t; + +typedef struct t10_targ_impl { + char *s_targ_base; + int s_targ_num; /* used in log messages */ + avl_tree_t s_open_lu; + pthread_mutex_t s_mutex; + + /* + * The transport layer will set the maximum output size + * it's able to deal with during a call to set_create_handle() + */ + size_t s_maxout; + + /* + * Target Port Set + */ + int s_tp_grp; + + /* + * transport version number to use in standard inquiry data + */ + int s_trans_vers; + + /* + * Transport response queue. This queue will be stored in each + * lun that gets created. + */ + target_queue_t *s_to_transport; + + /* + * During a SCSI WRITE the emulation will call trans_rqst_datain. + * If the transport indicated data was available by using non-zero + * values for the optional data and length when t10_send_cmd was + * called this callback is used when the emulation requests data. + */ + void (*s_dataout_cb)(t10_cmd_t *, char *data, + size_t *data_len); + +} t10_targ_impl_t; + +typedef struct t10_shutdown { + t10_lu_impl_t *t_lu; + target_queue_t *t_q; +} t10_shutdown_t; + +typedef struct scsi_cmd_table { + void (*cmd_start)(struct t10_cmd *, uint8_t *, size_t); + void (*cmd_data)(struct t10_cmd *, emul_handle_t e, + size_t offset, char *data, size_t data_len); + void (*cmd_end)(emul_handle_t e); + char *cmd_name; +} scsi_cmd_table_t; + +/* + * []---- + * | Interfaces + * []---- + */ + +extern target_queue_t *mgmtq; +void t10_init(target_queue_t *q); +void lu_buserr_handler(int sig, siginfo_t *sip, void *v); + +/* + * []------------------------------------------------------------------[] + * | Methods called by the transports | + * []------------------------------------------------------------------[] + */ +/* + * t10_handle_create -- create target handle to be used by transports + */ +t10_targ_handle_t +t10_handle_create(char *targ_name, int trans_version, int tpg, int max_out, + target_queue_t *transq, void (*datain_cb)(t10_cmd_t *, char *, size_t *)); + +/* + * t10_handle_disable -- drains commands from emulation queues + */ +void +t10_handle_disable(t10_targ_handle_t t); + +/* + * t10_handle_destroy -- free resources used by handle + */ +void +t10_handle_destroy(t10_targ_handle_t t); + +Boolean_t +t10_cmd_create(t10_targ_handle_t t, int lun_number, uint8_t *cdb, + size_t cdb_len, transport_t trans_id, t10_cmd_t **); + +/* + * t10_send_cmd -- send a command block to an target/LUN for emulation + */ +Boolean_t +t10_cmd_send(t10_targ_handle_t t, t10_cmd_t *cmd, + char *opt_data, size_t opt_data_len); + +Boolean_t +t10_cmd_data(t10_targ_handle_t t, t10_cmd_t *cmd, size_t offset, + char *data, size_t data_len); + +Boolean_t +t10_task_mgmt(t10_targ_handle_t t, TaskOp_t op, int opt_lun, void *tag); + +/* + * t10_cmd_state -- issue an event to change the state of a T10 layer command + */ +void t10_cmd_state(t10_cmd_t *c, t10_cmd_event_t e); + +void t10_targ_stat(t10_targ_handle_t t, char **buf); + +/* + * t10_thick_provision -- management function used when creating a new lun + */ +Boolean_t t10_thick_provision(char *target, int lun, target_queue_t *q); + +/* + * []------------------------------------------------------------------[] + * | Methods called by the emulation routines | + * []------------------------------------------------------------------[] + */ +/* + * trans_send_datain -- Emulation layer sending data to initiator + */ +Boolean_t +trans_send_datain(t10_cmd_t *cmd, char *data, size_t data_len, size_t offset, + void (*callback)(emul_handle_t t), Boolean_t last, emul_handle_t id); + +/* + * trans_rqst_dataout -- Emulation needs more data to complete request + */ +Boolean_t +trans_rqst_dataout(t10_cmd_t *cmd, char *data, size_t data_len, size_t offset, + emul_cmd_t emul_id); + +/* + * trans_send_complete -- Emulation has completed request w/ opt. sense data + */ +void +trans_send_complete(t10_cmd_t *cmd, int t10_status); + +/* + * trans_aiowrite -- asynchronous write and kicks the aio wait thread + */ +void trans_aiowrite(t10_cmd_t *cmd, char *data, size_t data_len, off_t offset, + aio_result_t *aiop); + +/* + * trans_aioread -- asynchronous read and kicks the aio wait thread + */ +void trans_aioread(t10_cmd_t *cmd, char *data, size_t data_len, off_t offset, + aio_result_t *aiop); + +/* + * trans_params_area -- given a t10_cmd return the dtype params + */ +void *trans_params_area(t10_cmd_t *cmd); + +/* + * []------------------------------------------------------------------[] + * | Declaration of emulation entry points | + * []------------------------------------------------------------------[] + */ +Boolean_t sbc_init_common(t10_lu_common_t *lu); +void sbc_fini_common(t10_lu_common_t *lu); +void sbc_task_mgmt(t10_lu_common_t *lu, TaskOp_t op); +void sbc_init_per(t10_lu_impl_t *itl); +void sbc_fini_per(t10_lu_impl_t *itl); +Boolean_t ssc_init_common(t10_lu_common_t *lu); +void ssc_fini_common(t10_lu_common_t *lu); +void ssc_task_mgmt(t10_lu_common_t *lu, TaskOp_t op); +void ssc_init_per(t10_lu_impl_t *itl); +void ssc_fini_per(t10_lu_impl_t *itl); +Boolean_t raw_init_common(t10_lu_common_t *lu); +void raw_fini_common(t10_lu_common_t *lu); +void raw_init_per(t10_lu_impl_t *itl); +void raw_fini_per(t10_lu_impl_t *itl); +Boolean_t osd_init_common(t10_lu_common_t *lu); +void osd_fini_common(t10_lu_common_t *lu); +void osd_init_per(t10_lu_impl_t *itl); +void osd_fini_per(t10_lu_impl_t *itl); + +#ifdef __cplusplus +} +#endif + +#endif /* _T10_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_osd.c b/usr/src/cmd/iscsi/iscsitgtd/t10_osd.c new file mode 100644 index 0000000000..a200dd1030 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_osd.c @@ -0,0 +1,591 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * []------------------------------------------------------------------[] + * | Implementation of OSD emulation | + * | | + * | NOTE: At this point in time this file is nothing more than a | + * | place holder for the implementation. As the project evolves we'll | + * | add more and more functionality. | + * []------------------------------------------------------------------[] + */ +#include <sys/types.h> +#include <aio.h> +#include <sys/asynch.h> +#include <sys/mman.h> +#include <stddef.h> +#include <strings.h> +#include <unistd.h> +#include <assert.h> + +#include <sys/scsi/generic/sense.h> +#include <sys/scsi/generic/status.h> +#include <sys/scsi/generic/inquiry.h> +#include <sys/scsi/generic/commands.h> +#include <sys/scsi/generic/mode.h> +#include <sys/scsi/generic/dad_mode.h> + +#include "t10.h" +#include "t10_spc.h" +#include "t10_osd.h" +#include "utility.h" + +/* + * Forward declarations + */ +static void osd_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +static void osd_data(t10_cmd_t *cmd, emul_handle_t e, size_t offset, + char *data, size_t data_len); +static scsi_cmd_table_t osd_table[]; +static void osd_list(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); + +/* + * []---- + * | osd_init_common -- Initialize LU data which is common to all I_T_Ls + * []---- + */ +/*ARGSUSED*/ +Boolean_t +osd_init_common(t10_lu_common_t *lu) +{ + osd_params_t *o; + char *str; + xml_node_t *node = lu->l_root; + + if ((o = (osd_params_t *)calloc(1, sizeof (*o))) == NULL) + return (False); + + if (xml_find_value_str(node, XML_ELEMENT_SIZE, &str) == True) { + o->o_size = strtoll(str, NULL, 0); + free(str); + } else { + free(o); + return (False); + } + + lu->l_dtype_params = (void *)o; + return (True); +} + +/*ARGSUSED*/ +void +osd_fini_common(t10_lu_common_t *lu) +{ + free(lu->l_dtype_params); +} + +/* + * []---- + * | osd_init_per -- Initialize per I_T_L information + * []---- + */ +/*ARGSUSED*/ +void +osd_init_per(t10_lu_impl_t *itl) +{ + itl->l_cmd = osd_cmd; + itl->l_data = osd_data; + itl->l_cmd_table = osd_table; + + /* + * The first time an I_T nexus connects to a LU it is supposed + * to receive an unit attention upon the first command sent. + */ + itl->l_status = KEY_UNIT_ATTENTION; + itl->l_asc = SPC_ASC_PWR_ON; + itl->l_ascq = SPC_ASCQ_PWR_ON; +} + +/*ARGSUSED*/ +void +osd_fini_per(t10_lu_impl_t *itl) +{ +} + +/* + * []---- + * | osd_cmd -- start a SCSI command + * | + * | This routine is called from within the SAM-3 Task router. + * []---- + */ +static void +osd_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + scsi_cmd_table_t *e; + + e = &cmd->c_lu->l_cmd_table[cdb[0]]; +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "SBC%x LUN%d Cmd %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name == NULL ? "(no name)" : e->cmd_name); +#endif + (*e->cmd_start)(cmd, cdb, cdb_len); +} + +/* + * []---- + * | osd_data -- Data phase for command. + * | + * | Normally this is only called for the WRITE command. Other commands + * | that have a data in phase will probably be short circuited when + * | we call trans_rqst_dataout() and the data is already available. + * | At least this is true for iSCSI. FC however will need a DataIn phase + * | for commands like MODE SELECT and PGROUT. + * []---- + */ +static void +osd_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + scsi_cmd_table_t *e; + + e = &cmd->c_lu->l_cmd_table[cmd->c_cdb[0]]; +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "SBC%x LUN%d Data %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name); +#endif + (*e->cmd_data)(cmd, id, offset, data, data_len); +} + +/* + * []------------------------------------------------------------------[] + * | SCSI Object-Based Storage Device Commands | + * | T10/1355-D | + * | The following functions implement the emulation of OSD type | + * | commands. | + * []------------------------------------------------------------------[] + */ +static void +osd_service_action(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + osd_generic_cdb_t *o; + uint16_t service_action; + + /* + * debug only -- no need to drop core if someone doesn't play right. + */ + assert(cdb_len == sizeof (*o)); + if (cdb_len != sizeof (*o)) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, SPC_ASCQ_INVALID_CDB); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + o = (osd_generic_cdb_t *)cdb; + service_action = o->ocdb_basic.b_service_action[0] << 8 | + o->ocdb_basic.b_service_action[1]; + + queue_prt(mgmtq, Q_STE_NONIO, + "OSD%x LUN%d service=0x%x, options=0x%x, specific_opts=0x%x," + " fmt=0x%x", cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, service_action, o->ocdb_options, + o->ocdb_specific_opts, o->ocdb_fmt); + + switch (service_action) { + case OSD_APPEND: + case OSD_CREATE: + case OSD_CREATE_AND_WRITE: + case OSD_CREATE_COLLECTION: + case OSD_CREATE_PARTITION: + case OSD_FLUSH: + case OSD_FLUSH_COLLECTION: + case OSD_FLUSH_OSD: + case OSD_FLUSH_PARTITION: + case OSD_FORMAT_OSD: + case OSD_GET_ATTR: + case OSD_LIST: + osd_list(cmd, cdb, cdb_len); + break; + + case OSD_LIST_COLLECTION: + case OSD_PERFORM_SCSI: + case OSD_TASK_MGMT: + default: + spc_unsupported(cmd, cdb, cdb_len); + break; + } +} + +/* + * []---- + * | osd_list -- return a list of objects + * []---- + */ +static void +osd_list(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + osd_cmd_list_t *o = (osd_cmd_list_t *)cdb; + osd_obj_id_t part; + osd_list_param_t *data; + uint64_t len, + alloc_len; + + part = (uint64_t)o->ocdb_partition_id[0] << 56 | + (uint64_t)o->ocdb_partition_id[1] << 48 | + (uint64_t)o->ocdb_partition_id[2] << 40 | + (uint64_t)o->ocdb_partition_id[3] << 32 | + (uint64_t)o->ocdb_partition_id[4] << 24 | + (uint64_t)o->ocdb_partition_id[5] << 16 | + (uint64_t)o->ocdb_partition_id[6] << 8 | + (uint64_t)o->ocdb_partition_id[7]; + len = (uint64_t)o->ocdb_length[0] << 56 | + (uint64_t)o->ocdb_length[1] << 48 | + (uint64_t)o->ocdb_length[2] << 40 | + (uint64_t)o->ocdb_length[3] << 32 | + (uint64_t)o->ocdb_length[4] << 24 | + (uint64_t)o->ocdb_length[5] << 16 | + (uint64_t)o->ocdb_length[6] << 8 | + (uint64_t)o->ocdb_length[7]; + + if (len == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + queue_prt(mgmtq, Q_STE_NONIO, "part=0x%llx, len=0x%llx", part, len); + alloc_len = MAX(sizeof (*data), len); + if ((data = calloc(1, alloc_len)) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + data->op_length[7] = sizeof (*data) - 8; + if (part == OSD_PARTITION_ROOT) + data->op_root = 1; + + trans_send_datain(cmd, (char *)data, sizeof (*data), 0, free, True, + (emul_handle_t)data); +} + +/* + * []------------------------------------------------------------------[] + * | Support related functions for OSD | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | Command table for OSD emulation. This is at the end of the file because + * | it's big and ugly. ;-) To make for fast translation to the appropriate + * | emulation routine we just have a big command table with all 256 possible + * | entries. Most will report STATUS_CHECK, unsupport operation. By doing + * | this we can avoid error checking for command range or the use of a switch + * | statement. + * []---- + */ +static scsi_cmd_table_t osd_table[] = { + /* 0x00 -- 0x0f */ + { spc_tur, NULL, NULL, "TEST_UNIT_READY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_request_sense, NULL, NULL, "REQUEST_SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "READ" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "WRITE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x10 -- 0x1f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_inquiry, NULL, NULL, "INQUIRY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_mselect, spc_mselect_data, NULL, "MODE_SELECT" }, + { spc_unsupported, NULL, NULL, "RESERVE" }, + { spc_unsupported, NULL, NULL, "RELEASE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "MODE_SENSE" }, + { spc_unsupported, NULL, NULL, "START_STOP" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_send_diag, NULL, NULL, "SEND_DIAG" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x20 -- 0x2f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "READ_CAPACITY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "READ_G1" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "WRITE_G1" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x30 -- 0x3f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "SYNC_CACHE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x40 -- 0x4f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "LOG_SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x50 -- 0x5f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "PERSISTENT_IN" }, + { spc_unsupported, NULL, NULL, "PERSISTENT_OUT" }, + + /* 0x60 -- 0x6f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x70 -- 0x7f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { osd_service_action, NULL, NULL, "SERVICE_ACTION" }, + + /* 0x80 -- 0x8f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "READ_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "WRITE_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x90 -- 0x9f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "SVC_ACTION_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xa0 - 0xaf */ + { spc_report_luns, NULL, NULL, "REPORT_LUNS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_report_tpgs, NULL, NULL, "REPORT_TPGS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xb0 -- 0xbf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xc0 -- 0xcf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xd0 -- 0xdf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xe0 -- 0xef */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xf0 -- 0xff */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, +}; diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_osd.h b/usr/src/cmd/iscsi/iscsitgtd/t10_osd.h new file mode 100644 index 0000000000..3a6d4d64ef --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_osd.h @@ -0,0 +1,174 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _T10_OSD_H +#define _T10_OSD_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Defines and structures for Object-Base Storage Device emulation + */ + +/* + * OSD revision 10, section 4.6.2 + * Partition_ID and User_Object_ID value assignments + */ +#define OSD_PARTITION_ROOT 0x00LL +#define OSD_PARTITION_BASE 0x10000LL +#define OSD_USER_OBJ_ROOT 0x00LL +#define OSD_USER_OBJ_BASE 0x10000LL + +/* + * OSD revision 10, section 6.1 + * Commands for OSD type devices + */ +#define OSD_APPEND 0x8807 +#define OSD_CREATE 0x8802 +#define OSD_CREATE_AND_WRITE 0x8812 +#define OSD_CREATE_COLLECTION 0x8815 +#define OSD_CREATE_PARTITION 0x880b +#define OSD_FLUSH 0x8808 +#define OSD_FLUSH_COLLECTION 0x881a +#define OSD_FLUSH_OSD 0x881c +#define OSD_FLUSH_PARTITION 0x881b +#define OSD_FORMAT_OSD 0x8801 +#define OSD_GET_ATTR 0x880e +#define OSD_LIST 0x8803 +#define OSD_LIST_COLLECTION 0x8817 +#define OSD_PERFORM_SCSI 0x8f7e +#define OSD_TASK_MGMT 0x8f7f + +typedef uint64_t osd_obj_id_t; + +typedef struct osd_params { + uint64_t o_size; +} osd_params_t; + +/* + * OSD revision 10, section 5.1 + * OSD CDB Format -- basic OSD CDB + */ +typedef struct osd_cmd_basic { + uint8_t b_code, + b_control, + b_rsvd[5], + b_add_cdblen, + b_service_action[2]; +} osd_cmd_basic_t; + +/* + * OSD revision 10, section 5.2.1 + * OSD service action specific fields + * The specification doesn't repeat the fields found in the basic OSD CDB, + * but it's included here so that one structure contains everything. + */ +typedef struct osd_generic_cdb { + osd_cmd_basic_t ocdb_basic; + uint8_t ocdb_options; +#if defined(_BIT_FIELDS_LTOH) + uint8_t ocdb_specific_opts : 4, + ocdb_fmt : 2, + : 2; +#elif defined(_BIT_FIELDS_HTOL) + uint8_t : 2, + ocdb_fmt : 2, + ocdb_specific_opts : 4; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uint8_t ocdb_ts_control, + ocdb_rsvd1[3], + ocdb_partition_id[8], + ocdb_object_id[8], + ocdb_rsvd2[4], + ocdb_length[8], + ocdb_start_addr[8], + ocdb_attr_params[28], + ocdb_capability[80], + ocdb_security_params[40]; +} osd_generic_cdb_t; + +/* + * []------------------------------------------------------------------[] + * | OSD revision 10, section 6.13 -- LIST command | + * []------------------------------------------------------------------[] + */ +typedef struct osd_cmd_list { + osd_cmd_basic_t ocdb_basic; + uint8_t ocdb_rsvd1; +#if defined(_BIT_FIELDS_LTOH) + uint8_t ocdb_sort_order : 4, + ocdb_fmt : 2, + : 2; +#elif defined(_BIT_FIELDS_HTOL) + uint8_t : 2, + ocdb_fmt : 2, + ocdb_sort_order : 4; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uint8_t ocdb_ts_control, + ocdb_rsvd2[3], + ocdb_partition_id[8], + ocdb_rsvd3[8], + ocdb_list_id[4], + ocdb_length[8], + ocdb_object_id[8], + ocdb_attr_params[28], + ocdb_capability[80], + ocdb_security_params[40]; +} osd_cmd_list_t; + +/* ---- Table 66, LIST command parameter data ---- */ +typedef struct osd_list_param { + uint8_t op_length[8], + op_cont_obj_id[8], + op_list_id[4], + op_rsvd1[3]; +#if defined(_BIT_FIELDS_LTOH) + uint8_t op_root : 1, + op_lstchg : 1, + : 6; +#elif defined(_BIT_FIELDS_HTOL) + uint8_t : 6, + op_lstchg : 1, + op_root : 1; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + osd_obj_id_t op_list[1]; +} osd_list_param_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _T10_OSD_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_raw_if.c b/usr/src/cmd/iscsi/iscsitgtd/t10_raw_if.c new file mode 100644 index 0000000000..05a6b933fc --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_raw_if.c @@ -0,0 +1,1519 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * []------------------------------------------------------------------[] + * | Implementation of SBC-2 emulation | + * []------------------------------------------------------------------[] + */ +#include <sys/types.h> +#include <sys/asynch.h> +#include <sys/mman.h> +#include <stddef.h> +#include <strings.h> +#include <errno.h> +#include <unistd.h> +#include <sys/sysmacros.h> + +#include <sys/scsi/generic/sense.h> +#include <sys/scsi/generic/status.h> +#include <sys/scsi/generic/inquiry.h> +#include <sys/scsi/generic/commands.h> +#include <sys/scsi/generic/mode.h> +#include <sys/scsi/generic/dad_mode.h> +#include <sys/scsi/impl/uscsi.h> + +#include "t10.h" +#include "t10_spc.h" +#include "utility.h" +#include "target.h" + +typedef struct raw_io { + t10_aio_t r_aio; + t10_cmd_t *r_cmd; + + uint8_t *r_cdb; + char *r_data; + size_t r_cdb_len, + r_data_len; + uint64_t r_offset, + r_lba; + size_t r_lba_cnt; + uint32_t r_status; +} raw_io_t; + +typedef struct raw_params { + uint64_t r_size; + int r_dtype; +} raw_params_t; + +typedef enum { RawDataToDevice, RawDataFromDevice, NoData } raw_direction_t; + +/* + * Forward declarations + */ +static scsi_cmd_table_t raw_table[]; +static void raw_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +static void raw_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, + char *data, size_t data_len); +static void raw_free_io(emul_handle_t id); +static void do_dataout(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len, + size_t opt_data_len); +static raw_io_t *do_datain(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len, + size_t data_len); +static int do_uscsi(t10_cmd_t *cmd, raw_io_t *io, raw_direction_t dir); +static void raw_read_cmplt(emul_handle_t id); +static void raw_write_cmplt(emul_handle_t e); + +/* + * []---- + * | raw_init_common -- Initialize LU data which is common to all I_T_Ls + * []---- + */ +Boolean_t +raw_init_common(t10_lu_common_t *lu) +{ + xml_node_t *node = lu->l_root; + char *str; + raw_params_t *r; + + if ((r = (raw_params_t *)calloc(1, sizeof (*r))) == NULL) + return (False); + + if (xml_find_value_str(node, XML_ELEMENT_SIZE, &str) == True) { + r->r_size = strtoll(str, NULL, 0); + free(str); + } + lu->l_dtype_params = (void *)r; + return (True); +} + +void +raw_fini_common(t10_lu_common_t *lu) +{ + free(lu->l_dtype_params); +} + +/* + * []---- + * | raw_init_per -- Initialize per I_T_L information + * []---- + */ +void +raw_init_per(t10_lu_impl_t *itl) +{ + itl->l_cmd = raw_cmd; + itl->l_data = raw_data; + itl->l_cmd_table = raw_table; + + /* + * The first time an I_T nexus connects to a LU it is supposed + * to receive an unit attention upon the first command sent. + */ + itl->l_status = KEY_UNIT_ATTENTION; + itl->l_asc = 0x29; + itl->l_ascq = 0x01; +} + +/*ARGSUSED*/ +void +raw_fini_per(t10_lu_impl_t *itl) +{ +} + +/* + * []---- + * | raw_cmd -- start a SCSI command + * | + * | This routine is called from within the SAM-3 Task router. + * []---- + */ +static void +raw_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + scsi_cmd_table_t *e; +#ifdef FULL_DEBUG + char debug[80]; +#endif + + e = &cmd->c_lu->l_cmd_table[cdb[0]]; +#ifdef FULL_DEBUG + (void) snprintf(debug, sizeof (debug), "RAW%d Cmd %s\n", + cmd->c_lu->l_common->l_num, + e->cmd_name == NULL ? "(no name)" : e->cmd_name); + queue_str(mgmtq, Q_STE_IO, msg_log, debug); +#endif + (*e->cmd_start)(cmd, cdb, cdb_len); +} + +/* + * []---- + * | raw_data -- Data phase for command. + * | + * | Normally this is only called for the WRITE command. Other commands + * | that have a data in phase will probably be short circuited when + * | we call trans_rqst_dataout() and the data is already available. + * | At least this is true for iSCSI. FC however will need a DataIn phase + * | for commands like MODE SELECT and PGROUT. + * []---- + */ +static void +raw_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + scsi_cmd_table_t *e; +#ifdef FULL_DEBUG + char debug[80]; +#endif + + e = &cmd->c_lu->l_cmd_table[cmd->c_cdb[0]]; +#ifdef FULL_DEBUG + (void) snprintf(debug, sizeof (debug), "RAW%d Data %s\n", + cmd->c_lu->l_common->l_num, e->cmd_name); + queue_str(mgmtq, Q_STE_IO, msg_log, debug); +#endif + (*e->cmd_data)(cmd, id, offset, data, data_len); +} + +/* + * []------------------------------------------------------------------[] + * | The following methods handle special case requirements for the | + * | raw devices. | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | raw_read_tape -- handle SCSI reads from raw tape + * | + * | Need to handle reads from SCSI tape differently than LBA devices + * | for two reasons. + * | (1) The command block for tape reads is different than for + * | LBA devices. There's only a count field. + * | (2) Since tapes have records it's not possible to break up + * | the read operations in the same manner as LBA devices. + * | All of the data must first be read in from the device + * | and then broken up to fit the transport. This is a slower + * | approach, but nobody expects tapes to be quick. If speed + * | is needed a better approach would be to create a virtual + * | tape device and then stage out the data to the device later. + * []---- + */ +/*ARGSUSED*/ +static void +raw_read_tape(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + size_t req_len, + xfer; + off_t offset = 0; + raw_io_t *io; + Boolean_t last; + + req_len = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4]; + if (cdb[1] & 0x1) + req_len *= 512; + + if (((io = do_datain(cmd, cdb, CDB_GROUP0, req_len)) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + while (offset < io->r_data_len) { + xfer = min(T10_MAX_OUT(cmd), io->r_data_len - offset); + last = ((offset + xfer) >= io->r_data_len) ? True : False; + + if (trans_send_datain(cmd, io->r_data + offset, + xfer, offset, raw_free_io, last, io) == False) { + raw_free_io(io); + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + offset += xfer; + } +} + +/* + * []---- + * | raw_read -- emulation of SCSI READ command + * []---- + */ +/*ARGSUSED*/ +static void +raw_read(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /*LINTED*/ + union scsi_cdb *u = (union scsi_cdb *)cdb; + diskaddr_t addr; + off_t offset = 0; + uint32_t cnt, + min; + raw_io_t *io; + uint64_t err_blkno; + int sense_len; + char debug[80]; + raw_params_t *r; + uchar_t addl_sense_len; + + if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + if (r->r_dtype == DTYPE_SEQUENTIAL) { + raw_read_tape(cmd, cdb, cdb_len); + return; + } + + switch (u->scc_cmd) { + case SCMD_READ: + /* + * SBC-2 Revision 16, section 5.5 + * Reserve bit checks + */ + if ((cdb[1] & 0xe0) || (cdb[5] & 0x38)) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x24, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + addr = (diskaddr_t)(uint32_t)GETG0ADDR(u); + cnt = GETG0COUNT(u); + + /* + * SBC-2 Revision 16 + * Section: 5.5 READ(6) command + * A TRANSFER LENGTH field set to zero specifies + * that 256 logical blocks shall be read. + */ + if (cnt == 0) + cnt = 256; + break; + + case SCMD_READ_G1: + /* + * SBC-2 Revision 16, section 5.6 + * Reserve bit checks. + */ + if ((cdb[1] & 6) || cdb[6] || (cdb[9] & 0x38)) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x24, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + addr = (diskaddr_t)(uint32_t)GETG1ADDR(u); + cnt = GETG1COUNT(u); + break; + + case SCMD_READ_G4: + /* + * SBC-2 Revision 16, section 5.8 + * Reserve bit checks + */ + if ((cdb[1] & 0x6) || (cdb[10] & 6) || cdb[14] || + (cdb[15] & 0x38)) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x24, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + addr = GETG4LONGADDR(u); + cnt = GETG4COUNT(u); + break; + + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((addr + cnt) > r->r_size) { + + /* + * request exceed the capacity of disk + * set error block number to capacity + 1 + */ + err_blkno = r->r_size + 1; + + /* + * XXX: What's SBC-2 say about ASC/ASCQ here. Solaris + * doesn't care about these values when key is set + * to KEY_ILLEGAL_REQUEST. + */ + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + addl_sense_len = INFORMATION_SENSE_DESCR; + else + addl_sense_len = 0; + + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len); + spc_sense_info(cmd, err_blkno); + spc_sense_ascq(cmd, 0x21, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + + (void) snprintf(debug, sizeof (debug), + "RAW%d READ Illegal sector (0x%llx + 0x%x) > 0x%llx", + cmd->c_lu->l_common->l_num, addr, cnt, r->r_size); + queue_str(mgmtq, Q_STE_ERRS, msg_log, debug); + return; + } + + cmd->c_lu->l_cmds_read++; + cmd->c_lu->l_sects_read += cnt; + + if (cnt == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + do { + if ((io = (raw_io_t *)calloc(1, sizeof (*io))) == NULL) { + + /* + * We're pretty much dead in the water. If we can't + * allocate memory. It's unlikey we'll be able to + * allocate a sense buffer or queue the command + * up to be sent back to the transport for delivery. + */ + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + min = MIN((cnt * 512) - offset, T10_MAX_OUT(cmd)); + + io->r_cmd = cmd; + io->r_lba = addr; + io->r_lba_cnt = cnt; + io->r_offset = offset; + io->r_data_len = min; + io->r_aio.a_aio_cmplt = raw_read_cmplt; + io->r_aio.a_id = io; + +#ifdef FULL_DEBUG + (void) snprintf(debug, sizeof (debug), + "RAW%d blk 0x%llx, cnt %d, offset 0x%llx, size %d", + cmd->c_lu->l_common->l_num, addr, cnt, io->r_offset, min); + queue_str(mgmtq, Q_STE_IO, msg_log, debug); +#endif + if ((io->r_data = (char *)malloc(min)) == NULL) { + err_blkno = addr + ((offset + 511) / 512); + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + sense_len = INFORMATION_SENSE_DESCR; + else + sense_len = 0; + spc_sense_create(cmd, KEY_HARDWARE_ERROR, + sense_len); + spc_sense_info(cmd, err_blkno); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + trans_aioread(cmd, io->r_data, min, (addr * 512LL) + + (off_t)io->r_offset, (aio_result_t *)io); + offset += min; + } while (offset < (off_t)(cnt * 512)); +} + +/* + * []---- + * | raw_read_cmplt -- Once we have the data, need to send it along. + * []---- + */ +static void +raw_read_cmplt(emul_handle_t id) +{ + raw_io_t *io = (raw_io_t *)id; + int sense_len; + uint64_t err_blkno; + t10_cmd_t *cmd = io->r_cmd; + Boolean_t last; + + if (io->r_aio.a_aio.aio_return != io->r_data_len) { + err_blkno = io->r_lba + ((io->r_offset + 511) / 512); + cmd->c_resid = (io->r_lba_cnt * 512) - io->r_offset; + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + sense_len = INFORMATION_SENSE_DESCR; + else + sense_len = 0; + spc_sense_create(cmd, KEY_HARDWARE_ERROR, sense_len); + spc_sense_info(cmd, err_blkno); + trans_send_complete(cmd, STATUS_CHECK); + raw_free_io(io); + return; + } + + last = ((io->r_offset + io->r_data_len) < (io->r_lba_cnt * 512LL)) ? + False : True; + if (trans_send_datain(cmd, io->r_data, io->r_data_len, + io->r_offset, raw_free_io, last, io) == False) { + raw_free_io(io); + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +/*ARGSUSED*/ +static void +raw_write_tape(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + size_t request_len, + xfer; + raw_io_t *io; + + request_len = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4]; + request_len *= (cdb[1] & 0x1) ? 512 : 1; + + if ((io = calloc(1, sizeof (*io))) == NULL) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if ((io->r_data = malloc(request_len)) == NULL) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } + io->r_data_len = request_len; + io->r_cmd = cmd; + + xfer = min(T10_MAX_OUT(cmd), request_len); + (void) trans_rqst_dataout(cmd, io->r_data, xfer, io->r_offset, io); +} + +/*ARGSUSED*/ +void +raw_write_tape_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + raw_io_t *io = (raw_io_t *)id; + size_t xfer; + + if ((io->r_offset + data_len) < io->r_data_len) { + io->r_offset += data_len; + xfer = min(T10_MAX_OUT(cmd), io->r_data_len - io->r_offset); + (void) trans_rqst_dataout(cmd, io->r_data + io->r_offset, xfer, + io->r_offset, io); + return; + } else { + trans_send_complete(cmd, do_uscsi(cmd, io, RawDataToDevice)); + } +} + +/* + * []---- + * | raw_write -- implement a SCSI write command. + * []---- + */ +/*ARGSUSED*/ +static void +raw_write(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /*LINTED*/ + union scsi_cdb *cdbp = (union scsi_cdb *)cdb; + off_t addr; + uint64_t err_blkno; + uint32_t cnt; + uchar_t addl_sense_len; + char debug[80]; /* debug */ + raw_params_t *r; + raw_io_t *io; + size_t max_out; + + if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + if (r->r_dtype == DTYPE_SEQUENTIAL) { + raw_write_tape(cmd, cdb, cdb_len); + return; + } + + switch (cdb[0]) { + case SCMD_WRITE: + /* + * SBC-2 revision 16, section 5.24 + * Reserve bit checks. + */ + if ((cdb[1] & 0xe0) || (cdb[5] & 0x38)) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x24, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + addr = (off_t)cdbp->g0_addr2 << 16 | + (off_t)cdbp->g0_addr1 << 8 | (off_t)cdbp->g0_addr0; + cnt = cdbp->g0_count0; + /* + * SBC-2 Revision 16/Section 5.24 WRITE(6) + * A TRANSFER LENGHT of 0 indicates that 256 logical blocks + * shall be written. + */ + if (cnt == 0) + cnt = 256; + break; + + case SCMD_WRITE_G1: + /* + * SBC-2 revision 16, section 5.25 + * Reserve bit checks. + */ + if ((cdb[1] & 0x6) || cdb[6] || (cdb[9] & 0x38)) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x24, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + addr = (off_t)cdbp->g1_addr3 << 24 | + (off_t)cdbp->g1_addr2 << 16 | + (off_t)cdbp->g1_addr1 << 8 | + (off_t)cdbp->g1_addr0; + cnt = cdbp->g1_count1 << 8 | cdbp->g1_count0; + break; + + case SCMD_WRITE_G4: + /* + * SBC-2 revision 16, section 5.27 + * Reserve bit checks. + */ + if ((cdb[1] & 0x6) || cdb[14] || (cdb[15] & 0x38)) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x24, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + addr = (off_t)(cdbp->g4_addr3 & 0xff) << 56 | + (off_t)(cdbp->g4_addr2 & 0xff) << 48 | + (off_t)(cdbp->g4_addr1 & 0xff) << 40 | + (off_t)(cdbp->g4_addr0 & 0xff) << 32 | + (off_t)(cdbp->g4_addtl_cdb_data3 & 0xff) << 24 | + (off_t)(cdbp->g4_addtl_cdb_data2 & 0xff) << 16 | + (off_t)(cdbp->g4_addtl_cdb_data1 & 0xff) << 8 | + (off_t)(cdbp->g4_addtl_cdb_data0 & 0xff); + cnt = cdbp->g4_count3 << 24 | cdbp->g4_count2 << 16 | + cdbp->g4_count1 << 8 | cdbp->g4_count0; + break; + + default: + queue_str(mgmtq, Q_STE_ERRS, msg_log, + "Unprocessed WRITE type"); + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x24, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + + } + + if ((addr < 0) || ((addr + cnt) > r->r_size)) { + + /* + * request exceed the capacity of disk + * set error block number to capacity + 1 + */ + err_blkno = r->r_size + 1; + + /* + * XXX: What's SBC-2 say about ASC/ASCQ here. Solaris + * doesn't care about these values when key is set + * to KEY_ILLEGAL_REQUEST. + */ + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + addl_sense_len = INFORMATION_SENSE_DESCR; + else + addl_sense_len = 0; + + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len); + spc_sense_info(cmd, err_blkno); + spc_sense_ascq(cmd, 0x21, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + + (void) snprintf(debug, sizeof (debug), + "RAW%d WRITE Illegal sector (0x%llx + 0x%x) > 0x%llx", + cmd->c_lu->l_common->l_num, addr, cnt, r->r_size); + queue_str(mgmtq, Q_STE_ERRS, msg_log, debug); + return; + } + + if (cnt == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + io = (raw_io_t *)cmd->c_emul_id; + if (io == NULL) { + if ((io = (raw_io_t *)calloc(1, sizeof (*io))) == NULL) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + io->r_lba = addr; + io->r_lba_cnt = cnt; + io->r_cmd = cmd; + io->r_aio.a_aio_cmplt = raw_write_cmplt; + io->r_aio.a_id = io; + + /* + * Only update the statistics the first time through + * for this particular command. If the requested transfer + * is larger than the transport can handle this routine + * will be called many times. + */ + cmd->c_lu->l_cmds_write++; + cmd->c_lu->l_sects_write += cnt; + } + + /* + * If a transport sets the maximum output value to zero we'll + * just request the entire amount. Otherwise, transfer no more + * than the maximum output or the reminder, whichever is less. + */ + max_out = cmd->c_lu->l_targ->s_maxout; + io->r_data_len = max_out ? MIN(max_out, + (cnt * 512) - io->r_offset) : (cnt * 512); + +#ifdef FULL_DEBUG + (void) snprintf(debug, sizeof (debug), + "RAW%d blk 0x%llx, cnt %d, offset 0x%llx, size %d", + cmd->c_lu->l_common->l_num, addr, cnt, io->r_offset, + io->r_data_len); + queue_str(mgmtq, Q_STE_IO, msg_log, debug); +#endif + + if ((io->r_data = (char *)malloc(io->r_data_len)) == NULL) { + + /* + * NOTE: May need a different ASC code + */ + err_blkno = addr + ((io->r_offset + 511) / 512); + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + addl_sense_len = INFORMATION_SENSE_DESCR; + else + addl_sense_len = 0; + + spc_sense_create(cmd, KEY_HARDWARE_ERROR, addl_sense_len); + spc_sense_info(cmd, err_blkno); + trans_send_complete(cmd, STATUS_CHECK); + return; + + } + if (trans_rqst_dataout(cmd, io->r_data, io->r_data_len, io->r_offset, + io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +/* + * []---- + * | raw_write_data -- store a chunk of data from the transport + * []---- + */ +/*ARGSUSED*/ +void +raw_write_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + raw_io_t *io = (raw_io_t *)id; + raw_params_t *r = T10_PARAMS_AREA(cmd); + + if (r == NULL) + return; + + if (r->r_dtype == DTYPE_SEQUENTIAL) { + raw_write_tape_data(cmd, id, offset, data, data_len); + return; + } + + trans_aiowrite(cmd, data, data_len, (io->r_lba * 512) + + (off_t)io->r_offset, (aio_result_t *)io); +} + +/* + * []---- + * | raw_write_cmplt -- deal with end game of write + * | + * | See if all of the data for this write operation has been dealt + * | with. If so, send a final acknowledgement back to the transport. + * | If not, update the offset, calculate the next transfer size, and + * | start the process again. + * []--- + */ +static void +raw_write_cmplt(emul_handle_t e) +{ + raw_io_t *io = (raw_io_t *)e; + t10_cmd_t *cmd = io->r_cmd; + + if ((io->r_offset + io->r_data_len) < (io->r_lba_cnt * 512)) { + free(io->r_data); + + io->r_offset += io->r_data_len; + io->r_data_len = MIN(cmd->c_lu->l_targ->s_maxout, + (io->r_lba_cnt * 512) - io->r_offset); + raw_write(cmd, cmd->c_cdb, cmd->c_cdb_len); + return; + } + raw_free_io(io); + trans_send_complete(cmd, STATUS_GOOD); +} + +static void +raw_reserve(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) { + trans_send_complete(cmd, STATUS_CHECK); + } else { + trans_send_complete(cmd, io->r_status); + raw_free_io(io); + } +} + +static void +raw_release(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) { + trans_send_complete(cmd, STATUS_CHECK); + } else { + trans_send_complete(cmd, io->r_status); + raw_free_io(io); + } +} + +static void +raw_msense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + int len; + + switch (cdb[0]) { + case SCMD_MODE_SENSE: + len = cdb[4]; + break; + + case SCMD_MODE_SENSE_G1: + len = (cdb[7] << 8) | cdb[8]; + break; + } + + if (((io = do_datain(cmd, cdb, CDB_GROUP0, len)) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0, + raw_free_io, True, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +static void +raw_tur(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) { + trans_send_complete(cmd, STATUS_CHECK); + } else { + trans_send_complete(cmd, io->r_status); + raw_free_io(io); + } +} + +static void +raw_request_sense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if (((io = do_datain(cmd, cdb, CDB_GROUP0, cdb[4])) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + } else { + if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0, + raw_free_io, True, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } + } +} + +static void +raw_inquiry(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + uint32_t len; + struct scsi_inquiry inq; + raw_params_t *r; + + if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + len = (cdb[3] << 8) | cdb[4]; + if (((io = do_datain(cmd, cdb, CDB_GROUP0, len)) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((cdb[1] & 1) == 0) { + bcopy(io->r_data, &inq, sizeof (inq)); + r->r_dtype = inq.inq_dtype; + } + if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0, + raw_free_io, True, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +static void +raw_mselect(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + int len; + + switch (cdb[0]) { + case SCMD_MODE_SELECT: + len = cdb[4]; + cdb_len = CDB_GROUP0; + break; + + case SCMD_MODE_SELECT_G1: + len = (cdb[7] << 8) | cdb[8]; + cdb_len = CDB_GROUP1; + break; + } + do_dataout(cmd, cdb, cdb_len, len); +} + +/*ARGSUSED*/ +static void +raw_mselect_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + raw_io_t *io = (raw_io_t *)id; + trans_send_complete(cmd, do_uscsi(cmd, io, RawDataToDevice)); +} + +static void +raw_startstop(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) { + trans_send_complete(cmd, STATUS_CHECK); + } else { + trans_send_complete(cmd, io->r_status); + raw_free_io(io); + } +} + +static void +raw_rewind(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) { + trans_send_complete(cmd, STATUS_CHECK); + } else { + trans_send_complete(cmd, io->r_status); + raw_free_io(io); + } +} + +static void +raw_send_diag(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + int len; + + len = (cdb[3] << 8) | cdb[4]; + do_dataout(cmd, cdb, CDB_GROUP0, len); +} + +/*ARGSUSED*/ +static void +raw_send_diag_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, + char *data, size_t data_len) +{ + raw_io_t *io = (raw_io_t *)id; + trans_send_complete(cmd, do_uscsi(cmd, io, RawDataToDevice)); +} + +static void +raw_recap(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + struct scsi_capacity cap; + raw_io_t *io; + raw_params_t *r; + + if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + if (((io = do_datain(cmd, cdb, CDB_GROUP1, sizeof (cap))) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + bcopy(io->r_data, &cap, sizeof (cap)); + /* + * Currently there's a bug in ZFS which doesn't report a capacity + * for any of the volumes. This means that when using ZFS the + * administrator must supply the device size. + */ + if (cap.capacity != 0) + r->r_size = cap.capacity; + if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0, + raw_free_io, True, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +static void +raw_service_actiong4(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + uint32_t len; + struct scsi_capacity_16 cap16; + raw_params_t *r; + + if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + len = (cdb[10] << 24) | (cdb[11] << 16) | (cdb[12] << 8) | cdb[13]; + if (((io = do_datain(cmd, cdb, CDB_GROUP4, len)) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + bcopy(io->r_data, &cap16, sizeof (cap16)); + /* + * Currently there's a bug in ZFS which doesn't report a capacity + * for any of the volumes. This means that when using ZFS the + * administrator must supply the device size. + */ + if (cap16.sc_capacity != 0) + r->r_size = cap16.sc_capacity; + if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0, + raw_free_io, True, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +static void +raw_synccache(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if ((io = do_datain(cmd, cdb, CDB_GROUP1, 0)) == NULL) { + trans_send_complete(cmd, STATUS_CHECK); + } else { + trans_send_complete(cmd, io->r_status); + raw_free_io(io); + } +} + +static void +raw_write_fm(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) { + trans_send_complete(cmd, STATUS_CHECK); + } else { + trans_send_complete(cmd, io->r_status); + raw_free_io(io); + } +} + +static void +raw_report_tpgs(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + uint32_t len; + + len = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; + if (((io = do_datain(cmd, cdb, CDB_GROUP5, len)) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0, + raw_free_io, True, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +static void +raw_read_limits(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + raw_io_t *io; + + /* + * spec defines this command to return 6 bytes of data + */ + if (((io = do_datain(cmd, cdb, CDB_GROUP0, 6)) == NULL) || + (io->r_status != STATUS_GOOD)) { + if (io != NULL) + raw_free_io(io); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0, + raw_free_io, True, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +/* + * []------------------------------------------------------------------[] + * | Support related functions for raw devices | + * []------------------------------------------------------------------[] + */ + +static void +do_dataout(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len, size_t opt_data_len) +{ + char *opt_data = NULL; + raw_io_t *io; + + if ((io = calloc(1, sizeof (*io))) == NULL) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if ((opt_data_len != 0) && + ((opt_data = malloc(opt_data_len)) == NULL)) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + io->r_cdb = cdb; + io->r_cdb_len = cdb_len; + io->r_data = opt_data; + io->r_data_len = opt_data_len; + if (trans_rqst_dataout(cmd, opt_data, opt_data_len, 0, io) == False) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } +} + +static raw_io_t * +do_datain(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len, size_t data_len) +{ + raw_io_t *io; + + if ((io = calloc(1, sizeof (*io))) == NULL) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + return (NULL); + } + + io->r_cdb = cdb; + io->r_cdb_len = cdb_len; + io->r_data_len = data_len; + if ((data_len != 0) && ((io->r_data = malloc(data_len)) == NULL)) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + free(io); + return (NULL); + } + (void) do_uscsi(cmd, io, data_len == 0 ? NoData : RawDataFromDevice); + return (io); +} + +static int +do_uscsi(t10_cmd_t *cmd, raw_io_t *io, raw_direction_t dir) +{ + struct uscsi_cmd u; + uchar_t sense_buf[128]; + + bzero(&u, sizeof (u)); + u.uscsi_cdb = (caddr_t)io->r_cdb; + u.uscsi_cdblen = io->r_cdb_len; + u.uscsi_bufaddr = io->r_data; + u.uscsi_buflen = io->r_data_len; + u.uscsi_flags = ((dir == RawDataToDevice) ? USCSI_WRITE : + (dir == RawDataFromDevice) ? USCSI_READ : 0) | USCSI_RQENABLE; + u.uscsi_rqbuf = (char *)sense_buf; + u.uscsi_rqlen = sizeof (sense_buf); + + if ((ioctl(cmd->c_lu->l_common->l_fd, USCSICMD, &u) == 0) && + (u.uscsi_status == 0)) { + io->r_status = 0; + return (0); + } + queue_prt(mgmtq, Q_STE_ERRS, + "RAW%d LUN%d USCSICMD errno %d, cmd_status %d, rqstatus %d, " + "rqresid %d", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, errno, + u.uscsi_status, u.uscsi_rqstatus, u.uscsi_rqresid); + + if ((u.uscsi_rqlen - u.uscsi_rqresid) < + sizeof (struct scsi_extended_sense)) { + queue_prt(mgmtq, Q_STE_ERRS, + "RAW%x LUN%d -- No sense data, got=%d, needed=%d", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, + u.uscsi_rqlen - u.uscsi_rqresid, + sizeof (struct scsi_extended_sense)); + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + io->r_status = STATUS_CHECK; + return (STATUS_CHECK); + } else { + spc_sense_raw(cmd, sense_buf, u.uscsi_rqlen - u.uscsi_rqresid); + io->r_status = u.uscsi_status; + return (u.uscsi_status); + } +} + +static void +raw_free_io(emul_handle_t id) +{ + raw_io_t *io = (raw_io_t *)id; + + if (io->r_data_len) + free(io->r_data); + free(io); +} + +/* + * []---- + * | Command table for LBA emulation. This is at the end of the file because + * | it's big and ugly. ;-) To make for fast translation to the appropriate + * | emulation routine we just have a big command table with all 256 possible + * | entries. Most will report STATUS_CHECK, unsupport operation. By doing + * | this we can avoid error checking for command range. + * []---- + */ +static scsi_cmd_table_t raw_table[] = { + /* 0x00 -- 0x0f */ + { raw_tur, NULL, NULL, "TEST_UNIT_READY" }, + { raw_rewind, NULL, NULL, "REWIND" }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_request_sense, NULL, NULL, "REQUEST_SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_read_limits, NULL, NULL, "READ_LIMITS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_read, NULL, NULL, "READ" }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_write, raw_write_data, NULL, "WRITE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x10 -- 0x1f */ + { raw_write_fm, NULL, NULL, "WRITE_FILEMARKS" }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_inquiry, NULL, NULL, "INQUIRY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_mselect, raw_mselect_data, NULL, "MODE_SELECT" }, + { raw_reserve, NULL, NULL, "RESERVE" }, + { raw_release, NULL, NULL, "RELEASE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_msense, NULL, NULL, "MODE_SENSE" }, + { raw_startstop, NULL, NULL, "START_STOP" }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_send_diag, raw_send_diag_data, NULL, "SEND_DIAG" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x20 -- 0x2f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_recap, NULL, NULL, "READ_CAPACITY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_read, NULL, NULL, "READ_G1" }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_write, raw_write_data, NULL, "WRITE_G1" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x30 -- 0x3f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_synccache, NULL, NULL, "SYNC_CACHE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x40 -- 0x4f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x50 -- 0x5f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_mselect, raw_mselect_data, NULL, "MODE_SELECT" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_msense, NULL, NULL, "MODE_SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x60 -- 0x6f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x70 -- 0x7f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x80 -- 0x8f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_read, NULL, NULL, "READ_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_write, raw_write_data, NULL, "WRITE_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x90 -- 0x9f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_service_actiong4, NULL, NULL, "SVC_ACTION_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xa0 - 0xaf */ + { spc_report_luns, NULL, NULL, "REPORT_LUNS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { raw_report_tpgs, NULL, NULL, "REPORT_TPGS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xb0 -- 0xbf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xc0 -- 0xcf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xd0 -- 0xdf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xe0 -- 0xef */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xf0 -- 0xff */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, +}; diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_sam.c b/usr/src/cmd/iscsi/iscsitgtd/t10_sam.c new file mode 100644 index 0000000000..dae87c1f9e --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_sam.c @@ -0,0 +1,2191 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <aio.h> +#include <sys/aio.h> +#include <sys/asynch.h> +#include <stddef.h> +#include <strings.h> +#include <pthread.h> +#include <sys/types.h> +#include <sys/statvfs.h> +#include <sys/avl.h> +#include <sys/param.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <assert.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <sys/ucontext.h> + +#include <sys/scsi/generic/sense.h> +#include <sys/scsi/generic/status.h> +#include <sys/scsi/generic/inquiry.h> + +#include "target.h" +#include "queue.h" +#include "t10.h" +#include "t10_spc.h" +#include "utility.h" + +/* + * []------------------------------------------------------------------[] + * | This file contains methods which isolate a transport from device | + * | emulation. The first part of the file contains method which are | + * | called by the transport to start commands or deliver data. The | + * | transport does not know anything about what emulation is being | + * | done. The emulation layer receieves cdb's and nows nothing about | + * | the transport. This is how it should be. There are a few special | + * | cases to deal with transports which have a notion of immediate | + * | data, but we're isolating that from the emulation layer. | + * []------------------------------------------------------------------[] + */ + +/* + * Forward declarations + */ +static t10_lu_impl_t *t10_find_lun(t10_targ_impl_t *t, int lun); +static void *lu_runner(void *v); +static Boolean_t t10_lu_initialize(t10_lu_common_t *lu, char *basedir); +static void *t10_aio_done(void *v); +static Boolean_t lu_remove_cmds(msg_t *m, void *v); +static void cmd_common_free(t10_lu_impl_t *lu, t10_cmd_t *cmd); +static Boolean_t load_params(t10_lu_common_t *lu, char *basedir); +static Boolean_t fallocate(int fd, off64_t len); +/* ---- These are AVL comparison routines ---- */ +static int find_lu_by_num(const void *v1, const void *v2); +static int find_lu_by_guid(const void *v1, const void *v2); +static int find_lu_by_targ(const void *v1, const void *v2); +static int find_cmd_by_addr(const void *v1, const void *v2); + +/* + * Global variables + */ +static avl_tree_t lu_list; +static pthread_mutex_t lu_list_mutex; +static int lu_id; +target_queue_t *mgmtq; +static pthread_mutex_t t10_mutex; +static int t10_num; +static sema_t t10_sema; + +/* + * []---- + * | t10_init -- called once at the beginning of time to initialize globals + * []---- + */ +void +t10_init(target_queue_t *q) +{ + pthread_t junk; + + mgmtq = q; + (void) pthread_mutex_init(&lu_list_mutex, NULL); + (void) pthread_mutex_init(&t10_mutex, NULL); + (void) sema_init(&t10_sema, 0, USYNC_THREAD, NULL); + avl_create(&lu_list, find_lu_by_guid, sizeof (t10_lu_common_t), + offsetof(t10_lu_common_t, l_all_luns)); + (void) pthread_create(&junk, NULL, t10_aio_done, NULL); +} + +/*ARGSUSED*/ +static void * +t10_aio_done(void *v) +{ + aio_result_t *result; + t10_aio_t *a; + + do { + if (sema_wait(&t10_sema) != 0) { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM- same_wait returned error"); + continue; + } + + if ((result = aiowait(NULL)) == (aio_result_t *)-1) { + if (errno == EINVAL) { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM- aiowait returned EINVAL"); + /* + * It's possible for aiowait to return + * prematurely. So, post another semaphore, + * sleep for a second, and try the wait + * again. + */ + (void) sema_post(&t10_sema); + (void) sleep(1); + continue; + } else + break; + } else { + a = (t10_aio_t *)result; + } + if ((a != NULL) && (a->a_aio_cmplt != NULL)) { + (*a->a_aio_cmplt)(a->a_id); + } else { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM aiowait returned results, but is NULL"); + } + /*CONSTANTCONDITION*/ + } while (1); + + return (NULL); +} + +/* + * []------------------------------------------------------------------[] + * | Methods called by transports to interface with SAM-3 | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | t10_handle_create -- Create the I_T nexus + * | + * | NOTES: + * | max_out can be set to 0 if the transport wishes to wait for all of + * | the data before receiving a DATAOUT message. Fibre Channel will most + * | likely set this to 0, whereas iSCSI will set max_out to the value + * | of MaxRecvDataSegment. + * | (*datain_cb)() is called, on the LU thread, when the emulation + * | module needs data *and* t10_send_cmd was called with opt_data_len, but + * | no opt_data. + * []---- + */ +t10_targ_handle_t +t10_handle_create(char *targ, int trans_version, int tpg, int max_out, + target_queue_t *tq, void (*datain_cb)(t10_cmd_t *, char *, size_t *)) +{ + t10_targ_impl_t *t = calloc(1, sizeof (t10_targ_impl_t)); + + if (t == NULL) + return (NULL); + + (void) pthread_mutex_lock(&t10_mutex); + t->s_targ_num = t10_num++; + (void) pthread_mutex_unlock(&t10_mutex); + t->s_targ_base = strdup(targ); + t->s_trans_vers = trans_version; + t->s_maxout = max_out; + t->s_to_transport = tq; + t->s_dataout_cb = datain_cb; + + /* + * Once we actually support two or more transports it would be + * possible for a collision between the underlying transports + * target port group values since one wouldn't necessarily know + * anything about the other. We'll use the upper bits of the + * target port group value to separate them. + * If we were to support many transports and with one then running + * out of bit space we'd need to change the allocation method. Since + * these values aren't stored anywhere and just used by initiators + * to determine relative path numbering there's no issue with changing + * this later if need be. + */ + switch (trans_version) { + case T10_TRANS_ISCSI: + t->s_tp_grp = 0x0000 | tpg; + break; + + case T10_TRANS_FC: + t->s_tp_grp = 0x8000 | tpg; + break; + } + + avl_create(&t->s_open_lu, find_lu_by_num, sizeof (t10_lu_impl_t), + offsetof(t10_lu_impl_t, l_open_targ_node)); + + (void) pthread_mutex_init(&t->s_mutex, NULL); + return ((t10_targ_handle_t)t); +} + +void +t10_handle_disable(t10_targ_handle_t tp) +{ + t10_targ_impl_t *t = (t10_targ_impl_t *)tp; + t10_lu_impl_t *l; + t10_shutdown_t s; + int lu_per_targ = 0; + + (void) pthread_mutex_lock(&t->s_mutex); + if (avl_numnodes(&t->s_open_lu) != 0) { + + s.t_q = queue_alloc(); + l = avl_first(&t->s_open_lu); + while (l != NULL) { + + s.t_lu = l; + queue_message_set(l->l_common->l_from_transports, 0, + msg_shutdown, (void *)&s); + queue_message_free(queue_message_get(s.t_q)); + lu_per_targ++; + l = AVL_NEXT(&t->s_open_lu, l); + } + queue_prt(mgmtq, Q_STE_NONIO, + "SAM%x Sent %d shutdown requests for %s", + t->s_targ_num, lu_per_targ, t->s_targ_base); + queue_free(s.t_q, NULL); + } + (void) pthread_mutex_unlock(&t->s_mutex); +} + +void +t10_handle_destroy(t10_targ_handle_t tp) +{ + t10_targ_impl_t *t = (t10_targ_impl_t *)tp; + t10_lu_impl_t *l; + t10_cmd_t *c, + *c2free; + int fast_free = 0; + + (void) pthread_mutex_lock(&t->s_mutex); + if (avl_numnodes(&t->s_open_lu) != 0) { + while ((l = avl_first(&t->s_open_lu)) != NULL) { + avl_remove(&t->s_open_lu, l); + + (void) pthread_mutex_lock(&l->l_cmd_mutex); + if (avl_numnodes(&l->l_cmds) != 0) { + c = avl_first(&l->l_cmds); + while (c != NULL) { + if (c->c_state == + T10_Cmd_DataOut_Sent) { + c2free = c; + c = AVL_NEXT(&l->l_cmds, c); + fast_free++; + cmd_common_free(l, c2free); + free(c2free); + } else if (c->c_state == + T10_Cmd_Canceled) { + c2free = c; + c = AVL_NEXT(&l->l_cmds, c); + cmd_common_free(l, c2free); + fast_free++; + } else { + queue_prt(mgmtq, Q_STE_NONIO, + "SAM%x cmd->c_state %d, " + "trans_id 0x%x", + t->s_targ_num, + c->c_state, + c->c_trans_id); + c = AVL_NEXT(&l->l_cmds, c); + } + } + queue_prt(mgmtq, Q_STE_NONIO, + "SAM%x FastFree %d ... " + "Waiting for %d cmds to drain", + t->s_targ_num, fast_free, + avl_numnodes(&l->l_cmds)); + if (avl_numnodes(&l->l_cmds) != 0) { + l->l_wait_for_drain = True; + while (l->l_wait_for_drain == True) { + (void) pthread_cond_wait( + &l->l_cmd_cond, + &l->l_cmd_mutex); + } + } + } + (void) pthread_mutex_unlock(&l->l_cmd_mutex); + avl_destroy(&l->l_cmds); + free(l); + } + } + avl_destroy(&t->s_open_lu); + (void) pthread_mutex_unlock(&t->s_mutex); + + free(t->s_targ_base); + free(t); +} + +/* + * []---- + * | t10_cmd_create -- creates a command pointer + * | + * | If an error occurs, a sense condition buffer will be created that can + * | be sent back to the initiator. The only time this should occur is during + * | LU setup and we've run out of resources like not having enough file + * | descriptors to open the backing store. If the cmdp is NULL, then there's + * | not even enough memory to create a command buffer and the transport + * | should shutdown it's connection a cleanly as possible. + * []---- + */ +Boolean_t +t10_cmd_create(t10_targ_handle_t t, int lun_number, uint8_t *cdb, + size_t cdb_len, transport_t trans_id, t10_cmd_t **cmdp) +{ + t10_cmd_t *cmd = NULL; + + *cmdp = NULL; + if (t == NULL) + goto error; + + if ((cmd = (t10_cmd_t *)calloc(1, sizeof (t10_cmd_t))) == NULL) + goto error; + + if ((cmd->c_cdb = (uint8_t *)malloc(cdb_len)) == NULL) + goto error; + + cmd->c_trans_id = trans_id; + *cmdp = cmd; + if ((cmd->c_lu = t10_find_lun((t10_targ_impl_t *)t, lun_number)) == + NULL) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + goto error; + } + + (void) pthread_mutex_lock(&cmd->c_lu->l_cmd_mutex); + avl_add(&cmd->c_lu->l_cmds, (void *)cmd); + cmd->c_state = T10_Cmd_Alloc; + (void) pthread_mutex_unlock(&cmd->c_lu->l_cmd_mutex); + bcopy(cdb, cmd->c_cdb, cdb_len); + cmd->c_cdb_len = cdb_len; + + return (True); + +error: + cmd->c_state = T10_Cmd_Errored; + if (cmd->c_cdb) { + free(cmd->c_cdb); + cmd->c_cdb = NULL; + } + + /* + * If we haven't set up the argument pointer, then free the memory + * that had been allocated to the command. + */ + if (*cmdp == NULL) + free(cmd); + return (False); +} + +/* + * []---- + * | t10_send_cmd -- send the give command to appropriate LUN emulation + * | + * | NOTE: emul_id is only provided for DATA_OUT commands (write ops) + * | which have multiple phases to complete the request. The emulation + * | module will provide this value when it requests more data to be + * | sent. + * []---- + */ +/*ARGSUSED*/ +Boolean_t +t10_cmd_send(t10_targ_handle_t t, t10_cmd_t *cmd, char *opt_data, + size_t opt_data_len) +{ + if (cmd == NULL) + return (False); + + cmd->c_data = opt_data; + cmd->c_data_len = opt_data_len; + + queue_message_set(cmd->c_lu->l_common->l_from_transports, 0, + msg_cmd_send, (void *)cmd); + + return (True); +} + +/*ARGSUSED*/ +Boolean_t +t10_cmd_data(t10_targ_handle_t t, t10_cmd_t *cmd, size_t offset, char *data, + size_t data_len) +{ + cmd->c_data = data; + cmd->c_data_len = data_len; + cmd->c_offset = offset; + + queue_message_set(cmd->c_lu->l_common->l_from_transports, 0, + msg_cmd_data_out, (void *)cmd); + + return (True); +} + +void +t10_cmd_state(t10_cmd_t *c, t10_cmd_event_t e) +{ + t10_lu_impl_t *lu = c->c_lu; + + if (c->c_state == T10_Cmd_Errored) { + free(c); + return; + } + (void) pthread_mutex_lock(&lu->l_cmd_mutex); + switch (e) { + case T10_Cmd_Event_DataOut_Sent: + switch (c->c_state) { + case T10_Cmd_DataOut: + c->c_state = T10_Cmd_DataOut_Sent; + break; + + default: + assert(0); + } + break; + + case T10_Cmd_Event_DataIn_Recv: + switch (c->c_state) { + case T10_Cmd_DataOut_Sent: + c->c_state = T10_Cmd_DataOut; + break; + + default: + assert(0); + } + break; + + case T10_Cmd_Event_Canceled: + switch (c->c_state) { + case T10_Cmd_Free: + assert(0); + + default: + c->c_state = T10_Cmd_Canceled; + break; + } + break; + + case T10_Cmd_Event_Release: + cmd_common_free(lu, c); + free(c); + if ((lu->l_wait_for_drain == True) && + (avl_numnodes(&lu->l_cmds) == 0)) { + lu->l_wait_for_drain = False; + (void) pthread_cond_signal(&lu->l_cmd_cond); + } + break; + + default: + assert(0); + } + (void) pthread_mutex_unlock(&lu->l_cmd_mutex); +} + +/* + * []---- + * | t10_task_mgmt -- handle SAM-3 task management needs + * []---- + */ +/*ARGSUSED*/ +Boolean_t +t10_task_mgmt(t10_targ_handle_t t1, TaskOp_t op, int opt_lun, void *tag) +{ + t10_targ_impl_t *t = (t10_targ_impl_t *)t1; + t10_lu_impl_t search, + *lu; + + switch (op) { + case InventoryChange: + if ((lu = avl_first(&t->s_open_lu)) != NULL) { + do { + /*CSTYLED*/ + queue_message_set(lu->l_common->l_from_transports, + 0, msg_targ_inventory_change, (void *)lu); + } while ((lu = AVL_NEXT(&t->s_open_lu, lu)) != NULL); + } + return (True); + + case ResetTarget: + if ((lu = avl_first(&t->s_open_lu)) != NULL) { + do { + /*CSTYLED*/ + queue_message_set(lu->l_common->l_from_transports, + Q_HIGH, msg_reset_lu, (void *)lu); + } while ((lu = AVL_NEXT(&t->s_open_lu, lu)) != NULL); + return (True); + } else + return (False); + + case ResetLun: + search.l_targ_lun = opt_lun; + if ((lu = avl_find(&t->s_open_lu, (void *)&search, NULL)) != + NULL) { + queue_message_set(lu->l_common->l_from_transports, + Q_HIGH, msg_reset_lu, (void *)lu); + return (True); + } else + return (False); + break; + + case CapacityChange: + search.l_targ_lun = opt_lun; + if ((lu = avl_find(&t->s_open_lu, (void *)&search, NULL)) != + NULL) { + queue_message_set(lu->l_common->l_from_transports, + Q_HIGH, msg_lu_capacity_change, + (void *)(uintptr_t)opt_lun); + return (True); + } else + return (False); + break; + + default: + return (False); + } +} + + +/* + * []---- + * | t10_targ_stat -- Return stats on each LU associated with target. + * []---- + */ +void +t10_targ_stat(t10_targ_handle_t t1, char **buf) +{ + t10_targ_impl_t *t = (t10_targ_impl_t *)t1; + t10_lu_impl_t *itl; + char lb[32], + *p; + + /* + * It's possible for the management interfaces to request stats + * even though a connection is not up and running. + */ + if (t == NULL) + return; + + itl = avl_first(&t->s_open_lu); + while (itl) { + buf_add_tag(buf, XML_ELEMENT_LUN, Tag_Start); + (void) snprintf(lb, sizeof (lb), "%d", itl->l_common->l_num); + buf_add_tag(buf, lb, Tag_String); + + (void) snprintf(lb, sizeof (lb), "%lld", itl->l_cmds_read); + xml_add_tag(buf, XML_ELEMENT_READCMDS, lb); + (void) snprintf(lb, sizeof (lb), "%lld", itl->l_cmds_write); + xml_add_tag(buf, XML_ELEMENT_WRITECMDS, lb); + (void) snprintf(lb, sizeof (lb), "%lld", itl->l_sects_read); + xml_add_tag(buf, XML_ELEMENT_READBLKS, lb); + (void) snprintf(lb, sizeof (lb), "%lld", itl->l_sects_write); + xml_add_tag(buf, XML_ELEMENT_WRITEBLKS, lb); + + switch (itl->l_common->l_state) { + case lu_online: + p = TGT_STATUS_ONLINE; + break; + case lu_offline: + p = TGT_STATUS_OFFLINE; + break; + case lu_errored: + p = TGT_STATUS_ERRORED; + break; + } + xml_add_tag(buf, XML_ELEMENT_STATUS, p); + + buf_add_tag(buf, XML_ELEMENT_LUN, Tag_End); + itl = AVL_NEXT(&t->s_open_lu, itl); + } +} + +/* + * []---- + * | t10_thick_provision -- fill the backing store with real blocks + * | + * | The backing store is initially created as a hole-y file. The only + * | thing wrong with leaving the files hole-y is that if a system + * | administrator over provisions the storage at some point a client + * | will attempt to write to a block and receive an error unless the + * | administrator adds more backing store before that event. Now, depending + * | on the client a write error isn't fatal. However, for file systems + * | like UFS and ZFS, they can not currently deal with getting a write + * | error when it's their metadata and panic. That's not good. The concept + * | of "Thin Provisioning" is relatively new so we'll normally preallocate + * | the space, but have the option of doing the "Thin Provisioning". + * []---- + */ +Boolean_t +t10_thick_provision(char *target, int lun, target_queue_t *q) +{ + t10_targ_handle_t t; + t10_cmd_t *cmd = NULL; + uint8_t cdb[16]; /* ---- fake buffer ---- */ + diskaddr_t offset = 0; + size_t size, + sync_size; + msg_t *m = NULL; + target_queue_t *rq = NULL; + char path[MAXPATHLEN]; + xml_node_t *n1; + Boolean_t rval = False; + struct statvfs fs; + + /* + * To guarantee that everything has been setup correctly + * we'll just use the standard interfaces. Otherwise we'd need + * to duplicate the code and therefore offer the chance of + * having something fixed/change in one location that isn't + * in another. Obvious right? + */ + if ((t = t10_handle_create(target, 0, 0, 0, q, NULL)) == NULL) { + queue_prt(mgmtq, Q_STE_ERRS, "STE%x Failed to create handle", + lun); + return (False); + } + if (t10_cmd_create(t, lun, cdb, sizeof (cdb), 0, &cmd) == False) { + queue_prt(mgmtq, Q_STE_ERRS, "STE%x Failed to create cmd", + lun); + goto error; + } + + /* + * Attempt to see if there is enough space currently for the LU. + * The initialization might still fail with out of space because someone + * else is consuming space while the initialization is occuring. + * Nothing we can do about that. + */ + if (fstatvfs(cmd->c_lu->l_common->l_fd, &fs) != 0) { + queue_prt(mgmtq, Q_STE_ERRS, "STE%x statvfs failed for LU", + lun); + goto error; + } else if ((fs.f_frsize * fs.f_bfree) < cmd->c_lu->l_common->l_size) { + queue_prt(mgmtq, Q_STE_ERRS, "STE%x Not enough space for LU", + lun); + goto error; + } + + if (fallocate(cmd->c_lu->l_common->l_fd, cmd->c_lu->l_common->l_size) == + False) { + /* + * The lu_runner will use this buffer to copy data. + */ + sync_size = 1024 * 1024; + if ((cmd->c_data = malloc(sync_size)) == NULL) + goto error; + + while ((offset < cmd->c_lu->l_common->l_size) && (rq == NULL)) { + size = min(cmd->c_lu->l_common->l_size - offset, + sync_size); + cmd->c_offset = offset; + cmd->c_data_len = size; + /*CSTYLED*/ + queue_message_set(cmd->c_lu->l_common->l_from_transports, 0, + msg_thick_provo, (void *)cmd); + while ((m = queue_message_get(q)) != NULL) { + switch (m->msg_type) { + case msg_thick_provo: + if ((int)(intptr_t)m->msg_data != 0) { + + /* + * An error occurred during + * initialization which mean we + * need to remove this target. + */ + queue_prt(mgmtq, Q_STE_ERRS, + "STE%x received data " + "error at 0x%llx", lun, + offset); + goto error; + } + break; + + case msg_shutdown: + queue_prt(mgmtq, Q_STE_NONIO, + "---- Thick provo got shutdown"); + rq = (target_queue_t *)m->msg_data; + queue_message_free(m); + continue; /* don't use break */ + + default: + assert(0); + } + break; + } + queue_message_free(m); + offset += size; + } + } else { + queue_prt(mgmtq, Q_STE_NONIO, "STE%x fallocate worked", lun); + } + + /* + * A forced shutdown is still considered a successful completion. + * Write errors and malloc failures constitute a failure. + */ + rval = True; + + /* ---- Completed successfully ---- */ + if (rq == NULL) { + + /* + * Now that the initialization is complete, update the params + * file to indicate the status is online. Once done, send a + * message to the LU thread indicating same. + */ + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", + target_basedir, cmd->c_lu->l_targ->s_targ_base, PARAMBASE, + lun); + + if ((n1 = xml_find_child(cmd->c_lu->l_common->l_root, + XML_ELEMENT_STATUS)) == NULL) { + queue_prt(mgmtq, Q_STE_ERRS, + "STE%x couldn't find <status>", lun); + goto error; + } + + if (xml_update_value_str(n1, XML_ELEMENT_STATUS, + TGT_STATUS_ONLINE) == False) { + queue_prt(mgmtq, Q_STE_ERRS, + "STE%x Could update <status> to online", lun); + goto error; + } + + if (xml_dump2file(cmd->c_lu->l_common->l_root, path) == + False) { + queue_prt(mgmtq, Q_STE_ERRS, + "STE%x failed to dump out params", lun); + goto error; + } + + queue_message_set(cmd->c_lu->l_common->l_from_transports, 0, + msg_lu_online, 0); + } + +error: + if (cmd != NULL) { + if (cmd->c_data != NULL) + free(cmd->c_data); + t10_cmd_state(cmd, T10_Cmd_Event_Release); + } + if (t != NULL) { + t10_handle_disable(t); + t10_handle_destroy(t); + } + if (rq != NULL) { + queue_message_set(rq, 0, msg_shutdown_rsp, 0); + } + + return (rval); +} + +/* + * []------------------------------------------------------------------[] + * | Methods called by emulation modules to interface with SAM-3 | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | trans_send_datain -- send data to transport + * | + * | NOTES: + * | (1) offset is only valid when a transport has set max_out to a non-zero + * | value. + * | (2) The emulation code must free the memory, if it was allocated, when + * | the transport is finished with it. The callback routine is used + * | to provide the emulation code the notification. The callback will + * | not be run on the same thread as the emulation code so appropriate + * | locking may be required by the emulation code. + * | (3) If the boolean 'last' is True it means that the transport can + * | assume the data out is finished with a CMD_SUCCESS and no futher + * | communication from the emulation layer will occur. + * []---- + */ +Boolean_t +trans_send_datain(t10_cmd_t *cmd, char *data, size_t data_len, size_t offset, + void (*callback)(emul_handle_t e), Boolean_t last, emul_handle_t id) +{ + t10_cmd_t *c; + + if (last == False) { + + if ((c = calloc(1, sizeof (*c))) == NULL) + return (False); + bcopy(cmd, c, sizeof (*c)); + if ((c->c_cdb = (uint8_t *)malloc(c->c_cdb_len)) == NULL) { + free(c); + return (False); + } + bcopy(cmd->c_cdb, c->c_cdb, c->c_cdb_len); + + (void) pthread_mutex_lock(&cmd->c_lu->l_cmd_mutex); + c->c_state = T10_Cmd_Alloc; + avl_add(&c->c_lu->l_cmds, (void *)c); + (void) pthread_mutex_unlock(&cmd->c_lu->l_cmd_mutex); + + } else + c = cmd; + +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, + "SAM%x LUN%d DataIn %d, offset %d, Last %s", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + data_len, offset, last == True ? "true" : "false"); +#endif + + (void) pthread_mutex_lock(&cmd->c_lu->l_cmd_mutex); + c->c_state = T10_Cmd_DataIn; + (void) pthread_mutex_unlock(&cmd->c_lu->l_cmd_mutex); + c->c_emul_complete = callback; + c->c_emul_id = id; + c->c_data = data; + c->c_data_len = data_len; + c->c_offset = offset; + c->c_last = last; + + queue_message_set(c->c_lu->l_to_transport, 0, msg_cmd_data_in, + (void *)c); + return (True); +} + +/* + * []---- + * | trans_rqst_dataout -- Request data from transport for command + * | + * | If the transport has indicated that data is immediately available, + * | which is common for iSCSI, then we'll copy that data into the buffer + * | and call the emulation modules datain function directly. + * []---- + */ +Boolean_t +trans_rqst_dataout(t10_cmd_t *cmd, char *data, size_t data_len, size_t offset, + emul_cmd_t emul_id) +{ + size_t max_xfer; + + cmd->c_emul_id = emul_id; + /* + * Transport supports immediate data on writes. Currently + * on the iSCSI protocol has this feature. + * XXX Should all of this be done in the transport? + */ + if (cmd->c_data_len) { +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, + "SAM%x LUN%d DataOut rqst w/ immed, data_len %d", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, data_len); +#endif + if (cmd->c_data == NULL) { + + /* + * When there's data available, but no buffer it + * means the transport has decided to leave the + * data on the socket and will read it in + * when called. + */ + max_xfer = data_len; + assert(cmd->c_lu->l_targ->s_dataout_cb != NULL); + (*cmd->c_lu->l_targ->s_dataout_cb)(cmd, data, + &max_xfer); + + } else { + + /* + * The data is already in the command buffer so + * we need to copy it out. + */ + max_xfer = MIN(cmd->c_data_len - cmd->c_resid, + data_len); + bcopy(cmd->c_data + cmd->c_resid, data, max_xfer); + cmd->c_resid = cmd->c_data_len - max_xfer; + + /* + * It's expected since the transport allocated + * the space, this routine will free the memory + * instead. + */ + (*cmd->c_lu->l_targ->s_dataout_cb)(cmd, data, + &max_xfer); + cmd->c_data = NULL; + + } + cmd->c_data_len = 0; + (void) pthread_mutex_lock(&cmd->c_lu->l_cmd_mutex); + cmd->c_state = T10_Cmd_DataOut; + (void) pthread_mutex_unlock(&cmd->c_lu->l_cmd_mutex); + (*cmd->c_lu->l_data)(cmd, emul_id, offset, data, max_xfer); + return (True); + } + +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, + "SAM%x LUN%d DataOut Rqst data_len %d", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, data_len); +#endif + + assert(cmd->c_data == NULL); + + (void) pthread_mutex_lock(&cmd->c_lu->l_cmd_mutex); + cmd->c_state = T10_Cmd_DataOut; + (void) pthread_mutex_unlock(&cmd->c_lu->l_cmd_mutex); + cmd->c_data = data; + cmd->c_data_len = data_len; + cmd->c_offset = offset; + cmd->c_resid = 0; + + /* + * Short cut. There's no reason to call the transport if the + * emulation code hasn't requested any data. If that's the + * case just call the emulation codes data function. + */ + if (data_len == 0) + (*cmd->c_lu->l_data)(cmd, emul_id, offset, data, max_xfer); + else + queue_message_set(cmd->c_lu->l_to_transport, 0, + msg_cmd_data_rqst, (void *)cmd); + return (True); +} + +/* + * []---- + * | trans_send_complete -- notify transport command has finished. + * | + * | This routine is called either for when the emulation has completed + * | a command which doesn't have a data in phase so we can't use the 'last' + * | flag or there's been an error. + * | The sense data is expected to be created by calling spc_create_sense(), + * | the memory for that sense data will be freed when the transport calls + * | t10_destroy_cmd(). + * | + * | NOTE [1]: If the t10_status equals STATUS_BUSY the command queue for this + * | ITL will be examined. If there are commands in progress the status will + * | be changed to STATUS_QFULL + * | + * | NOTE [2]: Do not access 'cmd' after calling this function. The transport + * | may receive the command, act on it, and then call + * | t10_cmd_state(cmd, T10_Cmd_Event_Release) before this function returns + * | thereby allowing 'cmd' to be freed and the space reallocated. + * []---- + */ +void +trans_send_complete(t10_cmd_t *cmd, int t10_status) +{ +#ifdef FULL_DEBUG + struct scsi_extended_sense e; +#endif + + (void) pthread_mutex_lock(&cmd->c_lu->l_cmd_mutex); + cmd->c_state = T10_Cmd_Complete; + + /* + * XXX Get the exact chapter and verse from the T10 documents. + * translate a STATUS_BUSY to STATUS_QFULL if there are outstanding + * commands in the queue. + */ + if ((t10_status == STATUS_BUSY) && + (avl_numnodes(&cmd->c_lu->l_cmds) != 0)) { + t10_status = STATUS_QFULL; + } + + (void) pthread_mutex_unlock(&cmd->c_lu->l_cmd_mutex); + + cmd->c_cmd_status = t10_status; + cmd->c_last = True; + cmd->c_data_len = 0; + cmd->c_data = 0; + +#ifdef FULL_DEBUG + if (t10_status != STATUS_GOOD) { + if (cmd->c_cmd_sense != NULL) { + bcopy(&cmd->c_cmd_sense[2], &e, sizeof (e)); + queue_prt(mgmtq, Q_STE_ERRS, + "SAM%x LUN%d key_sense=0x%x, " + "ASC=0x%x, ASCQ=0x%x", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, + e.es_key, e.es_add_code, e.es_qual_code); + } else { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM%x LUN%d key_sense=0x%x", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, t10_status); + } + } +#endif + + queue_message_set(cmd->c_lu->l_to_transport, 0, msg_cmd_cmplt, + (void *)cmd); +} + +void +trans_aiowrite(t10_cmd_t *cmd, char *data, size_t data_len, off_t offset, + aio_result_t *aiop) +{ + t10_aio_t *taio; + + if (aiowrite(cmd->c_lu->l_common->l_fd, data, data_len, offset, 0, + aiop) == -1) { + taio = (t10_aio_t *)aiop; + taio->a_aio.aio_return = -1; + (*taio->a_aio_cmplt)(taio->a_id); + } else + (void) sema_post(&t10_sema); +} + +void +trans_aioread(t10_cmd_t *cmd, char *data, size_t data_len, off_t offset, + aio_result_t *aiop) +{ + t10_aio_t *taio; + + if (aioread(cmd->c_lu->l_common->l_fd, data, data_len, offset, 0, + aiop) == -1) { + taio = (t10_aio_t *)aiop; + taio->a_aio.aio_return = -1; + (*taio->a_aio_cmplt)(taio->a_id); + } else + (void) sema_post(&t10_sema); +} + +/* + * []---- + * | trans_params_area -- return dtype params using a command pointer + * | + * | Lock down the ITL structure from change so that we can cleanly access + * | the params area. This is needed to deal with the transport closing + * | a connection while commands are in flight. When those commands finish + * | cleanup work needs to be done. Yet, the logical unit common area + * | can already be released since it doesn't know there's something to wait + * | for. + * []---- + */ +void * +trans_params_area(t10_cmd_t *cmd) +{ + void *p = NULL; + + (void) pthread_mutex_lock(&cmd->c_lu->l_mutex); + if (cmd->c_lu->l_common != NULL) + p = cmd->c_lu->l_common->l_dtype_params; + (void) pthread_mutex_unlock(&cmd->c_lu->l_mutex); + return (p); +} + +/* + * []------------------------------------------------------------------[] + * | Support routines for Routing and Task Management | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | t10_find_lun -- Locate a per target LUN structure + * | + * | Finds per I_T_L structure. If this is the first time that this structure + * | has been accessed we allocate the structure and add it to the global + * | LUN structure. If that structure has never been accessed before it is + * | created along with a thread to handle the queue. + * []---- + */ +/*ARGSUSED*/ +static t10_lu_impl_t * +t10_find_lun(t10_targ_impl_t *t, int lun) +{ + t10_lu_impl_t *l = NULL, + search; + avl_index_t wc = 0, /* where common */ + wt = 0; /* where target */ + char *guid = NULL; + t10_lu_common_t lc, + *common = NULL; + xml_node_t *n = NULL, + *n1; + xmlTextReaderPtr r = NULL; + char path[MAXPATHLEN]; + int xml_fd = -1; + + bzero(&lc, sizeof (lc)); + + /* + * Only l_num is used by the AVL search routines so that's + * the only thing we'll set. + */ + search.l_targ_lun = lun; + + if ((l = avl_find(&t->s_open_lu, (void *)&search, &wt)) != NULL) { + + /* + * This should be the normal fast path. At some point it + * might be good to look at optimizing this even more. + * If we know for example that the LUN numbers are sequential + * and there's fewer than 64 an array of pointers would be + * even faster than an AVL tree and not take up to much space. + */ + return (l); + } + + /* + * First access for this I_T_L so we need to allocate space for it. + */ + if ((l = calloc(1, sizeof (*l))) == NULL) + return (NULL); + + /* + * Initialize the various local fields. Certain fields will not be + * initialized until we've got the common LUN pointer. + */ + (void) pthread_mutex_init(&l->l_cmd_mutex, NULL); + (void) pthread_mutex_init(&l->l_mutex, NULL); + (void) pthread_cond_init(&l->l_cmd_cond, NULL); + avl_create(&l->l_cmds, find_cmd_by_addr, sizeof (t10_cmd_t), + offsetof(t10_cmd_t, c_cmd_avl)); + + l->l_wait_for_drain = False; + l->l_to_transport = t->s_to_transport; + l->l_targ = t; + l->l_targ_lun = lun; + + /* + * To locate the common LUN structure we need to find the GUID + * for this LUN. That's the only parsing this section of code will + * do to the params file. + */ + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", + target_basedir, t->s_targ_base, PARAMBASE, lun); + + (void) pthread_mutex_lock(&lu_list_mutex); + if ((xml_fd = open(path, O_RDONLY)) < 0) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + if ((r = (xmlTextReaderPtr)xmlReaderForFd(xml_fd, NULL, NULL, + 0)) != NULL) { + while (xmlTextReaderRead(r) == 1) + if (xml_process_node(r, &n) == False) + break; + } else { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + + (void) close(xml_fd); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + r = NULL; + + if (xml_find_value_str(n, XML_ELEMENT_GUID, &guid) == False) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + + if (strcmp(guid, "0") == 0) { + free(guid); + if (util_create_guid(&guid) == False) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + if ((n1 = xml_find_child(n, XML_ELEMENT_GUID)) == NULL) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + if (xml_update_value_str(n1, XML_ELEMENT_GUID, guid) == False) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + if (xml_dump2file(n, path) == False) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + } + + if (xml_decode_bytes(guid, &lc.l_guid, &lc.l_guid_len) == False) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + + /* + * See if the common LUN for this GUID already exists. + */ + wc = 0; + if ((common = avl_find(&lu_list, (void *)&lc, &wc)) == NULL) { + + /* + * The GUID wasn't found, so create a new LUN structure + * and thread. + */ + if ((common = calloc(1, sizeof (*common))) == NULL) { + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + + common->l_from_transports = queue_alloc(); + common->l_num = lun; + common->l_internal_num = lu_id++; + common->l_guid = lc.l_guid; + common->l_guid_len = lc.l_guid_len; + common->l_fd = -1; /* not open yet */ + common->l_mmap = MAP_FAILED; + + (void) pthread_mutex_init(&common->l_common_mutex, NULL); + + (void) snprintf(path, sizeof (path), "%s/%s", target_basedir, + t->s_targ_base); + if (t10_lu_initialize(common, path) == False) { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM%x FAILED to initialize LU %d", + t->s_targ_num, lun); + (void) pthread_mutex_unlock(&lu_list_mutex); + goto error; + } + + avl_create(&common->l_all_open, find_lu_by_targ, + sizeof (t10_lu_impl_t), + offsetof(t10_lu_impl_t, l_open_lu_node)); + + avl_insert(&lu_list, (void *)common, wc); + (void) pthread_create(&common->l_thr_id, NULL, lu_runner, + (void *)common); + queue_prt(mgmtq, Q_STE_NONIO, + "SAM%x LU[%d.%d] Created new LU thread 0x%x", + t->s_targ_num, common->l_internal_num, common->l_num, + common->l_thr_id); + + } else { + + /* + * If there's a common LU structure already we free + * the guid which was created for the search. If an error + * occurs the guid space will be freed in the error handling + * code. If a new LU is created though we don't free the guid + * since the LU needs the information. + */ + free(lc.l_guid); + lc.l_guid = NULL; + queue_prt(mgmtq, Q_STE_NONIO, + "SAM%x Found existing LU[%d.%d]", t->s_targ_num, + common->l_internal_num, common->l_num); + } + (void) pthread_mutex_lock(&common->l_common_mutex); + (void) avl_find(&common->l_all_open, (void *)l, &wc); + avl_insert(&common->l_all_open, (void *)l, wc); + (void) pthread_mutex_unlock(&common->l_common_mutex); + + (void) pthread_mutex_unlock(&lu_list_mutex); + + /* + * Now add this I_T_L to the targets list of open LUNs so that + * in the future we can get access through the AVL tree. + * We wait to add the LU to the target list until now so that we don't + * have to delete the node in case an error occurs. + */ + avl_insert(&t->s_open_lu, (void *)l, wt); + + (void) pthread_mutex_lock(&l->l_mutex); + l->l_common = common; + (void) pthread_mutex_unlock(&l->l_mutex); + + /* + * The common LU thread is responsible for filling in the command + * functions and table. + */ + queue_message_set(common->l_from_transports, 0, msg_lu_add, (void *)l); + + free(guid); + xml_tree_free(n); + + return (l); + +error: + if (guid) + free(guid); + if (xml_fd != -1) + (void) close(xml_fd); + if (r) { + xmlTextReaderClose(r); + xmlFreeTextReader(r); + } + if (n) + xml_tree_free(n); + if (l) + free(l); + if (lc.l_guid) + free(lc.l_guid); + if (common) + free(common); + return (NULL); +} + +static Boolean_t +t10_lu_initialize(t10_lu_common_t *lu, char *basedir) +{ + char *str = NULL; + + if (load_params(lu, basedir) == False) + return (False); + + if (xml_find_value_str(lu->l_root, XML_ELEMENT_DTYPE, &str) == True) { + if (strcmp(str, TGT_TYPE_DISK) == 0) { + lu->l_dtype = DTYPE_DIRECT; + if (sbc_init_common(lu) == False) + goto error; + } else if (strcmp(str, TGT_TYPE_RAW) == 0) { + lu->l_dtype = DTYPE_UNKNOWN; + if (raw_init_common(lu) == False) + goto error; + } else if (strcmp(str, TGT_TYPE_TAPE) == 0) { + lu->l_dtype = DTYPE_SEQUENTIAL; + if (ssc_init_common(lu) == False) + goto error; + } else if (strcmp(str, TGT_TYPE_OSD) == 0) { + lu->l_dtype = DTYPE_OSD; + if (osd_init_common(lu) == False) + goto error; + } + if (lu->l_dtype_params == NULL) + goto error; + free(str); + } else + goto error; + + return (True); +error: + if (str != NULL) + free(str); + return (False); +} + +/* + * []---- + * | lu_runner -- The workhorse for each LU + * | + * | This routine is the guts of the Task Router and Task Set for SAM-3. + * []---- + */ +static void * +lu_runner(void *v) +{ + t10_lu_common_t *lu = (t10_lu_common_t *)v; + msg_t *m; + t10_lu_impl_t *itl; + t10_cmd_t *cmd; + char *data, + *path; + size_t data_len, + new_size, + offset; + ssize_t cc; + void *provo_err; + t10_shutdown_t *s; + + util_title(mgmtq, Q_STE_NONIO, lu->l_internal_num, "Start LU"); + while ((m = queue_message_get(lu->l_from_transports)) != NULL) { + + switch (m->msg_type) { + case msg_cmd_send: + cmd = (t10_cmd_t *)m->msg_data; + if (cmd->c_lu->l_status) { + spc_sense_create(cmd, cmd->c_lu->l_status, 0); + spc_sense_ascq(cmd, cmd->c_lu->l_asc, + cmd->c_lu->l_ascq); + /* + * Clear out the per LU values before + * calling trans_send_complete(). It's + * possible for the transport to handle + * this command and free it before returning. + */ + cmd->c_lu->l_status = 0; + cmd->c_lu->l_asc = 0; + cmd->c_lu->l_ascq = 0; + trans_send_complete(cmd, STATUS_CHECK); + } else { + lu->l_curr = cmd; + (*cmd->c_lu->l_cmd)(cmd, cmd->c_cdb, + cmd->c_cdb_len); + lu->l_curr = NULL; + } + break; + + case msg_cmd_data_out: + cmd = (t10_cmd_t *)m->msg_data; + data = cmd->c_data; + data_len = cmd->c_data_len; + offset = cmd->c_offset; + + /* + * We clear the c_data_len here because if the + * emulation routine processes the data and still + * needs more it will call trans_rqst_datain() + * which will look at c_data_len to see if there + * was immediate data available from the transport. + * In this case we've already processed the data + * and need to request more from the transport. + * c_data is set to NULL because there's an assert + * in trans_rqst_datain() checking that c_data is + * indeed null. + */ + cmd->c_data_len = 0; + cmd->c_data = NULL; + + lu->l_curr = cmd; + (*cmd->c_lu->l_data)(cmd, cmd->c_emul_id, + offset, data, data_len); + lu->l_curr = NULL; + break; + + case msg_lu_add: + itl = (t10_lu_impl_t *)m->msg_data; + switch (lu->l_dtype) { + case DTYPE_DIRECT: + sbc_init_per(itl); + break; + + case DTYPE_SEQUENTIAL: + ssc_init_per(itl); + break; + + case DTYPE_OSD: + osd_init_per(itl); + break; + + case DTYPE_UNKNOWN: + raw_init_per(itl); + break; + } + break; + + case msg_reset_lu: + queue_reset(lu->l_from_transports); + (void) pthread_mutex_lock(&lu->l_common_mutex); + itl = avl_first(&lu->l_all_open); + do { + /* + * The current implementation is that we + * have a shared queue for each LU. That means + * if we reset a LU all I_T nexus' must + * receive a CHECK_CONDITION on their next + * command. + */ + switch (lu->l_dtype) { + case DTYPE_DIRECT: + sbc_fini_per(itl); + sbc_init_per(itl); + break; + + case DTYPE_SEQUENTIAL: + ssc_fini_per(itl); + ssc_init_per(itl); + break; + + case DTYPE_UNKNOWN: + raw_fini_per(itl); + raw_init_per(itl); + break; + + case DTYPE_OSD: + osd_fini_per(itl); + osd_init_per(itl); + } + + itl = AVL_NEXT(&lu->l_all_open, itl); + } while (itl != NULL); + (void) pthread_mutex_unlock(&lu->l_common_mutex); + break; + + case msg_shutdown: + + s = (t10_shutdown_t *)m->msg_data; + itl = s->t_lu; + (void) pthread_mutex_lock(&lu_list_mutex); + (void) pthread_mutex_lock(&lu->l_common_mutex); + assert(avl_find(&lu->l_all_open, (void *)itl, NULL) != + NULL); + avl_remove(&lu->l_all_open, (void *)itl); + + queue_walker_free(lu->l_from_transports, + lu_remove_cmds, (void *)itl); + switch (lu->l_dtype) { + case DTYPE_DIRECT: + sbc_fini_per(itl); + break; + + case DTYPE_SEQUENTIAL: + ssc_fini_per(itl); + break; + + case DTYPE_OSD: + osd_fini_per(itl); + break; + + case DTYPE_UNKNOWN: + raw_fini_per(itl); + break; + } + /* + * Don't remove reference to l_common area until after + * the emulation routines are finished since they + * are likely to reference l_dtype_params. + */ + (void) pthread_mutex_lock(&itl->l_mutex); + itl->l_common = NULL; + (void) pthread_mutex_unlock(&itl->l_mutex); + + queue_message_set(s->t_q, 0, msg_shutdown_rsp, + (void *)(uintptr_t)itl->l_targ_lun); + if (avl_numnodes(&lu->l_all_open) == 0) { + queue_prt(mgmtq, Q_STE_NONIO, + "LU_%x No remaining targets for LU(%d)", + lu->l_internal_num, lu->l_fd); + if (lu->l_mmap != MAP_FAILED) + (void) munmap(lu->l_mmap, + lu->l_size); + if (close(lu->l_fd) != 0) + queue_prt(mgmtq, Q_STE_ERRS, + "LU_%x Failed to close fd, " + "errno=%d", lu->l_internal_num, + errno); + switch (lu->l_dtype) { + case DTYPE_DIRECT: + sbc_fini_common(lu); + break; + + case DTYPE_SEQUENTIAL: + ssc_fini_common(lu); + break; + + case DTYPE_UNKNOWN: + raw_fini_common(lu); + break; + } + avl_remove(&lu_list, (void *)lu); + util_title(mgmtq, Q_STE_NONIO, + lu->l_internal_num, "End LU"); + queue_free(lu->l_from_transports, NULL); + (void) pthread_mutex_unlock( + &lu->l_common_mutex); + (void) pthread_mutex_unlock(&lu_list_mutex); + xml_tree_free(lu->l_root); + free(lu->l_pid); + free(lu->l_vid); + free(lu->l_guid); + free(lu); + queue_message_free(m); + pthread_exit(NULL); + } + (void) pthread_mutex_unlock(&lu->l_common_mutex); + (void) pthread_mutex_unlock(&lu_list_mutex); + break; + + case msg_targ_inventory_change: + itl = (t10_lu_impl_t *)m->msg_data; + itl->l_status = KEY_UNIT_ATTENTION; + /* + * SPC-3 revision 21c, section 4.5.6, Table 28 + * When LU inventory changes need to report + * a REPORTED LUNS DATA HAS CHANGED event. + */ + itl->l_asc = 0x3f; + itl->l_ascq = 0x0e; + queue_prt(mgmtq, Q_STE_NONIO, + "LU_%x Received InventoryChange for %d", + lu->l_internal_num, itl->l_common->l_num); + break; + + case msg_thick_provo: + cmd = (t10_cmd_t *)m->msg_data; + if (lu->l_mmap != MAP_FAILED) { + + /* + * If the file at c_offset is currently + * unallocated we'll read in that buffer + * which will be zeros and then write it + * back out which will force the underlying + * filesystem to allocate the blocks. + * If someone has already issued a write + * to this area we'll then just cause a + * useless, but safe read/write to occur. + */ + lu->l_curr = cmd; + lu->l_curr_provo = True; + bcopy((char *)lu->l_mmap + cmd->c_offset, + cmd->c_data, cmd->c_data_len); + cmd->c_lu->l_cmds_read++; + cmd->c_lu->l_sects_read += + cmd->c_data_len / 512; + bcopy(cmd->c_data, + (char *)lu->l_mmap + cmd->c_offset, + cmd->c_data_len); + cmd->c_lu->l_cmds_write++; + cmd->c_lu->l_sects_write += + cmd->c_data_len / 512; + lu->l_curr = NULL; + lu->l_curr_provo = False; + provo_err = 0; + + } else { + if ((cc = pread(lu->l_fd, cmd->c_data, + cmd->c_data_len, cmd->c_offset)) < 0) { + queue_prt(mgmtq, Q_STE_ERRS, + "LU_%x pread errno=%d", lu->l_num, + errno); + } else if (pwrite(lu->l_fd, cmd->c_data, cc, + cmd->c_offset) != cc) { + queue_prt(mgmtq, Q_STE_ERRS, + "LU_%x pwrite errno=%d", + lu->l_num, errno); + } + provo_err = (cc == cmd->c_data_len) ? + (void *)0 : (void *)1; + } + /* + * acknowledge this op and wait for next + */ + queue_message_set(cmd->c_lu->l_to_transport, 0, + msg_thick_provo, provo_err); + break; + + case msg_lu_capacity_change: + new_size = lseek(lu->l_fd, 0, SEEK_END); + queue_prt(mgmtq, Q_STE_NONIO, + "LU_%x Capacity Change from 0x%llx to 0x%llx", + lu->l_internal_num, lu->l_size, new_size); + if ((path = malloc(MAXPATHLEN)) == NULL) + break; + + (void) snprintf(path, MAXPATHLEN, "%s/%s", + target_basedir, itl->l_targ->s_targ_base); + (void) load_params(lu, path); + free(path); + switch (lu->l_dtype) { + case DTYPE_DIRECT: + sbc_task_mgmt(lu, CapacityChange); + break; + + case DTYPE_SEQUENTIAL: + ssc_task_mgmt(lu, CapacityChange); + break; + } + (void) pthread_mutex_lock(&lu->l_common_mutex); + itl = avl_first(&lu->l_all_open); + while (itl != NULL) { + itl->l_status = KEY_UNIT_ATTENTION; + itl->l_asc = SPC_ASC_CAP_CHANGE; + itl->l_ascq = SPC_ASCQ_CAP_CHANGE; + itl = AVL_NEXT(&lu->l_all_open, itl); + } + (void) pthread_mutex_unlock(&lu->l_common_mutex); + break; + + case msg_lu_online: + queue_prt(mgmtq, Q_STE_NONIO, + "LU_%x Received online event", lu->l_internal_num); + if ((path = malloc(MAXPATHLEN)) == NULL) + break; + + (void) pthread_mutex_lock(&lu->l_common_mutex); + itl = avl_first(&lu->l_all_open); + (void) pthread_mutex_unlock(&lu->l_common_mutex); + (void) snprintf(path, MAXPATHLEN, "%s/%s", + target_basedir, itl->l_targ->s_targ_base); + (void) load_params(lu, path); + free(path); + switch (lu->l_dtype) { + case DTYPE_DIRECT: + sbc_task_mgmt(lu, DeviceOnline); + break; + + case DTYPE_SEQUENTIAL: + ssc_task_mgmt(lu, DeviceOnline); + break; + } + (void) pthread_mutex_lock(&lu->l_common_mutex); + itl = avl_first(&lu->l_all_open); + while (itl != NULL) { + switch (lu->l_dtype) { + case DTYPE_DIRECT: + sbc_init_per(itl); + break; + + case DTYPE_SEQUENTIAL: + ssc_init_per(itl); + break; + } + itl = AVL_NEXT(&lu->l_all_open, itl); + } + (void) pthread_mutex_unlock(&lu->l_common_mutex); + break; + + } + queue_message_free(m); + } + + return (NULL); +} + +/* + * []---- + * | lu_buserr_handler -- deal with SIGBUS on mmap'd files + * | + * | Normally SIGBUS's are a real bad thing. With this project, which uses + * | mmap'd files that start out as hole-y, can represent more space than + * | the underlying storage has available. This is good and considered a + * | feature for "Thin Provisioning". However, this means that if the + * | administrator isn't on the ball the storage can fill up. Because of the + * | asynchronous nature of writing to a mmap'd file the OS will send a SIGBUS + * | to the thread which caused the problem. The thread will then locate its + * | data structure and in turn signal the initiator that a problem occurred. + * | Since we can't restart we're we left off because the out of space + * | condition is still present another thread is started to handle other + * | commands for the logical unit. The current thread will then exit. + * | + * | NOTE: + * | If for any reason this routine doesn't find what's it's expecting to + * | assert() will be called to create a core. This routine will only recover + * | from the expected case of a SIGBUS, otherwise something real bad has + * | happened and we need to see the core. + * []---- + */ +/*ARGSUSED*/ +void +lu_buserr_handler(int sig, siginfo_t *sip, void *v) +{ + t10_lu_common_t *lu; + pthread_t id = pthread_self(); + char *fa; + + if (pthread_mutex_trylock(&lu_list_mutex) != 0) { + assert(0); + return; + } + lu = avl_first(&lu_list); + while (lu != NULL) { + if (lu->l_thr_id == id) + break; + lu = AVL_NEXT(&lu_list, lu); + } + (void) pthread_mutex_unlock(&lu_list_mutex); + + if ((lu == NULL) || (lu->l_curr == NULL)) { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM%x BUS ERROR and couldn't find logical unit", + lu->l_num); + assert(0); + return; + } + + if (lu->l_mmap == MAP_FAILED) { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM%x BUS ERROR and device not mmap'd", lu->l_num); + assert(0); + return; + } + + fa = (char *)sip->__data.__fault.__addr; + if ((fa < (char *)lu->l_mmap) || + (fa > ((char *)lu->l_mmap + lu->l_size))) { + queue_prt(mgmtq, Q_STE_ERRS, + "SAM%x BUS ERROR occurred outsize of mmap bounds", + lu->l_num); + assert(0); + return; + } + + if (lu->l_curr_provo == True) { + lu->l_curr_provo = False; + queue_message_set(lu->l_curr->c_lu->l_to_transport, 0, + msg_thick_provo, (void *)1); + } else { + spc_sense_create(lu->l_curr, KEY_MEDIUM_ERROR, 0); + spc_sense_ascq(lu->l_curr, SPC_ASC_WRITE_ERROR, + SPC_ASCQ_WRITE_ERROR); + trans_send_complete(lu->l_curr, STATUS_CHECK); + } + + queue_prt(mgmtq, Q_STE_ERRS, + "SAM%x Caught an out-of-space issue", lu->l_num); + + /* + * Now restart another thread to pick up where we've left off with + * processing commands for this logical unit. + */ + (void) pthread_create(&lu->l_thr_id, NULL, lu_runner, (void *)lu); + pthread_exit((void *)0); +} + +/* + * []---- + * | lu_remove_cmds -- look for and free commands for a given ITL + * []---- + */ +static Boolean_t +lu_remove_cmds(msg_t *m, void *v) +{ + t10_lu_impl_t *lu = (t10_lu_impl_t *)v; + t10_cmd_t *c; + + switch (m->msg_type) { + case msg_cmd_send: + case msg_cmd_data_out: + c = (t10_cmd_t *)m->msg_data; + if (c->c_lu == lu) { + queue_prt(mgmtq, Q_STE_NONIO, + "SAM%x LUN %d, removed command during lu_remove", + c->c_lu->l_targ->s_targ_num, lu->l_common->l_num); + t10_cmd_state(c, T10_Cmd_Event_Release); + return (True); + } + break; + } + return (False); +} + +/* + * []---- + * | load_params -- load parameters and open LU backing store + * | + * | This routine can be called multiple times and will free and release + * | previous resources. + * []---- + */ +static Boolean_t +load_params(t10_lu_common_t *lu, char *basedir) +{ + char file[MAXPATHLEN], + *str; + int oflags = O_RDWR|O_LARGEFILE|O_NDELAY; + Boolean_t mmap_lun = True; + xml_node_t *node = NULL; + int version_maj = XML_VERS_LUN_MAJ, + version_min = XML_VERS_LUN_MIN, + xml_fd; + xmlTextReaderPtr r; + + /* + * Clean up from previous call to this function. This occurs if + * the LU has grown since it was last opened. + */ + if (lu->l_mmap != MAP_FAILED) + munmap(lu->l_mmap, lu->l_size); + if (lu->l_root != NULL) { + xml_tree_free(lu->l_root); + lu->l_root = NULL; + } + if (lu->l_fd != -1) + close(lu->l_fd); + + (void) snprintf(file, sizeof (file), "%s/%s%d", basedir, PARAMBASE, + lu->l_num); + + if ((xml_fd = open(file, O_RDONLY)) < 0) + return (False); + if ((r = (xmlTextReaderPtr)xmlReaderForFd(xml_fd, NULL, NULL, + 0)) != NULL) { + while (xmlTextReaderRead(r) == 1) + if (xml_process_node(r, &node) == False) + break; + lu->l_root = node; + } else + return (False); + + (void) close(xml_fd); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + + if (validate_version(node, &version_maj, &version_min) == False) + goto error; + + if (xml_find_value_str(node, XML_ELEMENT_PID, &lu->l_pid) == False) + goto error; + + if (xml_find_value_str(node, XML_ELEMENT_VID, &lu->l_vid) == False) + goto error; + + /* + * If there's no <status> tag it just means this is an older param + * file and there's no need to treat it as an error. Just mark + * the device as online. + */ + if (xml_find_value_str(node, XML_ELEMENT_STATUS, &str) == True) { + if (strcmp(str, TGT_STATUS_ONLINE) == 0) + lu->l_state = lu_online; + else if (strcmp(str, TGT_STATUS_OFFLINE) == 0) + lu->l_state = lu_offline; + else if (strcmp(str, TGT_STATUS_ERRORED) == 0) + lu->l_state = lu_errored; + } else + lu->l_state = lu_online; + free(str); + + /* + * If offline, we need to check to see if there's an initialization + * thread running for this lun. If not, start one. + */ + if ((lu->l_state == lu_offline) && + (thick_provo_chk_thr(strrchr(basedir, '/') + 1, lu->l_num) == + False)) { + queue_prt(mgmtq, Q_STE_NONIO, + "LU_%d No initialization thread running", lu->l_num); + if (thin_provisioning == False) { + thick_provo_t *tp; + pthread_t junk; + + if ((tp = calloc(1, sizeof (*tp))) != NULL) { + tp->targ_name = strdup(strrchr(basedir, '/')) + + 1; + tp->lun = lu->l_num; + tp->q = queue_alloc(); + (void) pthread_create(&junk, NULL, + thick_provo_start, tp); + /* ---- wait for start message ---- */ + queue_message_free(queue_message_get(tp->q)); + } + } + } + + /* + * The default is to disable the fast write acknowledgement which + * can be overridden in a couple of ways. First, see if the global + * fast-write-ack is enabled, then check the per logical unit flags. + * The per LU bit is settable via a SCSI command. + */ + lu->l_fast_write_ack = False; + (void) xml_find_value_boolean(main_config, XML_ELEMENT_FAST, + &lu->l_fast_write_ack); + (void) xml_find_value_boolean(node, XML_ELEMENT_FAST, + &lu->l_fast_write_ack); + if (lu->l_fast_write_ack == False) + oflags |= O_SYNC; + + /* + * Object-based Storage Devices currently use directories to + * represent the partitions and files in those directories to + * represent user objects and collections. Therefore, there's + * not just a single file to be opened, but potentially thousands. + * Therefore, stop here if we've got an OSD dtype. + */ + if (xml_find_value_str(node, XML_ELEMENT_DTYPE, &str) == False) + goto error; + if (strcmp(str, TGT_TYPE_OSD) == 0) { + free(str); + return (True); + } else + free(str); + + (void) snprintf(file, sizeof (file), "%s/%s%d", basedir, LUNBASE, + lu->l_num); + if ((lu->l_fd = open(file, oflags)) == -1) + goto error; + +#ifndef _LP64 + /* + * Since the address space is so limited on 32bit machines + * disable mmap'ing the file by default. + */ + mmap_lun = False; +#endif + (void) xml_find_value_boolean(node, XML_ELEMENT_MMAP_LUN, &mmap_lun); + if (xml_find_value_str(node, XML_ELEMENT_SIZE, &str) == True) { + lu->l_size = strtoll(str, NULL, 0) * 512LL; + free(str); + } else + goto error; + + if (mmap_lun == True) { + /* + * st_size will be wrong if the device is a block device + * but that's okay since you can't mmap in a block device. + * A block device will fall back to using AIO operations. + */ + lu->l_mmap = mmap(0, lu->l_size, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_ALIGN, lu->l_fd, 0); + } else { + + /* + * Since the default case will be to mmap + * in all files someone has asked that this + * lun not be mmap. + */ + lu->l_mmap = MAP_FAILED; + } + return (True); +error: + if (lu->l_root) + xml_tree_free(lu->l_root); + if (lu->l_pid) + free(lu->l_pid); + if (lu->l_vid) + free(lu->l_vid); + if (lu->l_fd != -1) + (void) close(lu->l_fd); + return (False); +} + +/* + * []---- + * | cmd_common_free -- frees data stored in the cmd used in two different spots + * | + * | NOTE: The mutex which protects c_state must be held when this routine + * | is called. + * []---- + */ +static void +cmd_common_free(t10_lu_impl_t *lu, t10_cmd_t *cmd) +{ + if (cmd->c_state != T10_Cmd_Free) { + avl_remove(&lu->l_cmds, cmd); + cmd->c_state = T10_Cmd_Free; + } + + cmd->c_data = 0; + cmd->c_data_len = 0; + + if (cmd->c_emul_complete != NULL) { + (*cmd->c_emul_complete)(cmd->c_emul_id); + cmd->c_emul_complete = NULL; + } + if (cmd->c_cdb) { + free(cmd->c_cdb); + cmd->c_cdb = NULL; + } + if (cmd->c_cmd_sense) { + free(cmd->c_cmd_sense); + cmd->c_cmd_sense = NULL; + } +} + +/* + * []---- + * | fallocate -- allocate blocks for file via file system interface + * | + * | This is a faster approach to allocating the blocks for a file. + * | Instead of reading and then writing each block which will force the + * | file system to allocate the data we simply ask the file system to + * | allocate the space. Unfortunately not all file systems support this + * | feature. + * []---- + */ +static Boolean_t +fallocate(int fd, off64_t len) +{ +#ifdef FALLOCATE_SUPPORTED +#if defined(_LARGEFILE64_SOURCE) && !defined(_LP64) + struct flock64 lck; + + lck.l_whence = 0; + lck.l_start = 0; + lck.l_len = len; + lck.l_type = F_WRLCK; + + if (fcntl(fd, F_ALLOCSP64, &lck) == -1) + return (False); + else + return (True); +#else + struct flock lck; + + lck.l_whence = 0; + lck.l_start = 0; + lck.l_len = len; + lck.l_type = F_WRLCK; + + if (fcntl(fd, F_ALLOCSP, &lck) == -1) + return (False); + else + return (True); +#endif +#else + return (False); +#endif +} + +/* + * []---- + * | find_lu_by_num -- AVL comparison which looks at LUN + * []---- + */ +static int +find_lu_by_num(const void *v1, const void *v2) +{ + t10_lu_impl_t *l1 = (t10_lu_impl_t *)v1, + *l2 = (t10_lu_impl_t *)v2; + + if (l1->l_targ_lun < l2->l_targ_lun) + return (-1); + if (l1->l_targ_lun > l2->l_targ_lun) + return (1); + return (0); +} + +/* + * []---- + * | find_lu_by_guid -- AVL comparison which looks at GUID + * []---- + */ +static int +find_lu_by_guid(const void *v1, const void *v2) +{ + t10_lu_common_t *l1 = (t10_lu_common_t *)v1, + *l2 = (t10_lu_common_t *)v2; + int i; + + if (l1->l_guid_len != l2->l_guid_len) { + return ((l1->l_guid_len < l2->l_guid_len) ? -1 : 1); + } + for (i = 0; i < l1->l_guid_len; i++) { + if (l1->l_guid[i] != l2->l_guid[i]) { + return ((l1->l_guid[i] < l2->l_guid[i]) ? -1 : 1); + } + } + return (0); +} + +/* + * []---- + * | find_lu_by_targ -- AVL comparison which looks at the target + * | + * | NOTE: + * | The target value is the memory address of the per target structure. + * | Therefore, it's not persistent in any manner, nor can any association + * | be made between the target value and the initiator. It will be unique + * | however which is all that we're looking for. + * []---- + */ +static int +find_lu_by_targ(const void *v1, const void *v2) +{ + t10_lu_impl_t *l1 = (t10_lu_impl_t *)v1, + *l2 = (t10_lu_impl_t *)v2; + + if ((uint64_t)(uintptr_t)l1->l_targ < (uint64_t)(uintptr_t)l2->l_targ) + return (-1); + else if ((uint64_t)(uintptr_t)l1->l_targ > + (uint64_t)(uintptr_t)l2->l_targ) + return (1); + else + return (0); +} + +/* + * []---- + * | find_cmd_by_addr -- AVL comparison using the simplist of methods + * []---- + */ +static int +find_cmd_by_addr(const void *v1, const void *v2) +{ + uint64_t cmd1 = (uint64_t)(uintptr_t)v1, + cmd2 = (uint64_t)(uintptr_t)v2; + + if (cmd1 < cmd2) + return (-1); + else if (cmd1 > cmd2) + return (1); + else + return (0); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_sbc.c b/usr/src/cmd/iscsi/iscsitgtd/t10_sbc.c new file mode 100644 index 0000000000..daae15a17e --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_sbc.c @@ -0,0 +1,1865 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * []------------------------------------------------------------------[] + * | Implementation of SBC-2 emulation | + * []------------------------------------------------------------------[] + */ +#include <sys/types.h> +#include <aio.h> +#include <sys/asynch.h> +#include <sys/mman.h> +#include <stddef.h> +#include <strings.h> +#include <unistd.h> +#include <assert.h> + +#include <sys/scsi/generic/sense.h> +#include <sys/scsi/generic/status.h> +#include <sys/scsi/generic/inquiry.h> +#include <sys/scsi/generic/commands.h> +#include <sys/scsi/generic/mode.h> +#include <sys/scsi/generic/dad_mode.h> + +#include "t10.h" +#include "t10_spc.h" +#include "t10_sbc.h" +#include "utility.h" + +/* + * Forward declarations + */ +static int sbc_mmap_overlap(const void *v1, const void *v2); +static void sbc_overlap_store(disk_io_t *io); +static void sbc_overlap_free(disk_io_t *io); +static void sbc_overlap_check(disk_io_t *io); +static void sbc_overlap_flush(disk_params_t *d); +static void sbc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +static void sbc_data(t10_cmd_t *cmd, emul_handle_t e, size_t offset, + char *data, size_t data_len); +static disk_io_t *sbc_io_alloc(t10_cmd_t *c); +static void sbc_io_free(emul_handle_t e); +static void sbc_read_cmplt(emul_handle_t e); +static void sbc_write_cmplt(emul_handle_t e); +static void sbc_read_capacity16(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +static char *sense_page3(disk_params_t *d, char *buf); +static char *sense_page4(disk_params_t *d, char *buf); +static char *sense_cache(disk_params_t *d, char *buf); +static char *sense_mode_control(t10_lu_impl_t *lu, char *buf); +static char *sense_info_ctrl(char *buf); +static scsi_cmd_table_t lba_table[]; + +/* + * []---- + * | sbc_init_common -- Initialize LU data which is common to all I_T_Ls + * []---- + */ +Boolean_t +sbc_init_common(t10_lu_common_t *lu) +{ + disk_params_t *d; + xml_node_t *node = lu->l_root; + + if ((d = (disk_params_t *)calloc(1, sizeof (*d))) == NULL) + return (False); + + (void) xml_find_value_int(node, XML_ELEMENT_BPS, + (int *)&d->d_bytes_sect); + (void) xml_find_value_int(node, XML_ELEMENT_HEADS, + (int *)&d->d_heads); + (void) xml_find_value_int(node, XML_ELEMENT_SPT, + (int *)&d->d_spt); + (void) xml_find_value_int(node, XML_ELEMENT_CYLINDERS, + (int *)&d->d_cyl); + (void) xml_find_value_int(node, XML_ELEMENT_RPM, + (int *)&d->d_rpm); + (void) xml_find_value_int(node, XML_ELEMENT_INTERLEAVE, + (int *)&d->d_interleave); + d->d_fast_write = lu->l_fast_write_ack; + d->d_size = lu->l_size / (uint64_t)d->d_bytes_sect; + d->d_state = lu->l_state; + + avl_create(&d->d_mmap_overlaps, sbc_mmap_overlap, + sizeof (disk_io_t), offsetof(disk_io_t, da_mmap_overlap)); + (void) pthread_mutex_init(&d->d_mutex, NULL); + (void) pthread_cond_init(&d->d_mmap_cond, NULL); + (void) pthread_cond_init(&d->d_io_cond, NULL); + if ((d->d_io_reserved = (disk_io_t *)calloc(1, sizeof (disk_io_t))) == + NULL) { + free(d); + return (False); + } + + lu->l_dtype_params = (void *)d; + return (True); +} + +void +sbc_fini_common(t10_lu_common_t *lu) +{ + disk_params_t *d = lu->l_dtype_params; + + sbc_overlap_flush(d); + avl_destroy(&d->d_mmap_overlaps); + free(d->d_io_reserved); + free(lu->l_dtype_params); +} + +void +sbc_task_mgmt(t10_lu_common_t *lu, TaskOp_t op) +{ + disk_params_t *d = (disk_params_t *)lu->l_dtype_params; + + switch (op) { + case CapacityChange: + d->d_size = lu->l_size / (uint64_t)d->d_bytes_sect; + break; + + case DeviceOnline: + d->d_state = lu->l_state; + break; + } +} + +/* + * []---- + * | sbc_init_per -- Initialize per I_T_L information + * []---- + */ +void +sbc_init_per(t10_lu_impl_t *itl) +{ + disk_params_t *d = (disk_params_t *)itl->l_common->l_dtype_params; + + if (d->d_state == lu_online) + itl->l_cmd = sbc_cmd; + else + itl->l_cmd = spc_cmd_offline; + itl->l_data = sbc_data; + itl->l_cmd_table = lba_table; + + /* + * The first time an I_T nexus connects to a LU it is supposed + * to receive an unit attention upon the first command sent. + */ + itl->l_status = KEY_UNIT_ATTENTION; + itl->l_asc = 0x29; + itl->l_ascq = 0x01; +} + +void +sbc_fini_per(t10_lu_impl_t *itl) +{ + disk_params_t *d = (disk_params_t *)itl->l_common->l_dtype_params; + t10_lu_impl_t *lu; + + if (d->d_reserve_owner == itl) { + + /* + * Since we currently own the reservation, drop it, + * and restore everyone elses command pointer. + */ + lu = avl_first(&itl->l_common->l_all_open); + do { + lu->l_cmd = sbc_cmd; + lu = AVL_NEXT(&itl->l_common->l_all_open, lu); + } while (lu != NULL); + d->d_reserve_owner = NULL; + } +} + +/* + * []---- + * | sbc_cmd -- start a SCSI command + * | + * | This routine is called from within the SAM-3 Task router. + * []---- + */ +static void +sbc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + scsi_cmd_table_t *e; + + e = &cmd->c_lu->l_cmd_table[cdb[0]]; +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "SBC%x LUN%d Cmd %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name == NULL ? "(no name)" : e->cmd_name); +#endif + (*e->cmd_start)(cmd, cdb, cdb_len); +} + +/* + * []---- + * | sbc_cmd_reserve -- Run commands when another I_T_L has a reservation + * []---- + */ +static void +sbc_cmd_reserved(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + scsi_cmd_table_t *e; + + switch (cdb[0]) { + case SCMD_TEST_UNIT_READY: + case SCMD_INQUIRY: + case SCMD_REPORT_LUNS: + case SCMD_LOG_SENSE_G1: + case SCMD_READ_MEDIA_SERIAL: + case SCMD_REPORT_TARGET_PORT_GROUPS: + case SCMD_REQUEST_SENSE: + /* + * SPC-2, revision 20, Section 5.5.1 table 10 + * The specification allows these three commands + * to run even through there's a reservation in place. + */ + e = &cmd->c_lu->l_cmd_table[cdb[0]]; +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "RESERVED: SBC%x LUN%d Cmd %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name == NULL ? "(no name)" : e->cmd_name); +#endif + (*e->cmd_start)(cmd, cdb, cdb_len); + break; + + default: + trans_send_complete(cmd, STATUS_RESERVATION_CONFLICT); + } +} + +/* + * []---- + * | sbc_data -- Data phase for command. + * | + * | Normally this is only called for the WRITE command. Other commands + * | that have a data in phase will probably be short circuited when + * | we call trans_rqst_dataout() and the data is already available. + * | At least this is true for iSCSI. FC however will need a DataIn phase + * | for commands like MODE SELECT and PGROUT. + * []---- + */ +static void +sbc_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + scsi_cmd_table_t *e; + + e = &cmd->c_lu->l_cmd_table[cmd->c_cdb[0]]; +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "SBC%x LUN%d Data %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name); +#endif + (*e->cmd_data)(cmd, id, offset, data, data_len); +} + +/* + * []------------------------------------------------------------------[] + * | SCSI Block Commands - 2 | + * | T10/1417-D | + * | The following functions implement the emulation of SBC-2 type | + * | commands. | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | sbc_read -- emulation of SCSI READ command + * []---- + */ +/*ARGSUSED*/ +static void +sbc_read(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /*LINTED*/ + union scsi_cdb *u = (union scsi_cdb *)cdb; + diskaddr_t addr; + off_t offset = 0; + uint32_t cnt, + min; + disk_io_t *io; + void *mmap_data = T10_MMAP_AREA(cmd); + uint64_t err_blkno; + disk_params_t *d; + uchar_t addl_sense_len; + + if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + switch (u->scc_cmd) { + case SCMD_READ: + /* + * SBC-2 Revision 16, section 5.5 + * Reserve bit checks + */ + if ((cdb[1] & 0xe0) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + addr = (diskaddr_t)(uint32_t)GETG0ADDR(u); + cnt = GETG0COUNT(u); + + /* + * SBC-2 Revision 16 + * Section: 5.5 READ(6) command + * A TRANSFER LENGTH field set to zero specifies + * that 256 logical blocks shall be read. + */ + if (cnt == 0) + cnt = 256; + break; + + case SCMD_READ_G1: + /* + * SBC-2 Revision 16, section 5.6 + * Reserve bit checks. + */ + if ((cdb[1] & 6) || cdb[6] || + SAM_CONTROL_BYTE_RESERVED(cdb[9])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + addr = (diskaddr_t)(uint32_t)GETG1ADDR(u); + cnt = GETG1COUNT(u); + break; + + case SCMD_READ_G4: + /* + * SBC-2 Revision 16, section 5.8 + * Reserve bit checks + */ + if ((cdb[1] & 0x6) || cdb[14] || + SAM_CONTROL_BYTE_RESERVED(cdb[15])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + addr = GETG4LONGADDR(u); + cnt = GETG4COUNT(u); + break; + + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((addr + cnt) > d->d_size) { + + if (addr > d->d_size) + err_blkno = addr; + else + err_blkno = d->d_size; + + /* + * XXX: What's SBC-2 say about ASC/ASCQ here. Solaris + * doesn't care about these values when key is set + * to KEY_ILLEGAL_REQUEST. + */ + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + addl_sense_len = INFORMATION_SENSE_DESCR; + else + addl_sense_len = 0; + + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len); + spc_sense_info(cmd, err_blkno); + spc_sense_ascq(cmd, 0x21, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + + queue_prt(mgmtq, Q_STE_ERRS, + "SBC%x LUN%d READ Illegal sector " + "(0x%llx + 0x%x) > 0x%ullx", cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, addr, cnt, d->d_size); + return; + } + + cmd->c_lu->l_cmds_read++; + cmd->c_lu->l_sects_read += cnt; + + if (cnt == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + do { + io = sbc_io_alloc(cmd); + min = MIN((cnt * 512) - offset, T10_MAX_OUT(cmd)); + + io->da_lba = addr; + io->da_lba_cnt = cnt; + io->da_offset = offset; + io->da_data_len = min; + +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, + "SBC%x LUN%d blk 0x%llx, cnt %d, offset 0x%llx, size %d", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + addr, cnt, io->da_offset, min); +#endif + if (mmap_data != MAP_FAILED) { + + io->da_clear_overlap = True; + io->da_data_alloc = False; + io->da_aio.a_aio.aio_return = min; + io->da_data = (char *)mmap_data + (addr * 512LL) + + io->da_offset; + sbc_overlap_store(io); + sbc_read_cmplt((emul_handle_t)io); + + } else { + if ((io->da_data = (char *)malloc(min)) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + io->da_clear_overlap = False; + io->da_data_alloc = True; + io->da_aio.a_aio_cmplt = sbc_read_cmplt; + io->da_aio.a_id = io; + trans_aioread(cmd, io->da_data, min, (addr * 512LL) + + (off_t)io->da_offset, (aio_result_t *)io); + } + offset += min; + } while (offset < (off_t)(cnt * 512)); +} + +/* + * []---- + * | sbc_read_cmplt -- Once we have the data, need to send it along. + * []---- + */ +static void +sbc_read_cmplt(emul_handle_t id) +{ + disk_io_t *io = (disk_io_t *)id; + int sense_len; + uint64_t err_blkno; + t10_cmd_t *cmd = io->da_cmd; + Boolean_t last; + + if (io->da_aio.a_aio.aio_return != io->da_data_len) { + err_blkno = io->da_lba + ((io->da_offset + 511) / 512); + cmd->c_resid = (io->da_lba_cnt * 512) - io->da_offset; + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + sense_len = INFORMATION_SENSE_DESCR; + else + sense_len = 0; + spc_sense_create(cmd, KEY_HARDWARE_ERROR, sense_len); + spc_sense_info(cmd, err_blkno); + trans_send_complete(cmd, STATUS_CHECK); + sbc_io_free(io); + return; + } + + last = (io->da_offset + io->da_data_len) < (io->da_lba_cnt * 512LL) ? + False : True; + if (trans_send_datain(cmd, io->da_data, io->da_data_len, io->da_offset, + sbc_io_free, last, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/* + * []---- + * | sbc_write -- implement a SCSI write command. + * []---- + */ +/*ARGSUSED*/ +static void +sbc_write(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + union scsi_cdb *u; + diskaddr_t addr; + uint64_t err_blkno; + uint32_t cnt; + uchar_t addl_sense_len; + disk_params_t *d; + disk_io_t *io; + size_t max_out; + void *mmap_area; + + if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + /*LINTED*/ + u = (union scsi_cdb *)cdb; + + switch (u->scc_cmd) { + case SCMD_WRITE: + /* + * SBC-2 revision 16, section 5.24 + * Reserve bit checks. + */ + if ((cdb[1] & 0xe0) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + addr = (diskaddr_t)(uint32_t)GETG0ADDR(u); + cnt = GETG0COUNT(u); + /* + * SBC-2 Revision 16/Section 5.24 WRITE(6) + * A TRANSFER LENGHT of 0 indicates that 256 logical blocks + * shall be written. + */ + if (cnt == 0) + cnt = 256; + break; + + case SCMD_WRITE_G1: + /* + * SBC-2 revision 16, section 5.25 + * Reserve bit checks. + */ + if ((cdb[1] & 0x6) || cdb[6] || + SAM_CONTROL_BYTE_RESERVED(cdb[9])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + addr = (diskaddr_t)(uint32_t)GETG1ADDR(u); + cnt = GETG1COUNT(u); + break; + + case SCMD_WRITE_G4: + /* + * SBC-2 revision 16, section 5.27 + * Reserve bit checks. + */ + if ((cdb[1] & 0x6) || cdb[14] || + SAM_CONTROL_BYTE_RESERVED(cdb[15])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + addr = (diskaddr_t)GETG4LONGADDR(u); + cnt = GETG4COUNT(u); + break; + + default: + queue_prt(mgmtq, Q_STE_ERRS, "Unprocessed WRITE type"); + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + + } + + if ((addr + cnt) > d->d_size) { + + if (addr > d->d_size) + err_blkno = addr; + else + err_blkno = d->d_size; + + /* + * XXX: What's SBC-2 say about ASC/ASCQ here. Solaris + * doesn't care about these values when key is set + * to KEY_ILLEGAL_REQUEST. + */ + if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) + addl_sense_len = INFORMATION_SENSE_DESCR; + else + addl_sense_len = 0; + + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len); + spc_sense_info(cmd, err_blkno); + spc_sense_ascq(cmd, 0x21, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + + queue_prt(mgmtq, Q_STE_ERRS, + "SBC%x LUN%d WRITE Illegal sector " + "(0x%llx + 0x%x) > 0x%ullx", cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, addr, cnt, d->d_size); + return; + } + + if (cnt == 0) { + queue_prt(mgmtq, Q_STE_NONIO, + "SBC%x LUN%d WRITE zero block count for addr 0x%x", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + addr); + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + io = (disk_io_t *)cmd->c_emul_id; + if (io == NULL) { + io = sbc_io_alloc(cmd); + io->da_lba = addr; + io->da_lba_cnt = cnt; + io->da_clear_overlap = False; + io->da_aio.a_aio_cmplt = sbc_write_cmplt; + io->da_aio.a_id = io; + + /* + * Only update the statistics the first time through + * for this particular command. If the requested transfer + * is larger than the transport can handle this routine + * will be called many times. + */ + cmd->c_lu->l_cmds_write++; + cmd->c_lu->l_sects_write += cnt; + } + +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, + "SBC%x LUN%d blk 0x%llx, cnt %d, offset 0x%llx, size %d", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, addr, + cnt, io->da_offset, io->da_data_len); +#endif + + /* + * If a transport sets the maximum output value to zero we'll + * just request the entire amount. Otherwise, transfer no more + * than the maximum output or the reminder, whichever is less. + */ + max_out = cmd->c_lu->l_targ->s_maxout; + io->da_data_len = max_out ? MIN(max_out, + (cnt * 512) - io->da_offset) : (cnt * 512); + + mmap_area = T10_MMAP_AREA(cmd); + if (mmap_area != MAP_FAILED) { + + io->da_data_alloc = False; + io->da_data = (char *)mmap_area + (addr * 512LL) + + io->da_offset; + sbc_overlap_check(io); + + } else if ((io->da_data = (char *)malloc(io->da_data_len)) == NULL) { + + trans_send_complete(cmd, STATUS_BUSY); + return; + + } else { + + io->da_data_alloc = True; + } + if (trans_rqst_dataout(cmd, io->da_data, io->da_data_len, + io->da_offset, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/* + * []---- + * | sbc_write_data -- store a chunk of data from the transport + * []---- + */ +/*ARGSUSED*/ +void +sbc_write_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + disk_io_t *io = (disk_io_t *)id; + disk_params_t *d; + + if (cmd->c_lu->l_common->l_mmap == MAP_FAILED) { + trans_aiowrite(cmd, data, data_len, (io->da_lba * 512) + + (off_t)io->da_offset, (aio_result_t *)io); + } else { + if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + if (d->d_fast_write == False) { + + /* + * We only need to worry about sync'ing the blocks + * in the mmap case because if the fast cache isn't + * enabled for AIO the file will be opened with F_SYNC + * which performs the correct action. + */ + if (fsync(cmd->c_lu->l_common->l_fd) == -1) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + } + + /* + * Since the data has already been transfered from the + * transport to the mmap area we just need to call + * the complete routine. + */ + sbc_write_cmplt(id); + } +} + +/* + * []---- + * | sbc_write_cmplt -- deal with end game of write + * | + * | See if all of the data for this write operation has been dealt + * | with. If so, send a final acknowledgement back to the transport. + * | If not, update the offset, calculate the next transfer size, and + * | start the process again. + * []--- + */ +static void +sbc_write_cmplt(emul_handle_t e) +{ + disk_io_t *io = (disk_io_t *)e; + t10_cmd_t *cmd = io->da_cmd; + + if ((io->da_offset + io->da_data_len) < (io->da_lba_cnt * 512)) { + if (io->da_data_alloc == True) { + io->da_data_alloc = False; + free(io->da_data); + } + + io->da_offset += io->da_data_len; + io->da_data_len = MIN(cmd->c_lu->l_targ->s_maxout, + (io->da_lba_cnt * 512) - io->da_offset); + sbc_write(cmd, cmd->c_cdb, cmd->c_cdb_len); + return; + } + sbc_io_free(io); + trans_send_complete(cmd, STATUS_GOOD); +} + +/*ARGSUSED*/ +void +sbc_startstop(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /* + * SBC-2 revision 16, section 5.17 + * Reserve bit checks + */ + if ((cdb[1] & 0xfe) || cdb[2] || cdb[3] || + (cdb[4] & ~(SBC_PWR_MASK|SBC_PWR_LOEJ|SBC_PWR_START)) || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + /* + * More reserve bit checks + */ + switch ((cdb[4] & SBC_PWR_MASK) >> SBC_PWR_SHFT) { + case SBC_PWR_START_VALID: + /* + * It's an error to ask that the media be ejected. + * + * NOTE: Look for method to pass the START bit + * along to underlying storage. If we're asked to + * stop the drive there's not much that we can do + * for the virtual storage, but maybe everything else + * has been requested to stop as well. + */ + if (cdb[4] & SBC_PWR_LOEJ) { + goto send_error; + } + break; + + case SBC_PWR_ACTIVE: + case SBC_PWR_IDLE: + case SBC_PWR_STANDBY: + case SBC_PWR_OBSOLETE: + break; + + case SBC_PWR_LU_CONTROL: + case SBC_PWR_FORCE_IDLE_0: + case SBC_PWR_FORCE_STANDBY_0: + break; + + default: +send_error: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((cdb[1] & 1) == 0) { + /* + * Immediate bit is not set, so go ahead a flush things. + */ + if (fsync(cmd->c_lu->l_common->l_fd) != 0) { + spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + } + trans_send_complete(cmd, STATUS_GOOD); +} + +/* + * []---- + * | sbc_recap -- read capacity of device being emulated. + * []---- + */ +/*ARGSUSED*/ +void +sbc_recap(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + uint64_t capacity; + int len; + uint32_t lba; + struct scsi_capacity *cap; + disk_params_t *d; + disk_io_t *io; + + if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + capacity = d->d_size; + + len = sizeof (struct scsi_capacity); + + /* + * SBC-2 Revision 16, section 5.10.1 + * Any of the following conditions will generate an error. + * (1) PMI bit is zero and LOGICAL block address is non-zero + * (2) Rserved bytes are not zero + * (3) Reseved bits are not zero + * (4) Reserved CONTROL bits are not zero + */ + if ((((cdb[8] & SBC_CAPACITY_PMI) == 0) && + (cdb[2] || cdb[3] || cdb[4] || cdb[5])) || + cdb[1] || cdb[6] || cdb[7] || (cdb[8] & ~SBC_CAPACITY_PMI) || + SAM_CONTROL_BYTE_RESERVED(cdb[9])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + /* + * if the device capacity larger than 32 bits then set + * the capacity of the device to all 0xf's. + * a device that supports LBAs larger than 32 bits which + * should be used read_capacity(16) comand to get the capacity. + * NOTE: the adjustment to subject one from the capacity is + * done below. + */ + if (capacity & 0xFFFFFFFF00000000ULL) + capacity = 0xFFFFFFFF; + + io = sbc_io_alloc(cmd); + + if ((cap = (struct scsi_capacity *)calloc(1, len)) == NULL) { + sbc_io_free(io); + trans_send_complete(cmd, STATUS_BUSY); + return; + } + io->da_data = (char *)cap; + io->da_data_alloc = True; + io->da_clear_overlap = False; + io->da_data_len = len; + + if (capacity != 0xFFFFFFFF) { + /* + * Look at the PMI information + */ + if (cdb[8] & SBC_CAPACITY_PMI) { + lba = cdb[2] << 24 | cdb[3] << 16 | + cdb[4] << 8 | cdb[5]; + if (lba >= capacity) + cap->capacity = htonl(0xffffffff); + else + cap->capacity = (capacity - 1); + } else { + cap->capacity = htonl(capacity - 1); + } + } else { + cap->capacity = htonl(capacity); + } + cap->lbasize = htonl(d->d_bytes_sect); + + if (trans_send_datain(cmd, io->da_data, io->da_data_len, 0, + sbc_io_free, True, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +void +sbc_msense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + struct mode_header *mode_hdr; + char *np; + disk_params_t *d; + disk_io_t *io; + + if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + /* + * SPC-3 Revision 21c section 6.8 + * Reserve bit checks + */ + if ((cdb[1] & ~SPC_MODE_SENSE_DBD) || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + /* + * Zero length causes a simple ack to occur. + */ + if (cdb[4] == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + io = sbc_io_alloc(cmd); + + /* + * Make sure that we have enough room in the data buffer. We'll + * only send back the amount requested though + */ + io->da_data_len = MAX(cdb[4], sizeof (struct mode_format) + + sizeof (struct mode_geometry) + + sizeof (struct mode_control_scsi3) + + sizeof (struct mode_cache_scsi3) + + sizeof (struct mode_info_ctrl) + (MODE_BLK_DESC_LENGTH * 5)); + if ((io->da_data = (char *)calloc(1, io->da_data_len)) == NULL) { + sbc_io_free(io); + trans_send_complete(cmd, STATUS_BUSY); + return; + } + io->da_clear_overlap = False; + io->da_data_alloc = True; + mode_hdr = (struct mode_header *)io->da_data; + + switch (cdb[2]) { + case MODE_SENSE_PAGE3_CODE: + if ((d->d_heads == 0) && (d->d_cyl == 0) && (d->d_spt == 0)) { + sbc_io_free(io); + spc_unsupported(cmd, cdb, cdb_len); + return; + } + mode_hdr->length = sizeof (struct mode_format); + mode_hdr->bdesc_length = MODE_BLK_DESC_LENGTH; + (void) sense_page3(d, + io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length); + + break; + + case MODE_SENSE_PAGE4_CODE: + if ((d->d_heads == 0) && (d->d_cyl == 0) && (d->d_spt == 0)) { + sbc_io_free(io); + spc_unsupported(cmd, cdb, cdb_len); + return; + } + mode_hdr->length = sizeof (struct mode_geometry); + mode_hdr->bdesc_length = MODE_BLK_DESC_LENGTH; + (void) sense_page4(d, + io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length); + break; + + case MODE_SENSE_CACHE: + mode_hdr->length = sizeof (struct mode_cache_scsi3); + mode_hdr->bdesc_length = MODE_BLK_DESC_LENGTH; + (void) sense_cache(d, + io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length); + break; + + case MODE_SENSE_CONTROL: + mode_hdr->length = sizeof (struct mode_control_scsi3); + mode_hdr->bdesc_length = MODE_BLK_DESC_LENGTH; + (void) sense_mode_control(cmd->c_lu, + io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length); + break; + + case MODE_SENSE_INFO_CTRL: + (void) sense_info_ctrl(io->da_data); + break; + + case MODE_SENSE_SEND_ALL: + /* + * SPC-3 revision 21c + * Section 6.9.1 Table 97 + * "Return all subpage 00h mode pages in page_0 format" + */ + if (io->da_data_len < (sizeof (struct mode_format) + + sizeof (struct mode_geometry) + + sizeof (struct mode_control_scsi3) + + sizeof (struct mode_info_ctrl))) { + + /* + * Believe it or not, there's an initiator out + * there which sends a mode sense request for all + * of the pages, without always sending a data-in + * size which is large enough. + * NOTE: Need to check the error key returned + * here and see if something else should be used. + */ + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + trans_send_complete(cmd, STATUS_CHECK); + + } else { + + /* + * If we don't have geometry then don't attempt + * report that information. + */ + if (d->d_heads && d->d_cyl && d->d_spt) { + np = sense_page3(d, io->da_data); + np = sense_page4(d, np); + } + np = sense_cache(d, np); + np = sense_mode_control(cmd->c_lu, np); + (void) sense_info_ctrl(np); + + } + break; + + case 0x00: + /* + * SPC-3 Revision 21c, section 6.9.1 + * Table 97 -- Mode page code usage for all devices + * Page Code 00 == Vendor specific. We are going to return + * zeros. + */ + break; + + default: + queue_prt(mgmtq, Q_STE_ERRS, + "SBC%x LUN%d Unsupported mode_sense request 0x%x", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + cdb[2]); + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + break; + } + + if (trans_send_datain(cmd, io->da_data, cdb[4], 0, sbc_io_free, + True, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +void +sbc_synccache(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /* + * SBC-2 revision 16, section 5.18 + * Reserve bit checks + */ + if ((cdb[1] & ~SBC_SYNC_CACHE_IMMED) || cdb[6] || + SAM_CONTROL_BYTE_RESERVED(cdb[9])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + } else { + /* + * SBC-3, revision 16, section 5.18 + * An IMMED bit set to one specifies that the device server + * shall return status as soon as the CDB has been validated. + */ + if (cdb[1] & SBC_SYNC_CACHE_IMMED) { + + /* + * Immediately return a status of GOOD. If an error + * occurs with the fsync the next command will pick + * up an error. + */ + trans_send_complete(cmd, STATUS_GOOD); + if (fsync(cmd->c_lu->l_common->l_fd) == -1) { + cmd->c_lu->l_status = KEY_HARDWARE_ERROR; + cmd->c_lu->l_asc = 0x00; + cmd->c_lu->l_ascq = 0x00; + } + } else { + if (fsync(cmd->c_lu->l_common->l_fd) == -1) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + } else + trans_send_complete(cmd, STATUS_GOOD); + } + } +} + +/*ARGSUSED*/ +void +sbc_service_actiong4(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + switch (cdb[1] & SPC_GROUP4_SERVICE_ACTION_MASK) { + case SSVC_ACTION_READ_CAPACITY_G4: + sbc_read_capacity16(cmd, cdb, cdb_len); + break; + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x20, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + break; + } +} + +/*ARGSUSED*/ +static void +sbc_read_capacity16(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + uint64_t capacity, + lba; + int rep_size; /* response data size */ + struct scsi_capacity_16 *cap16; + disk_params_t *d; + disk_io_t *io; + + if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) + return; + + capacity = d->d_size; + /* + * READ_CAPACITY(16) command + */ + rep_size = cdb[10] << 24 | cdb[11] << 16 | cdb[12] << 8 | cdb[13]; + if (rep_size == 0) { + + /* + * A zero length field means we're done. + */ + trans_send_complete(cmd, STATUS_GOOD); + return; + } + rep_size = MIN(rep_size, sizeof (*cap16)); + + /* + * Reserve bit checks. + */ + if ((cdb[1] & ~SPC_GROUP4_SERVICE_ACTION_MASK) || + (cdb[14] & ~SBC_CAPACITY_PMI) || + SAM_CONTROL_BYTE_RESERVED(cdb[15])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 | + (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 | + (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 | + (uint64_t)cdb[8] << 8 | (uint64_t)cdb[9]; + + io = sbc_io_alloc(cmd); + + /* + * We'll malloc enough space for the structure so that we can + * set the values as we place. However, we'll set the transfer + * length to the minimum of the requested size and our structure. + * This is per SBC-2 revision 16, section 5.11.1 regarding + * ALLOCATION LENGTH. + */ + if ((cap16 = (struct scsi_capacity_16 *)calloc(1, sizeof (*cap16))) == + NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + io->da_data = (char *)cap16; + io->da_data_len = sizeof (*cap16); + io->da_data_alloc = True; + io->da_clear_overlap = False; + + if (cdb[14] & SBC_CAPACITY_PMI) { + if (lba >= capacity) + cap16->sc_capacity = htonll(0xffffffffffffffffULL); + else + cap16->sc_capacity = htonll(capacity - 1); + } else { + cap16->sc_capacity = htonll(capacity - 1); + } + cap16->sc_lbasize = htonl(d->d_bytes_sect); + + if (trans_send_datain(cmd, io->da_data, io->da_data_len, 0, + sbc_io_free, True, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +static void +sbc_reserve(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + disk_params_t *p = (disk_params_t *)T10_PARAMS_AREA(cmd); + t10_lu_impl_t *lu; + + if (p == NULL) + return; + + if (cdb[1] & 0xe0 || SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((p->d_reserve_owner != NULL) && + (p->d_reserve_owner != cmd->c_lu)) { + + trans_send_complete(cmd, STATUS_RESERVATION_CONFLICT); + return; + + } else if (p->d_reserve_owner == cmd->c_lu) { + + /* + * According SPC-2 revision 20, section 7.21.2 + * It shall be permissible for an initiator to + * reserve a logic unit that is currently reserved + * by that initiator + */ + trans_send_complete(cmd, STATUS_GOOD); + } else { + + lu = avl_first(&cmd->c_lu->l_common->l_all_open); + do { + if (lu != cmd->c_lu) + lu->l_cmd = sbc_cmd_reserved; + lu = AVL_NEXT(&cmd->c_lu->l_common->l_all_open, lu); + } while (lu != NULL); + p->d_reserve_owner = cmd->c_lu; + trans_send_complete(cmd, STATUS_GOOD); + } +} + +/*ARGSUSED*/ +static void +sbc_release(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + disk_params_t *p = (disk_params_t *)T10_PARAMS_AREA(cmd); + t10_lu_impl_t *lu; + + if (p == NULL) + return; + + if (cdb[1] & 0xe0 || cdb[3] || cdb[4] || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if (p->d_reserve_owner == NULL) { + + /* + * If nobody is the owner this command is successful. + */ + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + /* + * At this point the only way to get in here is to be the owner + * of the reservation. + */ + lu = avl_first(&cmd->c_lu->l_common->l_all_open); + do { + lu->l_cmd = sbc_cmd; + lu = AVL_NEXT(&cmd->c_lu->l_common->l_all_open, lu); + } while (lu != NULL); + p->d_reserve_owner = NULL; + trans_send_complete(cmd, STATUS_GOOD); +} + +/* + * []------------------------------------------------------------------[] + * | Support related functions for SBC-2 | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | sense_page3 -- Create page3 sense code for Disk. + * | + * | This is a separate routine because this is called in two different + * | locations. + * []---- + */ +static char * +sense_page3(disk_params_t *d, char *buf) +{ + struct mode_format mode_fmt; + + bzero(&mode_fmt, sizeof (mode_fmt)); + mode_fmt.mode_page.code = MODE_SENSE_PAGE3_CODE; + mode_fmt.mode_page.length = sizeof (struct mode_format) - + sizeof (struct mode_page); + mode_fmt.data_bytes_sect = htons(d->d_bytes_sect); + mode_fmt.sect_track = htons(d->d_spt); + mode_fmt.interleave = htons(d->d_interleave); + bcopy(&mode_fmt, buf, sizeof (mode_fmt)); + + return (buf + sizeof (mode_fmt)); +} + +/* + * []---- + * | sense_page4 -- Create page4 sense code for Disk. + * | + * | This is a separate routine because this is called in two different + * | locations. + * []---- + */ +static char * +sense_page4(disk_params_t *d, char *buf) +{ + struct mode_geometry mode_geom; + + bzero(&mode_geom, sizeof (mode_geom)); + mode_geom.mode_page.code = MODE_SENSE_PAGE4_CODE; + mode_geom.mode_page.length = + sizeof (struct mode_geometry) - sizeof (struct mode_page); + mode_geom.heads = d->d_heads; + mode_geom.cyl_ub = d->d_cyl >> 16; + mode_geom.cyl_mb = d->d_cyl >> 8; + mode_geom.cyl_lb = d->d_cyl; + mode_geom.rpm = htons(d->d_rpm); + bcopy(&mode_geom, buf, sizeof (mode_geom)); + + return (buf + sizeof (mode_geom)); +} + +static char * +sense_cache(disk_params_t *d, char *buf) +{ + struct mode_cache_scsi3 mode_cache; + + bzero(&mode_cache, sizeof (mode_cache)); + + mode_cache.wce = d->d_fast_write == True ? 1 : 0; + bcopy(&mode_cache, buf, sizeof (mode_cache)); + + return (buf + sizeof (mode_cache)); +} + +/* + * []---- + * | sense_mode_control -- Create mode control page for disk + * []---- + */ +static char * +sense_mode_control(t10_lu_impl_t *lu, char *buf) +{ + struct mode_control_scsi3 m; + + bzero(&m, sizeof (m)); + m.mode_page.code = MODE_SENSE_CONTROL; + m.mode_page.length = sizeof (struct mode_control_scsi3) - + sizeof (struct mode_page); + m.d_sense = (lu->l_dsense_enabled == True) ? 1 : 0; + bcopy(&m, buf, sizeof (m)); + + return (buf + sizeof (m)); +} + +/* + * []---- + * | sense_info_ctrl -- Create mode information control page + * []---- + */ +static char * +sense_info_ctrl(char *buf) +{ + struct mode_info_ctrl info; + + bzero(&info, sizeof (info)); + info.mode_page.code = 0x1c; + info.mode_page.length = sizeof (struct mode_info_ctrl) - + sizeof (struct mode_page); + bcopy(&info, buf, sizeof (info)); + + return (buf + sizeof (info)); +} + +/* + * []---- + * | sbc_io_alloc -- return a disk_io_t structure + * | + * | If the call to calloc fails we use the structure that was allocate + * | during the initial common initialization call. This will allow the + * | daemon to at least make progress. + * []---- + */ +static disk_io_t * +sbc_io_alloc(t10_cmd_t *c) +{ + disk_io_t *io; + disk_params_t *d = T10_PARAMS_AREA(c); + + if ((io = (disk_io_t *)calloc(1, sizeof (*io))) == NULL) { + (void) pthread_mutex_lock(&d->d_mutex); + if (d->d_io_used == True) { + d->d_io_need = True; + while (d->d_io_used == True) + pthread_cond_wait(&d->d_io_cond, &d->d_mutex); + d->d_io_need = False; + } + d->d_io_used = True; + io = d->d_io_reserved; + (void) pthread_mutex_unlock(&d->d_mutex); + } + + io->da_cmd = c; + io->da_params = d; + + return (io); +} + +/* + * []---- + * | sbc_io_free -- free local i/o buffers when transport is finished + * | + * | If the io structure being free is the preallocated buffer see if + * | anyone is waiting for the buffer. If so, wake them up. + * []---- + */ +static void +sbc_io_free(emul_handle_t e) +{ + disk_io_t *io = (disk_io_t *)e; + + if (io->da_clear_overlap == True) + sbc_overlap_free(io); + + if (io->da_data_alloc == True) + free(io->da_data); + + if (io == io->da_params->d_io_reserved) { + (void) pthread_mutex_lock(&io->da_params->d_mutex); + io->da_params->d_io_used = False; + if (io->da_params->d_io_need == True) + pthread_cond_signal(&io->da_params->d_io_cond); + (void) pthread_mutex_unlock(&io->da_params->d_mutex); + } else { + free(io); + } +} + +static int +sbc_mmap_overlap(const void *v1, const void *v2) +{ + disk_io_t *d1 = (disk_io_t *)v1, + *d2 = (disk_io_t *)v2; + + if ((d1->da_data + d1->da_data_len) < d2->da_data) + return (-1); + if (d1->da_data > (d2->da_data + d2->da_data_len)) + return (1); + return (0); +} + +static void +sbc_overlap_store(disk_io_t *io) +{ + disk_params_t *d = io->da_params; + avl_index_t where = 0; + + assert(d != NULL); + + (void) pthread_mutex_lock(&d->d_mutex); + (void) avl_find(&d->d_mmap_overlaps, (void *)io, &where); + avl_insert(&d->d_mmap_overlaps, (void *)io, where); + (void) pthread_mutex_unlock(&d->d_mutex); +} + +static void +sbc_overlap_free(disk_io_t *io) +{ + disk_params_t *d = io->da_params; + + assert(d != NULL); + + (void) pthread_mutex_lock(&d->d_mutex); + avl_remove(&d->d_mmap_overlaps, (void *)io); + if (d->d_mmap_paused == True) { + d->d_mmap_paused = False; + (void) pthread_cond_signal(&d->d_mmap_cond); + } + (void) pthread_mutex_unlock(&d->d_mutex); +} + +static void +sbc_overlap_check(disk_io_t *io) +{ + disk_params_t *d = io->da_params; + + assert(d != NULL); +recheck: + (void) pthread_mutex_lock(&d->d_mutex); + if (avl_find(&d->d_mmap_overlaps, (void *)io, NULL) != NULL) { + d->d_mmap_paused = True; + while (d->d_mmap_paused == True) + (void) pthread_cond_wait(&d->d_mmap_cond, + &d->d_mutex); + + /* + * After waiting on the condition variable the link + * list has changed because someone removed a command. + * So, drop the lock and reexamine the list. + */ + (void) pthread_mutex_unlock(&d->d_mutex); + goto recheck; + } + (void) pthread_mutex_unlock(&d->d_mutex); +} + +/* + * []---- + * | sbc_overlap_flush -- wait until everyone has reported in + * []---- + */ +static void +sbc_overlap_flush(disk_params_t *d) +{ + assert(d != NULL); +recheck: + (void) pthread_mutex_lock(&d->d_mutex); + if (avl_numnodes(&d->d_mmap_overlaps) != 0) { + d->d_mmap_paused = True; + while (d->d_mmap_paused == True) + (void) pthread_cond_wait(&d->d_mmap_cond, + &d->d_mutex); + + /* + * After waiting on the condition variable the link + * list has changed because someone removed a command. + * So, drop the lock and reexamine the list. + */ + (void) pthread_mutex_unlock(&d->d_mutex); + goto recheck; + } + (void) pthread_mutex_unlock(&d->d_mutex); +} + +/* + * []---- + * | Command table for LBA emulation. This is at the end of the file because + * | it's big and ugly. ;-) To make for fast translation to the appropriate + * | emulation routine we just have a big command table with all 256 possible + * | entries. Most will report STATUS_CHECK, unsupport operation. By doing + * | this we can avoid error checking for command range. + * []---- + */ +static scsi_cmd_table_t lba_table[] = { + /* 0x00 -- 0x0f */ + { spc_tur, NULL, NULL, "TEST_UNIT_READY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_request_sense, NULL, NULL, "REQUEST_SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_read, NULL, sbc_read_cmplt, "READ" }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_write, sbc_write_data, sbc_write_cmplt, "WRITE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x10 -- 0x1f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_inquiry, NULL, NULL, "INQUIRY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_mselect, spc_mselect_data, NULL, "MODE_SELECT" }, + { sbc_reserve, NULL, NULL, "RESERVE" }, + { sbc_release, NULL, NULL, "RELEASE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_msense, NULL, NULL, "MODE_SENSE" }, + { sbc_startstop, NULL, NULL, "START_STOP" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_send_diag, NULL, NULL, "SEND_DIAG" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x20 -- 0x2f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_recap, NULL, NULL, "READ_CAPACITY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_read, NULL, sbc_read_cmplt, "READ_G1" }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_write, sbc_write_data, sbc_write_cmplt, "WRITE_G1" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x30 -- 0x3f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_synccache, NULL, NULL, "SYNC_CACHE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x40 -- 0x4f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "LOG_SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x50 -- 0x5f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "PERSISTENT_IN" }, + { spc_unsupported, NULL, NULL, "PERSISTENT_OUT" }, + + /* 0x60 -- 0x6f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x70 -- 0x7f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x80 -- 0x8f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_read, NULL, sbc_read_cmplt, "READ_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_write, sbc_write_data, sbc_write_cmplt, "WRITE_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x90 -- 0x9f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { sbc_service_actiong4, NULL, NULL, "SVC_ACTION_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xa0 - 0xaf */ + { spc_report_luns, NULL, NULL, "REPORT_LUNS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_report_tpgs, NULL, NULL, "REPORT_TPGS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xb0 -- 0xbf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xc0 -- 0xcf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xd0 -- 0xdf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xe0 -- 0xef */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xf0 -- 0xff */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, +}; diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_sbc.h b/usr/src/cmd/iscsi/iscsitgtd/t10_sbc.h new file mode 100644 index 0000000000..af1404d3c4 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_sbc.h @@ -0,0 +1,188 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _T10_SBC_H +#define _T10_SBC_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SBC-2 specific structures and defines + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SBC_CAPACITY_PMI 0x01 + +#define SBC_SYNC_CACHE_IMMED 0x02 +#define SBC_SYNC_CACHE_NV 0x04 + +/* + * SBC-2 revision 16, section 5.17 START_STOP + * Table 49 -- POWER CONDITION field + */ +#define SBC_PWR_MASK 0xf0 +#define SBC_PWR_SHFT 4 +#define SBC_PWR_START_VALID 0x00 +#define SBC_PWR_ACTIVE 0x01 +#define SBC_PWR_IDLE 0x02 +#define SBC_PWR_STANDBY 0x03 +#define SBC_PWR_OBSOLETE 0x05 /* JIST checks this one */ +#define SBC_PWR_LU_CONTROL 0x07 +#define SBC_PWR_FORCE_IDLE_0 0x0a +#define SBC_PWR_FORCE_STANDBY_0 0x0b +#define SBC_PWR_LOEJ 0x02 +#define SBC_PWR_START 0x01 + +typedef struct disk_params { + /* + * Size of LUN in blocks + */ + diskaddr_t d_size; + + /* + * Number of bytes per section. This will probably + * Become vary important for the T10 Data Integrity + * Stuff. + */ + uint32_t d_bytes_sect; + uint32_t d_heads, + d_spt, + d_cyl; + /* + * Another bogus values. + */ + uint32_t d_rpm, + d_interleave; + + /* + * This lock protects access to both the d_mmap_overlaps AVL tree + * and use of the reserved disk_io buffer when memory is exhausted. + */ + pthread_mutex_t d_mutex; + + /* + * When using mmap backing store it's possible for an application + * to issue a read and a write command of the same block without + * first waiting for the read to complete. Since we pass the address + * to the mmap area back to the transport and continue it's possible + * to start processing the write command which will change the data + * before the read has completed. To prevent this condition read + * commands store their data address and length into the avl tree. + * The write command will see if it's potential address is the same + * as one of the read commands. If so, the write command will pause + * until the read completes. + */ + avl_tree_t d_mmap_overlaps; + pthread_cond_t d_mmap_cond; + Boolean_t d_mmap_paused; + + pthread_cond_t d_io_cond; + Boolean_t d_io_need; + Boolean_t d_io_used; + struct disk_io *d_io_reserved; + + Boolean_t d_fast_write; + t10_lu_state_t d_state; + + t10_lu_impl_t *d_reserve_owner; +} disk_params_t; + +typedef struct disk_io { + /* + * This structure needs to be the first member. The first member + * of this structure is an aio_result_t. If we need to issue + * an aio request a generic handler is called for all aio requests. + * To allow this generic handler a means to callback to the appropriate + * emulation routines a generic header is used. The generic handler + * can cast the pointer returned from aiowait to a t10_aio_t structure. + * From there it can determine the call back routine and pass it + * a specific pointer. + */ + t10_aio_t da_aio; + + /* + * Communication with the SAM-3 layer requires us to send back this + * pointer which was passed in at the command start. + */ + t10_cmd_t *da_cmd; + + /* + * (1) During AIO operations we need to allocate space to hold the + * data. This pointer represents that data which will be freed + * from our callback (argument to trans_send_datain) after the + * transport has finished with it. + * (2) During mmap ops the memory address of the requested data block + * will be stored here along with the transfer size. This will + * be used by the overlap protection to see if we must hold + * off a write op. + */ + char *da_data; + size_t da_data_len; + + /* + * True if da_data has been malloc'd verses mmap and therefore + * we need to free it when the free routine is called. + */ + Boolean_t da_data_alloc; + + /* + * True if an overlap value was stored which needs to be cleared. + */ + Boolean_t da_clear_overlap; + + /* + * If we're breaking up the transfer to comply with max_out + * then da_offset indicates where in the transfer we're currently + * at. + */ + uint64_t da_offset; + + /* + * This is the LBA of a SCSI READ or WRITE command. Once decoded + * from the cdb we don't want to recompute it each time it's needed + * in the different phases. + */ + diskaddr_t da_lba; + size_t da_lba_cnt; + + disk_params_t *da_params; + + /* + * Normal command overlap protection is done by the SAM-3 layer. + * This overlap is to prevent a write op from changing data before + * an existing read op has transmitted the data. + */ + avl_node_t da_mmap_overlap; +} disk_io_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _T10_SBC_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_spc.c b/usr/src/cmd/iscsi/iscsitgtd/t10_spc.c new file mode 100644 index 0000000000..c8206c3273 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_spc.c @@ -0,0 +1,1121 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * []------------------------------------------------------------------[] + * | SPC-3 Support Functions | + * | These routines are not directly called by the SAM-3 layer. Those | + * | who write device emulation modules are free to call these routine | + * | to carry out housekeeping chores. | + * []------------------------------------------------------------------[] + */ + +#include <sys/types.h> +#include <sys/asynch.h> +#include <sys/param.h> +#include <strings.h> +#include <unistd.h> + +#include <sys/scsi/generic/sense.h> +#include <sys/scsi/generic/status.h> +#include <sys/scsi/generic/inquiry.h> +#include <sys/scsi/generic/mode.h> +#include <sys/scsi/generic/commands.h> + +#include "t10.h" +#include "t10_spc.h" +#include "target.h" + +static void spc_free(emul_handle_t id); + +/* + * []---- + * | spc_unsupported -- generic routine to indicate we don't support this cmd + * []---- + */ +/*ARGSUSED*/ +void +spc_unsupported(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + char debug[80]; + + (void) snprintf(debug, sizeof (debug), + "SAM%d LUN%d Command 0x%x (%s) unsupported\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + cdb[0], cmd->c_lu->l_cmd_table[cdb[0]].cmd_name != NULL ? + cmd->c_lu->l_cmd_table[cdb[0]].cmd_name : "No description"); + queue_str(mgmtq, Q_STE_ERRS, msg_log, debug); + + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, 0x20, 0x00); + trans_send_complete(cmd, STATUS_CHECK); +} + +/* + * []---- + * | spc_tur -- test unit ready + * []---- + */ +/*ARGSUSED*/ +void +spc_tur(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /* + * SPC-3 Revision 21c, section 6.31 + * Reserve bit checks + */ + if (cdb[1] || cdb[2] || cdb[3] || cdb[4] || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + } else + trans_send_complete(cmd, STATUS_GOOD); +} + +/* + * []---- + * | spc_request_sense -- + * []---- + */ +/*ARGSUSED*/ +void +spc_request_sense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /* ---- Check for reserved bit conditions ---- */ + if ((cdb[1] & 0xfe) || cdb[2] || cdb[3] || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + } else { + /* + * Since we always run with autosense enabled there's + * no sense data to return. That may change in the future + * if we decide to add such support, but for now return + * success always. + */ + spc_sense_create(cmd, 0, 0); + trans_send_complete(cmd, STATUS_GOOD); + } +} + +/* + * []---- + * | spc_inquiry -- Standard INQUIRY command + * []---- + */ +/*ARGSUSED*/ +void +spc_inquiry(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + uint8_t *rsp_buf, + *rbp; /* temporary var */ + struct scsi_inquiry *inq; + uint32_t len, + page83_len, + rqst_len, + rtn_len; + struct vpd_hdr *vhp; + struct vpd_desc vd; + size_t scsi_len; + t10_lu_common_t *lu = cmd->c_lu->l_common; + void *v; + uint16_t *vdv; + + /* + * Information obtained from: + * SPC-3 Revision 21c + * Section 6.4.1 INQUIRY command + * Need to generate a CHECK CONDITION with ILLEGAL REQUEST + * and INVALID FIELD IN CDB (0x24/0x00) if any of the following is + * true. + * (1) If the EVPD bit is not set, then the page code must be zero. + * (2) If any bit other than EVPD is set. + * (3) If any of the reserved bits in the CONTROL byte are set. + */ + if (((cdb[1] == 0) && (cdb[2] != 0)) || (cdb[1] & ~1) || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + rqst_len = cdb[3] << 8 | cdb[4]; + /* + * Zero length is not an error and we should just acknowledge + * the operation. + */ + if (rqst_len == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + /* + * We send back a total of six Vital Product Data descriptors + * plus associated data. Three are EUI values, two are for AULA + * support being simple 4 byte values, and one is the IQN string. + * NOTE: The IQN string is just an artifact of when the target + * was created. Once FC support is added, the t_name would be + * either a "naa." or "eui." string. + */ + scsi_len = ((strlen(cmd->c_lu->l_targ->s_targ_base) + 1) + 3) & ~3; + page83_len = (sizeof (struct vpd_desc) * 6) + scsi_len + + (lu->l_guid_len * 3) + (sizeof (uint32_t) * 2); + + /* + * We always allocate enough space so that the code can create + * either the full inquiry data or page 0x83 data. + */ + len = sizeof (struct vpd_hdr) + page83_len; + len = max(rqst_len, max(sizeof (*inq), len)); + + /* + * Allocate space with an alignment that will work for any casting. + */ + if ((v = memalign(sizeof (void *), len)) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + bzero(v, len); + rsp_buf = (uint8_t *)v; + + /* + * EVPD not set returns the standard inquiry data. + */ + if (cdb[1] == 0) { + /* + * Return whatever is the smallest amount between the + * INQUIRY data or the amount requested. + */ + rtn_len = min(rqst_len, sizeof (*inq)); + + inq = (struct scsi_inquiry *)rsp_buf; + /* + * SPC-3 Revision 21c, Section 6.4.2 .. Table 82 -- Version + * This target will comply with T10/1416-D + */ + inq->inq_ansi = SPC_INQ_VERS_SPC_3; + + inq->inq_len = sizeof (*inq) - 4; + inq->inq_dtype = lu->l_dtype; + inq->inq_normaca = 0; + inq->inq_rdf = SPC_INQ_RDF; + inq->inq_cmdque = 1; + inq->inq_linked = 0; + + /* + * JIST Requires that we show support for hierarchical + * support (HiSup). + */ + inq->inq_hisup = 1; + + /* + * SPC-4, revision 1a, section 6.4.2 + * Stand INQUIRY Data + * To support MPxIO we enable ALUA support and identify + * all paths to this device as being active/optimized + * we defaults to symmertrical devices. + */ + inq->inq_tpgs = 1; + + (void) snprintf(inq->inq_vid, + sizeof (inq->inq_vid) + sizeof (inq->inq_pid) + + sizeof (inq->inq_revision), "%-8s%-16s%-4s", + lu->l_vid, lu->l_pid, + DEFAULT_REVISION); + + /* + * SPC-3 Revision 21c, section 6.4.2 + * Table 85 -- Version Descriptor values + * Starting at byte 58 there are up to 8 version + * descriptors which are 16bits in size. + * + * NOTE: The ordering of these values is according + * to the standard. First comes the architectural + * version and then followed by: physical transport, + * SCSI transport, primary command set version, and + * finally the device type command set. + */ + vdv = &((uint16_t *)v)[29]; + + /* SAM-3 T10/1561-D rev 14 */ + *vdv++ = htons(SPC_INQ_VD_SAM3); + + /* physical transport code unknown */ + *vdv++ = htons(0x0000); + + *vdv++ = htons(cmd->c_lu->l_targ->s_trans_vers); + + /* SPC ANSI X3.301:1997 */ + *vdv++ = htons(SPC_INQ_VD_SPC3); + + if (lu->l_dtype == DTYPE_DIRECT) { + /* SBC-2 T10/1417-D rev 5a */ + *vdv++ = htons(SPC_INQ_VD_SBC2); + } else if (lu->l_dtype == DTYPE_SEQUENTIAL) { + /* SSC-2 (no version) */ + *vdv++ = htons(SPC_INQ_VD_SSC3); + } else if (lu->l_dtype == DTYPE_OSD) { + /* OSD T10/1355-D revision 10 */ + *vdv++ = htons(SPC_INQ_VD_OSD); + } + + } else { + + /* + * Return the smallest amount of data between the requested + * amount and the size of the Page83 data. + */ + rtn_len = min(rqst_len, sizeof (struct vpd_hdr) + page83_len); + + /* ---- Common information returned with all page types ---- */ + rsp_buf[0] = lu->l_dtype; + rsp_buf[1] = cdb[2]; + + switch (cdb[2]) { + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + + case SPC_INQ_PAGE0: + /* + * SPC-3 Revision 21c Section 7.6.10 + * EVPD page 0 returns information about which pages + * are supported. We support page 0x00 and 0x83. + * NOTE: The value found in byte[3] is the returned + * page length as defined by (n - 3) where 'n' is + * the last valid byte. In this case 5. + */ + rsp_buf[3] = 2; + rsp_buf[4] = SPC_INQ_PAGE0; + rsp_buf[5] = SPC_INQ_PAGE83; + break; + + case SPC_INQ_PAGE83: + /* + * Information obtained from: + * SPC-3 Revision 21c + * Section 7.6.4.1 + */ + + /* ---- VPD header ---- */ + vhp = (struct vpd_hdr *)v; + vhp->device_type = lu->l_dtype; + vhp->periph_qual = SPC_INQUIRY_PERIPH_CONN; + vhp->page_code = cdb[2]; + vhp->page_len[0] = hibyte(loword(page83_len)); + vhp->page_len[1] = lobyte(loword(page83_len)); + rbp = (uint8_t *)v + sizeof (*vhp); + + /* ---- VPD descriptor ---- */ + /* + * SPC-4 revision 1a, section 7.6.3.8 + * Target port group designator format + */ + vd.code_set = SPC_INQUIRY_CODE_SET_BINARY; + vd.id_type = SPC_INQUIRY_ID_TYPE_TARG_PORT; + vd.proto_id = SPC_INQUIRY_PROTOCOL_ISCSI; + vd.association = SPC_INQUIRY_ASSOC_TARGPORT; + vd.piv = 1; + vd.len = 4; + bcopy(&vd, rbp, sizeof (vd)); + rbp += sizeof (vd); + + len = SPC_DEFAULT_TPG; + rbp[2] = hibyte(loword(len)); + rbp[3] = lobyte(loword(len)); + rbp += vd.len; + + /* ---- VPD descriptor ---- */ + /* + * SPC-4, revision 1a, section 7.6.3.7 + * Relative target port designator format + */ + vd.code_set = SPC_INQUIRY_CODE_SET_BINARY; + vd.id_type = SPC_INQUIRY_ID_TYPE_RELATIVE; + vd.proto_id = SPC_INQUIRY_PROTOCOL_ISCSI; + vd.association = SPC_INQUIRY_ASSOC_TARGPORT; + vd.piv = 1; + vd.len = 4; + bcopy(&vd, rbp, sizeof (vd)); + rbp += sizeof (vd); + + rbp[2] = hibyte(loword(cmd->c_lu->l_targ->s_tp_grp)); + rbp[3] = lobyte(loword(cmd->c_lu->l_targ->s_tp_grp)); + rbp += vd.len; + + /* ---- VPD descriptor ---- */ + vd.code_set = SPC_INQUIRY_CODE_SET_BINARY; + vd.id_type = SPC_INQUIRY_ID_TYPE_EUI; + vd.proto_id = SPC_INQUIRY_PROTOCOL_ISCSI; + vd.association = SPC_INQUIRY_ASSOC_LUN; + vd.piv = 1; + vd.len = lu->l_guid_len; + bcopy(&vd, rbp, sizeof (vd)); + rbp += sizeof (vd); + + bcopy(lu->l_guid, &rbp[0], lu->l_guid_len); + rbp += lu->l_guid_len; + + /* ---- VPD descriptor ---- */ + vd.code_set = SPC_INQUIRY_CODE_SET_UTF8; + vd.id_type = SPC_INQUIRY_ID_TYPE_SCSI; + vd.proto_id = SPC_INQUIRY_PROTOCOL_ISCSI; + vd.association = SPC_INQUIRY_ASSOC_LUN; + vd.piv = 1; + vd.len = scsi_len; + bcopy(&vd, rbp, sizeof (vd)); + rbp += sizeof (vd); + + /* + * SPC-3 revision 23, section 7.6.3.11 + * Use the string length and not the scsi_len because + * we've rounded up the length to a multiple of four + * as required by the specification. + */ + bcopy(cmd->c_lu->l_targ->s_targ_base, &rbp[0], + strlen(cmd->c_lu->l_targ->s_targ_base)); + rbp += vd.len; + + /* ---- VPD descriptor ---- */ + vd.code_set = SPC_INQUIRY_CODE_SET_BINARY; + vd.id_type = SPC_INQUIRY_ID_TYPE_EUI; + vd.proto_id = SPC_INQUIRY_PROTOCOL_ISCSI; + vd.association = SPC_INQUIRY_ASSOC_TARG; + vd.piv = 1; + vd.len = lu->l_guid_len; + bcopy(&vd, rbp, sizeof (vd)); + rbp += sizeof (vd); + + /* + * XXX Is this right XXX + * Should we be using some other name. + */ + bcopy(lu->l_guid, &rbp[0], lu->l_guid_len); + rbp += lu->l_guid_len; + + /* ---- VPD descriptor ---- */ + vd.code_set = SPC_INQUIRY_CODE_SET_BINARY; + vd.id_type = SPC_INQUIRY_ID_TYPE_EUI; + vd.proto_id = SPC_INQUIRY_PROTOCOL_ISCSI; + vd.association = SPC_INQUIRY_ASSOC_TARGPORT; + vd.piv = 1; + vd.len = lu->l_guid_len; + bcopy(&vd, rbp, sizeof (vd)); + rbp += sizeof (vd); + + /* + * XXX Is this right XXX + * Should we be using some other name. + */ + bcopy(lu->l_guid, &rbp[0], lu->l_guid_len); + + /* + * rbp is updated here even though nobody will + * currently use it. Currently is the optertive word + * here. If for some reason we add another VDP + * then the pointer will be correct. + */ + rbp += lu->l_guid_len; + + break; + } + } + + if (trans_send_datain(cmd, (char *)rsp_buf, rtn_len, 0, spc_free, + True, rsp_buf) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/* + * []---- + * | spc_mselect -- Generic MODE SELECT command + * []---- + */ +/*ARGSUSED*/ +void +spc_mselect(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + char *buf; + + /* + * SPC-3 revision 21c, section 6.7 + * Reserve bit checks + */ + if ((cdb[1] & 0xee) || cdb[2] || cdb[3] || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if (cdb[4] == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + if (((buf = (char *)calloc(1, cdb[4])) == NULL) || + (trans_rqst_dataout(cmd, buf, cdb[4], 0, 0) == False)) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/* + * []---- + * | spc_mselect_data -- DataIn phase of MODE SELECT command + * []---- + */ +/*ARGSUSED*/ +void +spc_mselect_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + struct mode_control_scsi3 mode_ctl_page; + struct mode_header hdr; + + bcopy(data, &hdr, sizeof (hdr)); + bcopy(data + sizeof (hdr) + hdr.bdesc_length, &mode_ctl_page, + sizeof (mode_ctl_page)); + + switch (mode_ctl_page.mode_page.code) { + case MODE_SENSE_CONTROL: + /* + * SPC-3 revision 21c, section 7.4.6 + * Table 239 describes the fields. We're only interested + * in the descriptor sense bit. + */ + if (mode_ctl_page.d_sense == 1) { + cmd->c_lu->l_dsense_enabled = True; + } else { + cmd->c_lu->l_dsense_enabled = False; + } + break; + + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + break; + } + free(data); + trans_send_complete(cmd, STATUS_GOOD); +} + +/* + * []---- + * | spc_report_luns -- + * []---- + */ +/*ARGSUSED*/ +void +spc_report_luns(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + int expected_data; + uint8_t *buf = NULL; + int entries = 0, + len, + len_network, + select, + lun_idx, + lun_val; + char *str; + xml_node_t *targ, + *lun_list, + *lun; + + /* + * SPC-3 Revision 21c section 6.21 + * Error checking. + */ + if (cdb[1] || cdb[3] || cdb[4] || + (cdb[2] & ~SPC_RPT_LUNS_SELECT_MASK) || cdb[5] || cdb[10] || + SAM_CONTROL_BYTE_RESERVED(cdb[11])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + expected_data = cdb[6] << 24 | cdb[7] << 16 | cdb[8] << 8 | cdb[9]; + if (expected_data < 16) { + /* + * The allocation length should be at least 16 according + * to SPC-3. + */ + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + select = cdb[2]; + + targ = NULL; + while ((targ = xml_node_next(targets_config, XML_ELEMENT_TARG, targ)) != + NULL) { + if (xml_find_value_str(targ, XML_ELEMENT_INAME, &str) == + False) { + goto error; + } + if (strcmp(str, cmd->c_lu->l_targ->s_targ_base) == 0) { + free(str); + break; + } else + free(str); + } + if (!targ) + goto error; + if ((lun_list = xml_node_next(targ, XML_ELEMENT_LUNLIST, NULL)) == NULL) + goto error; + + lun = NULL; + while ((lun = xml_node_next(lun_list, XML_ELEMENT_LUN, lun)) != NULL) + entries++; + + + len = entries * SCSI_REPORTLUNS_ADDRESS_SIZE; + if ((buf = (uint8_t *)calloc(1, MAX(expected_data, len))) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + + len_network = htonl(len); + bcopy(&len_network, buf, sizeof (len_network)); + + if (expected_data >= (len + SCSI_REPORTLUNS_ADDRESS_SIZE)) { + + lun_idx = SCSI_REPORTLUNS_ADDRESS_SIZE; + lun = NULL; + while ((lun = xml_node_next(lun_list, XML_ELEMENT_LUN, lun)) != + NULL) { + if (xml_find_value_int(lun, XML_ELEMENT_LUN, + &lun_val) == False) + goto error; + if (spc_encode_lu_addr(&buf[lun_idx], select, + lun_val) == False) + continue; + lun_idx += SCSI_REPORTLUNS_ADDRESS_SIZE; + } + if (trans_send_datain(cmd, (char *)buf, expected_data, 0, + spc_free, True, buf) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } + } else { + /* + * This will return the size needed to complete this request + * and a implicit LUN zero. + */ + if (trans_send_datain(cmd, (char *)buf, 16, 0, spc_free, True, + buf) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } + } + return; + +error: + if (buf) + free(buf); + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); +} + +/*ARGSUSED*/ +void +spc_report_tpgs(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + rtpg_hdr_t *r; + rtpg_desc_t *dp; + rtpg_targ_desc_t *tp; + int rqst_len, + alloc_len, + i; + t10_lu_common_t *lu = cmd->c_lu->l_common; + t10_lu_impl_t *lu_per; + + if (disable_tpgs == True) { + spc_unsupported(cmd, cdb, cdb_len); + return; + } + + /* + * Reserve bit checks + */ + if ((cdb[1] & 0xe0) || (cdb[1] & ~SPC_MI_SVC_MASK) || + ((cdb[1] & SPC_MI_SVC_MASK) != SPC_MI_SVC_RTPG) || + cdb[2] || cdb[3] || cdb[4] || cdb[5] || + cdb[10] || SAM_CONTROL_BYTE_RESERVED(cdb[11])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + /* + * We only have one target port group which it's size is + * accounted for in rtpg_hdr_t. Take the number of tgts + * and subtract one since the first is accounted for in + * rtpg_targ_desc_t + */ + alloc_len = ((avl_numnodes(&lu->l_all_open) - 1) * + sizeof (rtpg_targ_desc_t)) + sizeof (rtpg_hdr_t); + + /* + * Make sure that we have enough room to store everything + * that we want to, but only returned the requested about + * of data which is why d->d_len is set to the request amount. + * A client could issue a REPORT_TPGS with a length of 4 bytes + * which would be just enough to see how much space is actually + * used. + */ + rqst_len = cdb[6] << 24 | cdb[7] << 16 | + cdb[8] << 8 | cdb[9]; + + if (rqst_len == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + if ((r = (rtpg_hdr_t *)calloc(1, alloc_len)) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + + i = alloc_len - sizeof (r->len); + r->len[0] = hibyte(hiword(i)); + r->len[1] = lobyte(hiword(i)); + r->len[2] = hibyte(loword(i)); + r->len[3] = lobyte(loword(i)); + dp = &r->desc_list[0]; + dp->tpg_cnt = avl_numnodes(&lu->l_all_open); + dp->status_code = 0; + dp->access_state = 0; /* Active/optimized */ + dp->pref = 1; + dp->t_sup = 1; + dp->u_sup = 1; + dp->s_sup = 1; + dp->an_sup = 0; + dp->ao_sup = 1; + i = SPC_DEFAULT_TPG; + dp->tpg[0] = hibyte(loword(i)); + dp->tpg[1] = lobyte(loword(i)); + + tp = &dp->targ_list[0]; + lu_per = avl_first(&lu->l_all_open); + do { + tp->rel_tpi[0] = hibyte(loword(lu_per->l_targ->s_tp_grp)); + tp->rel_tpi[1] = lobyte(loword(lu_per->l_targ->s_tp_grp)); + lu_per = AVL_NEXT(&lu->l_all_open, lu_per); + } while (lu_per != NULL); + + if (trans_send_datain(cmd, (char *)r, MIN(rqst_len, alloc_len), 0, + spc_free, True, (char *)r) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +void +spc_send_diag(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + /* + * SPC-3 Revision 21c, section 6.27 + * Reserve bit checks + */ + if ((cdb[1] & ~SPC_SEND_DIAG_SELFTEST) || cdb[2] || cdb[3] || cdb[4] || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + /* + * There's no diagnostics to be run at this time. So, always + * return success. If, at some point in the future, it's determined + * that something can be done which is meaningful then place the + * code here. + */ + trans_send_complete(cmd, STATUS_GOOD); +} + +static void +spc_free(emul_handle_t id) +{ + free(id); +} + +/* + * []---- + * | spc_cmd_offline -- return IN_PROGRESS for media related commands + * | + * | During LU initialization the device is in an offline state. When + * | offlined only non-media related commands are allowed to proceed. + * | TEST_UNIT_READY is considered a media command since it must return + * | a CHECK_CONDITION if a media command would do so. + * []---- + */ +void +spc_cmd_offline(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + scsi_cmd_table_t *e; + int old_dtype; + + e = &cmd->c_lu->l_cmd_table[cdb[0]]; + + switch (cdb[0]) { + case SCMD_TEST_UNIT_READY: + case SCMD_START_STOP: + case SCMD_READ: + case SCMD_READ_G1: + case SCMD_READ_G4: + case SCMD_WRITE: + case SCMD_WRITE_G1: + case SCMD_WRITE_G4: +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "SPC%x LUN%d Cmd %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name == NULL ? "(no name)" : e->cmd_name); +#endif + spc_sense_create(cmd, KEY_NOT_READY, 0); + spc_sense_ascq(cmd, SPC_ASC_IN_PROG, SPC_ASCQ_IN_PROG); + trans_send_complete(cmd, STATUS_CHECK); + break; + + case SCMD_INQUIRY: + /* + * While the device is being initialized any inquiry commands + * will return an unknown device type. This will cause the + * transport to hold off further plumbing until the + * initialization is complete and we send the inventory change + * notice. + */ + old_dtype = cmd->c_lu->l_common->l_dtype; + cmd->c_lu->l_common->l_dtype = 0x1f; + (*e->cmd_start)(cmd, cdb, cdb_len); + cmd->c_lu->l_common->l_dtype = old_dtype; + break; + + default: + (*e->cmd_start)(cmd, cdb, cdb_len); + break; + } +} + +/* + * []---- + * | spc_sense_create -- allocate sense structure + * | + * | If additional header sense length is requested and the I_T_Q has + * | enabled descriptor sense data allocate the space. + * []---- + */ +void +spc_sense_create(t10_cmd_t *cmd, int sense_key, int addl_sense_len) +{ + char *buf; + int size; + + /* + * It's possible under certain conditions -- namely a malloc error + * when setting up a command -- that the pointer to the ITL structure + * isn't valid. If that's the case don't attempt to dereference it + * to look at the dsense_enabled flag. + */ + if ((cmd->c_lu != NULL) && (cmd->c_lu->l_dsense_enabled == True)) { + struct scsi_descr_sense_hdr d; + + if ((buf = (char *)calloc(1, + sizeof (d) + 2 + addl_sense_len)) == NULL) + return; + + size = sizeof (d) + addl_sense_len; + bzero(&d, sizeof (d)); + d.ds_class = CLASS_EXTENDED_SENSE; + d.ds_code = CODE_FMT_DESCR_CURRENT; + d.ds_key = sense_key; + d.ds_addl_sense_length = addl_sense_len; + bcopy(&d, &buf[2], sizeof (d)); + + } else { + struct scsi_extended_sense e; + + if ((buf = (char *)calloc(1, sizeof (e) + 2)) == NULL) + return; + size = sizeof (e); + bzero(&e, sizeof (e)); + e.es_class = CLASS_EXTENDED_SENSE; + e.es_code = CODE_FMT_FIXED_CURRENT; + e.es_key = sense_key; + bcopy(&e, &buf[2], size); + + } + + /* ---- First two bytes of the sense store the length ---- */ + buf[0] = hibyte(loword(size)); + buf[1] = lobyte(loword(size)); + + cmd->c_cmd_sense = buf; + cmd->c_cmd_sense_len = size + 2; +} + +/* + * []---- + * | spc_sense_raw -- copy an existing sense buffer for return. + * | + * | If an emulation module already has a sense buffer there's no need + * | to decode the sense data and call the various spc_sense_ routines + * | to reencode the information. + * []---- + */ +void +spc_sense_raw(t10_cmd_t *cmd, uchar_t *sense_buf, size_t sense_len) +{ + ushort_t s = (ushort_t)sense_len; + if ((cmd->c_cmd_sense = malloc(sense_len + 2)) == NULL) + return; + bcopy(sense_buf, &cmd->c_cmd_sense[2], sense_len); + cmd->c_cmd_sense[0] = hibyte(s); + cmd->c_cmd_sense[1] = lobyte(s); + cmd->c_cmd_sense_len = s + 2; +} + +void +spc_sense_ascq(t10_cmd_t *cmd, int asc, int ascq) +{ + struct scsi_extended_sense s; + struct scsi_descr_sense_hdr d; + + bcopy(&cmd->c_cmd_sense[2], &s, sizeof (s)); + + switch (s.es_code) { + case CODE_FMT_DESCR_CURRENT: + case CODE_FMT_DESCR_DEFERRED: + bcopy(&cmd->c_cmd_sense[2], &d, sizeof (d)); + d.ds_add_code = asc; + d.ds_qual_code = ascq; + bcopy(&d, &cmd->c_cmd_sense[2], sizeof (d)); + break; + + default: + s.es_add_code = asc; + s.es_qual_code = ascq; + bcopy(&s, &cmd->c_cmd_sense[2], sizeof (s)); + break; + } +} + +void +spc_sense_info(t10_cmd_t *cmd, uint64_t info) +{ + struct scsi_information_sense_descr isd; + struct scsi_extended_sense s; + char *p; + uint32_t fixed_info = (uint32_t)info; + + switch (cmd->c_cmd_sense[2] & 0x0f) { + case CODE_FMT_DESCR_CURRENT: + case CODE_FMT_DESCR_DEFERRED: + isd.isd_descr_type = DESCR_INFORMATION; + isd.isd_addl_length = 0x0a; + isd.isd_valid = 1; + isd.isd_information[0] = (info >> 56) & 0xff; + isd.isd_information[1] = (info >> 48) & 0xff; + isd.isd_information[2] = (info >> 40) & 0xff; + isd.isd_information[3] = (info >> 32) & 0xff; + isd.isd_information[4] = (info >> 24) & 0xff; + isd.isd_information[5] = (info >> 16) & 0xff; + isd.isd_information[6] = (info >> 8) & 0xff; + isd.isd_information[7] = info & 0xff; + p = &cmd->c_cmd_sense[2] + sizeof (struct scsi_descr_sense_hdr); + bcopy(&isd, p, sizeof (isd)); + break; + + case CODE_FMT_VENDOR_SPECIFIC: + case CODE_FMT_FIXED_CURRENT: + case CODE_FMT_FIXED_DEFERRED: + default: + bcopy(&cmd->c_cmd_sense[2], &s, sizeof (s)); + + s.es_valid = 1; + if (info > FIXED_SENSE_ADDL_INFO_LEN) { + s.es_info_1 = 0xff; + s.es_info_2 = 0xff; + s.es_info_3 = 0xff; + s.es_info_4 = 0xff; + } else { + s.es_info_1 = hibyte(hiword(fixed_info)); + s.es_info_2 = lobyte(hiword(fixed_info)); + s.es_info_3 = hibyte(loword(fixed_info)); + s.es_info_4 = lobyte(loword(fixed_info)); + } + bcopy(&s, &cmd->c_cmd_sense[2], sizeof (s)); + break; + } +} + +void +spc_sense_flags(t10_cmd_t *cmd, int flags) +{ + struct scsi_extended_sense s; + + bcopy(&cmd->c_cmd_sense[2], &s, sizeof (s)); + if (flags & SPC_SENSE_EOM) + s.es_eom = 1; + if (flags & SPC_SENSE_FM) + s.es_filmk = 1; + if (flags & SPC_SENSE_ILI) + s.es_ili = 1; + bcopy(&s, &cmd->c_cmd_sense[2], sizeof (s)); +} + +/* + * []---- + * | spc_decode_lu_addr -- Decodes LU addressing as specified in SAM-3 + * []---- + */ +Boolean_t +spc_decode_lu_addr(uint8_t *buf, int len, uint32_t *val) +{ + uint32_t lun; + + if (len < 2) + return (False); + + switch (buf[0] & SCSI_REPORTLUNS_ADDRESS_MASK) { + case SCSI_REPORTLUNS_ADDRESS_PERIPHERAL: + case SCSI_REPORTLUNS_ADDRESS_FLAT_SPACE: + lun = ((buf[0] & 0x3f) << 8) | (buf[1] & 0xff); + break; + + /* + * Since we never encode a LUN using this method, we + * shouldn't receive it back. + */ + case SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT: + return (False); + + case SCSI_REPORTLUNS_ADDRESS_EXTENDED_UNIT: + switch (buf[0] & SCSI_REPORTLUNS_ADDRESS_EXTENDED_MASK) { + case SCSI_REPORTLUNS_ADDRESS_EXTENDED_2B: + lun = buf[1] & 0xff; + break; + + case SCSI_REPORTLUNS_ADDRESS_EXTENDED_4B: + if (len < 4) + return (False); + lun = buf[1] << 16 | buf[2] << 8 | (buf[3] & 0xff); + break; + + case SCSI_REPORTLUNS_ADDRESS_EXTENDED_6B: + if (len < 6) + return (False); + /* + * This should be able to handle a 40-bit LUN, + * but since our LUNs are only 32-bit we don't + * bother to decode buf[1]. This is okay since + * we generate the LUN in the first place. + */ + lun = buf[2] << 24 | buf[3] << 16 | + buf[4] << 8 | (buf[5] & 0xff); + break; + + case SCSI_REPORTLUNS_ADDRESS_EXTENDED_8B: + /* + * Since we current don't support larger than + * 32-bit LUNs we'll never create an extended + * address using this format. So, if we are to + * get this format in, just return an error. + */ + return (False); + + break; + } + } + *val = lun; + return (True); +} + +/* + * []---- + * | spc_encode_lu_addr -- encode, based on SAM-3/SPC-3 specs, a LUN + * | + * | NOTE: This routine only deals with 32-bit logical unit numbers. + * | If this program ever switches to using larger values we need to + * | simply deal with those formats. + * []---- + */ +Boolean_t +spc_encode_lu_addr(uint8_t *buf, int select_field, uint32_t lun) +{ + if (lun < 256) { + + /* + * SAM-3 revision 14, Section 4.9.6. + * No bus identifier for our luns. + */ + buf[0] = SCSI_REPORTLUNS_ADDRESS_PERIPHERAL; + buf[1] = lun; + + } else if (lun < 0x3fff) { + + /* + * SAM-3 revision 14, Section 4.9.7. + * 14-bit flat address space. + */ + buf[0] = SCSI_REPORTLUNS_ADDRESS_FLAT_SPACE | + (lun >> 8 & 0x3f); + buf[1] = lun & 0xff; + + } else if (select_field == SCSI_REPORTLUNS_SELECT_ALL) { + + buf[0] = SCSI_REPORTLUNS_ADDRESS_EXTENDED_UNIT | + SCSI_REPORTLUNS_ADDRESS_EXTENDED_6B; + /* + * 32-bit limitation. This format should be able to + * handle a 40-bit LUN. + */ + buf[1] = 0; + buf[2] = lun >> 24 & 0xff; + buf[3] = lun >> 16 & 0xff; + buf[4] = lun >> 8 & 0xff; + buf[5] = lun & 0xff; + } else + /* + * Either the user hasn't requested extended unit addressing + * or the LU is greater than 32bits. Since internally we + * only have 32bit numbers it's more likely that the initiator + * hasn't selected a correct report format. + */ + return (False); + return (True); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_spc.h b/usr/src/cmd/iscsi/iscsitgtd/t10_spc.h new file mode 100644 index 0000000000..90ec0ac9f1 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_spc.h @@ -0,0 +1,362 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SPC_H +#define _SPC_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * []------------------------------------------------------------------[] + * | SPC-3 | + * []------------------------------------------------------------------[] + */ + +/* + * FIXED_SENSE_ADDL_INFO_LEN is the length of INFORMATION field + * in fixed format sense data + */ +#define FIXED_SENSE_ADDL_INFO_LEN 0xFFFFFFFF +#define INFORMATION_SENSE_DESCR sizeof (struct scsi_information_sense_descr) + +#include <sys/scsi/generic/mode.h> + +/* + * SPC Command Functions + */ +void spc_tur(struct t10_cmd *cmd, uint8_t *cdb, size_t cdb_len); +void spc_request_sense(struct t10_cmd *cmd, uint8_t *cdb, size_t cdb_len); +void spc_unsupported(struct t10_cmd *cmd, uint8_t *cdb, size_t cdb_len); +void spc_inquiry(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +void spc_mselect(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +void spc_mselect_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, + char *data, size_t data_len); +void spc_report_luns(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +void spc_report_tpgs(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +void spc_msense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +void spc_startstop(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +void spc_send_diag(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); + +/* + * SPC Support Functions + */ +void spc_cmd_offline(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +void spc_sense_create(struct t10_cmd *cmd, int sense_key, int addl_sense_len); +void spc_sense_ascq(struct t10_cmd *cmd, int asc, int ascq); +void spc_sense_info(t10_cmd_t *cmd, uint64_t info); +void spc_sense_flags(t10_cmd_t *cmd, int flags); +void spc_sense_raw(t10_cmd_t *cmd, uchar_t *sense_buf, size_t sense_len); +Boolean_t spc_decode_lu_addr(uint8_t *buf, int len, uint32_t *val); +Boolean_t spc_encode_lu_addr(uint8_t *buf, int select_field, uint32_t lun); + +/* + * SPC flags to use when setting various sense code flags + */ +#define SPC_SENSE_EOM 0x01 +#define SPC_SENSE_FM 0x02 +#define SPC_SENSE_ILI 0x04 + +#ifdef _BIG_ENDIAN +#define htonll(x) (x) +#define ntohll(x) (x) +#else +#define htonll(x) ((((unsigned long long)htonl(x & 0xffffffff)) << 32) + \ + htonl(x >> 32)) +#define ntohll(x) ((((unsigned long long)ntohl(x)) << 32) + ntohl(x >> 32)) +#endif + +/* + * []------------------------------------------------------------------[] + * | SPC-3, revision 21c -- ASC/ASCQ values | + * | The full tables can be found in Appendix D (numerical order) or | + * | section 4.5.6 (alphabetical order). There are close to fifteen | + * | pages of values which will not be included here. Only those used | + * | by the code. | + * []------------------------------------------------------------------[] + */ +#define SPC_ASC_INVALID_CDB 0x24 +#define SPC_ASCQ_INVALID_CDB 0x00 +#define SPC_ASC_PWR_RESET 0x29 +#define SPC_ASCQ_PWR_RESET 0x00 +#define SPC_ASC_PWR_ON 0x29 +#define SPC_ASCQ_PWR_ON 0x01 +#define SPC_ASC_BUS_RESET 0x29 +#define SPC_ASCQ_BUS_RESET 0x02 +#define SPC_ASC_FM_DETECTED 0x00 /* file-mark detected */ +#define SPC_ASCQ_FM_DETECTED 0x01 +#define SPC_ASC_EOP 0x00 /* end-of-partition/medium detected */ +#define SPC_ASCQ_EOP 0x02 +#define SPC_ASC_WRITE_ERROR 0x0c +#define SPC_ASCQ_WRITE_ERROR 0x00 +#define SPC_ASC_CAP_CHANGE 0x2a +#define SPC_ASCQ_CAP_CHANGE 0x09 +#define SPC_ASC_IN_PROG 0x04 +#define SPC_ASCQ_IN_PROG 0x07 + +/* + * []------------------------------------------------------------------[] + * | SAM-3, revision 14, section 5.2 - Command descriptor block (CDB) | + * | | + * | "All CDBs shall contain a CONTROL byte (see table 21). The | + * | location of the CONTROL byte within a CDB depends on the CDB | + * | format (see SPC-3)." | + * | | + * | bits meaning | + * | 6-7 vendor specific (we don't use so must be zero) | + * | 3-5 reserved must be zero | + * | 2 NACA (currently we don't support so must be zero) | + * | 1 Obsolete | + * | 0 Link (currently we don't support so must be zero) | + * | | + * | So, this means the control byte must be zero and therefore if | + * | this macro returns a non-zero value the emulation code should | + * | return a CHECK CONDITION with status set to ILLEGAL REQUEST | + * | and the additional sense code set to INVALID FIELD IN CDB. | + * | | + * | In the future this will likely change with support routines | + * | added for dealing with NACA and Linked commands. | + * []------------------------------------------------------------------[] + */ +#define SAM_CONTROL_BYTE_RESERVED(byte) (byte) + +/* ---- Disable Block Descriptors ---- */ +#define SPC_MODE_SENSE_DBD 0x8 + +#define SPC_GROUP4_SERVICE_ACTION_MASK 0x1f + +#define SPC_SEND_DIAG_SELFTEST 0x04 + +/* + * []------------------------------------------------------------------[] + * | SPC-3 revision 21c, section 6.4 -- INQUIRY | + * | Various defines. The structure for the inquiry command can be | + * | found in /usr/include/sys/scsi/generic/inquiry.h | + * []------------------------------------------------------------------[] + */ +#define SPC_INQUIRY_CODE_SET_BINARY 1 +#define SPC_INQUIRY_CODE_SET_ASCII 2 +#define SPC_INQUIRY_CODE_SET_UTF8 3 + +/* ---- Table 82: Inquiry Version ---- */ +#define SPC_INQ_VERS_NONE 0x00 +#define SPC_INQ_VERS_OBSOLETE 0x02 +#define SPC_INQ_VERS_SPC_1 0x03 +#define SPC_INQ_VERS_SPC_2 0x04 +#define SPC_INQ_VERS_SPC_3 0x05 + +/* ---- INQUIRY Response Data Format field ---- */ +#define SPC_INQ_RDF 0x02 /* all other values are OBSOLETE */ + +/* + * Table 85 -- Version descriptor values + * There are many, many different values available, so we'll only include + * those that we actually use. + */ +#define SPC_INQ_VD_SAM3 0x0076 +#define SPC_INQ_VD_SPC3 0x0307 +#define SPC_INQ_VD_SBC2 0x0322 +#define SPC_INQ_VD_SSC3 0x0400 +#define SPC_INQ_VD_OSD 0x0355 + +#define SPC_INQ_PAGE0 0x00 +#define SPC_INQ_PAGE83 0x83 + +/* ---- REPORT LUNS select report has valid values of 0, 1, or 2 ---- */ +#define SPC_RPT_LUNS_SELECT_MASK 0x03 + +/* ---- Table 293: IDENTIFIER TYPE field ---- */ +#define SPC_INQUIRY_ID_TYPE_T10ID 1 /* ref 7.6.4.3 */ +#define SPC_INQUIRY_ID_TYPE_EUI 2 /* ref 7.6.4.4 */ +#define SPC_INQUIRY_ID_TYPE_NAA 3 /* ref 7.6.4.5 */ +#define SPC_INQUIRY_ID_TYPE_RELATIVE 4 /* ref 7.6.4.6 */ +#define SPC_INQUIRY_ID_TYPE_TARG_PORT 5 /* ref 7.6.4.7 */ +#define SPC_INQUIRY_ID_TYPE_LUN 6 /* ref 7.6.4.8 */ +#define SPC_INQUIRY_ID_TYPE_MD5 7 /* ref 7.6.4.9 */ +#define SPC_INQUIRY_ID_TYPE_SCSI 8 /* ref 7.6.4.10 */ + +/* ---- Table 292: ASSOCIATION field ---- */ +#define SPC_INQUIRY_ASSOC_LUN 0 +#define SPC_INQUIRY_ASSOC_TARGPORT 1 +#define SPC_INQUIRY_ASSOC_TARG 2 + +/* ---- Table 80: Peripheral qualifier ---- */ +#define SPC_INQUIRY_PERIPH_CONN 0 +#define SPC_INQUIRY_PERIPH_DISCONN 1 +#define SPC_INQUIRY_PERIPH_INVALID 3 + +/* ---- Table 256: PROTOCOL IDENTIFIER values ---- */ +#define SPC_INQUIRY_PROTOCOL_FC 0 +#define SPC_INQUIRY_PROTOCOL_PSCSI 1 +#define SPC_INQUIRY_PROTOCOL_SSA 2 +#define SPC_INQUIRY_PROTOCOL_IEEE1394 3 +#define SPC_INQUIRY_PROTOCOL_SCSIRDMA 4 +#define SPC_INQUIRY_PROTOCOL_ISCSI 5 +#define SPC_INQUIRY_PROTOCOL_SAS 6 +#define SPC_INQUIRY_PROTOCOL_ADT 7 +#define SPC_INQUIRY_PROTOCOL_ATA 8 + +#define SPC_DEFAULT_TPG 1 + +/* + * []------------------------------------------------------------------[] + * | SPC-4 revision 1a, section 6.25 -- REPORT TARGET PORT GROUPS | + * | Structures and defines | + * []------------------------------------------------------------------[] + */ +/* + * The service action must be set to 0x0A. This command is really a + * MAINTENANCE_IN command with a specific service action. + */ +#define SPC_MI_SVC_MASK 0x1f +#define SPC_MI_SVC_RTPG 0x0a + +/* ---- Table 167: Target port descriptor format ---- */ +typedef struct rtpg_targ_desc { + uchar_t obsolete[2], + rel_tpi[2]; +} rtpg_targ_desc_t; + +/* ---- Table 164: Target port group descript format ---- */ +typedef struct rtpg_desc { +#if defined(_BIT_FIELDS_LTOH) + uchar_t access_state : 4, + : 3, + pref : 1; + uchar_t ao_sup : 1, + an_sup : 1, + s_sup : 1, + u_sup : 1, + : 3, + t_sup : 1; +#elif defined(_BIT_FIELDS_HTOL) + uchar_t pref : 1, + : 3, + access_state : 4; + uchar_t t_sup : 1, + : 3, + u_sup : 1, + s_sup : 1, + an_sup : 1, + ao_sup : 1; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uchar_t tpg[2], + reserve_1, + status_code, + vendor_spec, + tpg_cnt; + rtpg_targ_desc_t targ_list[1]; +} rtpg_desc_t; + +/* ---- Table 163: parameter data format. ---- */ +typedef struct rtpg_data { + uchar_t len[4]; + rtpg_desc_t desc_list[1]; +} rtpg_hdr_t; + +/* + * []------------------------------------------------------------------[] + * | SPC-3, revision 21c, section 6.6 -- LOG_SENSE | + * | Structure and defines | + * []------------------------------------------------------------------[] + */ +#define SSC_LOG_SP 0x01 /* save parameters */ +#define SSC_LOG_PPC 0x02 /* parameter pointer control */ +#define SPC_LOG_PAGE_MASK 0x3f + +/* ---- section 7.2.1, Table 192: Log Parameter ---- */ +typedef struct spc_log_select_param { + char param_code[2]; +#if defined(_BIT_FIELDS_LTOH) + char lp : 1, /* list parameter */ + lbin : 1, + tmc : 2, /* threshold met criteria */ + etc : 1, /* enable threshold comparison */ + tsd : 1, /* target save disable */ + ds : 1, /* disable save */ + du : 1; /* disable update */ +#elif defined(_BIT_FIELDS_HTOL) + char du : 1, /* disable update */ + ds : 1, /* disable save */ + tsd : 1, /* target save disable */ + etc : 1, /* enable threshold comparison */ + tmc : 2, /* threshold met criteria */ + lbin : 1, + lp : 1; /* list parameter */ +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + char len; /* length of bytes to follow */ +} spc_log_select_param_t; + +/* ---- section 7.2.12, table 218: Supported log pages ---- */ +typedef struct spc_log_supported_pages { + char page_code, + resvd, + length[2], + list[1]; +} spc_log_supported_pages_t; + +/* + * []------------------------------------------------------------------[] + * | SPC-3, revision 21c, section 6.9 -- MODE_SENSE | + * | Structures and defines | + * []------------------------------------------------------------------[] + */ +/* ---- Section 7.4.11, Table 250: Information Controller Page ---- */ +struct mode_info_ctrl { + struct mode_page mode_page; + /* + * Currently we don't sent any of this information and it's set + * to zero's. We only care about the size. + */ + char info_data[10]; +}; + +#define MODE_SENSE_PAGE3_CODE 0x03 +#define MODE_SENSE_PAGE4_CODE 0x04 +#define MODE_SENSE_CACHE 0x08 +#define MODE_SENSE_CONTROL 0x0a +#define MODE_SENSE_COMPRESSION 0x0f +#define MODE_SENSE_DEV_CONFIG 0x10 +#define MODE_SENSE_INFO_CTRL 0x1c +#define MODE_SENSE_SEND_ALL 0x3f + +#define SCSI_REPORTLUNS_ADDRESS_SIZE 8 +#define SCSI_REPORTLUNS_ADDRESS_MASK 0xC0 +#define SCSI_REPORTLUNS_ADDRESS_PERIPHERAL 0x00 +#define SCSI_REPORTLUNS_ADDRESS_FLAT_SPACE 0x40 +#define SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT 0x80 +#define SCSI_REPORTLUNS_ADDRESS_EXTENDED_UNIT 0xC0 +#define SCSI_REPORTLUNS_ADDRESS_EXTENDED_2B 0x00 +#define SCSI_REPORTLUNS_ADDRESS_EXTENDED_4B 0x10 +#define SCSI_REPORTLUNS_ADDRESS_EXTENDED_6B 0x20 +#define SCSI_REPORTLUNS_ADDRESS_EXTENDED_8B 0x30 +#define SCSI_REPORTLUNS_ADDRESS_EXTENDED_MASK 0x30 +#define SCSI_REPORTLUNS_SELECT_ALL 0x02 + +#endif /* _SPC_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_ssc.c b/usr/src/cmd/iscsi/iscsitgtd/t10_ssc.c new file mode 100644 index 0000000000..1de425e042 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_ssc.c @@ -0,0 +1,1627 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Implementation of SSC-2 emulation + */ + +#include <strings.h> +#include <unistd.h> +#include <sys/types.h> +#include <aio.h> +#include <sys/asynch.h> +#include <sys/scsi/generic/sense.h> +#include <sys/scsi/generic/status.h> +#include <sys/scsi/targets/stdef.h> +#include <netinet/in.h> + +#include "target.h" +#include "utility.h" +#include "t10.h" +#include "t10_spc.h" +#include "t10_ssc.h" + +/* + * []---- + * | Forward declarations + * []---- + */ +static scsi_cmd_table_t ssc_table[]; +static void ssc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); +static void ssc_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, + char *data, size_t data_len); +static void ssc_free(emul_handle_t e); +static void ssc_write_cmplt(emul_handle_t e); +static void ssc_read_cmplt(emul_handle_t id); +static void ssc_setup_tape(ssc_params_t *s, t10_lu_common_t *lu); +static uint32_t find_last_obj_id(char *file_mark, off_t eod); +static char *sense_dev_config(ssc_params_t *s, char *data); +static char *sense_compression(ssc_params_t *s, char *data); + +/* + * []---- + * | ssc_init_common -- initialize common information that all ITLs will use + * []---- + */ +Boolean_t +ssc_init_common(t10_lu_common_t *lu) +{ + ssc_params_t *s; + ssc_obj_mark_t mark; + + if (lu->l_mmap == MAP_FAILED) + return (False); + + if ((s = (ssc_params_t *)calloc(1, sizeof (*s))) == NULL) + return (False); + + s->s_size = lu->l_size; + s->s_fast_write_ack = lu->l_fast_write_ack; + + bcopy(lu->l_mmap, &mark, sizeof (mark)); + if (mark.som_sig != SSC_OBJ_SIG) { + ssc_setup_tape(s, lu); + } + s->s_cur_fm = 0; + s->s_cur_rec = sizeof (ssc_obj_mark_t); + s->s_prev_rec = s->s_cur_rec; + s->s_state = lu->l_state; + + lu->l_dtype_params = (void *)s; + return (True); +} + +/* + * []---- + * | ssc_fini_common -- free any resources + * []---- + */ +void +ssc_fini_common(t10_lu_common_t *lu) +{ + free(lu->l_dtype_params); +} + +void +ssc_task_mgmt(t10_lu_common_t *lu, TaskOp_t op) +{ + ssc_params_t *s = (ssc_params_t *)lu->l_dtype_params; + + switch (op) { + case CapacityChange: + s->s_size = lu->l_size; + break; + + case DeviceOnline: + s->s_state = lu->l_state; + } +} + +/* + * []---- + * | ssc_init_per -- initialize per ITL information + * []---- + */ +void +ssc_init_per(t10_lu_impl_t *itl) +{ + ssc_params_t *s = (ssc_params_t *)itl->l_common->l_dtype_params; + + if (s->s_state == lu_online) + itl->l_cmd = ssc_cmd; + else + itl->l_cmd = spc_cmd_offline; + itl->l_data = ssc_data; + itl->l_cmd_table = ssc_table; + + itl->l_status = KEY_UNIT_ATTENTION; + itl->l_asc = SPC_ASC_PWR_ON; + itl->l_ascq = SPC_ASCQ_PWR_ON; +} + +/* + * []---- + * | ssc_fini_per -- release or free any ITL resources + * []---- + */ +/*ARGSUSED*/ +void +ssc_fini_per(t10_lu_impl_t *itl) +{ +} + +/* + * []---- + * | ssc_cmd -- start a SCSI command + * | + * | This routine is called from within the SAM-3 Task router. + * []---- + */ +static void +ssc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + scsi_cmd_table_t *e; + + e = &cmd->c_lu->l_cmd_table[cdb[0]]; +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "SSC%x LUN%d Cmd %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name == NULL ? "(no name)" : e->cmd_name); +#endif + (*e->cmd_start)(cmd, cdb, cdb_len); +} + +/* + * []---- + * | ssc_data -- Data phase for command. + * | + * | Normally this is only called for the WRITE command. Other commands + * | that have a data in phase will probably be short circuited when + * | we call trans_rqst_dataout() and the data is already available. + * | At least this is true for iSCSI. FC however will need a DataIn phase + * | for commands like MODE SELECT and PGROUT. + * []---- + */ +static void +ssc_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + scsi_cmd_table_t *e; + + e = &cmd->c_lu->l_cmd_table[cmd->c_cdb[0]]; +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, "SSC%x LUN%d Data %s\n", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + e->cmd_name); +#endif + (*e->cmd_data)(cmd, id, offset, data, data_len); +} + +/* + * []------------------------------------------------------------------[] + * | SCSI Streaming Commands - 3 | + * | T10/1611-D Revision 01c | + * | The following functions implement the emulation of SSC-3 type | + * | commands. | + * []------------------------------------------------------------------[] + */ + +/*ARGSUSED*/ +static void +ssc_read(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + ssc_io_t *io; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + ssc_obj_mark_t fm, + rm; + int fixed, + sili; + off_t offset = 0; + size_t xfer, + req_len; + void *mmap = cmd->c_lu->l_common->l_mmap; + + fixed = cdb[1] & 0x01; + sili = cdb[1] & 0x02; + + if (s == NULL) + return; + + /* + * Standard error checking. + */ + if ((sili && fixed) || (cdb[1] & 0xfc) || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + req_len = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4]; + req_len *= fixed ? 512 : 1; + + if (req_len == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, + "SSC%x LUN%d read 0x%x bytes", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, req_len); +#endif + + bcopy((char *)mmap + s->s_cur_fm, &fm, sizeof (fm)); + bcopy((char *)mmap + s->s_cur_fm + s->s_cur_rec, &rm, sizeof (rm)); + + if (rm.som_sig != SSC_OBJ_SIG) { + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d bad RECORD-MARK", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num); + spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } else if (rm.som_type != SSC_OBJ_TYPE_RM) { + s->s_cur_fm += fm.o_fm.size; + s->s_cur_rec = sizeof (ssc_obj_mark_t); + s->s_prev_rec = s->s_cur_rec; + + spc_sense_create(cmd, KEY_NO_SENSE, 0); + spc_sense_ascq(cmd, SPC_ASC_FM_DETECTED, SPC_ASCQ_FM_DETECTED); + spc_sense_info(cmd, req_len); + spc_sense_flags(cmd, SPC_SENSE_FM); + trans_send_complete(cmd, STATUS_CHECK); + return; + } else if ((sili == 0) && + ((rm.o_rm.size - sizeof (ssc_obj_mark_t)) != req_len)) { + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d Wrong size read", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num); + + s->s_prev_rec = s->s_cur_rec; + s->s_cur_rec += rm.o_rm.size; + + spc_sense_create(cmd, KEY_NO_SENSE, 0); + spc_sense_flags(cmd, SPC_SENSE_ILI); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + do { + if ((io = (ssc_io_t *)calloc(1, sizeof (*io))) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + + xfer = MIN((req_len - offset), T10_MAX_OUT(cmd)); + io->sio_cmd = cmd; + io->sio_offset = offset; + io->sio_total = req_len; + io->sio_data_len = xfer; + io->sio_data = (char *)mmap + s->s_cur_fm + + s->s_cur_rec + sizeof (ssc_obj_mark_t) + offset; + io->sio_aio.a_aio.aio_return = xfer; + + ssc_read_cmplt((emul_handle_t)io); + offset += xfer; + } while (offset < req_len); + + s->s_prev_rec = s->s_cur_rec; + s->s_cur_rec += req_len + sizeof (ssc_obj_mark_t); +} + +static void +ssc_read_cmplt(emul_handle_t id) +{ + ssc_io_t *io = (ssc_io_t *)id; + t10_cmd_t *cmd = io->sio_cmd; + + if (io->sio_aio.a_aio.aio_return != io->sio_data_len) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((io->sio_offset + io->sio_data_len) < io->sio_total) { + if (trans_send_datain(cmd, io->sio_data, io->sio_data_len, + io->sio_offset, ssc_free, False, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } + } else { + if (trans_send_datain(cmd, io->sio_data, io->sio_data_len, + io->sio_offset, ssc_free, True, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } + } +} + +/*ARGSUSED*/ +static void +ssc_write(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + ssc_obj_mark_t mark; + size_t request_len, + max_xfer; + int fixed, + prev_id; + ssc_io_t *io; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + + if (s == NULL) + return; + + if ((cdb[1] & 0xfe) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + fixed = cdb[1]; + request_len = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4]; + request_len *= fixed ? 512 : 1; + +#ifdef FULL_DEBUG + queue_prt(mgmtq, Q_STE_IO, + "SSC%x LUN%d write %d, fixed %d", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + request_len, fixed); +#endif + io = cmd->c_emul_id; + if (io == NULL) { + if ((io = calloc(1, sizeof (*io))) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + io->sio_total = request_len; + io->sio_cmd = cmd; + io->sio_offset = 0; + + /* + * Writing looses all information after the current + * file-mark. So, check to see if the current file-mark + * size doesn't reflect the end-of-media. If not, update + * it. + */ + bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm, + &mark, sizeof (mark)); + if (mark.o_fm.size != + (s->s_size - sizeof (ssc_obj_mark_t) - s->s_cur_fm)) { + mark.o_fm.size = s->s_size - sizeof (ssc_obj_mark_t) - + s->s_cur_fm; + bcopy(&mark, (char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm, sizeof (mark)); + } + + /* + * End-of-Partition detection + */ + if ((s->s_cur_rec + request_len) > (mark.o_fm.size)) { + spc_sense_create(cmd, KEY_VOLUME_OVERFLOW, 0); + spc_sense_ascq(cmd, SPC_ASC_EOP, SPC_ASCQ_EOP); + spc_sense_flags(cmd, SPC_SENSE_EOM); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((s->s_cur_fm == 0) && + (s->s_cur_rec == sizeof (ssc_obj_mark_t))) { + + /* + * The current position is a BOM. By setting + * the prev_id value to -1 the code below will + * create the first ID with a value of zero + * Which is what the specification requires. + */ + prev_id = -1; + + } else if (s->s_cur_rec == sizeof (ssc_obj_mark_t)) { + + /* + * If the current position is at the beginning of + * this file-mark use the object ID found in + * the file-mark header. It will have been updated + * from the last object ID in the previous file-mark. + * + * NOTE: We're counting on 'mark' still referring + * to the current file mark here. + */ + prev_id = mark.o_fm.last_obj_id; + } else { + bcopy((char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm + s->s_prev_rec, &mark, sizeof (mark)); + prev_id = mark.o_rm.obj_id; + } + + bzero(&mark, sizeof (mark)); + mark.som_sig = SSC_OBJ_SIG; + mark.som_type = SSC_OBJ_TYPE_RM; + mark.o_rm.size = request_len + + sizeof (ssc_obj_mark_t); + mark.o_rm.obj_id = prev_id + 1; + bcopy(&mark, (char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm + s->s_cur_rec, sizeof (mark)); + } + + max_xfer = min(io->sio_total - io->sio_offset, + cmd->c_lu->l_targ->s_maxout); + io->sio_aio.a_aio.aio_return = max_xfer; + io->sio_data_len = max_xfer; + io->sio_data = (char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm + s->s_cur_rec + sizeof (mark) + io->sio_offset; + + if (trans_rqst_dataout(cmd, io->sio_data, io->sio_data_len, + io->sio_offset, io) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +static void +ssc_write_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, + size_t data_len) +{ + ssc_io_t *io = (ssc_io_t *)id; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + + if (s == NULL) + return; + + if (s->s_fast_write_ack == False) { + /* + * We only need to worry about sync'ing the blocks + * in the mmap case because if the fast cache isn't + * enabled for AIO the file will be opened with F_SYNC + * which performs the correct action. + */ + if (fsync(cmd->c_lu->l_common->l_fd) == -1) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + } + ssc_write_cmplt((emul_handle_t)io); +} + +static void +ssc_write_cmplt(emul_handle_t e) +{ + ssc_io_t *io = (ssc_io_t *)e; + t10_cmd_t *cmd = io->sio_cmd; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + + if (s == NULL) + return; + + if ((io->sio_offset + io->sio_data_len) < io->sio_total) { + io->sio_offset += io->sio_data_len; + ssc_write(cmd, cmd->c_cdb, cmd->c_cdb_len); + return; + } + + s->s_prev_rec = s->s_cur_rec; + s->s_cur_rec += io->sio_total + sizeof (ssc_obj_mark_t); + free(io); + trans_send_complete(cmd, STATUS_GOOD); +} + +/*ARGSUSED*/ +static void +ssc_rewind(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + + if (s == NULL) + return; + + if ((cdb[1] & ~SSC_REWIND_IMMED) || cdb[2] || cdb[3] || cdb[4] || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + s->s_cur_fm = 0; + s->s_cur_rec = sizeof (ssc_obj_mark_t); + trans_send_complete(cmd, STATUS_GOOD); +} + +/*ARGSUSED*/ +static void +ssc_read_limits(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + struct read_blklim *rb; + int min_size = 512; + + if (cdb[1] || cdb[2] || cdb[3] || cdb[4] || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if ((rb = (struct read_blklim *)calloc(1, sizeof (*rb))) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + + /* + * maximum block size is set to zero to indicate no maximum block + * limit is specified. + */ + rb->granularity = 9; /* 512 block sizes */ + rb->min_hi = hibyte(min_size); + rb->min_lo = lobyte(min_size); + + if (trans_send_datain(cmd, (char *)rb, sizeof (*rb), 0, ssc_free, + True, (emul_handle_t)rb) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +static void +ssc_space(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + int code, + count; + ssc_params_t *s = T10_PARAMS_AREA(cmd); + ssc_obj_mark_t mark; + t10_lu_common_t *lu = cmd->c_lu->l_common; + + if (s == NULL) + return; + + if ((cdb[1] & 0xf0) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + code = cdb[1] & 0x0f; + count = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4]; + + if ((count == 0) && (code != SSC_SPACE_CODE_END_OF_DATA)) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } + + switch (code) { + case SSC_SPACE_CODE_BLOCKS: + if (count < 0) { + bcopy((char *)lu->l_mmap + s->s_cur_fm + s->s_cur_rec, + &mark, sizeof (mark)); + if ((mark.som_sig == SSC_OBJ_SIG) && + (mark.som_type == SSC_OBJ_TYPE_RM)) { + count = mark.o_rm.obj_id + count; + + /* + * If the count is still negative it means + * the request is still attempting to go + * beyond the beginning of the file mark. + */ + if (count < 0) { + count *= -1; + spc_sense_create(cmd, KEY_NO_SENSE, 0); + spc_sense_ascq(cmd, + SPC_ASC_FM_DETECTED, + SPC_ASCQ_FM_DETECTED); + spc_sense_info(cmd, count); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + s->s_cur_rec = s->s_cur_fm + sizeof (mark); + } else { + /* + * Something is not right. We'll let the + * processing below determine exactly what + * is wrong. So don't update the record + * mark and sent the count to 1. + */ + count = 1; + } + } + + while (count) { + bcopy((char *)lu->l_mmap + s->s_cur_fm + s->s_cur_rec, + &mark, sizeof (mark)); + + /* + * Something internally bad has happened with + * the marks in the file. + */ + if (mark.som_sig != SSC_OBJ_SIG) { + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d, bad sig mark: " + "expected=0x%x, got=0x%x", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, + SSC_OBJ_SIG, mark.som_sig); + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + /* + * Hit a filemark. Update the current record if + * we're not at the End-Of-Medium. + */ + if (mark.som_type == SSC_OBJ_TYPE_FM) { + if (mark.o_fm.eom == True) { + spc_sense_create(cmd, KEY_MEDIUM_ERROR, + 0); + spc_sense_ascq(cmd, SPC_ASC_EOP, + SPC_ASCQ_EOP); + spc_sense_info(cmd, count); + spc_sense_flags(cmd, SPC_SENSE_EOM); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + s->s_cur_fm += s->s_cur_rec; + s->s_cur_rec += sizeof (mark); + spc_sense_create(cmd, KEY_NO_SENSE, 0); + spc_sense_ascq(cmd, SPC_ASC_FM_DETECTED, + SPC_ASCQ_FM_DETECTED); + spc_sense_info(cmd, count); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + s->s_cur_rec += mark.o_rm.size; + count--; + } + trans_send_complete(cmd, STATUS_CHECK); + break; + + case SSC_SPACE_CODE_FILEMARKS: + if (count < 0) { + bcopy((char *)lu->l_mmap + s->s_cur_fm, &mark, + sizeof (mark)); + if ((mark.som_sig == SSC_OBJ_SIG) && + (mark.som_type == SSC_OBJ_TYPE_FM)) { + count = mark.o_fm.num + count; + + /* + * If the count is still negative it means + * the request is still attempting to go + * beyond the beginning of the file mark. + */ + if (count < 0) { + count *= -1; + spc_sense_create(cmd, KEY_NO_SENSE, 0); + spc_sense_ascq(cmd, + SPC_ASC_FM_DETECTED, + SPC_ASCQ_FM_DETECTED); + spc_sense_info(cmd, count); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + s->s_cur_fm = 0; + s->s_cur_rec = sizeof (ssc_obj_mark_t); + } else { + /* + * Something is not right. We'll let the + * processing below determine exactly what + * is wrong. So don't update the record + * mark and sent the count to 1. + */ + count = 1; + } + } + + while (count--) { + bcopy((char *)lu->l_mmap + s->s_cur_fm, &mark, + sizeof (mark)); + if (mark.som_sig != SSC_OBJ_SIG) { + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d, bad sig mark: " + "expected=0x%x, got=0x%x", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, + SSC_OBJ_SIG, mark.som_sig); + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if (mark.som_type != SSC_OBJ_TYPE_FM) { + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d, bad mark type: " + "expected=0x%x, got=0x%x", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, + SSC_OBJ_TYPE_FM, mark.som_type); + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if (mark.o_fm.eom == True) { + spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0); + spc_sense_ascq(cmd, SPC_ASC_EOP, SPC_ASCQ_EOP); + spc_sense_info(cmd, count); + spc_sense_flags(cmd, SPC_SENSE_EOM); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + s->s_cur_fm += mark.o_fm.size; + } + trans_send_complete(cmd, STATUS_GOOD); + break; + + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } +} + +/* + * []---- + * | ssc_msense -- MODE SENSE command + * | + * | This command is part of the SPC set, but is device specific enough + * | that it must be emulated in each device type. + * []---- + */ +/*ARGSUSED*/ +static void +ssc_msense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + ssc_params_t *s = T10_PARAMS_AREA(cmd); + struct mode_header *mode_hdr; + int request_len, + alloc_len; + char *data, + *np; + + /* + * SPC-3 Revision 21c section 6.8 + * Reserve bit checks + */ + if ((cdb[1] & ~8) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + /* + * Zero length causes a simple ack to occur. + */ + if (cdb[4] == 0) { + trans_send_complete(cmd, STATUS_GOOD); + return; + } else { + request_len = cdb[4]; + alloc_len = max(request_len, + sizeof (*mode_hdr) + MODE_BLK_DESC_LENGTH + + sizeof (ssc_data_compression_t) + sizeof (*mode_hdr) + + MODE_BLK_DESC_LENGTH + sizeof (ssc_device_config_t)); + } + + queue_prt(mgmtq, Q_STE_NONIO, "SSC%x LUN%d: MODE_SENSE(0x%x)", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, cdb[2]); + + if ((data = memalign(sizeof (void *), alloc_len)) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + + mode_hdr = (struct mode_header *)data; + + switch (cdb[2]) { + case MODE_SENSE_COMPRESSION: + mode_hdr->length = sizeof (ssc_data_compression_t); + mode_hdr->bdesc_length = MODE_BLK_DESC_LENGTH; + (void) sense_compression(s, data + sizeof (*mode_hdr) + + mode_hdr->bdesc_length); + break; + + case MODE_SENSE_DEV_CONFIG: + mode_hdr->length = sizeof (ssc_device_config_t); + mode_hdr->bdesc_length = MODE_BLK_DESC_LENGTH; + (void) sense_dev_config(s, data + sizeof (*mode_hdr) + + mode_hdr->bdesc_length); + break; + + case MODE_SENSE_SEND_ALL: + np = sense_compression(s, data); + (void) sense_dev_config(s, np); + break; + + case 0x00: + bzero(data, alloc_len); + break; + + default: + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d: MODE SENSE(0x%x) not handled", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, + cdb[2]); + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if (trans_send_datain(cmd, (char *)data, request_len, 0, ssc_free, + True, data) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +static void +ssc_read_pos(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + int service_action, + request_len, + alloc_len; + pos_short_form_t *sf; + void *data; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + ssc_obj_mark_t mark; + + if (s == NULL) + return; + + /* + * Standard reserve bit check + */ + if ((cdb[1] & 0xc0) || cdb[2] || cdb[3] || cdb[4] || cdb[5] || + cdb[6] || SAM_CONTROL_BYTE_RESERVED(cdb[9])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + service_action = cdb[1] & 0x1f; + request_len = (cdb[7] << 8) | cdb[8]; + switch (service_action) { + case SSC_READ_POS_SHORT_FORM: + alloc_len = max(request_len, sizeof (*sf)); + if ((data = memalign(sizeof (void *), alloc_len)) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + sf = (pos_short_form_t *)data; + bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm, + &mark, sizeof (mark)); + if ((mark.o_fm.bom == True) && + (s->s_cur_rec == sizeof (ssc_obj_mark_t))) + sf->bop = 1; + bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm + + s->s_cur_rec, &mark, sizeof (mark)); + sf->first_obj[0] = hibyte(hiword(mark.o_rm.obj_id)); + sf->first_obj[1] = lobyte(hiword(mark.o_rm.obj_id)); + sf->first_obj[2] = hibyte(loword(mark.o_rm.obj_id)); + sf->first_obj[3] = lobyte(loword(mark.o_rm.obj_id)); + + /* + * We mark the last object to be the same as the first + * object which indicates that nothing is currently + * buffered. + */ + sf->last_obj[0] = sf->first_obj[0]; + sf->last_obj[1] = sf->first_obj[1]; + sf->last_obj[2] = sf->first_obj[2]; + sf->last_obj[3] = sf->first_obj[3]; + + break; + + case SSC_READ_POS_LONG_FORM: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + if (trans_send_datain(cmd, (char *)data, request_len, 0, ssc_free, + True, data) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +static void +ssc_rpt_density(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + ssc_density_t *d; + ssc_density_media_t *dm; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + size_t cap = s->s_size / (1024 * 1024); + int medium_type, + request_len, + alloc_len; + void *data; + + if (s == NULL) + return; + + if ((cdb[1] & 0xfc) || cdb[2] || cdb[3] || cdb[4] || cdb[5] || + cdb[6] || SAM_CONTROL_BYTE_RESERVED(cdb[9])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + medium_type = cdb[1] & 0x2; + request_len = (cdb[7] << 8) | cdb[8]; + alloc_len = max(request_len, max(sizeof (*d), sizeof (*dm))); + if ((data = memalign(sizeof (void *), alloc_len)) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + if (medium_type == 0) { + d = (ssc_density_t *)data; + d->d_hdr.len = htons(sizeof (*d) - + sizeof (ssc_density_header_t)); + d->d_prim_code = 1; + d->d_wrtok = 1; + d->d_deflt = 1; + d->d_tracks[1] = 1; + d->d_capacity[0] = hibyte(hiword(cap)); + d->d_capacity[1] = lobyte(hiword(cap)); + d->d_capacity[2] = hibyte(loword(cap)); + d->d_capacity[3] = lobyte(loword(cap)); + bcopy(cmd->c_lu->l_common->l_vid, d->d_organization, + min(sizeof (d->d_organization), + strlen(cmd->c_lu->l_common->l_vid))); + bcopy(cmd->c_lu->l_common->l_pid, d->d_description, + min(sizeof (d->d_description), + strlen(cmd->c_lu->l_common->l_pid))); + } else { + dm = (ssc_density_media_t *)data; + dm->d_hdr.len = htons(sizeof (*d) - + sizeof (ssc_density_header_t)); + bcopy(cmd->c_lu->l_common->l_vid, d->d_organization, + min(sizeof (d->d_organization), + strlen(cmd->c_lu->l_common->l_vid))); + bcopy(cmd->c_lu->l_common->l_pid, dm->d_description, + min(sizeof (dm->d_description), + strlen(cmd->c_lu->l_common->l_pid))); + } + + if (trans_send_datain(cmd, (char *)data, request_len, 0, ssc_free, + True, (emul_handle_t)data) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } +} + +/*ARGSUSED*/ +static void +ssc_write_fm(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + int marks_requested; + off_t next_size; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + ssc_obj_mark_t mark_fm; + + if (s == NULL) + return; + + if ((cdb[1] & 0xfc) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + marks_requested = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4]; + while (marks_requested--) { + /* + * Get the last file-mark and update it's size. + */ + bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm, + &mark_fm, sizeof (mark_fm)); + if (mark_fm.som_sig != SSC_OBJ_SIG) { + spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + next_size = mark_fm.o_fm.size - s->s_cur_rec; + mark_fm.o_fm.size = s->s_cur_rec; + bcopy(&mark_fm, (char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm, sizeof (mark_fm)); + + /* + * Write new mark and update internal location of mark. + */ + mark_fm.o_fm.last_obj_id = + find_last_obj_id((char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm, s->s_cur_rec); + mark_fm.o_fm.bom = False; + mark_fm.o_fm.size = next_size; + s->s_cur_fm += s->s_cur_rec; + s->s_cur_rec = sizeof (ssc_obj_mark_t); + s->s_prev_rec = s->s_cur_rec; + bcopy(&mark_fm, (char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm, sizeof (mark_fm)); + mark_fm.o_fm.size = 0; + bcopy(&mark_fm, (char *)cmd->c_lu->l_common->l_mmap + + s->s_cur_fm + s->s_cur_rec, sizeof (mark_fm)); + } + trans_send_complete(cmd, STATUS_GOOD); +} + +/*ARGSUSED*/ +static void +ssc_locate(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ +} + +/*ARGSUSED*/ +static void +ssc_erase(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + ssc_obj_mark_t mark; + ssc_params_t *s = (ssc_params_t *)T10_PARAMS_AREA(cmd); + + if (s == NULL) + return; + + bzero(&mark, sizeof (mark)); + mark.som_sig = SSC_OBJ_SIG; + mark.som_type = SSC_OBJ_TYPE_FM; + mark.o_fm.bom = True; + mark.o_fm.eom = False; + mark.o_fm.size = s->s_size - sizeof (mark); + + bcopy(&mark, (char *)cmd->c_lu->l_common->l_mmap, sizeof (mark)); +} + +/*ARGSUSED*/ +static void +ssc_load(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + ssc_params_t *s = T10_PARAMS_AREA(cmd); + t10_lu_common_t *lu = cmd->c_lu->l_common; + ssc_obj_mark_t mark; + + /* + * SSC-3, revision 02, section 7.2 LOAD/UNLOAD command + * Check for various reserve bits. + */ + if ((cdb[1] & ~SSC_LOAD_CMD_IMMED) || cdb[2] || cdb[3] || + (cdb[4] & ~(SSC_LOAD_CMD_LOAD | SSC_LOAD_CMD_RETEN | + SSC_LOAD_CMD_EOT | SSC_LOAD_CMD_HOLD)) || + SAM_CONTROL_BYTE_RESERVED(cdb[5])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + queue_prt(mgmtq, Q_STE_NONIO, "SSC%x LUN%d load bits 0x%x", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, cdb[4]); + + /* + * There are four possible actions based on the LOAD and HOLD + * bits. + */ + switch (cdb[4] & (SSC_LOAD_CMD_LOAD|SSC_LOAD_CMD_HOLD)) { + case SSC_LOAD_CMD_LOAD|SSC_LOAD_CMD_HOLD: + /* + * Load the media into the system if not already done + * so, but do not position tape. The EOT and RETEN should + * be zero. Since this emulation currently is always available + * we're good to go. + */ + break; + + case SSC_LOAD_CMD_LOAD: + /* + * Without the HOLD bit the tape should be positioned at + * the beginning of partition 0. + */ + s->s_cur_fm = 0; + s->s_cur_rec = sizeof (ssc_obj_mark_t); + break; + + case SSC_LOAD_CMD_HOLD: + /* + * Without the LOAD bit we leave the tape online, but look + * at the RETEN and EOT bits. The RETEN doesn't mean anything + * for this virtual tape, but we can reposition to EOT. + */ + if (cdb[4] & SSC_LOAD_CMD_EOT) { + /*CONSTANTCONDITION*/ + while (1) { + bcopy((char *)lu->l_mmap + s->s_cur_fm, &mark, + sizeof (mark)); + if (mark.som_sig != SSC_OBJ_SIG) { + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d, bad sig mark: " + "expected=0x%x, got=0x%x", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, + SSC_OBJ_SIG, mark.som_sig); + spc_sense_create(cmd, + KEY_MEDIUM_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if (mark.som_type != SSC_OBJ_TYPE_FM) { + queue_prt(mgmtq, Q_STE_ERRS, + "SSC%x LUN%d, bad mark type: " + "expected=0x%x, got=0x%x", + cmd->c_lu->l_targ->s_targ_num, + cmd->c_lu->l_common->l_num, + SSC_OBJ_TYPE_FM, mark.som_type); + spc_sense_create(cmd, + KEY_MEDIUM_ERROR, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + if (mark.o_fm.eom == True) + break; + s->s_cur_fm += mark.o_fm.size; + } + } + break; + + case 0: + /* + * Unload the current tape. + */ + + break; + } + trans_send_complete(cmd, STATUS_GOOD); +} + +/*ARGSUSED*/ +static void +ssc_logsense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) +{ + spc_log_supported_pages_t p; + void *v; + + /* + * Reserve bit checks + */ + if ((cdb[1] & ~(SSC_LOG_SP|SSC_LOG_PPC)) || cdb[3] || cdb[4] || + SAM_CONTROL_BYTE_RESERVED(cdb[9])) { + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0); + trans_send_complete(cmd, STATUS_CHECK); + return; + } + + queue_prt(mgmtq, Q_STE_ERRS, "SSC%x LUN%d page code 0x%x", + cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, cdb[2]); + + switch (cdb[2] & SPC_LOG_PAGE_MASK) { + case 0: + if ((v = malloc(sizeof (p))) == NULL) { + trans_send_complete(cmd, STATUS_BUSY); + return; + } + bzero(&p, sizeof (p)); + p.length[0] = 1; + bcopy(&p, v, sizeof (p)); + if (trans_send_datain(cmd, (char *)v, sizeof (p), 0, free, + True, (emul_handle_t)v) == False) { + trans_send_complete(cmd, STATUS_BUSY); + } + break; + + default: + spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); + spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0); + trans_send_complete(cmd, STATUS_CHECK); + break; + } +} + +/* + * []------------------------------------------------------------------[] + * | Support functions for the SSC command set | + * []------------------------------------------------------------------[] + */ +static uint32_t +find_last_obj_id(char *file_mark, off_t eod) +{ + ssc_obj_mark_t rm; + off_t offset = sizeof (ssc_obj_mark_t); + uint32_t obj_id = 0xffffffff; + + bcopy(file_mark + offset, &rm, sizeof (rm)); + while (rm.som_type == SSC_OBJ_TYPE_RM) { + obj_id = rm.o_rm.obj_id; + offset += rm.o_rm.size; + if (offset >= eod) + break; + bcopy(file_mark + offset, &rm, sizeof (rm)); + } + return (obj_id); +} + +static void +ssc_setup_tape(ssc_params_t *s, t10_lu_common_t *lu) +{ + ssc_obj_mark_t mark; + + /* + * Add Begin-of-Partition marker + */ + bzero(&mark, sizeof (mark)); + mark.som_sig = SSC_OBJ_SIG; + mark.som_type = SSC_OBJ_TYPE_FM; + mark.o_fm.bom = True; + mark.o_fm.eom = False; + mark.o_fm.size = s->s_size - sizeof (mark); + bcopy(&mark, lu->l_mmap, sizeof (mark)); + + /* + * Add first file-record with a zero size. + */ + bzero(&mark, sizeof (mark)); + mark.som_sig = SSC_OBJ_SIG; + mark.som_type = SSC_OBJ_TYPE_RM; + mark.o_rm.size = 0; + mark.o_rm.obj_id = 0xffffffff; + bcopy(&mark, (char *)lu->l_mmap + sizeof (ssc_obj_mark_t), + sizeof (mark)); + + /* + * Add End-of-Partiton marker + */ + bzero(&mark, sizeof (mark)); + mark.som_sig = SSC_OBJ_SIG; + mark.som_type = SSC_OBJ_TYPE_FM; + mark.o_fm.bom = False; + mark.o_fm.eom = True; + mark.o_fm.size = 0; + bcopy(&mark, (char *)lu->l_mmap + s->s_size - sizeof (mark), + sizeof (mark)); +} + +static char * +sense_compression(ssc_params_t *s, char *data) +{ + ssc_data_compression_t d; + + bzero(&d, sizeof (d)); + d.mode_page.code = MODE_SENSE_COMPRESSION; + d.mode_page.length = sizeof (d) - sizeof (struct mode_page); + bcopy(&d, data, sizeof (d)); + + return (data + sizeof (d)); +} + +static char * +sense_dev_config(ssc_params_t *s, char *data) +{ + ssc_device_config_t d; + + bzero(&d, sizeof (d)); + d.mode_page.code = MODE_SENSE_DEV_CONFIG; + d.mode_page.length = sizeof (d) - sizeof (struct mode_page); + d.lois = 1; + d.socf = 1; + d.rewind_on_reset = 1; + bcopy(&d, data, sizeof (d)); + + return (data + sizeof (d)); +} + +static void +ssc_free(emul_handle_t e) +{ + free(e); +} + +/* + * []---- + * | Command table for SSC emulation. This is at the end of the file because + * | it's big and ugly. ;-) To make for fast translation to the appropriate + * | emulation routine we just have a big command table with all 256 possible + * | entries. Most will report STATUS_CHECK, unsupport operation. By doing + * | this we can avoid error checking for command range. + * []---- + */ +static scsi_cmd_table_t ssc_table[] = { + /* 0x00 -- 0x0f */ + { spc_tur, NULL, NULL, "TEST_UNIT_READY" }, + { ssc_rewind, NULL, NULL, "REWIND"}, + { spc_unsupported, NULL, NULL, NULL }, + { spc_request_sense, NULL, NULL, "REQUEST_SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_read_limits, NULL, NULL, "READ BLOCK LIMITS"}, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_read, NULL, ssc_read_cmplt, "READ(6)" }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_write, ssc_write_data, ssc_write_cmplt, "WRITE(6)" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x10 -- 0x1f */ + { ssc_write_fm, NULL, NULL, "WRITE_FILEMARKS(6)"}, + { ssc_space, NULL, NULL, "SPACE(6)"}, + { spc_inquiry, NULL, NULL, "INQUIRY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_mselect, spc_mselect_data, NULL, "MODE_SELECT(6)" }, + { spc_request_sense, NULL, NULL, "RESERVE" }, + { spc_request_sense, NULL, NULL, "RELEASE" }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_erase, NULL, NULL, "ERASE(6)"}, + { ssc_msense, NULL, NULL, "MODE_SENSE(6)" }, + { ssc_load, NULL, NULL, "LOAD_UNLOAD" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_send_diag, NULL, NULL, "SEND_DIAG" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x20 -- 0x2f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "READ_CAPACITY" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_read, NULL, ssc_read_cmplt, "READ_G1" }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_write, ssc_write_data, ssc_write_cmplt, "WRITE_G1" }, + { ssc_locate, NULL, NULL, "LOCATE(10)"}, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x30 -- 0x3f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_read_pos, NULL, NULL, "READ POSITION"}, + { spc_unsupported, NULL, NULL, "SYNC_CACHE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x40 -- 0x4f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_rpt_density, NULL, NULL, "REPORT DENSITY SUPPORT"}, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_logsense, NULL, NULL, "LOG SENSE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x50 -- 0x5f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x60 -- 0x6f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x70 -- 0x7f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x80 -- 0x8f */ + { ssc_write_fm, NULL, NULL, "WRITE_FILEMARKS(16)"}, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_read, NULL, ssc_read_cmplt, "READ_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_write, ssc_write_data, ssc_write_cmplt, "WRITE_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0x90 -- 0x9f */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { ssc_locate, NULL, NULL, "LOCATE(16)"}, + { ssc_erase, NULL, NULL, "ERASE" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, "SVC_ACTION_G4" }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xa0 - 0xaf */ + { spc_report_luns, NULL, NULL, "REPORT_LUNS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_report_tpgs, NULL, NULL, "REPORT_TPGS" }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xb0 -- 0xbf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xc0 -- 0xcf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xd0 -- 0xdf */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xe0 -- 0xef */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + + /* 0xf0 -- 0xff */ + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, + { spc_unsupported, NULL, NULL, NULL }, +}; diff --git a/usr/src/cmd/iscsi/iscsitgtd/t10_ssc.h b/usr/src/cmd/iscsi/iscsitgtd/t10_ssc.h new file mode 100644 index 0000000000..6096138899 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/t10_ssc.h @@ -0,0 +1,317 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _T10_SSC_H +#define _T10_SSC_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Implementation specific headers for SCSI Streaming Commands (Tapes) + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +#define SSC_SPACE_CODE_BLOCKS 0x00 /* Mandatory support */ +#define SSC_SPACE_CODE_FILEMARKS 0x01 /* Mandatory support */ +#define SSC_SPACE_CODE_SEQ_FILEMARKS 0x02 /* Optional support */ +#define SSC_SPACE_CODE_END_OF_DATA 0x03 /* Optional support */ + +#define SSC_READ_POS_SHORT_FORM 0x00 +#define SSC_READ_POS_LONG_FORM 0x06 + +#define SSC_OBJ_SIG 0x22005454 +#define SSC_OBJ_TYPE_FM 1 +#define SSC_OBJ_TYPE_RM 2 + +#define SSC_REWIND_IMMED 0x01 + +/* + * SSC-3, revision 01c, section 7.2 + * LOAD/UNLOAD bits + */ +#define SSC_LOAD_CMD_LOAD 0x01 +#define SSC_LOAD_CMD_RETEN 0x02 +#define SSC_LOAD_CMD_EOT 0x04 +#define SSC_LOAD_CMD_HOLD 0x08 +/* ---- bit 0 of byte 1 ---- */ +#define SSC_LOAD_CMD_IMMED 0x01 + +/* + * On disk file and record mark object structure. + */ +typedef struct ssc_obj_mark { + uint32_t som_sig; + uint32_t som_type; + union { + struct { + Boolean_t bom, + eom; + off_t size; + uint32_t num, + last_obj_id; + } _fm_; + struct { + off_t size; + /* + * SSC-3 Revision 1c, Section 3.1.40 + * Object Identifier relative to beginning of + * partition. + */ + uint32_t obj_id; + } _rm_; + char _filler_[512 - (sizeof (uint32_t) * 2)]; + } _u_; +} ssc_obj_mark_t; + +#define o_fm _u_._fm_ +#define o_rm _u_._rm_ + +/* + * In core structure which indicates current file-mark and record-mark + */ +typedef struct ssc_params { + Boolean_t s_fast_write_ack; + off_t s_size; + off_t s_cur_rec, /* starts at sizeof (ssc_obj_mark) */ + s_prev_rec, + s_cur_fm; /* starts at 0 */ + t10_lu_state_t s_state; +} ssc_params_t; + +/* + * During asynchronous I/O there are a few things needed to complete + * the operation. + */ +typedef struct ssc_io { + /* + * Look at SBC for the reason that this member must be first + */ + t10_aio_t sio_aio; + + t10_cmd_t *sio_cmd; + + char *sio_data; + size_t sio_data_len; + off_t sio_offset; /* offset from s_cur_rec */ + size_t sio_total; +} ssc_io_t; + +/* + * READ POSITION data format, short form + */ +typedef struct pos_short_form { +#if defined(_BIT_FIELDS_LTOH) + uchar_t rsvd2 : 1, + perr : 1, + lolu : 1, + rsvd1 : 1, + bycu : 1, + locu : 1, + eop : 1, + bop : 1; +#elif defined(_BIT_FIELDS_HTOL) + uchar_t bop : 1, + eop : 1, + locu : 1, + bycu : 1, + rsvd1 : 1, + lolu : 1, + perr : 1, + rsvd2 : 1; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uchar_t partition, + rsvd3[2], + first_obj[4], + last_obj[4], + rsvd4, + objs_in_buf[3], + bytes_in_buf[4]; +} pos_short_form_t; + +/* + * REPORT DENSITY SUPPORT header + */ +typedef struct ssc_density_header { + uint16_t len, + rsvd; +} ssc_density_header_t; + +typedef struct ssc_density { + ssc_density_header_t d_hdr; + uchar_t d_prim_code, + d_secondary_code; +#if defined(_BIT_FIELDS_LTOH) + uchar_t d_dlv : 1, + d_rsvd : 4, + d_deflt : 1, + d_dup : 1, + d_wrtok : 1; +#elif defined(_BIT_FIELDS_HTOL) + uchar_t d_wrtok : 1, + d_dup : 1, + d_deflt : 1, + d_rsvd : 4, + d_dlv : 1; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uchar_t d_len[2], + d_bpm[3], + d_width[2], + d_tracks[2], + d_capacity[4], + d_organization[8], + d_name[8], + d_description[20]; +} ssc_density_t; + +typedef struct ssc_density_media { + ssc_density_header_t d_hdr; + uchar_t d_type, + d_resvd1, + d_len[2], + d_num_codes, + d_codes[9], + d_width[2], + d_medium_len[2], + d_rsvd2[2], + d_organization[8], + d_medium_name[8], + d_description[20]; +} ssc_density_media_t; + +/* + * MODE_SENSE/MODE_SELECT, Page Code 0xf + * Data Compression + */ +typedef struct ssc_data_compression { + struct mode_page mode_page; +#if defined(_BIT_FIELDS_LTOH) + uchar_t c_rsvd1 : 6, + c_dcc : 1, + c_dce : 1; + uchar_t c_rsdv2 : 5, + c_red : 2, + c_dde : 1; +#elif defined(_BIT_FIELDS_HTOL) + uchar_t c_dce : 1, + c_dcc : 1, + c_rsvd1 : 6; + uchar_t c_dde : 1, + c_red : 2, + c_rsvd2 : 5; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uchar_t c_compression_algorithm[4], + c_decompression_algorithm[4], + c_rsvd3[4]; +} ssc_data_compression_t; + +/* + * MODE_SENSE/MODE_SELECT, Page Code 0x10 + * Device Configuration + */ +typedef struct ssc_device_config { + struct mode_page mode_page; +#if defined(_BIT_FIELDS_LTOH) + uchar_t active_format : 5, + caf : 1, + obsolete1 : 1, + rsvd : 1; + uchar_t active_partion, + wo_buf_ratio, + ro_buf_ratio, + wr_delay_time[2]; + uchar_t rew : 1, + robo : 1, + socf : 2, + avc : 1, + obsolete2 : 1, + lois : 1, + obr : 1; + uchar_t obsolete3; + uchar_t bam : 1, + baml : 1, + swp : 1, + sew : 1, + eeg : 1, + eod_defined : 3; + uchar_t obj_size_ew[3], + data_comp_algorithm; + uchar_t prmwp : 1, + perswp : 1, + asocwp : 1, + rewind_on_reset : 2, + oir : 1, + wtre : 2; +#elif defined(_BIT_FIELDS_HTOL) + uchar_t rsvd : 1, + obsolete1 : 1, + caf : 1, + active_format : 5; + uchar_t active_partion, + wo_buf_ratio, + ro_buf_ratio, + wr_delay_time[2]; + uchar_t obr : 1, + lois : 1, + obsolete2 : 1, + avc : 1, + socf : 2, + robo : 1, + rew : 1; + uchar_t obsolete3; + uchar_t eod_defined : 3, + eeg : 1, + sew : 1, + swp : 1, + baml : 1, + bam : 1; + uchar_t obj_size_ew[3], + data_comp_algorithm; + uchar_t wtre : 2, + oir : 1, + rewind_on_reset : 2, + asocwp : 1, + perswp : 1, + prmwp : 1; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif +} ssc_device_config_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _T10_SSC_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/target.h b/usr/src/cmd/iscsi/iscsitgtd/target.h new file mode 100644 index 0000000000..9209c1d74f --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/target.h @@ -0,0 +1,206 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _TARGET_H +#define _TARGET_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEFAULT_CONFIG_LOCATION "/etc/iscsi/target_config.xml" +#define DEFAULT_TARGET_BASEDIR "/iscsi_targets" +#define DEFAULT_TARGET_LOG "/tmp/target_log" + +/* + * []------------------------------------------------------------------[] + * | Common strings which are used throughout the target. | + * []------------------------------------------------------------------[] + */ +/* + * Target type strings are used to distinguish between the emulated types. + * These strings are stored in the params file so they must not be changed + * without thinking about upgrade issues. + */ +#define TGT_TYPE_DISK "disk" +#define TGT_TYPE_TAPE "tape" +#define TGT_TYPE_OSD "osd" +#define TGT_TYPE_RAW "raw" + +/* + * During the creation phase of a LU it starts out offline during block + * initialization, once initialization is complete it will transition to + * online. If during the initialization an error occurs it will be so marked. + */ +#define TGT_STATUS_OFFLINE "offline" +#define TGT_STATUS_ONLINE "online" +#define TGT_STATUS_ERRORED "errored" + +/* + * Base file names for the logical units (LU). The format used is params.%d and + * lun.%d These are used both to build the LU name and when searching the + * target directory for valid luns. Don't change these names unless the upgrade + * path has been thought about. + */ +#define PARAMBASE "params." +#define LUNBASE "lun." +#define OSDBASE "osd_root." + +#define ISCSI_TARGET_ALIAS "TargetAlias" + +/* + * The IQN names that are created use libuuid + the local target name + * as the idr_str portion of: iqn.1986-03.com.sun:<version>:<id_str> + * In case this changes we also include a version number. Currently + * version 1 is used by the Solaris iSCSI Initiator which has the MAC address, + * timestamp, and hostname. + */ +#define TARGET_NAME_VERS 2 +#define TARGET_NOFILE 10000 + +/* + * Minimum and maximum values for Target Portal Group Tag as specified + * by RFC3720 + */ +#define TPGT_MIN 1 +#define TPGT_MAX 65535 + +/* + * Minimum and maximum values for MaxRecvDataSegmentLength + */ +#define MAXRCVDATA_MIN 512 +#define MAXRCVDATA_MAX ((1 << 24) - 1) + +/* + * Major/minor versioning for the configuration files. + * If we find a configuration file that has a higher + * major number than we support we exit. Major number + * changes are for radical structure differences. Shouldn't + * happen, but we've got a means of detecting such a situation + * a bailing out before doing any damage. Minor number changes + * mean additions to the current format have been added. For + * right now, we use -1, which means ignore the minor number. In + * the future it would be possible for the software to determine + * that a file had certain additions, but maybe not all changes. + */ +#define XML_VERS_MAIN_MAJ 1 +#define XML_VERS_MAIN_MIN -1 +#define XML_VERS_TARG_MAJ 1 +#define XML_VERS_TARG_MIN -1 +#define XML_VERS_LUN_MAJ 1 +#define XML_VERS_LUN_MIN -1 +#define XML_VERS_RESULT_MAJ 1 +#define XML_VERS_RESULT_MIN -1 + +/* + * Default values of the LUN parameters + */ +#define DEFAULT_LUN_SIZE ((1024 * 1024 * 1024) / 512) +#define DEFAULT_RPM 7200 +#define DEFAULT_HEADS 16 +#define DEFAULT_CYLINDERS 100 +#define DEFAULT_SPT 128 +#define DEFAULT_BYTES_PER 512 +#define DEFAULT_INTERLEAVE 1 +#define DEFAULT_PID "SOLARIS" +#define DEFAULT_VID "SUN" +#define DEFAULT_REVISION "1" + +/* + * SPC-3 revision 21c, section 7.6.4.4.4 + * EUI-64 based 16-byte IDENTIFIER field format + */ +typedef struct eui_16 { + uchar_t e_vers, + e_resrv1, + e_mac[6], + e_company_id[3], + e_resv2, + e_timestamp[4]; +} eui_16_t; + +#define SUN_EUI_16_VERS 1 + +#define SUN_EN 0x2a; +#define MIN_VAL 4 + +#ifndef min +#define min(a, b) ((a) > (b) ? (b) : (a)) +#endif +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +typedef struct { + char *name; + char *(*func)(char *, char *); +} admin_table_t; + +#include <sys/socket.h> +#include "xml.h" +#include "queue.h" + +void create_func(xml_node_t *, target_queue_t *, target_queue_t *); +void modify_func(xml_node_t *, target_queue_t *, target_queue_t *); +void remove_func(xml_node_t *, target_queue_t *, target_queue_t *); +void list_func(xml_node_t *, target_queue_t *, target_queue_t *); +void logout_targ(char *targ); +char *update_basedir(char *, char *); +char *valid_radius_srv(char *name, char *prop); +Boolean_t if_find_mac(target_queue_t *mgmt); +void if_target_address(char **text, int *text_length, struct sockaddr *sp); +Boolean_t process_target_config(); + + +extern admin_table_t admin_prop_list[]; +extern char *target_basedir; +extern char *target_log; +extern char *config_file; +extern xml_node_t *targets_config; +extern xml_node_t *main_config; +extern uchar_t *mac_addr; +extern size_t mac_len; +extern int main_vers_maj, + main_vers_min, + targets_vers_maj, + targets_vers_min; +extern Boolean_t enforce_strict_guid, + thin_provisioning, + disable_tpgs, + dbg_timestamps; +extern pthread_mutex_t targ_config_mutex; + +#ifdef __cplusplus +} +#endif + +#endif /* _TARGET_H */ diff --git a/usr/src/cmd/iscsi/iscsitgtd/util.c b/usr/src/cmd/iscsi/iscsitgtd/util.c new file mode 100644 index 0000000000..ddddc4c678 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/util.c @@ -0,0 +1,1691 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> +#include <dirent.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <utility.h> +#include <fcntl.h> +#include <syslog.h> + +#include "local_types.h" +#include "target.h" +#include "utility.h" +#include "errcode.h" +#include "xml.h" +#include <sys/scsi/generic/commands.h> + +#define CRC32_STR "CRC32C" +#define NONE_STR "None" + +static thick_provo_t *thick_head, + *thick_tail; +pthread_mutex_t thick_mutex; + +static Boolean_t connection_parameters_get(iscsi_conn_t *c, char *targ_name); + +void +util_init() +{ + (void) pthread_mutex_init(&thick_mutex, NULL); +} + +/* + * []---- + * | read_retry -- read data into buffer + * | + * | There have been problems on Solaris when reads from a socket + * | fail to return the expected amount of data. It seems to occur + * | around 2KB, but nothing reproduciable. By issuing multiple reads + * | and adjusting the buffer pointer we can get around this problem. + * | The suspection at this point is an interaction with threads, sockets, + * | and large iSCSI transfers. + * []---- + */ +int +read_retry(int fd, char *buf, int count) +{ +#define SMALLER_READS +#ifdef SMALLER_READS + int cc, + min, + total = 0; + + while (count) { + min = MIN(count, 512); + cc = read(fd, buf, min); + if (cc == -1) { + if ((errno == EAGAIN) || (errno == 0)) + continue; + else + return (-1); + } + if (cc == 0) + break; + buf += cc; + count -= cc; + total += cc; + } + return (total); +#else + return (read(fd, buf, count)); +#endif +} + +/* + * []---- + * | check_access -- see if the requesting initiator is in the ACL + * | + * | Optionally will also check to see if this initiator requires + * | authentication. + * []---- + */ +Boolean_t +check_access(xml_node_t *targ, char *initiator_name, Boolean_t req_chap) +{ + xml_node_t *acl, + *inode = NULL, + *tgt_initiator = NULL; + char *dummy; + Boolean_t valid = False, + found_chap = False; + + /* + * If there's no ACL for this target everyone has access. + */ + if ((acl = xml_node_next(targ, XML_ELEMENT_ACLLIST, NULL)) == NULL) + return (True); + + /* + * Find the local initiator name and also save the knowledge + * if the initiator had a CHAP secret. + */ + while ((inode = xml_node_next(main_config, XML_ELEMENT_INIT, + inode)) != NULL) { + if (xml_find_value_str(inode, XML_ELEMENT_INAME, &dummy) == + True) { + if (strcmp(dummy, initiator_name) == 0) { + free(dummy); + if (xml_find_value_str(inode, + XML_ELEMENT_CHAPSECRET, &dummy) == True) { + free(dummy); + found_chap = True; + } + break; + } else { + free(dummy); + } + } + } + + if ((acl != NULL) && (inode == NULL)) + return (False); + + while ((tgt_initiator = xml_node_next(acl, XML_ELEMENT_INIT, + tgt_initiator)) != NULL) { + + if (strcmp(inode->x_value, tgt_initiator->x_value) == 0) { + valid = True; + break; + } + } + + if (valid == True) { + + /* + * If req_chap is True it means the login code hasn't gone + * through the authentication phase and it's trying to + * determine if the initiator should have done so. If + * we find a CHAP-secret then this routine will fail. + * No CHAP-secret for an initiator just means that a + * simple ACL list is used. This can be spoofed easily + * enough and is mainly used to limit the number of + * targets an initiator would see. + */ + if ((req_chap == True) && (found_chap == True)) + valid = False; + } + + return (valid); +} + +/* + * []---- + * | convert_local_tpgt -- Convert a local tpgt name to real addresses + * | + * | To simplify the configuration files targets only have a target portal + * | group tag string(s) associated. In the main configuration file there's + * | a tpgt element which has one or more ip-address elements. So the tag + * | is located and the actual data is inserted into the outgoing stream. + * []---- + */ +static Boolean_t +convert_local_tpgt(char **text, int *text_length, char *local_tpgt) +{ + xml_node_t *tpgt = NULL, + *x; + char buf[80]; + char ipaddr[4]; + + while ((tpgt = xml_node_next(main_config, XML_ELEMENT_TPGT, + tpgt)) != NULL) { + if (strcmp(tpgt->x_value, local_tpgt) == 0) { + + /* + * The only children of the tpgt element are + * ip-address elements. The value of each element is + * the string we need to use. So, we don't need to + * check the node's name to see if this is correct or + * not. + */ + if (tpgt->x_child == NULL) { + return (False); + } + for (x = tpgt->x_child; x; x = x->x_sibling) { + if (inet_pton(AF_INET, x->x_value, &ipaddr) + == 1) { + /* + * Valid IPv4 address + */ + (void) snprintf(buf, sizeof (buf), + "%s,%s", x->x_value, local_tpgt); + } else { + /* + * Invalid IPv4 address + * try with brackets (RFC2732) + */ + (void) snprintf(buf, sizeof (buf), + "[%s],%s", x->x_value, local_tpgt); + } + (void) add_text(text, text_length, + "TargetAddress", buf); + } + break; + } + } + + return (True); +} + +/* + * []---- + * | add_target_address -- find and add any target address information + * []---- + */ +static void +add_target_address(iscsi_conn_t *c, char **text, int *text_length, + xml_node_t *targ) +{ + xml_node_t *tpgt_list, + *tpgt = NULL; + struct sockaddr_in *sp4; + struct sockaddr_in6 *sp6; + /* + * 7 is enough room for the largest TPGT of "65536", the ',' and a NULL + */ + char buf[INET6_ADDRSTRLEN + 7], + net_buf[INET6_ADDRSTRLEN]; + + if ((tpgt_list = xml_node_next(targ, XML_ELEMENT_TPGTLIST, + NULL)) == NULL) { + if_target_address(text, text_length, + (struct sockaddr *)&c->c_target_sockaddr); + return; + } + + while ((tpgt = xml_node_next(tpgt_list, XML_ELEMENT_TPGT, + tpgt)) != NULL) { + if (convert_local_tpgt(text, text_length, tpgt->x_value) == + False) { + if (c->c_target_sockaddr.ss_family == AF_INET) { + /*CSTYLED*/ + sp4 = (struct sockaddr_in *)&c->c_target_sockaddr; + (void) snprintf(buf, sizeof (buf), "%s,%s", + inet_ntop(sp4->sin_family, + (void *)&sp4->sin_addr, + net_buf, sizeof (net_buf)), + tpgt->x_value); + } else { + /*CSTYLED*/ + sp6 = (struct sockaddr_in6 *)&c->c_target_sockaddr; + (void) snprintf(buf, sizeof (buf), "%s,%s", + inet_ntop(sp6->sin6_family, + (void *)&sp6->sin6_addr, + net_buf, sizeof (net_buf)), + tpgt->x_value); + } + (void) add_text(text, text_length, "TargetAddress", + buf); + } + } +} + +#ifdef notused +/* + * []---- + * | create_tpgt_list -- create XML list of tpgt's for target + * | + * | Caller must free the data returned. The incoming tname is the + * | iSCSI node name (we normally use the IQN form). + * []---- + */ +char * +create_tpgt_list(char *tname) +{ + char *buf = NULL, + *p; + xml_node_t *tnode = NULL, + *tpgtlist = NULL, + *tpgt = NULL; + + while ((tnode = xml_node_next(targets_config, XML_ELEMENT_TARG, + tnode)) != NULL) { + if (xml_find_value_str(tnode, XML_ELEMENT_INAME, &p) == + False) { + continue; + } + if (strcmp(tname, p) != 0) { + free(p); + continue; + } else + free(p); + if ((tpgtlist = xml_node_next(tnode, XML_ELEMENT_TPGTLIST, + NULL)) == NULL) { + + /* + * No TPGT list available so just return a NULL. + * This is not an error. + */ + return (NULL); + } + + buf_add_tag(&buf, XML_ELEMENT_TPGTLIST, Tag_Start); + while ((tpgt = xml_node_next(tpgtlist, XML_ELEMENT_TPGT, + tpgt)) != NULL) { + xml_add_tag(&buf, XML_ELEMENT_TPGT, tpgt->x_value); + } + buf_add_tag(&buf, XML_ELEMENT_TPGTLIST, Tag_End); + return (buf); + } + return (NULL); +} +#endif + +/* + * []---- + * | add_targets -- add TargetName and TargetAddress to text argument + * | + * | Add targets which this initiator is allowed to see based on + * | the access_list associated with a target. If a target doesn't + * | have an access list then let everyone see it. + * []---- + */ +static Boolean_t +add_targets(iscsi_conn_t *c, char **text, int *text_length) +{ + xml_node_t *targ = NULL; + Boolean_t rval = True; + char *targ_name = NULL; + + while ((rval == True) && ((targ = xml_node_next(targets_config, + XML_ELEMENT_TARG, targ)) != NULL)) { + + if (check_access(targ, c->c_sess->s_i_name, False) == True) { + + if (xml_find_value_str(targ, XML_ELEMENT_INAME, + &targ_name) == False) { + rval = False; + break; + } + queue_prt(c->c_mgmtq, Q_CONN_LOGIN, + "CON%x %24s = %s", c->c_num, "TargetName", + targ_name); + + (void) add_text(text, text_length, "TargetName", + targ_name); + free(targ_name); + add_target_address(c, text, text_length, targ); + } + } + return (rval); +} + +#ifdef notused +/* + * []---- + * | add_target_alias -- Add TargetAlias property if available. + * []---- + */ +Boolean_t +add_target_alias(iscsi_conn_t *c, char **text, int *text_length) +{ + xml_node_t *targ = NULL; + char *targ_name = NULL, + *alias_name = NULL; + Boolean_t rval = True; + + /* + * Most discovery sessions don't have a target name which is + * what they're looking for in the first place. + */ + if (c->c_sess->s_type == SessionDiscovery) + return (True); + + while ((targ = xml_node_next(targets_config, XML_ELEMENT_TARG, + targ)) != NULL) { + + /* + * This is a hard error. Since we use node-name quite often + * up to this point in time if this fails either we've run + * out of memory or there's a memory corruption problem. + */ + if (xml_find_value_str(targ, XML_ELEMENT_INAME, &targ_name) == + False) + return (False); + + if (strcmp(targ_name, c->c_sess->s_t_name) == 0) { + if (xml_find_value_str(targ, XML_ELEMENT_ALIAS, + &alias_name) == True) { + + /* + * Target name matches and we've got an alias. + */ + rval = add_text(text, text_length, + ISCSI_TARGET_ALIAS, alias_name); + queue_prt(c->c_mgmtq, Q_CONN_LOGIN, + "CON%x %-24s = %s", c->c_num, + ISCSI_TARGET_ALIAS, alias_name); + break; + } else if (xml_find_value_str(targ, XML_ELEMENT_TARG, + &alias_name) == True) { + + /* + * If we don't have a user settable alias + * then use the local name the administrator + * setup. + */ + rval = add_text(text, text_length, + ISCSI_TARGET_ALIAS, alias_name); + queue_prt(c->c_mgmtq, Q_CONN_LOGIN, + "CON%x %-24s = %s", c->c_num, + ISCSI_TARGET_ALIAS, alias_name); + break; + } + } + + /* + * We don't have to worry about freeing alias_name because + * it will only be set if we found a match and had an alias + * in which case we'd never get here. + */ + free(targ_name); + targ_name = NULL; + } + + if (targ_name) + free(targ_name); + if (alias_name) + free(alias_name); + + return (rval); +} +#endif + +/* + * []---- + * | add_text -- Add new name/value pair to possibly existing string + * []---- + */ +Boolean_t +add_text(char **text, int *current_length, char *name, char *val) +{ + int dlen = *current_length, + plen; + char *p; + + /* + * Length is 'name' + separator + 'value' + NULL + */ + plen = strlen(name) + 1 + strlen(val) + 1; + + if (dlen) { + if ((p = (char *)realloc(*text, dlen + plen)) == NULL) + return (False); + } else { + if ((p = (char *)malloc(plen)) == NULL) + return (False); + } + + *text = p; + p = *text + dlen; + + (void) snprintf(p, plen, "%s%c%s", name, ISCSI_TEXT_SEPARATOR, val); + *current_length = dlen + plen; + + return (True); +} + +static void +send_named_msg(iscsi_conn_t *c, msg_type_t t, char *name) +{ + target_queue_t *q = queue_alloc(); + msg_t *m; + name_request_t n; + + n.nr_q = q; + n.nr_name = name; + + queue_message_set(c->c_sessq, 0, t, &n); + m = queue_message_get(q); + queue_message_free(m); + queue_free(q, NULL); +} + +static Boolean_t +parse_digest_vals(Boolean_t *bp, char *name, char *val, char **text, int *len) +{ + Boolean_t rval; + + /* + * It's the initiators data so we'll allow them + * to determine if CRC checks should be enabled + * or not. So, look at the first token, which + * declares their preference, and use that. + */ + if (strncmp(val, CRC32_STR, strlen(CRC32_STR)) == 0) { + *bp = True; + rval = add_text(text, len, name, CRC32_STR); + } else if (strncmp(val, NONE_STR, strlen(NONE_STR)) == 0) { + *bp = False; + rval = add_text(text, len, name, NONE_STR); + } else { + *bp = False; + rval = add_text(text, len, name, "Reject"); + } + + return (rval); +} + +/* + * []---- + * | parse_text -- receive text information from initiator and parse + * | + * | Read in the current data based on the amount which the login PDU says + * | should be available. Add it to the end of previous data if it exists. + * | Previous data would be from a PDU which had the 'C' bit set and was + * | stored in the connection. + * | + * | Once values for parameter name has been selected store outgoing string + * | in text message for response. + * | + * | If errcode is non-NULL the appropriate login error code will be + * | stored. + * []---- + */ +Boolean_t +parse_text(iscsi_conn_t *c, int dlen, char **text, int *text_length, + int *errcode) +{ + char *p = NULL, + *n, + *cur_pair, + param_rsp[32]; + int plen; /* pair length */ + Boolean_t rval = True; + char param_buf[16]; + + if ((p = (char *)malloc(dlen)) == NULL) + return (False); + + /* + * Read in data to buffer. + */ + if (read(c->c_fd, p, dlen) != dlen) { + free(p); + return (False); + } + + queue_prt(c->c_mgmtq, Q_CONN_NONIO, "CON%x Available text size %d", + c->c_num, dlen); + + /* + * Read in and toss any pad data + */ + if (dlen % ISCSI_PAD_WORD_LEN) { + char junk[ISCSI_PAD_WORD_LEN]; + int pad_len = ISCSI_PAD_WORD_LEN - (dlen % ISCSI_PAD_WORD_LEN); + + if (read(c->c_fd, junk, pad_len) != pad_len) { + free(p); + return (False); + } + } + + if (c->c_text_area != NULL) { + if ((n = (char *)realloc(c->c_text_area, + c->c_text_len + dlen)) == NULL) { + free(p); + return (False); + } + bcopy(p, n + c->c_text_len, dlen); + + /* + * No longer need the space allocated to 'p' since it + * will point to the aggregated area of all data. + */ + free(p); + + /* + * Point 'p' to this new area for parsing and save the + * combined length in dlen. + */ + p = n; + dlen += c->c_text_len; + + /* + * Clear the indication that space has been allocated + */ + c->c_text_area = NULL; + c->c_text_len = 0; + } + + /* + * At this point 'p' points to the name/value parameters. Need + * to cycle through each pair. + */ + n = p; + while (dlen > 0) { + cur_pair = n; + + plen = strlen(n); + if ((n = strchr(cur_pair, ISCSI_TEXT_SEPARATOR)) == NULL) { + if (errcode != NULL) + *errcode = + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_INIT_ERR; + rval = False; + break; + } else + *n++ = '\0'; + + queue_prt(c->c_mgmtq, Q_CONN_LOGIN, "CON%x %-24s = %s", + c->c_num, cur_pair, n); + + /* + * At this point, 'cur_pair' points at the name and 'n' + * points at the value. + */ + if (strcmp("HeaderDigest", cur_pair) == 0) { + + rval = parse_digest_vals(&c->c_header_digest, + cur_pair, n, text, text_length); + + } else if (strcmp("DataDigest", cur_pair) == 0) { + + rval = parse_digest_vals(&c->c_data_digest, cur_pair, + n, text, text_length); + + } else if (strcmp("InitiatorName", cur_pair) == 0) { + + send_named_msg(c, msg_initiator_name, n); + + } else if (strcmp("InitiatorAlias", cur_pair) == 0) { + + send_named_msg(c, msg_initiator_alias, n); + + } else if (strcmp("TargetName", cur_pair) == 0) { + + send_named_msg(c, msg_target_name, n); + + /* + * Had to wait until now before loading any parameters + * because they are based on the TargetName which + * hasn't been known until now. This might fail + * because the target doesn't exist or the initiator + * doesn't have permission to access this target. + */ + if ((rval = connection_parameters_get(c, n)) == False) { + *errcode = + (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) | + ISCSI_LOGIN_STATUS_TGT_FORBIDDEN; + } else if ((rval = add_text(text, text_length, + "TargetAlias", c->c_targ_alias)) == True) { + + /* + * Add TPGT now + */ + (void) snprintf(param_buf, sizeof (param_buf), + "%d", c->c_tpgt); + rval = add_text(text, text_length, + "TargetPortalGroupTag", param_buf); + } + + } else if (strcmp("SessionType", cur_pair) == 0) { + + c->c_sess->s_type = strcmp(n, "Discovery") == 0 ? + SessionDiscovery : SessionNormal; + + } else if (strcmp("SendTargets", cur_pair) == 0) { + + if ((c->c_sess->s_type != SessionDiscovery) && + (strcmp("All", n) == 0)) { + rval = add_text(text, text_length, cur_pair, + "Irrelevant"); + } else { + rval = add_targets(c, text, text_length); + } + + } else if (strcmp("MaxRecvDataSegmentLength", cur_pair) == 0) { + + c->c_max_recv_data = strtol(n, NULL, 0); + rval = add_text(text, text_length, cur_pair, n); + + } else if (strcmp("DefaultTime2Wait", cur_pair) == 0) { + + c->c_default_time_2_wait = strtol(n, NULL, 0); + + } else if (strcmp("DefaultTime2Retain", cur_pair) == 0) { + + c->c_default_time_2_retain = strtol(n, NULL, 0); + + } else if (strcmp("ErrorRecoveryLevel", cur_pair) == 0) { + + c->c_erl = 0; + (void) snprintf(param_rsp, sizeof (param_rsp), + "%d", c->c_erl); + rval = add_text(text, text_length, + cur_pair, param_rsp); + + } else if (strcmp("IFMarker", cur_pair) == 0) { + + c->c_ifmarker = False; + rval = add_text(text, text_length, cur_pair, "No"); + + } else if (strcmp("OFMarker", cur_pair) == 0) { + + c->c_ofmarker = False; + rval = add_text(text, text_length, cur_pair, "No"); + + } else if (strcmp("InitialR2T", cur_pair) == 0) { + + c->c_initialR2T = True; + rval = add_text(text, text_length, cur_pair, "Yes"); + + } else if (strcmp("ImmediateData", cur_pair) == 0) { + + /* + * Since we can handle immediate data without + * a problem just echo back what the initiator + * sends. If the initiator decides to violate + * the spec by sending immediate data even though + * they've disabled it, it's there problem and + * we'll deal with the data. + */ + c->c_immediate_data = strcmp(n, "No") ? True : False; + rval = add_text(text, text_length, cur_pair, n); + + } else if (strcmp("MaxBurstLength", cur_pair) == 0) { + + c->c_max_burst_len = strtol(n, NULL, 0); + + } else if (strcmp("FirstBurstLength", cur_pair) == 0) { + + /* + * We can handle anything the initiator wishes + * to shove in our direction. So, store the value + * in case we ever wish to validate input data, + * but there's no real need to do so. + */ + c->c_first_burst_len = strtol(n, NULL, 0); + + } else if (strcmp("MaxOutstandingR2T", cur_pair) == 0) { + + /* + * Save the value, but at most we'll toss out + * one R2T packet. + */ + c->c_max_outstanding_r2t = strtol(n, NULL, 0); + + } else if (strcmp("MaxConnections", cur_pair) == 0) { + + /* ---- To be fixed ---- */ + c->c_max_connections = 1; + (void) snprintf(param_rsp, sizeof (param_rsp), + "%d", c->c_max_connections); + rval = add_text(text, text_length, + cur_pair, param_rsp); + + } else if (strcmp("DataPDUInOrder", cur_pair) == 0) { + + /* + * We can handle DataPDU's out of order and + * currently we'll only send them in order. We're + * to far removed from the hardware to see data + * coming off of the platters out of order so + * it's unlikely we'd ever implement this feature. + * Store the parameter and echo back the initiators + * request. + */ + c->c_data_pdu_in_order = strcmp(n, "Yes") == 0 ? + True : False; + rval = add_text(text, text_length, cur_pair, n); + + } else if (strcmp("DataSequenceInOrder", cur_pair) == 0) { + + /* + * Currently we're set up to look at and require + * PDU sequence numbers be in order. The check + * now is only done as a prelude to supporting + * MC/S and guaranteeing the order of incoming + * packetss on different connections. + */ + c->c_data_sequence_in_order = True; + rval = add_text(text, text_length, cur_pair, "Yes"); + + } else if ((strcmp("AuthMethod", cur_pair) == 0) || + (strcmp("CHAP_A", cur_pair) == 0) || + (strcmp("CHAP_I", cur_pair) == 0) || + (strcmp("CHAP_C", cur_pair) == 0) || + (strcmp("CHAP_N", cur_pair) == 0) || + (strcmp("CHAP_R", cur_pair) == 0)) { + + rval = add_text(&(c->auth_text), &(c->auth_text_length), + cur_pair, n); + + } else { + + /* + * It's perfectly legitimate for an initiator to + * send us a parameter we don't currently understand. + * For example, an initiator that supports iSER will + * send an RDMA options parameter. If we respond with + * a valid return value it knows to switch to iSER + * for future processing. + */ + rval = add_text(text, text_length, + cur_pair, "NotUnderstood"); + + /* + * Go ahead a log this information in case we see + * something unexpected. + */ + queue_prt(c->c_mgmtq, Q_CONN_ERRS, + "CON%x Unknown parameter %s=%s", + c->c_num, cur_pair, n); + } + + if (rval == False) { + /* + * Make sure the caller wants error status and that it + * hasn't already been set. + */ + if ((errcode != NULL) && (*errcode == 0)) + *errcode = + (ISCSI_STATUS_CLASS_TARGET_ERR << 8) | + ISCSI_LOGIN_STATUS_TARGET_ERROR; + break; + } + + /* + * next pair of parameters. 1 is added to include the NULL + * byte and the end of each string. + */ + n = cur_pair + plen + 1; + dlen -= (plen + 1); + } + + if (p != NULL) + free(p); + + return (rval); +} + +void +connection_parameters_default(iscsi_conn_t *c) +{ + c->c_tpgt = 1; +} + +/* + * []---- + * | find_main_tpgt -- Looks up the IP address and finds a match TPGT + * | + * | If no, TPGT for this address exists the routine returns 0 which + * | is an illegal TPGT value. + * []---- + */ +int +find_main_tpgt(struct sockaddr_storage *pst) +{ + char ip_addr[16]; + xml_node_t *tpgt = NULL, + *ip_node = NULL; + struct in_addr addr; + struct in6_addr addr6; + + /* + * Hardly can you believe that such struct-to-struct + * assignment IS valid. + */ + addr = ((struct sockaddr_in *)pst)->sin_addr; + addr6 = ((struct sockaddr_in6 *)pst)->sin6_addr; + + while ((tpgt = xml_node_next(main_config, XML_ELEMENT_TPGT, + tpgt)) != NULL) { + + ip_node = NULL; + while ((ip_node = xml_node_next(tpgt, XML_ELEMENT_IPADDR, + ip_node)) != NULL) { + + if (pst->ss_family == AF_INET) { + + if (inet_pton(AF_INET, ip_node->x_value, + ip_addr) != 1) { + continue; + } + if (bcmp(ip_addr, &addr, + sizeof (struct in_addr)) == 0) { + return (atoi(tpgt->x_value)); + } + } else if (pst->ss_family == AF_INET6) { + + if (inet_pton(AF_INET6, ip_node->x_value, + ip_addr) != 1) { + continue; + } + if (bcmp(ip_addr, &addr6, + sizeof (struct in6_addr)) == 0) { + return (atoi(tpgt->x_value)); + } + } + } + } + + return (1); +} + +static int +convert_to_tpgt(iscsi_conn_t *c, xml_node_t *targ) +{ + xml_node_t *list, + *tpgt = NULL; + int addr_tpgt, + pos_tpgt; + + /* + * If we don't find our IP in the general configuration list + * we'll use the default value which is 1 according to RFC3720. + */ + addr_tpgt = find_main_tpgt(&(c->c_target_sockaddr)); + + /* + * If this target doesn't have a list of target portal group tags + * just return the default which is 1. + */ + list = xml_node_next(targ, XML_ELEMENT_TPGTLIST, NULL); + if (list == NULL) + return (addr_tpgt); + + while ((tpgt = xml_node_next(list, XML_ELEMENT_TPGT, tpgt)) != NULL) { + (void) xml_find_value_int(tpgt, XML_ELEMENT_TPGT, &pos_tpgt); + if (pos_tpgt == addr_tpgt) { + return (addr_tpgt); + } + } + + return (0); +} + +/* + * []---- + * | find_target_node -- given a target IQN name, return the XML node + * []---- + */ +xml_node_t * +find_target_node(char *targ_name) +{ + xml_node_t *tnode = NULL; + char *iname; + + while ((tnode = xml_node_next(targets_config, XML_ELEMENT_TARG, + tnode)) != NULL) { + if (xml_find_value_str(tnode, XML_ELEMENT_INAME, &iname) == + True) { + if (strcmp(iname, targ_name) == 0) { + free(iname); + return (tnode); + } else + free(iname); + } + } + + return (NULL); +} + +static Boolean_t +connection_parameters_get(iscsi_conn_t *c, char *targ_name) +{ + xml_node_t *targ, + *alias; + Boolean_t rval = False; + + if ((targ = find_target_node(targ_name)) != NULL) { + + if (check_access(targ, c->c_sess->s_i_name, False) == False) + return (False); + + /* + * Have a valid node for our target. Start looking + * for connection oriented parameters. + */ + if ((c->c_tpgt = convert_to_tpgt(c, targ)) == 0) + return (False); + if ((alias = xml_node_next(targ, XML_ELEMENT_ALIAS, NULL)) == + NULL) { + (void) xml_find_value_str(targ, XML_ELEMENT_TARG, + &c->c_targ_alias); + } else { + (void) xml_find_value_str(alias, XML_ELEMENT_ALIAS, + &c->c_targ_alias); + } + + (void) xml_find_value_int(targ, XML_ELEMENT_MAXCMDS, + &c->c_maxcmdsn); + rval = True; + } + + return (rval); +} + +Boolean_t +validate_version(xml_node_t *node, int *maj_p, int *min_p) +{ + char *vers_str = NULL, + *minor_part; + int maj, + min; + + if ((xml_find_attr_str(node, XML_ELEMENT_VERS, &vers_str) == False) || + (vers_str == NULL)) + return (False); + + maj = strtol(vers_str, &minor_part, 0); + if ((maj > *maj_p) || (minor_part == NULL) || (*minor_part != '.')) { + free(vers_str); + return (False); + } + + min = strtol(&minor_part[1], NULL, 0); + *maj_p = maj; + *min_p = min; + free(vers_str); + + return (True); +} + +/* + * []---- + * | sna_lt -- Serial Number Arithmetic, 32 bits, less than, RFC1982 + * []---- + */ +int +sna_lt(uint32_t n1, uint32_t n2) +{ + return ((n1 != n2) && + (((n1 < n2) && ((n2 - n1) < SNA32_CHECK)) || + ((n1 > n2) && ((n2 - n1) < SNA32_CHECK)))); +} + +/* + * []---- + * sna_lte -- Serial Number Arithmetic, 32 bits, less than, RFC1982 + * []---- + */ +int +sna_lte(uint32_t n1, uint32_t n2) +{ + return ((n1 == n2) || + (((n1 < n2) && ((n2 - n1) < SNA32_CHECK)) || + ((n1 > n2) && ((n2 - n1) < SNA32_CHECK)))); +} + +Boolean_t +update_config_main(char **msg) +{ + if (xml_dump2file(main_config, config_file) == False) { + xml_rtn_msg(msg, ERR_UPDATE_MAINCFG_FAILED); + return (False); + } else + return (True); +} + +Boolean_t +update_config_targets(char **msg) +{ + char path[MAXPATHLEN]; + + (void) snprintf(path, sizeof (path), "%s/config.xml", target_basedir); + if (xml_dump2file(targets_config, path) == False) { + if (msg != NULL) + xml_rtn_msg(msg, ERR_UPDATE_TARGCFG_FAILED); + return (False); + } else + return (True); +} + +Boolean_t +util_create_guid(char **guid) +{ + eui_16_t eui; + /* + * We only have room for 32bits of data in the GUID. The hiword/loword + * macros will not work on 64bit variables. The work, but produce + * invalid results on Big Endian based machines. + */ + uint32_t tval = (uint_t)time((time_t *)0); + size_t guid_size; + int i, + fd; + + if ((mac_len == 0) && (if_find_mac(NULL) == False)) { + + /* + * By default strict GUID generation is enforced. This can + * be disabled by using the correct XML tag in the configuration + * file. + */ + if (enforce_strict_guid == True) + return (False); + + /* + * There's no MAC address available and we've even tried + * a second time to get one. So fallback to using a random + * number for the MAC address. + */ + if ((fd = open("/dev/random", O_RDONLY)) < 0) + return (False); + if (read(fd, &eui, sizeof (eui)) != sizeof (eui)) + return (False); + (void) close(fd); + + eui.e_vers = SUN_EUI_16_VERS; + eui.e_company_id[0] = 0; + eui.e_company_id[1] = 0; + eui.e_company_id[2] = SUN_EN; + + } else { + bzero(&eui, sizeof (eui)); + + eui.e_vers = SUN_EUI_16_VERS; + /* ---- [0] & [1] are zero for Sun's IEEE identifier ---- */ + eui.e_company_id[2] = SUN_EN; + eui.e_timestamp[0] = hibyte(hiword(tval)); + eui.e_timestamp[1] = lobyte(hiword(tval)); + eui.e_timestamp[2] = hibyte(loword(tval)); + eui.e_timestamp[3] = lobyte(loword(tval)); + for (i = 0; i < min(mac_len, sizeof (eui.e_mac)); i++) { + eui.e_mac[i] = mac_addr[i]; + } + + /* + * To prevent duplicate GUIDs we need to sleep for one + * second here since part of the GUID is a time stamp with + * a one second resolution. + */ + sleep(1); + } + + if (xml_encode_bytes((uint8_t *)&eui, sizeof (eui), guid, + &guid_size) == False) { + return (False); + } else + return (True); +} + +/* + * []---- + * | create_geom -- based on size determine best fit for CHS + * | + * | Given size in bytes which will be adjusted to sectors, find + * | the best fit for making C * H * S == sectors + * []---- + */ +void +create_geom(diskaddr_t size, int *cylinder, int *heads, int *spt) +{ + diskaddr_t sects = size >> 9, + c, h, s, t; + int pass; + + /* + * For certain odd size LU we can't generate correct geometry. + * If this occurs the values will be set to zero and the MODE + * pages will return unsupported. + */ + *cylinder = 0; + *heads = 0; + *spt = 0; + + for (pass = 0; pass < 2; pass++) + for (c = 0x8000; c > 0; c >>= 1) { + t = sects / c; + if ((t == 0) || ((sects % c) != 0)) + continue; + for (h = 1; h < 0xff; h++) + if ((t % h) == 0) { + s = t / h; + if (s > 0xffff) + continue; + if ((pass == 0) && + ((c < MIN_VAL) || (h < MIN_VAL) || + (s < MIN_VAL))) { + continue; + } + + *cylinder = (int)c; + *heads = (int)h; + *spt = (int)s; + return; + } + } +} + +/* + * []---- + * | strtol_multiplier -- common method to deal with human type numbers + * []---- + */ +Boolean_t +strtoll_multiplier(char *str, uint64_t *sp) +{ + char *m; + uint64_t size; + + size = strtoll(str, &m, 0); + if (m && *m) { + switch (*m) { + case 't': + case 'T': + size *= 1024; + /*FALLTHRU*/ + case 'g': + case 'G': + size *= 1024; + /*FALLTHRU*/ + case 'm': + case 'M': + size *= 1024; + /*FALLTHRU*/ + case 'k': + case 'K': + size *= 1024; + break; + + default: + return (False); + } + } + + *sp = size; + return (True); +} + +/* + * []---- + * | util_title -- print out start/end title in consistent manner + * []---- + */ +void +util_title(target_queue_t *q, int type, int num, char *title) +{ + char *type_str; + int len, + pad; + + len = strlen(title); + pad = len & 1; + + switch (type) { + case Q_CONN_LOGIN: + case Q_CONN_NONIO: + type_str = "CON"; + break; + + case Q_SESS_LOGIN: + case Q_SESS_NONIO: + type_str = "SES"; + break; + + case Q_STE_NONIO: + type_str = "SAM"; + break; + + default: + type_str = "UGH"; + break; + } + + queue_prt(q, type, "%s%x ---- %*s%s%*s ----", type_str, num, + ((60 - len) / 2), "", title, ((60 - len) / 2) + pad, ""); +} + +/* + * []---- + * | task_to_str -- convert task management event to string (DEBUG USE) + * []---- + */ +char * +task_to_str(int func) +{ + switch (func) { + case ISCSI_TM_FUNC_ABORT_TASK: return ("Abort"); + case ISCSI_TM_FUNC_ABORT_TASK_SET: return ("Abort Set"); + case ISCSI_TM_FUNC_CLEAR_ACA: return ("Clear ACA"); + case ISCSI_TM_FUNC_CLEAR_TASK_SET: return ("Clear Task"); + case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: return ("LUN Reset"); + case ISCSI_TM_FUNC_TARGET_WARM_RESET: return ("Target Warm Reset"); + case ISCSI_TM_FUNC_TARGET_COLD_RESET: return ("Target Cold Reset"); + case ISCSI_TM_FUNC_TASK_REASSIGN: return ("Task Reassign"); + default: return ("Unknown"); + } +} + +/* + * []---- + * | xml_rtn_msg -- create a common format for XML replies to management UI + * []---- + */ +void +xml_rtn_msg(char **buf, err_code_t code) +{ + char lbuf[16]; + + buf_add_tag_and_attr(buf, XML_ELEMENT_ERROR, "version='1.0'"); + (void) snprintf(lbuf, sizeof (lbuf), "%d", code); + xml_add_tag(buf, XML_ELEMENT_CODE, lbuf); + xml_add_tag(buf, XML_ELEMENT_MESSAGE, errcode_to_str(code)); + buf_add_tag(buf, XML_ELEMENT_ERROR, Tag_End); +} + +/* + * []---- + * | thick_provo_start -- start an initialization thread for targ/lun + * []---- + */ +void * +thick_provo_start(void *v) +{ + thick_provo_t *tp = (thick_provo_t *)v; + msg_t *m; + Boolean_t rval; + char *err = NULL; + + /* + * Add this threads information to the main queue. This is + * used in case the administrator decides to remove the LU + * before the initialization is complete. + */ + (void) pthread_mutex_lock(&thick_mutex); + if (thick_head == NULL) { + thick_head = tp; + } else { + thick_tail->next = tp; + tp->prev = thick_tail; + } + thick_tail = tp; + (void) pthread_mutex_unlock(&thick_mutex); + + /* + * This let's the parent thread know this thread is running. + */ + queue_message_set(tp->q, 0, msg_mgmt_rply, 0); + + /* ---- Start the initialization of the LU ---- */ + rval = t10_thick_provision(tp->targ_name, tp->lun, tp->q); + + /* ---- Remove from the linked list ---- */ + (void) pthread_mutex_lock(&thick_mutex); + if (tp->prev == NULL) { + assert(tp == thick_head); + thick_head = tp->next; + if (tp->next == NULL) { + assert(tp == thick_tail); + thick_tail = NULL; + } else + tp->next->prev = NULL; + } else { + tp->prev->next = tp->next; + if (tp->next != NULL) + tp->next->prev = tp->prev; + else + thick_tail = tp->prev; + } + (void) pthread_mutex_unlock(&thick_mutex); + + /* + * There's a race condition where t10_thick_provision() could + * finish and before the thick_mutex lock is grabbed again + * that another thread running the thick_provo_stop() could + * find a match and send a shutdown message. If that happened + * that thread would wait forever in queue_message_get(). So, + * After this target/lun pair has been removed check the message + * queue one last time to see if there's a message available. + * If so, send an ack. + */ + m = queue_message_try_get(tp->q); + if (m != NULL) { + assert(m->msg_type == msg_shutdown); + queue_message_set((target_queue_t *)m->msg_data, 0, + msg_shutdown_rsp, 0); + } + + if (rval == True) + iscsi_inventory_change(tp->targ_name); + else { + queue_prt(mgmtq, Q_GEN_ERRS, "Failed to initialize %s/%d", + tp->targ_name, tp->lun); + syslog(LOG_ERR, "Failed to initialize %s, LU%d", tp->targ_name, + tp->lun); + remove_target_common(tp->targ_name, tp->lun, &err); + if (err != NULL) { + + /* + * There's not much we can do here. The most likely + * cause of not being able to remove the target is + * that it's LU 0 and there is currently another + * LU allocated. + */ + queue_prt(mgmtq, Q_GEN_ERRS, "Failed to remove target"); + syslog(LOG_ERR, "Failed to remove target/lun after " + "initialization failure"); + } + } + + free(tp->targ_name); + queue_free(tp->q, NULL); + free(tp); + + return (NULL); +} + +/* + * []---- + * | thick_provo_stop -- stop initialization thread for given targ/lun + * []---- + */ +void +thick_provo_stop(char *targ, int lun) +{ + thick_provo_t *tp; + target_queue_t *q = queue_alloc(); + + (void) pthread_mutex_lock(&thick_mutex); + tp = thick_head; + while (tp) { + if ((strcmp(tp->targ_name, targ) == 0) && (tp->lun == lun)) { + queue_message_set(tp->q, 0, msg_shutdown, (void *)q); + /* + * Drop the global mutex because it's entirely + * possible for a thick_provo_start thread to be + * in the early stages in which it will can call + * thick_provo_chk() from the T10 SAM code. + */ + (void) pthread_mutex_unlock(&thick_mutex); + + queue_message_free(queue_message_get(q)); + + /* + * Pick the lock back up since it'll make the + * finish stage easier to deal with. + */ + (void) pthread_mutex_lock(&thick_mutex); + break; + } + tp = tp->next; + } + (void) pthread_mutex_unlock(&thick_mutex); + queue_free(q, NULL); +} + +/* + * []---- + * | thick_provo_chk_thr -- see if there's an initialization thread running + * []---- + */ +Boolean_t +thick_provo_chk_thr(char *targ, int lun) +{ + thick_provo_t *tp; + Boolean_t rval = False; + + (void) pthread_mutex_lock(&thick_mutex); + tp = thick_head; + while (tp) { + if ((strcmp(tp->targ_name, targ) == 0) && (tp->lun == lun)) { + rval = True; + break; + } + tp = tp->next; + } + (void) pthread_mutex_unlock(&thick_mutex); + + return (rval); +} + +/* + * []---- + * | remove_target_common -- remove targ/lun from system. + * | + * | This is a common function that's used both by the normal remove + * | target code and when a write failure occurs during initialization. + * | It will handle being given either the local target name or the full + * | IQN name of the target. + * []---- + */ +void +remove_target_common(char *name, int lun_num, char **msg) +{ + xml_node_t *targ = NULL, + *list, + *lun, + *node, + *c; + char path[MAXPATHLEN], + *tname = NULL, + *iname = NULL, + *bs_path = NULL; + int chk, + xml_fd; + Boolean_t bs_delete = False; + xmlTextReaderPtr r; + + (void) pthread_mutex_lock(&targ_config_mutex); + while ((targ = xml_node_next(targets_config, XML_ELEMENT_TARG, targ)) != + NULL) { + /* ---- Look for a match on the friendly name ---- */ + if (strcmp(targ->x_value, name) == 0) { + tname = name; + break; + } + + /* ---- Check to see if they gave the IQN name instead ---- */ + if ((xml_find_value_str(targ, XML_ELEMENT_INAME, &iname) == + True) && (strcmp(iname, name) == 0)) + break; + else { + free(iname); + iname = NULL; + } + } + + /* ---- Check to see if it's already been removed ---- */ + if (targ == NULL) { + (void) pthread_mutex_unlock(&targ_config_mutex); + return; + } + + /* + * We need both the friendly and IQN names so figure out which wasn't + * given and find it's value. + */ + if (tname == NULL) + tname = targ->x_value; + if (iname == NULL) { + if (xml_find_value_str(targ, XML_ELEMENT_INAME, &iname) == + False) { + xml_rtn_msg(msg, ERR_INTERNAL_ERROR); + (void) pthread_mutex_unlock(&targ_config_mutex); + return; + } + } + + if ((list = xml_node_next(targ, XML_ELEMENT_LUNLIST, NULL)) == NULL) + goto error; + + if (lun_num == 0) { + + /* + * LUN must be the last one removed, so check to + * see if others are still present. + */ + lun = NULL; + while ((lun = xml_node_next(list, XML_ELEMENT_LUN, lun)) != + NULL) { + if (xml_find_value_int(lun, XML_ELEMENT_LUN, &chk) == + False) + goto error; + + if (chk != lun_num) { + xml_rtn_msg(msg, ERR_LUN_ZERO_NOT_LAST); + goto error; + } + } + } else { + + /* + * Make sure the LU exists that's being removed + */ + lun = NULL; + while ((lun = xml_node_next(list, XML_ELEMENT_LUN, lun)) != + NULL) { + if (xml_find_value_int(lun, XML_ELEMENT_LUN, &chk) == + False) + goto error; + + if (chk == lun_num) { + lun = xml_alloc_node(XML_ELEMENT_LUN, Int, + &lun_num); + (void) xml_remove_child(list, lun, MatchBoth); + xml_free_node(lun); + break; + } + } + if (lun == NULL) { + xml_rtn_msg(msg, ERR_LUN_NOT_FOUND); + goto error; + } + } + + /* ---- Say goodbye to that data ---- */ + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + iname, LUNBASE, lun_num); + (void) unlink(path); + (void) snprintf(path, sizeof (path), "%s/%s/%s%d", target_basedir, + iname, PARAMBASE, lun_num); + + /* + * See if there's a backing store for this lun, which means LUNBASE + * was just a symbolic link, and delete the backing store if we + * created it in the first place. + */ + xml_fd = open(path, O_RDONLY); + if ((r = (xmlTextReaderPtr)xmlReaderForFd(xml_fd, NULL, NULL, 0)) != + NULL) { + node = NULL; + while (xmlTextReaderRead(r) == 1) + if (xml_process_node(r, &node) == False) + break; + close(xml_fd); + xmlTextReaderClose(r); + xmlFreeTextReader(r); + xmlCleanupParser(); + + (void) xml_find_value_str(node, XML_ELEMENT_BACK, &bs_path); + if ((xml_find_value_boolean(node, XML_ELEMENT_DELETE_BACK, + &bs_delete) == True) && (bs_delete == True)) + unlink(bs_path); + if (bs_path != NULL) + free(bs_path); + xml_tree_free(node); + } + + (void) unlink(path); + + /* + * If the was LUN 0 then do to the previous check + * we know that no other files exist in the target + * directory so the target information can be removed + * along with the directory. + */ + if (lun_num == 0) { + c = xml_alloc_node(XML_ELEMENT_TARG, String, tname); + (void) xml_remove_child(targets_config, c, MatchBoth); + xml_free_node(c); + (void) snprintf(path, sizeof (path), "%s/%s", target_basedir, + iname); + (void) rmdir(path); + + /* + * Don't forget to remove the symlink to + * the target directory. + */ + (void) snprintf(path, sizeof (path), "%s/%s", target_basedir, + tname); + (void) unlink(path); + } + + /* + * Not much we can do here if we fail to updated the config. + */ + if (update_config_targets(msg) == False) + syslog(LOG_ERR, "Failed to update target configuration!"); + +error: + (void) pthread_mutex_unlock(&targ_config_mutex); + if (iname != NULL) + free(iname); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/util_err.c b/usr/src/cmd/iscsi/iscsitgtd/util_err.c new file mode 100644 index 0000000000..83c8828a0c --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/util_err.c @@ -0,0 +1,195 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <libintl.h> +#include "errcode.h" + +char * +errcode_to_str(err_code_t err_code) +{ + switch (err_code) { + case ERR_SUCCESS: + return ((char *)gettext("Operation completed successfully")); + case ERR_NULL_XML_MESSAGE: + return ((char *)gettext("Null XML message")); + case ERR_SYNTAX_EMPTY: + return ((char *)gettext("Syntax error: " + "Empty XML message or syntax error")); + case ERR_SYNTAX_MISSING_ALL: + return ((char *)gettext("Syntax error: Missing --all")); + case ERR_SYNTAX_MISSING_BACKING_STORE: + return ((char *)gettext("Syntax error: Missing backing-store")); + case ERR_SYNTAX_MISSING_INAME: + return ((char *)gettext("Syntax error: Missing iscsi name")); + case ERR_SYNTAX_MISSING_IPADDR: + return ((char *)gettext("Syntax error: Missing IP address")); + case ERR_SYNTAX_MISSING_NAME: + return ((char *)gettext("Syntax error: Missing name")); + case ERR_SYNTAX_MISSING_OBJECT: + return ((char *)gettext("Syntax error: Missing object")); + case ERR_SYNTAX_MISSING_OPERAND: + return ((char *)gettext("Syntax error: Missing operand")); + case ERR_SYNTAX_MISSING_SIZE: + return ((char *)gettext("Syntax error: Missing size")); + case ERR_SYNTAX_MISSING_TYPE: + return ((char *)gettext("Syntax error: Missing type")); + case ERR_SYNTAX_EMPTY_ACL: + return ((char *)gettext("Syntax error: empty acl")); + case ERR_SYNTAX_EMPTY_ALIAS: + return ((char *)gettext("Syntax error: empty alias")); + case ERR_SYNTAX_EMPTY_CHAPNAME: + return ((char *)gettext("Empty chap-name")); + case ERR_SYNTAX_EMPTY_CHAPSECRET: + return ((char *)gettext("Empty 'chap-secret' element")); + case ERR_SYNTAX_EMPTY_IPADDR: + return ((char *)gettext("Syntax error: empty ip address")); + case ERR_SYNTAX_EMPTY_MAXRECV: + return ((char *)gettext("Syntax error: empty maxrecv")); + case ERR_SYNTAX_EMPTY_TPGT: + return ((char *)gettext("Syntax error: empty TPGT")); + case ERR_SYNTAX_INVALID_NAME: + return ((char *)gettext("Syntax error: name must only use " + "a..z, A..Z, 0-9, dot(.), dash(-), colon(:) characters")); + case ERR_INVALID_COMMAND: + return ((char *)gettext("Invalid command")); + case ERR_INVALID_OBJECT: + return ((char *)gettext("Invalid object")); + case ERR_INVALID_BASEDIR: + return ((char *)gettext("Invalid base directory")); + case ERR_INVALID_IP: + return ((char *)gettext("Invalid IP address")); + case ERR_INVALID_TPGT: + return ((char *)gettext("Invalid TPGT")); + case ERR_INVALID_MAXRECV: + return ((char *)gettext("Invalid MaxRecvDataSegmentLength")); + case ERR_INVALID_RADSRV: + return ((char *)gettext("Invalid RADIUS server name")); + case ERR_INVALID_SIZE: + return ((char *)gettext("Invalid size parameter")); + case ERR_INIT_EXISTS: + return ((char *)gettext("Initiator already exists")); + case ERR_LUN_EXISTS: + return ((char *)gettext("LUN already exists")); + case ERR_LUN_INVALID_RANGE: + return ((char *)gettext("LUN must be between 0 and 16383")); + case ERR_TPGT_EXISTS: + return ((char *)gettext("TPGT already exists")); + case ERR_ACL_NOT_FOUND: + return ((char *)gettext("Acl list not found")); + case ERR_INIT_NOT_FOUND: + return ((char *)gettext("Initiator not found")); + case ERR_TARG_NOT_FOUND: + return ((char *)gettext("Target not found")); + case ERR_LUN_NOT_FOUND: + return ((char *)gettext("LUN not found")); + case ERR_TPGT_NOT_FOUND: + return ((char *)gettext("TPGT not found")); + case ERR_ACCESS_RAW_DEVICE_FAILED: + return ((char *)gettext("Failed to " + "access direct access device")); + case ERR_CREATE_METADATA_FAILED: + return ((char *)gettext("Failed to " + "create meta data for tape device")); + case ERR_CREATE_SYMLINK_FAILED: + return ((char *)gettext("Failed to " + "create symbol link to backing store")); + case ERR_CREATE_NAME_TO_LONG: + return ((char *)gettext("Name must be less than 166 " + "characters")); + case ERR_NAME_TO_LONG: + return ((char *)gettext("Name to long, must be less that 223 " + "characters")); + case ERR_DISK_BACKING_SIZE_OR_FILE: + return ((char *)gettext("Size must be zero if backing store " + "exists")); + case ERR_DISK_BACKING_MUST_BE_REGULAR_FILE: + return ((char *)gettext("For type " + "'disk' backing must be a regular file")); + case ERR_DISK_BACKING_NOT_VALID_RAW: + return ((char *)gettext("Backing store is not valid raw " + "device")); + case ERR_STAT_BACKING_FAILED: + return ((char *)gettext("Failed to " + "stat(2) backing for 'disk'")); + case ERR_RAW_PART_NOT_CAP: + return ((char *)gettext("Partition size doesn't match capacity" + " of device, use p0 or ctd name")); + case ERR_CREATE_TARGET_DIR_FAILED: + return ((char *)gettext("Failed to " + "create target directory")); + case ERR_ENCODE_GUID_FAILED: + return ((char *)gettext("Failed to encode GUID value")); + case ERR_INIT_XML_READER_FAILED: + return ((char *)gettext("Failed to initialize XML reader")); + case ERR_OPEN_PARAM_FILE_FAILED: + return ((char *)gettext("Failed to open parameter file")); + case ERR_UPDATE_MAINCFG_FAILED: + return ((char *)gettext("Failed to " + "update main configuration file")); + case ERR_UPDATE_TARGCFG_FAILED: + return ((char *)gettext("Failed to " + "update target configuration file")); + case ERR_VALID_TARG_EXIST: + return ((char *)gettext("Valid targets " + "exist under current base directory")); + case ERR_TARGCFG_MISSING_INAME: + return ((char *)gettext("Missing " + "iscsi name in target configuration")); + case ERR_NO_MATCH: + return ((char *)gettext("No match")); + case ERR_NO_MEM: + return ((char *)gettext("Internal error: no memory")); + case ERR_LUN_ZERO_NOT_LAST: + return ((char *)gettext("LUN 0 must be the last one deleted")); + case ERR_LUN_ZERO_NOT_FIRST: + return ((char *)gettext("LUN 0 must exist before creating " + "other LUNs")); + case ERR_SIZE_MOD_BLOCK: + return ((char *)gettext("Size must be multiple of 512")); + case ERR_CANT_SHRINK_LU: + return ((char *)gettext("Shinking of LU is not supported")); + case ERR_RESIZE_WRONG_TYPE: + return ((char *)gettext("Backing store must be regular file")); + case ERR_RESIZE_WRONG_DTYPE: + return ((char *)gettext("Can't resize 'raw' targets")); + case ERR_LUN_NOT_GROWN: + return ((char *)gettext("Failed to grown LU")); + case ERR_FILE_TO_BIG: + return ((char *)gettext("Requested size is to large for " + "system")); + case ERR_FAILED_TO_CREATE_LU: + return ((char *)gettext("Failed to create backing store")); + case ERR_INTERNAL_ERROR: + return ((char *)gettext("Internal error")); + case ERR_TAPE_NOT_SUPPORTED_IN_32BIT: + return ((char *)gettext("Tape emulation not supported in " + "32-bit mode")); + default: + return ((char *)gettext("Internal error: unknown message")); + } +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/util_ifname.c b/usr/src/cmd/iscsi/iscsitgtd/util_ifname.c new file mode 100644 index 0000000000..c29f2c0364 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/util_ifname.c @@ -0,0 +1,508 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/dlpi.h> +#include <libdlpi.h> +#include <ctype.h> +#include <sys/sysmacros.h> +#include <net/if_types.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <net/if_dl.h> +#include <sys/sockio.h> +#include <sys/socket.h> +#include <unistd.h> +#include <strings.h> +#include <stropts.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "local_types.h" +#include "target.h" +#include "queue.h" +#include "utility.h" + +#define LOCAL_LOOPBACK "lo0" + +/* + * This entire file is all about getting these two variables. To create a + * unique iSCSI IQN string we need information that is unique. What Sun has + * decided is to use a MAC address along with a timestamp. No other machine + * at this given time will have the same MAC address and as time moves along + * well, time will change. + */ +uchar_t *mac_addr; +size_t mac_len; + +#define PATH_PART "/dev/" + +static struct lifreq *if_setup(int *n); +static void dump_addr_to_ascii(struct sockaddr *addr, char *buf, + size_t len); +static int strputmsg(int fd, uint8_t *ctl_buf, size_t ctl_len, int flags); +static int strgetmsg(int fd, char *ctl_buf, size_t *ctl_lenp, char *data_buf, + size_t *data_lenp); +static Boolean_t grab_address(char *ifname, uchar_t **addr, size_t *lp); + +/* + * []---- + * | if_find_mac -- Finds a valid MAC address to use for GUID & IQN creation + * | + * | To create both the GUID and the IQN string we need to make them unique + * | and do so without requiring the user to have to register each target + * | creation with Sun. Each machine that's using iSCSI will have a network + * | interface from which we can obtain the MAC address. That guarantees + * | uniqueness within the network, but doesn't guarantee uniqueness with + * | the machine. So when creating the GUID/IQN we also use a timestamp. + * []---- + */ +Boolean_t +if_find_mac(target_queue_t *mgmt) +{ + struct lifreq *lifrp, *first; + int n; + char *str = NULL; + + first = if_setup(&n); + for (lifrp = first; n > 0; n--, lifrp++) { + if (grab_address(lifrp->lifr_name, &mac_addr, + &mac_len) == True) { + str = _link_ntoa(mac_addr, str, mac_len, IFT_OTHER); + if ((str != NULL) && (mgmt != NULL)) { + queue_prt(mgmt, Q_GEN_DETAILS, + "MAIN %s: %s \n", lifrp->lifr_name, str); + free(str); + } + /* ---- grab the first valid MAC address ---- */ + break; + } + } + if (first) + free(first); + return (mac_len == 0 ? False : True); +} + +/* + * []---- + * | if_target_address -- setup IP address for SendTargets + * | + * | This routine is called when the iSCSI target is returning SendTargets + * | data during a discovery phase. The target name is returned along + * | with all of the IP address that can access that target. There's one + * | catch, the first address in the list will be the address used by + * | the initiator if it doesn't support multiple connections per session. + * | Therefore, whatever connection the initiator used is the first one + * | that should be in our list. The ramificiations of not doing this are + * | possible performance issues. Take for example a setup where both the + * | initiator and target have 10GbE and 1GbE interfaces. The initiator wants + * | to use the 10GbE interface because of it's speed. If the target returns + * | a list of addresses with the 1GbE listed first, that's the one which + * | the initiator would use. Not good. + * []---- + */ +void +if_target_address(char **text, int *text_length, struct sockaddr *sp) +{ + struct lifreq *lp, + *first; + int n, + i, + s; + struct sockaddr_in *sin4_cur, + *sin4_pos; + struct sockaddr_in6 *sin6_cur, + *sin6_pos; + char ta[80], + ip_buf[INET6_ADDRSTRLEN]; + int fromlen; + + if (sp->sa_family == AF_INET) { + fromlen = sizeof (struct sockaddr_in); + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return; + + /*LINTED*/ + sin4_cur = (struct sockaddr_in *)sp; + + /* + * Ugh. Would you believe that even though this array + * is defined as zero, we get back non-zero data from + * getsockname(). + */ + bzero(&sin4_cur->sin_zero[0], sizeof (sin4_cur->sin_zero)); + + } else if (sp->sa_family == AF_INET6) { + fromlen = sizeof (struct sockaddr_in6); + + if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) + return; + + /*LINTED*/ + sin6_cur = (struct sockaddr_in6 *)sp; + } else + return; + + first = if_setup(&n); + for (lp = first, i = 0; i < n; i++, lp++) { + + if (ioctl(s, SIOCGLIFADDR, lp) < 0) + continue; + + if (sp->sa_family != lp->lifr_addr.ss_family) + continue; + + /* + * Change the possible incoming addresses port number, + * which would be zero, to that of the current incoming + * port number. Otherwise the comparison will not match. + */ + if (sp->sa_family == AF_INET) { + sin4_pos = (struct sockaddr_in *)&lp->lifr_addr; + sin4_pos->sin_port = sin4_cur->sin_port; + } else if (sp->sa_family == AF_INET6) { + sin6_pos = (struct sockaddr_in6 *)&lp->lifr_addr; + sin6_pos->sin6_port = sin6_cur->sin6_port; + } else + goto clean_up; + } + + for (lp = first, i = 0; i < n; i++, lp++) { + + if (bcmp(sp, &lp->lifr_addr, fromlen) == 0) { + dump_addr_to_ascii((struct sockaddr *)&lp->lifr_addr, + ip_buf, sizeof (ip_buf)); + + if (sp->sa_family == AF_INET) { + (void) snprintf(ta, sizeof (ta), "%s,1", + ip_buf); + } else if (sp->sa_family == AF_INET6) { + (void) snprintf(ta, sizeof (ta), "[%s],1", + ip_buf); + } else + goto clean_up; + + (void) add_text(text, text_length, "TargetAddress", ta); + + /* + * There is possiblity that both IPv4 & IPv6 enabled on + * certain interface, then we will see that interface + * twice identically in the list. + * Of course we need only one of them, not both. + */ + break; + } + } + + for (lp = first, i = 0; i < n; i++, lp++) { + /* + * We allow for the loopback address to match the discovery + * address above since it's entirely possible to create + * a target on the same machine that you're running the + * initiator. Now, when we provide the list of other + * possible interfaces to use we don't want to include + * the loopback because that's obviously not a valid I/F + * for a remote node. + */ + if (strcmp(lp->lifr_name, LOCAL_LOOPBACK) == 0) + continue; + + if (bcmp(sp, &lp->lifr_addr, fromlen) != 0) { + dump_addr_to_ascii((struct sockaddr *)&lp->lifr_addr, + ip_buf, sizeof (ip_buf)); + + if (sp->sa_family == AF_INET) { + (void) snprintf(ta, sizeof (ta), "%s,1", + ip_buf); + } else if (sp->sa_family == AF_INET6) { + (void) snprintf(ta, sizeof (ta), "[%s],1", + ip_buf); + } else + goto clean_up; + (void) add_text(text, text_length, "TargetAddress", ta); + } + } + +clean_up: + (void) close(s); + if (first) + free(first); +} + +/* + * []---- + * | dump_addr_to_ascii -- Use appropriate translation routine + * []---- + */ +static void +dump_addr_to_ascii(struct sockaddr *addr, char *buf, size_t len) +{ + struct sockaddr_in *sin4; + struct sockaddr_in6 *sin6; + + if (addr->sa_family == AF_INET) { + /*LINTED*/ + sin4 = (struct sockaddr_in *)addr; + (void) inet_ntop(AF_INET, &sin4->sin_addr, buf, len); + } else if (addr->sa_family == AF_INET6) { + /*LINTED*/ + sin6 = (struct sockaddr_in6 *)addr; + (void) inet_ntop(AF_INET6, &sin6->sin6_addr, buf, len); + } +} + +/* + * []---- + * | if_setup -- Load up the interface names + * | + * | If this routine returns NULL, argument 'n' is also guaranteed to + * | be set to 0. + * []---- + */ +static struct lifreq * +if_setup(int *n) +{ + struct lifnum lifn; + struct lifconf lifc; + int numifs; + unsigned bufsize; + char *buf; + int s; + + *n = 0; + if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { + /* + * If we failed to open an IPv6 socket + * try IPv4 socket instead + */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return (NULL); + } + + lifn.lifn_family = AF_UNSPEC; + lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; + if (ioctl(s, SIOCGLIFNUM, (char *)&lifn) < 0) + return (NULL); + + numifs = lifn.lifn_count; + + bufsize = numifs * sizeof (struct lifreq); + if ((buf = malloc(bufsize)) == NULL) { + /* + * This call is made so early on that if we're out of memory + * here, just say goodbye. + */ + return (NULL); + } + + lifc.lifc_family = AF_UNSPEC; + lifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; + lifc.lifc_len = bufsize; + lifc.lifc_buf = buf; + + if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) { + free(buf); + return (NULL); + } + + (void) close(s); + *n = lifc.lifc_len / sizeof (struct lifreq); + return (lifc.lifc_req); +} + +static int +strputmsg(int fd, uint8_t *ctl_buf, size_t ctl_len, int flags) +{ + struct strbuf ctl; + + bzero(&ctl, sizeof (ctl)); + ctl.buf = (char *)ctl_buf; + ctl.len = ctl_len; + + return (putmsg(fd, &ctl, NULL, flags)); +} + +static int +strgetmsg(int fd, char *ctl_buf, size_t *ctl_lenp, char *data_buf, + size_t *data_lenp) +{ + struct strbuf ctl; + struct strbuf data; + int flags = 0, + res; + + bzero(&ctl, sizeof (ctl)); + ctl.buf = ctl_buf; + ctl.len = 0; + ctl.maxlen = (ctl_lenp != NULL) ? *ctl_lenp : 0; + + bzero(&data, sizeof (data)); + data.buf = data_buf; + data.len = 0; + data.maxlen = (data_lenp != NULL) ? *data_lenp : 0; + + res = getmsg(fd, &ctl, &data, &flags); + if (ctl_lenp != NULL) + *ctl_lenp = ctl.len; + if (data_lenp != NULL) + *data_lenp = data.len; + + return (res); +} + +static Boolean_t +ppa_attach(int fd, int ppa) +{ + union DL_primitives *buf = NULL; + dl_attach_req_t dlar; + size_t size; + Boolean_t rval = False; + + size = 0; + size = MAX(sizeof (dl_ok_ack_t), size); + size = MAX(sizeof (dl_error_ack_t), size); + + if ((buf = malloc(size)) == NULL) + return (False); + + dlar.dl_primitive = DL_ATTACH_REQ; + dlar.dl_ppa = ppa; + + if (strputmsg(fd, (uint8_t *)&dlar, DL_ATTACH_REQ_SIZE, 0) == -1) + goto error; + + if (strgetmsg(fd, (char *)buf, &size, NULL, NULL) == -1) + goto error; + + if (size < sizeof (t_uscalar_t)) + goto error; + + switch (buf->dl_primitive) { + case DL_OK_ACK: + if (size == DL_OK_ACK_SIZE) + rval = True; + break; + } + +error: + if (buf != NULL) + free(buf); + + return (rval); +} + +static Boolean_t +grab_address(char *ifname, uchar_t **addr, size_t *lp) +{ + char *dev_name = NULL, + *p; + size_t len; + int ppa, + fd = -1; + union DL_primitives *buf = NULL; + dl_phys_addr_req_t dlpar; + dl_phys_addr_ack_t *dlpaap; + Boolean_t rval = False; + + if (strcmp(ifname, LOCAL_LOOPBACK) == 0) + return (False); + + bzero(&dlpar, sizeof (dlpar)); + len = strlen(PATH_PART) + strlen(ifname) + 1; + if ((dev_name = (char *)malloc(len)) == NULL) { + goto error; + } + (void) snprintf(dev_name, len, "%s%s", PATH_PART, ifname); + + if ((fd = open(dev_name, O_RDWR)) < 0) { + for (p = dev_name; *p; p++) { + if (isdigit(*p)) { + ppa = atoi(p); + *p = '\0'; + if (((fd = open(dev_name, O_RDWR)) < 0) || + (ppa_attach(fd, ppa) == False)) { + goto error; + } + break; + } + } + if (fd == -1) + goto error; + } + + len = 0; + len = MAX(sizeof (dl_phys_addr_ack_t) + MAXADDRLEN, len); + len = MAX(sizeof (dl_error_ack_t), len); + + if ((buf = calloc(len, 1)) == NULL) + goto error; + dlpar.dl_primitive = DL_PHYS_ADDR_REQ; + dlpar.dl_addr_type = DL_CURR_PHYS_ADDR; + + if (strputmsg(fd, (uint8_t *)&dlpar, DL_PHYS_ADDR_REQ_SIZE, 0) == -1) { + goto error; + } + + if (strgetmsg(fd, (char *)buf, &len, NULL, NULL) == -1) { + goto error; + } + + switch (buf->dl_primitive) { + case DL_PHYS_ADDR_ACK: + if (len < DL_PHYS_ADDR_ACK_SIZE) { + goto error; + } + + dlpaap = (dl_phys_addr_ack_t *)buf; + if (dlpaap->dl_addr_offset != 0) { + if (dlpaap->dl_addr_length == 0) { + goto error; + } + *addr = malloc(dlpaap->dl_addr_length); + if (*addr == NULL) + goto error; + bcopy((char *)buf + dlpaap->dl_addr_offset, *addr, + dlpaap->dl_addr_length); + *lp = dlpaap->dl_addr_length; + rval = True; + } + break; + } + +error: + if (fd != -1) + (void) close(fd); + if (dev_name != NULL) + free(dev_name); + if (buf != NULL) + free(buf); + return (rval); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/util_port.c b/usr/src/cmd/iscsi/iscsitgtd/util_port.c new file mode 100644 index 0000000000..0c62e1b456 --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/util_port.c @@ -0,0 +1,260 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/socket.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <netinet/in.h> +#include <assert.h> +#include <syslog.h> +#include <unistd.h> + +#include "queue.h" +#include "port.h" +#include "iscsi_conn.h" +#include "utility.h" + +pthread_mutex_t port_mutex; +int port_conn_num; +iscsi_conn_t *conn_head, + *conn_tail; + +void +port_init() +{ + (void) pthread_mutex_init(&port_mutex, NULL); + port_conn_num = 0; +} + +void * +port_watcher(void *v) +{ + int s, + fd, + on = 1; + char debug[80]; + struct sockaddr_in sin_ip; + struct sockaddr_in6 sin6_ip; + struct sockaddr_storage st; + socklen_t socklen; + iscsi_conn_t *conn; + port_args_t *p = (port_args_t *)v; + target_queue_t *q = p->port_mgmtq; + int l, + accept_err_sleep = 1; + pthread_t junk; + struct in_addr addr; + struct in6_addr addr6; + + /* + * Try creating an IPv6 socket first + * If failed, try creating an IPv4 socket + */ + if ((s = socket(PF_INET6, SOCK_STREAM, 0)) == -1) { + + queue_str(q, Q_GEN_ERRS, msg_log, "Opening IPv4 socket"); + if ((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) { + queue_str(q, Q_GEN_ERRS, msg_log, + "Can't open socket"); + return (NULL); + } else { + bzero(&sin_ip, sizeof (sin_ip)); + sin_ip.sin_family = AF_INET; + sin_ip.sin_port = htons(p->port_num); + sin_ip.sin_addr.s_addr = INADDR_ANY; + + (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof (on)); + + if ((bind(s, (struct sockaddr *)&sin_ip, + sizeof (sin_ip))) < 0) { + (void) snprintf(debug, sizeof (debug), + "bind on port %d failed, errno %d", + p->port_num, errno); + queue_str(q, Q_GEN_ERRS, msg_status, debug); + return (NULL); + } + } + + } else { + + queue_str(q, Q_GEN_DETAILS, msg_log, "Got IPv6 socket"); + bzero(&sin6_ip, sizeof (sin6_ip)); + sin6_ip.sin6_family = AF_INET6; + sin6_ip.sin6_port = htons(p->port_num); + sin6_ip.sin6_addr = in6addr_any; + + (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, + sizeof (on)); + + if ((bind(s, (struct sockaddr *)&sin6_ip, sizeof (sin6_ip))) + < 0) { + (void) snprintf(debug, sizeof (debug), + "bind on port %d failed, errno %d", + p->port_num, errno); + queue_str(q, Q_GEN_ERRS, msg_status, debug); + return (NULL); + } + } + + if (listen(s, 5) < 0) { + queue_str(q, Q_GEN_ERRS, msg_status, "listen failed"); + return (NULL); + } + + /*CONSTANTCONDITION*/ + while (1) { + + socklen = sizeof (st); + if ((fd = accept(s, (struct sockaddr *)&st, + &socklen)) < 0) { + accept_err_sleep *= 2; + (void) sleep(accept_err_sleep); + if (accept_err_sleep > 60) { + accept_err_sleep = 1; + queue_prt(q, Q_GEN_ERRS, + "accept failed, errno %d", errno); + } + continue; + } + + l = 128 * 1024; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&l, + sizeof (l)) < 0) + queue_str(q, Q_GEN_ERRS, msg_status, + "setsockopt failed"); + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&l, + sizeof (l)) < 0) + queue_str(q, Q_GEN_ERRS, msg_status, + "setsockopt failed"); + l = 1; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&l, + sizeof (l)) < 0) + queue_str(q, Q_GEN_ERRS, msg_status, + "setsockopt keepalive failed"); + + if ((conn = (iscsi_conn_t *)calloc(sizeof (iscsi_conn_t), + 1)) == NULL) { + /* + * If we fail to get memory this is all rather + * pointless, since it's unlikely that queue_str + * could malloc memory to send a message. + */ + queue_str(q, Q_GEN_ERRS, msg_status, + "connection malloc failed"); + return (NULL); + } + + /* + * Save initiator sockaddr for future use + */ + conn->c_initiator_sockaddr = st; + + socklen = sizeof (st); + if (getsockname(fd, (struct sockaddr *)&st, + &socklen) == 0) { + /* + * Save target sockaddr for future use + */ + + addr6 = ((struct sockaddr_in6 *)&st)->sin6_addr; + if (st.ss_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&addr6)) { + /* + * If target address is IPv4 mapped IPv6 address + * convert it to IPv4 address + */ + IN6_V4MAPPED_TO_INADDR(&addr6, &addr); + ((struct sockaddr_in *)&st)->sin_addr = addr; + st.ss_family = AF_INET; + } + + conn->c_target_sockaddr = st; + } + + conn->c_fd = fd; + conn->c_mgmtq = q; + conn->c_up_at = time(NULL); + conn->c_state = S1_FREE; + (void) pthread_mutex_init(&conn->c_mutex, NULL); + (void) pthread_mutex_init(&conn->c_state_mutex, NULL); + (void) pthread_mutex_lock(&port_mutex); + conn->c_num = port_conn_num++; + if (conn_head == NULL) { + conn_head = conn; + assert(conn_tail == NULL); + conn_tail = conn; + } else { + conn_tail->c_next = conn; + conn->c_prev = conn_tail; + conn_tail = conn; + } + (void) pthread_mutex_unlock(&port_mutex); + + (void) pthread_create(&junk, NULL, conn_process, conn); + } + return (NULL); +} + +void +port_conn_remove(iscsi_conn_t *c) +{ + iscsi_conn_t *n; + + (void) pthread_mutex_lock(&port_mutex); + if (conn_head == c) { + conn_head = c->c_next; + if (conn_head == NULL) + conn_tail = NULL; + else + conn_head->c_prev = NULL; + } else { + n = c->c_prev; + n->c_next = c->c_next; + if (c->c_next != NULL) + c->c_next->c_prev = n; + else { + assert(conn_tail == c); + conn_tail = n; + } + } + + /* + * The connection queue is freed here so that it's protected by + * locks. The main thread of the deamon when processing incoming + * management requests will send them on the connection queues. + * The main thread will grab the port_mutex so that we know the + * queue is still valid. + */ + queue_free(c->c_dataq, conn_queue_data_remove); + (void) pthread_mutex_unlock(&port_mutex); +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/util_queue.c b/usr/src/cmd/iscsi/iscsitgtd/util_queue.c new file mode 100644 index 0000000000..48d78779fb --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/util_queue.c @@ -0,0 +1,483 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <assert.h> +#include <syslog.h> +#include <synch.h> + +#include "queue.h" +#include "iscsi_conn.h" +#include "utility.h" +#include "target.h" +#include "t10.h" + +FILE *qlog = NULL; +int qlog_lvl = 0; + +pthread_mutex_t q_mutex; +int queue_num; + +void +queue_init() +{ + (void) pthread_mutex_init(&q_mutex, NULL); + queue_log(True); +} + +target_queue_t * +queue_alloc() +{ + target_queue_t *q = + (target_queue_t *)calloc(1, sizeof (target_queue_t)); + + if (q == NULL) + return (NULL); + + (void) pthread_mutex_lock(&q_mutex); + q->q_num = queue_num++; + (void) pthread_mutex_unlock(&q_mutex); + + (void) sema_init(&q->q_sema, 0, USYNC_THREAD, NULL); + (void) pthread_mutex_init(&q->q_mutex, NULL); + + return (q); +} + +void +queue_log(Boolean_t on) +{ + (void) pthread_mutex_lock(&q_mutex); + if ((on == True) && (qlog == NULL) && (qlog_lvl != 0)) { + qlog = fopen(target_log, "ab"); + } else if ((on == False) && (qlog != NULL)) { + (void) fclose(qlog); + qlog = NULL; + } + (void) pthread_mutex_unlock(&q_mutex); +} + +/* + * []---- + * | queue_message_set -- add a given message to the queue. + * []---- + */ +void +queue_message_set(target_queue_t *q, uint32_t lvl, msg_type_t type, + void *data) +{ + msg_t *msg; + + if ((msg = (msg_t *)calloc(sizeof (msg_t), 1)) == NULL) + return; + + msg->msg_pri_level = lvl; + msg->msg_type = type; + msg->msg_data = data; + msg->msg_next = NULL; + + (void) pthread_mutex_lock(&q->q_mutex); + + if (q->q_head == NULL) { + q->q_head = msg; + assert(q->q_tail == NULL); + q->q_tail = msg; + } else if (lvl & Q_HIGH) { + msg->msg_next = q->q_head; + q->q_head->msg_prev = msg; + q->q_head = msg; + } else { + q->q_tail->msg_next = msg; + msg->msg_prev = q->q_tail; + q->q_tail = msg; + } + + (void) pthread_mutex_unlock(&q->q_mutex); + + (void) sema_post(&q->q_sema); +} + +/* + * []---- + * | queue_message_get -- retrieve the first message in the queue + * []---- + */ +msg_t * +queue_message_get(target_queue_t *q) +{ + msg_t *m; + + while (sema_wait(&q->q_sema) == -1) + (void) sleep(1); + (void) pthread_mutex_lock(&q->q_mutex); + m = q->q_head; + if (m == NULL) { + assert(q->q_tail == NULL); + (void) pthread_mutex_unlock(&q->q_mutex); + return (NULL); + } + q->q_head = m->msg_next; + if (q->q_head == NULL) + q->q_tail = NULL; + (void) pthread_mutex_unlock(&q->q_mutex); + + return (m); +} + +/* + * []---- + * | queue_message_try_get -- see if there's a message available + * []---- + */ +msg_t * +queue_message_try_get(target_queue_t *q) +{ + msg_t *m; + + if (sema_trywait(&q->q_sema) != 0) + return (NULL); + (void) pthread_mutex_lock(&q->q_mutex); + m = q->q_head; + q->q_head = m->msg_next; + if (q->q_head == NULL) + q->q_tail = NULL; + (void) pthread_mutex_unlock(&q->q_mutex); + + return (m); +} + +/* + * []---- + * | queue_walker_free -- Run through a queue and free certain messages. + * | + * | Users of the queues should not walk the queue structure themselves + * | unless they also need to grab the lock. To prevent that level of + * | knowledge of the queue structures this method is provided to enable + * | other subsystems to walk the queue looking for messages which need + * | to be deleted. + * []---- + */ +void +queue_walker_free(target_queue_t *q, Boolean_t (*func)(msg_t *m, void *v), + void *v1) +{ + msg_t *m, /* current working message */ + *n; /* next message */ + + (void) pthread_mutex_lock(&q->q_mutex); + m = q->q_head; + while (m) { + if ((*func)(m, v1) == True) { + if (m == q->q_head) { + q->q_head = m->msg_next; + if (m->msg_next == NULL) + q->q_tail = NULL; + else + m->msg_next->msg_prev = NULL; + } else { + m->msg_prev->msg_next = m->msg_next; + if (m->msg_next == NULL) + q->q_tail = m->msg_prev; + else + m->msg_next->msg_prev = m->msg_prev; + } + n = m->msg_next; + queue_message_free(m); + m = n; + } else { + m = m->msg_next; + } + } + (void) pthread_mutex_unlock(&q->q_mutex); +} + +/* + * []---- + * | queue_reset -- Flush a queue of all command messages messages. + * []---- + */ +void +queue_reset(target_queue_t *q) +{ + msg_t *m, + *n; + + (void) pthread_mutex_lock(&q->q_mutex); + m = q->q_head; + while (m != NULL) { + + switch (m->msg_type) { + case msg_cmd_data_out: + case msg_cmd_send: + if (m == q->q_head) { + q->q_head = m->msg_next; + if (m->msg_next == NULL) + q->q_tail = NULL; + else + m->msg_next->msg_prev = NULL; + } else { + assert(m->msg_prev != NULL); + m->msg_prev->msg_next = m->msg_next; + if (m->msg_next == NULL) + q->q_tail = m->msg_prev; + else + m->msg_next->msg_prev = m->msg_prev; + } + n = m->msg_next; + free(m); + m = n; + sema_wait(&q->q_sema); + break; + + case msg_reset_lu: + case msg_shutdown: + case msg_lu_add: + case msg_lu_remove: + case msg_lu_online: + case msg_thick_provo: + /* + * Don't flush the control messages + */ + m = m->msg_next; + break; + + default: + queue_prt(mgmtq, Q_STE_ERRS, + "---- Unexpected msg type %d ----", m->msg_type); + m = m->msg_next; + break; + } + } + + (void) pthread_mutex_unlock(&q->q_mutex); +} + +void +queue_message_free(msg_t *m) +{ + free(m); +} + +/* + * []---- + * | queue_free -- free resources used by queue structure + * []---- + */ +void +queue_free(target_queue_t *q, void (*free_func)(msg_t *)) +{ + msg_t *m, + *n; + + (void) pthread_mutex_lock(&q->q_mutex); + m = q->q_head; + while (m != NULL) { + if (free_func != NULL) + (*free_func)(m); + n = m->msg_next; + free(m); + m = n; + } + (void) pthread_mutex_unlock(&q->q_mutex); + + (void) pthread_mutex_destroy(&q->q_mutex); + (void) sema_destroy(&q->q_sema); + free(q); +} + +void +queue_prt(target_queue_t *q, int type, char *fmt, ...) +{ + va_list ap; + char buf[80]; + + va_start(ap, fmt); + /* LINTED variable format specifier */ + (void) vsnprintf(buf, sizeof (buf), fmt, ap); + queue_str(q, type, msg_log, buf); + va_end(ap); +} + +/* + * []---- + * | queue_str -- helper function which sends a string to the queue + * []---- + */ +void +queue_str(target_queue_t *q, uint32_t lvl, msg_type_t type, char *fmt) +{ + int len; + char *m; + hrtime_t h = gethrtime(), + delta; + static hrtime_t last_h = 0; + + (void) pthread_mutex_lock(&q_mutex); + if ((qlog) && (qlog_lvl & lvl)) { + (void) fprintf(qlog, "%s\n", fmt); + (void) fflush(qlog); + } + (void) pthread_mutex_unlock(&q_mutex); + + if ((dbg_timestamps == True) && (lvl != 0) && ((lvl & Q_HIGH) == 0)) { + len = strlen(fmt) + 12; + m = malloc(len); + delta = h - last_h; + last_h = h; + (void) snprintf(m, len, "%9.3f %s", + (double)delta / (double)1000000.0, fmt); + queue_message_set(q, lvl, type, (void *)m); + } else { + len = strlen(fmt) + 1; + m = malloc(len); + (void) strncpy(m, fmt, len); + queue_message_set(q, lvl, type, (void *)m); + } +} + +/* + * []------------------------------------------------------------------[] + * | Specialized free routines for queue data. | + * | It is possible for a shutdown to start because the STE thread | + * | receives an error while reading from the socket. If at the same | + * | time the connection poll thread is processing a PDU it could place | + * | a msg_ste_datain package on the STE queue. When the STE hits the | + * | shutdown message first it will exit and we need to clean up | + * | anything on that queue which means freeing memory in the | + * | appropriate manner. This is just one example and there are several | + * | others. Another method to deal with this would be to have a closed | + * | flag such that any futher calls to queue_message_set would return | + * | an error. This would require any calls to queue_message_set() deal | + * | with this condition. The approach used here seems cleaner. | + * | The drawback to this approach is that if any new messages are | + * | added then the developer had better add it to these routines as | + * | appropriate. | + * []------------------------------------------------------------------[] + */ + +/* + * []---- + * | sess_queue_data_remove -- free any message data left on the sess queue + * | + * | XXX This should be recoded so that we're doing the cleanup within + * | the session code. Peal off any messages and deal with them there. + * []---- + */ +void +sess_queue_data_remove(msg_t *m) +{ + mgmt_request_t *mq; + char **buf; + + syslog(LOG_ERR, "sess_queue_data: type %d", m->msg_type); + switch (m->msg_type) { + default: + syslog(LOG_ERR, "Unknown session type data being free'd, %d", + m->msg_type); + free(m->msg_data); + break; + + case msg_shutdown: + case msg_shutdown_rsp: + case msg_ste_media_error: + syslog(LOG_ERR, "Impossible message left in session queue" + " of type %d", m->msg_type); + break; + + case msg_cmd_data_out: + break; + + case msg_initiator_name: + case msg_initiator_alias: + case msg_target_name: + free(((name_request_t *)m->msg_data)->nr_name); + break; + + case msg_mgmt_rqst: + mq = (mgmt_request_t *)m->msg_data; + (void) pthread_mutex_lock(&mq->m_resp_mutex); + xml_add_tag(mq->m_u.m_resp, "queue_freed", NULL); + (void) pthread_mutex_unlock(&mq->m_resp_mutex); + queue_message_set(mq->m_q, 0, msg_mgmt_rply, 0); + break; + + case msg_mgmt_rply: + mq = (mgmt_request_t *)m->msg_data; + buf = mq->m_u.m_resp; + buf_add_tag(buf, XML_ELEMENT_STATS, Tag_End); + buf_add_tag(buf, XML_ELEMENT_CONN, Tag_End); + + (void) pthread_mutex_unlock(&mq->m_resp_mutex); + queue_message_set(mq->m_q, 0, msg_mgmt_rply, 0); + break; + + case msg_reset_targ: + case msg_reset_lu: + /* ---- these are safe to ignore, no data to free ---- */ + break; + } +} + +/* + * []---- + * | conn_queue_data_remove -- free any message data left on the conn queue + * []---- + */ +void +conn_queue_data_remove(msg_t *m) +{ + mgmt_request_t *mq; + + syslog(LOG_ERR, "conn_queue_data: type %d", m->msg_type); + switch (m->msg_type) { + case msg_cmd_data_rqst: + case msg_cmd_data_out: + case msg_cmd_cmplt: + syslog(LOG_ERR, "Free'ing data which should already be gone"); + free(m->msg_data); + break; + + case msg_mgmt_rqst: + mq = (mgmt_request_t *)m->msg_data; + (void) pthread_mutex_lock(&mq->m_resp_mutex); + if (mq->m_u.m_resp != NULL) + xml_add_tag(mq->m_u.m_resp, "queue_freed", NULL); + (void) pthread_mutex_unlock(&mq->m_resp_mutex); + queue_message_set(mq->m_q, 0, msg_mgmt_rply, 0); + break; + + default: + syslog(LOG_ERR, "Unknown connection message being free'd: %d", + m->msg_type); + free(m->msg_data); + break; + } +} diff --git a/usr/src/cmd/iscsi/iscsitgtd/utility.h b/usr/src/cmd/iscsi/iscsitgtd/utility.h new file mode 100644 index 0000000000..5d2145515e --- /dev/null +++ b/usr/src/cmd/iscsi/iscsitgtd/utility.h @@ -0,0 +1,96 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _TARGET_UTILITY_H +#define _TARGET_UTILITY_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Block comment which describes the contents of this file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "iscsi_conn.h" +#include <sys/iscsi_protocol.h> +#include "errcode.h" + +#define SNA32_CHECK 2147483648UL + +/* + * Generates a lot more information on each transfer. Disabling this reduces + * one possible performance impact in the data path. Event with logging turned + * off the messages are still queued which requires malloc's and possible lock + * contention on the management queue. This is a gut feeling, rather than + * actual tests to confirm. + */ +#undef FULL_DEBUG + +typedef struct thick_provo { + struct thick_provo *next, + *prev; + char *targ_name; + int lun; + target_queue_t *q; +} thick_provo_t; + +void util_init(); +int read_retry(int fd, char *buf, int count); +Boolean_t parse_text(iscsi_conn_t *c, int dlen, char **text, + int *text_length, int *errcode); +Boolean_t add_text(char **text, int *current_length, char *name, char *val); +char *task_to_str(int func); +void create_geom(diskaddr_t size, int *cylinder, int *heads, int *spt); +void connection_parameters_default(iscsi_conn_t *c); +int sna_lt(uint32_t n1, uint32_t n2); +int sna_lte(uint32_t n1, uint32_t n2); +void xml_rtn_msg(char **buf, err_code_t code); +Boolean_t update_config_targets(char **msg); +Boolean_t update_config_main(char **msg); +Boolean_t add_target_alias(iscsi_conn_t *c, char **text, int *test_length); +Boolean_t validate_version(xml_node_t *node, int *maj, int *min); +char *create_tpgt_list(char *tname); +int find_main_tpgt(struct sockaddr_storage *pst); +Boolean_t check_access(xml_node_t *targ, char *initiator_name, + Boolean_t req_chap); +xml_node_t *find_target_node(char *targ_name); +void util_title(target_queue_t *q, int type, int num, char *title); +Boolean_t util_create_guid(char **guid); +Boolean_t strtoll_multiplier(char *str, uint64_t *sp); +void thick_provo_stop(char *targ, int lun); +void *thick_provo_start(void *v); +Boolean_t thick_provo_chk_thr(char *targ, int lun); +void remove_target_common(char *name, int lun, char **msg); + + +#ifdef __cplusplus +} +#endif + +#endif /* _TARGET_UTILITY_H */ diff --git a/usr/src/lib/libsecdb/exec_attr.txt b/usr/src/lib/libsecdb/exec_attr.txt index 6ded54c77d..6cbee3ea1f 100644 --- a/usr/src/lib/libsecdb/exec_attr.txt +++ b/usr/src/lib/libsecdb/exec_attr.txt @@ -85,6 +85,7 @@ File System Management:suser:cmd:::/usr/sbin/fsck:euid=0 File System Management:suser:cmd:::/usr/sbin/fsdb:euid=0 File System Management:suser:cmd:::/usr/sbin/fstyp:euid=0 File System Management:suser:cmd:::/usr/sbin/fuser:euid=0 +File System Management:suser:cmd:::/usr/sbin/iscsitgtadm:euid=0,privs=basic File System Management:suser:cmd:::/usr/sbin/mkfile:euid=0 File System Management:suser:cmd:::/usr/sbin/mkfs:euid=0 File System Management:suser:cmd:::/usr/sbin/mount:uid=0 diff --git a/usr/src/pkgdefs/Makefile b/usr/src/pkgdefs/Makefile index f68c4145c1..f26c774f97 100644 --- a/usr/src/pkgdefs/Makefile +++ b/usr/src/pkgdefs/Makefile @@ -207,6 +207,8 @@ COMMON_SUBDIRS= \ SUNWippcore \ SUNWipplr \ SUNWipplu \ + SUNWiscsitgtr \ + SUNWiscsitgtu \ SUNWixgb \ SUNWkrbr \ SUNWkrbu \ diff --git a/usr/src/pkgdefs/SUNWiscsitgtr/Makefile b/usr/src/pkgdefs/SUNWiscsitgtr/Makefile new file mode 100644 index 0000000000..1ca5a4fecf --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtr/Makefile @@ -0,0 +1,38 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +DATAFILES += depend i.manifest r.manifest + +.KEEP_STATE: + +all: $(FILES) + +install: all pkg + +include ../Makefile.targ + diff --git a/usr/src/pkgdefs/SUNWiscsitgtr/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsitgtr/pkginfo.tmpl new file mode 100644 index 0000000000..3ffbf99ac5 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtr/pkginfo.tmpl @@ -0,0 +1,50 @@ +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +#ident "%Z%%M% %I% %E% SMI" +# +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWiscsitgtr" +NAME="Sun iSCSI Target (Root)" +ARCH="ISA" +CATEGORY="system" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="root" +CLASSES="manifest none rbac" +DESC="Sun iSCSI Target" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +VERSION="ONVERS,REV=0.0.0" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="true" +SUNW_PKG_THISZONE="false" diff --git a/usr/src/pkgdefs/SUNWiscsitgtr/prototype_com b/usr/src/pkgdefs/SUNWiscsitgtr/prototype_com new file mode 100644 index 0000000000..cadfeb7b3a --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtr/prototype_com @@ -0,0 +1,52 @@ +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +#ident "%Z%%M% %I% %E% SMI" +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# +i copyright +i pkginfo +i depend +i i.manifest +i r.manifest +# +# SUNWiscsitgtr files +# +d none var 755 root sys +d none var/svc 755 root sys +d none var/svc/manifest 755 root sys +d none var/svc/manifest/system 755 root sys +f manifest var/svc/manifest/system/iscsi_target.xml 0444 root sys +d none etc 755 root sys +d none etc/iscsi 0755 root sys diff --git a/usr/src/pkgdefs/SUNWiscsitgtr/prototype_i386 b/usr/src/pkgdefs/SUNWiscsitgtr/prototype_i386 new file mode 100644 index 0000000000..8377903804 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtr/prototype_i386 @@ -0,0 +1,48 @@ +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +#ident "%Z%%M% %I% %E% SMI" +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are Intel specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsitgtr +# diff --git a/usr/src/pkgdefs/SUNWiscsitgtr/prototype_sparc b/usr/src/pkgdefs/SUNWiscsitgtr/prototype_sparc new file mode 100644 index 0000000000..5fdae1683d --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtr/prototype_sparc @@ -0,0 +1,48 @@ +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +#ident "%Z%%M% %I% %E% SMI" +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsitgtr +# + diff --git a/usr/src/pkgdefs/SUNWiscsitgtu/Makefile b/usr/src/pkgdefs/SUNWiscsitgtu/Makefile new file mode 100644 index 0000000000..5e7da20fa8 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtu/Makefile @@ -0,0 +1,37 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +.KEEP_STATE: + +all: $(FILES) depend + +install: all pkg + +include ../Makefile.targ + diff --git a/usr/src/pkgdefs/SUNWiscsitgtu/depend b/usr/src/pkgdefs/SUNWiscsitgtu/depend new file mode 100644 index 0000000000..cbb04340d6 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtu/depend @@ -0,0 +1,51 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# This package information file defines software dependencies associated +# with the pkg. You can define three types of pkg dependencies with this file: +# P indicates a prerequisite for installation +# I indicates an incompatible package +# R indicates a reverse dependency +# <pkg.abbr> see pkginfo(4), PKG parameter +# <name> see pkginfo(4), NAME parameter +# <version> see pkginfo(4), VERSION parameter +# <arch> see pkginfo(4), ARCH parameter +# <type> <pkg.abbr> <name> +# (<arch>)<version> +# (<arch>)<version> +# ... +# <type> <pkg.abbr> <name> +# ... +# + +P SUNWcar Core Architecture, (Root) +P SUNWcakr Core Solaris Kernel Architecture (Root) +P SUNWkvm Core Architecture, (Kvm) +P SUNWcsr Core Solaris, (Root) +P SUNWckr Core Solaris Kernel (Root) +P SUNWcnetr Core Solaris Network Infrastructure (Root) +P SUNWcsu Core Solaris, (Usr) +P SUNWcsd Core Solaris Devices +P SUNWcsl Core Solaris Libraries +P SUNWiscsitgtr Sun iSCSI Target diff --git a/usr/src/pkgdefs/SUNWiscsitgtu/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsitgtu/pkginfo.tmpl new file mode 100644 index 0000000000..f7dd2b3e49 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtu/pkginfo.tmpl @@ -0,0 +1,49 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWiscsitgtu" +NAME="Sun iSCSI Target (Usr)" +ARCH="ISA" +CATEGORY="system" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="usr" +CLASSES="none" +DESC="Sun iSCSI Target" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +VERSION="ONVERS,REV=0.0.0" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="true" +SUNW_PKG_THISZONE="false" diff --git a/usr/src/pkgdefs/SUNWiscsitgtu/preremove b/usr/src/pkgdefs/SUNWiscsitgtu/preremove new file mode 100644 index 0000000000..9ed9f3d8e4 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtu/preremove @@ -0,0 +1,58 @@ +#! /usr/bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# + +PATH="/usr/bin:/usr/sbin:${PATH}" +export PATH + +SERVICE="svc:/system/iscsitgt:default" + +# +# Exit if not removing from the running system +# +[ "${PKG_INSTALL_ROOT:-/}" = "/" ] || exit 0 + +# +# Confirm service is installed, otherwise exit. +# +/usr/bin/svcprop -q ${SERVICE} || exit 0 + +# +# Check to see if the service is enabled and if so disable it. +# + +SVCPROP=`svcprop -p general/enabled ${SERVICE}` + +if [ "${SVCPROP}" = "true" ]; then + svcadm disable ${SERVICE} + if [ $? -ne 0 ]; then + echo "\n$0 Disabling of ${SERVICE} failed!\n" >&2 + exit 1 + fi +fi + +exit 0 diff --git a/usr/src/pkgdefs/SUNWiscsitgtu/prototype_com b/usr/src/pkgdefs/SUNWiscsitgtu/prototype_com new file mode 100644 index 0000000000..b974ad3fd4 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtu/prototype_com @@ -0,0 +1,48 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# +i copyright +i pkginfo +i depend +i preremove +# +# SUNWiscsitgtu +# +d none usr 0755 root sys +d none usr/sbin 0755 root bin +f none usr/sbin/iscsitadm 0555 root bin +l none usr/sbin/iscsitgtd=../../usr/lib/isaexec diff --git a/usr/src/pkgdefs/SUNWiscsitgtu/prototype_i386 b/usr/src/pkgdefs/SUNWiscsitgtu/prototype_i386 new file mode 100644 index 0000000000..1005a2c399 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtu/prototype_i386 @@ -0,0 +1,52 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are Intel specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsitgtu +# +d none usr/sbin/amd64 755 root bin +f none usr/sbin/amd64/iscsitgtd 555 root bin +d none usr/sbin/i86 755 root bin +f none usr/sbin/i86/iscsitgtd 555 root bin diff --git a/usr/src/pkgdefs/SUNWiscsitgtu/prototype_sparc b/usr/src/pkgdefs/SUNWiscsitgtu/prototype_sparc new file mode 100644 index 0000000000..6483a31b8a --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitgtu/prototype_sparc @@ -0,0 +1,49 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsitgtu +# +d none usr/sbin/sparcv9 755 root bin +f none usr/sbin/sparcv9/iscsitgtd 555 root bin diff --git a/usr/src/pkgdefs/etc/exception_list_i386 b/usr/src/pkgdefs/etc/exception_list_i386 index 7f76889c94..c208110ff3 100644 --- a/usr/src/pkgdefs/etc/exception_list_i386 +++ b/usr/src/pkgdefs/etc/exception_list_i386 @@ -802,3 +802,11 @@ usr/include/nss.h i386 # # bmc (IPMI) interfaces shared within ON. usr/include/sys/bmc_intf.h i386 +# +# These files are used by the iSCSI Target which is in this consolidation +# and the iSCSI Initiator which is in the DMG consolidation. There's no +# reason to ship these files. +# +usr/include/sys/iscsi_protocol.h i386 +usr/include/sys/iscsi_authclient.h i386 +usr/include/sys/iscsi_authclientglue.h i386 diff --git a/usr/src/pkgdefs/etc/exception_list_sparc b/usr/src/pkgdefs/etc/exception_list_sparc index 493427a850..a4d454f2d1 100644 --- a/usr/src/pkgdefs/etc/exception_list_sparc +++ b/usr/src/pkgdefs/etc/exception_list_sparc @@ -871,3 +871,11 @@ usr/include/nss.h sparc # This file is used in ON to build DSCP clients. It is not for customers. # usr/include/libdscp.h sparc +# +# These files are used by the iSCSI Target which is in this consolidation +# and the iSCSI Initiator which is in the DMG consolidation. There's no +# reason to ship these files. +# +usr/include/sys/iscsi_protocol.h sparc +usr/include/sys/iscsi_authclient.h sparc +usr/include/sys/iscsi_authclientglue.h sparc diff --git a/usr/src/uts/common/io/emul64_bsd.c b/usr/src/uts/common/io/emul64_bsd.c index 9e033b2bfe..64823f81e7 100644 --- a/usr/src/uts/common/io/emul64_bsd.c +++ b/usr/src/uts/common/io/emul64_bsd.c @@ -2,9 +2,8 @@ * CDDL HEADER START * * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License"). You may not use this file except in compliance - * with the License. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -199,19 +198,6 @@ static emul64_rng_overlap_t bsd_tgt_overlap(emul64_tgt_t *, diskaddr_t, int); char *emul64_name = "emul64"; -/* XXX replace with FORMG0COUNT */ -#define GETG0COUNT(cdb) ((cdb)->g0_count0) - -#define GETG1COUNT(cdb) ((cdb)->g1_count1 << 8) + \ - ((cdb)->g1_count0) - -#define GETG4COUNT(cdb) \ - ((uint64_t)(cdb)->g4_count3 << 24) + \ - ((uint64_t)(cdb)->g4_count2 << 16) + \ - ((uint64_t)(cdb)->g4_count1 << 8) + \ - ((uint64_t)(cdb)->g4_count0) - - /* * Initialize globals in this file. */ diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile index ce2cdf39a4..60f5eef05a 100644 --- a/usr/src/uts/common/sys/Makefile +++ b/usr/src/uts/common/sys/Makefile @@ -259,6 +259,9 @@ CHKHDRS= \ ipc.h \ ipc_impl.h \ isa_defs.h \ + iscsi_authclient.h \ + iscsi_authclientglue.h \ + iscsi_protocol.h \ jioctl.h \ kbd.h \ kbdreg.h \ diff --git a/usr/src/uts/common/sys/iscsi_authclient.h b/usr/src/uts/common/sys/iscsi_authclient.h new file mode 100644 index 0000000000..2d0756c07a --- /dev/null +++ b/usr/src/uts/common/sys/iscsi_authclient.h @@ -0,0 +1,403 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2000 by Cisco Systems, Inc. All rights reserved. + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ISCSI_AUTHCLIENT_H +#define _ISCSI_AUTHCLIENT_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file is the include file for for iscsiAuthClient.c + */ + +#ifdef __cplusplus +extern "C" { +#endif + +enum { iscsiAuthStringMaxLength = 256 }; +enum { iscsiAuthStringBlockMaxLength = 1024 }; +enum { iscsiAuthLargeBinaryMaxLength = 1024 }; + +enum { iscsiAuthRecvEndMaxCount = 10 }; + +enum { iscsiAuthClientSignature = 0x5984B2E3 }; + +enum { iscsiAuthChapResponseLength = 16 }; + +/* + * Note: The ordering of these values are chosen to match + * the ordering of the keys as shown in the iSCSI spec. + * The table IscsiAuthClientKeyInfo in iscsiAuthClient.c + * must also match this order. + */ +enum iscsiAuthKeyType_t { + iscsiAuthKeyTypeNone = -1, + iscsiAuthKeyTypeFirst = 0, + iscsiAuthKeyTypeAuthMethod = iscsiAuthKeyTypeFirst, + iscsiAuthKeyTypeChapAlgorithm, + iscsiAuthKeyTypeChapUsername, + iscsiAuthKeyTypeChapResponse, + iscsiAuthKeyTypeChapIdentifier, + iscsiAuthKeyTypeChapChallenge, + iscsiAuthKeyTypeMaxCount, + iscsiAuthKeyTypeLast = iscsiAuthKeyTypeMaxCount - 1 +}; +typedef enum iscsiAuthKeyType_t IscsiAuthKeyType; + +enum { + /* + * Common options for all keys. + */ + iscsiAuthOptionReject = -2, + iscsiAuthOptionNotPresent = -1, + iscsiAuthOptionNone = 1, + + iscsiAuthMethodChap = 2, + iscsiAuthMethodMaxCount = 2, + + iscsiAuthChapAlgorithmMd5 = 5, + iscsiAuthChapAlgorithmMaxCount = 2 +}; + +enum iscsiAuthNegRole_t { + iscsiAuthNegRoleOriginator = 1, + iscsiAuthNegRoleResponder = 2 +}; +typedef enum iscsiAuthNegRole_t IscsiAuthNegRole; + +/* + * Note: These values are chosen to map to the values sent + * in the iSCSI header. + */ +enum iscsiAuthVersion_t { + iscsiAuthVersionDraft8 = 2, + iscsiAuthVersionRfc = 0 +}; +typedef enum iscsiAuthVersion_t IscsiAuthVersion; + +enum iscsiAuthStatus_t { + iscsiAuthStatusNoError = 0, + iscsiAuthStatusError, + iscsiAuthStatusPass, + iscsiAuthStatusFail, + iscsiAuthStatusContinue, + iscsiAuthStatusInProgress +}; +typedef enum iscsiAuthStatus_t IscsiAuthStatus; + +enum iscsiAuthDebugStatus_t { + iscsiAuthDebugStatusNotSet = 0, + + iscsiAuthDebugStatusAuthPass, + iscsiAuthDebugStatusAuthRemoteFalse, + + iscsiAuthDebugStatusAuthFail, + + iscsiAuthDebugStatusAuthMethodBad, + iscsiAuthDebugStatusChapAlgorithmBad, + iscsiAuthDebugStatusPasswordDecryptFailed, + iscsiAuthDebugStatusPasswordTooShortWithNoIpSec, + iscsiAuthDebugStatusAuthServerError, + iscsiAuthDebugStatusAuthStatusBad, + iscsiAuthDebugStatusAuthPassNotValid, + iscsiAuthDebugStatusSendDuplicateSetKeyValue, + iscsiAuthDebugStatusSendStringTooLong, + iscsiAuthDebugStatusSendTooMuchData, + + iscsiAuthDebugStatusAuthMethodExpected, + iscsiAuthDebugStatusChapAlgorithmExpected, + iscsiAuthDebugStatusChapIdentifierExpected, + iscsiAuthDebugStatusChapChallengeExpected, + iscsiAuthDebugStatusChapResponseExpected, + iscsiAuthDebugStatusChapUsernameExpected, + + iscsiAuthDebugStatusAuthMethodNotPresent, + iscsiAuthDebugStatusAuthMethodReject, + iscsiAuthDebugStatusAuthMethodNone, + iscsiAuthDebugStatusChapAlgorithmReject, + iscsiAuthDebugStatusChapChallengeReflected, + iscsiAuthDebugStatusPasswordIdentical, + + iscsiAuthDebugStatusLocalPasswordNotSet, + + iscsiAuthDebugStatusChapIdentifierBad, + iscsiAuthDebugStatusChapChallengeBad, + iscsiAuthDebugStatusChapResponseBad, + iscsiAuthDebugStatusUnexpectedKeyPresent, + iscsiAuthDebugStatusTbitSetIllegal, + iscsiAuthDebugStatusTbitSetPremature, + + iscsiAuthDebugStatusRecvMessageCountLimit, + iscsiAuthDebugStatusRecvDuplicateSetKeyValue, + iscsiAuthDebugStatusRecvStringTooLong, + iscsiAuthDebugStatusRecvTooMuchData +}; +typedef enum iscsiAuthDebugStatus_t IscsiAuthDebugStatus; + +enum iscsiAuthNodeType_t { + iscsiAuthNodeTypeInitiator = 1, + iscsiAuthNodeTypeTarget = 2 +}; +typedef enum iscsiAuthNodeType_t IscsiAuthNodeType; + +enum iscsiAuthPhase_t { + iscsiAuthPhaseConfigure = 1, + iscsiAuthPhaseNegotiate, /* Negotiating */ + iscsiAuthPhaseAuthenticate, /* Authenticating */ + iscsiAuthPhaseDone, /* Authentication done */ + iscsiAuthPhaseError +}; +typedef enum iscsiAuthPhase_t IscsiAuthPhase; + +enum iscsiAuthLocalState_t { + iscsiAuthLocalStateSendAlgorithm = 1, + iscsiAuthLocalStateRecvAlgorithm, + iscsiAuthLocalStateRecvChallenge, + iscsiAuthLocalStateDone, + iscsiAuthLocalStateError +}; +typedef enum iscsiAuthLocalState_t IscsiAuthLocalState; + +enum iscsiAuthRemoteState_t { + iscsiAuthRemoteStateSendAlgorithm = 1, + iscsiAuthRemoteStateSendChallenge, + iscsiAuthRemoteStateRecvResponse, + iscsiAuthRemoteStateAuthRequest, + iscsiAuthRemoteStateDone, + iscsiAuthRemoteStateError +}; +typedef enum iscsiAuthRemoteState_t IscsiAuthRemoteState; + + +typedef void IscsiAuthClientCallback(void *, void *, int); + + +struct iscsiAuthClientGlobalStats_t { + unsigned long requestSent; + unsigned long responseReceived; +}; +typedef struct iscsiAuthClientGlobalStats_t IscsiAuthClientGlobalStats; + +struct iscsiAuthBufferDesc_t { + unsigned int length; + void *address; +}; +typedef struct iscsiAuthBufferDesc_t IscsiAuthBufferDesc; + +struct iscsiAuthKey_t { + unsigned int present:1; + unsigned int processed:1; + unsigned int valueSet:1; /* 1 if the value is set to be valid */ + char *string; +}; +typedef struct iscsiAuthKey_t IscsiAuthKey; + +struct iscsiAuthLargeBinaryKey_t { + unsigned int length; + unsigned char *largeBinary; + }; +typedef struct iscsiAuthLargeBinaryKey_t IscsiAuthLargeBinaryKey; + +struct iscsiAuthKeyBlock_t { + unsigned int transitBit:1; /* To transit: TRUE or FALSE */ + unsigned int duplicateSet:1; /* Set the value more than once */ + unsigned int stringTooLong:1; /* Key value too long */ + unsigned int tooMuchData:1; /* The keypair data blk overflows */ + unsigned int blockLength:16; /* The length of the keypair data blk */ + char *stringBlock; + IscsiAuthKey key[iscsiAuthKeyTypeMaxCount]; +}; +typedef struct iscsiAuthKeyBlock_t IscsiAuthKeyBlock; + +struct iscsiAuthStringBlock_t { + char stringBlock[iscsiAuthStringBlockMaxLength]; +}; +typedef struct iscsiAuthStringBlock_t IscsiAuthStringBlock; + +struct iscsiAuthLargeBinary_t { + unsigned char largeBinary[iscsiAuthLargeBinaryMaxLength]; +}; +typedef struct iscsiAuthLargeBinary_t IscsiAuthLargeBinary; + +struct iscsiAuthClient_t { + unsigned long signature; + + void *glueHandle; + struct iscsiAuthClient_t *next; + unsigned int authRequestId; + + IscsiAuthNodeType nodeType; + unsigned int authMethodCount; + int authMethodList[iscsiAuthMethodMaxCount]; + IscsiAuthNegRole authMethodNegRole; + unsigned int chapAlgorithmCount; + int chapAlgorithmList[iscsiAuthChapAlgorithmMaxCount]; + + /* + * To indicate if remote authentication is enabled (0 = no 1 = yes) + * For the case of initiator, remote authentication enabled means + * enabling target authentication. + */ + int authRemote; + + char username[iscsiAuthStringMaxLength]; + int passwordPresent; + unsigned int passwordLength; + unsigned char passwordData[iscsiAuthStringMaxLength]; + char methodListName[iscsiAuthStringMaxLength]; + IscsiAuthVersion version; + unsigned int chapChallengeLength; + int ipSec; + int base64; + + unsigned int authMethodValidCount; + int authMethodValidList[iscsiAuthMethodMaxCount]; + int authMethodValidNegRole; + const char *rejectOptionName; + const char *noneOptionName; + + int recvInProgressFlag; + int recvEndCount; + IscsiAuthClientCallback *callback; + void *userHandle; + void *messageHandle; + + IscsiAuthPhase phase; + IscsiAuthLocalState localState; + IscsiAuthRemoteState remoteState; + IscsiAuthStatus remoteAuthStatus; + IscsiAuthDebugStatus debugStatus; + int negotiatedAuthMethod; + int negotiatedChapAlgorithm; + int authResponseFlag; + int authServerErrorFlag; + int transitBitSentFlag; + + unsigned int sendChapIdentifier; + IscsiAuthLargeBinaryKey sendChapChallenge; + char chapUsername[iscsiAuthStringMaxLength]; + + int recvChapChallengeStatus; + IscsiAuthLargeBinaryKey recvChapChallenge; + + char scratchKeyValue[iscsiAuthStringMaxLength]; + + IscsiAuthKeyBlock recvKeyBlock; /* Received keypair data */ + IscsiAuthKeyBlock sendKeyBlock; /* Keypair data to be sent */ +}; +typedef struct iscsiAuthClient_t IscsiAuthClient; + + +#ifdef __cplusplus +} +#endif +#include <sys/iscsi_authclientglue.h> +#ifdef __cplusplus +extern "C" { +#endif + + +extern IscsiAuthClientGlobalStats iscsiAuthClientGlobalStats; + + +extern int iscsiAuthClientInit(int, int, IscsiAuthBufferDesc *); +extern int iscsiAuthClientFinish(IscsiAuthClient *); + +extern int iscsiAuthClientRecvBegin(IscsiAuthClient *); +extern int iscsiAuthClientRecvEnd(IscsiAuthClient *, + IscsiAuthClientCallback *, void *, void *); + +extern const char *iscsiAuthClientGetKeyName(int); +extern int iscsiAuthClientGetNextKeyType(int *); +extern int iscsiAuthClientKeyNameToKeyType(const char *); +extern int iscsiAuthClientRecvKeyValue(IscsiAuthClient *, int, const char *); +extern int iscsiAuthClientSendKeyValue(IscsiAuthClient *, int, int *, char *, + unsigned int); +extern int iscsiAuthClientRecvTransitBit(IscsiAuthClient *, int); +extern int iscsiAuthClientSendTransitBit(IscsiAuthClient *, int *); + +extern int iscsiAuthClientSetAuthMethodList(IscsiAuthClient *, unsigned int, + const int *); +extern int iscsiAuthClientSetAuthMethodNegRole(IscsiAuthClient *, int); +extern int iscsiAuthClientSetChapAlgorithmList(IscsiAuthClient *, unsigned int, + const int *); +extern int iscsiAuthClientSetUsername(IscsiAuthClient *, const char *); +extern int iscsiAuthClientSetPassword(IscsiAuthClient *, const unsigned char *, + unsigned int); +extern int iscsiAuthClientSetAuthRemote(IscsiAuthClient *, int); +extern int iscsiAuthClientSetGlueHandle(IscsiAuthClient *, void *); +extern int iscsiAuthClientSetMethodListName(IscsiAuthClient *, const char *); +extern int iscsiAuthClientSetIpSec(IscsiAuthClient *, int); +extern int iscsiAuthClientSetBase64(IscsiAuthClient *, int); +extern int iscsiAuthClientSetChapChallengeLength(IscsiAuthClient *, + unsigned int); +extern int iscsiAuthClientSetVersion(IscsiAuthClient *, int); +extern int iscsiAuthClientCheckPasswordNeeded(IscsiAuthClient *, int *); + +extern int iscsiAuthClientGetAuthPhase(IscsiAuthClient *, int *); +extern int iscsiAuthClientGetAuthStatus(IscsiAuthClient *, int *); +extern int iscsiAuthClientAuthStatusPass(int); +extern int iscsiAuthClientGetAuthMethod(IscsiAuthClient *, int *); +extern int iscsiAuthClientGetChapAlgorithm(IscsiAuthClient *, int *); +extern int iscsiAuthClientGetChapUsername(IscsiAuthClient *, char *, + unsigned int); + +extern int iscsiAuthClientSendStatusCode(IscsiAuthClient *, int *); +extern int iscsiAuthClientGetDebugStatus(IscsiAuthClient *, int *); +extern const char *iscsiAuthClientDebugStatusToText(int); + +/* + * The following is called by platform dependent code. + */ +extern void iscsiAuthClientAuthResponse(IscsiAuthClient *, int); + +/* + * The following routines are considered platform dependent, + * and need to be implemented for use by iscsiAuthClient.c. + */ + +extern int iscsiAuthClientChapAuthRequest(IscsiAuthClient *, char *, + unsigned int, + unsigned char *, unsigned int, unsigned char *, unsigned int); +extern void iscsiAuthClientChapAuthCancel(IscsiAuthClient *); + +extern int iscsiAuthClientTextToNumber(const char *, unsigned long *); +extern void iscsiAuthClientNumberToText(unsigned long, char *, unsigned int); + +extern void iscsiAuthRandomSetData(unsigned char *, unsigned int); +extern void iscsiAuthMd5Init(IscsiAuthMd5Context *); +extern void iscsiAuthMd5Update(IscsiAuthMd5Context *, unsigned char *, + unsigned int); +extern void iscsiAuthMd5Final(unsigned char *, IscsiAuthMd5Context *); + +extern int iscsiAuthClientData(unsigned char *, unsigned int *, unsigned char *, + unsigned int); + +#ifdef __cplusplus +} +#endif + +#endif /* _ISCSI_AUTHCLIENT_H */ diff --git a/usr/src/uts/common/sys/iscsi_authclientglue.h b/usr/src/uts/common/sys/iscsi_authclientglue.h new file mode 100644 index 0000000000..6001110f8b --- /dev/null +++ b/usr/src/uts/common/sys/iscsi_authclientglue.h @@ -0,0 +1,49 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2000 Cisco Systems, Inc. All rights reserved. + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * iSCSI Software Initiator + */ + +#ifndef _ISCSI_AUTHCLIENTGLUE_H +#define _ISCSI_AUTHCLIENTGLUE_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <md5.h> + +typedef MD5_CTX IscsiAuthMd5Context; + +extern int iscsiAuthIscsiServerHandle; +extern int iscsiAuthIscsiClientHandle; + +#ifdef __cplusplus +} +#endif + +#endif /* _ISCSI_AUTHCLIENTGLUE_H */ diff --git a/usr/src/uts/common/sys/iscsi_protocol.h b/usr/src/uts/common/sys/iscsi_protocol.h new file mode 100644 index 0000000000..1f529c4d84 --- /dev/null +++ b/usr/src/uts/common/sys/iscsi_protocol.h @@ -0,0 +1,704 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ISCSI_PROTOCOL_H +#define _ISCSI_PROTOCOL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * iSCSI connection daemon + * Copyright (C) 2001 Cisco Systems, Inc. + * All rights reserved. + * + * This file sets up definitions of messages and constants used by the + * iSCSI protocol. + * + */ + +#include <sys/types.h> +#include <sys/isa_defs.h> + +#define ISCSI_MAX_NAME_LEN 224 + +/* prototypes for iscsi_crc.c */ +uint32_t iscsi_crc32c(void *address, unsigned long length); +uint32_t iscsi_crc32c_continued(void *address, unsigned long length, + uint32_t crc); + +/* iSCSI listen port for incoming connections */ +#define ISCSI_LISTEN_PORT 3260 + +/* assumes a pointer to a 3-byte array */ +#define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2])) + +/* assumes a pointer to a 3 byte array, and an integer value */ +#define hton24(p, v) {\ + p[0] = (((v) >> 16) & 0xFF); \ + p[1] = (((v) >> 8) & 0xFF); \ + p[2] = ((v) & 0xFF); \ +} + + +/* for Login min, max, active version fields */ +#define ISCSI_MIN_VERSION 0x00 +#define ISCSI_DRAFT8_VERSION 0x02 +#define ISCSI_DRAFT20_VERSION 0x00 +#define ISCSI_MAX_VERSION 0x02 + +/* Min. and Max. length of a PDU we can support */ +#define ISCSI_MIN_PDU_LENGTH (8 << 9) /* 4KB */ +#define ISCSI_MAX_PDU_LENGTH (0xffffffff) /* Huge */ + +/* Padding word length */ +#define ISCSI_PAD_WORD_LEN 4 + +/* Max. number of Key=Value pairs in a text message */ +#define ISCSI_MAX_KEY_VALUE_PAIRS 8192 + +/* text separtor between key value pairs exhanged in login */ +#define ISCSI_TEXT_SEPARATOR '=' + +/* Sun's initiator session ID */ +#define ISCSI_SUN_ISID_0 0x40 /* ISID - EN format */ +#define ISCSI_SUN_ISID_1 0x00 /* Sec B */ +#define ISCSI_SUN_ISID_2 0x00 /* Sec B */ +#define ISCSI_SUN_ISID_3 0x2A /* Sec C - 42 = Sun's EN */ + +/* Reserved value for initiator/target task tag */ +#define ISCSI_RSVD_TASK_TAG 0xffffffff + +/* maximum length for text keys/values */ +#define KEY_MAXLEN 64 +#define VALUE_MAXLEN 255 +#define TARGET_NAME_MAXLEN VALUE_MAXLEN + +#define ISCSI_DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH 8192 + +/* most PDU types have a final bit */ +#define ISCSI_FLAG_FINAL 0x80 + +/* + * Strings used during SendTargets requests + */ +#define ISCSI_TEXT_SEPARATOR '=' +#define TARGETNAME "TargetName=" +#define TARGETADDRESS "TargetAddress=" + +/* iSCSI Template Message Header */ +typedef struct _iscsi_hdr { + uint8_t opcode; + uint8_t flags; /* Final bit */ + uint8_t rsvd2[2]; + uint8_t hlength; /* AHSs total length */ + uint8_t dlength[3]; /* Data length */ + uint8_t lun[8]; + uint32_t itt; /* Initiator Task Tag */ + uint8_t rsvd3[8]; + uint32_t expstatsn; + uint8_t other[16]; +} iscsi_hdr_t; + +typedef struct _iscsi_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd1[3]; + uint8_t dlength[3]; + uint8_t rsvd2[8]; + uint32_t itt; + uint8_t rsvd3[4]; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint8_t rsvd4[12]; +} iscsi_rsp_hdr_t; + +/* Opcode encoding bits */ +#define ISCSI_OP_RETRY 0x80 +#define ISCSI_OP_IMMEDIATE 0x40 +#define ISCSI_OPCODE_MASK 0x3F + +/* Client to Server Message Opcode values */ +#define ISCSI_OP_NOOP_OUT 0x00 +#define ISCSI_OP_SCSI_CMD 0x01 +#define ISCSI_OP_SCSI_TASK_MGT_MSG 0x02 +#define ISCSI_OP_LOGIN_CMD 0x03 +#define ISCSI_OP_TEXT_CMD 0x04 +#define ISCSI_OP_SCSI_DATA 0x05 +#define ISCSI_OP_LOGOUT_CMD 0x06 +#define ISCSI_OP_SNACK_CMD 0x10 + +/* Server to Client Message Opcode values */ +#define ISCSI_OP_NOOP_IN 0x20 +#define ISCSI_OP_SCSI_RSP 0x21 +#define ISCSI_OP_SCSI_TASK_MGT_RSP 0x22 +#define ISCSI_OP_LOGIN_RSP 0x23 +#define ISCSI_OP_TEXT_RSP 0x24 +#define ISCSI_OP_SCSI_DATA_RSP 0x25 +#define ISCSI_OP_LOGOUT_RSP 0x26 +#define ISCSI_OP_RTT_RSP 0x31 +#define ISCSI_OP_ASYNC_EVENT 0x32 +#define ISCSI_OP_REJECT_MSG 0x3f + + +/* SCSI Command Header */ +typedef struct _iscsi_scsi_cmd_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t lun[8]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t data_length; + uint32_t cmdsn; + uint32_t expstatsn; + uint8_t scb[16]; /* SCSI Command Block */ + /* + * Additional Data (Command Dependent) + */ +} iscsi_scsi_cmd_hdr_t; + +/* Command PDU flags */ +#define ISCSI_FLAG_CMD_READ 0x40 +#define ISCSI_FLAG_CMD_WRITE 0x20 +#define ISCSI_FLAG_CMD_ATTR_MASK 0x07 /* 3 bits */ + +/* SCSI Command Attribute values */ +#define ISCSI_ATTR_UNTAGGED 0 +#define ISCSI_ATTR_SIMPLE 1 +#define ISCSI_ATTR_ORDERED 2 +#define ISCSI_ATTR_HEAD_OF_QUEUE 3 +#define ISCSI_ATTR_ACA 4 + + +/* SCSI Response Header */ +typedef struct _iscsi_scsi_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t response; + uint8_t cmd_status; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd[8]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t rsvd1; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint32_t expdatasn; + uint32_t bi_residual_count; + uint32_t residual_count; + /* + * Response or Sense Data (optional) + */ +} iscsi_scsi_rsp_hdr_t; + +/* 10.2.2.3 - Extended CDB Additional Header Segment */ + +typedef struct _iscsi_addl_hdr { + iscsi_scsi_cmd_hdr_t ahs_isch; + uint8_t ahs_hlen_hi; + uint8_t ahs_hlen_lo; + uint8_t ahs_key; + uint8_t ahs_resv; + uint8_t ahs_extscb[4]; +} iscsi_addl_hdr_t; + +/* Command Response PDU flags */ +#define ISCSI_FLAG_CMD_BIDI_OVERFLOW 0x10 +#define ISCSI_FLAG_CMD_BIDI_UNDERFLOW 0x08 +#define ISCSI_FLAG_CMD_OVERFLOW 0x04 +#define ISCSI_FLAG_CMD_UNDERFLOW 0x02 + +/* iSCSI Status values. Valid if Rsp Selector bit is not set */ +#define ISCSI_STATUS_CMD_COMPLETED 0 +#define ISCSI_STATUS_TARGET_FAILURE 1 +#define ISCSI_STATUS_SUBSYS_FAILURE 2 + + +/* Asynchronous Event Header */ +typedef struct _iscsi_async_evt_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + uint8_t rsvd4[8]; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint8_t async_event; + uint8_t async_vcode; + uint16_t param1; + uint16_t param2; + uint16_t param3; + uint8_t rsvd5[4]; +} iscsi_async_evt_hdr_t; + +/* iSCSI Event Indicator values */ +#define ISCSI_ASYNC_EVENT_SCSI_EVENT 0 +#define ISCSI_ASYNC_EVENT_REQUEST_LOGOUT 1 +#define ISCSI_ASYNC_EVENT_DROPPING_CONNECTION 2 +#define ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS 3 +#define ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION 4 +#define ISCSI_ASYNC_EVENT_VENDOR_SPECIFIC 255 + + +/* NOP-Out Message */ +typedef struct _iscsi_nop_out_hdr { + uint8_t opcode; + uint8_t flags; + uint16_t rsvd2; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t ttt; /* Target Transfer Tag */ + uint32_t cmdsn; + uint32_t expstatsn; + uint8_t rsvd4[16]; +} iscsi_nop_out_hdr_t; + + +/* NOP-In Message */ +typedef struct _iscsi_nop_in_hdr { + uint8_t opcode; + uint8_t flags; + uint16_t rsvd2; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t ttt; /* Target Transfer Tag */ + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint8_t rsvd4[12]; +} iscsi_nop_in_hdr_t; + +/* SCSI Task Management Message Header */ +typedef struct _iscsi_scsi_task_mgt_hdr { + uint8_t opcode; + uint8_t function; + uint8_t rsvd1[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t lun[8]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t rtt; /* Reference Task Tag */ + uint32_t cmdsn; + uint32_t expstatsn; + uint32_t refcmdsn; + uint32_t expdatasn; + uint8_t rsvd2[8]; +} iscsi_scsi_task_mgt_hdr_t; + +#define ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK 0x7F + +/* Function values */ +#define ISCSI_TM_FUNC_ABORT_TASK 1 +#define ISCSI_TM_FUNC_ABORT_TASK_SET 2 +#define ISCSI_TM_FUNC_CLEAR_ACA 3 +#define ISCSI_TM_FUNC_CLEAR_TASK_SET 4 +#define ISCSI_TM_FUNC_LOGICAL_UNIT_RESET 5 +#define ISCSI_TM_FUNC_TARGET_WARM_RESET 6 +#define ISCSI_TM_FUNC_TARGET_COLD_RESET 7 +#define ISCSI_TM_FUNC_TASK_REASSIGN 8 + + +/* SCSI Task Management Response Header */ +typedef struct _iscsi_scsi_task_mgt_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t response; /* see Response values below */ + uint8_t qualifier; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd2[8]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t rtt; /* Reference Task Tag */ + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint8_t rsvd3[12]; +} iscsi_scsi_task_mgt_rsp_hdr_t; + + +/* Response values */ +#define SCSI_TCP_TM_RESP_COMPLETE 0x00 +#define SCSI_TCP_TM_RESP_NO_TASK 0x01 +#define SCSI_TCP_TM_RESP_NO_LUN 0x02 +#define SCSI_TCP_TM_RESP_TASK_ALLEGIANT 0x03 +#define SCSI_TCP_TM_RESP_NO_FAILOVER 0x04 +#define SCSI_TCP_TM_RESP_IN_PRGRESS 0x05 +#define SCSI_TCP_TM_RESP_REJECTED 0xff + +/* Ready To Transfer Header */ +typedef struct _iscsi_rtt_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t rsvd3[12]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t ttt; /* Target Transfer Tag */ + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint32_t rttsn; + uint32_t data_offset; + uint32_t data_length; +} iscsi_rtt_hdr_t; + + +/* SCSI Data Hdr */ +typedef struct _iscsi_data_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + uint32_t itt; + uint32_t ttt; + uint32_t rsvd4; + uint32_t expstatsn; + uint32_t rsvd5; + uint32_t datasn; + uint32_t offset; + uint32_t rsvd6; + /* + * Payload + */ +} iscsi_data_hdr_t; + +/* SCSI Data Response Hdr */ +typedef struct _iscsi_data_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2; + uint8_t cmd_status; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t lun[8]; + uint32_t itt; + uint32_t ttt; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint32_t datasn; + uint32_t offset; + uint32_t residual_count; +} iscsi_data_rsp_hdr_t; + +/* Data Response PDU flags */ +#define ISCSI_FLAG_DATA_ACK 0x40 +#define ISCSI_FLAG_DATA_OVERFLOW 0x04 +#define ISCSI_FLAG_DATA_UNDERFLOW 0x02 +#define ISCSI_FLAG_DATA_STATUS 0x01 + + +/* Text Header */ +typedef struct _iscsi_text_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd4[8]; + uint32_t itt; + uint32_t ttt; + uint32_t cmdsn; + uint32_t expstatsn; + uint8_t rsvd5[16]; + /* + * Text - key=value pairs + */ +} iscsi_text_hdr_t; + +#define ISCSI_FLAG_TEXT_CONTINUE 0x40 + +/* Text Response Header */ +typedef struct _iscsi_text_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd4[8]; + uint32_t itt; + uint32_t ttt; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint8_t rsvd5[12]; + /* + * Text Response - key:value pairs + */ +} iscsi_text_rsp_hdr_t; + +/* Login Header */ +typedef struct _iscsi_login_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t max_version; /* Max. version supported */ + uint8_t min_version; /* Min. version supported */ + uint8_t hlength; + uint8_t dlength[3]; + uint8_t isid[6]; /* Initiator Session ID */ + uint16_t tsid; /* Target Session ID */ + uint32_t itt; /* Initiator Task Tag */ + uint16_t cid; + uint16_t rsvd3; + uint32_t cmdsn; + uint32_t expstatsn; + uint8_t rsvd5[16]; +} iscsi_login_hdr_t; + +/* Login PDU flags */ +#define ISCSI_FLAG_LOGIN_TRANSIT 0x80 +#define ISCSI_FLAG_LOGIN_CONTINUE 0x40 +#define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK 0x0C /* 2 bits */ +#define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK 0x03 /* 2 bits */ + +#define ISCSI_LOGIN_CURRENT_STAGE(flags) \ + ((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2) +#define ISCSI_LOGIN_NEXT_STAGE(flags) \ + (flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK) + + +/* Login Response Header */ +typedef struct _iscsi_login_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t max_version; /* Max. version supported */ + uint8_t active_version; /* Active version */ + uint8_t hlength; + uint8_t dlength[3]; + uint8_t isid[6]; /* Initiator Session ID */ + uint16_t tsid; /* Target Session ID */ + uint32_t itt; /* Initiator Task Tag */ + uint32_t rsvd3; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint8_t status_class; /* see Login RSP ststus classes below */ + uint8_t status_detail; /* see Login RSP Status details below */ + uint8_t rsvd4[10]; +} iscsi_login_rsp_hdr_t; + +/* Login stage (phase) codes for CSG, NSG */ +#define ISCSI_SECURITY_NEGOTIATION_STAGE 0 +#define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1 +#define ISCSI_FULL_FEATURE_PHASE 3 + +/* Login Status response classes */ +#define ISCSI_STATUS_CLASS_SUCCESS 0x00 +#define ISCSI_STATUS_CLASS_REDIRECT 0x01 +#define ISCSI_STATUS_CLASS_INITIATOR_ERR 0x02 +#define ISCSI_STATUS_CLASS_TARGET_ERR 0x03 + +/* Login Status response detail codes */ +/* Class-0 (Success) */ +#define ISCSI_LOGIN_STATUS_ACCEPT 0x00 + +/* Class-1 (Redirection) */ +#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01 +#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02 + +/* Class-2 (Initiator Error) */ +#define ISCSI_LOGIN_STATUS_INIT_ERR 0x00 +#define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01 +#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02 +#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03 +#define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04 +#define ISCSI_LOGIN_STATUS_NO_VERSION 0x05 +#define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06 +#define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07 +#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08 +#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09 +#define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a +#define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b + +/* Class-3 (Target Error) */ +#define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00 +#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01 +#define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02 + +/* Logout Header */ +typedef struct _iscsi_logout_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd1[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd2[8]; + uint32_t itt; /* Initiator Task Tag */ + uint16_t cid; + uint8_t rsvd3[2]; + uint32_t cmdsn; + uint32_t expstatsn; + uint8_t rsvd4[16]; +} iscsi_logout_hdr_t; + +/* Logout PDU flags */ +#define ISCSI_FLAG_LOGOUT_REASON_MASK 0x7F + +/* logout reason_code values */ + +#define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0 +#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1 +#define ISCSI_LOGOUT_REASON_RECOVERY 2 +#define ISCSI_LOGOUT_REASON_AEN_REQUEST 3 + +/* Logout Response Header */ +typedef struct _iscsi_logout_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t response; /* see Logout response values below */ + uint8_t rsvd2; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd3[8]; + uint32_t itt; /* Initiator Task Tag */ + uint32_t rsvd4; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint32_t rsvd5; + uint16_t t2wait; + uint16_t t2retain; + uint32_t rsvd6; +} iscsi_logout_rsp_hdr_t; + +/* logout response status values */ + +#define ISCSI_LOGOUT_SUCCESS 0 +#define ISCSI_LOGOUT_CID_NOT_FOUND 1 +#define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED 2 +#define ISCSI_LOGOUT_CLEANUP_FAILED 3 + + +/* SNACK Header */ +typedef struct _iscsi_snack_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[14]; + uint32_t itt; + uint32_t begrun; + uint32_t runlength; + uint32_t expstatsn; + uint32_t rsvd3; + uint32_t expdatasn; + uint8_t rsvd6[8]; +} iscsi_snack_hdr_t; + +/* SNACK PDU flags */ +#define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */ + +/* Reject Message Header */ +typedef struct _iscsi_reject_rsp_hdr { + uint8_t opcode; + uint8_t flags; + uint8_t reason; + uint8_t rsvd2; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t rsvd4[16]; + uint32_t statsn; + uint32_t expcmdsn; + uint32_t maxcmdsn; + uint32_t datasn; + uint8_t rsvd5[8]; + /* + * Text - Rejected hdr + */ +} iscsi_reject_rsp_hdr_t; + +/* Reason for Reject */ +#define ISCSI_REJECT_CMD_BEFORE_LOGIN 1 +#define ISCSI_REJECT_DATA_DIGEST_ERROR 2 +#define ISCSI_REJECT_SNACK_REJECT 3 +#define ISCSI_REJECT_PROTOCOL_ERROR 4 +#define ISCSI_REJECT_CMD_NOT_SUPPORTED 5 +#define ISCSI_REJECT_IMM_CMD_REJECT 6 +#define ISCSI_REJECT_TASK_IN_PROGRESS 7 +#define ISCSI_REJECT_INVALID_DATA_ACK 8 +#define ISCSI_REJECT_INVALID_PDU_FIELD 9 +#define ISCSI_REJECT_LONG_OPERATION_REJECT 10 +#define ISCSI_REJECT_NEGOTIATION_RESET 11 +#define ISCSI_REJECT_WAITING_FOR_LOGOUT 12 + +/* Defaults as defined by the iSCSI specification */ +#define ISCSI_DEFAULT_IMMEDIATE_DATA TRUE +#define ISCSI_DEFAULT_INITIALR2T TRUE +#define ISCSI_DEFAULT_FIRST_BURST_LENGTH (64 * 1024) /* 64kbytes */ +#define ISCSI_DEFAULT_MAX_BURST_LENGTH (256 * 1024) /* 256kbytes */ +#define ISCSI_DEFAULT_DATA_PDU_IN_ORDER TRUE +#define ISCSI_DEFAULT_DATA_SEQUENCE_IN_ORDER TRUE +#define ISCSI_DEFAULT_TIME_TO_WAIT 2 /* 2 seconds */ +#define ISCSI_DEFAULT_TIME_TO_RETAIN 20 /* 20 seconds */ +#define ISCSI_DEFAULT_HEADER_DIGEST ISCSI_DIGEST_NONE +#define ISCSI_DEFAULT_DATA_DIGEST ISCSI_DIGEST_NONE +#define ISCSI_DEFAULT_MAX_RECV_SEG_LEN (64 * 1024) +#define ISCSI_DEFAULT_MAX_XMIT_SEG_LEN (8 * 1024) +#define ISCSI_DEFAULT_MAX_CONNECTIONS 1 +#define ISCSI_DEFAULT_MAX_OUT_R2T 1 +#define ISCSI_DEFAULT_ERROR_RECOVERY_LEVEL 0 +#define ISCSI_DEFAULT_IFMARKER FALSE +#define ISCSI_DEFAULT_OFMARKER FALSE + +/* + * Maximum values from the iSCSI specification + */ +#define ISCSI_MAX_HEADER_DIGEST 3 +#define ISCSI_MAX_DATA_DIGEST 3 +#define ISCSI_MAX_TIME2RETAIN 3600 +#define ISCSI_MAX_TIME2WAIT 3600 +#define ISCSI_MAX_ERROR_RECOVERY_LEVEL 2 +#define ISCSI_MAX_FIRST_BURST_LENGTH 0xffffff +#define ISCSI_MAX_BURST_LENGTH 0xffffff +#define ISCSI_MAX_CONNECTIONS 65535 +#define ISCSI_MAX_OUTSTANDING_R2T 65535 +#define ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH 0xffffff +#define ISCSI_MAX_TPGT_VALUE 65535 /* 16 bit numeric */ + +/* + * iqn and eui name prefixes and related defines + */ +#define ISCSI_IQN_NAME_PREFIX "iqn" +#define ISCSI_EUI_NAME_PREFIX "eui" +#define ISCSI_EUI_NAME_LEN 20 /* eui. plus 16 octets */ + +#ifdef __cplusplus +} +#endif + +#endif /* _ISCSI_PROTOCOL_H */ diff --git a/usr/src/uts/common/sys/scsi/generic/commands.h b/usr/src/uts/common/sys/scsi/generic/commands.h index 8d3c373d81..1829959a93 100644 --- a/usr/src/uts/common/sys/scsi/generic/commands.h +++ b/usr/src/uts/common/sys/scsi/generic/commands.h @@ -2,9 +2,8 @@ * CDDL HEADER START * * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License"). You may not use this file except in compliance - * with the License. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -19,8 +18,9 @@ * * CDDL HEADER END */ + /* - * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -323,8 +323,10 @@ extern "C" { */ #define SCMD_GROUP5 0xA0 #define SCMD_REPORT_LUNS 0xA0 +#define SCMD_REPORT_TARGET_PORT_GROUPS 0xA3 #define SCMD_READ_G5 0xA8 #define SCMD_WRITE_G5 0xAA +#define SCMD_READ_MEDIA_SERIAL 0xAB #define SCMD_GET_PERFORMANCE 0xAC diff --git a/usr/src/uts/common/sys/scsi/generic/inquiry.h b/usr/src/uts/common/sys/scsi/generic/inquiry.h index fa2676747d..6d49fce3e3 100644 --- a/usr/src/uts/common/sys/scsi/generic/inquiry.h +++ b/usr/src/uts/common/sys/scsi/generic/inquiry.h @@ -63,7 +63,7 @@ struct scsi_inquiry { /* byte 3 */ uchar_t inq_rdf : 4, /* response data format */ - : 1, /* reserved */ + inq_hisup : 1, /* Hierarchial support */ inq_normaca : 1, /* setting NACA bit supported */ inq_trmiop : 1, /* TERMINATE I/O PROC msg */ inq_aenc : 1; /* async event notification cap. */ @@ -72,8 +72,9 @@ struct scsi_inquiry { uchar_t inq_len; /* additional length */ - uchar_t : 8; /* reserved */ - + uchar_t : 4, /* reserved */ + inq_tpgs : 1, /* supports Target Port Group set */ + : 3; uchar_t inq_addr16 : 1, /* supports 16 bit wide SCSI addr */ inq_addr32 : 1, /* supports 32 bit wide SCSI addr */ inq_ackqreqq : 1, /* data tranfer on Q cable */ @@ -140,14 +141,16 @@ struct scsi_inquiry { uchar_t inq_aenc : 1, /* async event notification cap. */ inq_trmiop : 1, /* supports TERMINATE I/O PROC msg */ inq_normaca : 1, /* setting NACA bit supported */ - : 1, /* reserved */ + inq_hisup : 1, /* hierachial support */ inq_rdf : 4; /* response data format */ /* bytes 4-7 */ uchar_t inq_len; /* additional length */ - uchar_t : 8; /* reserved */ + uchar_t : 3, /* reserved */ + inq_tpgs : 1, /* supports Target Port Group Set */ + : 4; uchar_t : 2, /* reserved */ inq_port : 1, /* port receiving inquiry cmd */ @@ -292,6 +295,51 @@ struct scsi_inquiry { #define RDF_CCS 0x01 #define RDF_SCSI2 0x02 +/* + * SPC-3 revision 21c, section 7.6.4.1 + * Table 289 -- Device Identification VPD page + */ +struct vpd_hdr { +#if defined(_BIT_FIELDS_LTOH) + uchar_t device_type : 4, + periph_qual : 4; +#elif defined(_BIT_FIELDS_HTOL) + uchar_t periph_qual : 4, + device_type : 4; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uchar_t page_code, + page_len[2]; +}; + +/* + * SPC-3 revision 21c, section 7.6.4.1 + * Table 290 -- Identification descriptor + */ +struct vpd_desc { +#if defined(_BIT_FIELDS_LTOH) + uchar_t code_set : 4, + proto_id : 4; + uchar_t id_type : 4, + association : 2, + : 1, + piv : 1; +#elif defined(_BIT_FIELDS_HTOL) + uchar_t proto_id : 4, + code_set : 4; + uchar_t piv : 1, + : 1, + association : 2, + id_type : 4; +#else +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + uchar_t resrv1; + uchar_t len; + /* ---- data follows ---- */ +}; + #ifdef __cplusplus } #endif diff --git a/usr/src/uts/common/sys/scsi/impl/commands.h b/usr/src/uts/common/sys/scsi/impl/commands.h index 91ea8e6233..059f6b90b6 100644 --- a/usr/src/uts/common/sys/scsi/impl/commands.h +++ b/usr/src/uts/common/sys/scsi/impl/commands.h @@ -340,8 +340,10 @@ union scsi_cdb { /* scsi command description block */ (cdb)->g0_addr1 = ((addr) >> 8) & 0xFF; \ (cdb)->g0_addr0 = (addr) & 0xFF -#define GETG0ADDR(cdb) (((cdb)->g0_addr2 & 0x1F) << 16) + \ - ((cdb)->g0_addr1 << 8) + ((cdb)->g0_addr0) +#define GETG0COUNT(cdb) (cdb)->g0_count0 + +#define GETG0ADDR(cdb) ((((cdb)->g0_addr2 & 0x1F) << 16) + \ + ((cdb)->g0_addr1 << 8) + ((cdb)->g0_addr0)) #define GETG0TAG(cdb) ((cdb)->g0_addr2) @@ -357,10 +359,12 @@ union scsi_cdb { /* scsi command description block */ (cdb)->g1_addr1 = ((addr) >> 8) & 0xFF; \ (cdb)->g1_addr0 = (addr) & 0xFF -#define GETG1ADDR(cdb) ((cdb)->g1_addr3 << 24) + \ +#define GETG1COUNT(cdb) (((cdb)->g1_count1 << 8) + ((cdb)->g1_count0)) + +#define GETG1ADDR(cdb) (((cdb)->g1_addr3 << 24) + \ ((cdb)->g1_addr2 << 16) + \ ((cdb)->g1_addr1 << 8) + \ - ((cdb)->g1_addr0) + ((cdb)->g1_addr0)) #define GETG1TAG(cdb) (cdb)->g1_reladdr @@ -385,6 +389,20 @@ union scsi_cdb { /* scsi command description block */ (cdb)->g4_addtl_cdb_data0 = \ (addr) & 0xFF +#define GETG4COUNT(cdb) (((cdb)->g4_count3 << 24) + \ + ((cdb)->g4_count2 << 16) + \ + ((cdb)->g4_count1 << 8) + \ + ((cdb)->g4_count0)) + +#define GETG4LONGADDR(cdb) (((diskaddr_t)(cdb)->g4_addr3 << 56) + \ + ((diskaddr_t)(cdb)->g4_addr2 << 48) + \ + ((diskaddr_t)(cdb)->g4_addr1 << 40) + \ + ((diskaddr_t)(cdb)->g4_addr0 << 32) + \ + ((diskaddr_t)(cdb)->g4_addtl_cdb_data3 << 24) + \ + ((diskaddr_t)(cdb)->g4_addtl_cdb_data2 << 16) + \ + ((diskaddr_t)(cdb)->g4_addtl_cdb_data1 << 8) + \ + ((diskaddr_t)(cdb)->g4_addtl_cdb_data0)) + #define FORMG4ADDR(cdb, addr) (cdb)->g4_addr3 = (addr) >> 24; \ (cdb)->g4_addr2 = ((addr) >> 16) & 0xFF; \ (cdb)->g4_addr1 = ((addr) >> 8) & 0xFF; \ |