root/usr/src/lib/cfgadm_plugins/fp/common/cfga_cs.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#include        "cfga_fp.h"

/* define */
#define ALL_APID_LUNS_UNUSABLE  0x10

#define DEFAULT_LUN_COUNT       1024
#define LUN_SIZE                8
#define LUN_HEADER_SIZE         8
#define DEFAULT_LUN_LENGTH      DEFAULT_LUN_COUNT   *   \
                                LUN_SIZE            +   \
                                LUN_HEADER_SIZE

/* Some forward declarations */
static fpcfga_ret_t do_devctl_dev_create(apid_t *, char *, int,
                                                        uchar_t, char **);
static fpcfga_ret_t dev_rcm_online(apid_t *, int, cfga_flags_t, char **);
static void dev_rcm_online_nonoperationalpath(apid_t *, cfga_flags_t, char **);
static fpcfga_ret_t dev_rcm_offline(apid_t *, cfga_flags_t, char **);
static fpcfga_ret_t dev_rcm_remove(apid_t *, cfga_flags_t, char **);
static fpcfga_ret_t lun_unconf(char *, int, char *, char *, char **);
static fpcfga_ret_t dev_unconf(apid_t *, char **, uchar_t *);
static fpcfga_ret_t is_xport_phys_in_pathlist(apid_t *, char **);
static void copy_pwwn_data_to_str(char *, const uchar_t *);
static fpcfga_ret_t unconf_vhci_nodes(di_path_t, di_node_t, char *,
                        char *, int, int *, int *, char **, cfga_flags_t);
static fpcfga_ret_t unconf_non_vhci_nodes(di_node_t, char *, char *,
                        int, int *, int *, char **, cfga_flags_t);
static fpcfga_ret_t unconf_any_devinfo_nodes(apid_t *, cfga_flags_t, char **,
                        int *, int *);
static fpcfga_ret_t handle_devs(cfga_cmd_t, apid_t *, cfga_flags_t,
                        char **, HBA_HANDLE, int, HBA_PORTATTRIBUTES);


/*
 * This function initiates the creation of the new device node for a given
 * port WWN.
 * So, apidt->dyncomp CANNOT be NULL
 */
static fpcfga_ret_t
do_devctl_dev_create(apid_t *apidt, char *dev_path, int pathlen,
                                        uchar_t dev_dtype, char **errstring)
{
        devctl_ddef_t   ddef_hdl;
        devctl_hdl_t    bus_hdl, dev_hdl;
        char            *drvr_name = "dummy";
        la_wwn_t        pwwn;

        *dev_path = '\0';
        if ((ddef_hdl = devctl_ddef_alloc(drvr_name, 0)) == NULL) {
                cfga_err(errstring, errno, ERRARG_DC_DDEF_ALLOC, drvr_name, 0);
                return (FPCFGA_LIB_ERR);
        }

        if (cvt_dyncomp_to_lawwn(apidt->dyncomp, &pwwn)) {
                devctl_ddef_free(ddef_hdl);
                cfga_err(errstring, 0, ERR_APID_INVAL, 0);
                return (FPCFGA_LIB_ERR);
        }

        if (devctl_ddef_byte_array(ddef_hdl, PORT_WWN_PROP, FC_WWN_SIZE,
                                                        pwwn.raw_wwn) == -1) {
                devctl_ddef_free(ddef_hdl);
                cfga_err(errstring, errno, ERRARG_DC_BYTE_ARRAY,
                                                        PORT_WWN_PROP, 0);
                return (FPCFGA_LIB_ERR);
        }

        if ((bus_hdl = devctl_bus_acquire(apidt->xport_phys, 0)) == NULL) {
                devctl_ddef_free(ddef_hdl);
                cfga_err(errstring, errno, ERRARG_DC_BUS_ACQUIRE,
                                                        apidt->xport_phys, 0);
                return (FPCFGA_LIB_ERR);
        }

        /* Let driver handle creation of the new path */
        if (devctl_bus_dev_create(bus_hdl, ddef_hdl, 0, &dev_hdl)) {
                devctl_ddef_free(ddef_hdl);
                devctl_release(bus_hdl);
                if (dev_dtype == DTYPE_UNKNOWN) {
                        /*
                         * Unknown DTYPES are devices such as another system's
                         * FC HBA port. We have tried to configure it but
                         * have failed. Since devices with no device type
                         * or an unknown dtype cannot be configured, we will
                         * return an appropriate error message.
                         */
                        cfga_err(errstring, errno,
                            ERRARG_BUS_DEV_CREATE_UNKNOWN, apidt->dyncomp, 0);
                } else {
                        cfga_err(errstring, errno, ERRARG_BUS_DEV_CREATE,
                            apidt->dyncomp, 0);
                }
                return (FPCFGA_LIB_ERR);
        }
        devctl_release(bus_hdl);
        devctl_ddef_free(ddef_hdl);

        devctl_get_pathname(dev_hdl, dev_path, pathlen);
        devctl_release(dev_hdl);

        return (FPCFGA_OK);
}

/*
 * Online, in RCM, all the LUNs for a particular device.
 * Caller can specify the # of luns in the lunlist that have to be onlined
 * by passing a count that is not -ve.
 *
 * INPUT :
 * apidt - this is expected to have the list of luns for the device and so
 *         is assumed to be filled in prior to this call
 * count - # of LUNs in the list that have to be onlined.
 * errstring - If non-NULL, it will hold any error messages
 *
 * RETURNS :
 * 0 on success
 * non-zero otherwise
 */
static fpcfga_ret_t
dev_rcm_online(apid_t *apidt, int count, cfga_flags_t flags, char **errstring)
{
        luninfo_list_t  *lunlistp;
        int             i = 0, ret = 0;
        fpcfga_ret_t    retval = FPCFGA_OK;

        /* This check may be redundant, but safer this way */
        if ((apidt->flags & FLAG_DISABLE_RCM) != 0) {
                /* User has requested not to notify RCM framework */
                return (FPCFGA_OK);
        }

        lunlistp = apidt->lunlist;

        for (lunlistp = apidt->lunlist; lunlistp != NULL;
                                        i++, lunlistp = lunlistp->next) {
                if ((count >= 0) && (i >= count))
                        break;
                if (fp_rcm_online(lunlistp->path, errstring, flags) !=
                                                                FPCFGA_OK) {
                        ret++;
                }
        }

        if (ret > 0)
                retval = FPCFGA_LIB_ERR;

        return (retval);
}

/*
 * Online in RCM for devices which only have paths
 * not in ONLINE/STANDBY state
 */
void
dev_rcm_online_nonoperationalpath(apid_t *apidt, cfga_flags_t flags,
    char **errstring)
{
        luninfo_list_t  *lunlistp;

        if ((apidt->flags & FLAG_DISABLE_RCM) != 0) {
                return;
        }

        lunlistp = apidt->lunlist;

        for (lunlistp = apidt->lunlist; lunlistp != NULL;
            lunlistp = lunlistp->next) {
                if ((lunlistp->lun_flag & FLAG_SKIP_ONLINEOTHERS) != 0) {
                        continue;
                }
                (void) fp_rcm_online(lunlistp->path, errstring, flags);
        }
}

/*
 * Offline, in RCM, all the LUNs for a particular device.
 * This function should not be called for the MPXIO case.
 *
 * INPUT :
 * apidt - this is expected to have the list of luns for the device and so
 *         is assumed to be filled in prior to this call
 * errstring - If non-NULL, it will hold any error messages
 *
 * RETURNS :
 * FPCFGA_OK on success
 * error code otherwise
 */
