util.c revision a9fd9a9e12bea66c9ea9b31f4b6f0ef584933f59
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <assert.h>
#include <errno.h>
#include <utility.h>
#include <fcntl.h>
#include <syslog.h>
#include <iscsitgt_impl.h>
#include "target.h"
#include "utility.h"
#include "errcode.h"
#include "isns_client.h"
#include "mgmt_scf.h"
#define CRC32_STR "CRC32C"
#define NONE_STR "None"
static thick_provo_t *thick_head,
void
{
}
/*
* []----
* | check_access -- see if the requesting initiator is in the ACL
* |
* | Optionally will also check to see if this initiator requires
* | authentication.
* []----
*/
{
char *dummy;
/*
* If ISNS is enable check for access privilege from isns server
*/
if (isns_enabled() == True) {
== False) {
return (False);
}
return (access);
}
/* Need to check if CHAP is needed for initiator */
== True) {
if (tgt_find_value_str(inode,
== True) {
found_chap = True;
break;
}
}
}
}
}
return (access);
}
/*
* If there's no ACL for this target everyone has access.
*/
return (True);
/*
* Find the local initiator name and also save the knowledge
* if the initiator had a CHAP secret.
*/
True) {
if (tgt_find_value_str(inode,
found_chap = True;
}
break;
} else {
}
}
}
return (False);
tgt_initiator)) != NULL) {
break;
}
}
/*
* If req_chap is True it means the login code hasn't gone
* through the authentication phase and it's trying to
* determine if the initiator should have done so. If
* we find a CHAP-secret then this routine will fail.
* No CHAP-secret for an initiator just means that a
* simple ACL list is used. This can be spoofed easily
* enough and is mainly used to limit the number of
* targets an initiator would see.
*/
}
return (valid);
}
/*
* []----
* | convert_local_tpgt -- Convert a local tpgt name to real addresses
* |
* | To simplify the configuration files targets only have a target portal
* | group tag string(s) associated. In the main configuration file there's
* | a tpgt element which has one or more ip-address elements. So the tag
* | is located and the actual data is inserted into the outgoing stream.
* []----
*/
static Boolean_t
{
tgt_node_t *x;
char buf[80];
char ipaddr[4];
/*
* The only children of the tpgt element are
* ip-address elements. The value of each element is
* the string we need to use. So, we don't need to
* check the node's name to see if this is correct or
* not.
*/
return (False);
}
== 1) {
/*
* Valid IPv4 address
*/
} else {
/*
* Invalid IPv4 address
* try with brackets (RFC2732)
*/
}
"TargetAddress", buf);
}
break;
}
}
return (True);
}
/*
* []----
* | add_target_address -- find and add any target address information
* []----
*/
static void
{
struct sockaddr_in *sp4;
struct sockaddr_in6 *sp6;
/*
* 7 is enough room for the largest TPGT of "65536", the ',' and a NULL
*/
char net_buf[INET6_ADDRSTRLEN];
(struct sockaddr *)&c->c_target_sockaddr);
return;
}
False) {
/*CSTYLED*/
} else {
/*CSTYLED*/
}
buf);
}
}
}
/*
* []----
* | add_targets -- add TargetName and TargetAddress to text argument
* |
* | Add targets which this initiator is allowed to see based on
* | the access_list associated with a target. If a target doesn't
* | have an access list then let everyone see it.
* []----
*/
static Boolean_t
{
break;
}
}
}
return (rval);
}
/*
* []----
* []----
*/
{
int dlen = *current_length;
int plen;
char *p;
/*
* Length is 'name' + separator + 'value' + NULL
*/
if (dlen) {
return (False);
} else {
return (False);
}
*text = p;
return (True);
}
static void
{
target_queue_t *q = queue_alloc();
msg_t *m;
n.nr_q = q;
queue_message_set(c->c_sessq, 0, t, &n);
m = queue_message_get(q);
queue_free(q, NULL);
}
static Boolean_t
{
/*
* It's the initiators data so we'll allow them
* to determine if CRC checks should be enabled
* or not. So, look at the first token, which
* declares their preference, and use that.
*/
} else {
}
return (rval);
}
/*
* []----
* | parse_text -- receive text information from initiator and parse
* |
* | Read in the current data based on the amount which the login PDU says
* | should be available. Add it to the end of previous data if it exists.
* | Previous data would be from a PDU which had the 'C' bit set and was
* | stored in the connection.
* |
* | Once values for parameter name has been selected store outgoing string
* | in text message for response.
* |
* | If errcode is non-NULL the appropriate login error code will be
* | stored.
* []----
*/
int *errcode)
{
char *p = NULL;
char *n;
char *cur_pair;
char param_rsp[32];
int plen; /* pair length */
char param_buf[16];
return (False);
/*
* Read in data to buffer.
*/
free(p);
return (False);
}
/*
* Read in and toss any pad data
*/
if (dlen % ISCSI_PAD_WORD_LEN) {
char junk[ISCSI_PAD_WORD_LEN];
free(p);
return (False);
}
}
if (c->c_text_area != NULL) {
if ((n = (char *)realloc(c->c_text_area,
free(p);
return (False);
}
/*
* No longer need the space allocated to 'p' since it
* will point to the aggregated area of all data.
*/
free(p);
/*
* Point 'p' to this new area for parsing and save the
* combined length in dlen.
*/
p = n;
dlen += c->c_text_len;
/*
* Clear the indication that space has been allocated
*/
c->c_text_area = NULL;
c->c_text_len = 0;
}
/*
* to cycle through each pair.
*/
n = p;
while (dlen > 0) {
cur_pair = n;
*errcode =
(ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
break;
} else
*n++ = '\0';
/*
* At this point, 'cur_pair' points at the name and 'n'
* points at the value.
*/
/*
* []--------------------------------------------------[]
* | The order of parameters processed matches the |
* | the RFC in section 12. |
* []--------------------------------------------------[]
*/
/*
* 12.1 -- HeaderDigest
* Negotiated
*/
/*
* 12.1 -- DataDigest
* Negotiated
*/
n, text, text_length);
/*
* 12.2 -- MaxConnections
* Negotiated
*/
/* ---- To be fixed ---- */
c->c_max_connections = 1;
"%d", c->c_max_connections);
/*
* 12.3 -- SendTargets
* Declarative
*/
(strcmp("All", n) == 0)) {
"Irrelevant");
} else {
}
/*
* 12.4 -- TargetName
* Declarative
*/
send_named_msg(c, msg_target_name, n);
/*
* Had to wait until now before loading any parameters
* because they are based on the TargetName which
* hasn't been known until now. This might fail
* because the target doesn't exist or the initiator
* doesn't have permission to access this target.
*/
*errcode =
(ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
/*
* Add TPGT now
*/
"%d", c->c_tpgt);
"TargetPortalGroupTag", param_buf);
}
/*
* 12.5 -- IntiatorName
* Declarative
*/
send_named_msg(c, msg_initiator_name, n);
/* ---- Section 12.6 is handled within TargetName ---- */
/*
* 12.7 -- InitiatorAlias
* Declarative
*/
send_named_msg(c, msg_initiator_alias, n);
/*
* Sections 12.8 (TargetAddress) and 12.9
* (TargetPortalGroupTag) are handled during the SendTargets
* processing.
*/
/*
* 12.10 -- IntialR2T
* Negotiated
*/
c->c_initialR2T = True;
/*
* 12.11 -- ImmediateData
* Negotiated
*/
/*
* Since we can handle immediate data without
* a problem just echo back what the initiator
* sends. If the initiator decides to violate
* the spec by sending immediate data even though
* they've disabled it, it's their problem and
* we'll deal with the data.
*/
/*
* 12.12 -- MaxRecvDataSegmentLength
* Declarative
*/
/*
* 12.13 -- MaxBurstLength
* Negotiated
*/
/*
* 12.14 -- FirstBurstLength
* Negotiated
*/
/*
* We can handle anything the initiator wishes
* to shove in our direction. So, store the value
* in case we ever wish to validate input data,
* but there's no real need to do so.
*/
/*
* 12.15 DefaultTime2Wait
* Negotiated
*/
/*
* 12.16 -- DefaultTime2Retain
* Negotiated
*/
/*
* 12.17 -- MaxOutstandingR2T
* Negotiated
*/
/*
* Save the value, but at most we'll toss out
* one R2T packet.
*/
/*
* 12.18 -- DataPDUInOder
* Negotiated
*/
/*
* We can handle DataPDU's out of order and
* currently we'll only send them in order. We're
* to far removed from the hardware to see data
* coming off of the platters out of order so
* it's unlikely we'd ever implement this feature.
* Store the parameter and echo back the initiators
* request.
*/
/*
* 12.19 -- DataSequenceInOrder
* Negotiated
*/
/*
* Currently we're set up to look at and require
* PDU sequence numbers be in order. The check
* now is only done as a prelude to supporting
* MC/S and guaranteeing the order of incoming
* packets on different connections.
*/
c->c_data_sequence_in_order = True;
/*
* 12.20 -- ErrorRecoveryLevel
* Negotiated
*/
c->c_erl = 0;
"%d", c->c_erl);
/*
* 12.21 -- SessionType
* Declarative
*/
/*
* Appendix A 3.1 -- IFMarker
* Negotiated
*/
c->c_ifmarker = False;
/*
* Appendix A 3.1 -- OFMarker
* Negotiated
*/
c->c_ofmarker = False;
cur_pair, n);
} else {
/*
* It's perfectly legitimate for an initiator to
* send us a parameter we don't currently understand.
* For example, an initiator that supports iSER will
* send an RDMA options parameter. If we respond with
* a valid return value it knows to switch to iSER
* for future processing.
*/
cur_pair, "NotUnderstood");
/*
* Go ahead a log this information in case we see
* something unexpected.
*/
"CON%x Unknown parameter %s=%s\n",
}
/*
* Make sure the caller wants error status and that it
* hasn't already been set.
*/
*errcode =
(ISCSI_STATUS_CLASS_TARGET_ERR << 8) |
break;
}
/*
* next pair of parameters. 1 is added to include the NULL
* byte and the end of each string.
*/
}
if (p != NULL)
free(p);
return (rval);
}
void
{
c->c_tpgt = 1;
}
/*
* []----
* | find_main_tpgt -- Looks up the IP address and finds a match TPGT
* |
* | If no TPGT for this address exists the routine returns 0 which
* | is an illegal TPGT value.
* []----
*/
static int
{
char ip_addr[16];
/*
* Hardly can you believe that such struct-to-struct
* assignment IS valid.
*/
ip_addr) != 1) {
continue;
}
sizeof (struct in_addr)) == 0) {
}
ip_addr) != 1) {
continue;
}
sizeof (struct in6_addr)) == 0) {
}
}
}
}
return (0);
}
/*
* convert_to_tpgt -- return a TPGT based on the target address
*
* If a target doesn't have a TPGT list then just return the default
* value of 1. Otherwise determine which TPGT the target address is
* part of and find that TPGT value in the list of TPGTs this target
* is willing to expose. If the TPGT value is not found in the list
* return zero which will break the connection.
*/
static int
{
/*
* If this target doesn't have a list of target portal group tags
* just return the default which is 1.
*/
return (1);
/*
* If we don't find our IP in the general configuration list
* we'll use the default value which is 1 according to RFC3720.
*/
return (addr_tpgt);
}
}
return (0);
}
/*
* []----
* | find_target_node -- given a target IQN name, return the XML node
* []----
*/
find_target_node(char *targ_name)
{
char *iname;
True) {
return (tnode);
} else
}
}
return (NULL);
}
static Boolean_t
{
return (False);
/*
* Have a valid node for our target. Start looking
* for connection oriented parameters.
*/
return (False);
NULL) {
&c->c_targ_alias);
} else {
&c->c_targ_alias);
}
&c->c_maxcmdsn);
}
return (rval);
}
{
char *minor_part;
return (False);
return (False);
}
return (True);
}
/*
* []----
* | sna_lt -- Serial Number Arithmetic, 32 bits, less than, RFC1982
* []----
*/
int
{
}
/*
* []----
* sna_lte -- Serial Number Arithmetic, 32 bits, less than, RFC1982
* []----
*/
int
{
}
util_create_guid(char **guid)
{
/*
* macros will not work on 64bit variables. The work, but produce
* invalid results on Big Endian based machines.
*/
int i, fd;
/*
* By default strict GUID generation is enforced. This can
* be disabled by using the correct XML tag in the configuration
* file.
*/
if (enforce_strict_guid == True)
return (False);
/*
* There's no MAC address available and we've even tried
* a second time to get one. So fallback to using a random
* number for the MAC address.
*/
return (False);
return (False);
eui.e_company_id[0] = 0;
} else {
/* ---- [0] & [1] are zero for Sun's IEEE identifier ---- */
}
/*
* To prevent duplicate GUIDs we need to sleep for one
* second here since part of the GUID is a time stamp with
* a one second resolution.
*/
sleep(1);
}
return (False);
} else
return (True);
}
/*
* []----
* | create_geom -- based on size determine best fit for CHS
* |
* | Given size in bytes which will be adjusted to sectors, find
* | the best fit for making C * H * S == sectors
* []----
*/
void
{
diskaddr_t c, h, s, t;
int pass;
/*
* For certain odd size LU we can't generate correct geometry.
* If this occurs the values will be set to zero and the MODE
* pages will return unsupported.
*/
*cylinder = 0;
*heads = 0;
*spt = 0;
for (c = 0x8000; c > 0; c >>= 1) {
t = sects / c;
if ((t == 0) || ((sects % c) != 0))
continue;
for (h = 1; h < 0xff; h++)
if ((t % h) == 0) {
s = t / h;
if (s > 0xffff)
continue;
if ((pass == 0) &&
(s < MIN_VAL))) {
continue;
}
*cylinder = (int)c;
*heads = (int)h;
*spt = (int)s;
return;
}
}
/*
* At this point we've got a size which will not fit into
* any values where C * H * S = size. So, set the heads and
* sectors per track values to their largest which will make
* a single cylinder 8GB. Then divide that into the size to
* get the number of cylinders.
*/
h = 0xff;
s = 0xffff;
c = sects / (h * s);
*heads = (int)h;
*spt = (int)s;
*cylinder = (int)c;
}
/*
* []----
* | strtol_multiplier -- common method to deal with human type numbers
* []----
*/
{
char *m;
if (m && *m) {
switch (*m) {
case 't':
case 'T':
size *= 1024;
/*FALLTHRU*/
case 'g':
case 'G':
size *= 1024;
/*FALLTHRU*/
case 'm':
case 'M':
size *= 1024;
/*FALLTHRU*/
case 'k':
case 'K':
size *= 1024;
break;
default:
return (False);
}
}
return (True);
}
/*
* []----
* []----
*/
void
{
char *type_str;
switch (type) {
case Q_CONN_LOGIN:
case Q_CONN_NONIO:
type_str = "CON";
break;
case Q_SESS_LOGIN:
case Q_SESS_NONIO:
type_str = "SES";
break;
case Q_STE_NONIO:
type_str = "SAM";
break;
default:
type_str = "UGH";
break;
}
}
/*
* []----
* | task_to_str -- convert task management event to string (DEBUG USE)
* []----
*/
char *
task_to_str(int func)
{
switch (func) {
case ISCSI_TM_FUNC_ABORT_TASK: return ("Abort");
case ISCSI_TM_FUNC_ABORT_TASK_SET: return ("Abort Set");
case ISCSI_TM_FUNC_CLEAR_ACA: return ("Clear ACA");
case ISCSI_TM_FUNC_CLEAR_TASK_SET: return ("Clear Task");
case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: return ("LUN Reset");
case ISCSI_TM_FUNC_TARGET_WARM_RESET: return ("Target Warm Reset");
case ISCSI_TM_FUNC_TARGET_COLD_RESET: return ("Target Cold Reset");
case ISCSI_TM_FUNC_TASK_REASSIGN: return ("Task Reassign");
default: return ("Unknown");
}
}
/*
* []----
* | xml_rtn_msg -- create a common format for XML replies to management UI
* []----
*/
void
{
char lbuf[16];
}
/*
* []----
* []----
*/
void *
thick_provo_start(void *v)
{
msg_t *m;
/*
* Add this threads information to the main queue. This is
* used in case the administrator decides to remove the LU
* before the initialization is complete.
*/
(void) pthread_mutex_lock(&thick_mutex);
if (thick_head == NULL) {
thick_head = tp;
} else {
}
thick_tail = tp;
(void) pthread_mutex_unlock(&thick_mutex);
/*
* This let's the parent thread know this thread is running.
*/
/* ---- Start the initialization of the LU ---- */
/* ---- Remove from the linked list ---- */
(void) pthread_mutex_lock(&thick_mutex);
thick_tail = NULL;
} else
} else {
else
}
(void) pthread_mutex_unlock(&thick_mutex);
/*
* There's a race condition where t10_thick_provision() could
* finish and before the thick_mutex lock is grabbed again
* that another thread running the thick_provo_stop() could
* find a match and send a shutdown message. If that happened
* that thread would wait forever in queue_message_get(). So,
* queue one last time to see if there's a message available.
* If so, send an ack.
*/
m = queue_message_try_get(tp->q);
if (m != NULL) {
msg_shutdown_rsp, 0);
}
else {
/*
* There's not much we can do here. The most likely
* cause of not being able to remove the target is
* that it's LU 0 and there is currently another
* LU allocated.
*/
"Failed to remove target\n");
"initialization failure");
}
}
(void *)(uintptr_t)pthread_self());
return (NULL);
}
/*
* []----
* []----
*/
void
{
target_queue_t *q = queue_alloc();
(void) pthread_mutex_lock(&thick_mutex);
tp = thick_head;
while (tp) {
/*
* Drop the global mutex because it's entirely
* possible for a thick_provo_start thread to be
* in the early stages in which it will can call
* thick_provo_chk() from the T10 SAM code.
*/
(void) pthread_mutex_unlock(&thick_mutex);
/*
* Pick the lock back up since it'll make the
* finish stage easier to deal with.
*/
(void) pthread_mutex_lock(&thick_mutex);
break;
}
}
(void) pthread_mutex_unlock(&thick_mutex);
queue_free(q, NULL);
}
/*
* []----
* | thick_provo_chk_thr -- see if there's an initialization thread running
* []----
*/
{
(void) pthread_mutex_lock(&thick_mutex);
tp = thick_head;
while (tp) {
break;
}
}
(void) pthread_mutex_unlock(&thick_mutex);
return (rval);
}
/*
* []----
* |
* | This is a common function that's used both by the normal remove
* | target code and when a write failure occurs during initialization.
* | It will handle being given either the local target name or the full
* | IQN name of the target.
* []----
*/
void
{
char path[MAXPATHLEN];
int chk;
(void) pthread_mutex_lock(&targ_config_mutex);
NULL) {
/* ---- Look for a match on the friendly name ---- */
break;
}
/* ---- Check to see if they gave the IQN name instead ---- */
break;
else {
}
}
/* ---- Check to see if it's already been removed ---- */
(void) pthread_mutex_unlock(&targ_config_mutex);
return;
}
/*
* We need both the friendly and IQN names so figure out which wasn't
* given and find it's value.
*/
False) {
(void) pthread_mutex_unlock(&targ_config_mutex);
return;
}
}
goto error;
if (lun_num == 0) {
/*
* LUN must be the last one removed, so check to
* see if others are still present.
*/
NULL) {
goto error;
goto error;
}
}
} else {
/*
* Make sure the LU exists that's being removed
*/
NULL) {
goto error;
&lun_num);
break;
}
}
goto error;
}
}
/* ---- Say goodbye to that data ---- */
/*
* If the was LUN 0 then do to the previous check
* we know that no other files exist in the target
* directory so the target information can be removed
* along with the directory.
*/
if (lun_num == 0) {
iname);
/*
* Don't forget to remove the symlink to
* the target directory.
*/
tname);
/*
* 'tname' is just a reference to the memory within
* the targets_config structure. So once the tgt_node_remove()
* is called 'tname' is no longer valid.
*/
tgt_node_free(c);
}
/*
* Not much we can do here if we fail to updated the config.
*/
if (mgmt_config_save2scf() == False)
(void) pthread_mutex_unlock(&targ_config_mutex);
}
/*
* []----
* | get_local_name
* |
* | This function fetches local name from a iscsi-name
* | Caller is responsible to free the string.
* []----
*/
char *
get_local_name(char *iname)
{
char *str;
!= NULL) {
break;
}
}
return (ret);
}