root/usr/src/common/iscsit/iscsit_common.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 */

#include <sys/time.h>

#if defined(_KERNEL)
#include <sys/ddi.h>
#include <sys/types.h>
#include <sys/sunddi.h>
#include <sys/socket.h>
#include <inet/ip.h>
#include <inet/tcp.h>
#else
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <sys/iscsit/iscsit_common.h>
#include <sys/iscsi_protocol.h>
#include <sys/iscsit/isns_protocol.h>

void *
iscsit_zalloc(size_t size)
{
#if defined(_KERNEL)
        return (kmem_zalloc(size, KM_SLEEP));
#else
        return (calloc(1, size));
#endif
}

void
iscsit_free(void *buf, size_t size)     /* ARGSUSED */
{
#if defined(_KERNEL)
        kmem_free(buf, size);
#else
        free(buf);
#endif
}

/*
 * default_port should be the port to be used, if not specified
 * as part of the supplied string 'arg'.
 */

#define NI_MAXHOST      1025
#define NI_MAXSERV      32


struct sockaddr_storage *
it_common_convert_sa(char *arg, struct sockaddr_storage *buf,
    uint32_t default_port)
{
        /* Why does addrbuf need to be this big!??! XXX */
        char            addrbuf[NI_MAXHOST + NI_MAXSERV + 1];
        char            *addr_str;
        char            *port_str;
#ifndef _KERNEL
        char            *errchr;
#endif
        long            tmp_port = 0;
        sa_family_t     af;

        struct sockaddr_in      *sin;
        struct sockaddr_in6     *sin6;
        struct sockaddr_storage *sa = buf;

        if (!arg || !buf) {
                return (NULL);
        }

        bzero(buf, sizeof (struct sockaddr_storage));

        /* don't modify the passed-in string */
        (void) strlcpy(addrbuf, arg, sizeof (addrbuf));

        addr_str = addrbuf;

        if (*addr_str == '[') {
                /*
                 * An IPv6 address must be inside square brackets
                 */
                port_str = strchr(addr_str, ']');
                if (!port_str) {
                        /* No closing bracket */
                        return (NULL);
                }

                /* strip off the square brackets so we can convert */
                addr_str++;
                *port_str = '\0';
                port_str++;

                if (*port_str == ':') {
                        /* TCP port to follow */
                        port_str++;
                } else if (*port_str == '\0') {
                        /* No port specified */
                        port_str = NULL;
                } else {
                        /* malformed */
                        return (NULL);
                }
                af = AF_INET6;
        } else {
                port_str = strchr(addr_str, ':');
                if (port_str) {
                        *port_str = '\0';
                        port_str++;
                }
                af = AF_INET;
        }

        if (port_str) {
#if defined(_KERNEL)
                if (ddi_strtol(port_str, NULL, 10, &tmp_port) != 0) {
                        return (NULL);
                }
#else
                tmp_port = strtol(port_str, &errchr, 10);
#endif
                if (tmp_port < 0 || tmp_port > 65535) {
                        return (NULL);
                }
        } else {
                tmp_port = default_port;
        }

        sa->ss_family = af;

        sin = (struct sockaddr_in *)sa;
        if (af == AF_INET) {
                if (inet_pton(af, addr_str,
                    (void *)&(sin->sin_addr.s_addr)) != 1) {
                        return (NULL);
                }
                sin->sin_port = htons(tmp_port);
        } else {
                sin6 = (struct sockaddr_in6 *)sa;
                if (inet_pton(af, addr_str,
                    (void *)&(sin6->sin6_addr.s6_addr)) != 1) {
                        return (NULL);
                }
                sin6->sin6_port = htons(tmp_port);
        }

        /* successful */
        return (sa);
}


/*  Functions to convert iSCSI target structures to/from nvlists. */

#ifndef _KERNEL
int
it_config_to_nv(it_config_t *cfg, nvlist_t **nvl)
{
        int             ret;
        nvlist_t        *nv;
        nvlist_t        *lnv = NULL;

        if (!nvl) {
                return (EINVAL);
        }

        *nvl = NULL;

        ret = nvlist_alloc(&nv, NV_UNIQUE_NAME_TYPE, 0);
        if (ret != 0) {
                return (ret);
        }

        /* if there's no config, store an empty list */
        if (!cfg) {
                *nvl = nv;
                return (0);
        }

        ret = nvlist_add_uint32(nv, "cfgVersion", cfg->config_version);
        if (ret == 0) {
                ret = it_tgtlist_to_nv(cfg->config_tgt_list, &lnv);
        }

        if ((ret == 0) && (lnv != NULL)) {
                ret = nvlist_add_nvlist(nv, "targetList", lnv);
                nvlist_free(lnv);
                lnv = NULL;
        }

        if (ret == 0) {
                ret = it_tpglist_to_nv(cfg->config_tpg_list, &lnv);
        }

        if ((ret == 0) && (lnv != NULL)) {
                ret = nvlist_add_nvlist(nv, "tpgList", lnv);
                nvlist_free(lnv);
                lnv = NULL;
        }

        if (ret == 0) {
                ret = it_inilist_to_nv(cfg->config_ini_list, &lnv);
        }

        if ((ret == 0) && (lnv != NULL)) {
                ret = nvlist_add_nvlist(nv, "iniList", lnv);
                nvlist_free(lnv);
                lnv = NULL;
        }

        if (ret == 0) {
                ret = nvlist_add_nvlist(nv, "globalProperties",
                    cfg->config_global_properties);
        }

        if (ret == 0) {
                *nvl = nv;
        } else {
                nvlist_free(nv);
        }

        return (ret);
}
#endif /* !_KERNEL */