static fpcfga_ret_t
dev_rcm_offline(apid_t *apidt, cfga_flags_t flags, char **errstring)
{
        int             count = 0;
        luninfo_list_t  *lunlistp;

        if ((apidt->flags & FLAG_DISABLE_RCM) != 0) {
                /* User has requested not to notify RCM framework */
                return (FPCFGA_OK);
        }

        for (lunlistp = apidt->lunlist; lunlistp != NULL;
                                                lunlistp = lunlistp->next) {
                if ((lunlistp->lun_flag & FLAG_SKIP_RCMOFFLINE) != 0) {
                        continue;
                }
                if ((apidt->flags & FLAG_REMOVE_UNUSABLE_FCP_DEV) ==
                    FLAG_REMOVE_UNUSABLE_FCP_DEV) {
                        int ret = strncmp(lunlistp->path, SCSI_VHCI_ROOT,
                                strlen(SCSI_VHCI_ROOT));

                        if (((ret == 0) &&
                            (lunlistp->node_state == DI_PATH_STATE_OFFLINE)) ||
                            ((ret != 0) &&
                            ((lunlistp->node_state & DI_DEVICE_OFFLINE) ==
                            DI_DEVICE_OFFLINE))) {
                                /* Offline the device through RCM */
                                if (fp_rcm_offline(lunlistp->path, errstring,
                                            flags) != 0) {
                                        /*
                                         * Bring everything back online in
                                         * rcm and return
                                         */
                                        (void) dev_rcm_online(apidt, count,
                                                                flags, NULL);
                                        return (FPCFGA_LIB_ERR);
                                }
                                count++;
                        }
                } else {
                        /* Offline the device through RCM */
                        if (fp_rcm_offline(lunlistp->path, errstring,
                                    flags) != 0) {
                                /*
                                 * Bring everything back online in
                                 * rcm and return
                                 */
                                (void) dev_rcm_online(apidt, count, flags,
                                                                        NULL);
                                return (FPCFGA_LIB_ERR);
                        }
                        count++;
                }
        }
        return (FPCFGA_OK);
}

/*
 * Remove, in RCM, all the LUNs for a particular device.
 * This function should not be called for the MPXIO case.
 *
 * INPUT :
 * apidt - this is expected to have the list of luns for the device and so
 *         is assumed to be filled in prior to this call
 * errstring - If non-NULL, it will hold any error messages
 *
 * RETURNS :
 * FPCFGA_OK on success
 * error code otherwise
 */
static fpcfga_ret_t
dev_rcm_remove(apid_t *apidt, cfga_flags_t flags, char **errstring)
{
        int             count = 0;
        luninfo_list_t  *lunlistp;

        if ((apidt->flags & FLAG_DISABLE_RCM) != 0) {
                /* User has requested not to notify RCM framework */
                return (FPCFGA_OK);
        }

        for (lunlistp = apidt->lunlist; lunlistp != NULL;
                                                lunlistp = lunlistp->next) {
                if ((lunlistp->lun_flag & FLAG_SKIP_RCMREMOVE) != 0)
                        continue;
                if ((apidt->flags & FLAG_REMOVE_UNUSABLE_FCP_DEV) ==
                    FLAG_REMOVE_UNUSABLE_FCP_DEV) {
                        int ret = strncmp(lunlistp->path, SCSI_VHCI_ROOT,
                                strlen(SCSI_VHCI_ROOT));

                        if (((ret == 0) &&
                            (lunlistp->node_state == DI_PATH_STATE_OFFLINE)) ||
                            ((ret != 0) &&
                            ((lunlistp->node_state & DI_DEVICE_OFFLINE) ==
                            DI_DEVICE_OFFLINE))) {
                                /* remove the device through RCM */
                                if (fp_rcm_remove(lunlistp->path, errstring,
                                            flags) != 0) {
                                        /*
                                         * Bring everything back online in
                                         * rcm and return
                                         */
                                        (void) dev_rcm_online(apidt, count,
                                                                flags, NULL);
                                        return (FPCFGA_LIB_ERR);
                                }
                                count++;
                        }
                } else {
                        /* remove the device through RCM */
                        if (fp_rcm_remove(lunlistp->path, errstring,
                                flags) != 0) {
                                /*
                                 * Bring everything back online in rcm and
                                 * return
                                 */
                                (void) dev_rcm_online(apidt, count, flags,
                                                                        NULL);
                                return (FPCFGA_LIB_ERR);
                        }
                        count++;
                }
        }
        return (FPCFGA_OK);
}

static fpcfga_ret_t
lun_unconf(char *path, int lunnum, char *xport_phys, char *dyncomp,
                                                char **errstring)
{
        devctl_hdl_t    hdl;
        char            *ptr;           /* To use as scratch/temp pointer */
        char            pathname[MAXPATHLEN];

        if (path == NULL)
                return (FPCFGA_OK);

        if (strncmp(path, SCSI_VHCI_ROOT, strlen(SCSI_VHCI_ROOT)) == 0) {
                /*
                 * We have an MPXIO managed device here.
                 * So, we have to concoct a path for the device.
                 *
                 * xport_phys looks like :
                 * /devices/pci@b,2000/pci@1/SUNW,qlc@5/fp@0,0:fc
                 */
                (void) strlcpy(pathname, xport_phys, MAXPATHLEN);
                if ((ptr = strrchr(pathname, ':')) != NULL) {
                        *ptr = '\0';
                }

                /*
                 * Get pointer to driver name from VHCI path
                 * So, if lunlistp->path is
                 * /devices/scsi_vhci/ssd@g220000203707a417,
                 * we need a pointer to the last '/'
                 *
                 * Assumption:
                 * With MPXIO there will be only one entry per lun
                 * So, there will only be one entry in the linked list
                 * apidt->lunlist
                 */
                if ((ptr = strrchr(path, '/')) == NULL) {
                        /* This shouldn't happen, but anyways ... */
                        cfga_err(errstring, 0, ERRARG_INVALID_PATH, path, 0);
                        return (FPCFGA_LIB_ERR);
                }

                /*
                 * Make pathname to look something like :
                 * /devices/pci@x,xxxx/pci@x/SUNW,qlc@x/fp@x,x/ssd@w...
                 */
                strcat(pathname, ptr);

                /*
                 * apidt_create() will make sure that lunlist->path
                 * has a "@<something>" at the end even if the driver
                 * state is "detached"
                 */
                if ((ptr = strrchr(pathname, '@')) == NULL) {
                        /* This shouldn't happen, but anyways ... */
                        cfga_err(errstring, 0, ERRARG_INVALID_PATH,
                                                pathname, 0);
                        return (FPCFGA_LIB_ERR);
                }
                *ptr = '\0';

                /* Now, concoct the path */
                sprintf(&pathname[strlen(pathname)], "@w%s,%x",
                                                        dyncomp, lunnum);
                ptr = pathname;
        } else {
                /*
                 * non-MPXIO path, use the path that is passed in
                 */
                ptr = path;
        }

        if ((hdl = devctl_device_acquire(ptr,  0)) == NULL) {
                cfga_err(errstring, errno, ERRARG_DEV_ACQUIRE, ptr, 0);
                return (FPCFGA_LIB_ERR);
        }

        if (devctl_device_remove(hdl) != 0) {
                devctl_release(hdl);
                cfga_err(errstring, errno, ERRARG_DEV_REMOVE, ptr, 0);
                return (FPCFGA_LIB_ERR);
        }
        devctl_release(hdl);

        return (FPCFGA_OK);
}

