root/usr/src/uts/common/io/scsi/adapters/iscsi/iscsi_ioctl.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.
 * iSCSI Software Initiator
 */

/*
 * Framework interface routines for iSCSI
 */
#include "iscsi.h"                              /* main header */
#include <sys/idm/idm_text.h>                   /* main header */
#include <sys/iscsi_protocol.h>                 /* protocol structs */
#include <sys/scsi/adapters/iscsi_if.h>         /* ioctl interfaces */
#include "persistent.h"
#include <sys/scsi/adapters/iscsi_door.h>
#include "iscsi_targetparam.h"
#include <sys/strsubr.h>
#include <sys/socketvar.h>
#include <sys/bootprops.h>

extern ib_boot_prop_t   *iscsiboot_prop;

static iscsi_status_t iscsi_create_sendtgts_list(iscsi_conn_t *icp,
    char *data, int data_len, iscsi_sendtgts_list_t *stl);

/*
 * iscsi_ioctl_copyin -
 */
void *
iscsi_ioctl_copyin(caddr_t arg, int mode, size_t size)
{
        void    *data = NULL;

        ASSERT(arg != NULL);
        ASSERT(size != 0);

        data = kmem_alloc(size, KM_SLEEP);

        if (ddi_copyin(arg, data, size, mode) != 0) {
                kmem_free(data, size);
                data = NULL;
        }
        return (data);
}

/*
 * iscsi_ioctl_copyout -
 */
int
iscsi_ioctl_copyout(void *data, size_t size, caddr_t arg, int mode)
{
        int     rtn;

        rtn = EFAULT;
        if (ddi_copyout(data, arg, size, mode) == 0) {
                rtn = 0;
        }
        kmem_free(data, size);
        return (rtn);
}

/*
 * iscsi_conn_list_get_copyin -
 */
iscsi_conn_list_t *
iscsi_ioctl_conn_oid_list_get_copyin(caddr_t arg, int mode)
{
        iscsi_conn_list_t       *cl_tmp;
        iscsi_conn_list_t       *cl = NULL;
        size_t                  alloc_len;

        ASSERT(arg != NULL);

        cl_tmp = (iscsi_conn_list_t *)kmem_zalloc(sizeof (*cl_tmp), KM_SLEEP);

        if (ddi_copyin(arg, cl_tmp, sizeof (*cl_tmp), mode) == 0) {

                if (cl_tmp->cl_vers == ISCSI_INTERFACE_VERSION) {
                        alloc_len = sizeof (*cl);
                        if (cl_tmp->cl_in_cnt != 0) {
                                alloc_len += ((cl_tmp->cl_in_cnt - 1) *
                                    sizeof (iscsi_if_conn_t));
                        }

                        cl = (iscsi_conn_list_t *)kmem_zalloc(alloc_len,
                            KM_SLEEP);
                        bcopy(cl_tmp, cl, sizeof (*cl_tmp));
                }
        }
        kmem_free(cl_tmp, sizeof (*cl_tmp));
        return (cl);
}

/*
 * iscsi_conn_list_get_copyout -
 */
int
iscsi_ioctl_conn_oid_list_get_copyout(iscsi_conn_list_t *cl, caddr_t arg,
    int mode)
{
        size_t                  alloc_len;
        int                     rtn;

        ASSERT(cl != NULL);
        ASSERT(arg != NULL);

        rtn = EFAULT;
        alloc_len = sizeof (*cl);
        if (cl->cl_in_cnt != 0) {
                alloc_len += ((cl->cl_in_cnt - 1) * sizeof (iscsi_if_conn_t));
        }

        if (ddi_copyout(cl, arg, alloc_len, mode) == 0) {
                rtn = 0;
        }
        kmem_free(cl, alloc_len);
        return (rtn);
}

/*
 * iscsi_conn_oid_list_get -
 */
boolean_t
iscsi_ioctl_conn_oid_list_get(iscsi_hba_t *ihp, iscsi_conn_list_t *cl)
{
        iscsi_sess_t            *isp;
        iscsi_conn_t            *icp;
        iscsi_if_conn_t         *cnx;
        uint32_t                target_oid;

        /* Let's check the version. */
        if (cl->cl_vers != ISCSI_INTERFACE_VERSION) {
                return (B_FALSE);
        }

        /* We preinitialize the output connection counter. */
        cl->cl_out_cnt = 0;

        /* The list of sessions is walked holding the HBA mutex. */
        rw_enter(&ihp->hba_sess_list_rwlock, RW_READER);
        isp = ihp->hba_sess_list;

        /*
         * Check to see if oid references a target-param oid.  If so,
         * find the associated  session oid before getting lu list.
         */
        if (iscsi_targetparam_get_name(cl->cl_sess_oid) != NULL) {
                for (isp = ihp->hba_sess_list; isp; isp = isp->sess_next) {
                        if (isp->sess_target_oid == cl->cl_sess_oid) {
                                target_oid  = isp->sess_oid;
                                break;
                        }
                }
        } else {
                target_oid = cl->cl_sess_oid;
        }

        while (isp != NULL) {
                ASSERT(isp->sess_sig == ISCSI_SIG_SESS);

                /* return connections for NORMAL sessions only */
                if ((isp->sess_type == ISCSI_SESS_TYPE_NORMAL) &&
                    ((cl->cl_all_sess == B_TRUE) ||
                    (target_oid == isp->sess_oid))) {
                        /*
                         * The list of connections is walked holding
                         * the session mutex.
                         */
                        rw_enter(&isp->sess_conn_list_rwlock, RW_READER);
                        icp = isp->sess_conn_list;

                        while (icp != NULL) {
                                ASSERT(icp->conn_sig == ISCSI_SIG_CONN);

                                if (icp->conn_state ==
                                    ISCSI_CONN_STATE_LOGGED_IN) {

                                        if (cl->cl_out_cnt < cl->cl_in_cnt) {
                                                /* There's still room. */
                                                cnx =
                                                    &cl->cl_list[
                                                    cl->cl_out_cnt];

                                                bzero(cnx, sizeof (*cnx));

                                                cnx->c_cid = icp->conn_cid;
                                                cnx->c_oid = icp->conn_oid;
                                                cnx->c_sess_oid = isp->sess_oid;
                                        }
                                        ++cl->cl_out_cnt;
                                }
                                icp = icp->conn_next;
                        }
                        rw_exit(&isp->sess_conn_list_rwlock);

                        if (cl->cl_all_sess == B_FALSE) {
                                /*
                                 * We got here because it was the only session
                                 * we were looking for.  We can exit now.
                                 */
                                break;
                        }
                }
                isp = isp->sess_next;
        }
        rw_exit(&ihp->hba_sess_list_rwlock);
        return (B_TRUE);
}