/*
 * nvlist version of config is 3 list-of-list, + 1 proplist.  arrays
 * are interesting, but lists-of-lists are more useful when doing
 * individual lookups when we later add support for it.  Also, no
 * need to store name in individual struct representation.
 */
int
it_nv_to_config(nvlist_t *nvl, it_config_t **cfg)
{
        int             ret;
        uint32_t        intval;
        nvlist_t        *listval;
        it_config_t     *tmpcfg;

        if (!cfg) {
                return (EINVAL);
        }

        /* initialize output */
        *cfg = NULL;

        tmpcfg = iscsit_zalloc(sizeof (it_config_t));
        if (tmpcfg == NULL) {
                return (ENOMEM);
        }

        if (!nvl) {
                /* nothing to decode, but return the empty cfg struct */
                ret = nvlist_alloc(&tmpcfg->config_global_properties,
                    NV_UNIQUE_NAME, 0);
                if (ret != 0) {
                        iscsit_free(tmpcfg, sizeof (it_config_t));
                        return (ret);
                }
                *cfg = tmpcfg;
                return (0);
        }

        ret = nvlist_lookup_uint32(nvl, "cfgVersion", &intval);
        if (ret != 0) {
                iscsit_free(tmpcfg, sizeof (it_config_t));
                return (ret);
        }

        tmpcfg->config_version = intval;

        ret = nvlist_lookup_nvlist(nvl, "targetList", &listval);
        if (ret == 0) {
                /* decode list of it_tgt_t */
                ret = it_nv_to_tgtlist(listval, &(tmpcfg->config_tgt_count),
                    &(tmpcfg->config_tgt_list));
        }

        ret = nvlist_lookup_nvlist(nvl, "tpgList", &listval);
        if (ret == 0) {
                /* decode list of it_tpg_t */
                ret = it_nv_to_tpglist(listval, &(tmpcfg->config_tpg_count),
                    &(tmpcfg->config_tpg_list));
        }

        ret = nvlist_lookup_nvlist(nvl, "iniList", &listval);
        if (ret == 0) {
                /* decode list of initiators */
                ret = it_nv_to_inilist(listval, &(tmpcfg->config_ini_count),
                    &(tmpcfg->config_ini_list));
        }

        ret = nvlist_lookup_nvlist(nvl, "globalProperties", &listval);
        if (ret == 0) {
                /*
                 * don't depend on the original nvlist staying in-scope,
                 * duplicate the nvlist
                 */
                ret = nvlist_dup(listval, &(tmpcfg->config_global_properties),
                    0);
        } else if (ret == ENOENT) {
                /*
                 * No global properties defined, make an empty list
                 */
                ret = nvlist_alloc(&tmpcfg->config_global_properties,
                    NV_UNIQUE_NAME, 0);
        }

        if (ret == 0) {
                char            **isnsArray = NULL;
                uint32_t        numisns = 0;

                /*
                 * decode the list of iSNS server information to make
                 * references from the kernel simpler.
                 */
                if (tmpcfg->config_global_properties) {
                        ret = nvlist_lookup_string_array(
                            tmpcfg->config_global_properties,
                            PROP_ISNS_SERVER,
                            &isnsArray, &numisns);
                        if (ret == 0) {
                                ret = it_array_to_portallist(isnsArray,
                                    numisns, ISNS_DEFAULT_SERVER_PORT,
                                    &tmpcfg->config_isns_svr_list,
                                    &tmpcfg->config_isns_svr_count);
                        } else if (ret == ENOENT) {
                                /* It's OK if we don't have any iSNS servers */
                                ret = 0;
                        }
                }
        }

        if (ret == 0) {
                *cfg = tmpcfg;
        } else {
                it_config_free_cmn(tmpcfg);
        }

        return (ret);
}