static fpcfga_ret_t
dev_unconf(apid_t *apidt, char **errstring, uchar_t *flag)
{
        luninfo_list_t  *lunlistp;
        fpcfga_ret_t    ret = FPCFGA_OK;
        int lun_cnt = 0, unusable_lun_cnt = 0;

        for (lunlistp = apidt->lunlist; lunlistp != NULL;
            lunlistp = lunlistp->next) {
                lun_cnt++;
                /*
                 * Unconfigure each LUN.
                 * Note that for MPXIO devices, lunlistp->path will be a
                 * vHCI path
                 */
                if ((apidt->flags & FLAG_REMOVE_UNUSABLE_FCP_DEV) ==
                        FLAG_REMOVE_UNUSABLE_FCP_DEV) {
                    if (strncmp(lunlistp->path, SCSI_VHCI_ROOT,
                        strlen(SCSI_VHCI_ROOT)) == 0) {
                        if (lunlistp->node_state ==
                                DI_PATH_STATE_OFFLINE) {
                            unusable_lun_cnt++;
                            if ((ret = lun_unconf(lunlistp->path,
                                lunlistp->lunnum, apidt->xport_phys,
                                apidt->dyncomp, errstring)) != FPCFGA_OK) {
                                return (ret);
                            }
                        }
                    } else {
                        if ((lunlistp->node_state & DI_DEVICE_OFFLINE) ==
                                DI_DEVICE_OFFLINE) {
                            unusable_lun_cnt++;
                            if ((ret = lun_unconf(lunlistp->path,
                                lunlistp->lunnum, apidt->xport_phys,
                                apidt->dyncomp, errstring)) != FPCFGA_OK) {
                                return (ret);
                            }
                        }
                    }
                } else {
                /*
                 * Unconfigure each LUN.
                 * Note that for MPXIO devices, lunlistp->path will be a
                 * vHCI path
                 */
                    if ((ret = lun_unconf(lunlistp->path, lunlistp->lunnum,
                                apidt->xport_phys, apidt->dyncomp,
                                errstring)) != FPCFGA_OK) {
                        return (ret);
                    }
                }
        }

        if ((apidt->flags & FLAG_REMOVE_UNUSABLE_FCP_DEV) ==
                        FLAG_REMOVE_UNUSABLE_FCP_DEV) {
                /*
                 * when all luns are unconfigured
                 * indicate to remove repository entry.
                 */
                if (lun_cnt == unusable_lun_cnt) {
                        *flag = ALL_APID_LUNS_UNUSABLE;
                }
        }

        return (ret);
}

/*
 * Check if the given physical path (the xport_phys) is part of the
 * pHCI list and if the RCM should be done for a particular pHCI.
 * Skip non-MPxIO dev node if any.
 */
static fpcfga_ret_t
is_xport_phys_in_pathlist(apid_t *apidt, char **errstring)
{
        di_node_t       root, vhci, node, phci;
        di_path_t       path = DI_PATH_NIL;
        int             num_active_paths, found = 0;
        char            *vhci_path_ptr, *pathname_ptr, pathname[MAXPATHLEN];
        char            *phci_path, *node_path;
        char            phci_addr[MAXPATHLEN];
        char            *xport_phys, *vhci_path, *dyncomp;
        luninfo_list_t  *lunlistp, *temp;
        int             non_operational_path_count;

        /* a safety check */
        if ((apidt->dyncomp == NULL) || (*apidt->dyncomp == '\0')) {
                return (FPCFGA_LIB_ERR);
        }

        xport_phys = apidt->xport_phys;
        dyncomp = apidt->dyncomp;

        lunlistp = apidt->lunlist;
        for (lunlistp = apidt->lunlist; lunlistp != NULL;
            lunlistp = lunlistp->next) {

                if (strncmp(lunlistp->path, SCSI_VHCI_ROOT,
                    strlen(SCSI_VHCI_ROOT)) != 0) {
                        lunlistp->lun_flag |= FLAG_SKIP_ONLINEOTHERS;
                        continue;
                }

                vhci_path = lunlistp->path;

                num_active_paths = 0;   /* # of paths in ONLINE/STANDBY */
                non_operational_path_count = 0;

                if (xport_phys == NULL || vhci_path == NULL) {
                    cfga_err(errstring, 0, ERRARG_XPORT_NOT_IN_PHCI_LIST,
                    xport_phys, 0);
                        return (FPCFGA_LIB_ERR);
                }

                (void) strlcpy(pathname, xport_phys, MAXPATHLEN);
                if ((pathname_ptr = strrchr(pathname, ':')) != NULL) {
                        *pathname_ptr = '\0';
                }
                /* strip off the /devices/from the path */
                pathname_ptr = pathname + strlen(DEVICES_DIR);

                root = di_init("/", DINFOCPYALL|DINFOPATH);

                if (root == DI_NODE_NIL) {
                        return (FPCFGA_LIB_ERR);
                }

                vhci_path_ptr = vhci_path + strlen(DEVICES_DIR);
                if ((vhci = di_drv_first_node(SCSI_VHCI_DRVR, root)) ==
                    DI_NODE_NIL) {
                        return (FPCFGA_LIB_ERR);
                }
                found = 0;
                for (node = di_child_node(vhci); node != DI_NODE_NIL;
                    node = di_sibling_node(node)) {
                        if ((node_path = di_devfs_path(node)) != NULL) {
                                if (strncmp(vhci_path_ptr, node_path,
                                    strlen(node_path)) != 0) {
                                        di_devfs_path_free(node_path);
                                } else {
                                        found = 1;
                                        break;
                                }
                        }
                }
                if (found == 0) {
                        cfga_err(errstring, 0, ERRARG_XPORT_NOT_IN_PHCI_LIST,
                            xport_phys, 0);
                        di_fini(root);
                        return (FPCFGA_LIB_ERR);
                }
                /* found vhci_path we are looking for */
                di_devfs_path_free(node_path);
                found = 0;
                for (path = di_path_next_phci(node, DI_PATH_NIL);
                    path != DI_PATH_NIL;
                    path = di_path_next_phci(node, path)) {
                        if ((phci = di_path_phci_node(path)) == DI_NODE_NIL) {
                                cfga_err(errstring, 0,
                                    ERRARG_XPORT_NOT_IN_PHCI_LIST,
                                    xport_phys, 0);
                                di_fini(root);
                                return (FPCFGA_LIB_ERR);
                        }
                        if ((phci_path = di_devfs_path(phci)) == NULL) {
                                cfga_err(errstring, 0,
                                    ERRARG_XPORT_NOT_IN_PHCI_LIST,
                                    xport_phys, 0);
                                di_fini(root);
                                return (FPCFGA_LIB_ERR);
                        }
                        (void) di_path_addr(path, (char *)phci_addr);
                        if ((phci_addr == NULL) || (*phci_addr == '\0')) {
                                cfga_err(errstring, 0,
                                    ERRARG_XPORT_NOT_IN_PHCI_LIST,
                                    xport_phys, 0);
                                di_devfs_path_free(phci_path);
                                di_fini(root);
                                return (FPCFGA_LIB_ERR);
                        }
                        /*
                         * Check if the phci path has the same
                         * xport addr and the target addr with current lun
                         */
                        if ((strncmp(phci_path, pathname_ptr,
                            strlen(pathname_ptr)) == 0) &&
                            (strstr(phci_addr, dyncomp) != NULL)) {
                                /* SUCCESS Found xport_phys */
                                found = 1;
                        } else if ((di_path_state(path) ==
                            DI_PATH_STATE_ONLINE) ||
                            (di_path_state(path) == DI_PATH_STATE_STANDBY)) {
                                num_active_paths++;
                        } else {
                                /*
                                 * We have another path not in ONLINE/STANDBY
                                 * state now, so should do a RCM online after
                                 * the unconfiguration of current path.
                                 */
                                non_operational_path_count++;
                        }
                        di_devfs_path_free(phci_path);
                }
                di_fini(root);
                if (found == 1) {
                        if (num_active_paths != 0) {
                                /*
                                 * There are other ONLINE/STANDBY paths,
                                 * so no need to do the RCM
                                 */
                                lunlistp->lun_flag |= FLAG_SKIP_RCMREMOVE;
                                lunlistp->lun_flag |= FLAG_SKIP_RCMOFFLINE;
                        }
                        if (non_operational_path_count == 0) {
                                lunlistp->lun_flag |= FLAG_SKIP_ONLINEOTHERS;
                        }
                } else {
                        /*
                         * Fail all operations here
                         */
                        cfga_err(errstring, 0, ERRARG_XPORT_NOT_IN_PHCI_LIST,
                            xport_phys, 0);
                        return (FPCFGA_APID_NOEXIST);
                }
        }

        /* Mark duplicated paths for same vhci in the list */
        for (lunlistp = apidt->lunlist; lunlistp != NULL;
            lunlistp = lunlistp->next) {
                if (strncmp(lunlistp->path, SCSI_VHCI_ROOT,
                    strlen(SCSI_VHCI_ROOT)) != 0) {
                        continue;
                }
                for (temp = lunlistp->next; temp != NULL;
                    temp = temp->next) {
                        if (strcmp(lunlistp->path, temp->path) == 0) {
                                /*
                                 * don't do RCM for dup
                                 */
                                lunlistp->lun_flag |= FLAG_SKIP_RCMREMOVE;
                                lunlistp->lun_flag |= FLAG_SKIP_RCMOFFLINE;
                                lunlistp->lun_flag |= FLAG_SKIP_ONLINEOTHERS;
                        }
                }
        }
        return (FPCFGA_OK);
}
/*
 * apidt->dyncomp has to be non-NULL by the time this routine is called
 */
