/*
 * 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 = NULL;
	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);
}