it_tgt_t *
it_tgt_lookup(it_config_t *cfg, char *tgt_name)
{
        it_tgt_t *cfg_tgt = NULL;

        for (cfg_tgt = cfg->config_tgt_list;
            cfg_tgt != NULL;
            cfg_tgt = cfg_tgt->tgt_next) {
                if (strncmp(cfg_tgt->tgt_name, tgt_name,
                    MAX_ISCSI_NODENAMELEN) == 0) {
                        return (cfg_tgt);
                }
        }

        return (NULL);
}

int
it_nv_to_tgtlist(nvlist_t *nvl, uint32_t *count, it_tgt_t **tgtlist)
{
        int             ret = 0;
        it_tgt_t        *tgt;
        it_tgt_t        *prev = NULL;
        nvpair_t        *nvp = NULL;
        nvlist_t        *nvt;
        char            *name;

        if (!tgtlist || !count) {
                return (EINVAL);
        }

        *tgtlist = NULL;
        *count = 0;

        if (!nvl) {
                /* nothing to do */
                return (0);
        }

        while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
                name = nvpair_name(nvp);

                ret = nvpair_value_nvlist(nvp, &nvt);
                if (ret != 0) {
                        /* invalid entry? */
                        continue;
                }

                ret = it_nv_to_tgt(nvt, name, &tgt);
                if (ret != 0) {
                        break;
                }

                (*count)++;

                if (*tgtlist == NULL) {
                        *tgtlist = tgt;
                } else {
                        prev->tgt_next = tgt;
                }
                prev = tgt;
        }

        if (ret != 0) {
                it_tgt_free_cmn(*tgtlist);
                *tgtlist = NULL;
        }

        return (ret);
}

int
it_tgtlist_to_nv(it_tgt_t *tgtlist, nvlist_t **nvl)
{
        int             ret;
        it_tgt_t        *tgtp = tgtlist;
        nvlist_t        *pnv = NULL;
        nvlist_t        *tnv;

        if (!nvl) {
                return (EINVAL);
        }

        if (!tgtlist) {
                /* nothing to do */
                return (0);
        }

        /* create the target list if required */
        if (*nvl == NULL) {
                ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
                if (ret != 0) {
                        return (ret);
                }
                *nvl = pnv;
        }

        while (tgtp) {
                ret = it_tgt_to_nv(tgtp, &tnv);

                if (ret != 0) {
                        break;
                }

                ret = nvlist_add_nvlist(*nvl, tgtp->tgt_name, tnv);

                if (ret != 0) {
                        break;
                }

                nvlist_free(tnv);

                tgtp = tgtp->tgt_next;
        }

        if (ret != 0) {
                if (pnv) {
                        nvlist_free(pnv);
                        *nvl = NULL;
                }
        }

        return (ret);
}

int
it_tgt_to_nv(it_tgt_t *tgt, nvlist_t **nvl)
{
        int             ret;
        nvlist_t        *tnv = NULL;

        if (!nvl) {
                return (EINVAL);
        }

        if (!tgt) {
                /* nothing to do */
                return (0);
        }

        ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
        if (ret != 0) {
                return (ret);
        }

        if (tgt->tgt_properties) {
                ret = nvlist_add_nvlist(*nvl, "properties",
                    tgt->tgt_properties);
        }

        if (ret == 0) {
                ret = nvlist_add_uint64(*nvl, "generation",
                    tgt->tgt_generation);
        }

        if (ret == 0) {
                ret = it_tpgtlist_to_nv(tgt->tgt_tpgt_list, &tnv);
        }

        if ((ret == 0) && tnv) {
                ret = nvlist_add_nvlist(*nvl, "tpgtList", tnv);
                nvlist_free(tnv);
        }

        if (ret != 0) {
                nvlist_free(*nvl);
                *nvl = NULL;
        }

        return (ret);
}

int
it_nv_to_tgt(nvlist_t *nvl, char *name, it_tgt_t **tgt)
{
        int             ret;
        it_tgt_t        *ttgt;
        nvlist_t        *listval;
        uint32_t        intval;

        if (!nvl || !tgt || !name) {
                return (EINVAL);
        }

        *tgt = NULL;

        ttgt = iscsit_zalloc(sizeof (it_tgt_t));
        if (!ttgt) {
                return (ENOMEM);
        }

        (void) strlcpy(ttgt->tgt_name, name, sizeof (ttgt->tgt_name));

        ret = nvlist_lookup_nvlist(nvl, "properties", &listval);
        if (ret == 0) {
                /* duplicate list so it does not go out of context */
                ret = nvlist_dup(listval, &(ttgt->tgt_properties), 0);
        } else if (ret == ENOENT) {
                ret = 0;
        }

        if (ret == 0) {
                ret = nvlist_lookup_uint64(nvl, "generation",
                    &(ttgt->tgt_generation));
        } else if (ret == ENOENT) {
                ret = 0;
        }

        if (ret == 0) {
                ret = nvlist_lookup_nvlist(nvl, "tpgtList", &listval);
        }

        if (ret == 0) {
                ret = it_nv_to_tpgtlist(listval, &intval,
                    &(ttgt->tgt_tpgt_list));
                ttgt->tgt_tpgt_count = intval;
        } else if (ret == ENOENT) {
                ret = 0;
        }

        if (ret == 0) {
                *tgt = ttgt;
        } else {
                it_tgt_free_cmn(ttgt);
        }

        return (ret);
}