fpcfga_ret_t
dev_change_state(cfga_cmd_t state_change_cmd, apid_t *apidt, la_wwn_t *pwwn,
                cfga_flags_t flags, char **errstring, HBA_HANDLE handle,
                HBA_PORTATTRIBUTES portAttrs)
{
        char                    dev_path[MAXPATHLEN];
        char                    *update_str, *t_apid;
        int                     optflag = apidt->flags;
        int                     no_config_attempt = 0;
        fpcfga_ret_t            ret;
        apid_t                  my_apidt;
        uchar_t                 unconf_flag = 0, peri_qual;
        HBA_STATUS              status;
        HBA_PORTATTRIBUTES      discPortAttrs;
        uint64_t                lun = 0;
        struct scsi_inquiry     inq;
        struct scsi_extended_sense sense;
        HBA_UINT8               scsiStatus;
        uint32_t                inquirySize = sizeof (inq),
                                senseSize = sizeof (sense);
        report_lun_resp_t       *resp_buf;
        int                     i, l_errno, num_luns = 0;
        uchar_t                 *lun_string;

        if ((apidt->dyncomp == NULL) || (*apidt->dyncomp == '\0')) {
                /*
                 * No dynamic component specified. Just return success.
                 * Should not see this case. Just a safety check.
                 */
                return (FPCFGA_OK);
        }

        /* Now construct the string we are going to put in the repository */
        if ((update_str = calloc(1, (strlen(apidt->xport_phys) +
                strlen(DYN_SEP) + strlen(apidt->dyncomp) + 1))) == NULL) {
                cfga_err(errstring, errno, ERR_MEM_ALLOC, 0);
                return (FPCFGA_LIB_ERR);
        }
        strcpy(update_str, apidt->xport_phys);
        strcat(update_str, DYN_SEP);
        strcat(update_str, apidt->dyncomp);

        /* If force update of repository is sought, do it first */
        if (optflag & FLAG_FORCE_UPDATE_REP) {
                /* Ignore any failure in rep update */
                (void) update_fabric_wwn_list(
                        ((state_change_cmd == CFGA_CMD_CONFIGURE) ?
                        ADD_ENTRY : REMOVE_ENTRY),
                        update_str, errstring);
        }

        memset(&sense, 0, sizeof (sense));
        if ((ret = get_report_lun_data(apidt->xport_phys, apidt->dyncomp,
                &num_luns, &resp_buf, &sense, &l_errno)) != FPCFGA_OK) {
                /*
                 * Checking the sense key data as well as the additional
                 * sense key.  The SES Node is not required to repond
                 * to Report LUN.  In the case of Minnow, the SES node
                 * returns with KEY_ILLEGAL_REQUEST and the additional
                 * sense key of 0x20.  In this case we will blindly
                 * send the SCSI Inquiry call to lun 0
                 *
                 * if we get any other error we will set the inq_type
                 * appropriately
                 */
                if ((sense.es_key == KEY_ILLEGAL_REQUEST) &&
                    (sense.es_add_code == 0x20)) {
                        lun = 0;
                } else {
                        if (ret == FPCFGA_FCP_SEND_SCSI_DEV_NOT_TGT) {
                                inq.inq_dtype = DTYPE_UNKNOWN;
                        } else {
                                /*
                                 * Failed to get the LUN data for the device
                                 * If we find that there is a lunlist for this
                                 * device it could mean that there are dangling
                                 * devinfo nodes. So, we will go ahead and try
                                 * to unconfigure them.
                                 */
                                if ((apidt->lunlist == NULL) ||
                                    (state_change_cmd == CFGA_CMD_CONFIGURE)) {
                                        S_FREE(update_str);
                                        status = getPortAttrsByWWN(handle,
                                            *((HBA_WWN *)(pwwn)),
                                            &discPortAttrs);
                                        if (status ==
                                            HBA_STATUS_ERROR_ILLEGAL_WWN) {
                                                return (FPCFGA_APID_NOEXIST);
                                        } else {
                                                cfga_err(errstring, 0,
                                                    ERRARG_FC_REP_LUNS,
                                                    apidt->dyncomp, 0);
                                                return (FPCFGA_LIB_ERR);
                                        }
                                } else {
                                        /* unconfig with lunlist not empty */
                                        no_config_attempt++;
                                }
                        }
                }
        }
        for (i = 0; i < num_luns; i++) {
                /*
                 * issue the inquiry to the first valid lun found
                 * in the lun_string
                 */
                lun_string = (uchar_t *)&(resp_buf->lun_string[i]);
                memcpy(&lun, lun_string, sizeof (lun));

                memset(&sense, 0, sizeof (sense));
                status = HBA_ScsiInquiryV2(handle, portAttrs.PortWWN,
                    *(HBA_WWN *)(pwwn), lun, 0, 0, &inq, &inquirySize,
                    &scsiStatus, &sense, &senseSize);
                /*
                 * if Inquiry is returned correctly, check the
                 * peripheral qualifier for the lun.  if it is non-zero
                 * then try the SCSI Inquiry on the next lun
                 */
                if (status == HBA_STATUS_OK) {
                        peri_qual = inq.inq_dtype & FP_PERI_QUAL_MASK;
                        if (peri_qual == DPQ_POSSIBLE) {
                                break;
                        }
                }
        }

        if (ret == FPCFGA_OK)
                S_FREE(resp_buf);

        /*
         * If there are no luns on this target, we will attempt to send
         * the SCSI Inquiry to lun 0
         */
        if (num_luns == 0) {
                lun = 0;
                status = HBA_ScsiInquiryV2(handle, portAttrs.PortWWN,
                    *(HBA_WWN *)(pwwn), lun, 0, 0, &inq, &inquirySize,
                    &scsiStatus, &sense, &senseSize);
        }

        if (status != HBA_STATUS_OK) {
                if (status ==  HBA_STATUS_ERROR_NOT_A_TARGET) {
                        inq.inq_dtype = DTYPE_UNKNOWN;
                } else if (status ==  HBA_STATUS_ERROR_ILLEGAL_WWN) {
                        free(update_str);
                        return (FPCFGA_APID_NOEXIST);
                } else {
                        /*
                         * Failed to get the inq_dtype of device
                         * If we find that there is a lunlist for this
                         * device it could mean that there dangling
                         * devinfo nodes. So, we will go ahead and try
                         * to unconfigure them.  We'll just set the
                         * inq_dtype to some invalid value (0xFF)
                         */
                        if ((apidt->lunlist == NULL) ||
                            (state_change_cmd == CFGA_CMD_CONFIGURE)) {
                                cfga_err(errstring, 0,
                                    ERRARG_FC_INQUIRY,
                                    apidt->dyncomp, 0);
                                free(update_str);
                                return (FPCFGA_LIB_ERR);
                        } else {
                                /* unconfig with lunlist not empty */
                                no_config_attempt++;
                        }
                }
        }
        switch (state_change_cmd) {
        case CFGA_CMD_CONFIGURE:
            if (portAttrs.PortType != HBA_PORTTYPE_NLPORT &&
                portAttrs.PortType != HBA_PORTTYPE_NPORT) {
                free(update_str);
                return (FPCFGA_OK);
            }

            if (((inq.inq_dtype & DTYPE_MASK) == DTYPE_UNKNOWN) &&
                ((flags & CFGA_FLAG_FORCE) == 0)) {
                /*
                 * We assume all DTYPE_UNKNOWNs are HBAs and we wont
                 * waste time trying to config them. If they are not
                 * HBAs, then there is something wrong since they should
                 * have had a valid dtype.
                 *
                 * However, if the force flag is set (cfgadm -f), we
                 * go ahead and try to configure.
                 *
                 * In this path, however, the force flag is not set.
                 */
                free(update_str);
                return (FPCFGA_OK);
            }

            errno = 0;
                /*
                 * We'll issue the devctl_bus_dev_create() call even if the
                 * path exists in the devinfo tree. This is to take care of
                 * the situation where the device may be in a state other
                 * than the online and attached state.
                 */
            if ((ret = do_devctl_dev_create(apidt, dev_path, MAXPATHLEN,
                        inq.inq_dtype, errstring)) != FPCFGA_OK) {
                /*
                 * Could not configure device. To provide a more
                 * meaningful error message, first see if the supplied port
                 * WWN is there on the fabric. Otherwise print the error
                 * message using the information received from the driver
                 */
                status = getPortAttrsByWWN(handle, *((HBA_WWN *)(pwwn)),
                    &discPortAttrs);
                S_FREE(update_str);
                if (status == HBA_STATUS_ERROR_ILLEGAL_WWN) {
                        return (FPCFGA_APID_NOEXIST);
                } else {
                        return (FPCFGA_LIB_ERR);
                }
            }

            if (((optflag & (FLAG_FORCE_UPDATE_REP|FLAG_NO_UPDATE_REP)) == 0) &&
                update_fabric_wwn_list(ADD_ENTRY, update_str, errstring)) {
                    cfga_err(errstring, 0, ERR_CONF_OK_UPD_REP, 0);
            }

            S_FREE(update_str);

            if ((apidt->flags & FLAG_DISABLE_RCM) == 0) {
                /*
                 * There may be multiple LUNs associated with the
                 * WWN we created nodes for. So, we'll call
                 * apidt_create() again and let it build a list of
                 * all the LUNs for this WWN using the devinfo tree.
                 * We will then online all those devices in RCM
                 */
                    if ((t_apid = calloc(1, strlen(apidt->xport_phys) +
                                        strlen(DYN_SEP) +
                                        strlen(apidt->dyncomp) + 1)) == NULL) {
                            cfga_err(errstring, errno, ERR_MEM_ALLOC, 0);
                            return (FPCFGA_LIB_ERR);
                    }
                    sprintf(t_apid, "%s%s%s", apidt->xport_phys, DYN_SEP,
                        apidt->dyncomp);
                    if ((ret = apidt_create(t_apid, &my_apidt,
                                        errstring)) != FPCFGA_OK) {
                            free(t_apid);
                            return (ret);
                    }

                    my_apidt.flags = apidt->flags;
                    if ((ret = dev_rcm_online(&my_apidt, -1, flags,
                                        NULL)) != FPCFGA_OK) {
                            cfga_err(errstring, 0, ERRARG_RCM_ONLINE,
                                apidt->lunlist->path, 0);
                            apidt_free(&my_apidt);
                            free(t_apid);
                            return (ret);
                    }
                    S_FREE(t_apid);
                    apidt_free(&my_apidt);
            }
            return (FPCFGA_OK);

        case CFGA_CMD_UNCONFIGURE:
                if (portAttrs.PortType != HBA_PORTTYPE_NLPORT &&
                    portAttrs.PortType != HBA_PORTTYPE_NPORT) {
                        free(update_str);
                        return (FPCFGA_OPNOTSUPP);
                }

                status = getPortAttrsByWWN(handle, *((HBA_WWN *)(pwwn)),
                    &discPortAttrs);
                if (apidt->lunlist == NULL) {
                        /*
                         * But first, remove entry from the repository if it is
                         * there ... provided the force update flag is not set
                         * (in which case the update is already done) or if
                         * the no-update flag is not set.
                         */
                        if ((optflag &
                        (FLAG_FORCE_UPDATE_REP|FLAG_NO_UPDATE_REP)) == 0) {
                                if (update_fabric_wwn_list(REMOVE_ENTRY,
                                                update_str, errstring)) {
                                        free(update_str);
                                        cfga_err(errstring, 0,
                                                ERR_UNCONF_OK_UPD_REP, 0);
                                        return
                                        (FPCFGA_UNCONF_OK_UPD_REP_FAILED);
                                }
                        }
                        S_FREE(update_str);
                        if (status == HBA_STATUS_ERROR_ILLEGAL_WWN) {
                                return (FPCFGA_APID_NOEXIST);
                        }
                        return (FPCFGA_OK);
                }
                /*
                 * If there are multiple paths to the mpxio
                 * device, we will not check in RCM ONLY when there
                 * is atleast one other ONLINE/STANDBY path
                 */
                if (is_xport_phys_in_pathlist(apidt, errstring) !=
                    FPCFGA_OK) {
                        free(update_str);
                        return (FPCFGA_XPORT_NOT_IN_PHCI_LIST);
                }

                /*
                 * dev_rcm_offline() updates errstring
                 */
                if ((ret = dev_rcm_offline(apidt, flags, errstring)) !=
                    FPCFGA_OK) {
                        free(update_str);
                        return (ret);
                }
                if ((ret = dev_unconf(apidt, errstring, &unconf_flag)) !=
                    FPCFGA_OK) {
                        /* when inq failed don't attempt to reconfigure */
                    if (!no_config_attempt) {
                        (void) do_devctl_dev_create(apidt, dev_path, MAXPATHLEN,
                                inq.inq_dtype, NULL);
                        (void) dev_rcm_online(apidt, -1, flags, NULL);
                    }
                    free(update_str);
                    return (ret);
                }
                if ((ret = dev_rcm_remove(apidt, flags, errstring)) !=
                    FPCFGA_OK) {
                        (void) do_devctl_dev_create(apidt, dev_path, MAXPATHLEN,
                                inq.inq_dtype, NULL);
                        (void) dev_rcm_online(apidt, -1, flags, NULL);
                        free(update_str);
                        return (ret);
                }
                /*
                 * If we offlined a lun in RCM when there are multiple paths but
                 * none of them are ONLINE/STANDBY, we have to online it back
                 * in RCM now. This is a try best, will not fail for it.
                 */
                dev_rcm_online_nonoperationalpath(apidt, flags, NULL);

                /* Update the repository if we havent already done it */
                if ((optflag &
                        (FLAG_FORCE_UPDATE_REP|FLAG_NO_UPDATE_REP)) == 0) {
                        if (((optflag & FLAG_REMOVE_UNUSABLE_FCP_DEV) !=
                                    FLAG_REMOVE_UNUSABLE_FCP_DEV) ||
                            (((optflag & FLAG_REMOVE_UNUSABLE_FCP_DEV) ==
                                FLAG_REMOVE_UNUSABLE_FCP_DEV) &&
                            (unconf_flag == ALL_APID_LUNS_UNUSABLE))) {
                                if (update_fabric_wwn_list(REMOVE_ENTRY,
                                            update_str, errstring)) {
                                    free(update_str);
                                    cfga_err(errstring, errno,
                                        ERR_UNCONF_OK_UPD_REP, 0);
                                    return (FPCFGA_UNCONF_OK_UPD_REP_FAILED);
                                }
                        }
                }
                free(update_str);
                return (FPCFGA_OK);

        default:
                free(update_str);
                return (FPCFGA_OPNOTSUPP);
        }
}