/*
 * iscsi_ioctl_conn_props_get -
 */
boolean_t
iscsi_ioctl_conn_props_get(iscsi_hba_t *ihp, iscsi_conn_props_t *cp)
{
        iscsi_sess_t            *isp;
        iscsi_conn_t            *icp;
        boolean_t               rtn;
        idm_conn_t              *idm_conn;

        /* Let's check the version. */
        if (cp->cp_vers != ISCSI_INTERFACE_VERSION) {
                return (B_FALSE);
        }

        /* Let's find the session. */
        rw_enter(&ihp->hba_sess_list_rwlock, RW_READER);
        if (iscsi_sess_get(cp->cp_sess_oid, ihp, &isp) != 0) {
                rw_exit(&ihp->hba_sess_list_rwlock);
                return (B_FALSE);
        }

        ASSERT(isp->sess_sig == ISCSI_SIG_SESS);

        rtn = B_FALSE;

        rw_enter(&isp->sess_conn_list_rwlock, RW_READER);
        icp = isp->sess_conn_list;
        cp->cp_params_valid = B_FALSE;

        while (icp != NULL) {

                ASSERT(icp->conn_sig == ISCSI_SIG_CONN);

                if (icp->conn_oid == cp->cp_oid) {
                        struct sockaddr_storage *sal;
                        struct sockaddr_storage *sar;

                        idm_conn =
                            (idm_conn_t *)icp->conn_ic;

                        sal = &idm_conn->ic_laddr;
                        sar = &idm_conn->ic_raddr;

                        /* Local Address */
                        if (sal->ss_family == AF_INET) {
                                bcopy(&idm_conn->ic_laddr,
                                    &cp->cp_local,
                                    sizeof (struct sockaddr_in));
                        } else {
                                bcopy(&idm_conn->ic_laddr,
                                    &cp->cp_local,
                                    sizeof (struct sockaddr_in6));
                        }

                        /* Peer Address */
                        if (sar->ss_family == AF_INET) {
                                bcopy(&idm_conn->ic_raddr,
                                    &cp->cp_peer,
                                    sizeof (struct sockaddr_in));
                        } else {
                                bcopy(&idm_conn->ic_raddr,
                                    &cp->cp_peer,
                                    sizeof (struct sockaddr_in6));
                        }

                        if (icp->conn_state == ISCSI_CONN_STATE_LOGGED_IN) {
                                cp->cp_params_valid = B_TRUE;
                                bcopy(&icp->conn_params, &cp->cp_params,
                                    sizeof (icp->conn_params));
                        }

                        rtn = B_TRUE;
                        break;
                }
                icp = icp->conn_next;
        }
        rw_exit(&isp->sess_conn_list_rwlock);
        rw_exit(&ihp->hba_sess_list_rwlock);
        return (rtn);
}


/*
 * iscsi_ioctl_sendtgts_get - 0 on success; errno on failure
 *
 */
int
iscsi_ioctl_sendtgts_get(iscsi_hba_t *ihp, iscsi_sendtgts_list_t *stl)
{
#define ISCSI_SENDTGTS_REQ_STR          "SendTargets=All"

        int                     rtn = EFAULT;
        iscsi_status_t          status;
        iscsi_sess_t            *isp;
        iscsi_conn_t            *icp;
        uint32_t                oid;
        char                    *data;
        uint32_t                data_len;
        uint32_t                rx_data_len;
        iscsi_sockaddr_t        addr_snd;

        ASSERT(ihp != NULL);
        ASSERT(stl != NULL);

        iscsid_addr_to_sockaddr(stl->stl_entry.e_insize,
            &stl->stl_entry.e_u, stl->stl_entry.e_port,
            &addr_snd.sin);

        /* create discovery session */
        rw_enter(&ihp->hba_sess_list_rwlock, RW_WRITER);
        isp = iscsi_sess_create(ihp, iSCSIDiscoveryMethodSendTargets,
            NULL, SENDTARGETS_DISCOVERY, ISCSI_DEFAULT_TPGT,
            ISCSI_SUN_ISID_5, ISCSI_SESS_TYPE_DISCOVERY, &oid);
        if (isp == NULL) {
                rw_exit(&ihp->hba_sess_list_rwlock);
                return (1);
        }

        /* create connection */
        rw_enter(&isp->sess_conn_list_rwlock, RW_WRITER);
        status = iscsi_conn_create(&addr_snd.sin, isp, &icp);
        rw_exit(&isp->sess_conn_list_rwlock);

        if (!ISCSI_SUCCESS(status)) {
                (void) iscsi_sess_destroy(isp);
                rw_exit(&ihp->hba_sess_list_rwlock);
                return (1);
        }
        rw_exit(&ihp->hba_sess_list_rwlock);

        /* start login */
        mutex_enter(&icp->conn_state_mutex);
        status = iscsi_conn_online(icp);
        mutex_exit(&icp->conn_state_mutex);

        if (status == ISCSI_STATUS_SUCCESS) {
                data_len = icp->conn_params.max_xmit_data_seg_len;
retry_sendtgts:
                /* alloc/init buffer for SendTargets req/resp */
                data = kmem_zalloc(data_len, KM_SLEEP);
                bcopy(ISCSI_SENDTGTS_REQ_STR, data,
                    sizeof (ISCSI_SENDTGTS_REQ_STR));

                /* execute SendTargets operation */
                status = iscsi_handle_text(icp, data, data_len,
                    sizeof (ISCSI_SENDTGTS_REQ_STR), &rx_data_len);

                /* check if allocated buffer is too small for response */
                if (status == ISCSI_STATUS_DATA_OVERFLOW) {
                        kmem_free(data, data_len);
                        data_len = rx_data_len;
                        goto retry_sendtgts;
                }

                if (ISCSI_SUCCESS(status)) {
                        status = iscsi_create_sendtgts_list(icp, data,
                            rx_data_len, stl);
                        if (ISCSI_SUCCESS(status)) {
                                rtn = 0;
                        }
                } else {
                        rtn = EFAULT;
                }

                kmem_free(data, data_len);
        } else {
                rtn = EFAULT;
        }

        /*
         * check if session is still alive.  It may have been destroyed
         * by a driver unload
         */
        rw_enter(&ihp->hba_sess_list_rwlock, RW_WRITER);
        if (iscsi_sess_get(oid, ihp, &isp) == 0) {
                (void) iscsi_sess_destroy(isp);
        }
        rw_exit(&ihp->hba_sess_list_rwlock);

        return (rtn);
}