int
it_tpgt_to_nv(it_tpgt_t *tpgt, nvlist_t **nvl)
{
        int             ret;

        if (!nvl) {
                return (EINVAL);
        }

        if (!tpgt) {
                /* nothing to do */
                return (0);
        }

        ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
        if (ret != 0) {
                return (ret);
        }

        ret = nvlist_add_uint16(*nvl, "tag", tpgt->tpgt_tag);
        if (ret == 0) {
                ret = nvlist_add_uint64(*nvl, "generation",
                    tpgt->tpgt_generation);
        }

        if (ret != 0) {
                nvlist_free(*nvl);
                *nvl = NULL;
        }

        return (ret);
}

int
it_nv_to_tpgt(nvlist_t *nvl, char *name, it_tpgt_t **tpgt)
{
        int             ret;
        it_tpgt_t       *ptr;

        if (!tpgt || !name) {
                return (EINVAL);
        }

        *tpgt = NULL;

        if (!nvl) {
                return (0);
        }

        ptr = iscsit_zalloc(sizeof (it_tpgt_t));
        if (!ptr) {
                return (ENOMEM);
        }

        (void) strlcpy(ptr->tpgt_tpg_name, name, sizeof (ptr->tpgt_tpg_name));

        ret = nvlist_lookup_uint16(nvl, "tag", &(ptr->tpgt_tag));
        if (ret == 0) {
                ret = nvlist_lookup_uint64(nvl, "generation",
                    &(ptr->tpgt_generation));
        }

        if (ret == 0) {
                *tpgt = ptr;
        } else {
                iscsit_free(ptr, sizeof (it_tpgt_t));
        }

        return (ret);
}

int
it_tpgtlist_to_nv(it_tpgt_t *tpgtlist, nvlist_t **nvl)
{
        int             ret;
        nvlist_t        *pnv = NULL;
        nvlist_t        *tnv;
        it_tpgt_t       *ptr = tpgtlist;

        if (!nvl) {
                return (EINVAL);
        }

        if (!tpgtlist) {
                /* nothing to do */
                return (0);
        }

        /* create the target list if required */
        if (*nvl == NULL) {
                ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
                if (ret != 0) {
                        return (ret);
                }
                *nvl = pnv;
        }

        while (ptr) {
                ret = it_tpgt_to_nv(ptr, &tnv);

                if (ret != 0) {
                        break;
                }

                ret = nvlist_add_nvlist(*nvl, ptr->tpgt_tpg_name, tnv);

                if (ret != 0) {
                        break;
                }

                nvlist_free(tnv);

                ptr = ptr->tpgt_next;
        }

        if (ret != 0) {
                if (pnv) {
                        nvlist_free(pnv);
                        *nvl = NULL;
                }
        }

        return (ret);
}

int
it_nv_to_tpgtlist(nvlist_t *nvl, uint32_t *count, it_tpgt_t **tpgtlist)
{
        int             ret = 0;
        it_tpgt_t       *tpgt;
        it_tpgt_t       *prev = NULL;
        nvpair_t        *nvp = NULL;
        nvlist_t        *nvt;
        char            *name;

        if (!tpgtlist || !count) {
                return (EINVAL);
        }

        *tpgtlist = NULL;
        *count = 0;

        if (!nvl) {
                /* nothing to do */
                return (0);
        }

        while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
                name = nvpair_name(nvp);

                ret = nvpair_value_nvlist(nvp, &nvt);
                if (ret != 0) {
                        /* invalid entry? */
                        continue;
                }

                ret = it_nv_to_tpgt(nvt, name, &tpgt);
                if (ret != 0) {
                        break;
                }

                (*count)++;

                if (*tpgtlist == NULL) {
                        *tpgtlist = tpgt;
                } else {
                        prev->tpgt_next = tpgt;
                }

                prev = tpgt;
        }

        if (ret != 0) {
                it_tpgt_free_cmn(*tpgtlist);
                *tpgtlist = NULL;
        }

        return (ret);
}