/*
 * This function copies a port_wwn got by reading the property on a device
 * node (from_ptr in the function below) on to an array (to_ptr) so that it is
 * readable.
 *
 * Caller responsible to allocate enough memory in "to_ptr"
 */
static void
copy_pwwn_data_to_str(char *to_ptr, const uchar_t *from_ptr)
{
        if ((to_ptr == NULL) || (from_ptr == NULL))
                return;

        (void) sprintf(to_ptr, "%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x",
        from_ptr[0], from_ptr[1], from_ptr[2], from_ptr[3],
        from_ptr[4], from_ptr[5], from_ptr[6], from_ptr[7]);
}

static fpcfga_ret_t
unconf_vhci_nodes(di_path_t pnode, di_node_t fp_node, char *xport_phys,
        char *dyncomp, int unusable_flag,
        int *num_devs, int *failure_count, char **errstring,
        cfga_flags_t flags)
{
        int             iret1, iret2, *lunnump;
        char            *ptr;           /* scratch pad */
        char            *node_path, *vhci_path, *update_str;
        char            port_wwn[WWN_SIZE*2+1], pathname[MAXPATHLEN];
        uchar_t         *port_wwn_data = NULL;
        di_node_t       client_node;

        while (pnode != DI_PATH_NIL) {

                (*num_devs)++;


                if ((node_path = di_devfs_path(fp_node)) == NULL) {
                        cfga_err(errstring, 0, ERRARG_DEVINFO,
                                                        xport_phys, 0);
                        (*failure_count)++;
                        pnode = di_path_next_client(fp_node, pnode);
                        continue;
                }

                iret1 = di_path_prop_lookup_bytes(pnode, PORT_WWN_PROP,
                        &port_wwn_data);

                iret2 = di_path_prop_lookup_ints(pnode, LUN_PROP, &lunnump);

                if ((iret1 == -1) || (iret2 == -1)) {
                        cfga_err(errstring, 0, ERRARG_DI_GET_PROP,
                                                                node_path, 0);
                        di_devfs_path_free(node_path);
                        node_path = NULL;
                        (*failure_count)++;
                        pnode = di_path_next_client(fp_node, pnode);
                        continue;
                }

                copy_pwwn_data_to_str(port_wwn, port_wwn_data);

                if ((client_node = di_path_client_node(pnode)) ==
                                                                DI_NODE_NIL) {
                        (*failure_count)++;
                        di_devfs_path_free(node_path);
                        node_path = NULL;
                        pnode = di_path_next_client(fp_node, pnode);
                        continue;
                }

                if ((vhci_path = di_devfs_path(client_node)) == NULL) {
                        (*failure_count)++;
                        di_devfs_path_free(node_path);
                        node_path = NULL;
                        pnode = di_path_next_client(fp_node, pnode);
                        continue;
                }

                if ((ptr = strrchr(vhci_path, '@')) != NULL) {
                        *ptr = '\0';
                }

                if ((ptr = strrchr(vhci_path, '/')) == NULL) {
                        (*failure_count)++;
                        di_devfs_path_free(node_path);
                        node_path = NULL;
                        pnode = di_path_next_client(fp_node, pnode);
                        continue;
                }

                sprintf(pathname, "%s%s/%s@w%s,%x", DEVICES_DIR, node_path,
                                        ++ptr, port_wwn, *lunnump);

                di_devfs_path_free(node_path);
                di_devfs_path_free(vhci_path);
                node_path = vhci_path = NULL;

                /*
                 * Try to offline in RCM first and if that is successful,
                 * unconfigure the LUN. If offlining in RCM fails, then
                 * update the failure_count which gets passed back to caller
                 *
                 * Here we got to check if unusable_flag is set or not.
                 * If set, then unconfigure only those luns which are in
                 * node_state DI_PATH_STATE_OFFLINE. If not set, unconfigure
                 * all luns.
                 */
                if ((unusable_flag & FLAG_REMOVE_UNUSABLE_FCP_DEV) ==
                    FLAG_REMOVE_UNUSABLE_FCP_DEV) {
                        if (pnode->path_state == DI_PATH_STATE_OFFLINE) {
                                if (fp_rcm_offline(pathname, errstring,
                                    flags) != 0) {
                                        (*failure_count)++;
                                        pnode = di_path_next_client(fp_node,
                                            pnode);
                                        continue;
                                } else if (lun_unconf(pathname, *lunnump,
                                    xport_phys,dyncomp, errstring)
                                    != FPCFGA_OK) {
                                        (void) fp_rcm_online(pathname,
                                            NULL, flags);
                                        (*failure_count)++;
                                        pnode = di_path_next_client(fp_node,
                                            pnode);
                                        continue;
                                } else if (fp_rcm_remove(pathname, errstring,
                                    flags) != 0) {
                                        /*
                                         * Bring everything back online
                                         * in rcm and continue
                                         */
                                        (void) fp_rcm_online(pathname,
                                            NULL, flags);
                                        (*failure_count)++;
                                        pnode = di_path_next_client(fp_node,
                                            pnode);
                                        continue;
                                }
                        } else {
                                pnode = di_path_next(fp_node, pnode);
                                continue;
                        }
                } else {
                        if (fp_rcm_offline(pathname, errstring, flags) != 0) {
                                (*failure_count)++;
                                pnode = di_path_next_client(fp_node, pnode);
                                continue;
                        } else if (lun_unconf(pathname, *lunnump, xport_phys,
                            dyncomp, errstring) != FPCFGA_OK) {
                                (void) fp_rcm_online(pathname, NULL, flags);
                                (*failure_count)++;
                                pnode = di_path_next_client(fp_node, pnode);
                                continue;
                        } else if (fp_rcm_remove(pathname, errstring,
                            flags) != 0) {
                                /*
                                 * Bring everything back online
                                 * in rcm and continue
                                 */
                                (void) fp_rcm_online(pathname, NULL, flags);
                                (*failure_count)++;
                                pnode = di_path_next_client(fp_node, pnode);
                                continue;
                        }
                }

                /* Update the repository only on a successful unconfigure */
                if ((update_str = calloc(1, strlen(xport_phys) +
                                        strlen(DYN_SEP) +
                                        strlen(port_wwn) + 1)) == NULL) {
                        cfga_err(errstring, errno, ERR_UNCONF_OK_UPD_REP, 0);
                        (*failure_count)++;
                        pnode = di_path_next_client(fp_node, pnode);
                        continue;
                }

                /* Init the string to be removed from repository */
                sprintf(update_str, "%s%s%s", xport_phys, DYN_SEP, port_wwn);

                if (update_fabric_wwn_list(REMOVE_ENTRY, update_str,
                                                                errstring)) {
                        S_FREE(update_str);
                        cfga_err(errstring, errno,
                                        ERR_UNCONF_OK_UPD_REP, 0);
                        (*failure_count)++;
                        /* Cleanup and continue from here just for clarity */
                        pnode = di_path_next_client(fp_node, pnode);
                        continue;
                }

                S_FREE(update_str);
                pnode = di_path_next_client(fp_node, pnode);
        }

        return (FPCFGA_OK);
}