/*
 * iscsi_create_sendtgts_list -  Based upon the given data, build a
 * linked list of SendTarget information.  The data passed into this
 * function  is expected to be the data portion(s) of SendTarget text
 * response.
 */
static iscsi_status_t
iscsi_create_sendtgts_list(iscsi_conn_t *icp, char *data, int data_len,
    iscsi_sendtgts_list_t *stl)
{
        char                    *line = NULL;
        boolean_t               targetname_added = B_FALSE;
        iscsi_sendtgts_entry_t  *curr_ste = NULL,
            *prev_ste = NULL;
        struct hostent          *hptr;
        int                     error_num;

        /* initialize number of targets found */
        stl->stl_out_cnt = 0;

        if (data_len == 0)
                return (ISCSI_STATUS_SUCCESS);

        while ((line = iscsi_get_next_text(data, data_len, line)) != NULL) {
                if (strncmp(TARGETNAME, line, strlen(TARGETNAME)) == 0) {
                        /* check if this is first targetname */
                        if (prev_ste != NULL) {
                                stl->stl_out_cnt++;
                        }
                        if (stl->stl_out_cnt >= stl->stl_in_cnt) {
                                /*
                                 * continue processing the data so that
                                 * the total number of targets are known
                                 * and the caller can retry with the correct
                                 * number of entries in the list
                                 */
                                continue;
                        }
                        curr_ste = &(stl->stl_list[stl->stl_out_cnt]);

                        /*
                         * This entry will use the IP address and port
                         * that was passed into this routine. If the next
                         * line that we receive is a TargetAddress we will
                         * know to modify this entry with the new IP address,
                         * port and portal group tag. If this state flag
                         * is not set we'll just create a new entry using
                         * only the previous entries targetname.
                         */
                        (void) strncpy((char *)curr_ste->ste_name,
                            line + strlen(TARGETNAME),
                            sizeof (curr_ste->ste_name));

                        if (icp->conn_base_addr.sin.sa_family == AF_INET) {

                                struct sockaddr_in *addr_in =
                                    &icp->conn_base_addr.sin4;
                                curr_ste->ste_ipaddr.a_addr.i_insize =
                                    sizeof (struct in_addr);
                                bcopy(&addr_in->sin_addr.s_addr,
                                    &curr_ste->ste_ipaddr.a_addr.i_addr,
                                    sizeof (struct in_addr));
                                curr_ste->ste_ipaddr.a_port =
                                    htons(addr_in->sin_port);

                        } else {

                                struct sockaddr_in6 *addr_in6 =
                                    &icp->conn_base_addr.sin6;
                                curr_ste->ste_ipaddr.a_addr.i_insize =
                                    sizeof (struct in6_addr);
                                bcopy(&addr_in6->sin6_addr.s6_addr,
                                    &curr_ste->ste_ipaddr.a_addr.i_addr,
                                    sizeof (struct in6_addr));
                                curr_ste->ste_ipaddr.a_port =
                                    htons(addr_in6->sin6_port);
                        }
                        curr_ste->ste_tpgt = -1;

                        targetname_added = B_TRUE;

                } else if (strncmp(TARGETADDRESS, line,
                    strlen(TARGETADDRESS)) == 0) {

                        char *in_str,
                            *tmp_buf,
                            *addr_str,
                            *port_str,
                            *tpgt_str;
                        int type,
                            tmp_buf_len;
                        long result;

                        /*
                         * If TARGETADDRESS is first line a SendTarget response
                         * (i.e. no TARGETNAME lines preceding), treat as
                         * an error.  To check this an assumption is made that
                         * at least one sendtarget_entry_t should exist prior
                         * to entering this code.
                         */
                        if (prev_ste == NULL) {
                                cmn_err(CE_NOTE, "SendTargets protocol error: "
                                    "TARGETADDRESS first");
                                return (ISCSI_STATUS_PROTOCOL_ERROR);
                        }

                        /*
                         * If we can't find an '=' then the sendtargets
                         * response if invalid per spec.  Return empty list.
                         */
                        in_str = strchr(line, '=');
                        if (in_str == NULL) {
                                return (ISCSI_STATUS_PROTOCOL_ERROR);
                        }

                        /* move past the '=' */
                        in_str++;

                        /* Copy  addr, port, and tpgt into temporary buffer */
                        tmp_buf_len = strlen(in_str) + 1;
                        tmp_buf = kmem_zalloc(tmp_buf_len, KM_SLEEP);
                        (void) strncpy(tmp_buf, in_str, tmp_buf_len);

                        /*
                         * Parse the addr, port, and tpgt from
                         * sendtarget response
                         */
                        if (parse_addr_port_tpgt(tmp_buf, &addr_str, &type,
                            &port_str, &tpgt_str) == B_FALSE) {
                                /* Unable to extract addr */
                                kmem_free(tmp_buf, tmp_buf_len);
                                return (ISCSI_STATUS_PROTOCOL_ERROR);
                        }

                        /* Now convert string addr to binary */
                        hptr = kgetipnodebyname(addr_str, type,
                            AI_ALL, &error_num);
                        if (!hptr) {
                                /* Unable to get valid address */
                                kmem_free(tmp_buf, tmp_buf_len);
                                return (ISCSI_STATUS_PROTOCOL_ERROR);
                        }

                        /* Check if space for response */
                        if (targetname_added == B_FALSE) {
                                stl->stl_out_cnt++;
                                if (stl->stl_out_cnt >= stl->stl_in_cnt) {
                                        /*
                                         * continue processing the data so that
                                         * the total number of targets are
                                         * known and the caller can retry with
                                         * the correct number of entries in
                                         * the list
                                         */
                                        kfreehostent(hptr);
                                        kmem_free(tmp_buf, tmp_buf_len);
                                        continue;
                                }
                                curr_ste = &(stl->stl_list[stl->stl_out_cnt]);
                                (void) strcpy((char *)curr_ste->ste_name,
                                    (char *)prev_ste->ste_name);
                        }

                        curr_ste->ste_ipaddr.a_addr.i_insize = hptr->h_length;
                        bcopy(*hptr->h_addr_list,
                            &(curr_ste->ste_ipaddr.a_addr.i_addr),
                            curr_ste->ste_ipaddr.a_addr.i_insize);
                        kfreehostent(hptr);

                        if (port_str != NULL) {
                                (void) ddi_strtol(port_str, NULL, 0, &result);
                                curr_ste->ste_ipaddr.a_port = (short)result;
                        } else {
                                curr_ste->ste_ipaddr.a_port = ISCSI_LISTEN_PORT;
                        }

                        if (tpgt_str != NULL) {
                                (void) ddi_strtol(tpgt_str, NULL, 0, &result);
                                curr_ste->ste_tpgt = (short)result;
                        } else {
                                cmn_err(CE_NOTE, "SendTargets protocol error: "
                                    "TPGT not specified");
                                kmem_free(tmp_buf, tmp_buf_len);
                                return (ISCSI_STATUS_PROTOCOL_ERROR);
                        }

                        kmem_free(tmp_buf, tmp_buf_len);

                        targetname_added = B_FALSE;

                } else if (strlen(line) != 0) {
                        /*
                         * Any other string besides an empty string
                         * is a protocol error
                         */
                        cmn_err(CE_NOTE, "SendTargets protocol error: "
                            "unexpected response");
                        return (ISCSI_STATUS_PROTOCOL_ERROR);
                }

                prev_ste = curr_ste;
        }

        /*
         * If target found increment out count one more time because
         * this is the total number of entries in the list not an index
         * like it was used above
         */
        if (prev_ste != NULL) {
                stl->stl_out_cnt++;
        }

        return (ISCSI_STATUS_SUCCESS);
}