#ifndef _KERNEL
int
it_tpg_to_nv(it_tpg_t *tpg, nvlist_t **nvl)
{
        int             ret;
        char            **portalArray = NULL;
        int             i;
        it_portal_t     *ptr;

        if (!nvl) {
                return (EINVAL);
        }

        if (!tpg) {
                /* nothing to do */
                return (0);
        }

        ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
        if (ret != 0) {
                return (ret);
        }

        ret = nvlist_add_uint64(*nvl, "generation", tpg->tpg_generation);

        if ((ret == 0) && tpg->tpg_portal_list) {
                /* add the portals */
                portalArray = iscsit_zalloc(tpg->tpg_portal_count *
                    sizeof (it_portal_t));
                if (portalArray == NULL) {
                        nvlist_free(*nvl);
                        *nvl = NULL;
                        return (ENOMEM);
                }

                i = 0;
                ptr = tpg->tpg_portal_list;

                while (ptr && (i < tpg->tpg_portal_count)) {
                        ret = sockaddr_to_str(&(ptr->portal_addr),
                            &(portalArray[i]));
                        if (ret != 0) {
                                break;
                        }
                        ptr = ptr->portal_next;
                        i++;
                }
        }

        if ((ret == 0) && portalArray) {
                ret = nvlist_add_string_array(*nvl, "portalList",
                    portalArray, i);
        }


        if (portalArray) {
                while (--i >= 0) {
                        if (portalArray[i]) {
                                iscsit_free(portalArray[i],
                                    strlen(portalArray[i] + 1));
                        }
                }
                iscsit_free(portalArray,
                    tpg->tpg_portal_count * sizeof (it_portal_t));
        }

        if (ret != 0) {
                nvlist_free(*nvl);
                *nvl = NULL;
        }

        return (ret);
}
#endif /* !_KERNEL */

int
it_nv_to_tpg(nvlist_t *nvl, char *name, it_tpg_t **tpg)
{
        int             ret;
        it_tpg_t        *ptpg;
        char            **portalArray = NULL;
        uint32_t        count = 0;

        if (!name || !tpg) {
                return (EINVAL);
        }

        *tpg = NULL;

        ptpg = iscsit_zalloc(sizeof (it_tpg_t));
        if (ptpg == NULL) {
                return (ENOMEM);
        }

        (void) strlcpy(ptpg->tpg_name, name, sizeof (ptpg->tpg_name));

        ret = nvlist_lookup_uint64(nvl, "generation",
            &(ptpg->tpg_generation));

        if (ret == 0) {
                ret = nvlist_lookup_string_array(nvl, "portalList",
                    &portalArray, &count);
        }

        if (ret == 0) {
                /* set the portals */
                ret = it_array_to_portallist(portalArray, count,
                    ISCSI_LISTEN_PORT, &ptpg->tpg_portal_list,
                    &ptpg->tpg_portal_count);
        } else if (ret == ENOENT) {
                ret = 0;
        }

        if (ret == 0) {
                *tpg = ptpg;
        } else {
                it_tpg_free_cmn(ptpg);
        }

        return (ret);
}




#ifndef _KERNEL
int
it_tpglist_to_nv(it_tpg_t *tpglist, nvlist_t **nvl)
{
        int             ret;
        nvlist_t        *pnv = NULL;
        nvlist_t        *tnv;
        it_tpg_t        *ptr = tpglist;

        if (!nvl) {
                return (EINVAL);
        }

        if (!tpglist) {
                /* nothing to do */
                return (0);
        }

        /* create the target portal group list if required */
        if (*nvl == NULL) {
                ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
                if (ret != 0) {
                        return (ret);
                }
                *nvl = pnv;
        }

        while (ptr) {
                ret = it_tpg_to_nv(ptr, &tnv);

                if (ret != 0) {
                        break;
                }

                ret = nvlist_add_nvlist(*nvl, ptr->tpg_name, tnv);

                if (ret != 0) {
                        break;
                }

                nvlist_free(tnv);

                ptr = ptr->tpg_next;
        }

        if (ret != 0) {
                if (pnv) {
                        nvlist_free(pnv);
                        *nvl = NULL;
                }
        }

        return (ret);
}
#endif /* !_KERNEL */

it_tpg_t *
it_tpg_lookup(it_config_t *cfg, char *tpg_name)
{
        it_tpg_t *cfg_tpg = NULL;

        for (cfg_tpg = cfg->config_tpg_list;
            cfg_tpg != NULL;
            cfg_tpg = cfg_tpg->tpg_next) {
                if (strncmp(&cfg_tpg->tpg_name[0], tpg_name,
                    MAX_TPG_NAMELEN) == 0) {
                        return (cfg_tpg);
                }
        }

        return (NULL);
}