static fpcfga_ret_t
unconf_non_vhci_nodes(di_node_t dnode, char *xport_phys, char *dyncomp,
        int unusable_flag, int *num_devs, int *failure_count,
        char **errstring, cfga_flags_t flags)
{
        int     ret1, ret2, *lunnump;
        char    pathname[MAXPATHLEN];
        char    *node_path, *update_str;
        char    port_wwn[WWN_SIZE*2+1];
        uchar_t *port_wwn_data = NULL;

        while (dnode != DI_NODE_NIL) {

                (*num_devs)++;

                /* Get the physical path for this node */
                if ((node_path = di_devfs_path(dnode)) == NULL) {
                        /*
                         * We don't try to offline in RCM here because we
                         * don't know the path to offline. Just continue to
                         * the next node.
                         */
                        cfga_err(errstring, 0, ERRARG_DEVINFO, xport_phys, 0);
                        (*failure_count)++;
                        dnode = di_sibling_node(dnode);
                        continue;
                }

                /* Now get the LUN # of this device thru the property */
                ret1 = di_prop_lookup_ints(DDI_DEV_T_ANY, dnode,
                                                        LUN_PROP, &lunnump);

                /* Next get the port WWN of the device */
                ret2 = di_prop_lookup_bytes(DDI_DEV_T_ANY, dnode,
                                                PORT_WWN_PROP, &port_wwn_data);

                /* A failure in any of the above is not good */
                if ((ret1 == -1) || (ret2 == -1)) {
                        /*
                         * We don't try to offline in RCM here because we
                         * don't know the path to offline. Just continue to
                         * the next node.
                         */
                        cfga_err(errstring, 0,
                                        ERRARG_DI_GET_PROP, node_path, 0);
                        di_devfs_path_free(node_path);
                        node_path = NULL;
                        (*failure_count)++;
                        dnode = di_sibling_node(dnode);
                        continue;
                }

                /* Prepend the "/devices" prefix to the path and copy it */
                sprintf(pathname, "%s%s", DEVICES_DIR, node_path);
                di_devfs_path_free(node_path);
                node_path = NULL;

                copy_pwwn_data_to_str(port_wwn, port_wwn_data);

                if (strstr(pathname, "@w") == NULL) {
                        /*
                         * If the driver is detached, some part of the path
                         * may be missing and so we'll manually construct it
                         */
                        sprintf(&pathname[strlen(pathname)], "@w%s,%x",
                                                        port_wwn, *lunnump);
                }

                /*
                 * Try to offline in RCM first and if that is successful,
                 * unconfigure the LUN. If offlining in RCM fails, then
                 * update the failure count
                 *
                 * Here we got to check if unusable_flag is set or not.
                 * If set, then unconfigure only those luns which are in
                 * node_state DI_DEVICE_OFFLINE or DI_DEVICE_DOWN.
                 * If not set, unconfigure all luns.
                 */
                if ((unusable_flag & FLAG_REMOVE_UNUSABLE_FCP_DEV) ==
                    FLAG_REMOVE_UNUSABLE_FCP_DEV) {
                        if ((dnode->node_state == DI_DEVICE_OFFLINE) ||
                            (dnode->node_state == DI_DEVICE_DOWN)) {
                                if (fp_rcm_offline(pathname, errstring,
                                    flags) != 0) {
                                        (*failure_count)++;
                                        dnode = di_sibling_node(dnode);
                                        continue;
                                } else if (lun_unconf(pathname, *lunnump,
                                    xport_phys,dyncomp, errstring)
                                    != FPCFGA_OK) {
                                        (void) fp_rcm_online(pathname,
                                            NULL, flags);
                                        (*failure_count)++;
                                        dnode = di_sibling_node(dnode);
                                        continue;
                                } else if (fp_rcm_remove(pathname, errstring,
                                    flags) != 0) {
                                        /*
                                         * Bring everything back online
                                         * in rcm and continue
                                         */
                                        (void) fp_rcm_online(pathname,
                                            NULL, flags);
                                        (*failure_count)++;
                                        dnode = di_sibling_node(dnode);
                                        continue;
                                }
                        } else {
                                dnode = di_sibling_node(dnode);
                                continue;
                        }
                } else {
                        if (fp_rcm_offline(pathname, errstring, flags) != 0) {
                                (*failure_count)++;
                                dnode = di_sibling_node(dnode);
                                continue;
                        } else if (lun_unconf(pathname, *lunnump, xport_phys,
                            dyncomp, errstring) != FPCFGA_OK) {
                                (void) fp_rcm_online(pathname, NULL, flags);
                                (*failure_count)++;
                                dnode = di_sibling_node(dnode);
                                continue;
                        } else if (fp_rcm_remove(pathname, errstring,
                            flags) != 0) {
                                /*
                                 * Bring everything back online
                                 * in rcm and continue
                                 */
                                (void) fp_rcm_online(pathname, NULL, flags);
                                (*failure_count)++;
                                dnode = di_sibling_node(dnode);
                                continue;
                        }
                }

                /* Update the repository only on a successful unconfigure */
                if ((update_str = calloc(1, strlen(xport_phys) +
                                        strlen(DYN_SEP) +
                                        strlen(port_wwn) + 1)) == NULL) {
                        cfga_err(errstring, errno, ERR_UNCONF_OK_UPD_REP, 0);
                        (*failure_count)++;
                        dnode = di_sibling_node(dnode);
                        continue;
                }

                /* Init the string to be removed from repository */
                sprintf(update_str, "%s%s%s", xport_phys, DYN_SEP, port_wwn);

                if (update_fabric_wwn_list(REMOVE_ENTRY, update_str,
                                                                errstring)) {
                        S_FREE(update_str);
                        cfga_err(errstring, errno, ERR_UNCONF_OK_UPD_REP, 0);
                        (*failure_count)++;
                        dnode = di_sibling_node(dnode);
                        continue;
                }

                S_FREE(update_str);
                dnode = di_sibling_node(dnode);
        }

        return (FPCFGA_OK);
}