/*
 * iscsi_set_param - This function is a helper to ISCSI_SET_PARAM
 * IOCTL
 */
int
iscsi_set_param(iscsi_login_params_t *params, iscsi_param_set_t *ipsp)
{
        int rtn = 0;
        iscsi_param_get_t *ipgp;

        /*
         * Use get param to get the min, max and increment values for the
         * given parameter so validation can be done on the new value.
         */
        ipgp = (iscsi_param_get_t *)kmem_alloc(sizeof (*ipgp), KM_SLEEP);
        ipgp->g_param = ipsp->s_param;
        rtn = iscsi_get_param(params, B_TRUE, ipgp);
        if (rtn != 0) {
                kmem_free(ipgp, sizeof (*ipgp));
                return (rtn);
        }

        if (ipsp->s_param == ISCSI_LOGIN_PARAM_HEADER_DIGEST ||
            ipsp->s_param == ISCSI_LOGIN_PARAM_DATA_DIGEST ||
            ipsp->s_param == ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_RETAIN ||
            ipsp->s_param == ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_WAIT ||
            ipsp->s_param == ISCSI_LOGIN_PARAM_MAX_RECV_DATA_SEGMENT_LENGTH ||
            ipsp->s_param == ISCSI_LOGIN_PARAM_FIRST_BURST_LENGTH ||
            ipsp->s_param == ISCSI_LOGIN_PARAM_MAX_BURST_LENGTH) {

                if (ipsp->s_value.v_integer < ipgp->g_value.v_integer.i_min ||
                    ipsp->s_value.v_integer > ipgp->g_value.v_integer.i_max ||
                    (ipsp->s_value.v_integer %
                    ipgp->g_value.v_integer.i_incr) != 0) {
                        rtn = EINVAL;
                        kmem_free(ipgp, sizeof (*ipgp));
                        return (rtn);
                }

        }
        kmem_free(ipgp, sizeof (*ipgp));


        switch (ipsp->s_param) {

        /*
         * Boolean parameters
         */
        case ISCSI_LOGIN_PARAM_DATA_SEQUENCE_IN_ORDER:
                params->data_sequence_in_order = ipsp->s_value.v_bool;
                break;
        case ISCSI_LOGIN_PARAM_IMMEDIATE_DATA:
                params->immediate_data = ipsp->s_value.v_bool;
                break;
        case ISCSI_LOGIN_PARAM_INITIAL_R2T:
                params->initial_r2t = ipsp->s_value.v_bool;
                break;
        case ISCSI_LOGIN_PARAM_DATA_PDU_IN_ORDER:
                params->data_pdu_in_order = ipsp->s_value.v_bool;
                break;

        /*
         * Integer parameters
         */
        case ISCSI_LOGIN_PARAM_HEADER_DIGEST:
                params->header_digest = ipsp->s_value.v_integer;
                break;
        case ISCSI_LOGIN_PARAM_DATA_DIGEST:
                params->data_digest = ipsp->s_value.v_integer;
                break;
        case ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_RETAIN:
                params->default_time_to_retain = ipsp->s_value.v_integer;
                break;
        case ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_WAIT:
                params->default_time_to_wait = ipsp->s_value.v_integer;
                break;
        case ISCSI_LOGIN_PARAM_MAX_RECV_DATA_SEGMENT_LENGTH:
                params->max_recv_data_seg_len = ipsp->s_value.v_integer;
                break;
        case ISCSI_LOGIN_PARAM_FIRST_BURST_LENGTH:
                if (ipsp->s_value.v_integer <= params->max_burst_length) {
                        params->first_burst_length = ipsp->s_value.v_integer;
                } else {
                        rtn = EINVAL;
                }
                break;
        case ISCSI_LOGIN_PARAM_MAX_BURST_LENGTH:
                if (ipsp->s_value.v_integer >= params->first_burst_length) {
                        params->max_burst_length = ipsp->s_value.v_integer;
                } else {
                        rtn = EINVAL;
                }
                break;

        /*
         * Integer parameters which currently are unsettable
         */
        case ISCSI_LOGIN_PARAM_MAX_CONNECTIONS:
        case ISCSI_LOGIN_PARAM_OUTSTANDING_R2T:
        case ISCSI_LOGIN_PARAM_ERROR_RECOVERY_LEVEL:
                rtn = ENOTSUP;
                break;

        default:
                rtn = EINVAL;
                break;
        }
        return (rtn);
}