int
it_sa_compare(struct sockaddr_storage *sa1, struct sockaddr_storage *sa2)
{
        struct sockaddr_in      *sin1, *sin2;
        struct sockaddr_in6     *sin6_1, *sin6_2;

        /*
         * XXX - should we check here for IPv4 addrs mapped to v6?
         * see also iscsit_is_v4_mapped in iscsit_login.c
         */

        if (sa1->ss_family != sa2->ss_family) {
                return (1);
        }

        /*
         * sockaddr_in has padding which may not be initialized.
         * be more specific in the comparison, and don't trust the
         * caller has fully initialized the structure.
         */
        if (sa1->ss_family == AF_INET) {
                sin1 = (struct sockaddr_in *)sa1;
                sin2 = (struct sockaddr_in *)sa2;
                if ((bcmp(&sin1->sin_addr, &sin2->sin_addr,
                    sizeof (struct in_addr)) == 0) &&
                    (sin1->sin_port == sin2->sin_port)) {
                        return (0);
                }
        } else if (sa1->ss_family == AF_INET6) {
                sin6_1 = (struct sockaddr_in6 *)sa1;
                sin6_2 = (struct sockaddr_in6 *)sa2;
                if (bcmp(sin6_1, sin6_2, sizeof (struct sockaddr_in6)) == 0) {
                        return (0);
                }
        }

        return (1);
}

it_portal_t *
it_portal_lookup(it_tpg_t *tpg, struct sockaddr_storage *sa)
{
        it_portal_t *cfg_portal;

        for (cfg_portal = tpg->tpg_portal_list;
            cfg_portal != NULL;
            cfg_portal = cfg_portal->portal_next) {
                if (it_sa_compare(sa, &cfg_portal->portal_addr) == 0)
                        return (cfg_portal);
        }

        return (NULL);
}

it_portal_t *
it_sns_svr_lookup(it_config_t *cfg, struct sockaddr_storage *sa)
{
        it_portal_t *cfg_portal;

        for (cfg_portal = cfg->config_isns_svr_list;
            cfg_portal != NULL;
            cfg_portal = cfg_portal->portal_next) {
                if (it_sa_compare(sa, &cfg_portal->portal_addr) == 0)
                        return (cfg_portal);
        }

        return (NULL);
}

int
it_nv_to_tpglist(nvlist_t *nvl, uint32_t *count, it_tpg_t **tpglist)
{
        int             ret = 0;
        it_tpg_t        *tpg;
        it_tpg_t        *prev = NULL;
        nvpair_t        *nvp = NULL;
        nvlist_t        *nvt;
        char            *name;

        if (!tpglist || !count) {
                return (EINVAL);
        }

        *tpglist = NULL;
        *count = 0;

        if (!nvl) {
                /* nothing to do */
                return (0);
        }

        while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
                name = nvpair_name(nvp);

                ret = nvpair_value_nvlist(nvp, &nvt);
                if (ret != 0) {
                        /* invalid entry? */
                        continue;
                }

                ret = it_nv_to_tpg(nvt, name, &tpg);
                if (ret != 0) {
                        break;
                }

                (*count)++;

                if (*tpglist == NULL) {
                        *tpglist = tpg;
                } else {
                        prev->tpg_next = tpg;
                }
                prev = tpg;
        }

        if (ret != 0) {
                it_tpg_free_cmn(*tpglist);
                *tpglist = NULL;
        }

        return (ret);
}

int
it_ini_to_nv(it_ini_t *ini, nvlist_t **nvl)
{
        int             ret;

        if (!nvl) {
                return (EINVAL);
        }

        if (!ini) {
                return (0);
        }

        ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
        if (ret != 0) {
                return (ret);
        }

        if (ini->ini_properties) {
                ret = nvlist_add_nvlist(*nvl, "properties",
                    ini->ini_properties);
        }

        if (ret == 0) {
                ret = nvlist_add_uint64(*nvl, "generation",
                    ini->ini_generation);
        } else if (ret == ENOENT) {
                ret = 0;
        }

        if (ret != 0) {
                nvlist_free(*nvl);
                *nvl = NULL;
        }

        return (ret);
}

int
it_nv_to_ini(nvlist_t *nvl, char *name, it_ini_t **ini)
{
        int             ret;
        it_ini_t        *inip;
        nvlist_t        *listval;

        if (!name || !ini) {
                return (EINVAL);
        }

        *ini = NULL;

        if (!nvl) {
                return (0);
        }

        inip = iscsit_zalloc(sizeof (it_ini_t));
        if (!inip) {
                return (ENOMEM);
        }

        (void) strlcpy(inip->ini_name, name, sizeof (inip->ini_name));

        ret = nvlist_lookup_nvlist(nvl, "properties", &listval);
        if (ret == 0) {
                ret = nvlist_dup(listval, &(inip->ini_properties), 0);
        } else if (ret == ENOENT) {
                ret = 0;
        }

        if (ret == 0) {
                ret = nvlist_lookup_uint64(nvl, "generation",
                    &(inip->ini_generation));
        }

        if (ret == 0) {
                *ini = inip;
        } else {
                it_ini_free_cmn(inip);
        }

        return (ret);
}