/*
 * INPUT:
 * apidt - Pointer to apid_t structure with data filled in
 * flags - Flags for special handling
 *
 * OUTPUT:
 * errstring - Applicable only on a failure from plugin
 * num_devs  - Incremented per lun
 * failure_count - Incremented on any failed operation on lun
 *
 * RETURNS:
 * non-FPCFGA_OK on any validation check error. If this value is returned, no
 *             devices were handled.  Consequently num_devs and failure_count
 *             will not be incremented.
 * FPCFGA_OK This return value doesn't mean that all devices were successfully
 *             unconfigured, you have to check failure_count.
 */
static fpcfga_ret_t
unconf_any_devinfo_nodes(apid_t *apidt, cfga_flags_t flags, char **errstring,
                                int *num_devs, int *failure_count)
{
        char            *node_path = NULL;
        char            pathname[MAXPATHLEN], *ptr;     /* scratch pad */
        di_node_t       root_node, direct_node, fp_node;
        di_path_t       path_node = DI_PATH_NIL;

        /*
         * apidt->xport_phys is something like :
         * /devices/pci@.../SUNW,qlc@../fp@0,0:fc
         * Make sure we copy both the devinfo and pathinfo nodes
         */
        (void) strlcpy(pathname, apidt->xport_phys, MAXPATHLEN);

        /* Now get rid of the ':' at the end */
        if ((ptr = strstr(pathname, MINOR_SEP)) != NULL)
                *ptr = '\0';

        if (strncmp(pathname, DEVICES_DIR, strlen(DEVICES_DIR))) {
                cfga_err(errstring, 0, ERRARG_INVALID_PATH, pathname, 0);
                return (FPCFGA_INVALID_PATH);
        }

        if ((root_node = di_init("/", DINFOCPYALL | DINFOPATH)) ==
                                                                DI_NODE_NIL) {
                cfga_err(errstring, errno, ERRARG_DEVINFO,
                                                        apidt->xport_phys, 0);
                return (FPCFGA_LIB_ERR);
        }

        if ((fp_node = di_drv_first_node("fp", root_node)) == DI_NODE_NIL) {
                cfga_err(errstring, errno, ERRARG_DEVINFO,
                                                        apidt->xport_phys, 0);
                di_fini(root_node);
                return (FPCFGA_LIB_ERR);
        }

        /*
         * Search all the fp nodes to see if any match the one we are trying
         * to unconfigure
         */

        /* Skip the "/devices" prefix */
        ptr = pathname + strlen(DEVICES_DIR);

        while (fp_node != DI_NODE_NIL) {
                node_path = di_devfs_path(fp_node);
                if (strcmp(node_path, ptr) == 0) {
                        /* Found the fp node. 'pathname' has the full path */
                        di_devfs_path_free(node_path);
                        node_path = NULL;
                        break;
                }
                fp_node = di_drv_next_node(fp_node);
                di_devfs_path_free(node_path);
        }

        if (fp_node == DI_NODE_NIL) {
                cfga_err(errstring, 0, ERRARG_NOT_IN_DEVINFO,
                                                        apidt->xport_phys, 0);
                di_fini(root_node);
                return (FPCFGA_LIB_ERR);
        }

        direct_node = di_child_node(fp_node);
        path_node = di_path_next_client(fp_node, path_node);

        if ((direct_node == DI_NODE_NIL) && (path_node == DI_PATH_NIL)) {
                /* No devinfo or pathinfo nodes. Great ! Just return success */
                di_fini(root_node);
                return (FPCFGA_OK);
        }

        /* First unconfigure any non-MPXIO nodes */
        unconf_non_vhci_nodes(direct_node, apidt->xport_phys, apidt->dyncomp,
            apidt->flags, num_devs, failure_count, errstring, flags);

        /*
         * Now we will traverse any path info nodes that are there
         *
         * Only MPXIO devices have pathinfo nodes
         */
        unconf_vhci_nodes(path_node, fp_node, apidt->xport_phys, apidt->dyncomp,
            apidt->flags, num_devs, failure_count, errstring, flags);

        di_fini(root_node);

        /*
         * We don't want to check the return value of unconf_non_vhci_nodes()
         * and unconf_vhci_nodes().  But instead, we are interested only in
         * consistently incrementing num_devs and failure_count so that we can
         * compare them.
         */
        return (FPCFGA_OK);
}