int
iscsi_set_params(iscsi_param_set_t *ils, iscsi_hba_t *ihp, boolean_t persist)
{
        iscsi_login_params_t    *params = NULL;
        uchar_t                 *name   = NULL;
        iscsi_sess_t            *isp    = NULL;
        iscsi_param_get_t       *ilg;
        int                     rtn     = 0;
        uint32_t                event_count;

        /* handle special case for Initiator name */
        if (ils->s_param == ISCSI_LOGIN_PARAM_INITIATOR_NAME) {
                (void) strlcpy((char *)ihp->hba_name,
                    (char *)ils->s_value.v_name, ISCSI_MAX_NAME_LEN);
                if (persist) {
                        char                    *name;
                        boolean_t               rval;

                        /* save off old Initiator name */
                        name = kmem_alloc(ISCSI_MAX_NAME_LEN, KM_SLEEP);
                        rval = persistent_initiator_name_get(name,
                            ISCSI_MAX_NAME_LEN);

                        (void) persistent_initiator_name_set(
                            (char *)ihp->hba_name);
                        if (rval == B_TRUE) {
                                /*
                                 * check to see if we have login param,
                                 * chap param, or authentication params
                                 * loaded in persistent that we have to change
                                 * the name of
                                 */
                                persistent_param_t      *pp;
                                iscsi_chap_props_t      *chap;
                                iscsi_auth_props_t      *auth;

                                /* checking login params */
                                pp = kmem_zalloc(sizeof (persistent_param_t),
                                    KM_SLEEP);
                                if (persistent_param_get(name, pp)) {
                                        rval = persistent_param_clear(name);
                                        if (rval == B_TRUE) {
                                                rval = persistent_param_set(
                                                    (char *)ihp->hba_name, pp);
                                        }
                                        if (rval == B_FALSE) {
                                                rtn = EFAULT;
                                        }
                                }
                                kmem_free(pp, sizeof (persistent_param_t));

                                /* check chap params */
                                chap = kmem_zalloc(sizeof (iscsi_chap_props_t),
                                    KM_SLEEP);
                                if (persistent_chap_get(name, chap)) {
                                        rval = persistent_chap_clear(name);
                                        if (rval == B_TRUE) {
                                        /*
                                         * Update CHAP user name only if the
                                         * original username was set to the
                                         * initiator node name.  Otherwise
                                         * leave it the way it is.
                                         */
                                                int userSize;
                                                userSize =
                                                    sizeof (chap->c_user);
                                                if (strncmp((char *)
                                                    chap->c_user, name,
                                                    sizeof (chap->c_user))
                                                    == 0) {
                                                        bzero(chap->c_user,
                                                            userSize);
                                                        bcopy((char *)
                                                            ihp->hba_name,
                                                            chap->c_user,
                                                            strlen((char *)
                                                            ihp->hba_name));
                                                        chap->c_user_len =
                                                            strlen((char *)
                                                            ihp->hba_name);

                                        }
                                        rval = persistent_chap_set(
                                            (char *)ihp->hba_name, chap);
                                        }
                                        if (rval == B_FALSE) {
                                                rtn = EFAULT;
                                        }
                                }
                                kmem_free(chap, sizeof (iscsi_chap_props_t));

                                /* check authentication params */
                                auth = kmem_zalloc(sizeof (iscsi_auth_props_t),
                                    KM_SLEEP);
                                if (persistent_auth_get(name, auth)) {
                                        rval = persistent_auth_clear(name);
                                        if (rval == B_TRUE) {
                                                rval = persistent_auth_set(
                                                    (char *)ihp->hba_name,
                                                    auth);
                                        }
                                        if (rval == B_FALSE) {
                                                rtn = EFAULT;
                                        }
                                }
                                kmem_free(auth, sizeof (iscsi_auth_props_t));
                        }
                        kmem_free(name, ISCSI_MAX_NAME_LEN);
                }
        } else if (ils->s_param == ISCSI_LOGIN_PARAM_INITIATOR_ALIAS) {
                (void) strlcpy((char *)ihp->hba_alias,
                    (char *)ils->s_value.v_name, ISCSI_MAX_NAME_LEN);
                ihp->hba_alias_length =
                    strlen((char *)ils->s_value.v_name);
                if (persist) {
                        (void) persistent_alias_name_set(
                            (char *)ihp->hba_alias);
                }
        } else {
                /* switch login based if looking for initiator params */
                if (ils->s_oid == ihp->hba_oid) {
                        /* initiator */
                        params = &ihp->hba_params;
                        name = ihp->hba_name;
                        rtn = iscsi_set_param(params, ils);
                } else {
                        /* session */
                        name = iscsi_targetparam_get_name(ils->s_oid);
                        if (name == NULL)
                                rtn = EFAULT;

                        if (persist && (rtn == 0)) {
                                boolean_t               rval;
                                persistent_param_t      *pp;

                                pp = (persistent_param_t *)
                                    kmem_zalloc(sizeof (*pp), KM_SLEEP);
                                if (!persistent_param_get((char *)name, pp)) {
                                        iscsi_set_default_login_params(
                                            &pp->p_params);
                                }

                                pp->p_bitmap |= (1 << ils->s_param);
                                rtn = iscsi_set_param(&pp->p_params, ils);
                                if (rtn == 0) {
                                        rval = persistent_param_set(
                                            (char *)name, pp);
                                        if (rval == B_FALSE) {
                                                rtn = EFAULT;
                                        }
                                }
                                kmem_free(pp, sizeof (*pp));
                        }

                        /*
                         * Here may have multiple sessions with different
                         * tpgt values.  So it is needed to loop through
                         * the sessions and update all sessions.
                         */
                        if (rtn == 0) {
                                rw_enter(&ihp->hba_sess_list_rwlock, RW_READER);
                                for (isp = ihp->hba_sess_list; isp;
                                    isp = isp->sess_next) {
                                        if (iscsiboot_prop &&
                                            isp->sess_boot &&
                                            iscsi_chk_bootlun_mpxio(ihp)) {
                                                /*
                                                 * MPxIO is enabled so capable
                                                 * of changing. All changes
                                                 * will be applied later,
                                                 * after this function
                                                 */
                                                continue;
                                        }

                                        if (strncmp((char *)isp->sess_name,
                                            (char *)name,
                                            ISCSI_MAX_NAME_LEN) == 0) {
event_count = atomic_inc_32_nv(&isp->sess_state_event_count);
iscsi_sess_enter_state_zone(isp);
iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N7, event_count);
iscsi_sess_exit_state_zone(isp);
                                        }
                                }
                                rw_exit(&ihp->hba_sess_list_rwlock);
                        }

                } /* end of 'else' */

                if (params && persist && (rtn == 0)) {
                        boolean_t               rval;
                        persistent_param_t      *pp;

                        pp = (persistent_param_t *)
                            kmem_zalloc(sizeof (*pp), KM_SLEEP);
                        (void) persistent_param_get((char *)name, pp);
                        pp->p_bitmap |= (1 << ils->s_param);
                        bcopy(params, &pp->p_params, sizeof (*params));
                        rval = persistent_param_set((char *)name, pp);
                        if (rval == B_FALSE) {
                                rtn = EFAULT;
                        }
                        kmem_free(pp, sizeof (*pp));
                }
                /*
                 * if initiator parameter set, modify all associated
                 * sessions that don't already have the parameter
                 * overriden
                 */
                if ((ils->s_oid == ihp->hba_oid) && (rtn == 0)) {
                        ilg = (iscsi_param_get_t *)
                            kmem_alloc(sizeof (*ilg), KM_SLEEP);

                        rw_enter(&ihp->hba_sess_list_rwlock, RW_READER);
                        for (isp = ihp->hba_sess_list; isp;
                            isp = isp->sess_next) {
                                ilg->g_param = ils->s_param;
                                params = &isp->sess_params;
                                if (iscsi_get_persisted_param(
                                    isp->sess_name, ilg, params) != 0) {
                                        rtn = iscsi_set_param(params, ils);
                                        if (rtn != 0) {
                                                break;
                                        }
                                        if (iscsiboot_prop &&
                                            isp->sess_boot &&
                                            iscsi_chk_bootlun_mpxio(ihp)) {
                                                /*
                                                 * MPxIO is enabled so capable
                                                 * of changing. Changes will
                                                 * be applied later, right
                                                 * after this function
                                                 */
                                                continue;
                                        }

                                        /*
                                         * Notify the session that
                                         * the login parameters have
                                         * changed.
                                         */
                                        event_count = atomic_inc_32_nv(
                                            &isp->sess_state_event_count);
                                        iscsi_sess_enter_state_zone(isp);
                                        iscsi_sess_state_machine(isp,
                                            ISCSI_SESS_EVENT_N7, event_count);
                                        iscsi_sess_exit_state_zone(isp);
                                }
                        }
                        kmem_free(ilg, sizeof (*ilg));
                        rw_exit(&ihp->hba_sess_list_rwlock);
                }
        }
        return (rtn);
}