int
it_inilist_to_nv(it_ini_t *inilist, nvlist_t **nvl)
{
        int             ret;
        nvlist_t        *pnv = NULL;
        nvlist_t        *tnv;
        it_ini_t        *ptr = inilist;

        if (!nvl) {
                return (EINVAL);
        }

        if (!inilist) {
                return (0);
        }

        /* create the target list if required */
        if (*nvl == NULL) {
                ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
                if (ret != 0) {
                        return (ret);
                }
                *nvl = pnv;
        }

        while (ptr) {
                ret = it_ini_to_nv(ptr, &tnv);

                if (ret != 0) {
                        break;
                }

                ret = nvlist_add_nvlist(*nvl, ptr->ini_name, tnv);

                if (ret != 0) {
                        break;
                }

                nvlist_free(tnv);

                ptr = ptr->ini_next;
        }

        if (ret != 0) {
                if (pnv) {
                        nvlist_free(pnv);
                        *nvl = NULL;
                }
        }

        return (ret);
}

int
it_nv_to_inilist(nvlist_t *nvl, uint32_t *count, it_ini_t **inilist)
{
        int             ret = 0;
        it_ini_t        *inip;
        it_ini_t        *prev = NULL;
        nvpair_t        *nvp = NULL;
        nvlist_t        *nvt;
        char            *name;

        if (!inilist || !count) {
                return (EINVAL);
        }

        *inilist = NULL;
        *count = 0;

        if (!nvl) {
                /* nothing to do */
                return (0);
        }

        while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
                name = nvpair_name(nvp);

                ret = nvpair_value_nvlist(nvp, &nvt);
                if (ret != 0) {
                        /* invalid entry? */
                        continue;
                }

                ret = it_nv_to_ini(nvt, name, &inip);
                if (ret != 0) {
                        break;
                }

                (*count)++;

                if (*inilist == NULL) {
                        *inilist = inip;
                } else {
                        prev->ini_next = inip;
                }
                prev = inip;
        }

        if (ret != 0) {
                it_ini_free_cmn(*inilist);
                *inilist = NULL;
        }

        return (ret);
}

/*
 * Convert a sockaddr to the string representation, suitable for
 * storing in an nvlist or printing out in a list.
 */
#ifndef _KERNEL
int
sockaddr_to_str(struct sockaddr_storage *sa, char **addr)
{
        int                     ret;
        char                    buf[INET6_ADDRSTRLEN + 7]; /* addr : port */
        char                    pbuf[7];
        const char              *bufp;
        struct sockaddr_in      *sin;
        struct sockaddr_in6     *sin6;
        uint16_t                port;

        if (!sa || !addr) {
                return (EINVAL);
        }

        buf[0] = '\0';

        if (sa->ss_family == AF_INET) {
                sin = (struct sockaddr_in *)sa;
                bufp = inet_ntop(AF_INET,
                    (const void *)&(sin->sin_addr.s_addr),
                    buf, sizeof (buf));
                if (bufp == NULL) {
                        ret = errno;
                        return (ret);
                }
                port = ntohs(sin->sin_port);
        } else if (sa->ss_family == AF_INET6) {
                (void) strlcat(buf, "[", sizeof (buf));
                sin6 = (struct sockaddr_in6 *)sa;
                bufp = inet_ntop(AF_INET6,
                    (const void *)&sin6->sin6_addr.s6_addr,
                    &buf[1], (sizeof (buf) - 1));
                if (bufp == NULL) {
                        ret = errno;
                        return (ret);
                }
                (void) strlcat(buf, "]", sizeof (buf));
                port = ntohs(sin6->sin6_port);
        } else {
                return (EINVAL);
        }


        (void) snprintf(pbuf, sizeof (pbuf), ":%u", port);
        (void) strlcat(buf, pbuf, sizeof (buf));

        *addr = strdup(buf);
        if (*addr == NULL) {
                return (ENOMEM);
        }

        return (0);
}
#endif /* !_KERNEL */