/*
 * This function handles configuring/unconfiguring all the devices w.r.t
 * the FCA port specified by apidt.
 *
 * In the unconfigure case, it first unconfigures all the devices that are
 * seen through the given port at that moment and then unconfigures all the
 * devices that still (somehow) have devinfo nodes on the system for that FCA
 * port.
 *
 * INPUT:
 * cmd - CFGA_CMD_CONFIGURE or CFGA_CMD_UNCONFIGURE
 * apidt - Pointer to apid_t structure with data filled in
 * flags - Flags for special handling
 *
 * OUTPUT:
 * errstring - Applicable only on a failure from plugin
 *
 * RETURNS:
 * FPCFGA_OK on success
 * non-FPCFGA_OK otherwise
 */
static fpcfga_ret_t
handle_devs(cfga_cmd_t cmd, apid_t *apidt, cfga_flags_t flags,
        char **errstring, HBA_HANDLE handle, int portIndex,
        HBA_PORTATTRIBUTES portAttrs)
{
        int             num_devs = 0, dev_cs_failed = 0;
        char            port_wwn[WWN_S_LEN];
        la_wwn_t        pwwn;
        apid_t          my_apidt = {NULL};
        char            *my_apid;
        HBA_PORTATTRIBUTES      discPortAttrs;
        int                     discIndex;
        fpcfga_ret_t            rval = FPCFGA_OK;

        if ((my_apid = calloc(
                1, strlen(apidt->xport_phys) + strlen(DYN_SEP) +
                (2 * FC_WWN_SIZE) + 1)) == NULL) {
                cfga_err(errstring, errno, ERR_MEM_ALLOC, 0);
                return (FPCFGA_LIB_ERR);
        }

        num_devs = portAttrs.NumberofDiscoveredPorts;
        for (discIndex = 0; discIndex < portAttrs.NumberofDiscoveredPorts;
                discIndex++) {
            if (getDiscPortAttrs(handle, portIndex,
                discIndex, &discPortAttrs)) {
                dev_cs_failed++;
                /* Move on to the next target */
                continue;
            }
            (void) sprintf(port_wwn, "%016llx",
                wwnConversion(discPortAttrs.PortWWN.wwn));
                /*
                 * Construct a fake apid string similar to the one the
                 * plugin gets from the framework and have apidt_create()
                 * fill in the apid_t structure.
                 */
            strcpy(my_apid, apidt->xport_phys);
            strcat(my_apid, DYN_SEP);
            strcat(my_apid, port_wwn);
            if (apidt_create(my_apid, &my_apidt, errstring) != FPCFGA_OK) {
                dev_cs_failed++;
                continue;
            }
            my_apidt.flags = apidt->flags;

            memcpy(&pwwn, &(discPortAttrs.PortWWN), sizeof (la_wwn_t));
            if (dev_change_state(cmd, &my_apidt, &pwwn,
                flags, errstring, handle, portAttrs) != FPCFGA_OK) {
                dev_cs_failed++;
            }
            apidt_free(&my_apidt);
        }

        S_FREE(my_apid);

        /*
         * We have now handled all the devices that are currently visible
         * through the given FCA port. But, it is possible that there are
         * some devinfo nodes hanging around. For the unconfigure operation,
         * this has to be looked into too.
         */
        if (cmd == CFGA_CMD_UNCONFIGURE) {
                /* dev_cs_failed will be updated to indicate any failures */
                rval = unconf_any_devinfo_nodes(apidt, flags, errstring,
                    &num_devs, &dev_cs_failed);
        }

        if (rval == FPCFGA_OK) {
                if (dev_cs_failed == 0)
                        return (FPCFGA_OK);

                /*
                 * For the discovered ports, num_devs is counted on target
                 * basis, but for invisible targets, num_devs is counted on
                 * lun basis.
                 *
                 * But if dev_cs_failed and num_devs are incremented
                 * consistently, comparation of these two counters is still
                 * meaningful.
                 */
                if (dev_cs_failed == num_devs) {
                        /* Failed on all devices seen through this FCA port */
                        cfga_err(errstring, 0,
                        ((cmd == CFGA_CMD_CONFIGURE) ?
                                ERR_FCA_CONFIGURE : ERR_FCA_UNCONFIGURE), 0);
                        return (FPCFGA_LIB_ERR);
                } else {
                        /* Failed only on some of the devices */
                        cfga_err(errstring, 0, ERR_PARTIAL_SUCCESS, 0);
                        return (FPCFGA_LIB_ERR);
                }
        } else {
                if (dev_cs_failed == num_devs) {
                        /* Failed on all devices seen through this FCA port */
                        cfga_err(errstring, 0,
                        ((cmd == CFGA_CMD_CONFIGURE) ?
                                ERR_FCA_CONFIGURE : ERR_FCA_UNCONFIGURE), 0);
                        return (FPCFGA_LIB_ERR);
                } else {
                        /* Failed only on some of the devices */
                        cfga_err(errstring, 0, ERR_PARTIAL_SUCCESS, 0);
                        return (FPCFGA_LIB_ERR);
                }
        }

        /*
         * Should never get here
         */
}

fpcfga_ret_t
fca_change_state(cfga_cmd_t state_change_cmd, apid_t *apidt,
                cfga_flags_t flags, char **errstring)
{
        fpcfga_ret_t    ret;
        HBA_HANDLE      handle;
        HBA_PORTATTRIBUTES      portAttrs;
        int                     portIndex;

        if ((ret = findMatchingAdapterPort(apidt->xport_phys, &handle,
            &portIndex, &portAttrs, errstring)) != FPCFGA_OK) {
                return (ret);
        }

        /*
         * Bail out if not fabric/public loop
         */
        switch (state_change_cmd) {
        case CFGA_CMD_CONFIGURE:
            if (portAttrs.PortType != HBA_PORTTYPE_NLPORT &&
                portAttrs.PortType != HBA_PORTTYPE_NPORT) {
                        HBA_CloseAdapter(handle);
                        HBA_FreeLibrary();
                        return (FPCFGA_OK);
            }
            break;

        case CFGA_CMD_UNCONFIGURE:
            if (portAttrs.PortType != HBA_PORTTYPE_NLPORT &&
                portAttrs.PortType != HBA_PORTTYPE_NPORT) {
                HBA_CloseAdapter(handle);
                HBA_FreeLibrary();
                return (FPCFGA_OPNOTSUPP);
            }
            break;
        default:
                HBA_CloseAdapter(handle);
                HBA_FreeLibrary();
                return (FPCFGA_LIB_ERR);
        }
        ret = (handle_devs(state_change_cmd, apidt, flags, errstring,
            handle, portIndex, portAttrs));
        HBA_CloseAdapter(handle);
        HBA_FreeLibrary();
        return (ret);
}