int
iscsi_target_prop_mod(iscsi_hba_t *ihp, iscsi_property_t *ipp, int cmd)
{
        iscsi_sess_t *isp = NULL;
        iscsi_conn_t *icp;
        int rtn;
        char *name;

        /*
         * If we're just attempting to get the target properties don't
         * create the session if it doesn't already exist. If we setting
         * the property then create the session if needed because we'll
         * most likely see an ISCSI_LOGIN in a few.
         */
        rw_enter(&ihp->hba_sess_list_rwlock, RW_READER);

        /*
         * If the oid does represent a session check to see
         * if it is a target oid.  If so, return the target's
         * associated session.
         */
        rtn = iscsi_sess_get(ipp->p_oid, ihp, &isp);
        if (rtn != 0) {
                rtn = iscsi_sess_get_by_target(ipp->p_oid, ihp, &isp);
        }

        /*
         * If rtn is zero then we have found an existing session.
         * Use the session name for database lookup.  If rtn is
         * non-zero then create a targetparam object and use
         * its name for database lookup.
         */
        if (rtn == 0) {
                name = (char *)isp->sess_name;
        } else {
                name = (char *)iscsi_targetparam_get_name(ipp->p_oid);
                isp = NULL;
        }

        if (name == NULL) {
                rw_exit(&ihp->hba_sess_list_rwlock);
                rtn = EFAULT;
                return (rtn);
        }

        rtn = 0;
        if (cmd == ISCSI_TARGET_PROPS_GET) {
                /*
                 * If isp is not null get the session's parameters, otherwise
                 * the get is for a target-param object so defaults need to
                 * be returned.
                 */
                if (isp != NULL) {
                        int conn_count = 0;

                        bcopy(isp->sess_alias, ipp->p_alias,
                            isp->sess_alias_length);
                        bcopy(isp->sess_name, ipp->p_name,
                            isp->sess_name_length);
                        ipp->p_alias_len = isp->sess_alias_length;
                        ipp->p_name_len  = isp->sess_name_length;
                        ipp->p_discovery = isp->sess_discovered_by;
                        ipp->p_last_err  = isp->sess_last_err;
                        ipp->p_tpgt_conf = isp->sess_tpgt_conf;
                        ipp->p_tpgt_nego = isp->sess_tpgt_nego;
                        bcopy(isp->sess_isid, ipp->p_isid, ISCSI_ISID_LEN);

                        rw_enter(&isp->sess_conn_list_rwlock, RW_READER);
                        for (icp = isp->sess_conn_list; icp;
                            icp = icp->conn_next) {
                                if (icp->conn_state ==
                                    ISCSI_CONN_STATE_LOGGED_IN) {
                                        conn_count++;
                                }
                        }
                        rw_exit(&isp->sess_conn_list_rwlock);
                        ipp->p_num_of_connections = conn_count;
                        ipp->p_connected = (conn_count > 0) ? B_TRUE : B_FALSE;
                } else {
                        bcopy(name, ipp->p_name, strlen(name));
                        ipp->p_name_len  = strlen(name);
                        bcopy("", ipp->p_alias, strlen(""));
                        ipp->p_alias_len = strlen("");
                        ipp->p_discovery = iSCSIDiscoveryMethodUnknown;
                        ipp->p_last_err  =  NoError;
                        ipp->p_tpgt_conf = ISCSI_DEFAULT_TPGT;
                        ipp->p_tpgt_nego = ISCSI_DEFAULT_TPGT;
                        ipp->p_num_of_connections = 0;
                        ipp->p_connected = B_FALSE;
                }
        } else {
                if (isp == NULL) {
                        rw_exit(&ihp->hba_sess_list_rwlock);
                        rtn = EFAULT;
                        return (rtn);
                }

                /* ISCSI_TARGET_PROPS_SET */
                /*
                 * only update if new, otherwise could clear out alias
                 * if just updating the discovery.
                 */
                if (ipp->p_alias_len != 0) {
                        bcopy(ipp->p_alias, isp->sess_alias,
                            ipp->p_alias_len);
                        isp->sess_alias_length  = ipp->p_alias_len;
                }
                isp->sess_discovered_by = ipp->p_discovery;
        }
        rw_exit(&ihp->hba_sess_list_rwlock);
        return (rtn);
}

