/*
* 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
*/
/*
*/
/*
* ISCSID --
*
* Discovery of targets and access to the persistent storage starts here.
*/
#include "iscsi_targetparam.h"
#include "isns_client.h"
#include "isns_protocol.h"
#include "persistent.h"
#include "iscsi.h"
#include <sys/ethernet.h>
#include <sys/bootprops.h>
/*
* local function prototypes
*/
static void iscsid_threads_destroy(void);
static void iscsid_remove_target_param(char *name);
extern int modrootloaded;
static int iscsi_net_up = 0;
extern ib_boot_prop_t *iscsiboot_prop;
/*
* iSCSI target discovery thread table
*/
typedef struct iscsid_thr_table {
char *name;
"Static" },
"SendTarget" },
"SLP" },
"iSNS" },
NULL }
};
/*
* discovery method event table
*/
iSCSIDiscoveryMethodUnknown /* terminating value */
};
/*
* set iscsi:iscsi_boot_max_delay = 360
* , provides with customer a max wait time in
* seconds to wait for boot lun online during iscsi boot.
* Defaults to 180s.
*/
/*
* discovery configuration semaphore
*/
/*
* Check if IP is valid
*/
static boolean_t
{
int i = 0;
if (!ip)
return (B_FALSE);
for (; (ip[i] == 0) && (i < IB_IP_BUFLEN); i++) {}
if (i == IB_IP_BUFLEN) {
/* invalid IP address */
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Make an entry for the boot target.
* return B_TRUE upon success
* B_FALSE if fail
*/
static boolean_t
{
return (B_FALSE);
}
if (!iscsid_ip_check(
return (B_FALSE);
return (B_FALSE);
} else {
(void) bcopy(
}
return (B_TRUE);
}
/*
* Create the boot session
*/
static void
{
entry_t e;
return;
}
if (!iscsid_ip_check(
return;
}
if (!iscsid_make_entry(boot_prop_table, &e))
return;
} else {
if (!iscsid_make_entry(boot_prop_table, &e))
return;
iscsid_do_sendtgts(&e);
}
}
/*
* iscsid_init -- to initialize stuffs related to iscsi daemon,
* and to create boot session if needed
*/
{
SEMA_DRIVER, NULL);
if (modrootloaded == 1) {
/* normal case, load the persistent store */
if (persistent_load() == B_TRUE) {
} else {
return (B_FALSE);
}
}
if (!iscsid_boot_init_config(ihp)) {
} else {
if (iscsi_boot_wd_handle) {
} else {
}
}
" partially failed");
}
}
return (rval);
}
/*
* iscsid_start -- start the iscsi initiator daemon, actually this code
* is just to enable discovery methods which are set enabled in
* persistent store, as an economic way to present the 'daemon' funtionality
*/
}
}
}
/*
* In case of failure the events still need to be sent
* because the door daemon will pause until all these
* events have occurred.
*/
/* ---- Send both start and end events ---- */
}
}
return (rval);
}
/*
* iscsid_stop -- stop the iscsi initiator daemon, by disabling
* all the discovery methods first, and then try to stop all
* related threads. This is a try-best effort, leave any 'busy' device
* (and therefore session) there and just return.
*/
/* final check */
} else {
/*
* If only boot session is left, that is OK.
* Otherwise, we should report that some sessions are left.
*/
break;
}
}
}
return (rval);
}
/*
* iscsid_fini -- do whatever is required to clean up
*/
/* ARGSUSED */
void
{
if (iscsi_boot_wd_handle != NULL) {
}
}
/*
* iscsid_props -- returns discovery thread information, used by ioctl code
*/
void
{
/* ---- change once thread is implemented ---- */
}
/*
* iscsid_enable_discovery - start specified discovery methods
*/
/* ARGSUSED */
{
/*
* start the specified discovery method(s)
*/
dt++) {
break;
}
(void) iscsi_thread_send_wakeup(
}
} else {
/*
* unexpected condition. The threads for each
* discovery method should have started at
* initialization
*/
}
}
} /* END for() */
return (rval);
}
/*
* iscsid_disable_discovery - stop specified discovery methods
*/
{
/*
* stop the specified discovery method(s)
*/
dt++) {
/* signal discovery event change - begin */
/* Attempt to logout of all associated targets */
/* Successfully logged out of targets */
/*
* signal discovery
* event change - end
*/
break;
}
} else {
/*
* unexpected condition. The threads
* for each discovery method should
* have started at initialization
*/
}
}
/* signal discovery event change - end */
}
} /* END for() */
return (rval);
}
/*
* iscsid_poke_discovery - wakeup discovery methods to find any new targets
* and wait for all discovery processes to complete.
*/
void
{
/* reset discovery flags */
/* start all enabled discovery methods */
dt++) {
if ((method == iSCSIDiscoveryMethodUnknown) ||
B_TRUE) {
}
}
}
if (send_wakeup == B_FALSE) {
}
}
}
}
/*
* iscsid_do_sendtgts - issue send targets command to the given discovery
* address and then add the discovered targets to the discovery queue
*/
void
{
int stl_sz;
const char *ip;
int ctr;
int rc;
/* allocate and initialize sendtargets list header */
sizeof (iscsi_sendtgts_entry_t));
/* lock interface so only one SendTargets operation occurs */
"failure to get soft state");
return;
}
if (rc) {
"iscsi discovery failure - SendTargets (%s)\n", ip);
return;
}
/* check if all targets received */
((stl_num_tgts - 1) *
sizeof (iscsi_sendtgts_entry_t));
goto retry_sendtgts;
} else {
sizeof (struct in_addr) ?
"SendTargets overflow (%s)\n", ip);
return;
}
}
}
}
}
void
{
} else {
return;
}
int, query_status);
return;
}
pg_sz = sizeof (isns_portal_group_list_t);
if (pg_list->pg_out_cnt > 0) {
sizeof (isns_portal_group_t);
}
}
void
{
&pg_list);
int, query_status);
return;
}
if ((query_status != isns_ok &&
int, query_status);
pg_sz = sizeof (isns_portal_group_list_t);
if (pg_list->pg_out_cnt > 0) {
sizeof (isns_portal_group_t);
}
return;
}
pg_sz = sizeof (isns_portal_group_list_t);
if (pg_list->pg_out_cnt > 0) {
sizeof (isns_portal_group_t);
}
}
/*
* iscsid_config_one - for the given target name, attempt
* to login to all targets associated with name. If target
* name is not found in discovery queue, reset the discovery
* queue, kick the discovery processes, and then retry.
*
* NOTE: The caller of this function must hold the
* iscsid_config_semaphore across this call.
*/
void
{
int retry = 0;
int lun_online = 0;
int cur_sec = 0;
if (!iscsi_configroot_printed) {
" iSCSI boot session...");
}
if (iscsi_net_up == 0) {
if (iscsi_net_interface(B_FALSE) ==
iscsi_net_up = 1;
} else {
" for iSCSI boot session");
return;
}
}
/*
* create boot session
*/
retry++;
continue;
}
/*
* no active connection for the boot
* session, retry the login until
* one is found or the retry count
* is exceeded
*/
retry++;
continue;
}
/*
* The boot session has been created with active
* connection. If the target lun has not been online,
* we should wait here for a while
*/
do {
if (lun_online == 0) {
cur_sec++;
}
} while ((lun_online == 0) &&
(cur_sec < iscsi_boot_max_delay));
retry++;
}
if (!rc) {
" boot session");
}
} else {
NULL);
/*
* If we didn't login to the device we might have
* to update our discovery information and attempt
* the login again.
*/
/*
* Stale /dev links can cause us to get floods
* of config requests. Prevent these repeated
* requests from causing unneeded discovery updates
* if ISCSI_CONFIG_STORM_PROTECT is set.
*/
}
}
}
}
/*
* iscsid_config_all - reset the discovery queue, kick the
* discovery processes, and login to all targets found
*
* NOTE: The caller of this function must hold the
* iscsid_config_semaphore across this call.
*/
void
{
int retry = 0;
int lun_online = 0;
int cur_sec = 0;
if (!iscsi_configroot_printed) {
" iSCSI boot session...");
}
if (iscsi_net_up == 0) {
if (iscsi_net_interface(B_FALSE) ==
iscsi_net_up = 1;
}
}
/*
* No boot session has been created.
* We would like to create the boot
* Session first.
*/
retry++;
continue;
}
/*
* no active connection for the boot
* session, retry the login until
* one is found or the retry count
* is exceeded
*/
retry++;
continue;
}
/*
* The boot session has been created with active
* connection. If the target lun has not been online,
* we should wait here for a while
*/
do {
if (lun_online == 0) {
cur_sec++;
}
} while ((lun_online == 0) &&
(cur_sec < iscsi_boot_max_delay));
retry++;
}
if (!rc) {
" boot session");
}
} else {
/*
* Stale /dev links can cause us to get floods
* of config requests. Prevent these repeated
* requests from causing unneeded discovery updates
* if ISCSI_CONFIG_STORM_PROTECT is set.
*/
}
}
}
/*
* isns_scn_callback - iSNS client received an SCN
*
* This code processes the iSNS client SCN events. These
* could relate to the addition, removal, or update of a
* logical unit.
*/
void
{
int i, pg_sz;
int qry_status;
/* No argument */
return;
}
return;
}
/*
* All isns callbacks are from a standalone taskq
* of isns discovery method
*/
return;
}
switch (scn_type) {
/*
* ISNS_OBJ_ADDED - An object has been added.
*/
case ISNS_OBJ_ADDED:
/* Query iSNS server for contact information */
(uint8_t *)"",
&pg_list);
/* Verify portal group is found */
if ((qry_status != isns_ok &&
break;
}
/* Add all portals for logical unit to discovery cache */
for (i = 0; i < pg_list->pg_out_cnt; i++) {
/* Force target to login */
NULL);
}
pg_sz = sizeof (isns_portal_group_list_t);
if (pg_list->pg_out_cnt > 0) {
sizeof (isns_portal_group_t);
}
}
break;
/*
* ISNS_OBJ_REMOVED - logical unit has been removed
*/
case ISNS_OBJ_REMOVED:
if (iscsid_del(ihp,
(char *)((isns_scn_callback_arg_t *)arg)->
B_TRUE) {
"isns remove scn failed for target %s\n",
(char *)((isns_scn_callback_arg_t *)arg)->
}
break;
/*
* ISNS_OBJ_UPDATED - logical unit has changed
*/
case ISNS_OBJ_UPDATED:
"received iSNS update SCN for %s\n",
(char *)((isns_scn_callback_arg_t *)arg)->
break;
/*
* ISNS_OBJ_UNKNOWN -
*/
default:
"received unknown iSNS SCN type 0x%x\n", scn_type);
break;
}
}
/*
* iscsid_add - Creates discovered session and connection
*/
static boolean_t
{
int idx;
int isid;
int size;
char *tmp;
/* setup initial buffer for configured session information */
/* get configured sessions information */
tmp = target_name;
/*
* No target information available check for
* initiator information.
*/
/*
* No hba information is
* found. So assume default
* one session unbound behavior.
*/
}
}
/*
* iscsi boot with mpxio disabled
* no need to search configured boot session
*/
if (iscsi_cmp_boot_ini_name(tmp) ||
}
}
/* Check to see if we need to get more information */
/* record new size and free last buffer */
/* allocate new buffer */
/* get configured sessions information */
"unable to get configured session information\n",
return (B_FALSE);
}
}
/* loop for all configured sessions */
/* create or find matching session */
break;
}
/* create or find matching connection */
/*
* Teardown the session we just created. It can't
* have any luns or connections associated with it
* so this should always succeed (luckily since what
* would we do if it failed?)
*/
(void) iscsi_sess_destroy(isp);
break;
}
}
return (rtn);
}
/*
* iscsid_del - Attempts to delete all associated sessions
*/
{
/* target name can be NULL or !NULL */
/* addr_dsc can be NULL or !NULL */
/*
* If no target_name is listed (meaning all targets)
* or this specific target was listed. And the same
* discovery method discovered this target then
* continue evaulation. Otherwise fail.
*/
if (((target_name == NULL) ||
/*
* If iSNS, SendTargets, or Static then special
* handling for disc_addr.
*/
if ((method == iSCSIDiscoveryMethodISNS) ||
/*
* If NULL addr_dsc (meaning all disc_addr)
* or matching discovered addr.
*/
} else {
}
} else if (method == iSCSIDiscoveryMethodStatic) {
/*
* If NULL addr_dsc (meaning all disc_addr)
* or matching active connection.
*/
== 0))) {
} else {
}
} else {
/* Unknown discovery specified */
}
if (try_destroy == B_TRUE &&
if (ISCSI_SUCCESS(status)) {
} else if (status == ISCSI_STATUS_BUSY) {
/*
* The most likely destroy failure
* This means that the resource is
*/
"resource is in use\n",
} else {
"session logout failed (%d)\n",
}
} else {
}
} else {
}
}
return (rtn);
}
/*
* iscsid_login_tgt - request target(s) to login
*/
{
int total = 0;
/* Loop thru sessions */
if (!(method & iSCSIDiscoveryMethodBoot)) {
if (target_name == NULL) {
if (method == iSCSIDiscoveryMethodUnknown) {
/* unknown method mean login to all */
try_online = B_TRUE;
if ((method ==
(method ==
(bcmp(
== 0)) {
/*
* iSNS or sendtarget
* discovery and
* discovery address
* is NULL or match
*/
try_online = B_TRUE;
} else {
/* addr_dsc not a match */
}
} else {
/* static configuration */
try_online = B_TRUE;
}
} else {
/* method not a match */
}
} else if (strcmp(target_name,
/* target_name match */
try_online = B_TRUE;
} else {
/* target_name not a match */
}
} else {
/*
* online the boot session.
*/
try_online = B_TRUE;
}
}
if (try_online == B_TRUE &&
total++;
/* Copy these sessions to the list. */
isp_list =
sizeof (iscsi_sess_list_t), KM_SLEEP);
} else {
sizeof (iscsi_sess_list_t), KM_SLEEP);
}
}
}
if (total > 0) {
time_stamp = ddi_get_time();
"login_queue.%lx", time_stamp);
if (login_taskq == NULL) {
}
return (rtn);
}
DDI_SLEEP) != DDI_SUCCESS) {
"for login to the target: %s",
}
}
}
}
return (rtn);
}
/*
* +--------------------------------------------------------------------+
* | Local Helper Functions |
* +--------------------------------------------------------------------+
*/
/*
* iscsid_init_config -- initialize configuration parameters of iSCSI initiator
*/
static boolean_t
{
void *v = NULL;
char *name;
char *initiatorName;
int rc;
/* allocate memory to hold initiator names */
/*
* initialize iSCSI initiator name
*/
ISCSI_MAX_NAME_LEN) == B_TRUE) {
(void) strncpy(initiatorName,
/* use default tunable value */
" from firmware");
} else {
/* use default value */
}
sizeof (iscsi_tunable_params_t));
}
} else {
/*
* if no initiator-node name available it is most
* likely due to a fresh install, or the persistent
* store is not working correctly. Set
* a default initiator name so that the initiator can
* be brought up properly.
*/
}
/*
* initialize iSCSI initiator alias (if any)
*/
} else {
/* EMPTY */
/* No alias defined - not a problem. */
}
/*
* load up the overriden iSCSI initiator parameters
*/
v = NULL;
param_id++) {
if (rc == 0) {
}
if (rc != 0) {
/* note error but continue */
"Failed to set "
"param %d for OID %d",
}
}
} /* END for() */
if (iscsiboot_prop &&
(void) iscsi_reconfig_boot_sess(ihp);
}
break;
}
} /* END while() */
return (B_TRUE);
}
/*
* iscsid_init_targets -- Load up the driver with known static targets and
* targets whose parameters have been modified.
*
* This is done so that the CLI can find a list of targets the driver
* currently knows about.
*
* The driver doesn't need to log into these targets. Log in is done based
* upon the enabled discovery methods.
*/
static boolean_t
{
void *v = NULL;
char *name;
char *iname;
int rc;
/* allocate memory to hold target names */
/*
* load up targets whose parameters have been overriden
*/
/* ---- only need to be set once ---- */
/* allocate memory to hold initiator name */
v = NULL;
/*
* target name matched initiator's name so,
* continue to next target. Initiator's
* parmeters have already been set.
*/
continue;
}
/*
* boot target is not mpxio enabled
* simply ignore these overriden parameters
*/
continue;
}
param_id++) {
if (rc == 0) {
}
if (rc != 0) {
/* note error but continue ---- */
"param %d for OID %d",
}
}
} /* END for() */
(void) iscsi_reconfig_boot_sess(ihp);
}
} /* END while() */
return (B_TRUE);
}
/*
* iscsid_thread_static -- If static discovery is enabled, this routine obtains
* all statically configured targets from the peristent store and issues a
* login request to the driver.
*/
/* ARGSUSED */
static void
{
void *v = NULL;
/* ---- ensure static target discovery is enabled ---- */
if ((dm & iSCSIDiscoveryMethodStatic) == 0) {
"iscsi discovery failure - "
"StaticTargets method is not enabled");
continue;
}
/*
* walk list of the statically configured targets from the
* persistent store
*/
v = NULL;
B_TRUE) {
}
}
}
/*
* iscsid_thread_sendtgts -- If SendTargets discovery is enabled, this routine
* obtains all target discovery addresses configured from the peristent store
* a login request to the driver for all discoveryed targets.
*/
static void
{
void *v = NULL;
B_TRUE);
/* ---- ensure SendTargets discovery is enabled ---- */
if ((dm & iSCSIDiscoveryMethodSendTargets) == 0) {
"iscsi discovery failure - "
"SendTargets method is not enabled");
continue;
}
/*
* walk list of the SendTarget discovery addresses from the
* persistent store
*/
v = NULL;
}
B_FALSE);
}
}
/*
* iscsid_thread_slp -- If SLP discovery is enabled, this routine provides
* the SLP discovery service.
*/
static void
{
do {
/*
* Even though we don't have support for SLP at this point
* we'll send the events if someone has enabled this thread.
* If this is not done the daemon waiting for discovery to
* complete will pause forever holding up the boot process.
*/
}
/*
* iscsid_thread_isns --
*/
static void
{
/* ---- ensure iSNS discovery is enabled ---- */
if ((dm & iSCSIDiscoveryMethodISNS) == 0) {
"iscsi discovery failure - "
"iSNS method is not enabled");
continue;
}
}
/* Thread stopped. Deregister from iSNS servers(s). */
}
/*
* iscsid_threads_create -- Creates all the discovery threads.
*/
static void
{
iscsid_thr_table *t;
/*
* start a thread for each discovery method
*/
t++) {
t->func_start, ihp);
}
}
}
/*
* iscsid_threads_destroy -- Destroys all the discovery threads.
*/
static void
iscsid_threads_destroy(void)
{
iscsid_thr_table *t;
t++) {
}
}
}
/*
* iscsid_copyto_param_set - helper function for iscsid_init_params.
*/
static int
{
int rtn = 0;
if (param_id >= ISCSI_NUM_LOGIN_PARAM) {
return (EINVAL);
}
switch (param_id) {
/*
* Boolean parameters
*/
break;
break;
break;
break;
/*
* Integer parameters
*/
break;
break;
break;
break;
break;
break;
break;
/*
* Integer parameters which currently are unsettable
*/
/* ---- drop through to default case ---- */
default:
break;
}
/* if all is well, set the parameter identifier */
if (rtn == 0) {
}
return (rtn);
}
/*
* iscsid_add_pg_list_to_cache - Add portal groups in the list to the
* discovery cache.
*/
static void
{
int i;
for (i = 0; i < pg_list->pg_out_cnt; i++) {
}
}
/*
* set_initiator_name - set default initiator name and alias.
*
* This sets the default initiator name and alias. The
* initiator name is composed of sun's reverse domain name
* and registration followed and a unique classifier. This
* classifier is the mac address of the first NIC in the
* host and a timestamp to make sure the classifier is
* unique if the NIC is moved between hosts. The alias
* is just the hostname.
*/
void
{
int i;
time_t x;
/* Set default initiator-node name */
} else {
"iqn.1986-03.com.sun:01:");
for (i = 0; i < ETHERADDRL; i++) {
eaddr.ether_addr_octet[i]);
}
/* Set default initiator-node alias */
x = ddi_get_time();
(void) persistent_alias_name_set(
}
}
}
return;
}
/* Set default initiator-node CHAP settings */
ISCSI_MAX_NAME_LEN) == B_TRUE) {
KM_SLEEP);
B_FALSE) {
}
}
}
static void
{
/*
* Remove target-param <-> target mapping.
* Only remove if there is not any overridden
* parameters in the persistent store
*/
/*
* setup initial buffer for configured session
* information
*/
(void) iscsi_targetparam_remove_target(t_oid);
}
}
/*
* iscsid_addr_to_sockaddr - convert other types to struct sockaddr
*/
void
{
(src_insize == sizeof (struct in6_addr)));
/* translate discovery information */
if (src_insize == sizeof (struct in_addr)) {
(struct sockaddr_in *)dst_addr;
sizeof (struct in_addr));
} else {
(struct sockaddr_in6 *)dst_addr;
sizeof (struct in6_addr));
}
}
/*
* iscsi_discovery_event -- send event associated with discovery operations
*
* Each discovery event has a start and end event. Which is sent is based
* on the boolean argument start with the obvious results.
*/
static void
{
switch (m) {
} else {
}
break;
} else {
}
break;
case iSCSIDiscoveryMethodSLP:
} else {
}
break;
case iSCSIDiscoveryMethodISNS:
} else {
}
break;
}
}
/*
* iscsi_send_sysevent -- send sysevent using specified class
*/
void
char *eventclass,
char *subclass,
{
}
static boolean_t
{
}
/* or using default login param for boot session */
return (B_TRUE);
}
{
int idx;
char *name;
if (iscsiboot_prop == NULL) {
return (B_FALSE);
}
/* get information of number of sessions to be configured */
/*
* No target information available to check
* initiator information. Assume one session
* by default.
*/
}
}
/* get necessary information */
/* get configured sessions information */
if (persistent_get_config_session((char *)name,
"failed to setup multiple sessions",
name);
return (B_FALSE);
}
}
/* create a temporary session to keep boot session connective */
"failed to setup multiple sessions", name);
return (B_FALSE);
}
/* destroy all old boot sessions */
/*
* destroy all stale sessions
* except temporary boot session
*/
isp))) {
} else {
/*
* couldn't destroy stale sessions
* at least poke it to disconnect
*/
"failed to setup multiple"
" sessions", name);
}
} else {
}
} else {
}
}
" multiple sessions", name);
break;
}
}
/*
* fail to create any new boot session
* so only the temporary session is alive
* quit without destroying it
*/
return (rtn);
}
/* couldn't destroy temp boot session */
"failed to setup multiple sessions", name);
}
return (rtn);
}
static iscsi_sess_t *
{
} else {
sizeof (struct in6_addr));
}
/* create temp booting session failed */
return (NULL);
}
return (NULL);
}
/* now online created session */
return (NULL);
}
return (isp);
}
static void
{
while (rc != 0) {
if (persistent_load() == B_TRUE) {
}
}
(reconfigured == B_FALSE)) {
(void) iscsi_reconfig_boot_sess(ihp);
}
}
break;
}
}
}
{
ISCSI_MAX_NAME_LEN) == 0)) {
return (B_TRUE);
} else {
return (B_FALSE);
}
}
{
ISCSI_MAX_NAME_LEN) == 0)) {
return (B_TRUE);
} else {
return (B_FALSE);
}
}
{
if (iscsiboot_prop == NULL) {
return (B_FALSE);
}
if (!ihp->hba_mpxio_enabled) {
return (B_FALSE);
}
ISCSI_MAX_NAME_LEN) == 0) &&
/*
* found boot session.
* check its mdi path info is null or not
*/
}
}
}
}
}
if (bootlun_found) {
return (tgt_mpxio_enabled);
} else {
/*
* iscsiboot_prop not NULL while no boot lun found
* in most cases this is none iscsi boot while iscsiboot_prop
* is not NULL, in this scenario return iscsi HBA's mpxio config
*/
return (ihp->hba_mpxio_enabled);
}
}
static boolean_t
{
if (icp->conn_state ==
return (B_TRUE);
}
}
}
}
return (B_FALSE);
}