int
it_array_to_portallist(char **arr, uint32_t count, uint32_t default_port,
    it_portal_t **portallist, uint32_t *list_count)
{
        int             ret = 0;
        int             i;
        it_portal_t     *portal;
        it_portal_t     *prev = NULL;
        it_portal_t     *tmp;

        if (!arr || !portallist || !list_count) {
                return (EINVAL);
        }

        *list_count = 0;
        *portallist = NULL;

        for (i = 0; i < count; i++) {
                if (!arr[i]) {
                        /* should never happen */
                        continue;
                }
                portal = iscsit_zalloc(sizeof (it_portal_t));
                if (!portal) {
                        ret = ENOMEM;
                        break;
                }
                if (it_common_convert_sa(arr[i],
                    &(portal->portal_addr), default_port) == NULL) {
                        iscsit_free(portal, sizeof (it_portal_t));
                        ret = EINVAL;
                        break;
                }

                /* make sure no duplicates */
                tmp = *portallist;
                while (tmp) {
                        if (it_sa_compare(&(tmp->portal_addr),
                            &(portal->portal_addr)) == 0) {
                                iscsit_free(portal, sizeof (it_portal_t));
                                portal = NULL;
                                break;
                        }
                        tmp = tmp->portal_next;
                }

                if (!portal) {
                        continue;
                }

                /*
                 * The first time through the loop, *portallist == NULL
                 * because we assigned it to NULL above.  Subsequently
                 * prev will have been set.  Therefor it's OK to put
                 * lint override before prev->portal_next assignment.
                 */
                if (*portallist == NULL) {
                        *portallist = portal;
                } else {
                        prev->portal_next = portal;
                }

                prev = portal;
                (*list_count)++;
        }

        return (ret);
}

/*
 * Function:  it_config_free_cmn()
 *
 * Free any resources associated with the it_config_t structure.
 *
 * Parameters:
 *    cfg       A C representation of the current iSCSI configuration
 */
void
it_config_free_cmn(it_config_t *cfg)
{
        if (!cfg) {
                return;
        }

        if (cfg->config_tgt_list) {
                it_tgt_free_cmn(cfg->config_tgt_list);
        }

        if (cfg->config_tpg_list) {
                it_tpg_free_cmn(cfg->config_tpg_list);
        }

        if (cfg->config_ini_list) {
                it_ini_free_cmn(cfg->config_ini_list);
        }

        if (cfg->config_global_properties) {
                nvlist_free(cfg->config_global_properties);
        }

        if (cfg->config_isns_svr_list) {
                it_portal_t     *pp = cfg->config_isns_svr_list;
                it_portal_t     *pp_next;

                while (pp) {
                        pp_next = pp->portal_next;
                        iscsit_free(pp, sizeof (it_portal_t));
                        pp = pp_next;
                }
        }

        iscsit_free(cfg, sizeof (it_config_t));
}

/*
 * Function:  it_tgt_free_cmn()
 *
 * Frees an it_tgt_t structure.  If tgt_next is not NULL, frees
 * all structures in the list.
 */
void
it_tgt_free_cmn(it_tgt_t *tgt)
{
        it_tgt_t        *tgtp = tgt;
        it_tgt_t        *next;

        if (!tgt) {
                return;
        }

        while (tgtp) {
                next = tgtp->tgt_next;

                if (tgtp->tgt_tpgt_list) {
                        it_tpgt_free_cmn(tgtp->tgt_tpgt_list);
                }

                if (tgtp->tgt_properties) {
                        nvlist_free(tgtp->tgt_properties);
                }

                iscsit_free(tgtp, sizeof (it_tgt_t));

                tgtp = next;
        }
}

/*
 * Function:  it_tpgt_free_cmn()
 *
 * Deallocates resources of an it_tpgt_t structure.  If tpgt->next
 * is not NULL, frees all members of the list.
 */
void
it_tpgt_free_cmn(it_tpgt_t *tpgt)
{
        it_tpgt_t       *tpgtp = tpgt;
        it_tpgt_t       *next;

        if (!tpgt) {
                return;
        }

        while (tpgtp) {
                next = tpgtp->tpgt_next;

                iscsit_free(tpgtp, sizeof (it_tpgt_t));

                tpgtp = next;
        }
}

/*
 * Function:  it_tpg_free_cmn()
 *
 * Deallocates resources associated with an it_tpg_t structure.
 * If tpg->next is not NULL, frees all members of the list.
 */
void
it_tpg_free_cmn(it_tpg_t *tpg)
{
        it_tpg_t        *tpgp = tpg;
        it_tpg_t        *next;
        it_portal_t     *portalp;
        it_portal_t     *pnext;

        while (tpgp) {
                next = tpgp->tpg_next;

                portalp = tpgp->tpg_portal_list;

                while (portalp) {
                        pnext = portalp->portal_next;
                        iscsit_free(portalp, sizeof (it_portal_t));
                        portalp = pnext;
                }

                iscsit_free(tpgp, sizeof (it_tpg_t));

                tpgp = next;
        }
}

/*
 * Function:  it_ini_free_cmn()
 *
 * Deallocates resources of an it_ini_t structure. If ini->next is
 * not NULL, frees all members of the list.
 */
void
it_ini_free_cmn(it_ini_t *ini)
{
        it_ini_t        *inip = ini;
        it_ini_t        *next;

        if (!ini) {
                return;
        }

        while (inip) {
                next = inip->ini_next;

                if (inip->ini_properties) {
                        nvlist_free(inip->ini_properties);
                }

                iscsit_free(inip, sizeof (it_ini_t));

                inip = next;
        }
}