/*
 * iscsi_ioctl_get_config_sess - gets configured session information
 *
 * This function is an ioctl helper function to get the
 * configured session information from the persistent store.
 */
int
iscsi_ioctl_get_config_sess(iscsi_hba_t *ihp, iscsi_config_sess_t *ics)
{
        uchar_t *name;

        /* Get the matching iscsi node name for the oid */
        if (ics->ics_oid == ISCSI_INITIATOR_OID) {
                /* initiator name */
                name = ihp->hba_name;
        } else {
                /* target name */
                name = iscsi_targetparam_get_name(ics->ics_oid);
                if (name == NULL) {
                        /* invalid node name */
                        return (EINVAL);
                }
        }

        /* get configured session information */
        if (persistent_get_config_session((char *)name, ics) == B_FALSE) {
                /*
                 * There might not be anything in the database yet.  If
                 * this is a request for the target check the initiator
                 * value.  If neither is set return the default value.
                 */
                if (ics->ics_oid != ISCSI_INITIATOR_OID) {
                        if (persistent_get_config_session(
                            (char *)ihp->hba_name, ics) == B_FALSE) {
                                /*
                                 * No initiator value is set.
                                 * Return the defaults.
                                 */
                                ics->ics_out = ISCSI_DEFAULT_SESS_NUM;
                                ics->ics_bound = ISCSI_DEFAULT_SESS_BOUND;
                        }
                } else {
                        ics->ics_out = ISCSI_DEFAULT_SESS_NUM;
                        ics->ics_bound = ISCSI_DEFAULT_SESS_BOUND;
                }
        }

        return (0);
}

/*
 * iscsi_ioctl_set_config_sess - sets configured session information
 *
 * This function is an ioctl helper function to set the
 * configured session information in the persistent store.
 * In addition it will notify any active sessions of the
 * changed so this can update binding information.  It
 * will also destroy sessions that were removed and add
 * new sessions.
 */
