diff options
author | open-iscsi <open-iscsi@d7303112-9cec-0310-bdd2-e83a94d6c2b6> | 2005-01-18 02:22:39 +0000 |
---|---|---|
committer | open-iscsi <open-iscsi@d7303112-9cec-0310-bdd2-e83a94d6c2b6> | 2005-01-18 02:22:39 +0000 |
commit | 1fd764eeeea0c8c8281c1be1fd6e6f6e5a33f81a (patch) | |
tree | 1708d173ad349f26ce51280764dba538aabf402f | |
parent | 761b07cc9f4e75522111d1b588326b7c7f7af346 (diff) | |
download | open-iscsi-1fd764eeeea0c8c8281c1be1fd6e6f6e5a33f81a.tar.gz |
kernel/user compiles
git-svn-id: svn://svn.berlios.de/open-iscsi@34 d7303112-9cec-0310-bdd2-e83a94d6c2b6
-rw-r--r-- | etc/iscsid.conf | 510 | ||||
-rw-r--r-- | include/iscsi_proto.h | 20 | ||||
-rw-r--r-- | kernel/Makefile | 2 | ||||
-rw-r--r-- | kernel/iscsi_if.h | 2 | ||||
-rw-r--r-- | kernel/iscsi_mgr.c | 4 | ||||
-rw-r--r-- | kernel/iscsi_tcp.c | 16 | ||||
-rw-r--r-- | usr/Makefile | 5 | ||||
-rw-r--r-- | usr/auth.c | 6 | ||||
-rw-r--r-- | usr/config.c | 3278 | ||||
-rw-r--r-- | usr/config.h | 349 | ||||
-rw-r--r-- | usr/discovery.c | 1785 | ||||
-rw-r--r-- | usr/discovery.h | 17 | ||||
-rw-r--r-- | usr/initiator.h | 21 | ||||
-rw-r--r-- | usr/io.c | 14 | ||||
-rw-r--r-- | usr/iscsiadm.c | 4 | ||||
-rw-r--r-- | usr/iscsiadm.h | 54 | ||||
-rw-r--r-- | usr/iscsid.c | 5 | ||||
-rw-r--r-- | usr/iscsid.h | 15 | ||||
-rw-r--r-- | usr/login.c | 58 | ||||
-rw-r--r-- | usr/md5.h | 2 | ||||
-rw-r--r-- | usr/strings.c | 288 | ||||
-rw-r--r-- | usr/strings.h | 46 |
22 files changed, 6391 insertions, 110 deletions
diff --git a/etc/iscsid.conf b/etc/iscsid.conf index 5480e96..b56c864 100644 --- a/etc/iscsid.conf +++ b/etc/iscsid.conf @@ -1,23 +1,487 @@ -target_name = iqn.2001-04.com.example:storage.disk2.sys1.xyz -target_portal = 10.16.16.227:3260,1 -login_user = dima -login_password = aloha -initiator_name = iqn.com.dima -initiator_alias = dima-um -isid = 0x80.0x0000.0x00.0x0001 -first_burst = 262144 -max_recv_dlength = 65536 -max_burst = 262144 -max_r2t = 1 -max_cnx = 1 -erl = 0 -initial_r2t_en = 0 -imm_data_en = 1 -hdrdgst_en = 0 -datadgst_en = 0 -ifmarker_en = 0 -ofmarker_en = 0 -pdu_inorder_en = 1 -dataseq_inorder_en = 1 -time2wait = 5 -time2retain = 20 +# ============================================================================ +# iSCSI Configuration File Sample - see iscsi.conf(5) +# ============================================================================ +# +# All of the configuration parameters described in this file are applied +# globally to all targets, unless they are overridden by a local setting. The +# three types of local categories that can override the global settings are: +# +# Target Name (i.e., TargetName) +# Network (i.e., Subnet or Address) +# SCSI Routing Instance (i.e., DiscoveryAddress) +# +# All parameters that are localized to one of the categories above must be +# indented by at least one white space or a tab character. If the parameter is +# not indented, it will be interpreted as a global parameter (see examples for +# each parameter). +# +# If more that one entry exists for any given parameter (either global or +# local), the last entry has precedence. +# +# If a parameter setting under the network category conflicts with a different +# setting of the same parameter under the discovery address or target name +# category (for the same target), the network setting will have precedence. +# +# If a parameter is not specified in the iscsi.conf file, the default setting is +# used. The default values for all parameters can be found in the readme file. +# +# In the sample settings shown below, the following definitions apply: +# <text> = any alpha-numeric text string +# <number> = any numeric text string +# <address> = valid IP address of the form a.b.c.d[/e] +# <portal> = valid portal address of the form a.b.c.d[:e] +# +# ---------------- +# Network Category +# ---------------- +# To localize parameters to targets on a particular network (i.e., to +# override the global settings), you need to use either the "Subnet" or +# "Address" settings. The format for the "Subnet" setting is a.b.c.d/e. In +# addition, multiple subnets can be specified by using a "," delimiter. An +# example of these settings would be: +# +#Subnet=10.4.100.0/24 +# or +#Subnet=10.4.100.1/24,10.5.100.1/24 +# +# The format for "Address" is a.b.c.d and it too can have multiple values using +# a "," delimiter. An example of these settings would be: +# +#Address=10.4.100.0 +# or +#Address=10.5.101.4,10.5.101.5 +# +# The following parameters can be specified using the network category: +# +# 1) Connection Timeout Settings +# 2) Error Handling Timeout Settings +# 3) TCP Settings +# +# -------------------------- +# Discovery Address Category +# -------------------------- +# To localize parameters to targets found on a particular discovery address +# (i.e., to override the global settings), you need to use the +# "DiscoveryAddress" setting. The format for the "DiscoveryAddress" setting is +# a.b.c.d, a.b.c.d:e (where e is a TCP port number), or an instance name. An +# example of these settings would be: +# +#DiscoveryAddress=10.4.100.0 +# or +#DiscoveryAddress=10.4.100.1:3260 +# or +#DiscoveryAddress=scisrouter1 +# +# The following parameters can be specified using the discovery address +# category: +# +# 1) Authentication Settings +# 2) ConnectionTimeout Settings +# 3) Continuous Discovery settings +# 4) AsyncEvent Notification Settings +# +# -------------------- +# Target Name Category +# -------------------- +# To localize parameters to targets identified by a particular target name +# (i.e., to override the global settings), you need to use the +# "TargetName" setting. The format for the "TargetName" setting is +# either the 'iqn' or 'eui' format. An example of these settings would be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# +# The following parameters can be specified using the target name category: +# +# 1) CRC Settings +# 2) iSCSI Operational Parameter settings +# 3) Connection Timeout Settings +# 4) Session Timeout Settings +# 5) Error Handling Timeout Settings +# 6) TCP Settings +# 7) Enable/Disable targets +# +# ============================================================================ +# PARAMETERS +# ============================================================================ +# +# ----------------------- +# AUTHENTICATION SETTINGS +# ----------------------- +# To globally configure a CHAP username and password for initiator +# authentication by the target(s), uncomment the following lines: +# +#OutgoingUsername=<text> +#OutgoingPassword=<text> +# +# The maximum length for both the password and username is 256 characters. + +# An example username and password would be: +# +#OutgoingUsername=alice +#OutgoingPassword=nty57nbe +# +# To globally configure a CHAP username and password for target(s) +# authentication by the initiator, uncomment the following lines: +# +#IncomingUsername=<text> +#IncomingPassword=<text> +# +# The maximum length for both the password and username is 256 characters. + +# An example username and password would be: +# +#IncomingUsername=bill +#IncomingPassword=ghot67 +# +# The global authentication settings can be overridden on a per discovery +# address basis. An example of a unique username and password for all targets +# found at address 192.168.10.94 would be: +# +#DiscoveryAddress=192.168.10.94 +# OutgoingUsername=fred +# OutgoingPassword=uhyt6h +# and/or +# +#DiscoveryAddress=192.168.10.94 +# IncomingUsername=mary +# IncomingPassword=kdhjkd9l +# +# --------------- +# DIGEST SETTINGS +# --------------- +# To globally enable CRC32C digest checking for the header and/or data part of +# iSCSI PDUs, uncomment one or both of the following lines: +# +#HeaderDigest=always +#DataDigest=always +# +# To globally disable digest checking for the header and/or data part of +# iSCSI PDUs, uncomment one or both of the following lines: +# +#HeaderDigest=never +#DataDigest=never +# +# To globally allow the targets to control the setting of the digest checking, +# with the initiator requesting a preference of enabling the checking, uncomment +# one or both of the following lines: +# +#HeaderDigest=prefer-on +#DataDigest=prefer-on +# +# To globally allow the targets to control the setting of the digest checking, +# with the initiator requesting a preference of disabling the checking, +# uncomment one or both of the following lines: +# +#HeaderDigest=prefer-off +#DataDigest=prefer-off +# +# The global digest settings can be overridden on a per target name basis. An +# example of enabling header and data digest checking just for target iqn.1987- +# 05.com.cisco:00.0d1d898e8d66.t0 would be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# HeaderDigest=always +# DataDigest=always +# +# It should also be noted that if the initiator and the target have incompatible +# settings (e.g., target set for "always" and initiator set for "never"), the +# login will fail. +# +# ---------------------- +# ENABLE/DISABLE TARGETS +# ---------------------- +# To globally enable/disable group of targets use the following option. +# +# TargetNames mentioned after the below entry will be enabled by default. +#Enabled=yes +# +# TargetNames mentioned after the below entry will be disabled by default. +#Enabled=no +# +# To specifically enable/disable a target, use the following entry +# under Targetname. +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# Enabled=yes +# +# --------------------------- +# CONNECTION TIMEOUT SETTINGS +# --------------------------- +# To globally specify the time to wait for a login PDU to be received from +# the target in response to a login request sent by the initiator before failing +# the connection attempt, uncomment the following line: +# +#LoginTimeout=<number> +# +# where <number> is in seconds. A setting of "0" will result in commands never +# being timed out. +# +# To globally specify the time to wait for a login PDU carrying authentication +# information to be received from the target in response to a login request sent +# by the initiator before failing the connection attempt, uncomment the +# following line: +# +#AuthTimeout=<number> +# +# where <number> is in seconds. A setting of "0" will result in commands never +# being timed out. +# +# To globally specify the time to wait on a connection with no traffic being +# received from the target before checking the connection by sending out a ping, +# uncomment the following line: +# +#IdleTimeout=<number> +# +# where <number> is in seconds. A setting of "0" will result in a ping never +# being sent. +# +# To globally specify the time to wait for a ping response after a ping has been +# sent to a target before failing the existing connection and initiating a new +# one, uncomment the following line: +# +#PingTimeout=<number> +# +# where <number> is in seconds. A setting of "0" will result in the ping command +# never timing out. +# +# The global connection timeout settings can be overridden on a per target name, +# discovery address or IP address basis. An example of setting the +# "LoginTimeout" value to 12 seconds for just target iqn.1987- +# 05.com.cisco:00.0d1d898e8d66.t0 would be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# LoginTimeout=12 +# +# An example of setting the "AuthTimeout" value to 8 seconds for just all +# targets found at address 192.168.10.94 would be: +# +#DiscoveryAddress=192.168.10.94 +# AuthTimeout=8 +# +# An example of setting the "IdleTimeout" value to 3 seconds for just all +# targets found on subnet 192.168.10.94 would be: +# +#Subnet=192.168.10.0/24 +# IdleTimeout=3 +# +# ------------------------ +# SESSION TIMEOUT SETTINGS +# ------------------------ +# To globally specify the length of time to wait for session re-establishment +# before failing SCSI commands back to the application, uncomment the +# following line: +# +#ConnFailTimeout=<number> +# +# where <number> is in seconds. A setting of "0" will result in commands never +# being failed back due to connection failure. +# +# The global session timeout settings can be overridden on a per target name +# basis. An example of setting the "ConnFailTimeout" value to 5 seconds for +# just target iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 would be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# ConnFailTimeout=5 +# +# If a third party multipathing application is being used, +# then the "ConnFailTimeout" should be set to smaller value +# such as 15. This value is just a guideline so the actual value will be +# dependent on the users operating environment. +# +# ------------------------------- +# ERROR HANDLING TIMEOUT SETTINGS +# ------------------------------- +# To globally specify the length of time to wait for an abort command to +# complete before declaring the abort command has failed, uncomment the +# following line: +# +#AbortTimeout=<number> +# +# where <number> is in seconds. A setting of "0" will result in commands never +# being timed out. +# +# To globally specify the length of time to wait for a reset command to complete +# before declaring that the reset command has failed, uncomment the following +# line: +# +#ResetTimeout=<number> +# +# where <number> is in seconds. A setting of "0" will result in commands never +# being timed out. +# +# The global error handling timeout settings can be overridden on a per target +# name or per IP address basis. An example of setting the "AbortTimeout" value +# to 10 seconds for just target iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 would +# be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# AbortTimeout=10 +# +# An example of setting the "ResetTimeout" value to 6 seconds for just all +# targets found on portal 192.168.10.94 would be: +# +#Subnet=192.168.10.0/24 +# ResetTimeout=6 +# +# ----------------------------- +# CONTINUOUS DISCOVERY SETTINGS +# ----------------------------- +# To globally specify that all discovery sessions be kept open, uncomment the +# following line: +# +#Continuous=yes +# +# To globally specify that all discovery sessions be closed once discovery is +# completed, uncomment the following line: +# +#Continuous=no +# +# The global continuous discovery setting can be overridden on a per target +# basis. An example of setting "Continuous" to "no" for just target iqn.1987- +# 05.com.cisco:00.0d1d898e8d66.t0 would be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# Continuous=no +# +# --------------------------------- +# ASYNC EVENT NOTIFICATION SETTINGS +# --------------------------------- +# To globally specify that the initiator wants to receive vendor specific async +# events from the target(s), uncomment the following line: +# +#SendAsyncText=yes +# +# To globally specify that the initiator does not want to receive vendor +# specific async events from the target(s), uncomment the following line: +# +#SendAsyncText=no +# +# The SendAsyncText key can be specified for a particular Discovery Address. +# +# The global async event notification setting can be overridden on a per target +# basis. An example of setting "SendAsyncText" to "no" for just target iqn.1987- +# 05.com.cisco:00.0d1d898e8d66.t0 would be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# SendAysncText=no +# +# ------------------------------------ +# iSCSI OPERATIONAL PARAMETER SETTINGS +# ------------------------------------ +# To globally enable R2T flow control (i.e., the initiator must wait for an R2T +# command before sending any data), uncomment the following line: +# +#InitialR2T=yes +# +# To globally disable R2T flow control (i.e., the initiator has an implied +# initial R2T of "FirstBurstLength" at offset 0), uncomment the following line: +# +#InitialR2T=no +# +# To globally enable immediate data (i.e., the initiator sends unsolicited data +# with the iSCSI command packet), uncomment the following line: +# +#ImmediateData=yes +# +# To globally disable immediate data (i.e., the initiator does not send +# unsolicited data with the iSCSI command PDU), uncomment the following line: +# +#ImmediateData=no +# +# To globally specify the maximum number of data bytes the initiator can receive +# in an iSCSI PDU from a target, uncomment the following line: +# +#MaxRecvDataSegmentLength=<number> +# +# where <number> is the number of bytes in the range of 512 to (2^24-1) +# +# To globally specify the maximum number of unsolicited data bytes the initiator +# can send in an iSCSI PDU to a target, uncomment the following line: +# +#FirstBurstLength=<number> +# +# where <number> is the number of bytes in the range of 512 to (2^24-1) +# +# To globally specify the maximum SCSI payload that the initiator will negotiate +# with the target for, uncomment the following line: +# +#MaxBurstLength=<number> +# +# where <number> is the number of bytes in the range of 512 to (2^24-1) +# +# To globally specifiy the maximum number of bytes that can be sent over a TCP +# connection by the initiator before receiving an acknowledgement from the +# target, uncomment the following line: +# +#TCPWindowSize=<number> +# +# where <number> is the number of bytes in the range of 512 to (2^24-1) +# +# The global iSCSI operational parameter setting can be overridden on a per +# target basis. An example of setting multiple parameters for just target +# iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 would be: +# +#TargetName=iqn.1987-05.com.cisco:00.0d1d898e8d66.t0 +# InitialR2T=no +# ImmediateData=no +# MaxRecvDataSegmentLength=128 * 1024 +# FirstBurstLength=262144 +# MaxBurstLength=(16 * 1024 * 1024) - 1024 +# TCPWindowSize=262144 +# +# The global "TCPWindowSize" setting can also be overridden on a per portal +# basis. An example of setting the "TCPWindowSize" for just subnet 10.77.13.0/16 +# would be: +# +#Subnet=10.77.13.0/16 +# TCPWindowSize=262144 +# +# ------------ +# SLP SETTINGS +# ------------ +# To globally configure the unicast IP address of the SLP service or directory +# agent (i.e., the address at which iSCSI targets can be discovered), uncomment +# the following line: +# +#SLPUnicast=<address> +# +# where <address> is single IP address. +# +# To globally configure the multicast IP address of the SLP service or directory +# agent (i.e., the address at which iSCSI targets can be discovered), uncomment +# the following line: +# +#SLPMulticast=<address> +# +# where <address> is one of the following values: +# "all" +# "none" +# a comma delimited list of IP addresses +# +# An example of valid SLPMulticast settings are: +# +#SLPMulticast=all +#SLPMulticast=none +#SLPMulticast=192.168.10.94,10.77.10.94 +# +# To enable CHAP authentication for every target discovered through a given SLP +# directory or service agent, add an "OutgoingUsername" and "OutgoingPassword" +# entry indented below the "SLPUnicast" or "SLPMulticast" entries. An example of +# these configurations would be: +# +#SLPUnicast=192.168.10.95 +# OutgoingUsername=alice +# OutgoingPassword=nty57nbe +# +#SLPMulticast=all +# OutgoingUsername=alice1 +# OutgoingPassword=nty57ocf +# +# To specify the time interval between the sending of successive SLP queries, +# uncomment the following line: +# +#PollInterval=<text> +# +# where <text> is specified in either seconds (e.g., "30s"), minutes +# (e.g., "3m") or hours (e.g., "5h"). + diff --git a/include/iscsi_proto.h b/include/iscsi_proto.h index a5c1ff9..172e94d 100644 --- a/include/iscsi_proto.h +++ b/include/iscsi_proto.h @@ -85,7 +85,7 @@ typedef struct iscsi_hdr { #define ISCSI_OP_LOGOUT_RSP 0x26 #define ISCSI_OP_R2T 0x31 #define ISCSI_OP_ASYNC_EVENT 0x32 -#define ISCSI_OP_REJECT_MSG 0x3f +#define ISCSI_OP_REJECT 0x3f /* SCSI Command Header */ typedef struct iscsi_cmd { @@ -168,13 +168,13 @@ typedef struct iscsi_async { uint8_t rsvd5[4]; } iscsi_async_t; -/* iSCSI Event Indicator values */ -#define ASYNC_EVENT_SCSI_EVENT 0 -#define ASYNC_EVENT_REQUEST_LOGOUT 1 -#define ASYNC_EVENT_DROPPING_CONNECTION 2 -#define ASYNC_EVENT_DROPPING_ALL_CONNECTIONS 3 -#define ASYNC_EVENT_PARAM_NEGOTIATION 4 -#define ASYNC_EVENT_VENDOR_SPECIFIC 255 +/* iSCSI Event Codes */ +#define ISCSI_ASYNC_MSG_SCSI_EVENT 0 +#define ISCSI_ASYNC_MSG_REQUEST_LOGOUT 1 +#define ISCSI_ASYNC_MSG_DROPPING_CONNECTION 2 +#define ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS 3 +#define ISCSI_ASYNC_MSG_PARAM_NEGOTIATION 4 +#define ISCSI_ASYNC_MSG_VENDOR_SPECIFIC 255 /* NOP-Out Message */ typedef struct iscsi_nopout { @@ -556,7 +556,7 @@ typedef struct iscsi_snack { #define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */ /* Reject Message Header */ -typedef struct iscsi_reject_rsp { +typedef struct iscsi_reject { uint8_t opcode; uint8_t flags; uint8_t reason; @@ -570,7 +570,7 @@ typedef struct iscsi_reject_rsp { uint32_t datasn; uint8_t rsvd5[8]; /* Text - Rejected hdr */ -} iscsi_reject_rsp_t; +} iscsi_reject_t; /* Reason for Reject */ #define CMD_BEFORE_LOGIN 1 diff --git a/kernel/Makefile b/kernel/Makefile index 4afab0f..865bcc6 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -2,7 +2,7 @@ # Makefile for the Linux Kernel iSCSI Initiator # -EXTRA_CFLAGS += -I$(obj) +EXTRA_CFLAGS += -I$(obj) -I$(obj)/../include obj-m = iscsi.o iscsi-y = iscsi_mgr.o iscsi_tcp.o diff --git a/kernel/iscsi_if.h b/kernel/iscsi_if.h index 4d2e83b..8924cb2 100644 --- a/kernel/iscsi_if.h +++ b/kernel/iscsi_if.h @@ -20,7 +20,7 @@ #define ISCSI_IF_H #include <net/tcp.h> -#include <iscsi.h> +#include <iscsi_proto.h> typedef void* iscsi_snx_h; /* iSCSI Data-Path session handle */ typedef void* iscsi_cnx_h; /* iSCSI Data-Path connection handle */ diff --git a/kernel/iscsi_mgr.c b/kernel/iscsi_mgr.c index c8547bc..7c104fd 100644 --- a/kernel/iscsi_mgr.c +++ b/kernel/iscsi_mgr.c @@ -20,7 +20,7 @@ #include <scsi/scsi_device.h> #include <scsi/scsi_transport.h> #include <iscsi_if.h> -#include <iscsi_control.h> +#include <iscsi_mgr.h> static iscsi_provider_t provider_table[ISCSI_PROVIDER_MAX]; @@ -715,7 +715,7 @@ iscsi_control_recv_pdu(iscsi_cnx_h handle, iscsi_hdr_t *hdr, char *data) case ISCSI_OP_TEXT_RSP: case ISCSI_OP_LOGOUT_RSP: case ISCSI_OP_ASYNC_EVENT: - case ISCSI_OP_REJECT_MSG: + case ISCSI_OP_REJECT: break; default: return -EPERM; diff --git a/kernel/iscsi_tcp.c b/kernel/iscsi_tcp.c index 2816e71..1ff2296 100644 --- a/kernel/iscsi_tcp.c +++ b/kernel/iscsi_tcp.c @@ -91,6 +91,9 @@ static int iscsi_proc_info(struct Scsi_Host *host, char *buffer, char **start, * * ******************************************************************************/ +/* + * Insert before consumer pointer + */ static void __insert(iscsi_queue_t *queue, void *data) { @@ -105,17 +108,6 @@ __insert(iscsi_queue_t *queue, void *data) queue->pool[--queue->cons] = data; } -/* - * Insert before consumer pointer - */ -static void -iscsi_insert(iscsi_queue_t *queue, void *data) -{ - spin_lock_bh(queue->lock); - __insert(queue, data); - spin_unlock_bh(queue->lock); -} - static void __enqueue(iscsi_queue_t *queue, void *data) { @@ -1027,7 +1019,7 @@ iscsi_hdr_recv(iscsi_conn_t *conn) case ISCSI_OP_TEXT_RSP: case ISCSI_OP_LOGOUT_RSP: case ISCSI_OP_ASYNC_EVENT: - case ISCSI_OP_REJECT_MSG: + case ISCSI_OP_REJECT: if (!conn->in.datalen) { rc = iscsi_control_recv_pdu( conn->handle, hdr, NULL); diff --git a/usr/Makefile b/usr/Makefile index 77e4b47..f9b0642 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -1,12 +1,13 @@ CFLAGS += -O2 -fno-inline -Wall -Wstrict-prototypes -g -I../include PROGRAMS = iscsid iscsiadm +COMMON_SRCS = io.o auth.o login.o ctldev.o log.o md5.o sha1.o all: $(PROGRAMS) -iscsid: io.o auth.o login.o iscsid.o ctldev.o log.o md5.o sha1.o ipc.o +iscsid: $(COMMON_SRCS) iscsid.o ipc.o $(CC) $^ -o $@ -iscsiadm: iscsiadm.o ctldev.o +iscsiadm: $(COMMON_SRCS) strings.o config.o discovery.o iscsiadm.o $(CC) $^ -o $@ clean: @@ -99,7 +99,7 @@ acl_chap_auth_request(struct iscsi_acl *client, char *username, unsigned int id, unsigned char *response_data, unsigned int rsp_length) { - struct iscsi_session *session = client->session_handle; + iscsi_session_t *session = client->session_handle; struct MD5Context context; unsigned char verify_data[16]; @@ -1323,7 +1323,7 @@ acl_recv_begin(struct iscsi_acl *client) } int -acl_recv_end(struct iscsi_acl *client, struct iscsi_session *session_handle) +acl_recv_end(struct iscsi_acl *client, iscsi_session_t *session_handle) { int next_phase_flag = 0; @@ -1337,7 +1337,7 @@ acl_recv_end(struct iscsi_acl *client, struct iscsi_session *session_handle) client->phase = AUTH_PHASE_ERROR; return AUTH_STATUS_ERROR; } - + if (client->recv_end_count > AUTH_RECV_END_MAX_COUNT) { client->rmt_auth_status = AUTH_STATUS_FAIL; client->phase = AUTH_PHASE_DONE; diff --git a/usr/config.c b/usr/config.c new file mode 100644 index 0000000..7908cd7 --- /dev/null +++ b/usr/config.c @@ -0,0 +1,3278 @@ +/* + * iSCSI Configuration Reader + * + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "config.h" +#include "log.h" + +#define CHAP_AUTHENTICATION 1 + +static void +iscsi_init_config_defaults(struct iscsi_config_defaults *defaults) +{ + /* enabled/disabled */ + defaults->enabled = 1; + + /* discovery defaults */ + defaults->continuous_sendtargets = 1; /* auto detect */ + defaults->send_async_text = 1; +#ifdef SLP_ENABLE + defaults->slp_multicast = 1; +#else + defaults->slp_multicast = 0; +#endif + defaults->slp_scopes = NULL; + defaults->slp_poll_interval = 5 * 60; /* 5 minutes */ + + /* auth options */ + defaults->auth_options.authmethod = CHAP_AUTHENTICATION; + defaults->auth_options.password_length = 0; + defaults->auth_options.password_length_in = 0; + + /* connection timeouts */ + defaults->connection_timeout_options.login_timeout = 15; + defaults->connection_timeout_options.auth_timeout = 45; + defaults->connection_timeout_options.active_timeout = 5; + defaults->connection_timeout_options.idle_timeout = 60; + defaults->connection_timeout_options.ping_timeout = 5; + + /* error timeouts */ + defaults->error_timeout_options.abort_timeout = 10; + defaults->error_timeout_options.reset_timeout = 30; + + /* session timeouts */ + defaults->session_timeout_options.replacement_timeout = 0; + + /* tcp options */ + defaults->tcp_options.window_size = 256 * 1024; + + /* iSCSI operational parameters */ + defaults->iscsi_options.InitialR2T = 0; + defaults->iscsi_options.ImmediateData = 1; + defaults->iscsi_options.MaxRecvDataSegmentLength = 128 * 1024; + defaults->iscsi_options.FirstBurstLength = 256 * 1024; + defaults->iscsi_options.MaxBurstLength = (16 * 1024 * 1024) - 1024; + defaults->iscsi_options.DefaultTime2Wait = 0; + /* we only use session reinstatement (ERL 0) */ + defaults->iscsi_options.DefaultTime2Retain = 0; + /* we only use session reinstatement (ERL 0) */ + defaults->iscsi_options.HeaderDigest = CONFIG_DIGEST_PREFER_OFF; + defaults->iscsi_options.DataDigest = CONFIG_DIGEST_PREFER_OFF; + +} + +char * +get_iscsi_initiatorname(char *pathname) +{ + FILE *f = NULL; + int c; + char *line, buffer[1024]; + char *name = NULL; + + if (!pathname) { + log_error("No pathname to load InitiatorName from"); + return NULL; + } + + /* get the InitiatorName */ + if ((f = fopen(pathname, "r"))) { + while ((line = fgets(buffer, sizeof (buffer), f))) { + + while (line && isspace(c = *line)) + line++; + + if (strncmp(line, "InitiatorName=", 14) == 0) { + char *end = line + 14; + + /* the name is everything up to the first + * bit of whitespace + */ + while (*end && (!isspace(c = *end))) + end++; + + if (isspace(c = *end)) + *end = '\0'; + + if (end > line + 14) + name = strdup(line + 14); + } + } + fclose(f); + if (!name) { + log_error( + "an InitiatorName is required, but " + "was not found in %s", pathname); + return NULL; + } else { + log_debug(5, "InitiatorName=%s\n", name); + } + return name; + } else { + log_error("cannot open InitiatorName configuration file %s", + pathname); + return NULL; + } +} + +int +add_config_entry(struct iscsi_config *config, struct iscsi_config_entry *entry) +{ + if (config == NULL || entry == NULL) + return 0; + + if (config->head) { + entry->prev = config->tail; + entry->next = NULL; + config->tail->next = entry; + config->tail = entry; + } else { + entry->next = entry->prev = NULL; + config->head = config->tail = entry; + } + + return 1; +} + +int +remove_config_entry(struct iscsi_config *config, + struct iscsi_config_entry *entry) +{ + if (config == NULL || entry == NULL) + return 0; + + if (entry == config->head) { + config->head = entry->next; + if (config->head == NULL) + config->tail = NULL; + entry->next = entry->prev = NULL; + return 1; + } else if (entry == config->tail) { + entry->prev->next = NULL; + config->tail = entry->prev; + entry->next = entry->prev = NULL; + return 1; + } else if (entry->prev && entry->next) { + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + entry->next = entry->prev = NULL; + return 1; + } else { + return 0; + } +} + +void +free_config_entry(struct iscsi_config_entry *entry) +{ + if (entry == NULL) + return; + + switch (entry->type) { + case CONFIG_TYPE_SENDTARGETS:{ + struct iscsi_sendtargets_config *sendtargets = + entry->config.sendtargets; + + if (sendtargets->address) { + free(sendtargets->address); + sendtargets->address = NULL; + } + + free(sendtargets); + entry->config.sendtargets = NULL; + break; + } + case CONFIG_TYPE_SLP:{ + struct iscsi_slp_config *slp = entry->config.slp; + + if (slp->interfaces) { + free(slp->interfaces); + slp->interfaces = NULL; + } + + if (slp->address) { + free(slp->address); + slp->address = NULL; + } + + if (slp->scopes) { + free(slp->scopes); + slp->scopes = NULL; + } + + free(slp); + entry->config.slp = NULL; + break; + } + case CONFIG_TYPE_DISCOVERY_FILE:{ + struct iscsi_discovery_file_config *file = + entry->config.file; + + if (file->filename) { + free(file->filename); + file->filename = NULL; + } + if (file->address) { + free(file->address); + file->address = NULL; + } + if (file->port) { + free(file->port); + file->port = NULL; + } + + free(file); + entry->config.file = NULL; + break; + } + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config *targetname = + entry->config.targetname; + + if (targetname->TargetName) + free(targetname->TargetName); + + free(targetname); + entry->config.targetname = NULL; + break; + } + case CONFIG_TYPE_SUBNET:{ + struct iscsi_subnet_config *subnet = + entry->config.subnet; + + if (subnet->address) + free(subnet->address); + + free(subnet); + entry->config.subnet = NULL; + break; + } + default: + log_error("can't free unknown config entry %p type %u\n", + entry, entry->type); + break; + } + + free(entry); +} + +struct iscsi_auth_config * +entry_auth_options(struct iscsi_config_entry *entry) +{ + if (entry == NULL) + return NULL; + + switch (entry->type) { + case CONFIG_TYPE_SENDTARGETS:{ + struct iscsi_sendtargets_config *sendtargets = + entry->config.sendtargets; + + return &sendtargets->auth_options; + } + case CONFIG_TYPE_SLP:{ + struct iscsi_slp_config *slp = entry->config.slp; + + return &slp->auth_options; + } + case CONFIG_TYPE_DISCOVERY_FILE:{ + struct iscsi_discovery_file_config *file = + entry->config.file; + + return &file->auth_options; + } + case CONFIG_TYPE_TARGETNAME:{ +#ifdef PER_TARGETNAME_AUTH + struct iscsi_targetname_config *targetname = + entry->config.targetname; + + return &targetname->auth_options; +#else + /* disable configuring auth by TargetName for now, + * and always get the auth options at run-time + * from discovery + */ + return NULL; +#endif + } + case CONFIG_TYPE_SUBNET:{ + return NULL; + } + default: + return NULL; + } +} + +struct iscsi_connection_timeout_config * +entry_connection_timeout_options(struct iscsi_config_entry *entry) +{ + if (entry == NULL) + return NULL; + + switch (entry->type) { + case CONFIG_TYPE_SENDTARGETS:{ + struct iscsi_sendtargets_config *sendtargets = + entry->config.sendtargets; + + return &sendtargets->connection_timeout_options; + } + case CONFIG_TYPE_SLP:{ + return NULL; + } + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config *targetname = + entry->config.targetname; + + return &targetname->connection_timeout_options; + } + case CONFIG_TYPE_SUBNET:{ + struct iscsi_subnet_config *subnet = + entry->config.subnet; + + return &subnet->connection_timeout_options; + } + default: + return NULL; + } +} + +struct iscsi_session_timeout_config * +entry_session_timeout_options(struct iscsi_config_entry *entry) +{ + if (entry == NULL) + return NULL; + + switch (entry->type) { + case CONFIG_TYPE_SENDTARGETS:{ + return NULL; + } + case CONFIG_TYPE_SLP:{ + return NULL; + } + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config *targetname = + entry->config.targetname; + + return &targetname->session_timeout_options; + } + case CONFIG_TYPE_SUBNET:{ + return NULL; + } + default: + return NULL; + } +} + +struct iscsi_error_timeout_config * +entry_error_timeout_options(struct iscsi_config_entry *entry) +{ + if (entry == NULL) + return NULL; + + switch (entry->type) { + case CONFIG_TYPE_SENDTARGETS:{ + return NULL; + } + case CONFIG_TYPE_SLP:{ + return NULL; + } + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config *targetname = + entry->config.targetname; + + return &targetname->error_timeout_options; + } + case CONFIG_TYPE_SUBNET:{ + struct iscsi_subnet_config *subnet = + entry->config.subnet; + + return &subnet->error_timeout_options; + } + default: + return NULL; + } +} + +struct iscsi_tcp_config * +entry_tcp_options(struct iscsi_config_entry *entry) +{ + if (entry == NULL) + return NULL; + + switch (entry->type) { + case CONFIG_TYPE_SENDTARGETS:{ + return NULL; + } + case CONFIG_TYPE_SLP:{ + return NULL; + } + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config *targetname = + entry->config.targetname; + + return &targetname->tcp_options; + } + case CONFIG_TYPE_SUBNET:{ + struct iscsi_subnet_config *subnet = + entry->config.subnet; + + return &subnet->tcp_options; + } + default: + return NULL; + } +} + +struct iscsi_operational_config * +entry_iscsi_options(struct iscsi_config_entry *entry) +{ + if (entry == NULL) + return NULL; + + switch (entry->type) { + case CONFIG_TYPE_SENDTARGETS:{ + return NULL; + } + case CONFIG_TYPE_SLP:{ + return NULL; + } + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config *targetname = + entry->config.targetname; + + return &targetname->iscsi_options; + } + case CONFIG_TYPE_SUBNET:{ + return NULL; + } + default: + return NULL; + } +} + +int +same_portal_descriptor(struct iscsi_portal_descriptor *p1, + struct iscsi_portal_descriptor *p2) +{ + if (p1->port != p2->port) + return 0; + + if (p1->tag != p2->tag) + return 0; + + if (p1->ip_length != p2->ip_length) + return 0; + + if (memcmp(p1->ip, p2->ip, p1->ip_length)) + return 0; + + return 1; +} + +int +same_portal_descriptors(struct iscsi_portal_descriptor *portals1, + struct iscsi_portal_descriptor *portals2) +{ + struct iscsi_portal_descriptor *p1, *p2; + + if (portals1 && !portals2) + return 0; + + if (!portals1 && portals2) + return 0; + + /* same when p1 is a subset of p2 and p2 is a subset of p1 */ + + for (p1 = portals1; p1; p1 = p1->next) { + int same = 0; + + for (p2 = portals2; p2; p2 = p2->next) { + if (same_portal_descriptor(p1, p2)) { + same = 1; + break; + } + } + + if (!same) + return 0; + } + + for (p2 = portals2; p2; p2 = p2->next) { + int same = 0; + + for (p1 = portals1; p1; p1 = p1->next) { + if (same_portal_descriptor(p1, p2)) { + same = 1; + break; + } + } + + if (!same) + return 0; + } + + return 1; +} + +int +same_portal_config(struct iscsi_portal_config *p1, + struct iscsi_portal_config *p2) +{ + /* Note: this needs to be updated whenever new structures are + * added to the portal config + */ + if (memcmp + (&p1->connection_timeout_options, &p2->connection_timeout_options, + sizeof (p1->connection_timeout_options))) + return 0; + + if (memcmp(&p1->session_timeout_options, &p2->session_timeout_options, + sizeof (p1->session_timeout_options))) + return 0; + + if (memcmp(&p1->error_timeout_options, &p2->error_timeout_options, + sizeof (p1->error_timeout_options))) + return 0; + + if (memcmp(&p1->tcp_options, &p2->tcp_options, + sizeof (p1->tcp_options))) + return 0; + + if (memcmp(&p1->iscsi_options, &p2->iscsi_options, + sizeof (p1->iscsi_options))) + return 0; + + if (!same_portal_descriptor(p1->descriptor, p2->descriptor)) + return 0; + + return 1; +} + +int +same_portal_configs(struct iscsi_portal_config *p1, + struct iscsi_portal_config *p2) +{ + /* FIXME: ordering currently matters, but perhaps shouldn't */ + while (p1 || p2) { + + if (p1 && !p2) + return 0; + + if (!p1 && p2) + return 0; + + if (!same_portal_config(p1, p2)) + return 0; + + p1 = p1->next; + p2 = p2->next; + } + + return 1; +} + +int +same_session_config(struct iscsi_session_config *s1, + struct iscsi_session_config *s2) +{ + /* Note: this needs to be updated whenever fields are + * added to struct iscsi_session_config + */ + + if (s1->iscsi_bus != s2->iscsi_bus) + return 0; + + if (s1->target_id != s2->target_id) + return 0; + + if (memcmp(s1->isid, s2->isid, sizeof (s1->isid))) + return 0; + + if (s1->path_number != s2->path_number) + return 0; + + if (!same_portal_config(s1->portal, s2->portal)) + return 0; + + return 1; +} + +int +same_target_config(struct iscsi_target_config *t1, + struct iscsi_target_config *t2) +{ + /* Note: this needs to be updated whenever fields are + * added to struct iscsi_target_config + */ + + if (!t1 && !t2) + return 1; + + if (t1 && !t2) + return 0; + + if (!t1 && t2) + return 0; + + if (t1->enabled != t2->enabled) + return 0; + + if (memcmp + (&t1->auth_options, &t2->auth_options, sizeof (t1->auth_options))) + return 0; + + /* we don't recursively compare session configs */ + + return 1; +} + +void +free_portal_descriptors(struct iscsi_portal_descriptor *portals) +{ + struct iscsi_portal_descriptor *portal; + + while ((portal = portals)) { + portals = portal->next; + if (portal->address) { + log_debug(6, "freeing portal descriptor address %p", + portal->address); + free(portal->address); + } + log_debug(6, "freeing portal descriptor %p", portal); + free(portal); + } +} + +void +free_session_config(struct iscsi_session_config *config) +{ + + /* free the portal config */ + + /* + * we assume something else (the daemon's struct iscsi_target) + * is managing the lifetime of the descriptors, so we + * don't free them here. + */ + + log_debug(6, "freeing portal config %p of session config %p", + config->portal, config); + free(config->portal); + + /* and the config itself */ + log_debug(6, "freeing session config %p of target config %p", config, + config->target); + free(config); +} + +void +free_target_config(struct iscsi_target_config *config) +{ + struct iscsi_session_config *session; + + /* free the session configs */ + while ((session = config->sessions)) { + config->sessions = session->next; + + free_session_config(session); + } + + /* we assume something else (the daemon's struct iscsi_target) + * is managing the lifetime of the TargetName, so we dont + * free it here. + */ + config->TargetName = NULL; + + /* don't leave passwords in memory */ + memset(&config->auth_options, 0, sizeof (config->auth_options)); + + /* free the config itself */ + log_debug(6, "freeing target config %p", config); + free(config); +} + +int +parse_boolean(char *str) +{ + char *end = str; + int value = -1; + int c; + + /* stop at the first whitespace */ + while (*end && !isspace(c = *end)) + end++; + *end = '\0'; + + if (strncasecmp(str, "yes", 3) == 0) { + value = 1; + } else if (strncasecmp(str, "no", 2) == 0) { + value = 0; + } else { + value = strtol(str, &end, 0); + /* check for invalid input */ + if (*str && (*end != '\0')) + value = -1; + } + + return value; +} + +int +parse_number(char *str) +{ + char *end = str; + int number = -1; + int c; + + /* stop at the first whitespace */ + while (*end && !isspace(c = *end)) + end++; + *end = '\0'; + + number = strtol(str, &end, 0); + + if (*str && (*end == '\0')) + return number; /* it was all valid */ + else + return -1; /* something was invalid */ +} + +/* FIXME: accept suffixes for seconds, minutes, hours */ +int +parse_time(char *str) +{ + char *end = str; + int number = -1; + int c; + int units = 1; + + /* stop at the first whitespace */ + while (*end && !isspace(c = *end)) + end++; + *end = '\0'; + + end--; + switch (*end) { + case 's': + units = 1; /* seconds */ + *end = '\0'; + break; + case 'm': + units = 60; /* minutes */ + *end = '\0'; + break; + case 'h': + units = 60 * 60; /* hours */ + *end = '\0'; + break; + default: + /* let strtol flag it as invalid */ + break; + } + end++; + + /* FIXME: check for overflow */ + number = strtol(str, &end, 0) * units; + + if (*str && (*end == '\0')) + return number; /* it was all valid */ + else + return -1; /* something was invalid */ +} + +/* + * If string is quoted, terminate it at the end quote + * - else terminate at first whitespace. + * Return pointer after leading quote. + */ +char * +parse_quoted_string(char *strp) +{ + char *cp, *retp = strp; + int c; + + if (*strp == '"') { + cp = ++strp; + retp = cp; + /* find the end quote and NUL it */ + while ((*cp != '\0') && (*cp != '"')) { + cp++; + } + *cp = '\0'; + } else { + /* not quoted - terminate it at first whitespace */ + cp = strp; + while ((*cp != '\0') && (!isspace(c = *cp))) { + cp++; + } + *cp = '\0'; + } + + return retp; +} + +/* + * update the existing config so that it matches + * what's currently in the config file + * FIXME: use a real parser. + */ +int +update_iscsi_config(const char *pathname, struct iscsi_config *config) +{ + FILE *f = NULL; + char *line, *nl, buffer[2048]; + int c; + struct iscsi_config_entry *entry = NULL, *current_entry = + NULL, *slp_entry = NULL; + int indent = 0, entry_indent = 0; + int line_number = 1; + int slp_multicast_seen = 0; + + if (!config) + return 0; + + f = fopen(pathname, "r"); + if (!f) { + log_error("Cannot open configuration file %s", pathname); + return 0; + } + + log_debug(5, "updating config %p from %s", config, pathname); + + /* clear out any existing config */ + while ((entry = config->head)) { + remove_config_entry(config, entry); + free_config_entry(entry); + } + + + memset(config, 0, sizeof (*config)); + config->head = config->tail = NULL; + + /* reset to the platform's usual defaults */ + iscsi_init_config_defaults(&config->defaults); + + /* process the config file */ + do { + line = fgets(buffer, sizeof (buffer), f); + line_number++; + if (!line) + continue; + + /* skip but record leading whitespace */ + indent = 0; + while (isspace(c = *line)) { + if (*line == '\t') + indent += 8; + else + indent++; + + line++; + } + + /* strip trailing whitespace, including the newline. + * anything that needs the whitespace must be quoted. + */ + nl = line + strlen(line) - 1; + if (*nl == '\n') { + do { + *nl = '\0'; + nl--; + } while (isspace(c = *nl)); + } else { + log_error("config file line %d too long", + line_number); + return 0; + } + + /* process any non-empty, non-comment lines */ + if (*line && (*line != '#')) { + + log_debug(7, "config indent %d, line %s", indent, line); + + /* if this line isn't indented farther than + * the current entry, it's unrelated. + */ + if (indent <= entry_indent) + current_entry = NULL; + + if ((strncasecmp(line, "TargetIpAddr=", 13) == 0) || + (strncasecmp(line, "DiscoveryAddress=", 17) == 0)) { + char *addr = NULL, *port = NULL; + char *sep = line; + struct iscsi_sendtargets_config + *sendtargets_config = NULL; + + /* find the start of the address */ + while (*sep && *sep != '=') + sep++; + addr = ++sep; + if (*addr) { + /* look for a port number */ + /* FIXME: ought to handle the + * IPv6 syntax in the iSCSI spec + */ + while (*sep && !isspace(c = *sep) + && *sep != ':') + sep++; + if (*sep == ':') { + *sep = '\0'; + port = ++sep; + while (*sep + && isdigit(c = + *sep)) + sep++; + *sep = '\0'; + } else + *sep = '\0'; + + /* create a new sendtargets config entry + */ + entry = calloc(1, sizeof (*entry)); + if (entry == NULL) { + log_error( + "failed to allocate " + "config entry"); + return 0; + } + entry->line_number = line_number; + + sendtargets_config = + calloc(1, + sizeof + (*sendtargets_config)); + if (sendtargets_config == NULL) { + free(entry); + entry = NULL; + log_error( + "failed to allocate " + "sendtargets config"); + return 0; + } + + entry->type = CONFIG_TYPE_SENDTARGETS; + entry->config.sendtargets = + sendtargets_config; + + /* capture the current global defaults + */ + memcpy(&sendtargets_config-> + auth_options, + &config->defaults.auth_options, + sizeof (sendtargets_config-> + auth_options)); + memcpy(&sendtargets_config-> + connection_timeout_options, + &config->defaults. + connection_timeout_options, + sizeof (sendtargets_config-> + connection_timeout_options)); + sendtargets_config->continuous = + config->defaults. + continuous_sendtargets; + sendtargets_config->send_async_text = + config->defaults.send_async_text; + + /* record the address and port */ + sendtargets_config->address = + strdup(addr); + if (port && *port + && atoi(port) > 0) + sendtargets_config->port = + atoi(port); + else + sendtargets_config->port = + ISCSI_DEFAULT_PORT; + + /* append it to the list of all + * config entries + */ + add_config_entry(config, entry); + log_debug(5, + "config entry %p " + "sendtargets %p = %s:%d", + entry, sendtargets_config, + addr, + sendtargets_config->port); + + /* indented settings in the config file + * may modify this entry + */ + current_entry = entry; + entry_indent = indent; + } else { + log_error( + "error on line %d of %s, an " + "address is required", + line_number, pathname); + } + } else if (strncasecmp(line, "DiscoveryFile=", 14) == 0) { + char *filename = line + 14; + struct iscsi_discovery_file_config *file_config; + + if (strlen(filename)) { + /* create a new sendtargets config entry */ + entry = calloc(1, sizeof (*entry)); + if (entry == NULL) { + log_error( + "failed to allocate " + "config entry"); + return 0; + } + entry->line_number = line_number; + + file_config = + calloc(1, sizeof (*file_config)); + if (file_config == NULL) { + free(entry); + entry = NULL; + log_error( + "failed to allocate " + "discovery file config"); + return 0; + } + + entry->type = + CONFIG_TYPE_DISCOVERY_FILE; + entry->config.file = file_config; + + /* capture the current global defaults + */ + file_config->read_size = 512; + /* FIXME: make this configurable */ + file_config->continuous = + config->defaults. + continuous_sendtargets; + memcpy(&file_config->auth_options, + &config->defaults.auth_options, + sizeof (file_config-> + auth_options)); + + /* record the filename */ + file_config->filename = + strdup(filename); + + /* append it to the list of all + * config entries + */ + add_config_entry(config, entry); + log_debug(5, + "config entry %p discovery " + "file %p = %s", + entry, file_config, filename); + + /* indented settings in the config file + * may modify this entry + */ + current_entry = entry; + entry_indent = indent; + } else { + log_error( + "error on line %d of %s, " + "DiscoveryFile entry requires " + "a filename", + line_number, pathname); + } + } else if (strncasecmp(line, "Continuous=", 11) == 0) { + int value = parse_boolean(line + 11); + + if (value < 0) { + log_error( + "error on line %d of %s, " + "invalid value %s", + line_number, pathname, + line + 11); + } else if (current_entry + && current_entry->type == + CONFIG_TYPE_SENDTARGETS) { + struct iscsi_sendtargets_config + *sendtargets = + current_entry->config.sendtargets; + + sendtargets->continuous = value; + log_debug(5, + "config entry %p sendtargets " + "config %p continuous %d", + current_entry, sendtargets, + value); + } else if (current_entry + && current_entry->type == + CONFIG_TYPE_DISCOVERY_FILE) { + struct iscsi_discovery_file_config + *file_config = + current_entry->config.file; + + file_config->continuous = value; + log_debug(5, + "config entry %p discovery " + "file config %p continuous %d", + current_entry, file_config, + value); + } else { + config->defaults. + continuous_sendtargets = value; + log_debug(5, + "config global continuous " + "discovery %d", + value); + } + } else if (strncasecmp(line, "SendAsyncText=", 14) == 0) { + char *str = &line[14]; + int value = parse_boolean(str); + + if (value >= 0) { + if (current_entry + && current_entry->type == + CONFIG_TYPE_SENDTARGETS) { + struct iscsi_sendtargets_config + *sendtargets = + current_entry->config. + sendtargets; + sendtargets->send_async_text = + value; + log_debug(5, + "config entry %p " + "sendtargets config%p " + "sendasynctext %d", + current_entry, + sendtargets, value); + } else { + config->defaults. + send_async_text = value; + log_debug(5, + "config global " + "SendAsyncText " + "value %d", + value); + } + } else { + log_error( + "error on line %d of %s, " + "invalid value %s", + line_number, pathname, + line + 14); + } + } else if (strncasecmp(line, "ReadSize=", 9) == 0) { + int value = parse_number(line + 9); + + /* for testing, allow variable read sizes + * to simulate varying PDU sizes + */ + if (value < 0) { + log_error( + "error on line %d of %s, " + "invalid entry %s", + line_number, pathname, line); + } else if (current_entry + && current_entry->type == + CONFIG_TYPE_DISCOVERY_FILE) { + struct iscsi_discovery_file_config + *file_config = + current_entry->config.file; + + file_config->read_size = value; + log_debug(5, + "config entry %p discovery " + "file config %p continuous %d", + current_entry, file_config, + value); + } else { + log_error( + "error on line %d of %s, " + "invalid entry %s", + line_number, pathname, line); + } + } else if (strncasecmp(line, "DefaultAddress=", 15) == + 0) { + /* allow DiscoveryFile entries to specify + * a default address, so that TargetAddresses + * can be omitted from the discovery file. + */ + if (current_entry + && current_entry->type == + CONFIG_TYPE_DISCOVERY_FILE) { + struct iscsi_discovery_file_config + *file_config = + current_entry->config.file; + char *address = line + 15; + char *port = NULL; + + if ((port = strrchr(address, ':'))) { + *port = '\0'; + port++; + file_config->port = + strdup(port); + } else { + file_config->port = + strdup("3260"); + } + + file_config->address = strdup(address); + log_debug(5, + "config entry %p discovery " + "file config %p address %s " + "port %s ", + current_entry, file_config, + file_config->address, + file_config->port); + } else { + log_error( + "error on line %d of %s, " + "invalid entry %s", + line_number, pathname, line); + } + } else if (strncasecmp(line,"SLPMulticast=",13) == 0) { + char *value = parse_quoted_string(line + 13); + struct iscsi_slp_config *slp_config; + + slp_multicast_seen = 1; + + if ((value == NULL) || (*value == '\0')) { + log_error( + "error on line %d of %s, " + "SLPMulticast requires a list " + "of interface names\n", + line_number, pathname); + continue; + } + + /* add an entry for SLP */ + entry = calloc(1, sizeof (*entry)); + if (entry == NULL) { + log_error( + "failed to allocate " + "config entry"); + return 0; + } + entry->line_number = line_number; + + slp_config = + calloc(1, sizeof (struct iscsi_slp_config)); + if (slp_config == NULL) { + log_error( + "failed to allocate SLP config"); + return 0; + } + + entry->type = CONFIG_TYPE_SLP; + entry->config.slp = slp_config; + + slp_entry = entry; + + /* capture the global defaults */ + memcpy(&slp_config->auth_options, + &config->defaults.auth_options, + sizeof (slp_config->auth_options)); + if (config->defaults.slp_scopes) + slp_config->scopes = + strdup(config->defaults.slp_scopes); + else + slp_config->scopes = NULL; + slp_config->poll_interval = + config->defaults.slp_poll_interval; + + /* multicast on the specified interfaces */ + slp_config->interfaces = strdup(value); + slp_config->address = NULL; + slp_config->port = 0; + + /* append it to the list of all config entries + */ + add_config_entry(config, entry); + log_debug(5, + "config entry %p SLPMulticast %p = %s", + entry, slp_config, value); + + /* indented settings in the config file + * may modify this entry + */ + current_entry = entry; + entry_indent = indent; + } else if (strncasecmp(line, "SLPUnicast=", 11) == 0) { + char *addr = NULL, *port = NULL; + char *sep = line; + struct iscsi_slp_config *slp_config = NULL; + + /* find the start of the address */ + while (*sep && *sep != '=') + sep++; + + addr = ++sep; + if (*addr) { + /* look for a port number */ + /* FIXME: ought to handle the IPv6 + * syntax in the iSCSI spec + */ + while (*sep && !isspace(c = *sep) + && *sep != ':') + sep++; + if (*sep == ':') { + *sep = '\0'; + port = ++sep; + while (*sep + && isdigit(c = *sep)) + sep++; + *sep = '\0'; + } else + *sep = '\0'; + + /* create a new slp config entry */ + entry = calloc(1, sizeof (*entry)); + if (entry == NULL) { + log_error( + "failed to allocate " + "config entry"); + return 0; + } + entry->line_number = line_number; + + slp_config = + calloc(1, sizeof (*slp_config)); + if (slp_config == NULL) { + free(entry); + entry = NULL; + log_error( + "failed to allocate " + "SLP config"); + return 0; + } + + entry->type = CONFIG_TYPE_SLP; + entry->config.slp = slp_config; + + /* capture the current global defaults + */ + memcpy(&slp_config->auth_options, + &config->defaults.auth_options, + sizeof (slp_config-> + auth_options)); + if (config->defaults.slp_scopes) + slp_config->scopes = + strdup(config->defaults. + slp_scopes); + else + slp_config->scopes = NULL; + slp_config->poll_interval = + config->defaults.slp_poll_interval; + + /* record the address and port */ + slp_config->address = strdup(addr); + if (port && *port && (atoi(port) > 0)) /* FIXME: check for invalid port strings */ + slp_config->port = + atoi(port); + else + slp_config->port = ISCSI_DEFAULT_PORT; /* FIXME: what is the default SLP port? */ + + slp_config->interfaces = NULL; + /* let the OS pick an interface + * for unicasts + */ + + /* append it to the list of all + * config entries + */ + add_config_entry(config, entry); + log_debug(5, + "config entry %p SLPUnicast " + "%p = %s:%d", + entry, slp_config, addr, + slp_config->port); + + /* indented settings in the config file + * may modify this entry + */ + current_entry = entry; + entry_indent = indent; + } else { + log_error( + "error on line %d of %s, an " + "address is required", + line_number, pathname); + } + } else if (strncasecmp(line, "PollInterval=", 13) == 0) { + int value = parse_time(line + 13); + + if (value < 0) { + log_error( + "error on line %d of %s, " + "illegal value %s", + line_number, pathname, + line + 13); + } else if (current_entry + && current_entry->type == + CONFIG_TYPE_SLP) { + struct iscsi_slp_config *slp = + current_entry->config.slp; + + slp->poll_interval = value; + log_debug(5, + "config entry %p poll " + "interval %d", + slp, value); + } else { + config->defaults.slp_poll_interval = + value; + log_debug(5, + "config global SLP " + "poll interval %d", + value); + } + } else if (strncasecmp(line, "TargetName=", 11) == 0) { + /* settings for a specific iSCSI TargetName + * (can be quoted) + */ + char *n = parse_quoted_string(line + 11); + struct iscsi_targetname_config + *targetname_config; + + entry = calloc(1, sizeof (*entry)); + if (entry == NULL) { + log_error( + "failed to allocate " + "config entry"); + return 0; + } + entry->line_number = line_number; + + targetname_config = + calloc(1, sizeof (*targetname_config)); + if (targetname_config == NULL) { + free(entry); + entry = NULL; + log_error( + "failed to allocate " + "TargetName config"); + return 0; + } + + entry->type = CONFIG_TYPE_TARGETNAME; + entry->config.targetname = targetname_config; + + targetname_config->TargetName = strdup(n); + + /* capture the current global defaults */ + targetname_config->enabled = + config->defaults.enabled; + + memcpy(&targetname_config->auth_options, + &config->defaults.auth_options, + sizeof (targetname_config-> + auth_options)); + + memcpy(&targetname_config-> + connection_timeout_options, + &config->defaults. + connection_timeout_options, + sizeof (targetname_config-> + connection_timeout_options)); + memcpy(&targetname_config-> + session_timeout_options, + &config->defaults. + session_timeout_options, + sizeof (targetname_config-> + session_timeout_options)); + memcpy(&targetname_config-> + error_timeout_options, + &config->defaults.error_timeout_options, + sizeof (targetname_config-> + error_timeout_options)); + memcpy(&targetname_config->tcp_options, + &config->defaults.tcp_options, + sizeof (targetname_config->tcp_options)); + memcpy(&targetname_config->iscsi_options, + &config->defaults.iscsi_options, + sizeof (targetname_config-> + iscsi_options)); + + /* append it to the list of all config entries + */ + add_config_entry(config, entry); + log_debug(5, + "config entry %p targetname %p = %s", + entry, targetname_config, n); + + /* indented settings in the config file + * may modify this entry + */ + current_entry = entry; + entry_indent = indent; + } else if (strncasecmp(line, "Enabled=", 8) == 0) { + char *str = &line[8]; + int value = parse_boolean(str); + + if (value >= 0) { + if (current_entry) { + if (current_entry->type == + CONFIG_TYPE_TARGETNAME) { + struct iscsi_targetname_config + *targetname_config = + current_entry-> + config.targetname; + + targetname_config-> + enabled = value; + log_debug(5, + "config entry " + "%p targetname" + " %p to %s %s", + current_entry, + targetname_config, + targetname_config-> + TargetName, + value ? + "enabled" : + "disabled"); + } else { + log_error( + "error on line " + "%d of %s, " + "enabled does " + "not apply to " + "the current " + "entry", + line_number, + pathname); + } + } else { + log_debug(5, + "config global " + "targets %s", + value ? "enabled" : + "disabled"); + config->defaults.enabled = + value; + } + } else + log_error( + "error on line %d of %s, " + "illegal value %s", + line_number, pathname, str); + } else if ((strncasecmp(line, "Username=", 9) == 0) || + (strncasecmp(line, "UsernameOut=", 12) == 0) || + (strncasecmp(line, "OutgoingUsername=", 17) == + 0)) { + /* Username string can be quoted */ + char *u, *cp = &line[8]; + + /* find start of value */ + while (*cp && *cp != '=') { + cp++; + } + u = parse_quoted_string(++cp); + + if (current_entry) { + struct iscsi_auth_config *auth_options = + entry_auth_options(current_entry); + if (auth_options) { + strncpy(auth_options->username, + u, + sizeof (auth_options-> + username)); + auth_options-> + username[sizeof + (auth_options-> + username) - 1] = + '\0'; + log_debug(5, + "config entry %p " + "outgoing username %s", + current_entry, + auth_options-> + username); + } else { + log_error( + "Invalid entry on line" + " %d of %s," + " current entry cannot " + "have an outgoing " + "username, see man " + "page of iscsi.conf", + line_number, pathname); + } + } else { + /* default to use while processing the + * rest of the config file + */ + strncpy(config->defaults.auth_options. + username, u, + sizeof (config->defaults. + auth_options.username)); + config->defaults.auth_options. + username[sizeof + (config->defaults. + auth_options.username) - + 1] = '\0'; + log_debug(5, + "config global outgoing " + "username %s", + config->defaults.auth_options. + username); + } + } else if ((strncasecmp(line, "UsernameIn=", 11) == 0) + || + (strncasecmp(line, "IncomingUsername=", 17) + == 0)) { + char *u, *cp = line + 10; + + /* find start of value */ + while (*cp && *cp != '=') { + cp++; + } + u = parse_quoted_string(++cp); + + if (current_entry) { + struct iscsi_auth_config *auth_options = + entry_auth_options(current_entry); + if (auth_options) { + strncpy(auth_options-> + username_in, u, + sizeof (auth_options-> + username_in)); + auth_options-> + username_in[sizeof + (auth_options-> + username_in) - + 1] = '\0'; + log_debug(5, + "config entry %p " + "incoming username %s", + current_entry, + auth_options-> + username_in); + } else { + log_error( + "Invalid entry on line" + " %d of %s," + " current entry cannot " + "have an incoming " + "username, see " + "man page of iscsi." + "conf", + line_number, pathname); + } + } else { + /* default to use while processing the + * rest of the config file + */ + strncpy(config->defaults.auth_options. + username_in, u, + sizeof (config->defaults. + auth_options. + username_in)); + config->defaults.auth_options. + username_in[sizeof + (config->defaults. + auth_options. + username_in) - 1] = + '\0'; + log_debug(5, + "config global incoming " + "username %s", + config->defaults.auth_options. + username_in); + } + } else if ((strncasecmp(line, "Password=", 9) == 0) || + (strncasecmp(line, "PasswordOut=", 12) == 0) + || + (strncasecmp(line, "OutgoingPassword=", 17) + == 0)) { + /* Password string can be quoted */ + char *p, *cp = &line[8]; + + /* find start of value */ + while (*cp && *cp != '=') { + cp++; + } + p = parse_quoted_string(++cp); + + if (current_entry) { + struct iscsi_auth_config *auth_options = + entry_auth_options(current_entry); + if (auth_options) { + strncpy(auth_options->password, + p, + sizeof (auth_options-> + password)); + auth_options-> + password[sizeof + (auth_options-> + password) - 1] = + '\0'; + auth_options->password_length = + strlen(auth_options-> + password); + log_debug(5, + "config entry %p " + "outgoing password %s " + "length %u", + current_entry, + auth_options->password, + auth_options-> + password_length); + } else { + log_error( + "Invalid entry on line " + "%d of %s," + " current entry cannot " + "have an outgoing " + "password, see " + "man page of iscsi." + "conf", + line_number, pathname); + } + } else { + /* default to use while processing the + * rest of the config file + */ + strncpy(config->defaults.auth_options. + password, p, + sizeof (config->defaults. + auth_options.password)); + config->defaults.auth_options. + password[sizeof + (config->defaults. + auth_options.password) - + 1] = '\0'; + config->defaults.auth_options. + password_length = + strlen(config->defaults. + auth_options.password); + log_debug(5, + "config global outgoing " + "password %s length %u", + config->defaults.auth_options. + password, + config->defaults.auth_options. + password_length); + } + } else if ((strncasecmp(line, "PasswordIn=", 11) == 0) + || + (strncasecmp(line, "IncomingPassword=", 17) + == 0)) { + char *p, *cp = line + 10; + + /* find start of value */ + while (*cp && *cp != '=') { + cp++; + } + p = parse_quoted_string(++cp); + + if (current_entry) { + struct iscsi_auth_config *auth_options = + entry_auth_options(current_entry); + if (auth_options) { + strncpy(auth_options-> + password_in, p, + sizeof (auth_options-> + password_in)); + auth_options-> + password_in[sizeof + (auth_options-> + password_in) - + 1] = '\0'; + auth_options-> + password_length_in = + strlen(auth_options-> + password_in); + log_debug(5, + "config entry %p " + "incoming password %s," + " length %u", + current_entry, + auth_options-> + password_in, + auth_options-> + password_length_in); + } else { + log_error( + "Invalid entry on line " + "%d of %s," + " current entry cannot " + "have an incoming " + "password, see " + "man page of iscsi." + "conf", + line_number, pathname); + } + } else { + /* default to use while processing the + * rest of the config file + */ + strncpy(config->defaults.auth_options. + password_in, p, + sizeof (config->defaults. + auth_options. + password_in)); + config->defaults.auth_options. + password_in[sizeof + (config->defaults. + auth_options. + password_in) - 1] = + '\0'; + config->defaults.auth_options. + password_length_in = + strlen(config->defaults. + auth_options.password_in); + log_debug(1, + "config global incoming " + "password %s, length %u", + config->defaults.auth_options. + password_in, + config->defaults.auth_options. + password_length_in); + } + } else if ((strncasecmp(line, "Subnet=", 7) == 0) + || (strncasecmp(line, "Address=", 8) == 0)) { + char *address = line + 6; + char *mask; + struct in_addr addr; + + struct iscsi_subnet_config *subnet_config; + + while (*address && (*address != '=')) + address++; + + address++; + + entry = calloc(1, sizeof (*entry)); + if (entry == NULL) { + log_error( + "failed to allocate " + "config entry"); + return 0; + } + entry->line_number = line_number; + + subnet_config = + calloc(1, sizeof (*subnet_config)); + if (subnet_config == NULL) { + free(entry); + entry = NULL; + log_error( + "failed to allocate " + "Subnet config"); + return 0; + } + + entry->type = CONFIG_TYPE_SUBNET; + entry->config.subnet = subnet_config; + + subnet_config->subnet_mask = 0xFFFFFFFFU; + + /* look for a subnet mask */ + if ((mask = strrchr(address, '/'))) { + int bits; + + *mask = '\0'; /* terminate the address */ + mask++; /* and calculate the mask */ + bits = atoi(mask); + if ((bits >= 0) && (bits < 32)) + subnet_config->subnet_mask = + 0xFFFFFFFFU << (32 - bits); + } else if ((mask = strrchr(address, '&'))) { + *mask = '\0'; /* terminate the address */ + mask++; /* and calculate the mask */ + subnet_config->subnet_mask = + (uint32_t) strtoul(mask, NULL, 16); + } + + subnet_config->address = strdup(address); + + /* FIXME: IPv6 */ + if (inet_aton(address, &addr)) { + subnet_config->ip_length = 4; + memcpy(subnet_config->ip_address, + &addr.s_addr, 4); + } else { + /* discard this entry */ + log_error( + "error on line %d of %s, " + "bogus Subnet address %s\n", + line_number, pathname, address); + free_config_entry(entry); + entry = NULL; + continue; + } + + /* capture the current global defaults */ + memcpy(&subnet_config-> + connection_timeout_options, + &config->defaults. + connection_timeout_options, + sizeof (subnet_config-> + connection_timeout_options)); + memcpy(&subnet_config->error_timeout_options, + &config->defaults.error_timeout_options, + sizeof (subnet_config-> + error_timeout_options)); + memcpy(&subnet_config->tcp_options, + &config->defaults.tcp_options, + sizeof (subnet_config->tcp_options)); + + /* append it to the list of all config entries + */ + add_config_entry(config, entry); + log_debug(5, + "config entry %p subnet %p = addr %s " + "ip %u.%u.%u.%u mask 0x%x\n", + entry, subnet_config, address, + subnet_config->ip_address[0], + subnet_config->ip_address[1], + subnet_config->ip_address[2], + subnet_config->ip_address[3], + subnet_config->subnet_mask); + + /* indented settings in the config file may + * modify this entry + */ + current_entry = entry; + entry_indent = indent; + } else if (strncasecmp(line, "TCPWindowSize=", 14) == 0) { + char *num = &line[14]; + int value = parse_number(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_tcp_config + *tcp_options = + entry_tcp_options + (current_entry); + + if (tcp_options) { + tcp_options-> + window_size = value; + log_debug(5, + "config entry " + "%p " + "TCPWindowSize" + " %d", + current_entry, + value); + } else { + log_error( + "Invalid entry " + "on line " + "%d of %s, " + "TCPWindowSize " + "does not apply " + "to the current " + "entry, see " + "man page of" + "iscsi.conf", + line_number, + pathname); + } + } else { + config->defaults.tcp_options. + window_size = value; + log_debug(5, + "config global " + "TCPWindowSize %d", + value); + } + } else + log_error( + "error on line %d, " + "invalid TCPWindowSize %s", + line_number, num); + } else if (strncasecmp(line, "InitialR2T=", 11) == 0) { + char *str = &line[11]; + int value = parse_boolean(str); + + if (value >= 0) { + if (current_entry) { + struct iscsi_operational_config + *iscsi_options = + entry_iscsi_options + (current_entry); + + if (iscsi_options) { + log_debug(5, + "config entry " + "%p InitialR2T" + " %d", + current_entry, + value); + iscsi_options-> + InitialR2T = value; + } else { + log_error( + "Invalid " + "InitialR2T" + " entry on " + "line " + "%d of %s, " + "see man page " + "of iscsi." + "conf", + line_number, + pathname); + } + } else { + log_debug(5, + "config global " + "InitialR2T %d", + value); + config->defaults.iscsi_options. + InitialR2T = value; + } + } else + log_error( + "error on line %d of %s, " + "invalid InitialR2T %s", + line_number, pathname, str); + } else if (strncasecmp(line, "ImmediateData=", 14) == 0) { + char *str = &line[14]; + int value = parse_boolean(str); + + if (value >= 0) { + if (current_entry) { + struct iscsi_operational_config + *iscsi_options = + entry_iscsi_options + (current_entry); + + if (iscsi_options) { + log_debug(5, + "config entry " + "%p " + "ImmediateData" + " %d", + current_entry, + value); + iscsi_options-> + ImmediateData = + value; + } else { + log_error( + "Invalid " + "ImmediateData" + " entry on " + "line " + "%d of %s, " + "see man page" + " of iscsi." + "conf", + line_number, + pathname); + } + } else { + log_debug(5, + "config global " + "ImmediateData %d", + value); + config->defaults.iscsi_options. + ImmediateData = value; + } + } else + log_error( + "error on line %d of %s, " + "invalid ImmediateData %s", + line_number, pathname, str); + } else + if (strncasecmp + (line, "MaxRecvDataSegmentLength=", 25) == 0) { + char *num = &line[25]; + int value = parse_number(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_operational_config + *iscsi_options = + entry_iscsi_options + (current_entry); + + if (iscsi_options) { + log_debug(5, + "config entry " + "%p " + "MaxRecvDataSegmentLength " + "%d", + current_entry, + value); + iscsi_options-> + MaxRecvDataSegmentLength + = value; + } else { + log_error( + "Invalid " + "MaxRecvDataSegmentLength" + " entry on line" + " %d of %s, " + "see man page" + " of iscsi." + "conf", + line_number, + pathname); + } + } else { + log_debug(5, + "config global " + "MaxRecvDataSegmentLength " + "%d", + value); + config->defaults.iscsi_options. + MaxRecvDataSegmentLength = + value; + } + } else + log_error( + "error on line %d of %s, invalid" + " MaxRecvDataSegmentLength %s", + line_number, pathname, num); + } else if (strncasecmp(line, "FirstBurstLength=", 17) == + 0) { + char *num = &line[17]; + int value = parse_number(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_operational_config + *iscsi_options = + entry_iscsi_options + (current_entry); + + if (iscsi_options) { + log_debug(5, + "config entry " + "%p " + "FirstBurstLength" + " %d", + current_entry, + value); + iscsi_options-> + FirstBurstLength = + value; + } else { + log_error( + "Invalid " + "FirstBurstLength" + " entry on line" + " %d of %s, " + "see man page " + "of iscsi." + "conf", + line_number, + pathname); + } + } else { + log_debug(5, + "config global " + "FirstBurstLength %d", + value); + config->defaults.iscsi_options. + FirstBurstLength = value; + } + } else + log_error( + "error on line %d of %s, " + "invalid FirstBurstLength %s", + line_number, pathname, num); + } else if (strncasecmp(line, "MaxBurstLength=", 15) == + 0) { + char *num = &line[15]; + int value = parse_number(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_operational_config + *iscsi_options = + entry_iscsi_options + (current_entry); + + if (iscsi_options) { + log_debug(5, + "config entry " + "%p " + "MaxBurstLength" + " %d", + current_entry, + value); + iscsi_options-> + MaxBurstLength = + value; + } else { + log_error( + "Invalid " + "MaxBurstLength" + " entry on line" + " %d of %s, " + "see man page " + "of iscsi." + "conf", + line_number, + pathname); + } + } else { + log_debug(5, + "config global " + "MaxBurstLength %d", + value); + config->defaults.iscsi_options. + MaxBurstLength = value; + } + } else + log_error( + "error on line %d of %s, invalid" + " MaxBurstLength %s", + line_number, pathname, num); + } else if (strncasecmp(line, "HeaderDigest=", 13) == 0) { + char *m = line + 13; + int digest = -1; + + if ((strcasecmp(m, "never") == 0) + || (strcasecmp(m, "no") == 0) + || (strcasecmp(m, "none") == 0)) + digest = CONFIG_DIGEST_NEVER; + else if ((strcasecmp(m, "always") == 0) + || (strcasecmp(m, "yes") == 0) + || (strcasecmp(m, "crc32c") == 0)) + digest = CONFIG_DIGEST_ALWAYS; + else if ((strcasecmp(m, "prefer-on") == 0)) + digest = CONFIG_DIGEST_PREFER_ON; + else if ((strcasecmp(m, "prefer-off") == 0)) + digest = CONFIG_DIGEST_PREFER_OFF; + else { + digest = -1; + log_error( + "error on line %d of %s, invalid" + " HeaderDigest type %s\n", + line_number, pathname, m); + } + + if (digest != -1) { + if (current_entry) { + struct iscsi_operational_config + *iscsi_options = + entry_iscsi_options + (current_entry); + if (iscsi_options) { + log_debug(5, + "config entry " + "%p " + "HeaderDigest " + "%d", + current_entry, + digest); + iscsi_options-> + HeaderDigest = + digest; + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "iSCSI settings," + " invalid " + "HeaderDigest " + "%s", + line_number, + pathname, m); + } + } else { + /* default to use while + * processing the rest of the + * config file + */ + log_debug(5, + "config global " + "HeaderDigest %d", + digest); + config->defaults.iscsi_options. + HeaderDigest = digest; + } + } + } else if (strncasecmp(line, "DataDigest=", 11) == 0) { + char *m = line + 11; + int digest = -1; + + if ((strcasecmp(m, "never") == 0) + || (strcasecmp(m, "no") == 0) + || (strcasecmp(m, "none") == 0)) + digest = CONFIG_DIGEST_NEVER; + else if ((strcasecmp(m, "always") == 0) + || (strcasecmp(m, "yes") == 0) + || (strcasecmp(m, "crc32c") == 0)) + digest = CONFIG_DIGEST_ALWAYS; + else if ((strcasecmp(m, "prefer-on") == 0)) + digest = CONFIG_DIGEST_PREFER_ON; + else if ((strcasecmp(m, "prefer-off") == 0)) + digest = CONFIG_DIGEST_PREFER_OFF; + else { + digest = -1; + log_error( + "error on line %d of %s, invalid" + " DataDigest type %s\n", + line_number, pathname, m); + } + + if (digest != -1) { + if (current_entry) { + struct iscsi_operational_config + *iscsi_options = + entry_iscsi_options + (current_entry); + if (iscsi_options) { + log_debug(5, + "config entry " + "%p " + "DataDigest " + "%d", + current_entry, + digest); + iscsi_options-> + DataDigest = digest; + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "iSCSI settings," + " invalid " + "DataDigest " + "value %s", + line_number, + pathname, m); + } + } else { + /* default to use while + * processing the rest of the + * config file + */ + log_debug(5, + "config global " + "DataDigest %d", + digest); + config->defaults.iscsi_options. + DataDigest = digest; + } + } + } else if (strncasecmp(line, "LoginTimeout=", 13) == 0) { + char *num = &line[13]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_connection_timeout_config + *options = + entry_connection_timeout_options + (current_entry); + + if (options) { + log_debug(5, + "config entry " + "%p " + "LoginTimeout " + "%d", + current_entry, + value); + options->login_timeout = + value; + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "connection " + "timeout " + "settings, " + "invalid " + "LoginTimeout " + "%d, see man" + "page of iscsi" + ".conf", + line_number, + pathname, value); + } + } else { + log_debug(5, + "config global LoginTimeout %d", + value); + config->defaults. + connection_timeout_options. + login_timeout = value; + } + } else + log_error( + "error on line %d of %s, " + "invalid LoginTimeout %s", + line_number, pathname, num); + } else if (strncasecmp(line, "AuthTimeout=", 12) == 0) { + char *num = &line[12]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_connection_timeout_config + *options = + entry_connection_timeout_options + (current_entry); + + if (options) { + log_debug(5, + "config entry " + "%p " + "AuthTimeout " + "%d", + current_entry, + value); + options->auth_timeout = + value; + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "connection " + "timeout " + "settings, " + "invalid " + "AuthTimeout %d" + ", see man " + "page of " + "iscsi.conf", + line_number, + pathname, value); + } + } else { + log_debug(5, + "config global " + "AuthTimeout %d", + value); + config->defaults. + connection_timeout_options. + auth_timeout = value; + } + } else + log_error( + "error on line %d of %s, " + "invalid AuthTimeout %s", + line_number, pathname, num); + } else if (strncasecmp(line, "ActiveTimeout=", 14) == 0) { + char *num = &line[14]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_connection_timeout_config + *options = + entry_connection_timeout_options + (current_entry); + + if (options) { + log_debug(5, + "config entry " + "%p " + "ActiveTimeout" + " %d", + current_entry, + value); + options-> + active_timeout = + value; + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "connection " + "timeout " + "settings, " + "invalid " + "ActiveTimeout " + "%d, see man " + "page of iscsi" + ".conf", + line_number, + pathname, value); + } + } else { + log_debug(5, + "config global " + "ActiveTimeout %d", + value); + config->defaults. + connection_timeout_options. + active_timeout = value; + } + } else + log_error( + "error on line %d of %s, " + "invalid ActiveTimeout %s", + line_number, pathname, num); + } else if (strncasecmp(line, "IdleTimeout=", 12) == 0) { + char *num = &line[12]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_connection_timeout_config + *options = + entry_connection_timeout_options + (current_entry); + + if (options) { + options->idle_timeout = + value; + log_debug(5, + "config entry " + "%p " + "IdleTimeout " + "%d", + current_entry, + value); + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "connection " + "timeout " + "settings, " + "invalid " + "IdleTimeout %d" + ", see man page" + " of iscsi" + ".conf", + line_number, + pathname, value); + } + } else { + config->defaults. + connection_timeout_options. + idle_timeout = value; + log_debug(5, + "config global " + "IdleTimeout %d", + value); + } + } else + log_error( + "error on line %d of %s, " + "invalid IdleTimeout %s", + line_number, pathname, num); + } else if (strncasecmp(line, "PingTimeout=", 12) == 0) { + char *num = &line[12]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_connection_timeout_config + *options = + entry_connection_timeout_options + (current_entry); + + if (options) { + options->ping_timeout = + value; + log_debug(5, + "config entry " + "%p " + "PingTimeout " + "%d", + current_entry, + value); + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "connection " + "timeout " + "settings, " + "invalid " + "PingTimeout %d" + ", see man " + "page of iscsi" + ".conf", + line_number, + pathname, value); + } + } else { + config->defaults. + connection_timeout_options. + ping_timeout = value; + log_debug(5, + "config global " + "PingTimeout %d", + value); + } + } else + log_error( + "error on line %d of %s, invalid" + " PingTimeout %s", + line_number, pathname, num); + } else if (strncasecmp(line, "AbortTimeout=", 13) == 0) { + char *num = &line[13]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_error_timeout_config + *options = + entry_error_timeout_options + (current_entry); + + if (options) { + options->abort_timeout = + value; + log_debug(5, + "config entry " + "%p " + "AbortTimeout " + "%d", + current_entry, + value); + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "error timeout " + "settings, " + "invalid " + "AbortTimeout " + "%d, see man " + "page of iscsi" + ".conf", + line_number, + pathname, value); + } + } else { + config->defaults. + error_timeout_options. + abort_timeout = value; + log_debug(5, + "config global " + "AbortTimeout %d", + value); + } + } else + log_error( + "error on line %d of %s, " + "invalid AbortTimeout %s", + line_number, pathname, num); + } else if (strncasecmp(line, "ResetTimeout=", 13) == 0) { + char *num = &line[13]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_error_timeout_config + *options = + entry_error_timeout_options + (current_entry); + + if (options) { + options->reset_timeout = + value; + log_debug(5, + "config entry " + "%p " + "ResetTimeout " + "%d", + current_entry, + value); + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "error timeout " + "settings, " + "invalid " + "ResetTimeout " + "%d, see man " + "page of iscsi" + ".conf", + line_number, + pathname, value); + } + } else { + config->defaults. + error_timeout_options. + abort_timeout = value; + log_debug(5, + "config global " + "ResetTimeout %d", + value); + } + } else + log_error( + "error on line %d of %s, " + "invalid ResetTimeout %s", + line_number, pathname, num); + } else if (strncasecmp(line, "ConnFailTimeout=", 16) == + 0) { + char *num = &line[16]; + int value = parse_time(num); + + if (value >= 0) { + if (current_entry) { + struct iscsi_session_timeout_config + *options = + entry_session_timeout_options + (current_entry); + + if (options) { + options-> + replacement_timeout + = value; + log_debug(5, + "config entry " + "%p " + "ConnFailTimeout" + " %d", + current_entry, + value); + } else { + log_error( + "error on line " + "%d of %s, " + "current entry " + "does not have " + "session timeout" + " settings, " + "invalid " + "ConnFailTimeout" + " %d, see man" + "page of iscsi" + ".conf", + line_number, + pathname, value); + } + } else { + config->defaults. + session_timeout_options. + replacement_timeout = value; + log_debug(5, + "config global " + "ConnFailTimeout %d", + value); + } + } else { + log_error( + "error on line %d of %s, " + "invalid ConnFailTimeout %s", + line_number, pathname, num); + } + } else if (strncasecmp(line, "Target,Lun=", 11) == 0) { + /* do nothing, the LUN activator used to use + * these, but it's been replaced + */ + } else if (*line && (*line != '#')) { + /* if it's not a comment, warn about it */ + log_warning( + "error on line %d of %s, ignoring " + "unrecognized line %s", + line_number, pathname, line); + } + } + } while (line); + + fclose(f); + + if (!slp_multicast_seen && config->defaults.slp_multicast) { + /* FIXME: if SLP multicast is possible, but we didn't find a + * config file entry for it, assume SLPMulticast=all + */ + } + + log_debug(1, "updated config %p from %s", config, pathname); + + return 1; +} + +/* update the target config based on the config file */ +int +update_target_config(struct iscsi_target_config *target, + struct iscsi_config *config, + struct iscsi_auth_config *auth_options) +{ + int ret = 1; + + struct iscsi_config_entry *entry; + + log_debug(5, + "setting defaults for target config %p to %s from config %p", + target, target->TargetName, config); + + /* start with the global defaults */ + target->enabled = config->defaults.enabled; + + memcpy(&target->auth_options, &config->defaults.auth_options, + sizeof (target->auth_options)); + + /* the global authentication defaults can be overriden when a config is + * created, typically based on the authentication settings from a + * discovery process. + */ + if (auth_options) { + /* these completely override the current settings, even if that + * means removing an existing username and password. This is + * needed to deal with some broken targets that can't do + * security phase properly. + * It must be possible to disable all authentication. + */ + memcpy(&target->auth_options, auth_options, + sizeof (target->auth_options)); + log_debug(5, + "overriding target config %p auth options %p in favor " + "of auth options %p\n", + target, &target->auth_options, auth_options); + } + + /* apply the config file entries, which may override the defaults for + * particular targets or subnets, or for particular sections of + * the config file by resetting the globals between groups of + * TargetName or Subnet entries. + */ + for (entry = config->head; entry; entry = entry->next) { + /* if the config entry is applicable to this target, apply it */ + switch (entry->type) { + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config + *targetname_config = + entry->config.targetname; + + if (strcmp + (target->TargetName, + targetname_config->TargetName) == 0) { + log_debug(5, + "applying config entry %p line" + " %d targetname config %p to " + "target config %p", + entry, entry->line_number, + targetname_config, target); + + /* apply any target-wide settings */ + target->enabled = + targetname_config->enabled; +#ifdef PER_TARGETNAME_AUTH + /* FIXME: should we remove this, since + * it's more likely to cause problems + * than be a useful feature? When would + * a user need different credentials + * for the discovery and target logins? + */ + log_debug(5, + "applying username %s password" + " %p to target config %p", + targetname_config-> + auth_options.username, + targetname_config-> + auth_options.password, target); + memcpy(&target->auth_options, + &targetname_config->auth_options, + sizeof (target->auth_options)); +#else + log_debug(5, + "target config %p currently " + "has username %s password %s", + target, + target->auth_options.username, + target->auth_options.password); +#endif + } else { + log_debug(5, + "config entry %p line %d " + "targetname config %p does not" + " apply to target config %p", + entry, entry->line_number, + targetname_config, target); + } + break; + } + case CONFIG_TYPE_SUBNET:{ + /* not relevant for a target config */ + break; + } + default: + break; + } + } + + /* sanity check the target config */ + if (target->auth_options.username_in[0] + || target->auth_options.password_length_in) { + /* if we're expecting incoming credentials, we must have + * outgoing credentials + */ + if (target->auth_options.username[0] == '\0') { + log_error( + "target %s requires an outgoing username when " + "incoming credentials are expected\n", + target->TargetName); + ret = 0; + } + if (target->auth_options.password_length == 0) { + log_error( + "target %s requires an outgoing password when " + "incoming credentiuals are expected\n", + target->TargetName); + ret = 0; + } + } + + return ret; +} + +/* update the session configs and portal configs, based on the config file */ +int +update_session_configs(struct iscsi_target_config *target, + struct iscsi_config *config) +{ + struct iscsi_config_entry *entry; + struct iscsi_session_config *session_config; + + /* start with the global defaults for each portal config + * of each new session + */ + for (session_config = target->sessions; session_config; + session_config = session_config->next) { + struct iscsi_portal_config *portal_config; + + portal_config = session_config->portal; + + memcpy(&portal_config->connection_timeout_options, + &config->defaults.connection_timeout_options, + sizeof (portal_config->connection_timeout_options)); + memcpy(&portal_config->session_timeout_options, + &config->defaults.session_timeout_options, + sizeof (portal_config->session_timeout_options)); + memcpy(&portal_config->error_timeout_options, + &config->defaults.error_timeout_options, + sizeof (portal_config->error_timeout_options)); + memcpy(&portal_config->tcp_options, + &config->defaults.tcp_options, + sizeof (portal_config->tcp_options)); + memcpy(&portal_config->iscsi_options, + &config->defaults.iscsi_options, + sizeof (portal_config->iscsi_options)); + } + + /* apply the config file, which may override the defaults for + * particular targets or subnets, or for particular sections of + * the config file by resetting the globals between groups of + * TargetName or Subnet entries. + */ + for (entry = config->head; entry; entry = entry->next) { + /* if the config entry is applicable to this session, apply it + */ + switch (entry->type) { + case CONFIG_TYPE_TARGETNAME:{ + struct iscsi_targetname_config + *targetname_config = + entry->config.targetname; + + if (strcmp + (target->TargetName, + targetname_config->TargetName) == 0) { + log_debug(5, + "applying config entry %p line" + " %d targetname config %p to " + "target config %p sessions %p", + entry, entry->line_number, + targetname_config, target, + target->sessions); + + for (session_config = target->sessions; + session_config; + session_config = + session_config->next) { + struct iscsi_portal_config + *portal_config; + + /* apply these options to every + * portal for this session + */ + portal_config = + session_config->portal; + + log_debug(5, "applying " + "config entry %p " + "targetname config %p " + "to session #%d " + "config %p portal " + "config %p", entry, + targetname_config, + session_config-> + path_number, + session_config, + portal_config); + + memcpy(&portal_config-> + tcp_options, + &targetname_config-> + tcp_options, + sizeof(portal_config-> + tcp_options)); + memcpy(&portal_config-> + connection_timeout_options, + &targetname_config-> + connection_timeout_options, + sizeof(portal_config-> + connection_timeout_options)); + memcpy(&portal_config-> + session_timeout_options, + &targetname_config-> + session_timeout_options, + sizeof(portal_config-> + session_timeout_options)); + memcpy(&portal_config-> + error_timeout_options, + &targetname_config-> + error_timeout_options, + sizeof(portal_config-> + error_timeout_options)); + memcpy(&portal_config-> + iscsi_options, + &targetname_config-> + iscsi_options, + sizeof(portal_config-> + iscsi_options)); + + } + } else { + log_debug(5, + "config entry %p line %d " + "targetname config %p does not" + " apply to target config %p", + entry, entry->line_number, + targetname_config, target); + } + break; + } + case CONFIG_TYPE_SUBNET:{ + struct iscsi_subnet_config *subnet_config = + entry->config.subnet; + + for (session_config = target->sessions; + session_config; + session_config = session_config->next) { + struct iscsi_portal_config + *portal_config; + + /* apply these options to every portal + * for this session + */ + portal_config = + session_config->portal; + /* FIXME: IPv6 */ + if (portal_config->descriptor && + portal_config->descriptor->ip_length == + 4) { + uint32_t a1, a2; + + a1 = portal_config->descriptor-> + ip[0] << 24; + a1 |= portal_config-> + descriptor->ip[1] << 16; + a1 |= portal_config-> + descriptor->ip[2] << 8; + a1 |= portal_config-> + descriptor->ip[3]; + a1 &= subnet_config-> + subnet_mask; + + a2 = subnet_config-> + ip_address[0] << 24; + a2 |= subnet_config-> + ip_address[1] << 16; + a2 |= subnet_config-> + ip_address[2] << 8; + a2 |= subnet_config-> + ip_address[3]; + a2 &= subnet_config-> + subnet_mask; + + if (a1 == a2) { + log_debug(5, + "applying config entry %p line %d subnet config %p to session config %p portal config %p", + entry, + entry-> + line_number, + subnet_config, + session_config, + portal_config); + + memcpy + (&portal_config-> + connection_timeout_options, + &subnet_config-> + connection_timeout_options, + sizeof + (portal_config-> + connection_timeout_options)); + memcpy + (&portal_config-> + error_timeout_options, + &subnet_config-> + error_timeout_options, + sizeof + (portal_config-> + error_timeout_options)); + memcpy + (&portal_config-> + tcp_options, + &subnet_config-> + tcp_options, + sizeof + (portal_config-> + tcp_options)); + } else { + log_debug(5, + "config entry %p line %d subnet config %p does not apply to target config %p", + entry, + entry-> + line_number, + subnet_config, + target); + } + } + } + break; + } + default: + break; + } + } + + return 1; +} + +/* create one or more session configs based on the target config. */ +void +create_session_configs(struct iscsi_target_config *target, + struct iscsi_portal_descriptor *descriptors, + struct iscsi_config *config) +{ + struct iscsi_session_config *session = NULL; + struct iscsi_portal_descriptor *descriptor = NULL; + struct iscsi_portal_config *portal = NULL; + struct iscsi_session_config *prior = NULL; + int path_number = 1; + + /* disabled targets have no sessions */ + if (!target->enabled) { + log_debug(1, "target %p is disabled, creating no sessions to %s", + target, target->TargetName); + return; + } + + log_debug(1, "creating session configs for target config %p to %s", + target, target->TargetName); + + + /* one session for each portal */ + for (descriptor = descriptors; descriptor; + descriptor = descriptor->next) { + session = calloc(1, sizeof (*session)); + if (session == NULL) { + log_error("couldn't allocate session " + "config for target %s", + target->TargetName); + break; + } + + portal = calloc(1, sizeof (*portal)); + if (portal == NULL) { + log_error("couldn't allocate portal " + "config for target %s", + target->TargetName); + break; + } + + /* initialize session and portal */ + session->next = NULL; + session->isid[0] = DRIVER_ISID_0; + session->isid[1] = DRIVER_ISID_1; + session->isid[2] = DRIVER_ISID_2; + session->isid[3] = (path_number >> 16) & 0xFF; + session->isid[4] = (path_number >> 8) & 0xFF; + session->isid[5] = (path_number) & 0xFF; + session->path_number = path_number++; + session->target = target; + + portal->descriptor = descriptor; + /* only one portal */ + portal->next = NULL; + session->portal = portal; + + log_debug(1, "target config %p session #%d config %p portal %p " + "using descriptor %p", target, session->path_number, session, portal, descriptor); + + /* add it to the list of new session configs */ + if (prior) { + log_debug(7, "session %p portal %p follows session %p " + "for target %p", session, portal, prior, + target); + prior->next = session; + } else { + log_debug(7, "session %p portal %p is first session " + "for target %p", session, portal, target); + target->sessions = session; + } + prior = session; + /*FIXME what do I do with prior -krmurthy */ + } + + /* fill in the session and portal configs based on the config file info + */ + update_session_configs(target, config); + +} + +struct iscsi_target_config * +create_target_config(char *name, struct iscsi_portal_descriptor *descriptors, + struct iscsi_config *config, + struct iscsi_auth_config *auth_options) +{ + struct iscsi_target_config *target = calloc(1, sizeof (*target)); + + if (target) { + target->TargetName = name; + target->sessions = NULL; + + log_debug(6, "allocated target config %p", target); + + if (!update_target_config(target, config, auth_options)) { + free(target); + target = NULL; + } + + create_session_configs(target, descriptors, config); + } + + return target; +} diff --git a/usr/config.h b/usr/config.h new file mode 100644 index 0000000..ba04ae3 --- /dev/null +++ b/usr/config.h @@ -0,0 +1,349 @@ +/* + * iSCSI Configuration + * + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include "types.h" +#include "auth.h" /* for the username and password sizes */ + +/* default iSCSI port number */ +#define ISCSI_DEFAULT_PORT 3260 + +/* ISIDs now have a typed naming authority in them. We use an OUI */ +#define DRIVER_ISID_0 0x00 +#define DRIVER_ISID_1 0x02 +#define DRIVER_ISID_2 0x3D + +/* default window size */ +#define TCP_WINDOW_SIZE (256 * 1024) + +/* the following structures store the options set in the config file. + * a structure is defined for each logically-related group of options. + * if you are adding a new option, first check if it should belong + * to one of the existing groups. If it does, add it. If not, define + * a new structure. + */ + +/* all authentication-related options should be added to this structure. + * this structure is per-session, and can be configured + * by TargetName but not Subnet. + */ +struct iscsi_auth_config { + unsigned int authmethod; + char username[AUTH_STR_MAX_LEN]; + unsigned char password[AUTH_STR_MAX_LEN]; + unsigned int password_length; + char username_in[AUTH_STR_MAX_LEN]; + unsigned char password_in[AUTH_STR_MAX_LEN]; + unsigned int password_length_in; +}; + +/* all per-connection timeouts go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_connection_timeout_config { + int login_timeout; + int auth_timeout; + int active_timeout; + int idle_timeout; + int ping_timeout; +}; + +/* all per-connection timeouts go in this structure. + * this structure is per-session, and can be configured + * by TargetName but not by Subnet. + */ +struct iscsi_session_timeout_config { + int replacement_timeout; +}; + +/* all error handling timeouts go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_error_timeout_config { + int abort_timeout; + int reset_timeout; +}; + +/* all TCP options go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_tcp_config { + int window_size; + int type_of_service; /* try to set IP TOS bits */ +}; + +/* all iSCSI operational params go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_operational_config { + int protocol; + int InitialR2T; + int ImmediateData; + int MaxRecvDataSegmentLength; + int FirstBurstLength; + int MaxBurstLength; + int DefaultTime2Wait; + int DefaultTime2Retain; + int HeaderDigest; + int DataDigest; +}; + +#define CONFIG_DIGEST_NEVER 0 +#define CONFIG_DIGEST_ALWAYS 1 +#define CONFIG_DIGEST_PREFER_ON 2 +#define CONFIG_DIGEST_PREFER_OFF 3 + +/* the following structures represent the contents of the config file */ + +struct iscsi_targetname_config { + char *TargetName; + int enabled; + struct iscsi_auth_config auth_options; + struct iscsi_tcp_config tcp_options; + struct iscsi_connection_timeout_config connection_timeout_options; + struct iscsi_session_timeout_config session_timeout_options; + struct iscsi_error_timeout_config error_timeout_options; + struct iscsi_operational_config iscsi_options; +}; + +struct iscsi_subnet_config { + char *address; + int ip_length; + char ip_address[16]; + int port; + uint32_t subnet_mask; /* IPv4 subnet mask */ + struct iscsi_connection_timeout_config connection_timeout_options; + struct iscsi_error_timeout_config error_timeout_options; + struct iscsi_tcp_config tcp_options; +}; + +struct iscsi_sendtargets_config { + char *address; + int port; + int continuous; + int send_async_text; + struct iscsi_auth_config auth_options; + struct iscsi_connection_timeout_config connection_timeout_options; +}; + +struct iscsi_slp_config { + char *address; /* for unicast */ + int port; /* for unicast */ + char *scopes; + char *interfaces; /* for multicast, list of interfaces names, + * "all", or "none" + */ + int poll_interval; + struct iscsi_auth_config auth_options; +}; + +struct iscsi_discovery_file_config { + char *filename; + int read_size; + char *address; + char *port; + int continuous; + struct iscsi_auth_config auth_options; +}; + +/* config defaults */ +struct iscsi_config_defaults { + /* discovery defaults */ + int continuous_sendtargets; /* if non-zero, default to keeping + * iSCSI discovery sessions open + */ + int send_async_text; /* if non-zero, target will send + * vendor specific async events. + */ + int slp_multicast; /* if non-zero, default to attempting SLP + * multicast discovery of all targets this + * initiator can access + */ + char *slp_scopes; + int slp_poll_interval; + + /* global default options, used if no options are in the config, + * or if the user sets global defaults in the config file. + */ + int enabled; + struct iscsi_auth_config auth_options; + struct iscsi_connection_timeout_config connection_timeout_options; + struct iscsi_error_timeout_config error_timeout_options; + struct iscsi_session_timeout_config session_timeout_options; + struct iscsi_tcp_config tcp_options; + struct iscsi_operational_config iscsi_options; +}; + +struct iscsi_config_entry { + struct iscsi_config_entry *prev; + struct iscsi_config_entry *next; + int type; + int line_number; + union { + struct iscsi_sendtargets_config *sendtargets; + struct iscsi_slp_config *slp; + struct iscsi_discovery_file_config *file; + struct iscsi_targetname_config *targetname; + struct iscsi_subnet_config *subnet; + } config; +}; + +#define CONFIG_TYPE_UNKNOWN 0 +#define CONFIG_TYPE_SENDTARGETS 1 +#define CONFIG_TYPE_SLP 2 +#define CONFIG_TYPE_DISCOVERY_FILE 3 +#define CONFIG_TYPE_TARGETNAME 4 +#define CONFIG_TYPE_SUBNET 5 +#define CONFIG_TYPE_ADDRESS 6 + +/* collect all iscsi config file info together */ +struct iscsi_config { + struct iscsi_config_defaults defaults; + struct iscsi_config_entry *head; + struct iscsi_config_entry *tail; +}; + +/* the following structures represent the run-time configuration, + * computed based on the discovery info and the structures representing + * the config file contents. + */ + +/* discovery produces portal descriptors. + * this arguably belongs in a different header file, + * but is here since everything references them + * via portal_configs + */ +struct iscsi_portal_descriptor { + struct iscsi_portal_descriptor *next; + char *address; /* text string */ + char ip[16]; /* binary IP */ + int ip_length; + int port; + int tag; +}; + +#define PORTAL_GROUP_TAG_UNKNOWN -1 + +/* structures dynamically created as the main daemon collects discovery info + * and processes config info + */ + +struct iscsi_portal_config { + struct iscsi_portal_config *next; + struct iscsi_portal_descriptor *descriptor; /* the target_config's + * portal descriptor + */ + /* and the config options to use when the connection uses this portal */ + struct iscsi_connection_timeout_config connection_timeout_options; + struct iscsi_session_timeout_config session_timeout_options; + struct iscsi_error_timeout_config error_timeout_options; + struct iscsi_tcp_config tcp_options; + struct iscsi_operational_config iscsi_options; +}; + +struct iscsi_portal_config_list { + struct iscsi_portal_config *head; + struct iscsi_portal_config *tail; +}; + +/* config code doesn't need to care about what's in the session process struct + */ +struct iscsi_session_process; + +struct iscsi_target_config; + +/* a normal iSCSI session */ +struct iscsi_session_config { + struct iscsi_session_config *next; + struct iscsi_session_process *process; + struct iscsi_target_config *target; + struct iscsi_portal_config *portal; + int iscsi_bus; + int target_id; + unsigned char isid[6]; + int path_number; +}; + +struct iscsi_discovery_process; + +struct iscsi_target_config { + char *TargetName; /* typically shared with some other structure, + * which manages the lifetime + */ + + /* options that only make sense for the target as a whole */ + int enabled; + struct iscsi_auth_config auth_options; + + struct iscsi_session_config *sessions; + +}; + +/* exported functions */ +extern char *get_iscsi_initiatorname(char *pathname); + +extern int update_iscsi_config(const char *pathname, + struct iscsi_config *config); + +extern int add_config_entry(struct iscsi_config *config, + struct iscsi_config_entry *entry); +extern int remove_config_entry(struct iscsi_config *config, + struct iscsi_config_entry *entry); +extern void free_config_entry(struct iscsi_config_entry *entry); + +extern struct iscsi_target_config *create_target_config(char *name, + struct + iscsi_portal_descriptor + *portals, + struct iscsi_config + *config, + struct iscsi_auth_config + *auth_options); + +extern void free_target_config(struct iscsi_target_config *config); + +extern void create_session_configs(struct iscsi_target_config *target, + struct iscsi_portal_descriptor *portals, + struct iscsi_config *config); + +extern void free_session_config(struct iscsi_session_config *config); + +extern void free_portal_descriptors(struct iscsi_portal_descriptor *portals); + +/* comparisons */ +extern int same_portal_descriptor(struct iscsi_portal_descriptor *p1, + struct iscsi_portal_descriptor *p2); +extern int same_portal_descriptors(struct iscsi_portal_descriptor *portals1, + struct iscsi_portal_descriptor *portals2); +extern int same_portal_config(struct iscsi_portal_config *p1, + struct iscsi_portal_config *p2); +extern int same_portal_configs(struct iscsi_portal_config *portals1, + struct iscsi_portal_config *portals2); +extern int same_session_config(struct iscsi_session_config *s1, + struct iscsi_session_config *s2); +extern int same_target_config(struct iscsi_target_config *t1, + struct iscsi_target_config *t2); + +#endif /* CONFIG_H */ diff --git a/usr/discovery.c b/usr/discovery.c new file mode 100644 index 0000000..875357d --- /dev/null +++ b/usr/discovery.c @@ -0,0 +1,1785 @@ +/* + * iSCSI Discovery + * + * Copyright (C) 2002 Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + */ + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <netdb.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/poll.h> +#include <sys/time.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "strings.h" +#include "iscsi_proto.h" +#include "initiator.h" +#include "iscsiadm.h" +#include "config.h" +#include "log.h" + +#ifdef SLP_ENABLE +#include "iscsi-slp-discovery.h" +#endif + +#define DISCOVERY_NEED_RECONNECT 0xdead0001 + +static int rediscover = 0; +static int record_begin; + +static void +sighup_handler(int unused) +{ + rediscover = 1; +} + +static int +send_nop_reply(iscsi_session_t *session, iscsi_nopin_t *nop, + char *data, int timeout) +{ + iscsi_nopout_t out; + + memset(&out, 0, sizeof (out)); + out.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE; + out.flags = ISCSI_FLAG_CMD_FINAL; + memcpy(out.lun, nop->lun, sizeof (out.lun)); + out.itt = nop->itt; + out.ttt = nop->ttt; + memcpy(out.dlength, nop->dlength, sizeof (out.dlength)); + out.cmdsn = htonl(session->cmdsn); /* don't increment after + * immediate cmds + */ + out.exp_statsn = htonl(session->exp_statsn); + + log_debug(4, "sending nop reply for ttt %u, cmdsn %u, dlength %d", + ntohl(out.ttt), ntohl(out.cmdsn), ntoh24(out.dlength)); + + return iscsi_send_pdu(session, (iscsi_hdr_t *)&out, + ISCSI_DIGEST_NONE, data, ISCSI_DIGEST_NONE, timeout); +} + +static int +iscsi_make_text_pdu(iscsi_session_t *session, iscsi_hdr_t *hdr, + char *data, int max_data_length) +{ + iscsi_text_t *text_pdu = (iscsi_text_t *)hdr; + + /* initialize the PDU header */ + memset(text_pdu, 0, sizeof (*text_pdu)); + + text_pdu->opcode = ISCSI_OP_TEXT; + text_pdu->itt = htonl(session->itt); + text_pdu->ttt = ISCSI_RESERVED_TAG; + text_pdu->cmdsn = htonl(session->cmdsn++); + text_pdu->exp_statsn = htonl(session->exp_statsn); + + return 1; +} + +static int +request_targets(iscsi_session_t *session) +{ + char data[64]; + iscsi_text_t text; + iscsi_hdr_t *hdr = (iscsi_hdr_t *) &text; + + memset(&text, 0, sizeof (text)); + memset(data, 0, sizeof (data)); + + /* make a text PDU with SendTargets=All */ + if (!iscsi_make_text_pdu(session, hdr, data, sizeof (data))) { + log_error("failed to make a SendTargets PDU"); + return 0; + } + + if (!iscsi_add_text + (session, hdr, data, sizeof (data), "SendTargets", "All")) { + log_error("failed to add SendTargets text key"); + exit(1); + } + + text.ttt = ISCSI_RESERVED_TAG; + text.flags = ISCSI_FLAG_CMD_FINAL; + + if (++session->itt == ISCSI_RESERVED_TAG) + session->itt = 1; + + if (!iscsi_send_pdu(session, hdr, ISCSI_DIGEST_NONE, data, + ISCSI_DIGEST_NONE, session->active_timeout)) { + log_error("failed to send SendTargets PDU"); + return 0; + } + + return 1; +} + +static int +iterate_targets(iscsi_session_t *session, uint32_t ttt) +{ + char data[64]; + iscsi_text_t text; + struct iscsi_hdr *pdu = (struct iscsi_hdr *) &text; + + memset(&text, 0, sizeof (text)); + memset(data, 0, sizeof (data)); + + /* make an empty text PDU */ + if (!iscsi_make_text_pdu(session, pdu, data, sizeof (data))) { + log_error("failed to make an empty text PDU"); + return 0; + } + + text.ttt = ttt; + text.flags = ISCSI_FLAG_CMD_FINAL; + + if (++session->itt == ISCSI_RESERVED_TAG) + session->itt = 1; + + if (!iscsi_send_pdu(session, pdu, ISCSI_DIGEST_NONE, data, + ISCSI_DIGEST_NONE, session->active_timeout)) { + log_error("failed to send empty text PDU"); + return 0; + } + + return 1; +} + +int +add_portal(struct string_buffer *info, char *address, char *port, char *tag) +{ + struct hostent *hostn = NULL; + + /* resolve the address, in case it was a DNS name */ + hostn = gethostbyname(address); + if (!hostn) { + log_error("cannot resolve %s", address); + return 0; + } + + /* convert the resolved name to text */ + if (hostn->h_length == 4) { + struct in_addr addr; + + memcpy(&addr, hostn->h_addr, sizeof (addr)); + + if (tag && *tag) { + if (!append_sprintf(info, "TT=%s\n", tag)) { + log_error("couldn't add portal tag %s", + tag); + return 0; + } + } + + if (port && *port) { + if (!append_sprintf(info, "TP=%s\n", port)) { + log_error("couldn't add port %s", port); + return 0; + } + } + + if (strcmp(inet_ntoa(addr), address)) { + /* if the resolved name doesn't match the original, + * send an RA line as well as a TA line + */ + return append_sprintf(info, "RA=%s\nTA=%s\n", + inet_ntoa(addr), address); + } else { + /* don't need the RA line */ + return append_sprintf(info, "TA=%s\n", address); + } + } else { + /* FIXME: IPv6 */ + log_error("can't handle network address %s", address); + return 0; + } +} + +int +add_target_record(struct string_buffer *info, char *name, char *end, + int lun_inventory_changed, char *default_address, + char *default_port, int fd) +{ + char *text = NULL; + char *nul = name; + size_t length; + size_t original = data_length(info); + + /* address = IPv4 + * address = [IPv6] + * address = DNSname + * address = IPv4:port + * address = [IPv6]:port + * address = DNSname:port + * address = IPv4,tag + * address = [IPv6],tag + * address = DNSname,tag + * address = IPv4:port,tag + * address = [IPv6]:port,tag + * address = DNSname:port,tag + */ + + log_debug(7, "adding target record %p, end %p", name, end); + + /* find the end of the name */ + while ((nul < end) && (*nul != '\0')) + nul++; + + length = nul - name; + if (length > TARGET_NAME_MAXLEN) { + log_error("TargetName %s too long, ignoring", name); + return 0; + } + + if (!record_begin) { + if (!append_sprintf + (info, lun_inventory_changed ? "DLC=%s\n" : "DTN=%s\n", + name)) { + log_error("couldn't report target %s", name); + truncate_buffer(info, original); + return 0; + } + record_begin = 1; + } else { + if (!append_sprintf + (info, lun_inventory_changed ? "LC=%s\n" : "TN=%s\n", + name)) { + log_error("couldn't report target %s", name); + truncate_buffer(info, original); + return 0; + } + } + + text = name + length; + + /* skip NULs after the name */ + while ((text < end) && (*text == '\0')) + text++; + + /* if no address is provided, use the default */ + if (text >= end) { + if (default_address == NULL) { + log_error( + "no default address known for target %s", name); + truncate_buffer(info, original); + return 0; + } else + if (!add_portal(info, default_address, default_port, NULL)) + { + log_error( + "failed to add default portal, ignoring " + "target %s", name); + truncate_buffer(info, original); + return 0; + } else if (!append_string(info, ";\n")) { + log_error( + "failed to terminate target record, " + "ignoring target %s", name); + truncate_buffer(info, original); + return 0; + } + + /* finished adding the default */ + return 1; + } + + /* process TargetAddresses */ + while (text < end) { + char *next = text + strlen(text) + 1; + + log_debug(7, "text %p, next %p, end %p, %s", text, next, end, + text); + + if (strncmp(text, "TargetAddress=", 14) == 0) { + char *port = NULL; + char *tag = NULL; + char *address = text + 14; + + /* FIXME: handle IPv6 */ + if (address[0] == '[') { + /* This is an IPv6 numeric address; skip it */ + text = next; + continue; + } + if ((tag = strrchr(text, ','))) { + *tag = '\0'; + tag++; + } + if ((port = strrchr(text, ':'))) { + *port = '\0'; + port++; + } + + if (!add_portal(info, address, port, tag)) { + log_error( + "failed to add default portal, " + "ignoring target %s", name); + truncate_buffer(info, original); + return 0; + } + } else { + log_error("unexpected SendTargets data: %s", + text); + } + + text = next; + } + + /* indicate the end of the target record */ + if (!append_string(info, ";\n")) { + log_error( + "failed to terminate target record, ignoring target %s", + name); + truncate_buffer(info, original); + return 0; + } + + return 1; +} + +static int +process_sendtargets_response(struct string_buffer *sendtargets, + struct string_buffer *info, int final, + int lun_inventory_changed, char *default_address, + char *default_port, int fd) +{ + char *start = buffer_data(sendtargets); + char *text = start; + char *end = text + data_length(sendtargets); + char *nul = end - 1; + char *record = NULL; + int num_targets = 0; + size_t valid_info = 0; + + if (start == end) { + /* no SendTargets data */ + goto done; + } + + /* scan backwards to find the last NUL in the data, to ensure we + * don't walk off the end. Since key=value pairs can span PDU + * boundaries, we're not guaranteed that the end of the data has a + * NUL. + */ + while ((nul > start) && *nul) + nul--; + + if (nul == start) { + /* couldn't find anything we can process now, + * it's one big partial string + */ + goto done; + } + + /* find the boundaries between target records (TargetName or final PDU) + */ + for (;;) { + /* skip NULs */ + while ((text < nul) && (*text == '\0')) + text++; + + if (text == nul) + break; + + log_debug(7, + "processing sendtargets record %p, text %p, line %s", + record, text, text); + + /* look for the start of a new target record */ + if (strncmp(text, "TargetName=", 11) == 0) { + if (record) { + /* send the last record, which we just found + * the end of. don't bother passing the + * "TargetName=" prefix. + */ + if (!add_target_record + (info, record + 11, text, + lun_inventory_changed, default_address, + default_port, fd)) { + log_error( + "failed to add target record"); + truncate_buffer(sendtargets, 0); + truncate_buffer(info, valid_info); + goto done; + } + num_targets++; + valid_info = data_length(info); + } + record = text; + } + + /* everything up til the next NUL must be part of the + * current target record + */ + while ((text < nul) && (*text != '\0')) + text++; + } + + if (record) { + if (final) { + /* if this is the last PDU of the text sequence, + * it also ends a target record + */ + log_debug(7, + "processing final sendtargets record %p, " + "line %s", + record, record); + if (add_target_record + (info, record + 11, text, lun_inventory_changed, + default_address, default_port, fd)) { + num_targets++; + record = NULL; + truncate_buffer(sendtargets, 0); + } else { + log_error("failed to add target record"); + truncate_buffer(sendtargets, 0); + truncate_buffer(info, valid_info); + goto done; + } + } else { + /* remove the parts of the sendtargets buffer we've + * processed, and move the parts we haven't to the + * beginning of the buffer. + */ + log_debug(7, + "processed %d bytes of sendtargets data, " + "%d remaining", + record - buffer_data(sendtargets), + buffer_data(sendtargets) + + data_length(sendtargets) - record); + remove_initial(sendtargets, + record - buffer_data(sendtargets)); + } + } + + done: + /* send all of the discovered targets to the parent daemon */ + if (append_string(info, final ? "!\n" : ".\n")) { + write_buffer(info, fd); + truncate_buffer(info, 0); + if (final) { + record_begin = 0; + } + log_debug(4, "sent %d targets to parent daemon", num_targets); + return 1; + } else { + log_error("couldn't send %d targets to parent", + num_targets); + truncate_buffer(info, 0); + return 0; + } + + return 1; +} + +static int +add_async_record(struct string_buffer *info, char *record, int targetoffline, + int fd) +{ + int length = strlen(record); + size_t original = data_length(info); + + log_debug(7, " adding async record for %s", record); + + if (targetoffline) { + /* We have received targetoffline event */ + if (length > TARGET_NAME_MAXLEN) { + log_error("Targetname %s too long, ignoring", + record); + return 0; + } + } + if (!append_sprintf + (info, targetoffline ? "ATF=%s\n" : "APF=%s\n", record)) { + log_error("couldn't report the record\n"); + truncate_buffer(info, original); + return 0; + } else if (!append_string(info, ";\n")) { + log_error( + "failed to terminate target record, ignoring target %s", + record); + truncate_buffer(info, original); + } + return 1; +} + +static void +clear_timer(struct timeval *timer) +{ + memset(timer, 0, sizeof (*timer)); +} + +static int +process_async_event_text(struct string_buffer *sendtargets, + struct string_buffer *info, struct timeval *timer, + int fd) +{ + char *text = buffer_data(sendtargets); + int targetoffline = 0; + int slen = (ntohs(*(short *) (text))); + text = text + 2 + slen; + + if (strncmp(text, "X-com.cisco.targetOffline=", 26) == 0) { + targetoffline = 1; + if (!add_async_record(info, text + 26, targetoffline, fd)) { + log_error("failed to add async record"); + return 0; + } + clear_timer(timer); + } else if (strncmp(text, "X-com.cisco.portalOffline=", 26) == 0) { + targetoffline = 0; + if (!add_async_record(info, text + 26, targetoffline, fd)) { + log_error("failed to add async record"); + return 0; + } + clear_timer(timer); + } else { + log_debug(1, "sendtargets for the other events\n"); + return 1; + } + + if (append_string(info, "!\n")) { + write_buffer(info, fd); + truncate_buffer(info, 0); + log_debug(4, "sent async event record to parent daemon\n"); + return 1; + } else { + log_error("couldn't send async event record \n"); + return 0; + } +} + +/* set timer to now + seconds */ +static void +set_timer(struct timeval *timer, int seconds) +{ + if (timer) { + memset(timer, 0, sizeof (*timer)); + gettimeofday(timer, NULL); + + timer->tv_sec += seconds; + } +} + +static int +timer_expired(struct timeval *timer) +{ + struct timeval now; + + /* no timer, can't have expired */ + if ((timer == NULL) || ((timer->tv_sec == 0) && (timer->tv_usec == 0))) + return 0; + + memset(&now, 0, sizeof (now)); + gettimeofday(&now, NULL); + + if (now.tv_sec > timer->tv_sec) + return 1; + if ((now.tv_sec == timer->tv_sec) && (now.tv_usec >= timer->tv_usec)) + return 1; + return 0; +} + +static int +msecs_until(struct timeval *timer) +{ + struct timeval now; + int msecs; + long partial; + + /* no timer, can't have expired, infinite time til it expires */ + if ((timer == NULL) || ((timer->tv_sec == 0) && (timer->tv_usec == 0))) + return -1; + + memset(&now, 0, sizeof (now)); + gettimeofday(&now, NULL); + + /* already expired? */ + if (now.tv_sec > timer->tv_sec) + return 0; + if ((now.tv_sec == timer->tv_sec) && (now.tv_usec >= timer->tv_usec)) + return 0; + + /* not expired yet, do the math */ + partial = timer->tv_usec - now.tv_usec; + if (partial < 0) { + partial += 1000 * 1000; + msecs = (partial + 500) / 1000; + msecs += (timer->tv_sec - now.tv_sec - 1) * 1000; + } else { + msecs = (partial + 500) / 1000; + msecs += (timer->tv_sec - now.tv_sec) * 1000; + } + + return msecs; +} + +static int +soonest_msecs(struct timeval *t1, struct timeval *t2, struct timeval *t3) +{ + int m1 = msecs_until(t1); + int m2 = msecs_until(t2); + int m3 = msecs_until(t3); + + /* infinity is -1, handle it specically */ + if ((m1 == -1) && (m2 == -1)) + return m3; + + if ((m1 == -1) && (m3 == -1)) + return m2; + + if ((m2 == -1) && (m3 == -1)) + return m1; + + if (m1 == -1) + return (m2 < m3) ? m2 : m3; + + if (m2 == -1) + return (m1 < m3) ? m1 : m3; + + if (m3 == -1) + return (m1 < m2) ? m1 : m2; + + if (m1 < m2) + return (m1 < m3) ? m1 : m3; + else + return (m2 < m3) ? m2 : m3; +} + +static iscsi_session_t * +init_new_session(struct iscsi_sendtargets_config *config, + struct iscsi_discovery_process *process) +{ + iscsi_session_t *session; + + session = calloc(1, sizeof (*session)); + if (session == NULL) { + log_error( + "discovery process to %s:%d failed to " + "allocate a session\n", + config->address, config->port); + goto done; + } + + /* initialize the session */ + session->socket_fd = -1; + session->type = ISCSI_SESSION_TYPE_DISCOVERY; + session->login_timeout = + config->connection_timeout_options.login_timeout; + session->auth_timeout = config->connection_timeout_options.auth_timeout; + session->active_timeout = + config->connection_timeout_options.active_timeout; + session->idle_timeout = config->connection_timeout_options.idle_timeout; + session->ping_timeout = config->connection_timeout_options.ping_timeout; + session->send_async_text = + config->continuous ? config->send_async_text : -1; + /* OUI and uniqifying number */ + session->isid[0] = DRIVER_ISID_0; + session->isid[1] = DRIVER_ISID_1; + session->isid[2] = DRIVER_ISID_2; + session->isid[3] = (process->order >> 16) & 0xFF; + session->isid[4] = (process->order >> 8) & 0xFF; + session->isid[5] = (process->order >> 0) & 0xFF; + session->initiator_name = dconfig->initiator_name; + session->initiator_alias = dconfig->initiator_alias; + session->header_digest = ISCSI_DIGEST_NONE; + session->data_digest = ISCSI_DIGEST_NONE; + session->max_recv_data_segment_len = + DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH; + session->max_xmit_data_segment_len = + DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH; + session->portal_group_tag = PORTAL_GROUP_TAG_UNKNOWN; + + log_debug(4, + "sendtargets discovery process %p to %s:%d using " + "isid 0x%02x%02x%02x%02x%02x%02x", + process, config->address, config->port, session->isid[0], + session->isid[1], session->isid[2], session->isid[3], + session->isid[4], session->isid[5]); + done: + return(session); +} + + +static int +setup_authentication(iscsi_session_t *session, + struct iscsi_sendtargets_config *config) +{ + int rc; + + rc = 1; + + /* if we have any incoming credentials, we insist on authenticating + * the target or not logging in at all + */ + if (config->auth_options.username_in[0] + || config->auth_options.password_length_in) { + session->bidirectional_auth = 1; + + /* sanity check the config */ + if ((config->auth_options.username[0] == '\0') + || (config->auth_options.password_length == 0)) { + log_error( + "discovery process to %s:%d has incoming " + "authentication credentials but has no outgoing " + "credentials configured\n", + config->address, config->port); + log_error( + "discovery process to %s:%d exiting, bad " + "configuration\n", + config->address, config->port); + rc = 0; + goto done; + } + } else { + /* no or 1-way authentication */ + session->bidirectional_auth = 0; + } + + /* copy in whatever credentials we have */ + strncpy(session->username, config->auth_options.username, + sizeof (session->username)); + session->username[sizeof (session->username) - 1] = '\0'; + if ((session->password_length = config->auth_options.password_length)) + memcpy(session->password, config->auth_options.password, + session->password_length); + + strncpy(session->username_in, config->auth_options.username_in, + sizeof (session->username_in)); + session->username_in[sizeof (session->username_in) - 1] = '\0'; + if ((session->password_length_in = + config->auth_options.password_length_in)) + memcpy(session->password_in, config->auth_options.password_in, + session->password_length_in); + + if (session->password_length || session->password_length_in) { + /* setup the auth buffers */ + session->auth_buffers[0].address = &session->auth_client_block; + session->auth_buffers[0].length = + sizeof (session->auth_client_block); + session->auth_buffers[1].address = + &session->auth_recv_string_block; + session->auth_buffers[1].length = + sizeof (session->auth_recv_string_block); + + session->auth_buffers[2].address = + &session->auth_send_string_block; + session->auth_buffers[2].length = + sizeof (session->auth_send_string_block); + + session->auth_buffers[3].address = + &session->auth_recv_binary_block; + session->auth_buffers[3].length = + sizeof (session->auth_recv_binary_block); + + session->auth_buffers[4].address = + &session->auth_send_binary_block; + session->auth_buffers[4].length = + sizeof (session->auth_send_binary_block); + + session->num_auth_buffers = 5; + } else { + session->num_auth_buffers = 0; + } + done: + return(rc); +} + +static int +process_recvd_pdu(struct iscsi_hdr *pdu, + struct iscsi_sendtargets_config *config, + iscsi_session_t *session, + struct string_buffer *sendtargets, + struct string_buffer *info, + int *lun_inventory_changed, + char *default_port, + struct iscsi_discovery_process *process, + int *active, + int *long_lived, + struct timeval *async_timer, + char *data) +{ + int rc=0; + + switch (pdu->opcode) { + case ISCSI_OP_TEXT_RSP:{ + iscsi_text_rsp_t *text_response = + (iscsi_text_rsp_t *) pdu; + int dlength = ntoh24(pdu->dlength); + int final = (text_response->flags & ISCSI_FLAG_CMD_FINAL)|| + (text_response-> ttt == ISCSI_RESERVED_TAG); + + log_debug(4, "discovery session to %s:%d received text" + " response, %d data bytes, ttt 0x%x, " + "final 0x%x", + config->address, + config->port, + dlength, + ntohl(text_response->ttt), + text_response->flags & ISCSI_FLAG_CMD_FINAL); + + /* mark how much more data in the sendtargets + * buffer is now valid + */ + enlarge_data (sendtargets, dlength); + + /* process as much as we + * can right now + */ + process_sendtargets_response (sendtargets, + info, final, + *lun_inventory_changed, + config->address, + default_port, + process->pipe_fd); + + if (final) { + /* SendTargets exchange is now complete + */ + *active = 0; + *lun_inventory_changed = 1; + /* from now on, after any reconnect, + * assume LUNs may have changed + */ + } else { + /* ask for more targets */ + if (!iterate_targets(session, + text_response->ttt)) { + rc = DISCOVERY_NEED_RECONNECT; + goto done; + } + } + break; + } + case ISCSI_OP_ASYNC_EVENT:{ + iscsi_async_t *async_hdr = + (iscsi_async_t *) pdu; + int dlength = ntoh24(pdu->dlength); + short senselen; + char logbuf[128]; + int i; + + /* + * If we receive an async message stating + * the target wants to close the connection, + * then don't try to reconnect anymore. + * This is reasonable, so we don't log + * anything here. + */ + if ((async_hdr->async_event == + ISCSI_ASYNC_MSG_REQUEST_LOGOUT)|| + (async_hdr->async_event == + ISCSI_ASYNC_MSG_DROPPING_CONNECTION)|| + (async_hdr->async_event == + ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS)) { + *long_lived=0; + break; + } + + /* + * Log info about the async message. + */ + log_warning( + "Received Async Msg from target, Event = %d, " + "Code = %d, Data Len = %d\n", + async_hdr->async_event, + async_hdr->async_vcode, dlength); + + /* + * If there was data, print out the first 8 bytes + */ + if (dlength > 0) { + memset(logbuf, 0, sizeof(logbuf)); + for (i=0; i<8 && i<dlength; ++i) { + sprintf(logbuf+i*5, "0x%02x ", + data[i]); + } + log_warning(" Data[0]-[%d]: %s\n", + i<dlength ? dlength-1 : i-1, + logbuf); + } + + + if (dlength > (sizeof (short))) { + senselen = (ntohs(*(short *) (data))); + + log_debug(1, " senselen = %d\n", senselen); + if (dlength > senselen + 2) { + log_debug(1, + " recvd async event : %s\n", + data + 2 + senselen); + } + } + *long_lived = 1; + + /* + * Arrange for a rediscovery to occur in the near + * future. We use a timer so that we merge + * multiple events that occur in rapid succession, + * and only rediscover once for each burst of + * Async events. + */ + set_timer(async_timer, 1); + if (*active) { + log_debug(4, + "discovery process %p received Async " + "event while active", process); + } else { + log_debug(4, + "discovery process %p received Async " + "event while idle", process); + } + process_async_event_text(sendtargets, info, + async_timer, + process->pipe_fd); + break; + } + case ISCSI_OP_NOOP_IN:{ + iscsi_nopin_t *nop = + (iscsi_nopin_t *) pdu; + + /* + * The iSCSI spec doesn't allow Nops on + * discovery sessions, but some targets + * use them anyway. If we receive one, we + * can safely assume that the target + * supports long-lived discovery sessions + * (otherwise it wouldn't be sending nops + * to verify the connection is still + * working). + */ + *long_lived = 1; + log_debug(4,"discovery session to %s:%d received" + " Nop-in with itt %u, ttt %u, dlength %u", + config->address, config->port, + ntohl(nop->itt), ntohl(nop->ttt), + ntoh24(nop->dlength)); + + if (nop->ttt != ISCSI_RESERVED_TAG) { + /* reply to the Nop-in */ + if (!send_nop_reply(session, nop, data, + session->active_timeout)) { + log_error( + "discovery session to %s:%d " + "failed to send Nop reply, " + "ttt %u, reconnecting", + config->address, + config->port, + ntohl(nop->ttt)); + rc = DISCOVERY_NEED_RECONNECT; + goto done; + } + } + break; + } + case ISCSI_OP_REJECT:{ + iscsi_reject_t *reject = + (iscsi_reject_t *) pdu; + int dlength = ntoh24(pdu->dlength); + + log_error("reject, dlength=%d, " + "data[0]=0x%x\n", + dlength, data[0]); + log_error( + "Received a reject from the target " + "with reason code = 0x%x\n", + reject->reason); + /* + * Just attempt to reconnect if we receive a reject + */ + rc = DISCOVERY_NEED_RECONNECT; + goto done; + break; + } + default:{ + log_warning( + "discovery session to %s:%d received " + "unexpected opcode 0x%x", + config->address, config->port, pdu->opcode); + rc = DISCOVERY_NEED_RECONNECT; + goto done; + } + } + done: + return(rc); +} + +/* + * Make a best effort to logout the session, then disconnect the + * socket. + */ +static void +iscsi_logout_and_disconnect(iscsi_session_t * session) +{ + iscsi_logout_t logout_req; + iscsi_logout_rsp_t logout_resp; + int rc; + + /* + * Build logout request header + */ + memset(&logout_req, 0, sizeof (logout_req)); + logout_req.opcode = ISCSI_OP_LOGOUT | ISCSI_OP_IMMEDIATE; + logout_req.flags = ISCSI_FLAG_CMD_FINAL | + (ISCSI_LOGOUT_REASON_CLOSE_SESSION & + ISCSI_FLAG_LOGOUT_REASON_MASK); + logout_req.itt = htonl(session->itt); + if (++session->itt == ISCSI_RESERVED_TAG) + session->itt = 1; + logout_req.cmdsn = htonl(session->cmdsn); + logout_req.exp_statsn = htonl(++session->exp_statsn); + + /* + * Send the logout request + */ + rc = iscsi_send_pdu(session, (struct iscsi_hdr *)&logout_req, + ISCSI_DIGEST_NONE, NULL, ISCSI_DIGEST_NONE, 3); + if (!rc) { + log_error( + "iscsid: iscsi_logout - failed to send logout PDU.\n"); + goto done; + } + + /* + * Read the logout response + */ + memset(&logout_resp, 0, sizeof(logout_resp)); + rc = iscsi_recv_pdu(session, (struct iscsi_hdr *)&logout_resp, + ISCSI_DIGEST_NONE, NULL, 0, ISCSI_DIGEST_NONE, 1); + if (!rc) { + log_error( + "iscsid: logout - failed to receive logout resp\n"); + goto done; + } + if (logout_resp.response != ISCSI_LOGOUT_SUCCESS) { + log_error("iscsid: logout failed - response = 0x%x\n", + logout_resp.response); + } + +done: + /* + * Close the socket. + */ + iscsi_disconnect(session); +} + +static void +sendtargets_discovery_process(struct iscsi_discovery_process *process) +{ + struct iscsi_sendtargets_config *config = + process->entry->config.sendtargets; + struct sigaction action; + iscsi_session_t *session; + struct hostent *hostn = NULL; + struct pollfd pfd; + struct iscsi_hdr pdu_buffer; + struct iscsi_hdr *pdu = &pdu_buffer; + char *data = NULL; + char *end_of_data; + int long_lived = (config->continuous > 0) ? 1 : 0; + int lun_inventory_changed = 0; + int active = 0; + struct timeval connection_timer, async_timer; + int timeout; + int rc; + struct string_buffer sendtargets; + struct string_buffer info; + uint8_t status_class = 0, status_detail = 0; + unsigned int login_failures = 0; + int login_delay = 0; + char ip_address[16]; + char default_port[12]; + int ip_length = 0; + int port = config->port; + + /* initial setup */ + process->pid = getpid(); + log_debug(1, + "sendtargets discovery process %p starting, address %s:%d, " + "continuous %d", + process, config->address, config->port, config->continuous); + + memset(&pdu_buffer, 0, sizeof (pdu_buffer)); + clear_timer(&connection_timer); + clear_timer(&async_timer); + + /* set SIGTERM, SIGINT to kill the process */ + memset(&action, 0, sizeof (struct sigaction)); + action.sa_sigaction = NULL; + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + sigaction(SIGTERM, &action, NULL); + sigaction(SIGINT, &action, NULL); + + /* set SIGPIPE to be ignored, so that we get EPIPE */ + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); + + /* set SIGHUP to request a rediscovery */ + action.sa_handler = sighup_handler; + sigaction(SIGHUP, &action, NULL); + + /* allocate data buffers for SendTargets data and discovery pipe info */ + init_string_buffer(&sendtargets, 32 * 1024); + init_string_buffer(&info, 8 * 1024); + + /* allocate a new session, and initialize default values */ + session = init_new_session(config, process); + if (session == NULL) { + exit(1); + } + + /* resolve the DiscoveryAddress to an IP address */ + while (!hostn) { + hostn = gethostbyname(config->address); + if (hostn) { + /* save the resolved address */ + port = config->port; + ip_length = hostn->h_length; + memcpy(&ip_address, hostn->h_addr, + MIN(sizeof (ip_address), hostn->h_length)); + /* FIXME: IPv6 */ + log_debug(4, "resolved %s to %u.%u.%u.%u\n", + config->address, ip_address[0], ip_address[1], + ip_address[2], ip_address[3]); + } else { + log_error("cannot resolve host name %s", + config->address); + sleep(1); + } + } + + sprintf(default_port, "%d", config->port); + + log_debug(4, + "discovery timeouts: login %d, auth %d, active %d, " + "idle %d, ping %d", + session->login_timeout, session->auth_timeout, + session->active_timeout, session->idle_timeout, + session->ping_timeout); + + /* setup authentication variables for the session*/ + rc = setup_authentication(session, config); + if (rc == 0) + exit(0); + + + + set_address: + /* + * copy the saved address to the session, + * undoing any temporary redirect + */ + session->port = port; + session->ip_length = ip_length; + memcpy(session->ip_address, ip_address, + MIN(sizeof (session->ip_address), ip_length)); + + reconnect: + + iscsi_disconnect(session); + + session->cmdsn = 1; + session->itt = 1; + session->portal_group_tag = PORTAL_GROUP_TAG_UNKNOWN; + + /* + * if we're violating the protocol anyway, there's no reason + * to be picky about sending keys. + */ + session->vendor_specific_keys = long_lived; + + /* slowly back off the frequency of login attempts */ + if (login_failures == 0) + login_delay = 0; + else if (login_failures < 10) + login_delay = 1; /* 10 seconds at 1 sec each */ + else if (login_failures < 20) + login_delay = 2; /* 20 seconds at 2 sec each */ + else if (login_failures < 26) + login_delay = 5; /* 30 seconds at 5 sec each */ + else if (login_failures < 34) + login_delay = 15; /* 60 seconds at 15 sec each */ + else + login_delay = 60; /* after 2 minutes, try once a minute */ + + if (login_delay) { + log_debug(4, + "discovery session to %s:%d sleeping for %d " + "seconds before next login attempt", + config->address, config->port, login_delay); + sleep(login_delay); + } + + if (!iscsi_connect(session)) { + /* FIXME: IPv6 */ + log_error("Connection to Discovery Address %u.%u.%u.%u " + "failed", session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3]); + + login_failures++; + /* If a temporary redirect sent us to something unreachable, + * we want to go back to the original IP address, so make sure + * we reset the session's IP. + */ + goto set_address; + } + log_warning("Connected to Discovery Address %u.%u.%u.%u", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3]); + + log_debug(4, "discovery session to %s:%d starting iSCSI login on fd %d", + config->address, config->port, session->socket_fd); + status_class = 0; + status_detail = 0; + switch (iscsi_login + (session, buffer_data(&sendtargets), + unused_length(&sendtargets), &status_class, &status_detail)) { + case LOGIN_OK: + break; + + case LOGIN_IO_ERROR: + case LOGIN_WRONG_PORTAL_GROUP: + case LOGIN_REDIRECTION_FAILED: + /* try again */ + /* FIXME: IPv6 */ + log_warning("retrying discovery login to %u.%u.%u.%u", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3]); + iscsi_disconnect(session); + login_failures++; + goto set_address; + + default: + case LOGIN_FAILED: + case LOGIN_NEGOTIATION_FAILED: + case LOGIN_AUTHENTICATION_FAILED: + case LOGIN_VERSION_MISMATCH: + case LOGIN_INVALID_PDU: + /* FIXME: IPv6 */ + log_error( + "discovery login to %u.%u.%u.%u failed, giving up", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3]); + iscsi_disconnect(session); + exit(0); + } + + /* check the login status */ + switch (status_class) { + case ISCSI_STATUS_CLS_SUCCESS: + /* FIXME: IPv6 */ + log_debug(4, "discovery login success to %u.%u.%u.%u", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3]); + login_failures = 0; + break; + case ISCSI_STATUS_CLS_REDIRECT: + switch (status_detail) { + /* the session IP address was changed by the login + * library, so just try again with this portal + * config but the new address. + */ + case ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP: + /* FIXME: IPv6 */ + log_warning( + "discovery login temporarily redirected to " + "%u.%u.%u.%u port %d\n", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3], + session->port); + goto reconnect; + case ISCSI_LOGIN_STATUS_TGT_MOVED_PERM: + /* FIXME: IPv6 */ + log_warning( + "discovery login permanently redirected to " + "%u.%u.%u.%u port %d\n", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3], + session->port); + /* make the new address permanent */ + ip_length = session->ip_length; + memcpy(ip_address, session->ip_address, + MIN(sizeof (ip_address), session->ip_length)); + port = session->port; + + goto reconnect; + default: + log_error( + "discovery login rejected: redirection type " + "0x%x not supported\n", + status_detail); + goto set_address; + } + break; + case ISCSI_STATUS_CLS_INITIATOR_ERR: + /* FIXME: IPv6 */ + log_error( + "discovery login to %u.%u.%u.%u rejected: " + "initiator error (%02x/%02x), non-retryable, giving up", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3], + status_class, status_detail); + iscsi_disconnect(session); + exit(0); + case ISCSI_STATUS_CLS_TARGET_ERR: + /* FIXME: IPv6 */ + log_error( + "discovery login to %u.%u.%u.%u rejected: " + "target error (%02x/%02x)", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3], + status_class, status_detail); + iscsi_disconnect(session); + login_failures++; + goto reconnect; + default: + /* FIXME: IPv6 */ + log_error( + "discovery login to %u.%u.%u.%u failed, response " + "with unknown status class 0x%x, detail 0x%x", + session->ip_address[0], session->ip_address[1], + session->ip_address[2], session->ip_address[3], + status_class, status_detail); + iscsi_disconnect(session); + login_failures++; + goto reconnect; + } + + rediscover: + /* reinitialize */ + truncate_buffer(&sendtargets, 0); + truncate_buffer(&info, 0); + + /* we're going to do a discovery regardless */ + clear_timer(&async_timer); + + /* ask for targets */ + if (!request_targets(session)) { + goto reconnect; + } + active = 1; + + /* set timeouts */ + if (long_lived) { + clear_timer(&connection_timer); + } else { + set_timer(&connection_timer, + session->active_timeout + session->ping_timeout); + } + + /* prepare to poll */ + memset(&pfd, 0, sizeof (pfd)); + pfd.fd = session->socket_fd; + pfd.events = POLLIN | POLLPRI; + + /* check timers before blocking */ + if (timer_expired(&connection_timer)) { + if (long_lived || !lun_inventory_changed) { + /* long-lived, or never finished the first + * exchange (might be long-lived) + */ + clear_timer(&connection_timer); + log_debug(1, + "discovery session to %s:%d " + "reconnecting after connection timeout", + config->address, config->port); + goto reconnect; + } else { + log_warning( + "discovery session to %s:%d session " + "logout, connection timer expired", + config->address, config->port); + iscsi_logout_and_disconnect(session); + exit(0); + } + } + + if (active) { + /* ignore the async timer, we're in the middle + * of a discovery + */ + timeout = msecs_until(&connection_timer); + } else { + /* to avoid doing LUN probing repeatedly, try to merge + * multiple Async PDUs into one rediscovery by + * deferring discovery until a timeout expires. + */ + if (timer_expired(&async_timer)) { + log_debug(4, + "discovery session to %s:%d async " + "timer expired, rediscovering", + config->address, config->port); + clear_timer(&async_timer); + goto rediscover; + } else + timeout = + soonest_msecs(NULL, + &connection_timer, + &async_timer); + } + + /* block until we receive a PDU, a TCP FIN, a TCP RST, + * or a timeout + */ + log_debug(4, + "discovery process %s:%d polling fd %d, " + "timeout in %f seconds", + config->address, config->port, pfd.fd, + timeout / 1000.0); + + pfd.revents = 0; + rc = poll(&pfd, 1, timeout); + + log_debug(7, + "discovery process to %s:%d returned from poll, rc %d", + config->address, config->port, rc); + + if (rc > 0) { + if (pfd.revents & (POLLIN | POLLPRI)) { + /* put any PDU data into the + * sendtargets buffer for now + */ + data = buffer_data(&sendtargets) + + data_length(&sendtargets); + end_of_data = + data + unused_length(&sendtargets); + timeout = msecs_until(&connection_timer); + + if (iscsi_recv_pdu + (session, pdu, ISCSI_DIGEST_NONE, data, + end_of_data - data, ISCSI_DIGEST_NONE, + timeout)) { + /* + * process iSCSI PDU received + */ + rc = process_recvd_pdu( + pdu, + config, + session, + &sendtargets, + &info, + &lun_inventory_changed, + default_port, + process, + &active, + &long_lived, + &async_timer, + data); + if (rc == DISCOVERY_NEED_RECONNECT) + goto reconnect; + + /* reset timers after receiving a PDU */ + if (long_lived) { + clear_timer(&connection_timer); + } else { + if (active) + set_timer + (&connection_timer, + session-> + active_timeout); + else + /* + * 3 minutes to try + * to go long-lived + */ + set_timer(&connection_timer, 3 * 60); + } + } else { + if (long_lived) { + log_debug(1, + "discovery session to " + "%s:%d failed to recv a " + "PDU response, " + "reconnecting", + config->address, + config->port); + goto reconnect; + } else { + log_debug(1, + "discovery session to " + "%s:%d failed to recv a " + "PDU response, " + "terminating", + config->address, + config->port); + iscsi_disconnect(session); + exit(0); + } + } + } + if (pfd.revents & POLLHUP) { + if (long_lived) { + log_warning( + "discovery session to %s:%d " + "reconnecting after hangup", + config->address, config->port); + goto reconnect; + } else { + log_warning( + "discovery session to %s:%d " + "terminating after hangup", + config->address, config->port); + iscsi_disconnect(session); + exit(0); + } + } + + if (pfd.revents & POLLNVAL) { + log_warning("discovery POLLNVAL"); + sleep(1); + goto reconnect; + } + + if (pfd.revents & POLLERR) { + log_warning("discovery POLLERR"); + sleep(1); + goto reconnect; + } + } else if (rc < 0) { + if (errno == EINTR) { + /* if we got SIGHUP, reconnect and rediscover */ + if (rediscover) { + rediscover = 0; + log_debug(1, "rediscovery requested"); + goto reconnect; + } + } else { + log_error("poll error"); + sleep(5); + exit(1); + } + } + + log_debug(1, "discovery process to %s:%d exiting", + config->address, config->port); +} + +static void +discovery_file_process(struct iscsi_discovery_process *process) +{ + struct iscsi_discovery_file_config *config = + process->entry->config.file; + struct sigaction action; + int fd; + int luns_changed = 0; + struct string_buffer sendtargets; + struct string_buffer info; + + process->pid = getpid(); + log_debug(1, + "discovery file process %d, pid %d, file %s, " + "username %s, password %s", + process->order, process->pid, config->filename, + config->auth_options.username, config->auth_options.password); + + if (!config->filename || !config->filename[0]) { + log_error("no discovery filename specified"); + exit(0); + } + + /* set SIGTERM, SIGINT, and SIGPIPE to kill the process */ + memset(&action, 0, sizeof (struct sigaction)); + action.sa_sigaction = NULL; + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + sigaction(SIGTERM, &action, NULL); + sigaction(SIGINT, &action, NULL); + sigaction(SIGPIPE, &action, NULL); + + /* set SIGHUP to loop again and report LUN INVENTORY CHANGED for testing + */ + action.sa_handler = sighup_handler; + sigaction(SIGHUP, &action, NULL); + + /* allocate data buffers for SendTargets data and pipe info */ + init_string_buffer(&sendtargets, 32 * 1024); + init_string_buffer(&info, 8 * 1024); + +repeat: + truncate_buffer(&sendtargets, 0); + truncate_buffer(&info, 0); + + if ((fd = open(config->filename, 0))) { + char *data = buffer_data(&sendtargets); + int rc; + int final = 0; + + log_debug(4, + "discovery process %p pid %d opened discovery file %s", + process, process->pid, config->filename); + + do { + char *start = + buffer_data(&sendtargets) + + data_length(&sendtargets); + char *end = start + config->read_size; + + /* don't overflow the buffer */ + if (end > (start + unused_length(&sendtargets))) + end = start + unused_length(&sendtargets); + + data = start; + + /* read a chunk of data */ + do { + rc = read(fd, data, end - data); + if (rc > 0) { + data += rc; + enlarge_data(&sendtargets, rc); + log_debug(7, + "discovery process %p pid %d " + "read %d bytes from %s", + process, process->pid, rc, + config->filename); + } else if (rc < 0) { + log_error + ("discovery process %p pid %d " + "error reading discovery file %s", + process, process->pid, + config->filename); + final = 1; + break; + } else { + log_debug(4, + "discovery process %p pid %d " + "read to the end of " + "discovery file %s", + process, process->pid, + config->filename); + end = data; + final = 1; + break; + } + } while (data < end); + + /* convert all the whitespace to NULs */ + for (data = start; data < end; data++) { + if ((*data == '\n') || (*data == ' ') + || (*data == '\t')) + *data = '\0'; + } + + /* process the data */ + process_sendtargets_response(&sendtargets, &info, final, + luns_changed, + config->address, + config->port, + process->pipe_fd); + + } while (!final); + } else { + log_error("discovery process %p pid %d couldn't open " + "discovery file %s", process, process->pid, + config->filename); + } + + while (config->continuous) { + /* wait for a signal, then repeat */ + luns_changed = 1; + log_debug(7, "discovery file process waiting for signals"); + poll(NULL, 0, -1); + if (rediscover) { + rediscover = 0; + goto repeat; + } + } + + exit(0); +} + +#ifdef SLP_ENABLE + +void +slp_discovery_process(struct iscsi_discovery_process *discovery) +{ + struct iscsi_slp_config *config = discovery->entry->config.slp; + struct sigaction action; + char *pl; + unsigned short flag = 0; + + memset(&action, 0, sizeof (struct sigaction)); + action.sa_sigaction = NULL; + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + sigaction(SIGTERM, &action, NULL); + sigaction(SIGINT, &action, NULL); + sigaction(SIGPIPE, &action, NULL); + + action.sa_handler = sighup_handler; + sigaction(SIGHUP, &action, NULL); + + if (iscsi_process_should_exit()) { + log_debug(1, "slp discovery process %p exiting\n", discovery); + exit(0); + } + + discovery->pid = getpid(); + + pl = generate_predicate_list(discovery, &flag); + + while (1) { + if (flag == SLP_MULTICAST_ENABLED) { + discovery->flag = SLP_MULTICAST_ENABLED; + slp_multicast_srv_query(discovery, pl, GENERIC_QUERY); + } + + if (flag == SLP_UNICAST_ENABLED) { + discovery->flag = SLP_UNICAST_ENABLED; + slp_unicast_srv_query(discovery, pl, GENERIC_QUERY); + } + + sleep(config->poll_interval); + } + + exit(0); +} + +#endif + +void +discovery_process(struct iscsi_discovery_process *discovery) +{ + if (discovery->entry) { + switch (discovery->entry->type) { + case CONFIG_TYPE_DISCOVERY_FILE: + discovery_file_process(discovery); + break; + case CONFIG_TYPE_SENDTARGETS: + sendtargets_discovery_process(discovery); + break; + case CONFIG_TYPE_SLP: +#ifdef SLP_ENABLE + slp_discovery_process(discovery); +#else + log_error("this build does not support SLP"); + exit(0); +#endif + break; + default: + log_error("can't fork unexpected discovery type %d", + discovery->entry->type); + break; + } + } +} diff --git a/usr/discovery.h b/usr/discovery.h new file mode 100644 index 0000000..5a7ed43 --- /dev/null +++ b/usr/discovery.h @@ -0,0 +1,17 @@ +#ifndef ISCSI_DISCOVERY_H_ +#define ISCSI_DISCOVERY_H_ + +#include "iscsid.h" /* for process types */ +#include "string-buffer.h" + +/* functions */ +extern void discovery_process(struct iscsi_discovery_process *discovery); + +/* functions useful for implementing other types of discovery processes */ +extern int add_target_record(struct string_buffer *info, char *name, char *end, + int lun_inventory_changed, char *default_address, + char *default_port, int fd); +extern int add_portal(struct string_buffer *info, char *address, char *port, + char *tag); + +#endif diff --git a/usr/initiator.h b/usr/initiator.h index 41ef343..00c0004 100644 --- a/usr/initiator.h +++ b/usr/initiator.h @@ -22,11 +22,12 @@ #include <stdint.h> +#include "types.h" #include "iscsi_proto.h" #include "auth.h" /* daemon's session structure */ -struct iscsi_session { +typedef struct iscsi_session { int socket_fd; int login_timeout; int auth_timeout; @@ -85,8 +86,8 @@ struct iscsi_session { char username_in[AUTH_STR_MAX_LEN]; uint8_t password_in[AUTH_STR_MAX_LEN]; int password_length_in; -}; -extern int iscsi_update_address(struct iscsi_session *session, char *address); +} iscsi_session_t; +extern int iscsi_update_address(iscsi_session_t *session, char *address); /* login.c */ @@ -125,13 +126,14 @@ enum iscsi_login_status { }; /* implemented in iscsi-login.c for use on all platforms */ -extern int iscsi_add_text(struct iscsi_session *session, struct iscsi_hdr *pdu, +extern int iscsi_add_text(iscsi_session_t *session, iscsi_hdr_t *hdr, char *data, int max_data_length, char *param, char *value); -extern enum iscsi_login_status iscsi_login(struct iscsi_session *session, +extern enum iscsi_login_status iscsi_login(iscsi_session_t *session, char *buffer, uint32_t bufsize, uint8_t * status_class, uint8_t * status_detail); +extern int iscsi_update_address(iscsi_session_t *session, char *address); /* Digest types */ #define ISCSI_DIGEST_NONE 0 @@ -148,4 +150,13 @@ extern enum iscsi_login_status iscsi_login(struct iscsi_session *session, #define IRRELEVANT_DATAPDUINORDER 0x40 #define IRRELEVANT_DATASEQUENCEINORDER 0x80 +/* io.c */ +extern int iscsi_connect(iscsi_session_t *session); +extern void iscsi_disconnect(iscsi_session_t *session); +extern int iscsi_send_pdu(iscsi_session_t *session, iscsi_hdr_t *hdr, + int hdr_digest, char *data, int data_digest, int timeout); +extern int iscsi_recv_pdu(iscsi_session_t *session, iscsi_hdr_t *hdr, + int hdr_digest, char *data, int max_data_length, int data_digest, + int timeout); + #endif /* INITIATOR_H */ @@ -45,7 +45,7 @@ sigalarm_handler(int unused) } int -iscsi_connect(struct iscsi_session *session) +iscsi_connect(iscsi_session_t *session) { int ret, rc, sock, onearg; struct sockaddr_in addr; @@ -161,7 +161,7 @@ iscsi_connect(struct iscsi_session *session) } void -iscsi_disconnect(struct iscsi_session *session) +iscsi_disconnect(iscsi_session_t *session) { if (session->socket_fd >= 0) { log_debug(1, "disconnecting session %p, fd %d", session, @@ -172,7 +172,7 @@ iscsi_disconnect(struct iscsi_session *session) } static void -iscsi_log_text(struct iscsi_hdr *pdu, char *data) +iscsi_log_text(iscsi_hdr_t *pdu, char *data) { int dlength = ntoh24(pdu->dlength); char *text = data; @@ -187,7 +187,7 @@ iscsi_log_text(struct iscsi_hdr *pdu, char *data) } int -iscsi_send_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, +iscsi_send_pdu(iscsi_session_t *session, iscsi_hdr_t *hdr, int hdr_digest, char *data, int data_digest, int timeout) { int rc, ret = 0; @@ -340,7 +340,7 @@ iscsi_send_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, } int -iscsi_recv_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, +iscsi_recv_pdu(iscsi_session_t *session, iscsi_hdr_t *hdr, int hdr_digest, char *data, int max_data_length, int data_digest, int timeout) { @@ -357,8 +357,8 @@ iscsi_recv_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, struct sigaction action; struct sigaction old; - /* set a timeout, since the socket calls may take a long - * time to timeout on their own + /* set a timeout, since the socket calls may take a long + * time to timeout on their own */ memset(data, 0, max_data_length); memset(&action, 0, sizeof (struct sigaction)); diff --git a/usr/iscsiadm.c b/usr/iscsiadm.c index 0c46c79..2bcf3e4 100644 --- a/usr/iscsiadm.c +++ b/usr/iscsiadm.c @@ -37,6 +37,10 @@ static char program_name[] = "iscsiadm"; +/* global config info */ +struct iscsi_daemon_config daemon_config; +struct iscsi_daemon_config *dconfig = &daemon_config; + static struct option const long_options[] = { {"version", no_argument, 0, 'v'}, diff --git a/usr/iscsiadm.h b/usr/iscsiadm.h index d6b7af5..278c866 100644 --- a/usr/iscsiadm.h +++ b/usr/iscsiadm.h @@ -24,16 +24,41 @@ #define ISCSIADM_NAME_LEN 128 typedef enum iscsiadm_cmd { - C_SESSION_ADD, - C_SESSION_REMOVE, + IPC_SESSION_ADD, + IPC_SESSION_REMOVE, + IPC_CONN_ADD, + IPC_CONN_REMOVE, } iscsiadm_cmd_e; +typedef struct msg_session_add { + char name[ISCSIADM_NAME_LEN]; + char alias[ISCSIADM_NAME_LEN]; +} msg_session_add_t; + +typedef struct msg_session_rm { + int sid; +} msg_session_rm_t; + +typedef struct msg_conn_add { + uint8_t ip_address[16]; + int port; +} msg_conn_add_t; + +typedef struct msg_conn_rm { + int sid; + int cid; +} msg_conn_rm_t; + /* IPC Request */ typedef struct iscsiadm_req { iscsiadm_cmd_e command; union { /* messages */ + msg_session_add_t s_add; + msg_session_rm_t s_rm; + msg_conn_add_t c_add; + msg_conn_rm_t c_rm; } u; } iscsiadm_req_t; @@ -46,4 +71,29 @@ int ipc_handle(int accept_fd); int ipc_listen(void); void ipc_close(int fd); +struct iscsi_discovery_process { + struct iscsi_discovery_process *volatile prev; + struct iscsi_discovery_process *volatile next; + struct iscsi_config_entry *entry; + pid_t pid; + int order; + int pipe_fd; + int in_progress; + int remove; /* kill and remove this from the + * list at the next opportunity */ + int restart; /* restart if the pid is 0 */ + unsigned short flag; /* UNICAST or MULTICAST */ +}; + +/* daemon config */ +struct iscsi_daemon_config { + char *config_file; + char *pid_file; + char *initiator_name_file; + char *initiator_name; + char *initiator_alias; +}; + +extern struct iscsi_daemon_config *dconfig; + #endif /* ISCSIADM_H */ diff --git a/usr/iscsid.c b/usr/iscsid.c index a9bb8c3..7581f37 100644 --- a/usr/iscsid.c +++ b/usr/iscsid.c @@ -77,7 +77,7 @@ CONFIG_FILE ").\n\ } void -handle_iscsi_events(void) +iscsi_events_handle(void) { iscsi_uevent_t event; int res; @@ -132,11 +132,10 @@ event_loop(void) } if (poll_array[POLL_CTRL].revents) - handle_iscsi_events(); + iscsi_events_handle(); if (poll_array[POLL_IPC].revents) ipc_handle(ipc_fd); - } } diff --git a/usr/iscsid.h b/usr/iscsid.h index 3af55b3..da795e8 100644 --- a/usr/iscsid.h +++ b/usr/iscsid.h @@ -23,8 +23,7 @@ #include <sys/types.h> #include <sys/ioctl.h> -#include "types.h" -#include "iscsi_proto.h" +#include "initiator.h" #define BHS_SIZE 48 @@ -36,15 +35,17 @@ typedef struct iscsi_pdu { unsigned int datasize; } iscsi_pdu_t; -/* ctldev.c */ -extern int ctrl_fd; - -extern int ctldev_open(void); - #define version() \ do { \ printf("%s version %s\n", program_name, ISCSI_VERSION_STR); \ exit(0); \ } while (0) +/* ctldev.c */ +extern int ctrl_fd; + +extern int ctldev_open(void); +extern void ctldev_close(int fd); + + #endif /* ISCSID_H */ diff --git a/usr/login.c b/usr/login.c index dc3665a..a39bac7 100644 --- a/usr/login.c +++ b/usr/login.c @@ -34,7 +34,7 @@ /* caller is assumed to be well-behaved and passing NUL terminated strings */ int -iscsi_add_text(struct iscsi_session *session, struct iscsi_hdr *pdu, char *data, +iscsi_add_text(iscsi_session_t *session, iscsi_hdr_t *pdu, char *data, int max_data_length, char *param, char *value) { int param_len = strlen(param); @@ -156,12 +156,12 @@ get_auth_key_type(struct iscsi_acl *auth_client, char **data, char *end) return LOGIN_NEGOTIATION_FAILED; } -/* - * try to reset the session's IP address and port, based on the TargetAddress - * provided +/* + * try to reset the session's IP address and port, based on the TargetAddress + * provided */ int -iscsi_update_address(struct iscsi_session *session, char *address) +iscsi_update_address(iscsi_session_t *session, char *address) { char *port; char *tag; @@ -196,7 +196,7 @@ iscsi_update_address(struct iscsi_session *session, char *address) } static enum iscsi_login_status -get_security_text_keys(struct iscsi_session *session, char **data, +get_security_text_keys(iscsi_session_t *session, char **data, struct iscsi_acl *auth_client, char *end) { char *text = *data; @@ -277,7 +277,7 @@ get_security_text_keys(struct iscsi_session *session, char **data, } static enum iscsi_login_status -get_op_params_text_keys(struct iscsi_session *session, char **data, char *end) +get_op_params_text_keys(iscsi_session_t *session, char **data, char *end) { char *text = *data; char *value = NULL; @@ -547,7 +547,7 @@ get_op_params_text_keys(struct iscsi_session *session, char **data, char *end) } static enum iscsi_login_status -check_security_stage_status(struct iscsi_session *session, +check_security_stage_status(iscsi_session_t *session, struct iscsi_acl *auth_client) { int debug_status = 0; @@ -588,7 +588,7 @@ check_security_stage_status(struct iscsi_session *session, * size, and then appending a NULL to the PDU. */ static enum iscsi_login_status -iscsi_process_login_response(struct iscsi_session *session, +iscsi_process_login_response(iscsi_session_t *session, iscsi_login_rsp_t *login_rsp, char *data, int max_data_length) { @@ -721,7 +721,7 @@ iscsi_process_login_response(struct iscsi_session *session, } static int -add_params_normal_session(struct iscsi_session *session, struct iscsi_hdr *pdu, +add_params_normal_session(iscsi_session_t *session, iscsi_hdr_t *pdu, char *data, int max_data_length) { char value[AUTH_STR_MAX_LEN]; @@ -764,7 +764,7 @@ add_params_normal_session(struct iscsi_session *session, struct iscsi_hdr *pdu, } static int -add_vendor_specific_text(struct iscsi_session *session, struct iscsi_hdr *pdu, +add_vendor_specific_text(iscsi_session_t *session, iscsi_hdr_t *pdu, char *data, int max_data_length) { char value[AUTH_STR_MAX_LEN]; @@ -813,7 +813,7 @@ add_vendor_specific_text(struct iscsi_session *session, struct iscsi_hdr *pdu, } static int -check_irrelevant_keys(struct iscsi_session *session, struct iscsi_hdr *pdu, +check_irrelevant_keys(iscsi_session_t *session, iscsi_hdr_t *pdu, char *data, int max_data_length) { /* If you receive irrelevant keys, just check them from the irrelevant @@ -864,7 +864,7 @@ check_irrelevant_keys(struct iscsi_session *session, struct iscsi_hdr *pdu, } static int -fill_crc_digest_text(struct iscsi_session *session, struct iscsi_hdr *pdu, +fill_crc_digest_text(iscsi_session_t *session, iscsi_hdr_t *pdu, char *data, int max_data_length) { switch (session->header_digest) { @@ -918,7 +918,7 @@ fill_crc_digest_text(struct iscsi_session *session, struct iscsi_hdr *pdu, } static int -fill_op_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu, +fill_op_params_text(iscsi_session_t *session, iscsi_hdr_t *pdu, char *data, int max_data_length, int *transit) { char value[AUTH_STR_MAX_LEN]; @@ -989,7 +989,7 @@ fill_op_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu, } static void -enum_auth_keys(struct iscsi_acl *auth_client, struct iscsi_hdr *pdu, +enum_auth_keys(struct iscsi_acl *auth_client, iscsi_hdr_t *pdu, char *data, int max_data_length, int keytype) { int present = 0, rc; @@ -1022,7 +1022,7 @@ enum_auth_keys(struct iscsi_acl *auth_client, struct iscsi_hdr *pdu, } static int -fill_security_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu, +fill_security_params_text(iscsi_session_t *session, iscsi_hdr_t *pdu, struct iscsi_acl *auth_client, char *data, int max_data_length, int *transit) { @@ -1075,7 +1075,7 @@ fill_security_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu, * **/ static int -iscsi_make_login_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, +iscsi_make_login_pdu(iscsi_session_t *session, iscsi_hdr_t *hdr, char *data, int max_data_length) { int transit = 0; @@ -1092,7 +1092,7 @@ iscsi_make_login_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, login_hdr->cid = 0; memcpy(login_hdr->isid, session->isid, sizeof(session->isid)); login_hdr->tsih = 0; - login_hdr->cmdsn = htonl(session->cmdsn); + login_hdr->cmdsn = htonl(session->cmdsn); /* don't increment on immediate */ login_hdr->min_version = ISCSI_DRAFT20_VERSION; login_hdr->max_version = ISCSI_DRAFT20_VERSION; @@ -1185,7 +1185,7 @@ iscsi_make_login_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, } static enum iscsi_login_status -check_for_authentication(struct iscsi_session *session, +check_for_authentication(iscsi_session_t *session, struct iscsi_acl *auth_client) { enum iscsi_login_status ret = LOGIN_FAILED; @@ -1199,7 +1199,7 @@ check_for_authentication(struct iscsi_session *session, return LOGIN_FAILED; } - if (session->username && + if (session->username && (acl_set_user_name(auth_client, session->username) != AUTH_STATUS_NO_ERROR)) { log_error("Couldn't set username\n"); @@ -1236,7 +1236,7 @@ check_for_authentication(struct iscsi_session *session, } static enum iscsi_login_status -check_status_login_response(struct iscsi_session *session, +check_status_login_response(iscsi_session_t *session, iscsi_login_rsp_t *login_rsp, char *data, int max_data_length, int *final) { @@ -1300,11 +1300,11 @@ check_status_login_response(struct iscsi_session *session, * that we don't have any policy logic here. **/ enum iscsi_login_status -iscsi_login(struct iscsi_session *session, char *buffer, size_t bufsize, +iscsi_login(iscsi_session_t *session, char *buffer, size_t bufsize, uint8_t * status_class, uint8_t * status_detail) { struct iscsi_acl *auth_client = NULL; - struct iscsi_hdr pdu; + iscsi_hdr_t pdu; iscsi_login_rsp_t *login_rsp = (iscsi_login_rsp_t *)&pdu; char *data; @@ -1362,8 +1362,7 @@ iscsi_login(struct iscsi_session *session, char *buffer, size_t bufsize, */ if (!iscsi_make_login_pdu(session, &pdu, data, max_data_length)) { - log_error("login failed, couldn't make " - "a login PDU\n"); + log_error("login failed, couldn't make a login PDU\n"); ret = LOGIN_FAILED; goto done; } @@ -1374,11 +1373,9 @@ iscsi_login(struct iscsi_session *session, char *buffer, size_t bufsize, /* * FIXME: caller might want us to distinguish I/O * error and timeout. Might want to switch portals on - * timeouts, but - * not I/O errors. + * timeouts, but not I/O errors. */ - log_error("Login I/O error, failed to " - "send a PDU\n"); + log_error("Login I/O error, failed to send a PDU\n"); ret = LOGIN_IO_ERROR; goto done; } @@ -1392,8 +1389,7 @@ iscsi_login(struct iscsi_session *session, char *buffer, size_t bufsize, * error and timeout. Might want to switch portals on * timeouts, but not I/O errors. */ - log_error("Login I/O error, failed to " - "receive a PDU\n"); + log_error("Login I/O error, failed to receive a PDU\n"); ret = LOGIN_IO_ERROR; goto done; } @@ -24,7 +24,7 @@ #define MD5_H #include <string.h> -#include <sys/types.h> +#include <sys/types.h> #include <netinet/in.h> #include <stdint.h> #if (__BYTE_ORDER == __BIG_ENDIAN) diff --git a/usr/strings.c b/usr/strings.c new file mode 100644 index 0000000..a3d4a23 --- /dev/null +++ b/usr/strings.c @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/param.h> + +#include "strings.h" +#include "log.h" + +int +init_string_buffer(struct string_buffer *s, size_t initial_allocation) +{ + if (s) { + memset(s, 0, sizeof (*s)); + s->buffer = NULL; + if (initial_allocation) { + s->buffer = malloc(initial_allocation); + if (s->buffer) { + s->allocated_length = initial_allocation; + memset(s->buffer, 0, initial_allocation); + } + } + s->data_length = 0; + return 1; + } + + return 0; +} + +struct string_buffer * +alloc_string_buffer(size_t initial_allocation) +{ + struct string_buffer *s = calloc(1, sizeof (*s)); + + if (s) { + init_string_buffer(s, initial_allocation); + } + + return s; +} + +void +free_string_buffer(struct string_buffer *s) +{ + if (s) { + if (s->buffer) { + free(s->buffer); + s->buffer = NULL; + } + s->allocated_length = 0; + s->data_length = 0; + free(s); + } +} + +void +enlarge_data(struct string_buffer *s, int length) +{ + if (s) { + s->data_length += length; + if (s->data_length >= s->allocated_length) { + /* too big */ + log_error( + "enlarged buffer %p to %d data bytes, " + "with only %d bytes of buffer space", + s, s->data_length, s->allocated_length); + } + } +} + +void +remove_initial(struct string_buffer *s, int length) +{ + char *remaining = s->buffer + length; + int amount = s->data_length - length; + + if (s && length) { + memmove(s->buffer, remaining, amount); + s->data_length = amount; + s->buffer[amount] = '\0'; + } +} + +static int +realloc_buffer(struct string_buffer *s, size_t min_length) +{ + size_t length = MAX(min_length + 1, s->allocated_length + 1024); + char *buf = realloc(s->buffer, length); + + if (buf) { + s->buffer = buf; + s->buffer[length - 1] = '\0'; + s->allocated_length = length; + return 1; + } else { + log_error( + "failed to allocate more space for string buffer %p", s); + return 0; + } +} + +/* truncate the data length down */ +void +truncate_buffer(struct string_buffer *s, size_t length) +{ + if (s) { + if (length <= s->data_length) { + s->data_length = length; + s->buffer[s->data_length] = '\0'; + } else if (length <= s->allocated_length) { + /* clear the data, and declare the + * data length to be larger + */ + memset(s->buffer + s->data_length, 0, + length - s->data_length); + s->data_length = length; + } else { + log_error( + "couldn't truncate data buffer to length %d, " + "only allocated %d", + length, s->allocated_length); + } + } +} + +/* append a string onto the buffer */ +int +append_string(struct string_buffer *s, const char *str) +{ + size_t length = strlen(str); + size_t needed = s->data_length + length + 1; /* existing + new + + * trailing NUL + */ + + if (needed >= s->allocated_length) { + /* need more space */ + if (!realloc_buffer(s, needed)) + return 0; + } + + strcpy(s->buffer + s->data_length, str); + s->data_length += length; + return 1; +} + +int +append_sprintf(struct string_buffer *s, const char *format, ...) +{ + va_list args; + size_t appended; + size_t available = s->allocated_length - s->data_length - 1; + int ret = 0; + + va_start(args, format); + + for (;;) { + appended = vsnprintf(s->buffer + s->data_length, available, + format, args); + + if (appended < 0) { + /* error, need more space, but don't know how much */ + if (!realloc_buffer(s, s->data_length + 1024)) + goto done; + } else if (appended >= available) { + /* what would have been output overflows the buffer, + * need more space + */ + if (!realloc_buffer(s, s->data_length + appended)) + goto done; + } else { + /* it fit */ + s->data_length += appended; + ret = 1; + break; + } + } + + done: + va_end(args); + + return ret; +} + +/* append a string after the NUL at the end of any current data. This + * maintains NUL termination of all strings + */ +int +adjoin_string(struct string_buffer *s, const char *str) +{ + size_t length = strlen(str) + 1; + size_t needed; + + if (s->buffer[s->data_length - 1] == '\0') + needed = s->data_length + length; /* lengths include NULs + */ + else + needed = s->data_length + 1 + length; /* existing + NUL + + * new + NUL + */ + if (needed >= s->allocated_length) { + /* need more space */ + if (!realloc_buffer(s, needed)) + return 0; + } + + if (s->buffer[s->data_length - 1] == '\0') { + memcpy(s->buffer + s->data_length, str, length); + /* lengths already include NULs + */ + s->data_length += length; /* new string + NUL */ + } else { + memcpy(s->buffer + s->data_length + 1, str, length); + /* NUL + new + NUL */ + s->data_length += 1 + length; /* NUL + new string + NUL */ + } + + return 1; +} + +char * +buffer_data(struct string_buffer *s) +{ + if (s) + return s->buffer; + else + return NULL; +} + +size_t +data_length(struct string_buffer * s) +{ + if (s) + return s->data_length; + else + return 0; +} + +size_t +unused_length(struct string_buffer * s) +{ + if (s) + return s->allocated_length - s->data_length; + else + return 0; +} + +/* write the entire buffer to the fd, or exit */ +void +write_buffer(struct string_buffer *s, int fd) +{ + const char *data = buffer_data(s); + const char *end = data + data_length(s); + int result; + + /* write the target info to the pipe */ + log_debug(7, "writing to pipe %d, data %p, size %u, text '%s'", + fd, data, end - data, data); + while (data < end) { + result = write(fd, data, end - data); + if (result < 0) { + if (errno != EINTR) { + log_error("can't write to pipe %d", fd); + exit(1); + } + } else { + data += result; + } + } +} diff --git a/usr/strings.h b/usr/strings.h new file mode 100644 index 0000000..a209a44 --- /dev/null +++ b/usr/strings.h @@ -0,0 +1,46 @@ +/* + * iSCSI variable-sized string buffers + * + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef STRINGS_H +#define STRINGS_H + +struct string_buffer { + size_t allocated_length; + size_t data_length; /* not including the trailing NUL */ + char *buffer; +}; + +extern int init_string_buffer(struct string_buffer *s, + size_t initial_allocation); +extern struct string_buffer *alloc_string_buffer(size_t initial_allocation); +extern void free_string_buffer(struct string_buffer *s); + +extern void enlarge_data(struct string_buffer *s, int length); +extern void remove_initial(struct string_buffer *s, int length); +extern void truncate_buffer(struct string_buffer *s, size_t length); +extern int append_string(struct string_buffer *s, const char *str); +extern int append_sprintf(struct string_buffer *s, const char *format, ...); +extern int adjoin_string(struct string_buffer *s, const char *str); +extern char *buffer_data(struct string_buffer *s); +extern size_t data_length(struct string_buffer *s); +extern size_t unused_length(struct string_buffer *s); +extern void write_buffer(struct string_buffer *s, int fd); + +#endif /* STRINGS_H */ |