int
iscsi_ioctl_set_config_sess(iscsi_hba_t *ihp, iscsi_config_sess_t *ics)
{
        uchar_t *name;
        iscsi_sess_t *isp;
        uint32_t event_count;

        /* check range infomration */
        if ((ics->ics_in < ISCSI_MIN_CONFIG_SESSIONS) ||
            (ics->ics_in > ISCSI_MAX_CONFIG_SESSIONS)) {
                /* invalid range information */
                return (EINVAL);
        }

        if (ics->ics_oid == ISCSI_INITIATOR_OID) {
                name = ihp->hba_name;
        } else {
                /* get target name */
                name = iscsi_targetparam_get_name(ics->ics_oid);
                if (name == NULL) {
                        /* invalid node name */
                        return (EINVAL);
                }
        }

        /* store the new information */
        if (persistent_set_config_session((char *)name, ics) == B_FALSE) {
                /* failed to store new information */
                return (EINVAL);
        }

        /* notify existing sessions of change */
        rw_enter(&ihp->hba_sess_list_rwlock, RW_READER);
        isp = ihp->hba_sess_list;
        while (isp != NULL) {

                if ((ics->ics_oid == ISCSI_INITIATOR_OID) ||
                    (strncmp((char *)isp->sess_name, (char *)name,
                    ISCSI_MAX_NAME_LEN) == 0)) {

                        /*
                         * If this sessions least signficant byte
                         * of the isid is less than or equal to
                         * the the number of configured sessions
                         * then we need to tear down this session.
                         */
                        if (ics->ics_in <= isp->sess_isid[5]) {
                                /* First attempt to destory the session */
                                if (ISCSI_SUCCESS(iscsi_sess_destroy(isp))) {
                                        isp = ihp->hba_sess_list;
                                } else {
                                        /*
                                         * If we can't destroy it then
                                         * atleast poke it to disconnect
                                         * it.
                                         */
                                        event_count = atomic_inc_32_nv(
                                            &isp->sess_state_event_count);
                                        iscsi_sess_enter_state_zone(isp);
                                        iscsi_sess_state_machine(isp,
                                            ISCSI_SESS_EVENT_N7, event_count);
                                        iscsi_sess_exit_state_zone(isp);

                                        isp = isp->sess_next;
                                }
                        } else {
                                isp = isp->sess_next;
                        }
                } else {
                        isp = isp->sess_next;
                }
        }
        rw_exit(&ihp->hba_sess_list_rwlock);

        /*
         * The number of targets has changed.  Since we don't expect
         * this to be a common operation lets keep the code simple and
         * just use a slightly larger hammer and poke discovery.  This
         * force the reevaulation of this target and all other targets.
         */
        iscsid_poke_discovery(ihp, iSCSIDiscoveryMethodUnknown);
        /* lock so only one config operation occrs */
        sema_p(&iscsid_config_semaphore);
        iscsid_config_all(ihp, B_FALSE);
        sema_v(&iscsid_config_semaphore);

        return (0);
}

int
iscsi_ioctl_set_tunable_param(iscsi_hba_t *ihp, iscsi_tunable_object_t *tpss)
{
        uchar_t *name;
        iscsi_sess_t *isp;
        iscsi_conn_t *icp;
        int     param_id = 0;
        persistent_tunable_param_t *pparam;

        if (tpss->t_oid == ihp->hba_oid) {
                name = ihp->hba_name;
        } else {
                /* get target name */
                name = iscsi_targetparam_get_name(tpss->t_oid);
                if (name == NULL) {
                        /* invalid node name */
                        return (EINVAL);
                }
        }

        pparam = (persistent_tunable_param_t *)kmem_zalloc(sizeof (*pparam),
            KM_SLEEP);
        if (persistent_get_tunable_param((char *)name, pparam) == B_FALSE) {
                /* use default value */
                pparam->p_params.recv_login_rsp_timeout =
                    ISCSI_DEFAULT_RX_TIMEOUT_VALUE;
                pparam->p_params.polling_login_delay =
                    ISCSI_DEFAULT_LOGIN_POLLING_DELAY;
                pparam->p_params.conn_login_max =
                    ISCSI_DEFAULT_CONN_DEFAULT_LOGIN_MAX;
        }

        pparam->p_bitmap |= (1 << (tpss->t_param -1));
        param_id = 1 << (tpss->t_param -1);
        switch (param_id) {
        case ISCSI_TUNABLE_PARAM_RX_TIMEOUT_VALUE:
                pparam->p_params.recv_login_rsp_timeout =
                    tpss->t_value.v_integer;
                break;
        case ISCSI_TUNABLE_PARAM_LOGIN_POLLING_DELAY:
                pparam->p_params.polling_login_delay =
                    tpss->t_value.v_integer;
                break;
        case ISCSI_TUNABLE_PARAM_CONN_LOGIN_MAX:
                pparam->p_params.conn_login_max =
                    tpss->t_value.v_integer;
                break;
        default:
                break;
        }
        if (persistent_set_tunable_param((char *)name,
            pparam) == B_FALSE) {
                kmem_free(pparam, sizeof (*pparam));
                return (EINVAL);
        }

        if (tpss->t_oid == ihp->hba_oid) {
                bcopy(&pparam->p_params, &ihp->hba_tunable_params,
                    sizeof (iscsi_tunable_params_t));
        }

        rw_enter(&ihp->hba_sess_list_rwlock, RW_READER);
        for (isp = ihp->hba_sess_list; isp; isp = isp->sess_next) {
                if (isp->sess_type != ISCSI_SESS_TYPE_NORMAL) {
                        continue;
                }
                rw_enter(&isp->sess_conn_list_rwlock, RW_READER);
                icp = isp->sess_conn_list;
                while (icp != NULL) {
                        if (strcmp((const char *)name,
                            (const char *)isp->sess_name) == 0) {
                                bcopy(&pparam->p_params,
                                    &icp->conn_tunable_params,
                                    sizeof (iscsi_tunable_params_t));
                        } else {
                                /*
                                 * this session connected target
                                 * tunable parameters not set,
                                 * use initiator's default
                                 */
                                bcopy(&ihp->hba_tunable_params,
                                    &icp->conn_tunable_params,
                                    sizeof (iscsi_tunable_params_t));
                        }
                        icp = icp->conn_next;
                }
                rw_exit(&isp->sess_conn_list_rwlock);
        }
        rw_exit(&ihp->hba_sess_list_rwlock);
        kmem_free(pparam, sizeof (*pparam));
        return (0);
}