diff options
| author | John Forte <John.Forte@Sun.COM> | 2008-10-14 15:09:13 -0700 |
|---|---|---|
| committer | John Forte <John.Forte@Sun.COM> | 2008-10-14 15:09:13 -0700 |
| commit | fcf3ce441efd61da9bb2884968af01cb7c1452cc (patch) | |
| tree | 0e80d59ad41702571586195bf099ccc14222ce02 /usr/src/lib/cfgadm_plugins | |
| parent | 247b82a1f1cb5ebd2d163bd9afdb1a3065611962 (diff) | |
| download | illumos-joyent-fcf3ce441efd61da9bb2884968af01cb7c1452cc.tar.gz | |
6745433 Merge NWS consolidation into OS/Net consolidation
Diffstat (limited to 'usr/src/lib/cfgadm_plugins')
19 files changed, 10207 insertions, 1 deletions
diff --git a/usr/src/lib/cfgadm_plugins/Makefile b/usr/src/lib/cfgadm_plugins/Makefile index 7611450bcc..39543eabd3 100644 --- a/usr/src/lib/cfgadm_plugins/Makefile +++ b/usr/src/lib/cfgadm_plugins/Makefile @@ -27,7 +27,7 @@ include $(SRC)/Makefile.master -COMMON_SUBDIRS= scsi sdcard pci usb ib +COMMON_SUBDIRS= scsi sdcard pci usb ib fp sparc_SUBDIRS= sbd ac sysctrl i386_SUBDIRS= sata diff --git a/usr/src/lib/cfgadm_plugins/fp/Makefile b/usr/src/lib/cfgadm_plugins/fp/Makefile new file mode 100644 index 0000000000..22bf6ff9df --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/Makefile @@ -0,0 +1,73 @@ +# +# 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 +# +# lib/cfgadm_plugins/fp/Makefile +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +include ../../Makefile.lib + +SUBDIRS = $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET= all +clean := TARGET= clean +clobber := TARGET= clobber +install := TARGET= install +lint := TARGET= lint +_msg := TARGET= _msg + +TEXT_DOMAIN= SUNW_OST_OSLIB +XGETFLAGS= -a -x fp.xcl +POFILE= fp.po +POFILES= generic.po + +SED= sed +GREP= grep +CP= cp + +.KEEP_STATE: + +all lint clean clobber delete install package: $(SUBDIRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +_msg: $(MSGDOMAIN) $(POFILE) + $(RM) $(MSGDOMAIN)/$(POFILE) + $(CP) $(POFILE) $(MSGDOMAIN) + +$(POFILE): $(POFILES) + $(RM) $@ + $(CAT) $(POFILES) > $@ + +$(POFILES): + $(RM) messages.po + $(XGETTEXT) $(XGETFLAGS) `$(GREP) -l gettext */*.[ch]` + $(SED) -e '/^# msg/d' -e '/^domain/d' messages.po > $@ + $(RM) messages.po + +FRC: + +include ../../Makefile.targ diff --git a/usr/src/lib/cfgadm_plugins/fp/Makefile.com b/usr/src/lib/cfgadm_plugins/fp/Makefile.com new file mode 100644 index 0000000000..02697d0db7 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/Makefile.com @@ -0,0 +1,101 @@ +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +LIBRARY= fp.a +VERS= .1 + +OBJECTS = cfga_fp.o cfga_rcm.o cfga_cs.o cfga_utils.o +OBJECTS += cfga_cvt.o cfga_list.o cfga_rep.o + +# include library definitions +include ../../../Makefile.lib + +SRCDIR = ../common +ROOTLIBDIR= $(ROOT)/usr/lib/cfgadm +ROOTLIBDIR64= $(ROOTLIBDIR)/$(MACH64) + +LIBS= $(DYNLIB) + +LINTFLAGS += -DDEBUG +LINTFLAGS64 += -DDEBUG + +CFLAGS += $(CCVERBOSE) +CFLAGS64 += $(CCVERBOSE) + +CPPFLAGS += -D_POSIX_PTHREAD_SEMANTICS + +LINTFLAGS += -erroff=E_SEC_SPRINTF_UNBOUNDED_COPY +LINTFLAGS += -erroff=E_FUNC_RET_MAYBE_IGNORED2 +LINTFLAGS += -erroff=E_FUNC_RET_ALWAYS_IGNOR2 +LINTFLAGS += -erroff=E_BAD_FORMAT_ARG_TYPE2 +LINTFLAGS += -erroff=E_SEC_CREAT_WITHOUT_MODE +LINTFLAGS64 += $(LINTFLAGS) + +LDLIBS += -lc -ldevice -ldevinfo -lrcm +LDLIBS += -lHBAAPI -lgen + +MANIFEST= devices-fc-fabric.xml +ROOTMANIFESTDIR= $(ROOT)/var/svc/manifest/system/device +ROOTMANIFEST= $(MANIFEST:%=$(ROOTMANIFESTDIR)/%) +$(ROOTMANIFEST) := FILEMODE= 444 + +SVCMETHOD= fc-fabric +ROOTSVCMETHODDIR= $(ROOT)/lib/svc/method +ROOTSVCMETHOD= $(SVCMETHOD:%=$(ROOTSVCMETHODDIR)/%) +$(ROOTSVCMETHOD):= FILEMODE= 555 + +.KEEP_STATE: + +all: $(LIBS) + +lint: lintcheck + +# Install rules + +$(ROOTLIBDIR)/%: % $(ROOTLIBDIR) + $(INS.file) + +$(ROOTLIBDIR64)/%: % $(ROOTLIBDIR64) + $(INS.file) + +$(ROOTLIBDIR) $(ROOTLIBDIR64): + $(INS.dir) + +$(ROOTMANIFESTDIR)/%: ../common/% $(ROOTMANIFESTDIR) + $(INS.file) + +$(ROOTSVCMETHODDIR)/%: ../common/% $(ROOTSVCMETHODDIR) + $(INS.file) + +$(ROOTSVCMETHODDIR) $(ROOTMANIFESTDIR): + $(INS.dir) + +# include library targets +include ../../../Makefile.targ + +objs/%.o pics/%.o: ../common/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) diff --git a/usr/src/lib/cfgadm_plugins/fp/amd64/Makefile b/usr/src/lib/cfgadm_plugins/fp/amd64/Makefile new file mode 100644 index 0000000000..4c3452781e --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/amd64/Makefile @@ -0,0 +1,32 @@ +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +.KEEP_STATE: + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_cs.c b/usr/src/lib/cfgadm_plugins/fp/common/cfga_cs.c new file mode 100644 index 0000000000..a0bd3741d7 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_cs.c @@ -0,0 +1,1606 @@ +/* + * 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 2008 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 *, char **, cfga_flags_t); +static fpcfga_ret_t unconf_non_vhci_nodes(di_node_t, char *, char *, + 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 *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 + */ + 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 *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 + */ + 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, + 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, + 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); +} diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_cvt.c b/usr/src/lib/cfgadm_plugins/fp/common/cfga_cvt.c new file mode 100644 index 0000000000..deff5b826e --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_cvt.c @@ -0,0 +1,788 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#include "cfga_fp.h" + +/* Function prototypes */ + +static fpcfga_ret_t get_xport_devlink(const char *hba_phys, + char **hba_logpp, int *l_errnop); +static char ctoi(char c); +static fpcfga_ret_t is_apid_configured(const char *xport_phys, + const char *dyncomp, struct luninfo_list **lunlistpp, int *l_errnop); +static fpcfga_ret_t insert_lun_to_lunlist(struct luninfo_list **lunlistpp, + const char *dyncomp, di_node_t devnode, int *l_errnop); +static fpcfga_ret_t update_lunlist(struct luninfo_list **lunlistpp, int lun, + uint_t state, char *pathp, int *l_errnop); + + +/* Globals */ + +/* Various conversions routines */ + +void +cvt_lawwn_to_dyncomp(const la_wwn_t *pwwn, char **dyncomp, int *l_errnop) +{ + *dyncomp = calloc(1, WWN_SIZE*2 + 1); + if (*dyncomp == NULL) { + *l_errnop = errno; + } + + (void) sprintf(*dyncomp, "%016llx", + (wwnConversion((uchar_t *)pwwn->raw_wwn))); +} + + +int +cvt_dyncomp_to_lawwn(const char *dyncomp, la_wwn_t *port_wwn) +{ + int i; + char c, c1; + uchar_t *wwnp; + + wwnp = port_wwn->raw_wwn; + for (i = 0; i < WWN_SIZE; i++, wwnp++) { + + c = ctoi(*dyncomp++); + c1 = ctoi(*dyncomp++); + if (c == -1 || c1 == -1) + return (-1); + *wwnp = ((c << 4) + c1); + } + + return (0); +} + + +static char +ctoi(char c) +{ + if ((c >= '0') && (c <= '9')) + c -= '0'; + else if ((c >= 'A') && (c <= 'F')) + c = c - 'A' + 10; + else if ((c >= 'a') && (c <= 'f')) + c = c - 'a' + 10; + else + c = -1; + return (c); +} + + +/* + * Generates the HBA logical ap_id from physical ap_id. + */ +fpcfga_ret_t +make_xport_logid(const char *xport_phys, char **xport_logpp, int *l_errnop) +{ + if (*xport_logpp != NULL) { + return (FPCFGA_ERR); + } + + /* + * A devlink for the XPORT should exist. Without the /dev/cfg link + * driver name and instance number based based link needs to be + * constructed for the minor node type of DDI_NT_FC_ATTACHMENT_POINT. + * sunddi.h defines DDI_NT_FC_ATTACHMENT_POINT for + * ddi_ctl:attachment_point:fc + */ + if (get_xport_devlink(xport_phys, xport_logpp, l_errnop) == FPCFGA_OK) { + assert(*xport_logpp != NULL); + return (FPCFGA_OK); + } else { + return (FPCFGA_ERR); + } +} + +static fpcfga_ret_t +get_xport_devlink(const char *xport_phys, char **xport_logpp, int *l_errnop) +{ + int match_minor; + size_t len; + fpcfga_ret_t ret; + + match_minor = 1; + ret = physpath_to_devlink(CFGA_DEV_DIR, (char *)xport_phys, + xport_logpp, l_errnop, match_minor); + if (ret != FPCFGA_OK) { + return (ret); + } + + assert(*xport_logpp != NULL); + + /* Remove the "/dev/cfg/" prefix */ + len = strlen(CFGA_DEV_DIR SLASH); + + (void) memmove(*xport_logpp, *xport_logpp + len, + strlen(*xport_logpp + len) + 1); + + return (FPCFGA_OK); +} + + +/* + * Given a xport path and dynamic ap_id, returns the physical + * path in pathpp. If the dynamic ap is not configured pathpp set to NULL + * and returns FPCFGA_APID_NOCONFIGURE. + */ +fpcfga_ret_t +dyn_apid_to_path( + const char *xport_phys, + const char *dyncomp, + struct luninfo_list **lunlistpp, + int *l_errnop) +{ + fpcfga_ret_t ret; + + /* A device MUST have a dynamic component */ + if (dyncomp == NULL) { + return (FPCFGA_LIB_ERR); + } + + ret = is_apid_configured(xport_phys, dyncomp, lunlistpp, l_errnop); + + assert(ret != FPCFGA_OK); + + return (ret); +} + +/* + * When both the transport and dynamic comp are given this function + * checks to see if the dynamic ap is configured on the dev tree. + * If it is configured the devfs path will be stored in pathpp. + * When the dynamic comp is null this function check to see if the transport + * node has any child. + * + * Retrun value: FPCFGA_OK if the apid is configured. + * FPCFGA_APID_NOCONFIGURE if the apid is not configured. + * FPCFGA_LIB_ERR for other errors. + */ +static fpcfga_ret_t +is_apid_configured( + const char *xport_phys, + const char *dyncomp, + struct luninfo_list **lunlistpp, + int *l_errnop) +{ + char *devfs_phys, *devfs_fp_path, *client_path, *cp, + *pathp = NULL; + char path_name[MAXPATHLEN]; + di_node_t tree_root, root, fpnode, dev_node, client_node; + di_path_t path = DI_PATH_NIL; + di_prop_t prop = DI_PROP_NIL; + uchar_t *port_wwn_data = NULL; + char *lun_guid = NULL; + char port_wwn[WWN_SIZE*2+1]; + int count, *lunnump, devlen, + found_fp = 0; + uint_t state; + uint_t statep; + fpcfga_ret_t ret; + + if (*lunlistpp != NULL) { + return (FPCFGA_LIB_ERR); + } + + ret = FPCFGA_APID_NOCONFIGURE; + + if ((devfs_phys = strdup(xport_phys)) == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + if (strncmp(devfs_phys, DEVICES_DIR SLASH, strlen(DEVICES_DIR) + + strlen(SLASH)) == 0) { + cp = devfs_phys + strlen(DEVICES_DIR); + (void) memmove(devfs_phys, cp, strlen(cp) + 1); + } + + if ((cp = strstr(devfs_phys, MINOR_SEP)) != NULL) { + *cp = '\0'; /* Terminate string. */ + } + + if ((tree_root = di_init("/", DINFOCPYALL | DINFOPATH)) + == DI_NODE_NIL) { + *l_errnop = errno; + S_FREE(devfs_phys); + return (FPCFGA_LIB_ERR); + } + + fpnode = di_drv_first_node("fp", tree_root); + + while (fpnode) { + devfs_fp_path = di_devfs_path(fpnode); + if ((devfs_fp_path) && !(strncmp(devfs_fp_path, + devfs_phys, strlen(devfs_phys)))) { + found_fp = 1; + di_devfs_path_free(devfs_fp_path); + break; + } + di_devfs_path_free(devfs_fp_path); + fpnode = di_drv_next_node(fpnode); + } + if (!(found_fp)) { + ret = FPCFGA_LIB_ERR; + goto out; + } else { + root = fpnode; + } + + /* + * when there is no child and path info node the + * FPCFGA_APID_NOCONFIGURE is returned + * regardless of the dynamic comp. + */ + dev_node = di_child_node(root); + path = di_path_next_client(root, path); + if ((dev_node == DI_NODE_NIL) && (path == DI_PATH_NIL)) { + *l_errnop = errno; + ret = FPCFGA_APID_NOCONFIGURE; + goto out; + } + + /* + * when dyn comp is null the function just checks if there is any + * child under fp transport attachment point. + */ + if (dyncomp == NULL) { + ret = FPCFGA_OK; + goto out; + } + + /* + * now checks the children node to find + * if dynamic ap is configured. if there are multiple luns + * store into lunlist. + */ + if (dev_node != DI_NODE_NIL) { + do { + while ((prop = di_prop_next(dev_node, prop)) != + DI_PROP_NIL) { + /* is property name port-wwn */ + if ((!(strcmp(PORT_WWN_PROP, + di_prop_name(prop)))) && + (di_prop_type(prop) == + DI_PROP_TYPE_BYTE)) { + break; + } + } + + if (prop != DI_PROP_NIL) { + count = di_prop_bytes(prop, &port_wwn_data); + if (count != WWN_SIZE) { + ret = FPCFGA_LIB_ERR; + goto out; + } else { + (void) sprintf(port_wwn, + "%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x", + port_wwn_data[0], port_wwn_data[1], + port_wwn_data[2], port_wwn_data[3], + port_wwn_data[4], port_wwn_data[5], + port_wwn_data[6], port_wwn_data[7]); + if (!(strncmp(port_wwn, dyncomp, + WWN_SIZE*2))) { + ret = insert_lun_to_lunlist( + lunlistpp, dyncomp, + dev_node, l_errnop); + if (ret != FPCFGA_OK) { + goto out; + } + } + } + } + dev_node = di_sibling_node(dev_node); + prop = DI_PROP_NIL; + } while (dev_node != DI_NODE_NIL); + } + + /* + * now checks the path info node to find + * if dynamic ap is configured. if there are multiple luns + * store into lunlist. + */ + if (path != DI_PATH_NIL) { + /* + * now parse the path info node. + */ + do { + count = di_path_prop_lookup_bytes(path, PORT_WWN_PROP, + &port_wwn_data); + if (count != WWN_SIZE) { + ret = FPCFGA_LIB_ERR; + goto out; + } + + (void) sprintf(port_wwn, + "%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x", + port_wwn_data[0], port_wwn_data[1], + port_wwn_data[2], port_wwn_data[3], + port_wwn_data[4], port_wwn_data[5], + port_wwn_data[6], port_wwn_data[7]); + + /* if matches get the path of scsi_vhci child node. */ + if (!(strncmp(port_wwn, dyncomp, WWN_SIZE*2))) { + client_node = di_path_client_node(path); + if (client_node == DI_NODE_NIL) { + ret = FPCFGA_LIB_ERR; + *l_errnop = errno; + goto out; + } + count = di_path_prop_lookup_ints(path, + LUN_PROP, &lunnump); + client_path = di_devfs_path(client_node); + strcpy(path_name, client_path); + di_devfs_path_free(client_path); + state = di_state(client_node); + statep = di_path_state(path); + + /* + * If the node is + * state then check the devfs_path to + * see if it has a complete path. + * For non scsi_vhci node the path + * doesn't contain @w(portwwn) part + * consistently. For scsi_vhci + * this behavior may not be there. + * To be safe @g(guid) is attempted + * to be added here. + */ + if ((state & DI_DRIVER_DETACHED) && + (strstr(path_name, "@g") == NULL)) { + prop = DI_PROP_NIL; + while ((prop = di_prop_next(client_node, + prop)) != DI_PROP_NIL) { + /* is property name lun-wwn */ + if ((!(strcmp(LUN_GUID_PROP, + di_prop_name(prop)))) && + (di_prop_type(prop) == + DI_PROP_TYPE_STRING)) { + break; + } + } + + if (prop != DI_PROP_NIL) { + count = di_prop_strings( + prop, &lun_guid); + sprintf(&path_name[ + strlen(path_name)], + "@g%s", lun_guid); + } else { + ret = FPCFGA_LIB_ERR; + goto out; + } + } + + devlen = strlen(DEVICES_DIR) + + strlen(path_name) + 1; + if ((pathp = calloc(1, devlen)) + == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } else { + (void) snprintf(pathp, devlen, + "%s%s", DEVICES_DIR, path_name); + } + if ((ret = (update_lunlist(lunlistpp, *lunnump, + statep, pathp, l_errnop))) != + FPCFGA_OK) { + S_FREE(pathp); + goto out; + } + } + path = di_path_next_client(root, path); + } while (path != DI_PATH_NIL); + } + +out: + di_fini(tree_root); + S_FREE(devfs_phys); + return (ret); +} + +static fpcfga_ret_t +insert_lun_to_lunlist( + struct luninfo_list **lunlistpp, + const char *dyncomp, + di_node_t dev_node, + int *l_errnop) +{ + char path_name[MAXPATHLEN]; + char *pathp, *dev_phys; + di_prop_t prop_lun = DI_PROP_NIL; + uint_t state; + int count, devlen; + int *lunp; + + while ((prop_lun = di_prop_next(dev_node, prop_lun)) != DI_PROP_NIL) { + if (!(strcmp(LUN_PROP, di_prop_name(prop_lun))) && + (di_prop_type(prop_lun) == DI_PROP_TYPE_INT)) { + count = di_prop_ints(prop_lun, &lunp); + if (count <= 0) { + return (FPCFGA_LIB_ERR); + } + break; + } + } + + if (prop_lun == DI_PROP_NIL) { + return (FPCFGA_LIB_ERR); + } + + /* + * stores state info in state. + * This information is used to get the + * validity of path. + * if driver_detached don't try to get + * the devfs_path since it is not + * complete. ex, /pci@1f,2000/pci@1/ + * SUNW,qlc@5/fp@0,0/ssd + * which doesn't contain the port wwn + * part. The attached node looks like + * /pci@1f,2000/pci@1/SUNW,qlc@5/fp@0,0/ + * ssd@w2100002037006b14,0 + */ + state = di_state(dev_node); + + dev_phys = di_devfs_path(dev_node); + if (dev_phys == NULL) { + return (FPCFGA_LIB_ERR); + } + + strcpy(path_name, dev_phys); + + di_devfs_path_free(dev_phys); + + if ((state & DI_DRIVER_DETACHED) && + (strstr(path_name, "@w") == NULL)) { + sprintf(&path_name[strlen(path_name)], "@w%s,%x", dyncomp, + *lunp); + } + + devlen = strlen(DEVICES_DIR) + strlen(path_name) + 1; + + if ((pathp = calloc(1, devlen)) + == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } else { + (void) snprintf(pathp, devlen, "%s%s", DEVICES_DIR, path_name); + } + + return (update_lunlist(lunlistpp, *lunp, state, pathp, l_errnop)); +} + +static fpcfga_ret_t +update_lunlist( + struct luninfo_list **lunlistpp, + int lun, + uint_t state, + char *pathp, + int *l_errnop) +{ + struct luninfo_list *newlun, *curlun, *prevlun; + + newlun = curlun = prevlun = (struct luninfo_list *)NULL; + + newlun = calloc(1, sizeof (struct luninfo_list)); + if (newlun == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + newlun->lunnum = lun; + newlun->node_state = state; + newlun->path = pathp; + newlun->next = (struct luninfo_list *)NULL; + + /* if lunlist is empty add the new lun info and return. */ + if (*lunlistpp == NULL) { + *lunlistpp = newlun; + return (FPCFGA_OK); + } + + /* if the first lun in the list is the same as the new lun return. */ + if ((*lunlistpp)->lunnum == lun) { + S_FREE(newlun); + return (FPCFGA_OK); + } + + /* + * if the first lun in the list is less than the new lun add the + * new lun as the first lun and return. + */ + if ((*lunlistpp)->lunnum < lun) { + newlun->next = *lunlistpp; + *lunlistpp = newlun; + return (FPCFGA_OK); + } + + /* + * if the first lun in the list is greater than the new lun and + * there is a single lun add new lun after the first lun and return. + */ + if ((*lunlistpp)->next == NULL) { + (*lunlistpp)->next = newlun; + return (FPCFGA_OK); + } + + /* + * now there is more than two luns in the list and the first lun + * is greter than the input lun. + */ + curlun = (*lunlistpp)->next; + prevlun = *lunlistpp; + + while (curlun != NULL) { + if (curlun->lunnum == lun) { + S_FREE(newlun); + return (FPCFGA_OK); + } else if (curlun->lunnum < lun) { + newlun->next = curlun; + prevlun->next = newlun; + return (FPCFGA_OK); + } else { + prevlun = curlun; + curlun = curlun->next; + } + } + + /* add the new lun at the end of list. */ + prevlun->next = newlun; + return (FPCFGA_OK); + +} + + +fpcfga_ret_t +make_dyncomp_from_dinode( + const di_node_t node, + char **dyncompp, + int *l_errnop) +{ + di_prop_t prop = DI_PROP_NIL; + uchar_t *port_wwn_data; + int count; + + *l_errnop = 0; + *dyncompp = calloc(1, WWN_SIZE*2 + 1); + if (*dyncompp == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + /* now get port-wwn for the input node. */ + while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) { + if (!(strcmp(PORT_WWN_PROP, di_prop_name(prop))) && + (di_prop_type(prop) == DI_PROP_TYPE_BYTE)) { + break; + } + } + + if (prop != DI_PROP_NIL) { + count = di_prop_bytes(prop, &port_wwn_data); + if (count != WWN_SIZE) { + S_FREE(*dyncompp); + return (FPCFGA_LIB_ERR); + } + + (void) sprintf(*dyncompp, "%016llx", + (wwnConversion(port_wwn_data))); + } else { + *l_errnop = errno; + S_FREE(*dyncompp); + return (FPCFGA_LIB_ERR); + } + + return (FPCFGA_OK); +} + +fpcfga_ret_t +make_portwwn_luncomp_from_dinode( + const di_node_t node, + char **dyncompp, + int **luncompp, + int *l_errnop) +{ + uchar_t *port_wwn_data; + int pwwn_ret, lun_ret; + + *l_errnop = 0; + + if ((dyncompp != NULL) && + ((pwwn_ret = di_prop_lookup_bytes(DDI_DEV_T_ANY, + node, PORT_WWN_PROP, &port_wwn_data)) <= 0)) { + *l_errnop = errno; + } + if ((luncompp != NULL) && + ((lun_ret = di_prop_lookup_ints(DDI_DEV_T_ANY, + node, LUN_PROP, luncompp)) <= 0)) { + *l_errnop = errno; + } + + /* + * di_prop* returns the number of entries found or 0 if not found + * or -1 for othere failure. + */ + if ((pwwn_ret <= 0) || (lun_ret <= 0)) { + return (FPCFGA_LIB_ERR); + } + + *dyncompp = calloc(1, WWN_SIZE*2+1); + if (*dyncompp == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + (void) sprintf(*dyncompp, "%016llx", (wwnConversion(port_wwn_data))); + + return (FPCFGA_OK); +} + +fpcfga_ret_t +make_portwwn_luncomp_from_pinode( + const di_path_t pinode, + char **dyncompp, + int **luncompp, + int *l_errnop) +{ + uchar_t *port_wwn_data; + int pwwn_ret, lun_ret; + + *l_errnop = 0; + + if ((dyncompp != NULL) && + ((pwwn_ret = di_path_prop_lookup_bytes(pinode, + PORT_WWN_PROP, &port_wwn_data)) <= 0)) { + *l_errnop = errno; + } + if ((luncompp != NULL) && + ((lun_ret = di_path_prop_lookup_ints(pinode, + LUN_PROP, luncompp)) <= 0)) { + *l_errnop = errno; + } + + /* + * di_prop* returns the number of entries found or 0 if not found + * or -1 for othere failure. + */ + if ((pwwn_ret <= 0) || (lun_ret <= 0)) { + return (FPCFGA_LIB_ERR); + } + + *dyncompp = calloc(1, WWN_SIZE*2+1); + if (*dyncompp == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + (void) sprintf(*dyncompp, "%016llx", (wwnConversion(port_wwn_data))); + + return (FPCFGA_OK); +} + +fpcfga_ret_t +construct_nodepath_from_dinode( + const di_node_t node, + char **node_pathp, + int *l_errnop) +{ + char *devfs_path, path_name[MAXPATHLEN], *lun_guid, *port_wwn; + uchar_t *port_wwn_data; + int is_scsi_vhci_dev, di_ret, devlen; + uint_t state; + + devfs_path = di_devfs_path(node); + strcpy(path_name, devfs_path); + di_devfs_path_free(devfs_path); + state = di_state(node); + + is_scsi_vhci_dev = (strstr(path_name, SCSI_VHCI_DRVR) != NULL) ? 1 : 0; + + /* + * If the node is + * state then check the devfs_path to + * see if it has a complete path. + * For non scsi_vhci node the path + * doesn't contain @w(portwwn) part + * consistently. For scsi_vhci + * this behavior may not be there. + * To be safe @g(guid) is attempted + * to be added here. + */ + if (state & DI_DRIVER_DETACHED) { + if (is_scsi_vhci_dev && + (strstr(path_name, "@g") == NULL)) { + di_ret = di_prop_lookup_strings(DDI_DEV_T_ANY, node, + LUN_GUID_PROP, &lun_guid); + if (di_ret == -1) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } else { + sprintf(&path_name[strlen(path_name)], + "@g%s", lun_guid); + } + } else if (!is_scsi_vhci_dev && + (strstr(path_name, "@w") == NULL)) { + di_ret = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, + PORT_WWN_PROP, &port_wwn_data); + if (di_ret == -1) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } else { + if ((port_wwn = calloc(1, WWN_SIZE*2 + 1)) + == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + (void) sprintf(port_wwn, "%016llx", + (wwnConversion(port_wwn_data))); + (void) sprintf(&path_name[strlen(path_name)], + "@w%s", port_wwn); + S_FREE(port_wwn); + } + } + } + + devlen = strlen(DEVICES_DIR) + strlen(path_name) + 1; + if ((*node_pathp = calloc(1, devlen)) == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } else { + (void) snprintf(*node_pathp, devlen, + "%s%s", DEVICES_DIR, path_name); + } + + return (FPCFGA_OK); +} + +u_longlong_t +wwnConversion(uchar_t *wwn) +{ + u_longlong_t tmp; + memcpy(&tmp, wwn, sizeof (u_longlong_t)); + return (ntohll(tmp)); +} diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_fp.c b/usr/src/lib/cfgadm_plugins/fp/common/cfga_fp.c new file mode 100644 index 0000000000..d24104c900 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_fp.c @@ -0,0 +1,404 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + + + +#include "cfga_fp.h" + +/* + * This file contains the entry points to the plug-in as defined in the + * config_admin(3X) man page. + */ + +/* + * Set the version number + */ +int cfga_version = CFGA_HSL_V2; + +/*ARGSUSED*/ +cfga_err_t +cfga_change_state( + cfga_cmd_t state_change_cmd, + const char *ap_id, + const char *options, + struct cfga_confirm *confp, + struct cfga_msg *msgp, + char **errstring, + cfga_flags_t flags) +{ + apid_t apidt = {NULL}; + fpcfga_ret_t ret; + la_wwn_t pwwn; + char *value, *hw_option, *hw_option_p; + char *fp_cs_hw_opts[] = {"disable_rcm", "force_update", + "no_update", "unusable_SCSI_LUN", "unusable_FCP_dev", NULL}; + HBA_HANDLE handle; + HBA_PORTATTRIBUTES portAttrs; + int portIndex; + + if (errstring != NULL) { + *errstring = NULL; + } + + /* Check for super user priveleges */ + if (geteuid() != 0) { + return (CFGA_PRIV); + } + + /* Only configure and unconfigure operations are supported */ + if (state_change_cmd != CFGA_CMD_CONFIGURE && + state_change_cmd != CFGA_CMD_UNCONFIGURE) { + return (CFGA_OPNOTSUPP); + } + + if ((ret = apidt_create(ap_id, &apidt, errstring)) != CFGA_OK) { + return (err_cvt(ret)); + } + + if (options != NULL) { + hw_option = calloc(1, strlen(options) + 1); + (void) snprintf(hw_option, strlen(options) + 1, "%s", options); + hw_option_p = hw_option; + /* Use getsubopt() if more options get added */ + while (*hw_option_p != '\0') { + switch (getsubopt(&hw_option_p, fp_cs_hw_opts, &value)) { + case OPT_DISABLE_RCM : + apidt.flags |= FLAG_DISABLE_RCM; + break; + case OPT_FORCE_UPDATE_REP : + apidt.flags |= FLAG_FORCE_UPDATE_REP; + break; + case OPT_NO_UPDATE_REP : + apidt.flags |= FLAG_NO_UPDATE_REP; + break; + case OPT_REMOVE_UNUSABLE_FCP_DEV : + case OPT_REMOVE_UNUSABLE_SCSI_LUN: + if (state_change_cmd != CFGA_CMD_UNCONFIGURE) { + cfga_err(errstring, 0, ERRARG_OPT_INVAL, + options, 0); + S_FREE(hw_option); + apidt_free(&apidt); + return (CFGA_ERROR); + } + apidt.flags |= FLAG_REMOVE_UNUSABLE_FCP_DEV; + break; + default : + /* process unknonw option. */ + cfga_err(errstring, 0, ERRARG_OPT_INVAL, + options, 0); + S_FREE(hw_option); + apidt_free(&apidt); + return (CFGA_ERROR); + } + } + S_FREE(hw_option); + } + + if (options != NULL && apidt.flags == 0) { + /* invalid option specified. */ + cfga_err(errstring, 0, ERRARG_OPT_INVAL, options, 0); + apidt_free(&apidt); + return (CFGA_ERROR); + } + + if (apidt.dyncomp != NULL) { /* Was there a port WWN passed ? */ + /* + * Yes - so change state of the particular device + * + * First Get the WWN in la_wwn_t form + */ + if (cvt_dyncomp_to_lawwn(apidt.dyncomp, &pwwn)) { + cfga_err(errstring, 0, ERR_APID_INVAL, 0); + return (err_cvt(FPCFGA_LIB_ERR)); + } + + if ((ret = findMatchingAdapterPort(apidt.xport_phys, + &handle, &portIndex, &portAttrs, errstring)) == + FPCFGA_OK) { + ret = dev_change_state(state_change_cmd, &apidt, &pwwn, + flags, errstring, handle, portAttrs); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + } + } else { + /* Change state of all devices on FCA and the FCA itself */ + ret = fca_change_state(state_change_cmd, &apidt, + flags, errstring); + } + + apidt_free(&apidt); + return (err_cvt(ret)); +} + + +/*ARGSUSED*/ +cfga_err_t +cfga_private_func( + const char *func, + const char *ap_id, + const char *options, + struct cfga_confirm *confp, + struct cfga_msg *msgp, + char **errstring, + cfga_flags_t flags) +{ + if (errstring != NULL) { + *errstring = NULL; + } + + if (geteuid() != 0) { + return (CFGA_PRIV); + } + + return (CFGA_OPNOTSUPP); +} + + +/*ARGSUSED*/ +cfga_err_t +cfga_test( + const char *ap_id, + const char *options, + struct cfga_msg *msgp, + char **errstring, + cfga_flags_t flags) +{ + if (errstring != NULL) { + *errstring = NULL; + } + + if (geteuid() != 0) { + return (CFGA_PRIV); + } + + return (CFGA_OPNOTSUPP); +} + + +/*ARGSUSED*/ +cfga_err_t +cfga_list_ext( + const char *ap_id, + cfga_list_data_t **ap_id_list, + int *nlistp, + const char *options, + const char *listopts, + char **errstring, + cfga_flags_t flags) +{ + int fca, expand, nelem; + ldata_list_t *ldatalistp = NULL; + apid_t apidt = {NULL}; + fpcfga_cmd_t cmd; + fpcfga_ret_t ret; + char *value, *hw_option, *hw_option_p; + uint_t fp_flags = 0; + char *fp_list_hw_opts[] = {"devinfo_force", "show_SCSI_LUN", + "show_FCP_dev", NULL}; + + if (errstring != NULL) { + *errstring = NULL; + } + + /* Check for super user privileges */ + if (geteuid() != 0) { + return (CFGA_PRIV); + } + + if (ap_id_list == NULL || nlistp == NULL) { + return (CFGA_ERROR); + } + + *ap_id_list = NULL; + *nlistp = 0; + + if (options != NULL) { + hw_option = calloc(1, strlen(options) + 1); + (void) snprintf(hw_option, strlen(options) + 1, "%s", options); + hw_option_p = hw_option; + /* Use getsubopt() if more options get added */ + while (*hw_option_p != '\0') { + switch (getsubopt(&hw_option_p, fp_list_hw_opts, &value)) { + case OPT_DEVINFO_FORCE : + fp_flags |= FLAG_DEVINFO_FORCE; + break; + case OPT_FCP_DEV : + case OPT_SHOW_SCSI_LUN: + fp_flags |= FLAG_FCP_DEV; + break; + default : + /* process unknonw option. */ + cfga_err(errstring, 0, ERRARG_OPT_INVAL, + options, 0); + S_FREE(hw_option); + return (CFGA_ERROR); + } + } + S_FREE(hw_option); + } + + /* if force_devinfo is specified check uid = 0 or not. */ + if (((fp_flags & FLAG_DEVINFO_FORCE) == FLAG_DEVINFO_FORCE) && + (geteuid() != 0)) { + return (CFGA_PRIV); + } + + fca = 0; + if (GET_DYN(ap_id) == NULL) { + fca = 1; + } + + expand = 0; + if ((flags & CFGA_FLAG_LIST_ALL) == CFGA_FLAG_LIST_ALL) { + expand = 1; + } + + /* + * We expand published attachment points but not + * dynamic attachment points + */ + + if (!fca) { /* Stat a single device - no expansion for devices */ + cmd = FPCFGA_STAT_FC_DEV; + } else if (!expand) { /* Stat only the HBA */ + cmd = FPCFGA_STAT_FCA_PORT; + } else { /* Expand HBA attachment point */ + cmd = FPCFGA_STAT_ALL; + } + + ldatalistp = NULL; + nelem = 0; + + if ((fp_flags & FLAG_FCP_DEV) == FLAG_FCP_DEV) { + ret = do_list_FCP_dev(ap_id, fp_flags, cmd, &ldatalistp, &nelem, + errstring); + if (ret != FPCFGA_OK) { + list_free(&ldatalistp); + return (err_cvt(ret)); + } + } else { + if ((ret = apidt_create(ap_id, &apidt, errstring)) + != FPCFGA_OK) { + return (err_cvt(ret)); + } + + if (options != NULL) { + apidt.flags |= fp_flags; + } + + ret = do_list(&apidt, cmd, &ldatalistp, &nelem, errstring); + if (ret != FPCFGA_OK) { + list_free(&ldatalistp); + apidt_free(&apidt); + return (err_cvt(ret)); + } + apidt_free(&apidt); + } + + assert(ldatalistp != NULL); + + if (list_ext_postprocess(&ldatalistp, nelem, ap_id_list, nlistp, + errstring) != FPCFGA_OK) { + assert(*ap_id_list == NULL && *nlistp == 0); + ret = FPCFGA_LIB_ERR; + } else { + assert(*ap_id_list != NULL && *nlistp == nelem); + ret = FPCFGA_OK; + } + + list_free(&ldatalistp); + return (err_cvt(ret)); +} + + +/*ARGSUSED*/ +cfga_err_t +cfga_help(struct cfga_msg *msgp, const char *options, cfga_flags_t flags) +{ + cfga_msg(msgp, MSG_HELP_HDR, MSG_HELP_USAGE, 0); + + return (CFGA_OK); + +} + +/*ARGSUSED*/ +int +cfga_ap_id_cmp(const char *ap_id1, const char *ap_id2) +{ + int i = 0; + long long ret; + + if (ap_id1 == ap_id2) { + return (0); + } + + if (ap_id1 == NULL || ap_id2 == NULL) { + if (ap_id1 == NULL) { + /* Return a negative value */ + return (0 - (uchar_t)ap_id2[0]); + } else { + return ((uchar_t)ap_id1[0]); + } + } + + /* + * Search for first different char + */ + while (ap_id1[i] == ap_id2[i] && ap_id1[i] != '\0') + i++; + + if ((ap_id1[i] == '\0') && + !(strncmp(&ap_id2[i], LUN_COMP_SEP, strlen(LUN_COMP_SEP)))) { + return (0); + } else if ((ap_id2[i] == '\0') && + !(strncmp(&ap_id1[i], LUN_COMP_SEP, strlen(LUN_COMP_SEP)))) { + return (0); + } + + /* + * If one of the char is a digit, back up to where the + * number started, compare the number. + */ + if (isxdigit(ap_id1[i]) || isxdigit(ap_id2[i])) { + while ((i > 0) && isxdigit(ap_id1[i - 1])) + i--; + + if (isxdigit(ap_id1[i]) && isxdigit(ap_id2[i])) { + ret = (strtoll((ap_id1 + i), NULL, 16)) - + (strtoll((ap_id2 + i), NULL, 16)); + if (ret > 0) { + return (1); + } else if (ret < 0) { + return (-1); + } else { + return (0); + } + } + } + + /* One of them isn't a number, compare the char */ + return (ap_id1[i] - ap_id2[i]); +} diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_fp.h b/usr/src/lib/cfgadm_plugins/fp/common/cfga_fp.h new file mode 100644 index 0000000000..a0f20c3f1b --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_fp.h @@ -0,0 +1,547 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _CFGA_FP_H +#define _CFGA_FP_H + + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <sys/mkdev.h> +#include <stddef.h> +#include <locale.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <locale.h> +#include <langinfo.h> +#include <time.h> +#include <stdarg.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <sys/dditypes.h> +#include <sys/modctl.h> +#include <libdevinfo.h> +#include <libdevice.h> +#include <librcm.h> +#include <dirent.h> +#include <strings.h> + + +#include <sys/ioctl.h> +#include <sys/byteorder.h> +#include <sys/scsi/scsi.h> +#include <strings.h> +#include <sys/vfstab.h> +#include <sys/stat.h> +#include <setjmp.h> +#include <signal.h> +#include <hbaapi.h> +#include <sys/fibre-channel/fcio.h> +#include <sys/fibre-channel/ulp/fcp_util.h> + +#include <sys/uio.h> +#include <sys/param.h> + +#include <synch.h> +#include <thread.h> + +#include <limits.h> +#include <ftw.h> + +#define CFGA_PLUGIN_LIB +#include <config_admin.h> + +#if !defined(DEBUG) +#define NDEBUG 1 +#else +#undef NDEBUG +#endif + +#include <assert.h> + +/* Return/error codes */ +typedef enum { + FPCFGA_ERR = -2, + FPCFGA_LIB_ERR = -1, + FPCFGA_OK = 0, + FPCFGA_ACCESS_OK, + FPCFGA_NACK, + FPCFGA_BUSY, + FPCFGA_SYSTEM_BUSY, + FPCFGA_APID_NOCONFIGURE, + FPCFGA_APID_NOACCESS, + FPCFGA_APID_NOEXIST, + FPCFGA_OPNOTSUPP, + FPCFGA_PRIV, + FPCFGA_UNLOCKED, + FPCFGA_NO_REC, + FPCFGA_OP_INTR, + FPCFGA_DB_INVAL, + FPCFGA_CONF_OK_UPD_REP_FAILED, + FPCFGA_UNCONF_OK_UPD_REP_FAILED, + FPCFGA_INVALID_PATH, + FPCFGA_VHCI_GET_PATHLIST_FAILED, + FPCFGA_XPORT_NOT_IN_PHCI_LIST, + FPCFGA_UNKNOWN_ERR, + FPCFGA_FCP_TGT_SEND_SCSI_FAILED, + FPCFGA_FCP_SEND_SCSI_DEV_NOT_TGT +} fpcfga_ret_t; + +/* Commands used internally */ +typedef enum { + FPCFGA_INVAL_CMD = -1, + FPCFGA_DEV_OP = 0, + FPCFGA_BUS_OP, + FPCFGA_STAT_FC_DEV, + FPCFGA_STAT_FCA_PORT, + FPCFGA_STAT_ALL, + FPCFGA_GET_DEVPATH, + FPCFGA_INSERT_DEV, + FPCFGA_REMOVE_DEV, + FPCFGA_REPLACE_DEV, + FPCFGA_WALK_NODE, + FPCFGA_WALK_MINOR, + FPCFGA_BUS_QUIESCE, + FPCFGA_BUS_UNQUIESCE, + FPCFGA_BUS_GETSTATE, + FPCFGA_DEV_GETSTATE, + FPCFGA_BUS_CONFIGURE, + FPCFGA_BUS_UNCONFIGURE, + FPCFGA_DEV_CONFIGURE, + FPCFGA_DEV_UNCONFIGURE, + FPCFGA_DEV_REMOVE, + FPCFGA_RESET_DEV, + FPCFGA_RESET_BUS, + FPCFGA_RESET_ALL, + FPCFGA_READ, + FPCFGA_WRITE +} fpcfga_cmd_t; + +typedef enum { + FPCFGA_TERMINATE = 0, + FPCFGA_CONTINUE +} fpcfga_recur_t; + + +/* Structures for tree walking code */ + +typedef struct { + uint_t flags; + int (*fcn)(di_node_t node, void *argp); +} walk_node_t; + +typedef struct { + const char *nodetype; + int (*fcn)(di_node_t node, di_minor_t minor, void *argp); +} walk_minor_t; + +typedef union { + walk_node_t node_args; + walk_minor_t minor_args; +} walkmode_t; + +typedef struct { + uint_t flags; + walkmode_t walkmode; +} walkarg_t; + +typedef struct { + char *phys; + char *log; + fpcfga_ret_t ret; + int match_minor; + int l_errno; +} pathm_t; + +typedef struct ldata_list { + cfga_list_data_t ldata; + struct ldata_list *next; +} ldata_list_t; + +typedef struct { + struct cfga_confirm *confp; + struct cfga_msg *msgp; +} prompt_t; + +typedef struct luninfo_list { + int lunnum; + uint_t node_state; + uint_t lun_flag; + char *path; + struct luninfo_list *next; +} luninfo_list_t; + +typedef struct { + char *xport_phys; + char *dyncomp; + uint_t flags; + luninfo_list_t *lunlist; /* Singly linked list */ +} apid_t; + +/* Report luns names */ +#define FP_SCMD_REPORT_LUN 0xA0 +#define DEFAULT_NUM_LUN 1024 +#define REPORT_LUN_HDR_SIZE 8 +#define SAM_LUN_SIZE 8 + +#ifdef _BIG_ENDIAN +#define htonll(x) (x) +#define ntohll(x) (x) +#else +#define htonll(x) ((((unsigned long long)htonl(x)) << 32) + htonl(x >> 32)) +#define ntohll(x) ((((unsigned long long)ntohl(x)) << 32) + ntohl(x >> 32)) +#endif + +typedef struct report_lun_resp { + uint32_t num_lun; + uint32_t reserved; + longlong_t lun_string[DEFAULT_NUM_LUN]; +} report_lun_resp_t; + +/* + * Hardware options acceptable for fp plugin. + * list related options are handled by getsupopts() and set to + * index of array. + */ +#define OPT_DEVINFO_FORCE 0 +#define OPT_SHOW_SCSI_LUN 1 +#define OPT_FCP_DEV 2 +#define OPT_DISABLE_RCM 0 +#define OPT_FORCE_UPDATE_REP 1 +#define OPT_NO_UPDATE_REP 2 +#define OPT_REMOVE_UNUSABLE_SCSI_LUN 3 +#define OPT_REMOVE_UNUSABLE_FCP_DEV 4 + +/* walk tree flag */ +#define FLAG_PATH_INFO_WALK 0x00000001 + +/* apid_t flags */ +#define FLAG_DISABLE_RCM 0x00000001 +#define FLAG_FORCE_UPDATE_REP 0x00000010 +#define FLAG_NO_UPDATE_REP 0x00000100 +#define FLAG_DYN_AP_CONFIGURED 0x00001000 +#define FLAG_DEVINFO_FORCE 0x00010000 +#define FLAG_FCP_DEV 0x00100000 +#define FLAG_REMOVE_UNUSABLE_FCP_DEV 0x01000000 + +/* apid_t lun flags */ +#define FLAG_SKIP_RCMOFFLINE 0x00000001 +#define FLAG_SKIP_RCMREMOVE 0x00000010 +#define FLAG_SKIP_ONLINEOTHERS 0x00000100 + +/* define for peripheral qualifier mask */ +#define FP_PERI_QUAL_MASK 0xE0 + +/* Message ids */ +typedef enum { + +/* ERRORS */ +ERR_UNKNOWN = -1, +ERR_OP_FAILED, +ERR_CMD_INVAL, +ERR_NOT_BUSAPID, +ERR_APID_INVAL, +ERR_NOT_BUSOP, +ERR_NOT_DEVOP, +ERR_UNAVAILABLE, +ERR_CTRLR_CRIT, +ERR_BUS_GETSTATE, +ERR_BUS_NOTCONNECTED, +ERR_BUS_CONNECTED, +ERR_BUS_QUIESCE, +ERR_BUS_UNQUIESCE, +ERR_BUS_CONFIGURE, +ERR_BUS_UNCONFIGURE, +ERR_DEV_CONFIGURE, +ERR_DEV_UNCONFIGURE, +ERR_FCA_CONFIGURE, +ERR_FCA_UNCONFIGURE, +ERR_DEV_REPLACE, +ERR_DEV_INSERT, +ERR_DEV_GETSTATE, +ERR_RESET, +ERR_LIST, +ERR_FC, +ERR_FC_GET_DEVLIST, +ERR_FC_GET_FIRST_DEV, +ERR_FC_GET_NEXT_DEV, +ERRARG_FC_DEV_MAP_INIT, +ERRARG_FC_PROP_LOOKUP_BYTES, +ERRARG_FC_INQUIRY, +ERRARG_FC_REP_LUNS, +ERRARG_FC_TOPOLOGY, +ERRARG_PATH_TOO_LONG, +ERRARG_INVALID_PATH, +ERRARG_OPENDIR, +ERRARG_VHCI_GET_PATHLIST, +ERRARG_XPORT_NOT_IN_PHCI_LIST, +ERR_SIG_STATE, +ERR_MAYBE_BUSY, +ERR_BUS_DEV_MISMATCH, +ERR_GET_DEVLIST, +ERR_MEM_ALLOC, +ERR_DEVCTL_OFFLINE, +ERR_UPD_REP, +ERR_CONF_OK_UPD_REP, +ERR_UNCONF_OK_UPD_REP, +ERR_PARTIAL_SUCCESS, +ERR_HBA_LOAD_LIBRARY, +ERR_MATCHING_HBA_PORT, +ERR_NO_ADAPTER_FOUND, + +/* Errors with arguments */ +ERRARG_OPT_INVAL, +ERRARG_HWCMD_INVAL, +ERRARG_DEVINFO, +ERRARG_NOT_IN_DEVLIST, +ERRARG_NOT_IN_DEVINFO, +ERRARG_DI_GET_PROP, +ERRARG_DC_DDEF_ALLOC, +ERRARG_DC_BYTE_ARRAY, +ERRARG_DC_BUS_ACQUIRE, +ERRARG_BUS_DEV_CREATE, +ERRARG_BUS_DEV_CREATE_UNKNOWN, +ERRARG_DEV_ACQUIRE, +ERRARG_DEV_REMOVE, + +/* RCM Errors */ +ERR_RCM_HANDLE, +ERRARG_RCM_SUSPEND, +ERRARG_RCM_RESUME, +ERRARG_RCM_OFFLINE, +ERRARG_RCM_ONLINE, +ERRARG_RCM_REMOVE, +ERRARG_RCM_INFO, + +/* Commands */ +CMD_INSERT_DEV, +CMD_REMOVE_DEV, +CMD_REPLACE_DEV, +CMD_RESET_DEV, +CMD_RESET_BUS, +CMD_RESET_ALL, + +/* help messages */ +MSG_HELP_HDR, +MSG_HELP_USAGE, + +/* Hotplug messages */ +MSG_INSDEV, +MSG_RMDEV, +MSG_REPLDEV, + +/* Hotplugging confirmation prompts */ +CONF_QUIESCE_1, +CONF_QUIESCE_2, +CONF_UNQUIESCE, + +/* Misc. */ +WARN_DISCONNECT +} msgid_t; + +typedef struct { + msgid_t str_id; + fpcfga_cmd_t cmd; + fpcfga_ret_t (*fcn)(fpcfga_cmd_t, apid_t *, prompt_t *, char **); +} hw_cmd_t; + +typedef struct { + msgid_t msgid; + int nargs; /* Number of arguments following msgid */ + int intl; /* Flag: if 1, internationalize */ + const char *msgstr; +} msgcvt_t; + + +#define SLASH "/" +#define CFGA_DEV_DIR "/dev/cfg" +#define DEV_DIR "/dev" +#define DEVICES_DIR "/devices" +#define DEV_DSK "/dev/dsk" +#define DEV_RDSK "/dev/rdsk" +#define DEV_RMT "/dev/rmt" +#define DSK_DIR "dsk" +#define RDSK_DIR "rdsk" +#define RMT_DIR "rmt" + + +#define DYN_SEP "::" +#define LUN_COMP_SEP "," +#define MINOR_SEP ":" + +#define S_FREE(x) (((x) != NULL) ? (free(x), (x) = NULL) : (void *)0) +#define S_STR(x) (((x) == NULL) ? "" : (x)) + + +#define IS_STUB_NODE(s) (di_instance(s) == -1 && \ + di_nodeid(s) == (DI_PROM_NODEID)) + +#define GET_MSG_STR(i) (str_tbl[msg_idx(i)].msgstr) + +#define GET_DYN(a) (((a) != NULL) ? strstr((a), DYN_SEP) : (void *)0) +#define GET_LUN_DYN(a) (((a) != NULL) ? strstr((a), LUN_COMP_SEP) : (void *)0) + +/* + * The following macro removes the separator from the dynamic component. + */ +#define DYN_TO_DYNCOMP(a) ((a) + strlen(DYN_SEP)) +#define LUN_DYN_TO_LUNCOMP(a) ((a) + strlen(LUN_COMP_SEP)) + +/* + * Property names + */ +#define PORT_WWN_PROP "port-wwn" +#define LUN_GUID_PROP "client-guid" +#define LUN_PROP "lun" + +#define WWN_S_LEN 17 /* NULL terminated string */ +#define WWN_SIZE 8 +/* Constants used for repository updates */ +#define ADD_ENTRY 0 +#define REMOVE_ENTRY 1 + +#define FAB_REPOSITORY_DIR "/etc/cfg/fp" +#define FAB_REPOSITORY "/etc/cfg/fp/fabric_WWN_map" +#define TMP_FAB_REPOSITORY "/etc/cfg/fp/fabric_WWN_map.tmp" +#define OLD_FAB_REPOSITORY "/etc/cfg/fp/fabric_WWN_map.old" + +/* MPXIO VHCI root dir */ +#define SCSI_VHCI_ROOT "/devices/scsi_vhci/" +#define SCSI_VHCI_DRVR "scsi_vhci" +#define HBA_MAX_RETRIES 10 + +/* Function prototypes */ + +fpcfga_ret_t get_report_lun_data(const char *xport_phys, + const char *dyncomp, int *num_luns, report_lun_resp_t **resp_buf, + struct scsi_extended_sense *sense, int *l_errnop); +/* Functions in cfga_cs.c */ +fpcfga_ret_t +dev_change_state(cfga_cmd_t, apid_t *, la_wwn_t *, cfga_flags_t, char **, + HBA_HANDLE handle, HBA_PORTATTRIBUTES portAttrs); +fpcfga_ret_t +fca_change_state(cfga_cmd_t, apid_t *, cfga_flags_t, char **); + +/* Functions in cfga_rep.c */ +int update_fabric_wwn_list(int, const char *, char **); + +fpcfga_ret_t dev_insert(fpcfga_cmd_t cmd, apid_t *apidp, prompt_t *argsp, + char **errstring); +fpcfga_ret_t dev_replace(fpcfga_cmd_t cmd, apid_t *apidp, prompt_t *argsp, + char **errstring); +fpcfga_ret_t dev_remove(fpcfga_cmd_t cmd, apid_t *apidp, prompt_t *argsp, + char **errstring); +fpcfga_ret_t reset_common(fpcfga_cmd_t cmd, apid_t *apidp, prompt_t *argsp, + char **errstring); + + +/* List related routines */ +fpcfga_ret_t do_list(apid_t *apidp, fpcfga_cmd_t cmd, + ldata_list_t **ldatalistp, int *nelem, char **errstring); +fpcfga_ret_t do_list_FCP_dev(const char *ap_id, uint_t flags, fpcfga_cmd_t cmd, + ldata_list_t **llpp, int *nelemp, char **errstring); +fpcfga_ret_t list_ext_postprocess(ldata_list_t **ldatalistp, int nelem, + cfga_list_data_t **ap_id_list, int *nlistp, char **errstring); +int stat_path_info_node(di_node_t root, void *arg, int *l_errnop); + +/* Conversion routines */ +fpcfga_ret_t make_xport_logid(const char *xport_phys, char **xport_logpp, + int *l_errnop); +fpcfga_ret_t dyn_apid_to_path(const char *xport_phys, const char *dyncomp, + struct luninfo_list **lunlistpp, int *l_errnop); +void cvt_lawwn_to_dyncomp(const la_wwn_t *pwwn, char **dyncomp, int *l_errnop); +int cvt_dyncomp_to_lawwn(const char *dyncomp, la_wwn_t *port_wwn); +fpcfga_ret_t make_dyncomp_from_dinode(const di_node_t node, char **dyncompp, + int *l_errnop); +fpcfga_ret_t make_portwwn_luncomp_from_dinode(const di_node_t node, + char **dyncompp, int **luncompp, int *l_errnop); +fpcfga_ret_t make_portwwn_luncomp_from_pinode(const di_path_t pinode, + char **dyncompp, int **luncompp, int *l_errnop); +fpcfga_ret_t construct_nodepath_from_dinode(const di_node_t node, + char **node_pathp, int *l_errnop); +u_longlong_t wwnConversion(uchar_t *wwn); + + +/* Functions in cfga_rcm.c */ +fpcfga_ret_t fp_rcm_offline(char *, char **, cfga_flags_t); +fpcfga_ret_t fp_rcm_online(char *, char **, cfga_flags_t); +fpcfga_ret_t fp_rcm_remove(char *, char **, cfga_flags_t); +fpcfga_ret_t fp_rcm_suspend(char *, char *, char **, cfga_flags_t); +fpcfga_ret_t fp_rcm_resume(char *, char *, char **, cfga_flags_t); +fpcfga_ret_t fp_rcm_info(char *, char **, char **); + +/* Utility routines */ +fpcfga_ret_t physpath_to_devlink(const char *basedir, char *xport_phys, + char **xport_logpp, int *l_errnop, int match_minor); +fpcfga_ret_t recurse_dev(const char *basedir, void *arg, + fpcfga_recur_t (*fcn)(const char *lpath, void *arg)); +fpcfga_ret_t apidt_create(const char *ap_id, apid_t *apidp, + char **errstring); +void apidt_free(apid_t *apidp); +cfga_err_t err_cvt(fpcfga_ret_t err); +void list_free(ldata_list_t **llpp); +int known_state(di_node_t node); + +fpcfga_ret_t devctl_cmd(const char *ap_id, fpcfga_cmd_t cmd, + uint_t *statep, int *l_errnop); +fpcfga_ret_t invoke_cmd(const char *func, apid_t *apidt, prompt_t *prp, + char **errstring); + +void cfga_err(char **errstring, int use_errno, ...); +void cfga_msg(struct cfga_msg *msgp, ...); +char *cfga_str(int append_newline, ...); +int msg_idx(msgid_t msgid); +fpcfga_ret_t walk_tree(const char *physpath, void *arg, uint_t init_flags, + walkarg_t *up, fpcfga_cmd_t cmd, int *l_errnop); +int hba_dev_cmp(const char *hba, const char *dev); +int dev_cmp(const char *dev1, const char *dev2, int match_minor); +char *pathdup(const char *path, int *l_errnop); +int getPortAttrsByWWN(HBA_HANDLE handle, HBA_WWN wwn, + HBA_PORTATTRIBUTES *attrs); +int getDiscPortAttrs(HBA_HANDLE handle, int portIndex, + int discIndex, HBA_PORTATTRIBUTES *attrs); +int getAdapterPortAttrs(HBA_HANDLE handle, int portIndex, + HBA_PORTATTRIBUTES *attrs); +int getAdapterAttrs(HBA_HANDLE handle, HBA_ADAPTERATTRIBUTES *attrs); +fpcfga_ret_t findMatchingAdapterPort(char *portPath, + HBA_HANDLE *matchingHandle, int *matchingPortIndex, + HBA_PORTATTRIBUTES *matchingPortAttrs, char **errstring); + +extern msgcvt_t str_tbl[]; + +#ifdef __cplusplus +} +#endif + +#endif /* _CFGA_FP_H */ diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_list.c b/usr/src/lib/cfgadm_plugins/fp/common/cfga_list.c new file mode 100644 index 0000000000..8bde695934 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_list.c @@ -0,0 +1,3742 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#include "cfga_fp.h" +#include <sys/fibre-channel/impl/fc_error.h> + +/* Structure for walking the tree */ +typedef struct { + apid_t *apidp; + char *xport_logp; + ldata_list_t *listp; + fpcfga_cmd_t cmd; + cfga_stat_t chld_config; + cfga_type_t xport_type; + cfga_stat_t xport_rstate; + fpcfga_ret_t ret; + int l_errno; +} fpcfga_list_t; + +typedef struct { + uint_t itype; + const char *ntype; + const char *name; +} fpcfga_devtype_t; + +#define ERR_INQ_DTYPE 0xff + +/* The TYPE field is parseable and should not contain spaces */ +#define FP_FC_PORT_TYPE "fc" +#define FP_FC_PORT_ERROR "fc-error" +#define FP_FC_FABRIC_PORT_TYPE "fc-fabric" +#define FP_FC_PUBLIC_PORT_TYPE "fc-public" +#define FP_FC_PRIVATE_PORT_TYPE "fc-private" +#define FP_FC_PT_TO_PT_PORT_TYPE "fc-pt_to_pt" + +/* Indicates no plag passing */ +#define NO_FLAG 0 + +/* defines for retry algorithm */ +#define OPEN_RETRY_COUNT 5 +#define OPEN_RETRY_INTERVAL 10000 /* 1/100 of a sec. */ +#define IOCTL_RETRY_COUNT 5 +#define IOCTL_RETRY_INTERVAL 5000000 /* 5 sec */ + +/* define for fcp scsi passthru wait */ +#define FCP_SCSI_CMD_TIMEOUT 10 + +/* define for fcp pseudo node */ +#define FCP_PATH "/devices/pseudo/fcp@0:fcp" + +/* Function prototypes */ +static fpcfga_ret_t postprocess_list_data(const ldata_list_t *listp, + fpcfga_cmd_t cmd, cfga_stat_t chld_config, int *np, uint_t flags); +static int stat_fc_dev(di_node_t node, void *arg); +static int stat_FCP_dev(di_node_t node, void *arg); +static fpcfga_ret_t do_stat_fca_xport(fpcfga_list_t *lap, int limited_stat, + HBA_PORTATTRIBUTES portAttrs); +static int get_xport_state(di_node_t node, void *arg); + +static fpcfga_ret_t do_stat_fc_dev(const di_node_t node, const char *nodepath, + fpcfga_list_t *lap, int limited_stat); +static fpcfga_ret_t do_stat_FCP_dev(const di_node_t node, const char *nodepath, + fpcfga_list_t *lap, int limited_stat); +static cfga_stat_t xport_devinfo_to_recep_state(uint_t xport_di_state); +static cfga_stat_t dev_devinfo_to_occupant_state(uint_t dev_di_state); +static void get_hw_info(di_node_t node, cfga_list_data_t *clp); +static const char *get_device_type(di_node_t); +static fpcfga_ret_t init_ldata_for_accessible_dev(const char *dyncomp, + uchar_t inq_type, fpcfga_list_t *lap); +static fpcfga_ret_t init_ldata_for_accessible_FCP_dev(const char *port_wwn, + int num_luns, struct report_lun_resp *resp_buf, + fpcfga_list_t *larg, int *l_errnop); +static fpcfga_ret_t is_dyn_ap_on_ldata_list(const char *port_wwn, + const ldata_list_t *listp, ldata_list_t **matchldpp, int *l_errno); +static fpcfga_ret_t is_FCP_dev_ap_on_ldata_list(const char *port_wwn, + const int lun_num, ldata_list_t *ldatap, ldata_list_t **matchldpp); + +static fpcfga_ret_t init_ldata_for_mpath_dev(di_path_t path, char *port_wwn, + int *l_errnop, fpcfga_list_t *lap); +static fpcfga_ret_t insert_ldata_to_ldatalist(const char *port_wwn, + int *lun_nump, ldata_list_t *listp, ldata_list_t **ldatapp); +static fpcfga_ret_t insert_fc_dev_ldata(const char *port_wwn, + ldata_list_t *listp, ldata_list_t **ldatapp); +static fpcfga_ret_t insert_FCP_dev_ldata(const char *port_wwn, int lun_num, + ldata_list_t *listp, ldata_list_t **ldatapp); +static int stat_path_info_fc_dev(di_node_t root, fpcfga_list_t *lap, + int *l_errnop); +static int stat_path_info_FCP_dev(di_node_t root, fpcfga_list_t *lap, + int *l_errnop); +static fpcfga_ret_t get_accessible_FCP_dev_ldata(const char *dyncomp, + fpcfga_list_t *lap, int *l_errnop); +static fpcfga_ret_t get_standard_inq_data(const char *xport_phys, + const char *dyncomp, uchar_t *lun_num, struct scsi_inquiry **inq_buf, + int *l_errnop); +static void init_fcp_scsi_cmd(struct fcp_scsi_cmd *fscsi, uchar_t *lun_num, + la_wwn_t *pwwn, void *scmdbuf, size_t scmdbuf_len, void *respbuf, + size_t respbuf_len, void *sensebuf, size_t sensebuf_len); +static fpcfga_ret_t issue_fcp_scsi_cmd(const char *xport_phys, + struct fcp_scsi_cmd *fscsi, int *l_errnop); +static uchar_t get_inq_dtype(char *xport_phys, char *dyncomp, HBA_HANDLE handle, + HBA_PORTATTRIBUTES *portAttrs, HBA_PORTATTRIBUTES *discPortAttrs); + +static fpcfga_devtype_t device_list[] = { + { DTYPE_DIRECT, DDI_NT_BLOCK_CHAN, "disk"}, + { DTYPE_DIRECT, DDI_NT_BLOCK, "disk"}, + { DTYPE_DIRECT, DDI_NT_BLOCK_WWN, "disk"}, + { DTYPE_DIRECT, DDI_NT_BLOCK_FABRIC, "disk"}, + { DTYPE_SEQUENTIAL, DDI_NT_TAPE, "tape"}, + { DTYPE_PRINTER, NULL, "printer"}, + { DTYPE_PROCESSOR, NULL, "processor"}, + { DTYPE_WORM, NULL, "WORM"}, + { DTYPE_RODIRECT, DDI_NT_CD_CHAN, "CD-ROM"}, + { DTYPE_RODIRECT, DDI_NT_CD, "CD-ROM"}, + { DTYPE_SCANNER, NULL, "scanner"}, + { DTYPE_OPTICAL, NULL, "optical"}, + { DTYPE_CHANGER, NULL, "med-changer"}, + { DTYPE_COMM, NULL, "comm-device"}, + { DTYPE_ARRAY_CTRL, NULL, "array-ctrl"}, + { DTYPE_ESI, NULL, "ESI"}, + /* + * This has to be the LAST entry for DTYPE_UNKNOWN_INDEX. + * Add entries before this. + */ + { DTYPE_UNKNOWN, NULL, "unknown"} +}; + +#define N_DEVICE_TYPES (sizeof (device_list) / sizeof (device_list[0])) + +#define DTYPE_UNKNOWN_INDEX (N_DEVICE_TYPES - 1) + +/* + * Main routine for list operation. + * It calls various routines to consturct ldata list and + * postprocess the list data. + * + * Overall algorithm: + * Get the device list on input hba port and construct ldata list for + * accesible devices. + * Stat hba port and devices through walking the device tree. + * Verify the validity of the list data. + */ +fpcfga_ret_t +do_list( + apid_t *apidp, + fpcfga_cmd_t cmd, + ldata_list_t **llpp, + int *nelemp, + char **errstring) +{ + int n = -1, l_errno = 0, limited_stat; + walkarg_t walkarg; + fpcfga_list_t larg = {NULL}; + fpcfga_ret_t ret; + la_wwn_t pwwn; + char *dyncomp = NULL; + HBA_HANDLE handle; + HBA_PORTATTRIBUTES portAttrs; + HBA_PORTATTRIBUTES discPortAttrs; + HBA_STATUS status; + int portIndex, discIndex; + int retry; + uchar_t inq_dtype; + + if (*llpp != NULL || *nelemp != 0) { + return (FPCFGA_ERR); + } + + /* Create the hba logid (also base component of logical ap_id) */ + ret = make_xport_logid(apidp->xport_phys, &larg.xport_logp, &l_errno); + if (ret != FPCFGA_OK) { + cfga_err(errstring, l_errno, ERR_LIST, 0); + return (FPCFGA_ERR); + } + + assert(larg.xport_logp != NULL); + + larg.cmd = cmd; + larg.apidp = apidp; + larg.xport_rstate = CFGA_STAT_NONE; + + if ((ret = findMatchingAdapterPort(larg.apidp->xport_phys, &handle, + &portIndex, &portAttrs, errstring)) != FPCFGA_OK) { + S_FREE(larg.xport_logp); + return (ret); + } + + /* + * If stating a specific device, we will do limited stat on fca port. + * otherwise full stat on fca part is required. + * If stating a specific device we don't know if it exists or is + * configured yet. larg.ret is set to apid noexist for do_stat_dev. + * otherwise larg.ret is set to ok initially. + */ + if (larg.cmd == FPCFGA_STAT_FC_DEV) { + limited_stat = 1; /* for do_stat_fca_xport */ + larg.ret = FPCFGA_APID_NOEXIST; /* for stat_fc_dev */ + } else { + limited_stat = 0; /* for do_stat_fca_xport */ + larg.ret = FPCFGA_OK; /* for stat_fc_dev */ + } + + /* For all list commands, the fca port needs to be stat'ed */ + if ((ret = do_stat_fca_xport(&larg, limited_stat, + portAttrs)) != FPCFGA_OK) { + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (ret); + } + +#ifdef DEBUG + if (limited_stat) { + assert(larg.listp == NULL); + } else { + assert(larg.listp != NULL); + } +#endif + /* + * If stat'ing a FCA port or ALL, we have the bus stat data at + * this point. + * Assume that the bus has no configured children. + */ + larg.chld_config = CFGA_STAT_UNCONFIGURED; + + switch (larg.cmd) { + case FPCFGA_STAT_FC_DEV: + /* la_wwn_t has uchar_t raw_wwn[8] thus no need to free. */ + if (cvt_dyncomp_to_lawwn(apidp->dyncomp, &pwwn) != 0) { + cfga_err(errstring, 0, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + /* + * if the dyncomp exists on disco ports construct list_data + * otherwise return FPCFGA_APID_NOEXIST. + */ + retry = 0; + do { + status = getPortAttrsByWWN(handle, + *((HBA_WWN *)(&pwwn)), &discPortAttrs); + if (status == HBA_STATUS_ERROR_STALE_DATA) { + /* get Port Attributes again after refresh. */ + HBA_RefreshInformation(handle); + } else { + break; /* either okay or some other error */ + } + } while (retry++ < HBA_MAX_RETRIES); + + if (status == HBA_STATUS_OK) { + /* + * if dyncomp found in disco ports + * construct ldata_list and return. + * otherwise continue to stat on dev tree with + * larg.ret set to access_ok which informs stat_fc_dev + * the existence of device on disco ports. + * + * if path is null that guatantees the node is not + * configured. if node is detached the path + * is incomplete and not usable for further + * operations like uscsi_inq so take care of it here. + */ + inq_dtype = get_inq_dtype(apidp->xport_phys, + apidp->dyncomp, handle, &portAttrs, &discPortAttrs); + + if (init_ldata_for_accessible_dev(apidp->dyncomp, + inq_dtype, &larg) != FPCFGA_OK) { + cfga_err(errstring, larg.l_errno, + ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + if (apidp->lunlist == NULL) { + n = 0; + if (postprocess_list_data( + larg.listp, cmd, + larg.chld_config, &n, NO_FLAG) != + FPCFGA_OK) { + cfga_err(errstring, + larg.l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + *nelemp = n; + *llpp = larg.listp; + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_OK); + } + larg.ret = FPCFGA_ACCESS_OK; + } else if (status == HBA_STATUS_ERROR_ILLEGAL_WWN) { + /* + * path indicates if the node exists in dev tree. + * if not found in dev tree return apid no exist. + * otherwise continue to stat with larg.ret set to + * apid_noexist. + */ + if (apidp->lunlist == NULL) { + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_APID_NOEXIST); + } + } else { /* any error */ + /* + * path indicates if the node exists in dev tree. + * if not found in dev tree return lib error. + * otherwise continue to stat with larg.ret set to + * apid_noexist. + */ + if (apidp->lunlist == NULL) { + cfga_err(errstring, 0, ERR_FC_GET_DEVLIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + } + break; + case FPCFGA_STAT_ALL: + /* + * for each dev in disco ports, create a ldata_list element. + * if if no disco ports found, continue to stat on devinfo tree + * to see if any node exist on the fca port. + */ + for (discIndex = 0; + discIndex < portAttrs.NumberofDiscoveredPorts; + discIndex++) { + if (getDiscPortAttrs(handle, portIndex, + discIndex, &discPortAttrs)) { + /* Move on to the next target */ + continue; + } + memcpy(&pwwn, &discPortAttrs.PortWWN, sizeof (la_wwn_t)); + cvt_lawwn_to_dyncomp(&pwwn, &dyncomp, &l_errno); + if (dyncomp == NULL) { + cfga_err(errstring, l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + inq_dtype = get_inq_dtype(apidp->xport_phys, dyncomp, + handle, &portAttrs, &discPortAttrs); + + if ((ret = init_ldata_for_accessible_dev( + dyncomp, inq_dtype, &larg)) != FPCFGA_OK) { + S_FREE(dyncomp); + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + S_FREE(dyncomp); + } + break; + default: + break; + } + + /* we need to stat at least 1 device for all commands */ + if (apidp->flags == FLAG_DEVINFO_FORCE) { + walkarg.flags = FLAG_DEVINFO_FORCE; + } else { + walkarg.flags = 0; + } + + walkarg.flags |= FLAG_PATH_INFO_WALK; + walkarg.walkmode.node_args.flags = DI_WALK_CLDFIRST; + walkarg.walkmode.node_args.fcn = stat_fc_dev; + + /* + * Subtree is ALWAYS rooted at the HBA (not at the device) as + * otherwise deadlock may occur if bus is disconnected. + * + * DINFOPROP was sufficient on apidp->xport_phys prior to the support + * on scsi_vhci child node. In order to get the link between + * scsi_vhci node and path info node the snap shot of the + * the whole device tree is required with DINFOCPYALL | DINFOPATH flag. + */ + ret = walk_tree(apidp->xport_phys, &larg, DINFOCPYALL | DINFOPATH, + &walkarg, FPCFGA_WALK_NODE, &larg.l_errno); + + /* + * ret from walk_tree is either FPCFGA_OK or FPCFGA_ERR. + * larg.ret is used to detect other errors. Make sure larg.ret + * is set to a correct error. + */ + if (ret != FPCFGA_OK || (ret = larg.ret) != FPCFGA_OK) { + if (ret != FPCFGA_APID_NOEXIST) { + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + } + /* if larg.ret = FPCFGA_APID_NOEXIST; */ + goto out; + } + + assert(larg.listp != NULL); + + n = 0; + ret = postprocess_list_data(larg.listp, cmd, larg.chld_config, &n, + NO_FLAG); + if (ret != FPCFGA_OK) { + cfga_err(errstring, 0, ERR_LIST, 0); + ret = FPCFGA_LIB_ERR; + goto out; + } + + *nelemp = n; + *llpp = larg.listp; + ret = FPCFGA_OK; + /* FALLTHROUGH */ +out: + if (ret != FPCFGA_OK) list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (ret); +} + +/* + * Main routine for list operation when show_FCP_dev option is given. + * It calls various routines to consturct ldata list and + * postprocess the list data. + * + * The difference between do_list() and do_list_FCP_dev() is to + * process FCP SCSI LUN data list via uscsi report lun operation and + * stat lun level instead of port WWN based target level. + * The rest of logic is same. + * + * Overall algorithm: + * Get the device list on input hba port and construct ldata list for + * accesible devices. + * For each configured device, USCSI report lun is issued and ldata list + * with FCP device level(LUN) information is created. + * Stat hba port and LUN devices through walking the device tree. + * Verify the validity of the list data. + */ +fpcfga_ret_t +do_list_FCP_dev( + const char *ap_id, + uint_t flags, + fpcfga_cmd_t cmd, + ldata_list_t **llpp, + int *nelemp, + char **errstring) +{ + int n = -1, l_errno = 0, limited_stat, len; + walkarg_t walkarg; + fpcfga_list_t larg = {NULL}; + fpcfga_ret_t ret; + la_wwn_t pwwn; + char *xport_phys = NULL, *dyn = NULL, *dyncomp = NULL, + *lun_dyn = NULL; + apid_t apid_con = {NULL}; + HBA_HANDLE handle; + HBA_PORTATTRIBUTES portAttrs; + HBA_PORTATTRIBUTES discPortAttrs; + HBA_STATUS status; + int portIndex, discIndex; + int retry; + uint64_t lun = 0; + struct scsi_inquiry inq; + struct scsi_extended_sense sense; + HBA_UINT8 scsiStatus; + uint32_t inquirySize = sizeof (inq), + senseSize = sizeof (sense); + + if (*llpp != NULL || *nelemp != 0) { + return (FPCFGA_ERR); + } + + if ((xport_phys = pathdup(ap_id, &l_errno)) == NULL) { + cfga_err(errstring, l_errno, ERR_OP_FAILED, 0); + return (FPCFGA_LIB_ERR); + } + + /* Extract the base(hba) and dynamic(device) component if any */ + if ((dyn = GET_DYN(xport_phys)) != NULL) { + len = strlen(DYN_TO_DYNCOMP(dyn)) + 1; + dyncomp = calloc(1, len); + if (dyncomp == NULL) { + cfga_err(errstring, errno, ERR_OP_FAILED, 0); + S_FREE(xport_phys); + return (FPCFGA_LIB_ERR); + } + + (void) strcpy(dyncomp, DYN_TO_DYNCOMP(dyn)); + /* Remove the dynamic component from the base. */ + *dyn = '\0'; + /* if lun dyncomp exists delete it */ + if ((lun_dyn = GET_LUN_DYN(dyncomp)) != NULL) { + *lun_dyn = '\0'; + } + } + + apid_con.xport_phys = xport_phys; + apid_con.dyncomp = dyncomp; + apid_con.flags = flags; + + larg.apidp = &apid_con; + + /* Create the hba logid (also base component of logical ap_id) */ + ret = make_xport_logid(larg.apidp->xport_phys, &larg.xport_logp, + &l_errno); + if (ret != FPCFGA_OK) { + cfga_err(errstring, l_errno, ERR_LIST, 0); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + return (FPCFGA_ERR); + } + + assert(larg.xport_logp != NULL); + + larg.cmd = cmd; + larg.xport_rstate = CFGA_STAT_NONE; + + if ((ret = findMatchingAdapterPort(larg.apidp->xport_phys, &handle, + &portIndex, &portAttrs, errstring)) != FPCFGA_OK) { + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->dyncomp); + return (ret); + } + + /* + * If stating a specific device, we will do limited stat on fca port. + * otherwise full stat on fca part is required. + * If stating a specific device we don't know if it exists or is + * configured yet. larg.ret is set to apid noexist for do_stat_dev. + * otherwise larg.ret is set to ok initially. + */ + if (larg.cmd == FPCFGA_STAT_FC_DEV) { + limited_stat = 1; /* for do_stat_fca_xport */ + larg.ret = FPCFGA_APID_NOEXIST; /* for stat_fc_dev */ + } else { + limited_stat = 0; /* for do_stat_fca_xport */ + larg.ret = FPCFGA_OK; /* for stat_fc_dev */ + } + + /* For all list commands, the fca port needs to be stat'ed */ + if ((ret = do_stat_fca_xport(&larg, limited_stat, + portAttrs)) != FPCFGA_OK) { + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (ret); + } + + /* + * If stat'ing a FCA port or ALL, we have the bus stat data at + * this point. + * Assume that the bus has no configured children. + */ + larg.chld_config = CFGA_STAT_UNCONFIGURED; + + switch (larg.cmd) { + case FPCFGA_STAT_FC_DEV: + /* la_wwn_t has uchar_t raw_wwn[8] thus no need to free. */ + if (cvt_dyncomp_to_lawwn(larg.apidp->dyncomp, &pwwn) != 0) { + cfga_err(errstring, 0, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + /* + * if the dyncomp exists on disco ports construct list_data + * otherwise return FPCFGA_APID_NOEXIST. + */ + retry = 0; + do { + status = getPortAttrsByWWN(handle, + *((HBA_WWN *)(&pwwn)), &discPortAttrs); + if (status == HBA_STATUS_ERROR_STALE_DATA) { + /* get Port Attributes again after refresh. */ + HBA_RefreshInformation(handle); + } else { + break; /* either okay or some other error */ + } + } while (retry++ < HBA_MAX_RETRIES); + + if (status == HBA_STATUS_OK) { + /* + * if dyncomp exists only in dev list + * construct ldata_list and return. + * otherwise continue to stat on dev tree with + * larg.ret set to access_ok which informs stat_fc_dev + * the existence of device on dev_list. + * + * if path is null that guatantees the node is not + * configured. if node is detached the path + * is incomplete and not usable for further + * operations like uscsi_inq so take care of it here. + */ + status = HBA_ScsiInquiryV2(handle, portAttrs.PortWWN, + discPortAttrs.PortWWN, lun, 0, 0, + &inq, &inquirySize, &scsiStatus, + &sense, &senseSize); + if (status == HBA_STATUS_OK) { + inq.inq_dtype = inq.inq_dtype & DTYPE_MASK; + } else if (status == HBA_STATUS_ERROR_NOT_A_TARGET) { + inq.inq_dtype = DTYPE_UNKNOWN; + } else { + inq.inq_dtype = ERR_INQ_DTYPE; + } + + if (init_ldata_for_accessible_dev(larg.apidp->dyncomp, + inq.inq_dtype, &larg) != FPCFGA_OK) { + cfga_err(errstring, larg.l_errno, + ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + if ((ret = get_accessible_FCP_dev_ldata( + larg.apidp->dyncomp, &larg, &l_errno)) + != FPCFGA_OK) { + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } else { + /* continue to stat dev with access okay. */ + larg.ret = FPCFGA_ACCESS_OK; + } + } else if (status == HBA_STATUS_ERROR_ILLEGAL_WWN) { + /* + * path indicates if the node exists in dev tree. + * if not found in dev tree return apid no exist. + * otherwise continue to stat with larg.ret set to + * apid_noexist. + */ + if (larg.apidp->lunlist == NULL) { + list_free(&larg.listp); + S_FREE(larg.xport_logp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_APID_NOEXIST); + } + } else { /* not found or any error */ + /* + * continue to stat dev with larg.ret set to + * apid_noexist. + */ + larg.ret = FPCFGA_APID_NOEXIST; + } + break; + case FPCFGA_STAT_ALL: + /* + * for each dev in disco ports, create a ldata_list element. + * if if no disco ports found, continue to stat on devinfo tree + * to see if any node exist on the fca port. + */ + for (discIndex = 0; + discIndex < portAttrs.NumberofDiscoveredPorts; + discIndex++) { + if (getDiscPortAttrs(handle, portIndex, + discIndex, &discPortAttrs)) { + /* Move on to the next target */ + continue; + } + memcpy(&pwwn, &discPortAttrs.PortWWN, sizeof (la_wwn_t)); + cvt_lawwn_to_dyncomp(&pwwn, &dyncomp, &l_errno); + if (dyncomp == NULL) { + cfga_err(errstring, l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + status = HBA_ScsiInquiryV2(handle, portAttrs.PortWWN, + discPortAttrs.PortWWN, lun, 0, 0, + &inq, &inquirySize, &scsiStatus, + &sense, &senseSize); + if (status == HBA_STATUS_OK) { + inq.inq_dtype = inq.inq_dtype & DTYPE_MASK; + } else if (status == HBA_STATUS_ERROR_NOT_A_TARGET) { + inq.inq_dtype = DTYPE_UNKNOWN; + } else { + inq.inq_dtype = ERR_INQ_DTYPE; + } + if ((ret = init_ldata_for_accessible_dev( + dyncomp, inq.inq_dtype, &larg)) != FPCFGA_OK) { + S_FREE(dyncomp); + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + if ((ret = get_accessible_FCP_dev_ldata( + dyncomp, &larg, &l_errno)) != FPCFGA_OK) { + S_FREE(dyncomp); + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (ret); + } + S_FREE(dyncomp); + } + break; + /* default: continue */ + } + + /* we need to stat at least 1 device for all commands */ + if ((larg.apidp->flags & FLAG_DEVINFO_FORCE) == FLAG_DEVINFO_FORCE) { + walkarg.flags = FLAG_DEVINFO_FORCE; + } else { + walkarg.flags = 0; + } + + walkarg.flags |= FLAG_PATH_INFO_WALK; + walkarg.walkmode.node_args.flags = DI_WALK_CLDFIRST; + walkarg.walkmode.node_args.fcn = stat_FCP_dev; + + /* + * Subtree is ALWAYS rooted at the HBA (not at the device) as + * otherwise deadlock may occur if bus is disconnected. + * + * DINFOPROP was sufficient on apidp->xport_phys prior to the support + * on scsi_vhci child node. In order to get the link between + * scsi_vhci node and path info node the snap shot of the + * the whole device tree is required with DINFOCPYALL | DINFOPATH flag. + */ + ret = walk_tree(larg.apidp->xport_phys, &larg, DINFOCPYALL | DINFOPATH, + &walkarg, FPCFGA_WALK_NODE, &larg.l_errno); + + /* + * ret from walk_tree is either FPCFGA_OK or FPCFGA_ERR. + * larg.ret is used to detect other errors. Make sure larg.ret + * is set to a correct error. + */ + if (ret != FPCFGA_OK || (ret = larg.ret) != FPCFGA_OK) { + if (ret != FPCFGA_APID_NOEXIST) { + cfga_err(errstring, larg.l_errno, ERR_LIST, 0); + } + /* if larg.ret = FPCFGA_APID_NOEXIST return. */ + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (ret); + } + + assert(larg.listp != NULL); + + n = 0; + ret = postprocess_list_data(larg.listp, cmd, larg.chld_config, &n, + flags); + if (ret != FPCFGA_OK) { + cfga_err(errstring, 0, ERR_LIST, 0); + list_free(&larg.listp); + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + + *nelemp = n; + *llpp = larg.listp; + ret = FPCFGA_OK; + S_FREE(larg.xport_logp); + S_FREE(larg.apidp->xport_phys); + S_FREE(larg.apidp->dyncomp); + HBA_CloseAdapter(handle); + HBA_FreeLibrary(); + return (FPCFGA_OK); +} + +/* + * This routine returns initialize struct fcp_ioctl. + */ +static void +init_fcp_scsi_cmd( + struct fcp_scsi_cmd *fscsi, + uchar_t *lun_num, + la_wwn_t *pwwn, + void *scmdbuf, + size_t scmdbuf_len, + void *respbuf, + size_t respbuf_len, + void *sensebuf, + size_t sensebuf_len) +{ + memset(fscsi, 0, sizeof (struct fcp_scsi_cmd)); + memset(scmdbuf, 0, scmdbuf_len); + memcpy(fscsi->scsi_fc_pwwn.raw_wwn, pwwn, sizeof (u_longlong_t)); + fscsi->scsi_fc_rspcode = 0; + fscsi->scsi_flags = FCP_SCSI_READ; + fscsi->scsi_timeout = FCP_SCSI_CMD_TIMEOUT; /* second */ + fscsi->scsi_cdbbufaddr = (caddr_t)scmdbuf; + fscsi->scsi_cdblen = scmdbuf_len; + fscsi->scsi_bufaddr = (caddr_t)respbuf; + fscsi->scsi_buflen = respbuf_len; + fscsi->scsi_bufresid = 0; + fscsi->scsi_bufstatus = 0; + fscsi->scsi_rqbufaddr = (caddr_t)sensebuf; + fscsi->scsi_rqlen = sensebuf_len; + fscsi->scsi_rqresid = 0; + memcpy(&fscsi->scsi_lun, lun_num, sizeof (fscsi->scsi_lun)); +} + +/* + * This routine returns issues FCP_TGT_SEND_SCSI + */ +static fpcfga_ret_t +issue_fcp_scsi_cmd( + const char *xport_phys, + struct fcp_scsi_cmd *fscsi, + int *l_errnop) +{ + struct stat stbuf; + int fcp_fd, retry, rv; + + if (stat(xport_phys, &stbuf) < 0) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + fscsi->scsi_fc_port_num = (uint32_t)minor(stbuf.st_rdev); + fcp_fd = open(FCP_PATH, O_RDONLY | O_NDELAY); + retry = 0; + while (fcp_fd < 0 && retry++ < OPEN_RETRY_COUNT && ( + errno == EBUSY || errno == EAGAIN)) { + (void) usleep(OPEN_RETRY_INTERVAL); + fcp_fd = open(FCP_PATH, O_RDONLY|O_NDELAY); + } + if (fcp_fd < 0) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + rv = ioctl(fcp_fd, FCP_TGT_SEND_SCSI, fscsi); + retry = 0; + while ((rv != 0 && retry++ < IOCTL_RETRY_COUNT && + (errno == EBUSY || errno == EAGAIN)) || + (retry++ < IOCTL_RETRY_COUNT && + ((uchar_t)fscsi->scsi_bufstatus & STATUS_MASK) + == STATUS_BUSY)) { + (void) usleep(IOCTL_RETRY_INTERVAL); + rv = ioctl(fcp_fd, FCP_TGT_SEND_SCSI, fscsi); + } + close(fcp_fd); + + if (fscsi->scsi_fc_status == FC_DEVICE_NOT_TGT) { + return (FPCFGA_FCP_SEND_SCSI_DEV_NOT_TGT); + } else if (rv != 0 || fscsi->scsi_bufstatus != 0) { + *l_errnop = errno; + return (FPCFGA_FCP_TGT_SEND_SCSI_FAILED); + } + return (FPCFGA_OK); +} + +/* + * This routine returns standard inq data for + * a target represented by dyncomp. + * + * Calls FCP passthru ioctl FCP_TGT_SEND_SCSI to get inquiry data. + * + * Caller should free the *inq_buf. + */ +static fpcfga_ret_t +get_standard_inq_data( + const char *xport_phys, + const char *dyncomp, + uchar_t *lun_num, + struct scsi_inquiry **inq_buf, + int *l_errnop) +{ + struct fcp_scsi_cmd fscsi; + struct scsi_extended_sense sensebuf; + union scsi_cdb scsi_inq_req; + la_wwn_t pwwn; + int alloc_len; + fpcfga_ret_t ret; + + + alloc_len = sizeof (struct scsi_inquiry); + if ((*inq_buf = (struct scsi_inquiry *)calloc(1, alloc_len)) == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + if (cvt_dyncomp_to_lawwn(dyncomp, &pwwn) != 0) { + return (FPCFGA_LIB_ERR); + } + + init_fcp_scsi_cmd(&fscsi, lun_num, &pwwn, &scsi_inq_req, + sizeof (scsi_inq_req), *inq_buf, alloc_len, &sensebuf, + sizeof (struct scsi_extended_sense)); + scsi_inq_req.scc_cmd = SCMD_INQUIRY; + scsi_inq_req.g0_count0 = sizeof (struct scsi_inquiry); + + if ((ret = issue_fcp_scsi_cmd(xport_phys, &fscsi, l_errnop)) + != FPCFGA_OK) { + S_FREE(*inq_buf); + return (ret); + } + + return (FPCFGA_OK); +} + +/* + * This routine returns report lun data and number of luns found + * on a target represented by dyncomp. + * + * Calls FCP passthru ioctl FCP_TGT_SEND_SCSI to get report lun data. + * + * Caller should free the *resp_buf when FPCFGA_OK is returned. + */ +fpcfga_ret_t +get_report_lun_data( + const char *xport_phys, + const char *dyncomp, + int *num_luns, + report_lun_resp_t **resp_buf, + struct scsi_extended_sense *sensebuf, + int *l_errnop) +{ + struct fcp_scsi_cmd fscsi; + union scsi_cdb scsi_rl_req; + la_wwn_t pwwn; + int alloc_len; + fpcfga_ret_t ret; + uchar_t lun_data[SAM_LUN_SIZE]; + + alloc_len = sizeof (struct report_lun_resp); + if ((*resp_buf = (report_lun_resp_t *)calloc(1, alloc_len)) == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + if (cvt_dyncomp_to_lawwn(dyncomp, &pwwn) != 0) { + S_FREE(*resp_buf); + return (FPCFGA_LIB_ERR); + } + + /* sending to LUN 0 so initializing lun_data buffer to be 0 */ + memset(lun_data, 0, sizeof (lun_data)); + init_fcp_scsi_cmd(&fscsi, lun_data, &pwwn, &scsi_rl_req, + sizeof (scsi_rl_req), *resp_buf, alloc_len, sensebuf, + sizeof (struct scsi_extended_sense)); + scsi_rl_req.scc_cmd = FP_SCMD_REPORT_LUN; + FORMG5COUNT(&scsi_rl_req, alloc_len); + + if ((ret = issue_fcp_scsi_cmd(xport_phys, &fscsi, l_errnop)) + != FPCFGA_OK) { + S_FREE(*resp_buf); + return (ret); + } + + if (ntohl((*resp_buf)->num_lun) > + (sizeof (struct report_lun_resp) - REPORT_LUN_HDR_SIZE)) { + alloc_len = (*resp_buf)->num_lun + REPORT_LUN_HDR_SIZE; + S_FREE(*resp_buf); + if ((*resp_buf = (report_lun_resp_t *)calloc(1, alloc_len)) + == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + (void) memset((char *)*resp_buf, 0, alloc_len); + FORMG5COUNT(&scsi_rl_req, alloc_len); + + fscsi.scsi_bufaddr = (caddr_t)*resp_buf; + fscsi.scsi_buflen = alloc_len; + + if ((ret = issue_fcp_scsi_cmd(xport_phys, &fscsi, l_errnop)) + != FPCFGA_OK) { + S_FREE(*resp_buf); + return (ret); + } + } + + /* num_lun represent number of luns * 8. */ + *num_luns = ntohl((*resp_buf)->num_lun) >> 3; + + return (FPCFGA_OK); +} + +/* + * Routine for consturct ldata list for each FCP SCSI LUN device + * for a discovered target device. + * It calls get_report_lun_data to get report lun data and + * construct ldata list per each lun. + * + * It is called only when show_FCP_dev option is given. + * + * Overall algorithm: + * Get the report lun data thru FCP passthru ioctl. + * Call init_ldata_for_accessible_FCP_dev to process the report LUN data. + * For each LUN found standard inquiry is issued to get device type. + */ +static fpcfga_ret_t +get_accessible_FCP_dev_ldata( + const char *dyncomp, + fpcfga_list_t *lap, + int *l_errnop) +{ + report_lun_resp_t *resp_buf; + struct scsi_extended_sense sense; + int num_luns; + fpcfga_ret_t ret; + + memset(&sense, 0, sizeof (sense)); + if ((ret = get_report_lun_data(lap->apidp->xport_phys, dyncomp, + &num_luns, &resp_buf, &sense, l_errnop)) != FPCFGA_OK) { + /* + * when report lun data fails then return FPCFGA_OK thus + * keep the ldata for the target which is acquired previously. + * For remote hba node this will be normal. + * For a target error may already be detected through + * FCP_TGT_INQ. + */ + if ((ret == FPCFGA_FCP_TGT_SEND_SCSI_FAILED) || + (ret == FPCFGA_FCP_SEND_SCSI_DEV_NOT_TGT)) { + ret = FPCFGA_OK; + } + return (ret); + } + + if (num_luns > 0) { + ret = init_ldata_for_accessible_FCP_dev( + dyncomp, num_luns, resp_buf, lap, l_errnop); + } else { + /* + * proceed with to stat if no lun found. + * This will make the target apid will be kept. + */ + ret = FPCFGA_OK; + } + + S_FREE(resp_buf); + return (ret); +} + +/* + * Routine for checking validity of ldata list based on input argumemnt. + * Set the occupant state of hba port if the list is valid. + */ +static fpcfga_ret_t +postprocess_list_data( + const ldata_list_t *listp, + fpcfga_cmd_t cmd, + cfga_stat_t chld_config, + int *np, + uint_t flags) +{ + ldata_list_t *tmplp = NULL; + cfga_list_data_t *xport_ldatap = NULL; + int i; + + + *np = 0; + + if (listp == NULL) { + return (FPCFGA_ERR); + } + + tmplp = (ldata_list_t *)listp; + for (i = 0; tmplp != NULL; tmplp = tmplp->next) { + i++; + if (GET_DYN(tmplp->ldata.ap_phys_id) == NULL) { + /* A bus stat data */ + assert(GET_DYN(tmplp->ldata.ap_log_id) == NULL); + xport_ldatap = &tmplp->ldata; +#ifdef DEBUG + } else { + assert(GET_DYN(tmplp->ldata.ap_log_id) != NULL); +#endif + } + } + + switch (cmd) { + case FPCFGA_STAT_FC_DEV: + if ((flags & FLAG_FCP_DEV) == FLAG_FCP_DEV) { + if (i < 1 || xport_ldatap != NULL) { + return (FPCFGA_LIB_ERR); + } + } else { + if (i != 1 || xport_ldatap != NULL) { + return (FPCFGA_LIB_ERR); + } + } + break; + case FPCFGA_STAT_FCA_PORT: + if (i != 1 || xport_ldatap == NULL) { + return (FPCFGA_LIB_ERR); + } + break; + case FPCFGA_STAT_ALL: + if (i < 1 || xport_ldatap == NULL) { + return (FPCFGA_LIB_ERR); + } + break; + default: + return (FPCFGA_LIB_ERR); + } + + *np = i; + + /* Fill in the occupant (child) state. */ + if (xport_ldatap != NULL) { + xport_ldatap->ap_o_state = chld_config; + } + return (FPCFGA_OK); +} + +/* + * Routine for checking each target device found in device tree. + * When the matching port WWN dev is found from the accessble ldata list + * the target device is updated with configured ostate. + * + * Overall algorithm: + * Parse the device tree to find configured devices which matches with + * list argument. If cmd is stat on a specific target device it + * matches port WWN and continues to further processing. If cmd is + * stat on hba port all the device target under the hba are processed. + */ +static int +stat_fc_dev(di_node_t node, void *arg) +{ + fpcfga_list_t *lap = NULL; + char *devfsp = NULL, *nodepath = NULL; + size_t len = 0; + int limited_stat = 0, match_minor, rv; + fpcfga_ret_t ret; + di_prop_t prop = DI_PROP_NIL; + uchar_t *port_wwn_data; + char port_wwn[WWN_SIZE*2+1]; + int count; + + lap = (fpcfga_list_t *)arg; + + /* + * Skip partial nodes + * + * This checking is from the scsi plug-in and will be deleted for + * fp plug-in. The node will be processed for fp even if it is + * in driver detached state. From fp perspective the node is configured + * as long as the node is not in offline or down state. + * scsi plug-in considers the known state when it is offlined + * regradless of driver detached state or when it is not in driver + * detached state like normal state. + * If the node is only in driver detached state it is considered as + * unknown state. + * + * if (!known_state(node) && (lap->cmd != FPCFGA_STAT_FC_DEV)) { + * return (DI_WALK_CONTINUE); + * + */ + + devfsp = di_devfs_path(node); + if (devfsp == NULL) { + rv = DI_WALK_CONTINUE; + goto out; + } + + len = strlen(DEVICES_DIR) + strlen(devfsp) + 1; + + nodepath = calloc(1, len); + if (nodepath == NULL) { + lap->l_errno = errno; + lap->ret = FPCFGA_LIB_ERR; + rv = DI_WALK_TERMINATE; + goto out; + } + + (void) snprintf(nodepath, len, "%s%s", DEVICES_DIR, devfsp); + + /* Skip node if it is HBA */ + match_minor = 0; + if (!dev_cmp(lap->apidp->xport_phys, nodepath, match_minor)) { + rv = DI_WALK_CONTINUE; + goto out; + } + + /* If stat'ing a specific device, is this node that device */ + if (lap->cmd == FPCFGA_STAT_FC_DEV) { + /* checks port wwn property to find a match */ + while ((prop = di_prop_next(node, prop)) + != DI_PROP_NIL) { + if ((strcmp(PORT_WWN_PROP, + di_prop_name(prop)) == 0) && + (di_prop_type(prop) == + DI_PROP_TYPE_BYTE)) { + break; + } + } + + if (prop != DI_PROP_NIL) { + count = di_prop_bytes(prop, &port_wwn_data); + if (count != WWN_SIZE) { + lap->ret = FPCFGA_LIB_ERR; + rv = DI_WALK_TERMINATE; + goto out; + } + (void) sprintf(port_wwn, "%016llx", + (wwnConversion(port_wwn_data))); + /* + * port wwn doesn't match contine to walk + * if match call do_stat_fc_dev. + */ + if (strncmp(port_wwn, lap->apidp->dyncomp, + WWN_SIZE*2)) { + rv = DI_WALK_CONTINUE; + goto out; + } + } else { + rv = DI_WALK_CONTINUE; + goto out; + } + } + + /* + * If stat'ing a xport only, we look at device nodes only to get + * xport configuration status. So a limited stat will suffice. + */ + if (lap->cmd == FPCFGA_STAT_FCA_PORT) { + limited_stat = 1; + } else { + limited_stat = 0; + } + + /* + * Ignore errors if stat'ing a bus or listing all + */ + ret = do_stat_fc_dev(node, nodepath, lap, limited_stat); + if (ret != FPCFGA_OK) { + if (lap->cmd == FPCFGA_STAT_FC_DEV) { + lap->ret = ret; + rv = DI_WALK_TERMINATE; + } else { + rv = DI_WALK_CONTINUE; + } + goto out; + } + + /* Are we done ? */ + rv = DI_WALK_CONTINUE; + if (lap->cmd == FPCFGA_STAT_FCA_PORT && + lap->chld_config == CFGA_STAT_CONFIGURED) { + rv = DI_WALK_TERMINATE; + } else if (lap->cmd == FPCFGA_STAT_FC_DEV) { + /* + * If stat'ing a specific device, we are done at this point. + */ + rv = DI_WALK_TERMINATE; + } + + /*FALLTHRU*/ +out: + S_FREE(nodepath); + if (devfsp != NULL) di_devfs_path_free(devfsp); + return (rv); +} + +/* + * Routine for checking each FCP SCSI LUN device found in device tree. + * When the matching port WWN and LUN are found from the accessble ldata list + * the FCP SCSI LUN is updated with configured ostate. + * + * Overall algorithm: + * Parse the device tree to find configured devices which matches with + * list argument. If cmd is stat on a specific target device it + * matches port WWN and continues to further processing. If cmd is + * stat on hba port all the FCP SCSI LUN under the hba are processed. + */ +static int +stat_FCP_dev(di_node_t node, void *arg) +{ + fpcfga_list_t *lap = NULL; + char *devfsp = NULL, *nodepath = NULL; + size_t len = 0; + int limited_stat = 0, match_minor, rv, di_ret; + fpcfga_ret_t ret; + uchar_t *port_wwn_data; + char port_wwn[WWN_SIZE*2+1]; + + lap = (fpcfga_list_t *)arg; + + devfsp = di_devfs_path(node); + if (devfsp == NULL) { + rv = DI_WALK_CONTINUE; + goto out; + } + + len = strlen(DEVICES_DIR) + strlen(devfsp) + 1; + + nodepath = calloc(1, len); + if (nodepath == NULL) { + lap->l_errno = errno; + lap->ret = FPCFGA_LIB_ERR; + rv = DI_WALK_TERMINATE; + goto out; + } + + (void) snprintf(nodepath, len, "%s%s", DEVICES_DIR, devfsp); + + /* Skip node if it is HBA */ + match_minor = 0; + if (!dev_cmp(lap->apidp->xport_phys, nodepath, match_minor)) { + rv = DI_WALK_CONTINUE; + goto out; + } + + /* If stat'ing a specific device, is this node that device */ + if (lap->cmd == FPCFGA_STAT_FC_DEV) { + /* checks port wwn property to find a match */ + di_ret = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, + PORT_WWN_PROP, &port_wwn_data); + if (di_ret == -1) { + rv = DI_WALK_CONTINUE; + goto out; + } else { + (void) sprintf(port_wwn, "%016llx", + (wwnConversion(port_wwn_data))); + /* + * port wwn doesn't match contine to walk + * if match call do_stat_FCP_dev. + */ + if (strncmp(port_wwn, lap->apidp->dyncomp, + WWN_SIZE*2)) { + rv = DI_WALK_CONTINUE; + goto out; + } + } + } + + /* + * If stat'ing a xport only, we look at device nodes only to get + * xport configuration status. So a limited stat will suffice. + */ + if (lap->cmd == FPCFGA_STAT_FCA_PORT) { + limited_stat = 1; + } else { + limited_stat = 0; + } + + /* + * Ignore errors if stat'ing a bus or listing all + */ + ret = do_stat_FCP_dev(node, nodepath, lap, limited_stat); + if (ret != FPCFGA_OK) { + rv = DI_WALK_CONTINUE; + goto out; + } + + /* Are we done ? */ + rv = DI_WALK_CONTINUE; + if (lap->cmd == FPCFGA_STAT_FCA_PORT && + lap->chld_config == CFGA_STAT_CONFIGURED) { + rv = DI_WALK_TERMINATE; + } + + /*FALLTHRU*/ +out: + S_FREE(nodepath); + if (devfsp != NULL) di_devfs_path_free(devfsp); + return (rv); +} + +static fpcfga_ret_t +do_stat_fca_xport(fpcfga_list_t *lap, int limited_stat, + HBA_PORTATTRIBUTES portAttrs) +{ + cfga_list_data_t *clp = NULL; + ldata_list_t *listp = NULL; + int l_errno = 0; + uint_t devinfo_state = 0; + walkarg_t walkarg; + fpcfga_ret_t ret; + cfga_cond_t cond = CFGA_COND_UNKNOWN; + + assert(lap->xport_logp != NULL); + + /* Get xport state */ + if (lap->apidp->flags == FLAG_DEVINFO_FORCE) { + walkarg.flags = FLAG_DEVINFO_FORCE; + } else { + walkarg.flags = 0; + } + walkarg.walkmode.node_args.flags = 0; + walkarg.walkmode.node_args.fcn = get_xport_state; + + ret = walk_tree(lap->apidp->xport_phys, &devinfo_state, + DINFOCPYALL | DINFOPATH, &walkarg, FPCFGA_WALK_NODE, &l_errno); + if (ret == FPCFGA_OK) { + lap->xport_rstate = xport_devinfo_to_recep_state(devinfo_state); + } else { + lap->xport_rstate = CFGA_STAT_NONE; + } + + /* + * Get topology works okay even if the fp port is connected + * to a switch and no devices connected to the switch. + * In this case the list will only shows fp port info without + * any device listed. + */ + switch (portAttrs.PortType) { + case HBA_PORTTYPE_NLPORT: + (void) snprintf(lap->xport_type, + sizeof (lap->xport_type), "%s", + FP_FC_PUBLIC_PORT_TYPE); + break; + case HBA_PORTTYPE_NPORT: + (void) snprintf(lap->xport_type, + sizeof (lap->xport_type), "%s", + FP_FC_FABRIC_PORT_TYPE); + break; + case HBA_PORTTYPE_LPORT: + (void) snprintf(lap->xport_type, + sizeof (lap->xport_type), "%s", + FP_FC_PRIVATE_PORT_TYPE); + break; + case HBA_PORTTYPE_PTP: + (void) snprintf(lap->xport_type, + sizeof (lap->xport_type), "%s", + FP_FC_PT_TO_PT_PORT_TYPE); + break; + /* + * HBA_PORTTYPE_UNKNOWN means nothing is connected + */ + case HBA_PORTTYPE_UNKNOWN: + (void) snprintf(lap->xport_type, + sizeof (lap->xport_type), "%s", + FP_FC_PORT_TYPE); + break; + /* NOT_PRESENT, OTHER, FPORT, FLPORT */ + default: + (void) snprintf(lap->xport_type, + sizeof (lap->xport_type), "%s", + FP_FC_PORT_TYPE); + cond = CFGA_COND_FAILED; + break; + } + + if (limited_stat) { + /* We only want to know bus(receptacle) connect status */ + return (FPCFGA_OK); + } + + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + lap->l_errno = errno; + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + + (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s", + lap->xport_logp); + (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s", + lap->apidp->xport_phys); + + clp->ap_class[0] = '\0'; /* Filled by libcfgadm */ + clp->ap_r_state = lap->xport_rstate; + clp->ap_o_state = lap->chld_config; + clp->ap_cond = cond; + clp->ap_busy = 0; + clp->ap_status_time = (time_t)-1; + clp->ap_info[0] = '\0'; + (void) strncpy(clp->ap_type, lap->xport_type, sizeof (clp->ap_type)); + + /* Link it in. lap->listp is NULL originally. */ + listp->next = lap->listp; + /* lap->listp now gets cfga_list_data for the fca port. */ + lap->listp = listp; + + return (FPCFGA_OK); +} + + +static int +get_xport_state(di_node_t node, void *arg) +{ + uint_t *di_statep = (uint_t *)arg; + + *di_statep = di_state(node); + + return (DI_WALK_TERMINATE); +} + +/* + * Routine for updating ldata list based on the state of device node. + * When no matching accessible ldata is found a new ldata is created + * with proper state information. + * + * Overall algorithm: + * If the device node is online and the matching ldata is found + * the target device is updated with configued and unknown condition. + * If the device node is offline or down and the matching ldata is found + * the target device is updated with configued and unusable condition. + * If the device node is online but the matching ldata is not found + * the target device is created with configued and failing condition. + * If the device node is offline or down and the matching ldata is not found + * the target device is created with configued and unusable condition. + */ +static fpcfga_ret_t +do_stat_fc_dev( + const di_node_t node, + const char *nodepath, + fpcfga_list_t *lap, + int limited_stat) +{ + uint_t dctl_state = 0, devinfo_state = 0; + char *dyncomp = NULL; + cfga_list_data_t *clp = NULL; + cfga_busy_t busy; + ldata_list_t *listp = NULL; + ldata_list_t *matchldp = NULL; + int l_errno = 0; + cfga_stat_t ostate; + cfga_cond_t cond; + fpcfga_ret_t ret; + + assert(lap->apidp->xport_phys != NULL); + assert(lap->xport_logp != NULL); + + cond = CFGA_COND_UNKNOWN; + + devinfo_state = di_state(node); + ostate = dev_devinfo_to_occupant_state(devinfo_state); + + /* + * NOTE: The framework cannot currently detect layered driver + * opens, so the busy indicator is not very reliable. Also, + * non-root users will not be able to determine busy + * status (libdevice needs root permissions). + * This should probably be fixed by adding a DI_BUSY to the di_state() + * routine in libdevinfo. + */ + if (devctl_cmd(nodepath, FPCFGA_DEV_GETSTATE, &dctl_state, + &l_errno) == FPCFGA_OK) { + busy = ((dctl_state & DEVICE_BUSY) == DEVICE_BUSY) ? 1 : 0; + } else { + busy = 0; + } + + /* We only want to know device config state */ + if (limited_stat) { + if (((strcmp(lap->xport_type, FP_FC_FABRIC_PORT_TYPE) == 0) || + strcmp(lap->xport_type, FP_FC_PUBLIC_PORT_TYPE) == 0)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + } else { + if (ostate != CFGA_STAT_UNCONFIGURED) { + lap->chld_config = CFGA_STAT_CONFIGURED; + } + } + return (FPCFGA_OK); + } + + /* + * If child device is configured, see if it is accessible also + * for FPCFGA_STAT_FC_DEV cmd. + */ + if (lap->cmd == FPCFGA_STAT_FC_DEV) { + switch (ostate) { + case CFGA_STAT_CONFIGURED: + /* + * if configured and not accessble, the device is + * till be displayed with failing condition. + * return code should be FPCFGA_OK to display it. + */ + case CFGA_STAT_NONE: + /* + * If not unconfigured and not attached + * the state is set to CFGA_STAT_NONE currently. + * This is okay for the detached node due to + * the driver being unloaded. + * May need to define another state to + * isolate the detached only state. + * + * handle the same way as configured. + */ + if (lap->ret != FPCFGA_ACCESS_OK) { + cond = CFGA_COND_FAILING; + } + lap->chld_config = CFGA_STAT_CONFIGURED; + break; + case CFGA_STAT_UNCONFIGURED: + /* + * if unconfigured - offline or down, + * set to cond to unusable regardless of accessibility. + * This behavior needs to be examined further. + * When the device is not accessible the node + * may get offline or down. In that case failing + * cond may make more sense. + * In anycase the ostate will be set to configured + * configured. + */ + cond = CFGA_COND_UNUSABLE; + /* + * For fabric port the fca port is considered as + * configured since user configured previously + * for any existing node. Otherwise when the + * device was accessible, the hba is considered as + * configured. + */ + if (((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) || + (lap->ret == FPCFGA_ACCESS_OK)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + } else { + lap->ret = FPCFGA_APID_NOEXIST; + return (FPCFGA_OK); + } + break; + default: + break; + } + + /* if device found in disco ports, ldata already created. */ + if (lap->ret == FPCFGA_ACCESS_OK) { + /* + * if cond is not changed then don't update + * condition to keep the previous condition. + */ + if (cond != CFGA_COND_UNKNOWN) { + lap->listp->ldata.ap_cond = cond; + } + lap->listp->ldata.ap_o_state = CFGA_STAT_CONFIGURED; + lap->listp->ldata.ap_busy = busy; + lap->ret = FPCFGA_OK; + return (FPCFGA_OK); + } + } + + /* + * if cmd is stat all check ldata list + * to see if the node exist on the dev list. Otherwise create + * the list element. + */ + if (lap->cmd == FPCFGA_STAT_ALL) { + if (lap->listp != NULL) { + if ((ret = make_dyncomp_from_dinode(node, + &dyncomp, &l_errno)) != FPCFGA_OK) { + return (ret); + } + ret = is_dyn_ap_on_ldata_list(dyncomp, lap->listp, + &matchldp, &l_errno); + switch (ret) { + case FPCFGA_ACCESS_OK: + /* node exists so set ostate to configured. */ + lap->chld_config = CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_o_state = + CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_busy = busy; + clp = &matchldp->ldata; + switch (ostate) { + case CFGA_STAT_CONFIGURED: + /* + * If not unconfigured and not attached + * the state is set to CFGA_STAT_NONE currently. + * This is okay for the detached node due to + * the driver being unloaded. + * May need to define another state to + * isolate the detached only state. + */ + case CFGA_STAT_NONE: + /* update ap_type and ap_info */ + get_hw_info(node, clp); + break; + /* + * node is offline or down. + * set cond to unusable. + */ + case CFGA_STAT_UNCONFIGURED: + /* + * if cond is not unknown + * we already set the cond from + * a different node with the same + * port WWN or initial probing + * was failed so don't update again. + */ + if (matchldp->ldata.ap_cond == + CFGA_COND_UNKNOWN) { + matchldp->ldata.ap_cond = + CFGA_COND_UNUSABLE; + } + break; + default: + break; + } + /* node found in ldata list so just return. */ + lap->ret = FPCFGA_OK; + S_FREE(dyncomp); + return (FPCFGA_OK); + case FPCFGA_LIB_ERR: + lap->l_errno = l_errno; + S_FREE(dyncomp); + return (ret); + case FPCFGA_APID_NOACCESS: + switch (ostate) { + /* node is attached but not in dev list */ + case CFGA_STAT_CONFIGURED: + case CFGA_STAT_NONE: + lap->chld_config = CFGA_STAT_CONFIGURED; + cond = CFGA_COND_FAILING; + break; + /* + * node is offline or down. + * set cond to unusable. + */ + case CFGA_STAT_UNCONFIGURED: + /* + * For fabric port the fca port is + * considered as configured since user + * configured previously for any + * existing node. + */ + cond = CFGA_COND_UNUSABLE; + if ((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) { + lap->chld_config = + CFGA_STAT_CONFIGURED; + } else { + lap->ret = FPCFGA_OK; + S_FREE(dyncomp); + return (FPCFGA_OK); + } + break; + default: + /* + * continue to create ldata_list struct for + * this node + */ + break; + } + default: + break; + } + } else { + /* + * dev_list is null so there is no accessible dev. + * set the cond and continue to create ldata. + */ + switch (ostate) { + case CFGA_STAT_CONFIGURED: + case CFGA_STAT_NONE: + cond = CFGA_COND_FAILING; + lap->chld_config = CFGA_STAT_CONFIGURED; + break; + /* + * node is offline or down. + * set cond to unusable. + */ + case CFGA_STAT_UNCONFIGURED: + cond = CFGA_COND_UNUSABLE; + /* + * For fabric port the fca port is + * considered as configured since user + * configured previously for any + * existing node. + */ + if ((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) { + lap->chld_config = + CFGA_STAT_CONFIGURED; + } else { + lap->ret = FPCFGA_OK; + S_FREE(dyncomp); + return (FPCFGA_OK); + } + break; + default: + break; + } + } + } + + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + lap->l_errno = errno; + S_FREE(dyncomp); + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + + /* Create the dynamic component. */ + if (dyncomp == NULL) { + ret = make_dyncomp_from_dinode(node, &dyncomp, &l_errno); + if (ret != FPCFGA_OK) { + S_FREE(listp); + return (ret); + } + } + + /* Create logical and physical ap_id */ + (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s%s%s", + lap->xport_logp, DYN_SEP, dyncomp); + + (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s%s%s", + lap->apidp->xport_phys, DYN_SEP, dyncomp); + + S_FREE(dyncomp); + + clp->ap_class[0] = '\0'; /* Filled in by libcfgadm */ + clp->ap_r_state = lap->xport_rstate; + /* set to ostate to configured and set cond with info. */ + clp->ap_o_state = CFGA_STAT_CONFIGURED; + clp->ap_cond = cond; + clp->ap_busy = busy; + clp->ap_status_time = (time_t)-1; + + /* get ap_type and ap_info. */ + get_hw_info(node, clp); + + /* Link it in */ + listp->next = lap->listp; + lap->listp = listp; + + lap->ret = FPCFGA_OK; + return (FPCFGA_OK); +} + +/* + * Wrapper routine for handling path info. + * + * When show_FCP_dev option is given stat_path_info_FCP_dev() is called. + * Otherwise stat_path_info_fc_dev() is called. + */ +int +stat_path_info_node( + di_node_t root, + void *arg, + int *l_errnop) +{ + fpcfga_list_t *lap = NULL; + + lap = (fpcfga_list_t *)arg; + if ((lap->apidp->flags & (FLAG_FCP_DEV)) == FLAG_FCP_DEV) { + return (stat_path_info_FCP_dev(root, lap, l_errnop)); + } else { + return (stat_path_info_fc_dev(root, lap, l_errnop)); + } +} + +/* + * Routine for updating ldata list based on the state of path info node. + * When no matching accessible ldata is found a new ldata is created + * with proper state information. + * + * Overall algorithm: + * If the path info node is not offline and the matching ldata is found + * the target device is updated with configued and unknown condition. + * If the path info node is offline or failed and the matching ldata is found + * the target device is updated with configued and unusable condition. + * If the path info node is online but the matching ldata is not found + * the target device is created with configued and failing condition. + * If the path info is offline or failed and the matching ldata is not found + * the target device is created with configued and unusable condition. + */ +static int +stat_path_info_fc_dev( + di_node_t root, + fpcfga_list_t *lap, + int *l_errnop) +{ + ldata_list_t *matchldp = NULL; + di_path_t path = DI_PATH_NIL; + uchar_t *port_wwn_data; + char port_wwn[WWN_SIZE*2+1]; + int count; + fpcfga_ret_t ret; + di_path_state_t pstate; + + if (root == DI_NODE_NIL) { + return (FPCFGA_LIB_ERR); + } + + /* + * if stat on a specific dev and walk_node found it okay + * then just return ok. + */ + if ((lap->cmd == FPCFGA_STAT_FC_DEV) && (lap->ret == FPCFGA_OK)) { + return (FPCFGA_OK); + } + + /* + * if stat on a fca xport and chld_config is set + * then just return ok. + */ + if ((lap->cmd == FPCFGA_STAT_FCA_PORT) && + (lap->chld_config == CFGA_STAT_CONFIGURED)) { + return (FPCFGA_OK); + } + + /* + * when there is no path_info node return FPCFGA_OK. + * That way the result from walk_node shall be maintained. + */ + if ((path = di_path_next_client(root, path)) == DI_PATH_NIL) { + /* + * if the dev was in dev list but not found + * return OK to indicate is not configured. + */ + if (lap->ret == FPCFGA_ACCESS_OK) { + lap->ret = FPCFGA_OK; + } + return (FPCFGA_OK); + } + + /* if stat on fca port return. */ + if (lap->cmd == FPCFGA_STAT_FCA_PORT) { + if (((strcmp(lap->xport_type, FP_FC_FABRIC_PORT_TYPE) == 0) || + strcmp(lap->xport_type, FP_FC_PUBLIC_PORT_TYPE) == 0)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + return (FPCFGA_OK); + } else { + if ((pstate = di_path_state(path)) != + DI_PATH_STATE_OFFLINE) { + lap->chld_config = CFGA_STAT_CONFIGURED; + return (FPCFGA_OK); + } + } + } + /* + * now parse the path info node. + */ + do { + count = di_path_prop_lookup_bytes(path, PORT_WWN_PROP, + &port_wwn_data); + if (count != WWN_SIZE) { + ret = FPCFGA_LIB_ERR; + break; + } + + (void) sprintf(port_wwn, "%016llx", + (wwnConversion(port_wwn_data))); + switch (lap->cmd) { + case FPCFGA_STAT_FC_DEV: + /* if no match contine to the next path info node. */ + if (strncmp(port_wwn, lap->apidp->dyncomp, + WWN_SIZE*2)) { + break; + } + /* if device in dev_list, ldata already created. */ + if (lap->ret == FPCFGA_ACCESS_OK) { + lap->listp->ldata.ap_o_state = + CFGA_STAT_CONFIGURED; + if (((pstate = di_path_state(path)) == + DI_PATH_STATE_OFFLINE) || + (pstate == DI_PATH_STATE_FAULT)) { + lap->listp->ldata.ap_cond = + CFGA_COND_UNUSABLE; + } + lap->ret = FPCFGA_OK; + return (FPCFGA_OK); + } else { + if ((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + return (init_ldata_for_mpath_dev( + path, port_wwn, l_errnop, lap)); + } else { + if ((di_path_state(path)) != + DI_PATH_STATE_OFFLINE) { + return (init_ldata_for_mpath_dev( + path, port_wwn, l_errnop, lap)); + } else { + lap->ret = FPCFGA_APID_NOEXIST; + return (FPCFGA_OK); + } + } + } + case FPCFGA_STAT_ALL: + /* check if there is list data. */ + if (lap->listp != NULL) { + ret = is_dyn_ap_on_ldata_list(port_wwn, + lap->listp, &matchldp, l_errnop); + if (ret == FPCFGA_ACCESS_OK) { + lap->chld_config = CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_o_state = + CFGA_STAT_CONFIGURED; + /* + * Update the condition as unusable + * if the pathinfo state is failed + * or offline. + */ + if (((pstate = di_path_state(path)) == + DI_PATH_STATE_OFFLINE) || + (pstate == + DI_PATH_STATE_FAULT)) { + matchldp->ldata.ap_cond = + CFGA_COND_UNUSABLE; + } + break; + } else if (ret == FPCFGA_LIB_ERR) { + lap->l_errno = *l_errnop; + return (ret); + } + } + /* + * now create ldata for this particular path info node. + * if port top is private loop and pathinfo is in + * in offline state don't include to ldata list. + */ + if (((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) || + (di_path_state(path) != + DI_PATH_STATE_OFFLINE)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + ret = init_ldata_for_mpath_dev( + path, port_wwn, l_errnop, lap); + if (ret != FPCFGA_OK) { + return (ret); + } + } + break; + case FPCFGA_STAT_FCA_PORT: + if (di_path_state(path) != DI_PATH_STATE_OFFLINE) { + lap->chld_config = CFGA_STAT_CONFIGURED; + return (FPCFGA_OK); + } + } + path = di_path_next_client(root, path); + } while (path != DI_PATH_NIL); + + return (FPCFGA_OK); + +} + +/* + * Routine for updating ldata list based on the state of path info node. + * When no matching accessible ldata is found a new ldata is created + * with proper state information. + * + * The difference from stat_path_info_fc_dev() is + * to handle FCP SCSI LUN information. Otherwise overall algorithm is + * same. + * + * Overall algorithm: + * If the path info node is not offline and the matching ldata is found + * the target device is updated with configued and unknown condition. + * If the path info node is offline or failed and the matching ldata is found + * the target device is updated with configued and unusable condition. + * If the path info node is online but the matching ldata is not found + * the target device is created with configued and failing condition. + * If the path info is offline or failed and the matching ldata is not found + * the target device is created with configued and unusable condition. + */ +static int +stat_path_info_FCP_dev( + di_node_t root, + fpcfga_list_t *lap, + int *l_errnop) +{ + ldata_list_t *matchldp = NULL, *listp = NULL; + cfga_list_data_t *clp; + di_path_t path = DI_PATH_NIL; + di_node_t client_node = DI_NODE_NIL; + char *port_wwn = NULL, *nodepath = NULL; + int *lun_nump; + fpcfga_ret_t ldata_ret; + di_path_state_t pstate; + cfga_busy_t busy; + uint_t dctl_state = 0; + + if (root == DI_NODE_NIL) { + return (FPCFGA_LIB_ERR); + } + + /* + * if stat on a fca xport and chld_config is set + * then just return ok. + */ + if ((lap->cmd == FPCFGA_STAT_FCA_PORT) && + (lap->chld_config == CFGA_STAT_CONFIGURED)) { + return (FPCFGA_OK); + } + /* + * when there is no path_info node return FPCFGA_OK. + * That way the result from walk_node shall be maintained. + */ + if ((path = di_path_next_client(root, path)) == DI_PATH_NIL) { + /* + * if the dev was in dev list but not found + * return ok. + */ + if (lap->ret == FPCFGA_ACCESS_OK) { + lap->ret = FPCFGA_OK; + } + return (FPCFGA_OK); + } + /* + * If stat on fca port and port topology is fabric return here. + * If not fabric return only when path state is not offfline. + * The other cases are handbled below. + */ + if (lap->cmd == FPCFGA_STAT_FCA_PORT) { + if (((strcmp(lap->xport_type, FP_FC_FABRIC_PORT_TYPE) == 0) || + strcmp(lap->xport_type, FP_FC_PUBLIC_PORT_TYPE) == 0)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + return (FPCFGA_OK); + } else { + if ((pstate = di_path_state(path)) != + DI_PATH_STATE_OFFLINE) { + lap->chld_config = CFGA_STAT_CONFIGURED; + return (FPCFGA_OK); + } + } + } + /* + * now parse the path info node. + */ + do { + switch (lap->cmd) { + case FPCFGA_STAT_FC_DEV: + if ((make_portwwn_luncomp_from_pinode(path, &port_wwn, + &lun_nump, l_errnop)) != FPCFGA_OK) { + return (FPCFGA_LIB_ERR); + } + + if ((ldata_ret = is_FCP_dev_ap_on_ldata_list(port_wwn, + *lun_nump, lap->listp, &matchldp)) + == FPCFGA_LIB_ERR) { + S_FREE(port_wwn); + return (ldata_ret); + } + + if (ldata_ret == FPCFGA_ACCESS_OK) { + lap->chld_config = CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_o_state = + CFGA_STAT_CONFIGURED; + /* + * Update the condition as unusable + * if the pathinfo state is failed + * or offline. + */ + if (((pstate = di_path_state(path)) == + DI_PATH_STATE_OFFLINE) || + (pstate == DI_PATH_STATE_FAULT)) { + matchldp->ldata.ap_cond = + CFGA_COND_UNUSABLE; + } + lap->ret = FPCFGA_OK; + break; + } + + if (strncmp(port_wwn, lap->apidp->dyncomp, WWN_SIZE*2) + != 0) { + break; + } + /* + * now create ldata for this particular path info node. + * if port top is private loop and pathinfo is in + * in offline state don't include to ldata list. + */ + if (((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) || + (di_path_state(path) != + DI_PATH_STATE_OFFLINE)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + /* create ldata for this pi node. */ + client_node = di_path_client_node(path); + if (client_node == DI_NODE_NIL) { + *l_errnop = errno; + S_FREE(port_wwn); + return (FPCFGA_LIB_ERR); + } + if ((construct_nodepath_from_dinode( + client_node, &nodepath, l_errnop)) + != FPCFGA_OK) { + S_FREE(port_wwn); + return (FPCFGA_LIB_ERR); + } + + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + S_FREE(port_wwn); + S_FREE(nodepath); + lap->l_errno = errno; + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + + /* Create logical and physical ap_id */ + (void) snprintf(clp->ap_log_id, + sizeof (clp->ap_log_id), "%s%s%s%s%d", + lap->xport_logp, DYN_SEP, port_wwn, + LUN_COMP_SEP, *lun_nump); + (void) snprintf(clp->ap_phys_id, + sizeof (clp->ap_phys_id), "%s%s%s%s%d", + lap->apidp->xport_phys, DYN_SEP, port_wwn, + LUN_COMP_SEP, *lun_nump); + /* + * We reached here since FCP dev is not found + * in ldata list but path info node exists. + * + * Update the condition as failing + * if the pathinfo state was normal. + * Update the condition as unusable + * if the pathinfo state is failed + * or offline. + */ + clp->ap_class[0] = '\0'; /* Filled by libcfgadm */ + clp->ap_o_state = CFGA_STAT_CONFIGURED; + if (((pstate = di_path_state(path)) + == DI_PATH_STATE_OFFLINE) || + (pstate == DI_PATH_STATE_FAULT)) { + clp->ap_cond = CFGA_COND_UNUSABLE; + } else { + clp->ap_cond = CFGA_COND_FAILING; + } + clp->ap_r_state = lap->xport_rstate; + clp->ap_info[0] = '\0'; + /* update ap_type and ap_info */ + get_hw_info(client_node, clp); + if (devctl_cmd(nodepath, FPCFGA_DEV_GETSTATE, + &dctl_state, l_errnop) == FPCFGA_OK) { + busy = ((dctl_state & DEVICE_BUSY) + == DEVICE_BUSY) ? 1 : 0; + } else { + busy = 0; + } + clp->ap_busy = busy; + clp->ap_status_time = (time_t)-1; + + (void) insert_ldata_to_ldatalist(port_wwn, + lun_nump, listp, &(lap->listp)); + } + break; + case FPCFGA_STAT_ALL: + if ((make_portwwn_luncomp_from_pinode(path, &port_wwn, + &lun_nump, l_errnop)) != FPCFGA_OK) { + return (FPCFGA_LIB_ERR); + } + + if ((ldata_ret = is_FCP_dev_ap_on_ldata_list(port_wwn, + *lun_nump, lap->listp, &matchldp)) + == FPCFGA_LIB_ERR) { + S_FREE(port_wwn); + return (ldata_ret); + } + + if (ldata_ret == FPCFGA_ACCESS_OK) { + lap->chld_config = CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_o_state = + CFGA_STAT_CONFIGURED; + /* + * Update the condition as unusable + * if the pathinfo state is failed + * or offline. + */ + if (((pstate = di_path_state(path)) == + DI_PATH_STATE_OFFLINE) || + (pstate == DI_PATH_STATE_FAULT)) { + matchldp->ldata.ap_cond = + CFGA_COND_UNUSABLE; + } + break; + } + /* + * now create ldata for this particular path info node. + * if port top is private loop and pathinfo is in + * in offline state don't include to ldata list. + */ + if (((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) || + (di_path_state(path) != + DI_PATH_STATE_OFFLINE)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + /* create ldata for this pi node. */ + client_node = di_path_client_node(path); + if (client_node == DI_NODE_NIL) { + *l_errnop = errno; + S_FREE(port_wwn); + return (FPCFGA_LIB_ERR); + } + if ((construct_nodepath_from_dinode( + client_node, &nodepath, l_errnop)) + != FPCFGA_OK) { + S_FREE(port_wwn); + return (FPCFGA_LIB_ERR); + } + + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + S_FREE(port_wwn); + S_FREE(nodepath); + lap->l_errno = errno; + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + + /* Create logical and physical ap_id */ + (void) snprintf(clp->ap_log_id, + sizeof (clp->ap_log_id), "%s%s%s%s%d", + lap->xport_logp, DYN_SEP, port_wwn, + LUN_COMP_SEP, *lun_nump); + (void) snprintf(clp->ap_phys_id, + sizeof (clp->ap_phys_id), "%s%s%s%s%d", + lap->apidp->xport_phys, DYN_SEP, port_wwn, + LUN_COMP_SEP, *lun_nump); + /* + * We reached here since FCP dev is not found + * in ldata list but path info node exists. + * + * Update the condition as failing + * if the pathinfo state was normal. + * Update the condition as unusable + * if the pathinfo state is failed + * or offline. + */ + clp->ap_class[0] = '\0'; /* Filled by libcfgadm */ + clp->ap_o_state = CFGA_STAT_CONFIGURED; + if (((pstate = di_path_state(path)) + == DI_PATH_STATE_OFFLINE) || + (pstate == DI_PATH_STATE_FAULT)) { + clp->ap_cond = CFGA_COND_UNUSABLE; + } else { + clp->ap_cond = CFGA_COND_FAILING; + } + clp->ap_r_state = lap->xport_rstate; + clp->ap_info[0] = '\0'; + /* update ap_type and ap_info */ + get_hw_info(client_node, clp); + if (devctl_cmd(nodepath, FPCFGA_DEV_GETSTATE, + &dctl_state, l_errnop) == FPCFGA_OK) { + busy = ((dctl_state & DEVICE_BUSY) + == DEVICE_BUSY) ? 1 : 0; + } else { + busy = 0; + } + clp->ap_busy = busy; + clp->ap_status_time = (time_t)-1; + + (void) insert_ldata_to_ldatalist(port_wwn, + lun_nump, listp, &(lap->listp)); + } + break; + case FPCFGA_STAT_FCA_PORT: + if (di_path_state(path) != DI_PATH_STATE_OFFLINE) { + lap->chld_config = CFGA_STAT_CONFIGURED; + lap->ret = FPCFGA_OK; + return (FPCFGA_OK); + } + } + path = di_path_next_client(root, path); + } while (path != DI_PATH_NIL); + + lap->ret = FPCFGA_OK; + S_FREE(port_wwn); + S_FREE(nodepath); + return (FPCFGA_OK); + +} + +/* + * Routine for updating ldata list based on the state of device node. + * When no matching accessible ldata is found a new ldata is created + * with proper state information. + * + * The difference from do_stat_fc_dev() is + * to handle FCP SCSI LUN information. Otherwise overall algorithm is + * same. + * + * Overall algorithm: + * If the device node is online and the matching ldata is found + * the target device is updated with configued and unknown condition. + * If the device node is offline or down and the matching ldata is found + * the target device is updated with configued and unusable condition. + * If the device node is online but the matching ldata is not found + * the target device is created with configued and failing condition. + * If the device node is offline or down and the matching ldata is not found + * the target device is created with configued and unusable condition. + */ +static fpcfga_ret_t +do_stat_FCP_dev( + const di_node_t node, + const char *nodepath, + fpcfga_list_t *lap, + int limited_stat) +{ + uint_t dctl_state = 0, devinfo_state = 0; + char *port_wwn = NULL; + cfga_list_data_t *clp = NULL; + cfga_busy_t busy; + ldata_list_t *listp = NULL; + ldata_list_t *matchldp = NULL; + int l_errno = 0, *lun_nump; + cfga_stat_t ostate; + cfga_cond_t cond; + fpcfga_ret_t ldata_ret; + + assert(lap->apidp->xport_phys != NULL); + assert(lap->xport_logp != NULL); + + cond = CFGA_COND_UNKNOWN; + + devinfo_state = di_state(node); + ostate = dev_devinfo_to_occupant_state(devinfo_state); + + /* + * NOTE: The devctl framework cannot currently detect layered driver + * opens, so the busy indicator is not very reliable. Also, + * non-root users will not be able to determine busy + * status (libdevice needs root permissions). + * This should probably be fixed by adding a DI_BUSY to the di_state() + * routine in libdevinfo. + */ + if (devctl_cmd(nodepath, FPCFGA_DEV_GETSTATE, &dctl_state, + &l_errno) == FPCFGA_OK) { + busy = ((dctl_state & DEVICE_BUSY) == DEVICE_BUSY) ? 1 : 0; + } else { + busy = 0; + } + + /* We only want to know device config state */ + if (limited_stat) { + if (((strcmp(lap->xport_type, FP_FC_FABRIC_PORT_TYPE) == 0) || + strcmp(lap->xport_type, FP_FC_PUBLIC_PORT_TYPE) == 0)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + } else { + if (ostate != CFGA_STAT_UNCONFIGURED) { + lap->chld_config = CFGA_STAT_CONFIGURED; + } + } + return (FPCFGA_OK); + } + + /* + * If child device is configured, see if it is accessible also + * for FPCFGA_STAT_FC_DEV cmd. + */ + if ((make_portwwn_luncomp_from_dinode(node, &port_wwn, &lun_nump, + &l_errno)) != FPCFGA_OK) { + lap->l_errno = l_errno; + return (FPCFGA_LIB_ERR); + } + + if ((ldata_ret = is_FCP_dev_ap_on_ldata_list(port_wwn, *lun_nump, + lap->listp, &matchldp)) == FPCFGA_LIB_ERR) { + lap->l_errno = l_errno; + S_FREE(port_wwn); + return (ldata_ret); + } + + if (lap->cmd == FPCFGA_STAT_FC_DEV) { + switch (ostate) { + case CFGA_STAT_CONFIGURED: + /* + * if configured and not accessble, the device is + * till be displayed with failing condition. + * return code should be FPCFGA_OK to display it. + */ + case CFGA_STAT_NONE: + /* + * If not unconfigured and not attached + * the state is set to CFGA_STAT_NONE currently. + * This is okay for the detached node due to + * the driver being unloaded. + * May need to define another state to + * isolate the detached only state. + * + * handle the same way as configured. + */ + if (ldata_ret != FPCFGA_ACCESS_OK) { + cond = CFGA_COND_FAILING; + } + lap->chld_config = CFGA_STAT_CONFIGURED; + break; + case CFGA_STAT_UNCONFIGURED: + /* + * if unconfigured - offline or down, + * set to cond to unusable regardless of accessibility. + * This behavior needs to be examined further. + * When the device is not accessible the node + * may get offline or down. In that case failing + * cond may make more sense. + * In anycase the ostate will be set to configured + * configured. + */ + cond = CFGA_COND_UNUSABLE; + /* + * For fabric port the fca port is considered as + * configured since user configured previously + * for any existing node. Otherwise when the + * device was accessible, the hba is considered as + * configured. + */ + if (((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) || + (lap->ret == FPCFGA_ACCESS_OK)) { + lap->chld_config = CFGA_STAT_CONFIGURED; + } else { + /* + * if lap->ret is okay there is at least + * one matching ldata exist. Need to keep + * okay ret to display the matching ones. + */ + if (lap->ret != FPCFGA_OK) { + lap->ret = FPCFGA_APID_NOEXIST; + } + S_FREE(port_wwn); + return (FPCFGA_OK); + } + break; + default: + break; + } + + /* if device found in dev_list, ldata already created. */ + if (ldata_ret == FPCFGA_ACCESS_OK) { + /* + * if cond is not changed then don't update + * condition to keep any condtion + * from initial discovery. If the initial + * cond was failed the same condition will be kept. + */ + if (cond != CFGA_COND_UNKNOWN) { + matchldp->ldata.ap_cond = cond; + } + matchldp->ldata.ap_o_state = CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_busy = busy; + /* update ap_info via inquiry */ + clp = &matchldp->ldata; + /* update ap_type and ap_info */ + get_hw_info(node, clp); + lap->ret = FPCFGA_OK; + S_FREE(port_wwn); + return (FPCFGA_OK); + } + } + + /* + * if cmd is stat all check ldata list + * to see if the node exist on the dev list. Otherwise create + * the list element. + */ + if (lap->cmd == FPCFGA_STAT_ALL) { + switch (ldata_ret) { + case FPCFGA_ACCESS_OK: + /* node exists so set ostate to configured. */ + lap->chld_config = CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_o_state = + CFGA_STAT_CONFIGURED; + matchldp->ldata.ap_busy = busy; + clp = &matchldp->ldata; + switch (ostate) { + case CFGA_STAT_CONFIGURED: + /* + * If not unconfigured and not attached + * the state is set to CFGA_STAT_NONE currently. + * This is okay for the detached node due to + * the driver being unloaded. + * May need to define another state to + * isolate the detached only state. + */ + case CFGA_STAT_NONE: + /* update ap_type and ap_info */ + get_hw_info(node, clp); + break; + /* + * node is offline or down. + * set cond to unusable. + */ + case CFGA_STAT_UNCONFIGURED: + /* + * if cond is not unknown + * initial probing was failed + * so don't update again. + */ + if (matchldp->ldata.ap_cond == + CFGA_COND_UNKNOWN) { + matchldp->ldata.ap_cond = + CFGA_COND_UNUSABLE; + } + break; + default: + break; + } + /* node found in ldata list so just return. */ + lap->ret = FPCFGA_OK; + S_FREE(port_wwn); + return (FPCFGA_OK); + case FPCFGA_APID_NOACCESS: + switch (ostate) { + /* node is attached but not in dev list */ + case CFGA_STAT_CONFIGURED: + case CFGA_STAT_NONE: + lap->chld_config = CFGA_STAT_CONFIGURED; + cond = CFGA_COND_FAILING; + break; + /* + * node is offline or down. + * set cond to unusable. + */ + case CFGA_STAT_UNCONFIGURED: + /* + * For fabric port the fca port is + * considered as configured since user + * configured previously for any + * existing node. + */ + cond = CFGA_COND_UNUSABLE; + if ((strcmp(lap->xport_type, + FP_FC_PUBLIC_PORT_TYPE) == 0) || + (strcmp(lap->xport_type, + FP_FC_FABRIC_PORT_TYPE) == 0)) { + lap->chld_config = + CFGA_STAT_CONFIGURED; + } else { + lap->ret = FPCFGA_OK; + S_FREE(port_wwn); + return (FPCFGA_OK); + } + break; + default: + /* + * continue to create ldata_list struct for + * this node + */ + break; + } + default: + break; + } + } + + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + lap->l_errno = errno; + S_FREE(port_wwn); + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + + /* Create logical and physical ap_id */ + (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), + "%s%s%s%s%d", lap->xport_logp, DYN_SEP, port_wwn, + LUN_COMP_SEP, *lun_nump); + (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), + "%s%s%s%s%d", lap->apidp->xport_phys, DYN_SEP, port_wwn, + LUN_COMP_SEP, *lun_nump); + clp->ap_class[0] = '\0'; /* Filled in by libcfgadm */ + clp->ap_r_state = lap->xport_rstate; + clp->ap_o_state = CFGA_STAT_CONFIGURED; + clp->ap_cond = cond; + clp->ap_busy = busy; + clp->ap_status_time = (time_t)-1; + clp->ap_info[0] = '\0'; + + get_hw_info(node, clp); + + (void) insert_ldata_to_ldatalist(port_wwn, lun_nump, listp, + &(lap->listp)); + + lap->ret = FPCFGA_OK; + S_FREE(port_wwn); + return (FPCFGA_OK); +} + +/* + * Searches the ldata_list to find if the the input port_wwn exist. + * + * Input: port_wwn, ldata_list. + * Return value: FPCFGA_APID_NOACCESS if not found on ldata_list. + * FPCFGA_ACCESS_OK if found on ldata_list. + */ +static fpcfga_ret_t +is_dyn_ap_on_ldata_list(const char *port_wwn, const ldata_list_t *listp, + ldata_list_t **matchldpp, int *l_errnop) +{ + char *dyn = NULL, *dyncomp = NULL; + int len; + ldata_list_t *tmplp; + fpcfga_ret_t ret; + + + ret = FPCFGA_APID_NOACCESS; + + tmplp = (ldata_list_t *)listp; + while (tmplp != NULL) { + if ((dyn = GET_DYN(tmplp->ldata.ap_phys_id)) != NULL) { + len = strlen(DYN_TO_DYNCOMP(dyn)) + 1; + dyncomp = calloc(1, len); + if (dyncomp == NULL) { + *l_errnop = errno; + ret = FPCFGA_LIB_ERR; + break; + } + (void) strcpy(dyncomp, DYN_TO_DYNCOMP(dyn)); + if (!(strncmp(port_wwn, dyncomp, WWN_SIZE*2))) { + *matchldpp = tmplp; + S_FREE(dyncomp); + ret = FPCFGA_ACCESS_OK; + break; + } + S_FREE(dyncomp); + } + tmplp = tmplp->next; + } + + return (ret); +} + +/* + * Searches the ldata_list to find if the the input port_wwn and lun exist. + * + * Input: port_wwn, ldata_list. + * Return value: FPCFGA_APID_NOACCESS if not found on ldata_list. + * FPCFGA_ACCESS_OK if found on ldata_list. + */ +static fpcfga_ret_t +is_FCP_dev_ap_on_ldata_list(const char *port_wwn, const int lun_num, + ldata_list_t *ldatap, + ldata_list_t **matchldpp) +{ + ldata_list_t *curlp = NULL; + char *dyn = NULL, *dyncomp = NULL; + char *lun_dyn = NULL, *lunp = NULL; + int ldata_lun; + fpcfga_ret_t ret; + + /* + * if there is no list data just return the FCP dev list. + * Normally this should not occur since list data should + * be created through discoveredPort list. + */ + ret = FPCFGA_APID_NOACCESS; + if (ldatap == NULL) { + return (ret); + } + + dyn = GET_DYN(ldatap->ldata.ap_phys_id); + if (dyn != NULL) dyncomp = DYN_TO_DYNCOMP(dyn); + if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) == 0)) { + lun_dyn = GET_LUN_DYN(dyncomp); + if (lun_dyn != NULL) { + lunp = LUN_DYN_TO_LUNCOMP(lun_dyn); + if ((ldata_lun = atoi(lunp)) == lun_num) { + *matchldpp = ldatap; + return (FPCFGA_ACCESS_OK); + } else if (ldata_lun > lun_num) { + return (ret); + } + /* else continue */ + } else { + /* we have match without lun comp. */ + *matchldpp = ldatap; + return (FPCFGA_ACCESS_OK); + } + } + + curlp = ldatap->next; + + dyn = dyncomp = NULL; + lun_dyn = lunp = NULL; + while (curlp != NULL) { + dyn = GET_DYN(curlp->ldata.ap_phys_id); + if (dyn != NULL) dyncomp = DYN_TO_DYNCOMP(dyn); + if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) == 0)) { + lun_dyn = GET_LUN_DYN(dyncomp); + if (lun_dyn != NULL) { + lunp = LUN_DYN_TO_LUNCOMP(lun_dyn); + if ((ldata_lun = atoi(lunp)) == lun_num) { + *matchldpp = curlp; + return (FPCFGA_ACCESS_OK); + } else if (ldata_lun > lun_num) { + return (ret); + } + /* else continue */ + } else { + /* we have match without lun comp. */ + *matchldpp = curlp; + return (FPCFGA_ACCESS_OK); + } + } + dyn = dyncomp = NULL; + lun_dyn = lunp = NULL; + curlp = curlp->next; + } + + return (ret); + +} + +/* + * This routine is called when a pathinfo without matching pwwn in dev_list + * is found. + */ +static fpcfga_ret_t +init_ldata_for_mpath_dev(di_path_t path, char *pwwn, int *l_errnop, + fpcfga_list_t *lap) +{ + ldata_list_t *listp = NULL; + cfga_list_data_t *clp = NULL; + size_t devlen; + char *devpath; + di_node_t client_node = DI_NODE_NIL; + uint_t dctl_state = 0; + cfga_busy_t busy; + char *client_path; + di_path_state_t pstate; + + /* get the client node path */ + if (path == DI_PATH_NIL) { + return (FPCFGA_LIB_ERR); + } + client_node = di_path_client_node(path); + if (client_node == DI_NODE_NIL) { + return (FPCFGA_LIB_ERR); + } + if ((client_path = di_devfs_path(client_node)) == NULL) { + return (FPCFGA_LIB_ERR); + } + devlen = strlen(DEVICES_DIR) + strlen(client_path) + 1; + devpath = calloc(1, devlen); + if (devpath == NULL) { + di_devfs_path_free(client_path); + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + (void) snprintf(devpath, devlen, "%s%s", DEVICES_DIR, client_path); + + /* now need to create ldata for this dev */ + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + di_devfs_path_free(client_path); + S_FREE(devpath); + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + + /* Create logical and physical ap_id */ + (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s%s%s", + lap->xport_logp, DYN_SEP, pwwn); + (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s%s%s", + lap->apidp->xport_phys, DYN_SEP, pwwn); + + /* Filled in by libcfgadm */ + clp->ap_class[0] = '\0'; /* Filled by libcfgadm */ + clp->ap_r_state = lap->xport_rstate; + /* set to ostate to configured. */ + clp->ap_o_state = CFGA_STAT_CONFIGURED; + /* + * This routine is called when a port WWN is not found in dev list + * but path info node exists. + * + * Update the condition as failing if the pathinfo state was normal. + * Update the condition as unusable if the pathinfo state is failed + * or offline. + */ + if (((pstate = di_path_state(path)) == DI_PATH_STATE_OFFLINE) || + (pstate == DI_PATH_STATE_FAULT)) { + clp->ap_cond = CFGA_COND_UNUSABLE; + } else { + clp->ap_cond = CFGA_COND_FAILING; + } + clp->ap_status_time = (time_t)-1; + /* update ap_type and ap_info */ + get_hw_info(client_node, clp); + + if (devctl_cmd(devpath, FPCFGA_DEV_GETSTATE, + &dctl_state, l_errnop) == FPCFGA_OK) { + busy = ((dctl_state & DEVICE_BUSY) == DEVICE_BUSY) ? 1 : 0; + } else { + busy = 0; + } + clp->ap_busy = busy; + /* Link it in */ + listp->next = lap->listp; + lap->listp = listp; + + di_devfs_path_free(client_path); + S_FREE(devpath); + + /* now return with ok status with ldata. */ + lap->ret = FPCFGA_OK; + return (FPCFGA_OK); +} + +/* + * Initialize the cfga_list_data struct for an accessible device + * from g_get_dev_list(). + * + * Input: fca port ldata. + * Output: device cfga_list_data. + * + */ +static fpcfga_ret_t +init_ldata_for_accessible_dev(const char *dyncomp, uchar_t inq_type, + fpcfga_list_t *lap) +{ + ldata_list_t *listp = NULL; + cfga_list_data_t *clp = NULL; + int i; + + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + lap->l_errno = errno; + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + + assert(dyncomp != NULL); + + /* Create logical and physical ap_id */ + (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s%s%s", + lap->xport_logp, DYN_SEP, dyncomp); + + (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s%s%s", + lap->apidp->xport_phys, DYN_SEP, dyncomp); + + clp->ap_class[0] = '\0'; /* Filled in by libcfgadm */ + clp->ap_r_state = lap->xport_rstate; + clp->ap_o_state = CFGA_STAT_UNCONFIGURED; + clp->ap_cond = CFGA_COND_UNKNOWN; + clp->ap_busy = 0; + clp->ap_status_time = (time_t)-1; + clp->ap_info[0] = '\0'; + for (i = 0; i < N_DEVICE_TYPES; i++) { + if (inq_type == device_list[i].itype) { + (void) snprintf(clp->ap_type, sizeof (clp->ap_type), + "%s", (char *)device_list[i].name); + break; + } + } + if (i == N_DEVICE_TYPES) { + if (inq_type == ERR_INQ_DTYPE) { + clp->ap_cond = CFGA_COND_FAILED; + snprintf(clp->ap_type, sizeof (clp->ap_type), "%s", + (char *)GET_MSG_STR(ERR_UNAVAILABLE)); + } else { + (void) snprintf(clp->ap_type, sizeof (clp->ap_type), + "%s", "unknown"); + } + } + + /* Link it in */ + (void) insert_ldata_to_ldatalist(dyncomp, NULL, listp, &(lap->listp)); + + return (FPCFGA_OK); +} + +/* + * Initialize the cfga_list_data struct for an accessible FCP SCSI LUN device + * from the report lun data. + * + * Input: fca port ldata. report lun info + * Output: device cfga_list_data. + * + */ +static fpcfga_ret_t +init_ldata_for_accessible_FCP_dev( + const char *port_wwn, + int num_luns, + struct report_lun_resp *resp_buf, + fpcfga_list_t *lap, + int *l_errnop) +{ + ldata_list_t *listp = NULL, *listp_start = NULL, *listp_end = NULL, + *prevlp = NULL, *curlp = NULL, *matchp_start = NULL, + *matchp_end = NULL; + cfga_list_data_t *clp = NULL; + char *dyn = NULL, *dyncomp = NULL; + uchar_t *lun_string; + uint16_t lun_num; + int i, j, str_ret; + fpcfga_ret_t ret; + char dtype[CFGA_TYPE_LEN]; + struct scsi_inquiry *inq_buf; + uchar_t peri_qual; + cfga_cond_t cond = CFGA_COND_UNKNOWN; + uchar_t lun_num_raw[SAM_LUN_SIZE]; + + /* when number of lun is 0 it is not an error. so just return ok. */ + if (num_luns == 0) { + return (FPCFGA_OK); + } + + for (i = 0; i < num_luns; i++) { + lun_string = (uchar_t *)&(resp_buf->lun_string[i]); + memcpy(lun_num_raw, lun_string, sizeof (lun_num_raw)); + if ((ret = get_standard_inq_data(lap->apidp->xport_phys, port_wwn, + lun_num_raw, &inq_buf, l_errnop)) + != FPCFGA_OK) { + if (ret == FPCFGA_FCP_TGT_SEND_SCSI_FAILED) { + (void) strlcpy(dtype, + (char *)GET_MSG_STR(ERR_UNAVAILABLE), CFGA_TYPE_LEN); + cond = CFGA_COND_FAILED; + } else { + S_FREE(inq_buf); + return (FPCFGA_LIB_ERR); + } + } else { + peri_qual = inq_buf->inq_dtype & FP_PERI_QUAL_MASK; + /* + * peripheral qualifier is not 0 so the device node should not + * included in the ldata list. There should not be a device + * node for the lun either. + */ + if (peri_qual != DPQ_POSSIBLE) { + S_FREE(inq_buf); + continue; + } + *dtype = NULL; + for (j = 0; j < N_DEVICE_TYPES; j++) { + if ((inq_buf->inq_dtype & DTYPE_MASK) + == device_list[j].itype) { + (void) strlcpy(dtype, (char *)device_list[j].name, + CFGA_TYPE_LEN); + break; + } + } + if (*dtype == NULL) { + (void) strlcpy(dtype, + (char *)device_list[DTYPE_UNKNOWN_INDEX].name, + CFGA_TYPE_LEN); + } + } + /* + * Followed FCP driver for getting lun number from report + * lun data. + * According to SAM-2 there are multiple address method for + * FCP SCIS LUN. Logincal unit addressing, peripheral device + * addressing, flat space addressing, and extended logical + * unit addressing. + * + * as of 11/2001 FCP supports logical unit addressing and + * peripheral device addressing even thoough 3 defined. + * SSFCP_LUN_ADDRESSING 0x80 + * SSFCP_PD_ADDRESSING 0x00 + * SSFCP_VOLUME_ADDRESSING 0x40 + * + * the menthod below is used by FCP when (lun_string[0] & 0xC0) + * is either SSFCP_LUN_ADDRESSING or SSFCP_PD_ADDRESSING mode. + */ + lun_num = ((lun_string[0] & 0x3F) << 8) | lun_string[1]; + listp = calloc(1, sizeof (ldata_list_t)); + if (listp == NULL) { + *l_errnop = errno; + list_free(&listp_start); + return (FPCFGA_LIB_ERR); + } + + clp = &listp->ldata; + /* Create logical and physical ap_id */ + (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), + "%s%s%s%s%d", lap->xport_logp, DYN_SEP, port_wwn, + LUN_COMP_SEP, lun_num); + (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), + "%s%s%s%s%d", lap->apidp->xport_phys, DYN_SEP, port_wwn, + LUN_COMP_SEP, lun_num); + (void) strncpy(clp->ap_type, dtype, strlen(dtype)); + clp->ap_class[0] = '\0'; /* Filled in by libcfgadm */ + clp->ap_r_state = lap->xport_rstate; + clp->ap_o_state = CFGA_STAT_UNCONFIGURED; + clp->ap_cond = cond; + clp->ap_busy = 0; + clp->ap_status_time = (time_t)-1; + clp->ap_info[0] = '\0'; + if (listp_start == NULL) { + listp_start = listp; + } else { + if ((ret = insert_FCP_dev_ldata( + port_wwn, lun_num, listp, + &listp_start)) != FPCFGA_OK) { + list_free(&listp_start); + return (ret); + } + } + listp = NULL; + S_FREE(inq_buf); + } + + /* + * list data can be null when device peripheral qualifier is not 0 + * for any luns. Return ok to continue. + */ + if (listp_start == NULL) { + return (FPCFGA_OK); + } + /* + * get the end of list for later uses. + */ + curlp = listp_start->next; + prevlp = listp_start; + while (curlp) { + prevlp = curlp; + curlp = curlp->next; + } + listp_end = prevlp; + + /* + * if there is no list data just return the FCP dev list. + * Normally this should not occur since list data should + * be created through g_get_dev_list(). + */ + if (lap->listp == NULL) { + lap->listp = listp_start; + for (listp = listp_start; listp != NULL; listp = listp->next) { + listp->ldata.ap_cond = CFGA_COND_FAILING; + } + return (FPCFGA_OK); + } + + dyn = GET_DYN(lap->listp->ldata.ap_phys_id); + if ((dyn != NULL) && ((dyncomp = DYN_TO_DYNCOMP(dyn)) != NULL)) { + if ((str_ret = strncmp(dyncomp, port_wwn, WWN_SIZE*2)) == 0) { + matchp_start = matchp_end = lap->listp; + while (matchp_end->next != NULL) { + dyn = GET_DYN( + matchp_end->next->ldata.ap_phys_id); + if ((dyn != NULL) && + ((dyncomp = DYN_TO_DYNCOMP(dyn)) != NULL)) { + if ((str_ret = strncmp(dyncomp, + port_wwn, WWN_SIZE*2)) == 0) { + matchp_end = matchp_end->next; + } else { + break; + } + } else { + break; + } + } + /* fillup inqdtype */ + for (listp = listp_start; listp != NULL; + listp = listp->next) { + listp->ldata.ap_cond = + lap->listp->ldata.ap_cond; + } + /* link the new elem of lap->listp. */ + listp_end->next = matchp_end->next; + /* free the one matching wwn. */ + matchp_end->next = NULL; + list_free(&matchp_start); + /* link lap->listp to listp_start. */ + lap->listp = listp_start; + return (FPCFGA_OK); + } else if (str_ret > 0) { + for (listp = listp_start; listp != NULL; + listp = listp->next) { + listp->ldata.ap_cond = CFGA_COND_FAILING; + } + listp_end->next = lap->listp->next; + lap->listp = listp_start; + return (FPCFGA_OK); + } + } + + prevlp = lap->listp; + curlp = lap->listp->next; + + dyn = dyncomp = NULL; + while (curlp != NULL) { + dyn = GET_DYN(curlp->ldata.ap_phys_id); + if ((dyn != NULL) && + ((dyncomp = DYN_TO_DYNCOMP(dyn)) != NULL)) { + if ((str_ret = strncmp(dyncomp, port_wwn, + WWN_SIZE*2)) == 0) { + matchp_start = matchp_end = curlp; + while (matchp_end->next != NULL) { + dyn = GET_DYN( + matchp_end->next->ldata.ap_phys_id); + if ((dyn != NULL) && + ((dyncomp = DYN_TO_DYNCOMP(dyn)) + != NULL)) { + if ((str_ret = strncmp(dyncomp, + port_wwn, WWN_SIZE*2)) + == 0) { + matchp_end = + matchp_end->next; + } else { + break; + } + } else { + break; + } + } + for (listp = listp_start; listp != NULL; + listp = listp->next) { + listp->ldata.ap_cond = curlp->ldata.ap_cond; + } + /* link the next elem to listp_end. */ + listp_end->next = matchp_end->next; + /* link prevlp to listp_start to drop curlp. */ + prevlp->next = listp_start; + /* free matching pwwn elem. */ + matchp_end->next = NULL; + list_free(&matchp_start); + return (FPCFGA_OK); + } else if (str_ret > 0) { + for (listp = listp_start; listp != NULL; + listp = listp->next) { + /* + * Dev not found from accessible + * fc dev list but the node should + * exist. Set to failing cond now + * and check the node state later. + */ + listp->ldata.ap_cond = CFGA_COND_FAILING; + } + /* keep the cur elem by linking to list_end. */ + listp_end->next = curlp; + prevlp->next = listp_start; + return (FPCFGA_OK); + } + } + dyn = dyncomp = NULL; + prevlp = curlp; + curlp = curlp->next; + } + + prevlp->next = listp_start; + for (listp = listp_start; listp != NULL; listp = listp->next) { + listp->ldata.ap_cond = CFGA_COND_FAILING; + } + + return (FPCFGA_OK); + +} + +/* fill in device type, vid, pid from properties */ +static void +get_hw_info(di_node_t node, cfga_list_data_t *clp) +{ + char *cp = NULL; + char *inq_vid, *inq_pid; + int i; + + /* + * if the type is not previously assigned with valid SCSI device type + * check devinfo to find the type. + * once device is configured it should have a valid device type. + * device node is configured but no valid device type is found + * the type will be set to unavailable. + */ + if (clp->ap_type != NULL) { + /* + * if the type is not one of defined SCSI device type + * check devinfo to find the type. + * + * Note: unknown type is not a valid device type. + * It is added in to the device list table to provide + * constant string of "unknown". + */ + for (i = 0; i < (N_DEVICE_TYPES -1); i++) { + if (strncmp((char *)clp->ap_type, (char *)device_list[i].name, + sizeof (clp->ap_type)) == 0) { + break; + } + } + if (i == (N_DEVICE_TYPES - 1)) { + cp = (char *)get_device_type(node); + if (cp == NULL) { + cp = (char *)GET_MSG_STR(ERR_UNAVAILABLE); + } + (void) snprintf(clp->ap_type, sizeof (clp->ap_type), "%s", + S_STR(cp)); + } + } else { + cp = (char *)get_device_type(node); + if (cp == NULL) { + cp = (char *)GET_MSG_STR(ERR_UNAVAILABLE); + } + (void) snprintf(clp->ap_type, sizeof (clp->ap_type), "%s", + S_STR(cp)); + } + + /* + * Fill in vendor and product ID. + */ + if ((di_prop_lookup_strings(DDI_DEV_T_ANY, node, + "inquiry-product-id", &inq_pid) == 1) && + (di_prop_lookup_strings(DDI_DEV_T_ANY, node, + "inquiry-vendor-id", &inq_vid) == 1)) { + (void) snprintf(clp->ap_info, sizeof (clp->ap_info), + "%s %s", inq_vid, inq_pid); + } +} + +/* + * Get dtype from "inquiry-device-type" property. If not present, + * derive it from minor node type + */ +static const char * +get_device_type(di_node_t node) +{ + char *name = NULL; + int *inq_dtype; + int i; + + if (node == DI_NODE_NIL) { + return (NULL); + } + + /* first, derive type based on inquiry property */ + if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "inquiry-device-type", + &inq_dtype) != -1) { + int itype = (*inq_dtype) & DTYPE_MASK; + + for (i = 0; i < N_DEVICE_TYPES; i++) { + if (itype == device_list[i].itype) { + name = (char *)device_list[i].name; + break; + } + } + /* + * when found to be unknown type, set name to null to check + * device minor node type. + */ + if (i == (N_DEVICE_TYPES - 1)) { + name = NULL; + } + } + + /* if property fails, use minor nodetype */ + if (name == NULL) { + char *nodetype; + di_minor_t minor = di_minor_next(node, DI_MINOR_NIL); + + if ((minor != DI_MINOR_NIL) && + ((nodetype = di_minor_nodetype(minor)) != NULL)) { + for (i = 0; i < N_DEVICE_TYPES; i++) { + if (device_list[i].ntype && + (strcmp(nodetype, device_list[i].ntype) + == 0)) { + name = (char *)device_list[i].name; + break; + } + } + } + } + + return (name); +} + +/* Transform list data to stat data */ +fpcfga_ret_t +list_ext_postprocess( + ldata_list_t **llpp, + int nelem, + cfga_list_data_t **ap_id_list, + int *nlistp, + char **errstring) +{ + cfga_list_data_t *ldatap = NULL; + ldata_list_t *tmplp = NULL; + int i = -1; + + *ap_id_list = NULL; + *nlistp = 0; + + if (*llpp == NULL || nelem < 0) { + return (FPCFGA_LIB_ERR); + } + + if (nelem == 0) { + return (FPCFGA_APID_NOEXIST); + } + + ldatap = calloc(nelem, sizeof (cfga_list_data_t)); + if (ldatap == NULL) { + cfga_err(errstring, errno, ERR_LIST, 0); + return (FPCFGA_LIB_ERR); + } + + /* Extract the list_data structures from the linked list */ + tmplp = *llpp; + for (i = 0; i < nelem && tmplp != NULL; i++) { + ldatap[i] = tmplp->ldata; + tmplp = tmplp->next; + } + + if (i < nelem || tmplp != NULL) { + S_FREE(ldatap); + return (FPCFGA_LIB_ERR); + } + + *nlistp = nelem; + *ap_id_list = ldatap; + + return (FPCFGA_OK); +} + +/* + * Convert bus state to receptacle state + */ +static cfga_stat_t +xport_devinfo_to_recep_state(uint_t xport_di_state) +{ + cfga_stat_t rs; + + switch (xport_di_state) { + case DI_BUS_QUIESCED: + case DI_BUS_DOWN: + rs = CFGA_STAT_DISCONNECTED; + break; + /* + * NOTE: An explicit flag for active should probably be added to + * libdevinfo. + */ + default: + rs = CFGA_STAT_CONNECTED; + break; + } + + return (rs); +} + +/* + * Convert device state to occupant state + * if driver is attached the node is configured. + * if offline or down the node is unconfigured. + * if only driver detached it is none state which is treated the same + * way as configured state. + */ +static cfga_stat_t +dev_devinfo_to_occupant_state(uint_t dev_di_state) +{ + /* Driver attached ? */ + if ((dev_di_state & DI_DRIVER_DETACHED) != DI_DRIVER_DETACHED) { + return (CFGA_STAT_CONFIGURED); + } + + if ((dev_di_state & DI_DEVICE_OFFLINE) == DI_DEVICE_OFFLINE || + (dev_di_state & DI_DEVICE_DOWN) == DI_DEVICE_DOWN) { + return (CFGA_STAT_UNCONFIGURED); + } else { + return (CFGA_STAT_NONE); + } +} + +/* + * Wrapper routine for inserting ldata to make an sorted ldata list. + * + * When show_FCP_dev option is given insert_FCP_dev_ldata() is called. + * Otherwise insert_fc_dev_ldata() is called. + */ +static fpcfga_ret_t +insert_ldata_to_ldatalist( + const char *port_wwn, + int *lun_nump, + ldata_list_t *listp, + ldata_list_t **ldatapp) +{ + + if (lun_nump == NULL) { + return (insert_fc_dev_ldata(port_wwn, listp, ldatapp)); + } else { + return + (insert_FCP_dev_ldata(port_wwn, *lun_nump, listp, ldatapp)); + } +} + +/* + * Insert an input ldata to ldata list to make sorted ldata list. + */ +static fpcfga_ret_t +insert_fc_dev_ldata( + const char *port_wwn, + ldata_list_t *listp, + ldata_list_t **ldatapp) +{ + ldata_list_t *prevlp = NULL, *curlp = NULL; + char *dyn = NULL, *dyncomp = NULL; + + if (*ldatapp == NULL) { + *ldatapp = listp; + return (FPCFGA_OK); + } + + dyn = GET_DYN((*ldatapp)->ldata.ap_phys_id); + if (dyn != NULL) dyncomp = DYN_TO_DYNCOMP(dyn); + if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) >= 0)) { + listp->next = *ldatapp; + *ldatapp = listp; + return (FPCFGA_OK); + } + /* else continue */ + + prevlp = *ldatapp; + curlp = (*ldatapp)->next; + + dyn = dyncomp = NULL; + while (curlp != NULL) { + dyn = GET_DYN(curlp->ldata.ap_phys_id); + if (dyn != NULL) dyncomp = DYN_TO_DYNCOMP(dyn); + if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) >= 0)) { + listp->next = prevlp->next; + prevlp->next = listp; + return (FPCFGA_OK); + } + dyn = dyncomp = NULL; + prevlp = curlp; + curlp = curlp->next; + } + + /* add the ldata to the end of the list. */ + prevlp->next = listp; + return (FPCFGA_OK); +} + +/* + * Insert an input ldata to ldata list to make sorted ldata list. + */ +static fpcfga_ret_t +insert_FCP_dev_ldata( + const char *port_wwn, + int lun_num, + ldata_list_t *listp, + ldata_list_t **ldatapp) +{ + ldata_list_t *prevlp = NULL, *curlp = NULL; + char *dyn = NULL, *dyncomp = NULL; + char *lun_dyn = NULL, *lunp = NULL; + + if (*ldatapp == NULL) { + *ldatapp = listp; + return (FPCFGA_OK); + } + + dyn = GET_DYN((*ldatapp)->ldata.ap_phys_id); + if (dyn != NULL) dyncomp = DYN_TO_DYNCOMP(dyn); + if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) == 0)) { + lun_dyn = GET_LUN_DYN(dyncomp); + if (lun_dyn != NULL) { + lunp = LUN_DYN_TO_LUNCOMP(lun_dyn); + if ((atoi(lunp)) >= lun_num) { + listp->next = *ldatapp; + *ldatapp = listp; + return (FPCFGA_OK); + } + } + } else if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) > 0)) { + listp->next = *ldatapp; + *ldatapp = listp; + return (FPCFGA_OK); + } + + prevlp = *ldatapp; + curlp = (*ldatapp)->next; + + dyn = dyncomp = NULL; + lun_dyn = lunp = NULL; + while (curlp != NULL) { + dyn = GET_DYN(curlp->ldata.ap_phys_id); + if (dyn != NULL) dyncomp = DYN_TO_DYNCOMP(dyn); + + if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) == 0)) { + lun_dyn = GET_LUN_DYN(dyncomp); + if (lun_dyn != NULL) { + lunp = LUN_DYN_TO_LUNCOMP(lun_dyn); + if ((atoi(lunp)) >= lun_num) { + listp->next = prevlp->next; + prevlp->next = listp; + return (FPCFGA_OK); + } + } + /* else continue */ + } else if ((dyncomp != NULL) && + (strncmp(dyncomp, port_wwn, WWN_SIZE*2) > 0)) { + listp->next = prevlp->next; + prevlp->next = listp; + return (FPCFGA_OK); + } + /* else continue */ + + dyn = dyncomp = NULL; + lun_dyn = lunp = NULL; + prevlp = curlp; + curlp = curlp->next; + } + + /* add the ldata to the end of the list. */ + prevlp->next = listp; + return (FPCFGA_OK); +} + +/* + * This function will return the dtype for the given device + * It will first issue a report lun to lun 0 and then it will issue a SCSI + * Inquiry to the first lun returned by report luns. + * + * If everything is successful, the dtype will be returned with the peri + * qualifier masked out. + * + * If either the report lun or the scsi inquiry fails, we will first check + * the return status. If the return status is SCSI_DEVICE_NOT_TGT, then + * we will assume this is a remote HBA and return an UNKNOWN DTYPE + * for all other failures, we will return a dtype of ERR_INQ_DTYPE + */ +static uchar_t +get_inq_dtype(char *xport_phys, char *dyncomp, HBA_HANDLE handle, + HBA_PORTATTRIBUTES *portAttrs, HBA_PORTATTRIBUTES *discPortAttrs) { + HBA_STATUS status; + report_lun_resp_t *resp_buf; + int num_luns = 0, ret, l_errno; + uchar_t *lun_string; + uint64_t lun = 0; + struct scsi_inquiry inq; + struct scsi_extended_sense sense; + HBA_UINT8 scsiStatus; + uint32_t inquirySize = sizeof (inq); + uint32_t senseSize = sizeof (sense); + + memset(&inq, 0, sizeof (inq)); + memset(&sense, 0, sizeof (sense)); + if ((ret = get_report_lun_data(xport_phys, 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 { + inq.inq_dtype = ERR_INQ_DTYPE; + } + return (inq.inq_dtype); + } + } else { + /* send the inquiry to the first lun */ + lun_string = (uchar_t *)&(resp_buf->lun_string[0]); + memcpy(&lun, lun_string, sizeof (lun)); + S_FREE(resp_buf); + } + + memset(&sense, 0, sizeof (sense)); + status = HBA_ScsiInquiryV2(handle, + portAttrs->PortWWN, discPortAttrs->PortWWN, lun, 0, 0, + &inq, &inquirySize, &scsiStatus, &sense, &senseSize); + if (status == HBA_STATUS_OK) { + inq.inq_dtype = inq.inq_dtype & DTYPE_MASK; + } else if (status == HBA_STATUS_ERROR_NOT_A_TARGET) { + inq.inq_dtype = DTYPE_UNKNOWN; + } else { + inq.inq_dtype = ERR_INQ_DTYPE; + } + return (inq.inq_dtype); +} diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_rcm.c b/usr/src/lib/cfgadm_plugins/fp/common/cfga_rcm.c new file mode 100644 index 0000000000..5fbf41c96b --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_rcm.c @@ -0,0 +1,613 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#include "cfga_fp.h" + +static fpcfga_ret_t fp_rcm_init(char *, cfga_flags_t, char **, uint_t *, + char **rsrc_fixed); +static int fp_rcm_process_node(di_node_t, void *); +static fpcfga_ret_t fp_rcm_info_table(rcm_info_t *, char **); +static char *chop_minor(char *); + +#define MAX_FORMAT 80 +#define DEVICES "/devices" + +typedef struct { + char *bus_path; + char *filter; + char **errstring; + fpcfga_ret_t ret; + cfga_flags_t flags; + fpcfga_ret_t (*func)(char *, char *, char **, cfga_flags_t); +} walkargs_t; + +static fpcfga_ret_t fp_rcm_info_table(rcm_info_t *, char **); +static int fp_rcm_process_node(di_node_t, void *); +static fpcfga_ret_t fp_rcm_init(char *, cfga_flags_t, char **, uint_t *, + char **); +static char *chop_minor(char *); + +static rcm_handle_t *rcm_handle = NULL; +static mutex_t rcm_handle_lock; + +/* + * fp_rcm_offline() + * + * Offline FP resource consumers. + */ +fpcfga_ret_t +fp_rcm_offline(char *rsrc, char **errstring, cfga_flags_t flags) +{ + int rret; + uint_t rflags = 0; + char *rsrc_fixed; + rcm_info_t *rinfo = NULL; + fpcfga_ret_t ret = FPCFGA_OK; + + if ((ret = fp_rcm_init(rsrc, flags, errstring, &rflags, &rsrc_fixed)) + != FPCFGA_OK) + return (ret); + + if ((rret = rcm_request_offline(rcm_handle, rsrc_fixed, rflags, &rinfo)) + != RCM_SUCCESS) { + cfga_err(errstring, 0, ERRARG_RCM_OFFLINE, rsrc_fixed, 0); + if (rinfo) { + (void) fp_rcm_info_table(rinfo, errstring); + rcm_free_info(rinfo); + } + if (rret == RCM_FAILURE) + (void) fp_rcm_online(rsrc, errstring, flags); + ret = FPCFGA_BUSY; + } + + S_FREE(rsrc_fixed); + + return (ret); +} + +/* + * fp_rcm_online() + * + * Online FP resource consumers that were previously offlined. + */ +fpcfga_ret_t +fp_rcm_online(char *rsrc, char **errstring, cfga_flags_t flags) +{ + char *rsrc_fixed; + rcm_info_t *rinfo = NULL; + fpcfga_ret_t ret = FPCFGA_OK; + + if ((ret = fp_rcm_init(rsrc, flags, errstring, NULL, &rsrc_fixed)) + != FPCFGA_OK) + return (ret); + + if (rcm_notify_online(rcm_handle, rsrc_fixed, 0, &rinfo) + != RCM_SUCCESS && rinfo != NULL) { + cfga_err(errstring, 0, ERRARG_RCM_ONLINE, rsrc_fixed, 0); + (void) fp_rcm_info_table(rinfo, errstring); + rcm_free_info(rinfo); + ret = FPCFGA_ERR; + } + + S_FREE(rsrc_fixed); + + return (ret); +} + +/* + * fp_rcm_remove() + * + * Remove FP resource consumers after their kernel removal. + */ +fpcfga_ret_t +fp_rcm_remove(char *rsrc, char **errstring, cfga_flags_t flags) +{ + char *rsrc_fixed; + rcm_info_t *rinfo = NULL; + fpcfga_ret_t ret = FPCFGA_OK; + + if ((ret = fp_rcm_init(rsrc, flags, errstring, NULL, &rsrc_fixed)) + != FPCFGA_OK) + return (ret); + + if (rcm_notify_remove(rcm_handle, rsrc_fixed, 0, &rinfo) + != RCM_SUCCESS) { + cfga_err(errstring, 0, ERRARG_RCM_REMOVE, rsrc_fixed, 0); + if (rinfo) { + (void) fp_rcm_info_table(rinfo, errstring); + rcm_free_info(rinfo); + } + ret = FPCFGA_ERR; + } + + S_FREE(rsrc_fixed); + + return (ret); +} + +/* + * fp_rcm_suspend() + * + * Suspend FP resource consumers before a bus quiesce. + */ +fpcfga_ret_t +fp_rcm_suspend(char *rsrc, char *filter, char **errstring, cfga_flags_t flags) +{ + int rret; + uint_t rflags = 0; + char *rsrc_fixed; + char *filter_fixed; + char *rsrc_devpath; + rcm_info_t *rinfo = NULL; + di_node_t node; + fpcfga_ret_t ret = FPCFGA_OK; + walkargs_t walkargs; + timespec_t zerotime = { 0, 0 }; + + if ((ret = fp_rcm_init(rsrc, flags, errstring, &rflags, &rsrc_fixed)) + != FPCFGA_OK) + return (ret); + + /* If a filter is provided, ensure that it makes sense */ + if (filter != NULL && strstr(filter, rsrc) != filter) { + S_FREE(rsrc_fixed); + cfga_err(errstring, 0, ERR_APID_INVAL, 0); + return (FPCFGA_ERR); + } + + /* + * If no filter is specified: attempt a suspension on the resource, + * directly. + */ + if (filter == NULL) { + if ((rret = rcm_request_suspend(rcm_handle, rsrc_fixed, rflags, + &zerotime, &rinfo)) != RCM_SUCCESS) { + cfga_err(errstring, 0, ERRARG_RCM_SUSPEND, rsrc_fixed, + 0); + if (rinfo) { + (void) fp_rcm_info_table(rinfo, errstring); + rcm_free_info(rinfo); + } + if (rret == RCM_FAILURE) + (void) fp_rcm_resume(rsrc, filter, errstring, + (flags & (~CFGA_FLAG_FORCE))); + ret = FPCFGA_BUSY; + } + S_FREE(rsrc_fixed); + return (ret); + } + + /* + * If a filter is specified: open the resource with libdevinfo, walk + * through its nodes, and attempt a suspension of each node that + * mismatches the filter. + */ + + /* Chop off the filter's minor name */ + if ((filter_fixed = chop_minor(filter)) == NULL) + return (FPCFGA_ERR); + + /* get a libdevinfo snapshot of the resource's subtree */ + rsrc_devpath = rsrc_fixed; + if (strstr(rsrc_fixed, DEVICES) != NULL) + rsrc_devpath += strlen(DEVICES); + node = di_init(rsrc_devpath, DINFOSUBTREE | DINFOMINOR); + if (node == DI_NODE_NIL) { + cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0); + ret = FPCFGA_ERR; + } + + /* apply the filter, and suspend all resources not filtered out */ + if (ret == FPCFGA_OK) { + + walkargs.bus_path = rsrc_fixed; + walkargs.filter = filter_fixed; + walkargs.errstring = errstring; + walkargs.ret = FPCFGA_OK; + walkargs.flags = rflags; + walkargs.func = fp_rcm_suspend; + + if (di_walk_node(node, 0, &walkargs, fp_rcm_process_node) < 0) + cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0); + + ret = walkargs.ret; + } + + if (node != DI_NODE_NIL) + di_fini(node); + + S_FREE(rsrc_fixed); + S_FREE(filter_fixed); + + if (ret != FPCFGA_OK) + (void) fp_rcm_resume(rsrc, filter, errstring, + (flags & (~CFGA_FLAG_FORCE))); + + return (ret); +} + +/* + * fp_rcm_resume() + * + * Resume FP resource consumers after a bus has been unquiesced. + */ +fpcfga_ret_t +fp_rcm_resume(char *rsrc, char *filter, char **errstring, cfga_flags_t flags) +{ + uint_t rflags = 0; + char *rsrc_fixed; + char *filter_fixed; + char *rsrc_devpath; + rcm_info_t *rinfo = NULL; + di_node_t node; + fpcfga_ret_t ret = FPCFGA_OK; + walkargs_t walkargs; + + if ((ret = fp_rcm_init(rsrc, flags, errstring, &rflags, &rsrc_fixed)) + != FPCFGA_OK) + return (ret); + + /* If a filter is provided, ensure that it makes sense */ + if (filter != NULL && strstr(filter, rsrc) != filter) { + S_FREE(rsrc_fixed); + cfga_err(errstring, 0, ERR_APID_INVAL, 0); + return (FPCFGA_ERR); + } + + /* + * If no filter is specified: resume the resource directly. + */ + if (filter == NULL) { + if (rcm_notify_resume(rcm_handle, rsrc_fixed, rflags, &rinfo) + != RCM_SUCCESS && rinfo != NULL) { + cfga_err(errstring, 0, ERRARG_RCM_RESUME, rsrc_fixed, + 0); + (void) fp_rcm_info_table(rinfo, errstring); + rcm_free_info(rinfo); + ret = FPCFGA_BUSY; + } + S_FREE(rsrc_fixed); + return (ret); + } + + /* + * If a filter is specified: open the resource with libdevinfo, walk + * through its nodes, and resume each of its nodes that mismatches + * the filter. + */ + + /* Chop off the filter's minor name */ + if ((filter_fixed = chop_minor(filter)) == NULL) + return (FPCFGA_ERR); + + /* get a libdevinfo snapshot of the resource's subtree */ + rsrc_devpath = rsrc_fixed; + if (strstr(rsrc_fixed, DEVICES) != NULL) + rsrc_devpath += strlen(DEVICES); + node = di_init(rsrc_devpath, DINFOSUBTREE | DINFOMINOR); + if (node == DI_NODE_NIL) { + cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0); + ret = FPCFGA_ERR; + } + + /* apply the filter, and resume all resources not filtered out */ + if (ret == FPCFGA_OK) { + + walkargs.bus_path = rsrc_fixed; + walkargs.filter = filter_fixed; + walkargs.errstring = errstring; + walkargs.ret = FPCFGA_OK; + walkargs.flags = rflags; + walkargs.func = fp_rcm_resume; + + if (di_walk_node(node, 0, &walkargs, fp_rcm_process_node) < 0) + cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0); + + ret = walkargs.ret; + } + + if (node != DI_NODE_NIL) + di_fini(node); + + S_FREE(rsrc_fixed); + S_FREE(filter_fixed); + + return (ret); +} + +/* + * fp_rcm_info + * + * Queries RCM information for resources, and formats it into a table. + * The table is appended to the info argument. If the info argument is a + * null pointer, then a new string is malloc'ed. If the info argument is + * not a null pointer, then it is realloc'ed to the required size. + */ +fpcfga_ret_t +fp_rcm_info(char *rsrc, char **errstring, char **info) +{ + char *rsrc_fixed; + rcm_info_t *rinfo = NULL; + fpcfga_ret_t ret = FPCFGA_OK; + + if ((ret = fp_rcm_init(rsrc, 0, errstring, NULL, &rsrc_fixed)) + != FPCFGA_OK) + return (ret); + + if (info == NULL) { + S_FREE(rsrc_fixed); + return (FPCFGA_ERR); + } + + if (rcm_get_info(rcm_handle, rsrc_fixed, 0, &rinfo) + != RCM_SUCCESS) { + cfga_err(errstring, 0, ERRARG_RCM_INFO, rsrc_fixed, 0); + ret = FPCFGA_ERR; + } else if (rinfo == NULL) + ret = FPCFGA_OK; + + if (rinfo) { + if ((ret = fp_rcm_info_table(rinfo, info)) != FPCFGA_OK) + cfga_err(errstring, 0, ERRARG_RCM_INFO, rsrc_fixed, 0); + rcm_free_info(rinfo); + } + + S_FREE(rsrc_fixed); + + return (ret); +} + +/* + * fp_rcm_init() + * + * Contains common initialization code for entering a fp_rcm_xx() + * routine. + */ +static fpcfga_ret_t +fp_rcm_init(char *rsrc, cfga_flags_t flags, char **errstring, uint_t *rflags, + char **rsrc_fixed) +{ + /* Validate the rsrc argument */ + if (rsrc == NULL) { + cfga_err(errstring, 0, ERR_APID_INVAL, 0); + return (FPCFGA_ERR); + } + + /* Translate the cfgadm flags to RCM flags */ + if (rflags && (flags & CFGA_FLAG_FORCE)) + *rflags |= RCM_FORCE; + + /* Get a handle for the RCM operations */ + (void) mutex_lock(&rcm_handle_lock); + if (rcm_handle == NULL) { + if (rcm_alloc_handle(NULL, RCM_NOPID, NULL, &rcm_handle) != + RCM_SUCCESS) { + cfga_err(errstring, 0, ERR_RCM_HANDLE, 0); + (void) mutex_unlock(&rcm_handle_lock); + return (FPCFGA_LIB_ERR); + } + } + (void) mutex_unlock(&rcm_handle_lock); + + /* Chop off the rsrc's minor, if it has one */ + if ((*rsrc_fixed = chop_minor(rsrc)) == NULL) + return (FPCFGA_ERR); + + return (FPCFGA_OK); +} + +/* + * fp_rcm_process_node + * + * Helper routine for fp_rcm_{suspend,resume}. This is a di_walk_node() + * callback that will apply a filter to every node it sees, and either suspend + * or resume it if it doesn't match the filter. + */ +static int +fp_rcm_process_node(di_node_t node, void *argp) +{ + char *devfs_path; + walkargs_t *walkargs; + fpcfga_ret_t ret = FPCFGA_OK; + char disk_path[MAXPATHLEN]; + + /* Guard against bad arguments */ + if ((walkargs = (walkargs_t *)argp) == NULL) + return (DI_WALK_TERMINATE); + if (walkargs->filter == NULL || walkargs->errstring == NULL) { + walkargs->ret = FPCFGA_ERR; + return (DI_WALK_TERMINATE); + } + + /* If the node has no minors, then skip it */ + if (di_minor_next(node, DI_MINOR_NIL) == DI_MINOR_NIL) + return (DI_WALK_CONTINUE); + + /* Construct the devices path */ + if ((devfs_path = di_devfs_path(node)) == NULL) + return (DI_WALK_CONTINUE); + (void) snprintf(disk_path, MAXPATHLEN, "%s%s", DEVICES, devfs_path); + di_devfs_path_free(devfs_path); + + /* + * If the node does not correspond to the targeted FP bus or the + * disk being filtered out, then use the appropriate suspend/resume + * function. + */ + if (strcmp(disk_path, walkargs->bus_path) != 0 && + strcmp(disk_path, walkargs->filter) != 0) + ret = (*walkargs->func)(disk_path, NULL, walkargs->errstring, + walkargs->flags); + + /* Stop the walk early if the above operation failed */ + if (ret != FPCFGA_OK) { + walkargs->ret = ret; + return (DI_WALK_TERMINATE); + } + + return (DI_WALK_CONTINUE); +} + +/* + * fp_rcm_info_table + * + * Takes an opaque rcm_info_t pointer and a character pointer, and appends + * the rcm_info_t data in the form of a table to the given character pointer. + */ +static fpcfga_ret_t +fp_rcm_info_table(rcm_info_t *rinfo, char **table) +{ + int i; + size_t w; + size_t width = 0; + size_t w_rsrc = 0; + size_t w_info = 0; + size_t table_size = 0; + uint_t tuples = 0; + rcm_info_tuple_t *tuple = NULL; + char *rsrc; + char *info; + char *newtable; + static char format[MAX_FORMAT]; + const char *info_info_str, *info_rsrc_str; + + /* Protect against invalid arguments */ + if (rinfo == NULL || table == NULL) + return (FPCFGA_ERR); + + /* Set localized table header strings */ + rsrc = gettext("Resource"); + info = gettext("Information"); + + /* A first pass, to size up the RCM information */ + while (tuple = rcm_info_next(rinfo, tuple)) { + info_info_str = rcm_info_info(tuple); + info_rsrc_str = rcm_info_rsrc(tuple); + if ((info_info_str != NULL) && (info_rsrc_str != NULL)) { + tuples++; + if ((w = strlen(info_rsrc_str)) > w_rsrc) + w_rsrc = w; + if ((w = strlen(info_info_str)) > w_info) + w_info = w; + } + } + + /* If nothing was sized up above, stop early */ + if (tuples == 0) + return (FPCFGA_OK); + + /* Adjust column widths for column headings */ + if ((w = strlen(rsrc)) > w_rsrc) + w_rsrc = w; + else if ((w_rsrc - w) % 2) + w_rsrc++; + if ((w = strlen(info)) > w_info) + w_info = w; + else if ((w_info - w) % 2) + w_info++; + + /* + * Compute the total line width of each line, + * accounting for intercolumn spacing. + */ + width = w_info + w_rsrc + 4; + + /* Allocate space for the table */ + table_size = (2 + tuples) * (width + 1) + 2; + if (*table == NULL) + *table = malloc(table_size); + else { + newtable = realloc(*table, strlen(*table) + table_size); + if (newtable != NULL) + *table = newtable; + } + if (*table == NULL) + return (FPCFGA_ERR); + + /* Place a table header into the string */ + + /* The resource header */ + (void) strcat(*table, "\n"); + w = strlen(rsrc); + for (i = 0; i < ((w_rsrc - w) / 2); i++) + (void) strcat(*table, " "); + (void) strcat(*table, rsrc); + for (i = 0; i < ((w_rsrc - w) / 2); i++) + (void) strcat(*table, " "); + + /* The information header */ + (void) strcat(*table, " "); + w = strlen(info); + for (i = 0; i < ((w_info - w) / 2); i++) + (void) strcat(*table, " "); + (void) strcat(*table, info); + for (i = 0; i < ((w_info - w) / 2); i++) + (void) strcat(*table, " "); + + /* Underline the headers */ + (void) strcat(*table, "\n"); + for (i = 0; i < w_rsrc; i++) + (void) strcat(*table, "-"); + (void) strcat(*table, " "); + for (i = 0; i < w_info; i++) + (void) strcat(*table, "-"); + + /* Construct the format string */ + (void) snprintf(format, MAX_FORMAT, "%%-%ds %%-%ds", w_rsrc, w_info); + + /* Add the tuples to the table string */ + tuple = NULL; + while ((tuple = rcm_info_next(rinfo, tuple)) != NULL) { + info_info_str = rcm_info_info(tuple); + info_rsrc_str = rcm_info_rsrc(tuple); + if ((info_info_str != NULL) && (info_rsrc_str != NULL)) { + (void) strcat(*table, "\n"); + (void) sprintf(&((*table)[strlen(*table)]), + format, info_rsrc_str, info_info_str); + } + } + + return (FPCFGA_OK); +} + +/* + * chop_minor() + * + * Chops off the minor name portion of a resource. Allocates storage for + * the returned string. Caller must free the storage if return is non-NULL. + */ +static char * +chop_minor(char *rsrc) +{ + char *rsrc_fixed; + char *cp; + + if ((rsrc_fixed = strdup(rsrc)) == NULL) + return (NULL); + if ((cp = strrchr(rsrc_fixed, ':')) != NULL) + *cp = '\0'; + return (rsrc_fixed); +} diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_rep.c b/usr/src/lib/cfgadm_plugins/fp/common/cfga_rep.c new file mode 100644 index 0000000000..0ca8cb4e09 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_rep.c @@ -0,0 +1,614 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + + +#include <libgen.h> +#include "cfga_fp.h" + +/* The following are used by update_fabric_wwn_list() */ +#define COPY_EXT ".cpy." /* Extn used in naming backup file */ +#define TMP_EXT ".tmp." /* Extn used in naming temp file */ +static char *HDR = +"#\n" +"# fabric_WWN_map\n" +"#\n" +"# The physical ap_id list of configured fabric devices.\n" +"# Do NOT edit this file by hand -- refer to the cfgadm_fp(1M)\n" +"# man page and use cfgadm(1m) instead.\n" +"#\n"; + +/* + * This function searches for "srch_str" (of length "slen") in "buf" (of length + * "buflen"). If it is not found, "write_offset" has the offset in "buf" where + * "srch_str" would have to be added in "buf". If "srch_str" is found in "buf", + * "write_offset" has its offset in "buf" + * + * ARGUMENTS : + * buf - buffer to search in + * buflen - length of buffer + * srch_str - string to search + * slen - length of srch_str + * write_offset - Set in function on exit + * - It is the offset in buf where srch_str is or should be + * bytes_left - Set in function on exit + * - It is the # of bytes left beyond write_offset in buf + * + * Notes : + * - This function assumes "buf" is sorted in ascending order + * - If 'buflen' is > 0, it assumes it has a header on top and skips it + * - "srch_str" has '\n' at the end, but when update_fabric_wwn_list() calls + * this function, 'slen' does not include the last `\n' + * + * RETURN VALUES : + * Zero - "srch_str" found in "buf"... "write_offset" has offset in "buf" + * > 0 - "srch_str" NOT found in "buf" ... "write_offset" has offset in "buf" + * where "srch_str" can fit in. + * "buf" had contents > "srch_str" + * < 0 - "srch_str" NOT found in "buf" ... "write_offset" has offset in "buf" + * where "srch_str" can fit in. + * "buf" had contents < "srch_str" + */ +static int +search_line(char *buf, int buflen, char *srch_str, int slen, + int *write_offset, int *bytes_left) +{ + int retval, sizeof_rep_hdr = strlen(HDR); + char *sol; /* Pointer to Start-Of-Line */ + char *cur_pos; /* current position */ + + *bytes_left = buflen; + *write_offset = 0; + + if (buf == NULL || *buf == NULL || buflen <= 0) + return (-2); /* Arbitrary -ve val. srch_str not found */ + + if (srch_str == NULL || *srch_str == NULL || slen <= 0) + return (0); /* This says srch_str was found */ + + sol = cur_pos = buf; + if (buflen >= sizeof_rep_hdr) { + /* skip header */ + sol = cur_pos = buf + sizeof_rep_hdr; + *bytes_left -= sizeof_rep_hdr; + } + + while (*bytes_left >= slen) { + if ((retval = strncmp(sol, srch_str, slen)) >= 0) { + /* strncmp will pass if srch_str is a substring */ + if ((retval == 0) && (*bytes_left > slen) && + (*(sol+slen) != '\n')) + retval = 1; /* Force it to be > 0 */ + *write_offset = sol - buf; + return (retval); + } + + /* retval < 0 */ + if ((cur_pos = strchr(sol, (int)'\n')) == NULL) { + *write_offset = buflen; + return (retval); + } + + /* Get the length of this line */ + *cur_pos = '\0'; /* kludge to get string length */ + *bytes_left -= (strlen(sol) + 1); + *cur_pos = '\n'; /* Put back the original char */ + + sol = cur_pos = cur_pos + 1; + } + + if (*bytes_left > 0) { + /* In this case the bytes left will be less than slen */ + if ((retval = strncmp(sol, srch_str, *bytes_left)) >= 0) { + *write_offset = sol - buf; + } else { + *write_offset = buflen; + } + return (retval); + } + *write_offset = sol - buf; + /* Should return a value < 0 to show that search string goes to eof */ + return (-1); +} + +/* + * This function sets an advisory lock on the file pointed to by the argument + * fd, which is a file descriptor. The lock is set using fcntl() which uses + * flock structure. + */ +int +lock_register(int fd, int cmd, int type, off_t offset, int whence, off_t len) +{ + struct flock lock; + + lock.l_type = type; + lock.l_start = offset; + lock.l_whence = whence; + lock.l_len = len; + + return (fcntl(fd, cmd, &lock)); +} + +/* Lot of places to cleanup - Less chance of missing out using this macro */ +#define CLEANUP_N_RET(ret) \ + if (fd != -1) { \ + close(fd); \ + } \ + if (copy_fd != -1) { \ + close(copy_fd); \ + } \ + if (tmp_fd != -1) { \ + close(tmp_fd); \ + } \ + if (copy_rep != NULL) { \ + remove(copy_rep); \ + free(copy_rep); \ + } \ + if (tmp_rep != NULL) { \ + remove(tmp_rep); \ + free(tmp_rep); \ + } \ + if (upd_str != NULL) { \ + free(upd_str); \ + } \ + if (repbuf != NULL) { \ + munmap(repbuf, filesize); \ + } \ + if (c_repbuf != NULL) { \ + munmap(c_repbuf, filesize); \ + } \ + if (t_repbuf != NULL) { \ + munmap(t_repbuf, size); \ + } \ + return (ret) + +/* + * INPUTS: + * cmd - ADD_ENTRY or REMOVE_ENTRY + * update_str - string for repository operation + * - Assumed NOT to have a '\n' and that it is null terminated + * errstring - Pointer that will be updated by this function + * - Any error msgs that has to be sent back to caller + * + * RETURNS : + * FPCFGA_OK on success + * FPCFGA_LIB_ERR on error + * + * SYNOPSIS: + * This function adds or deletes 'update_str' from FAB_REPOSITORY based on + * value of 'cmd'. The repository has a warning line on the top to disallow + * manual editing of the file. If the repository is being created fresh or if + * it is of zero length or if it has only warning lines in it, the operation + * speicified by 'cmd' is performed and returned. If the repository exists + * and has some data, it is expected to be of atleast the size of the lenght + * of the warning header. This is the only check that is performed on the + * validity of the file. No other checks are performed. On a valid + * repository, to perform the update, this function basically makes use of + * 3 buffers - the original buffer (repbuf), a copy buffer (c_repbuf) and a + * temp buffer (t_repbuf). + * The contents of the repository are mmap-ed into the repbuf and then + * copied into the c_repbuf. All further operations are done using the copy. + * t_repbuf is created to be the size of c_repbuf +/- 'slen' (based on + * whether it is add or remove operation). After adding/removing the + * 'update_str', the c_repbuf is copied to a OLD_FAB_REPOSITORY and t_repbuf + * is made FAB_REPOSITORY. + * + */ +int +update_fabric_wwn_list(int cmd, const char *update_str, char **errstring) +{ + int fd, copy_fd, tmp_fd, new_file_flag = 0; + int len, write_offset, bytes_left; + int sizeof_rep_hdr = strlen(HDR); + char *repbuf, *c_repbuf, *t_repbuf; + char *copy_rep, *tmp_rep, *upd_str; + off_t filesize, size; + struct stat stbuf; + + /* Do some initializations */ + fd = copy_fd = tmp_fd = -1; + repbuf = c_repbuf = t_repbuf = NULL; + copy_rep = tmp_rep = upd_str = NULL; + size = filesize = write_offset = bytes_left = 0; + + /* + * Set the mode to read only. Root user can still open as RDWR. + * We ignore errors in general here. But, just notice ENOENTs + */ + if ((chmod(FAB_REPOSITORY, S_IRUSR|S_IRGRP|S_IROTH) == -1) && + (errno == ENOENT)) { + new_file_flag = 1; + mkdirp(FAB_REPOSITORY_DIR, + S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + } + + /* Create the repository if its not there */ + if ((fd = open(FAB_REPOSITORY, O_RDWR | O_CREAT)) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + return (FPCFGA_LIB_ERR); + } + + /* Now try to chmod again. This time we dont ignore errors */ + if (fchmod(fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { + close(fd); + cfga_err(errstring, errno, ERR_UPD_REP, 0); + return (FPCFGA_LIB_ERR); + } + + if (lock_register(fd, F_SETLKW, F_WRLCK, 0, SEEK_SET, 0) < 0) { + close(fd); + cfga_err(errstring, 0, ERR_UPD_REP, 0); + return (FPCFGA_LIB_ERR); + } + + if (fstat(fd, &stbuf) == -1) { + close(fd); + cfga_err(errstring, errno, ERR_UPD_REP, 0); + return (FPCFGA_LIB_ERR); + } + + filesize = size = stbuf.st_size; + + /* A very Minimal check on repository */ + if (filesize && filesize < sizeof_rep_hdr) { + /* + * If there is some data, it should be atleast the size of + * the header + */ + close(fd); + cfga_err(errstring, errno, ERR_UPD_REP, 0); + return (FPCFGA_LIB_ERR); + } + + if ((len = strlen(update_str)) == 0) { + /* + * We are trying to add/remove a NULL string. + * Just return success + */ + close(fd); + return (FPCFGA_OK); + } + + if ((upd_str = calloc(1, len + 2)) == NULL) { + close(fd); + cfga_err(errstring, errno, ERR_UPD_REP, 0); + return (FPCFGA_LIB_ERR); + } + + strcpy(upd_str, update_str); + strcat(upd_str, "\n"); /* Append a new line char */ + len = strlen(upd_str); + + if (filesize > 0) { + if ((copy_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) + + sizeof (COPY_EXT) + sizeof (pid_t))) == NULL) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + (void) sprintf(copy_rep, "%s%s%ld", FAB_REPOSITORY, COPY_EXT, + getpid()); + + if ((copy_fd = open(copy_rep, O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR)) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if ((repbuf = (char *)mmap(0, filesize, PROT_READ, + MAP_SHARED, fd, 0)) == MAP_FAILED) { + close(fd); + free(upd_str); + cfga_err(errstring, errno, ERR_UPD_REP, 0); + return (FPCFGA_LIB_ERR); + } + + if (lseek(copy_fd, filesize - 1, SEEK_SET) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (write(copy_fd, "", 1) != 1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if ((c_repbuf = (char *)mmap(0, filesize, + PROT_READ | PROT_WRITE, + MAP_SHARED, copy_fd, 0)) == MAP_FAILED) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + memcpy(c_repbuf, repbuf, filesize); + /* + * We cannot close the repository since we hold a lock + * But we'll free up the mmap-ed area. + */ + munmap(repbuf, filesize); + repbuf = NULL; + } + + /* + * If we just created this file, or it was an empty repository file + * add a header to the beginning of file. + * If it had was a repository file with just the header, + */ + if (new_file_flag != 0 || filesize == 0 || filesize == sizeof_rep_hdr) { + if ((filesize != sizeof_rep_hdr) && + (write(fd, HDR, sizeof_rep_hdr) != sizeof_rep_hdr)) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + /* + * We know its a new file, empty file or a file with only a + * header so lets get the update operation done with + */ + switch (cmd) { + case ADD_ENTRY: + /* If there is a header, we have to skip it */ + if (lseek(fd, 0, SEEK_END) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (write(fd, upd_str, len) != len) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (filesize > 0) { + /* Now create the '.old' file */ + if (msync(c_repbuf, filesize, MS_SYNC) == -1) { + cfga_err(errstring, errno, + ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (fchmod(copy_fd, + S_IRUSR | S_IRGRP | S_IROTH) < 0) { + cfga_err(errstring, errno, + ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + rename(copy_rep, OLD_FAB_REPOSITORY); + } + + CLEANUP_N_RET(FPCFGA_OK); + + case REMOVE_ENTRY: + /* + * So, the side effect of a remove on an empty or + * non-existing repository is that the repository got + * created + */ + CLEANUP_N_RET(FPCFGA_OK); + + default: + cfga_err(errstring, 0, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + } + + /* Now, size and filesize are > sizeof_rep_hdr */ + + switch (cmd) { + case ADD_ENTRY: + size += len; + /* + * We'll search the full repository, header included, since + * we dont expect upd_str to match anything in the header. + */ + if (search_line(c_repbuf, filesize, upd_str, + len - 1, &write_offset, &bytes_left) == 0) { + /* line already exists in repository or len == 0 */ + CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */ + } + + /* construct temp file name using pid. */ + if ((tmp_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) + + sizeof (TMP_EXT) + sizeof (pid_t))) == NULL) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + (void) sprintf(tmp_rep, "%s%s%ld", FAB_REPOSITORY, + TMP_EXT, getpid()); + + /* Open tmp repository file in absolute mode */ + if ((tmp_fd = open(tmp_rep, O_RDWR|O_CREAT|O_TRUNC, + S_IRUSR | S_IWUSR)) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (lseek(tmp_fd, size - 1, SEEK_SET) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (write(tmp_fd, "", 1) != 1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if ((t_repbuf = (char *)mmap(0, size, PROT_READ|PROT_WRITE, + MAP_SHARED, tmp_fd, 0)) == MAP_FAILED) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + memcpy(t_repbuf, c_repbuf, write_offset); + strncpy(t_repbuf + write_offset, upd_str, len); + if (write_offset != filesize) { + memcpy(t_repbuf + write_offset + len, + c_repbuf + write_offset, bytes_left); + } + + /* + * we are using the copy of FAB_REPOSITORY and will + * do msync first since it will be renamed to '.old' file. + */ + if (msync(c_repbuf, filesize, MS_SYNC) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (fchmod(copy_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (msync(t_repbuf, size, MS_SYNC) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (fchmod(tmp_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + close(copy_fd); copy_fd = -1; + close(tmp_fd); tmp_fd = -1; + + /* here we do rename and rename before close fd */ + rename(copy_rep, OLD_FAB_REPOSITORY); + rename(tmp_rep, FAB_REPOSITORY); + + if (lock_register(fd, F_SETLK, F_UNLCK, 0, SEEK_SET, 0) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + CLEANUP_N_RET(FPCFGA_OK); + + case REMOVE_ENTRY: + if (size >= sizeof_rep_hdr + len - 1) { + size -= len; + /* + * No need to init the 'else' part (size < len) because + * in that case, there will be nothing to delete from + * the file and so 'size' will not be used in the code + * below since search_line() will not find upd_str. + */ + } + + if (search_line(c_repbuf, filesize, upd_str, len - 1, + &write_offset, &bytes_left) != 0) { + /* this line does not exists - nothing to remove */ + CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */ + } + + /* construct temp file name using pid. */ + if ((tmp_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) + + sizeof (TMP_EXT) + sizeof (pid_t))) == NULL) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + (void) sprintf(tmp_rep, "%s%s%ld", FAB_REPOSITORY, + TMP_EXT, getpid()); + + /* Open tmp repository file in absolute mode */ + if ((tmp_fd = open(tmp_rep, O_RDWR|O_CREAT|O_TRUNC, + S_IRUSR | S_IWUSR)) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (size > 0) { + if (lseek(tmp_fd, size - 1, SEEK_SET) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (write(tmp_fd, "", 1) != 1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if ((t_repbuf = (char *)mmap(0, size, + PROT_READ|PROT_WRITE, + MAP_SHARED, tmp_fd, 0)) == MAP_FAILED) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + memcpy(t_repbuf, c_repbuf, write_offset); + if ((bytes_left - len) > 0) { + memcpy(t_repbuf + write_offset, + c_repbuf + write_offset + len, + bytes_left - len); + } + + if (msync(t_repbuf, size, MS_SYNC) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + } + + if (fchmod(tmp_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + /* + * we are using the copy of FAB_REPOSITORY and will + * do msync first since it will be renamed to bak file. + */ + if (msync(c_repbuf, filesize, MS_SYNC) == -1) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + if (fchmod(copy_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + /* Close and invalidate the fd's */ + close(copy_fd); copy_fd = -1; + close(tmp_fd); tmp_fd = -1; + + /* here we do rename and rename before close fd */ + rename(copy_rep, OLD_FAB_REPOSITORY); + rename(tmp_rep, FAB_REPOSITORY); + + if (lock_register(fd, F_SETLK, F_UNLCK, 0, SEEK_SET, 0) < 0) { + cfga_err(errstring, errno, ERR_UPD_REP, 0); + CLEANUP_N_RET(FPCFGA_LIB_ERR); + } + + CLEANUP_N_RET(FPCFGA_OK); + + default: + /* Unexpected - just getout */ + break; + } + + CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */ +} diff --git a/usr/src/lib/cfgadm_plugins/fp/common/cfga_utils.c b/usr/src/lib/cfgadm_plugins/fp/common/cfga_utils.c new file mode 100644 index 0000000000..0ab98b77f5 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/cfga_utils.c @@ -0,0 +1,1372 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#include "cfga_fp.h" + +/* + * This file contains helper routines for the FP plugin + */ + +#if !defined(TEXT_DOMAIN) +#define TEXT_DOMAIN "SYS_TEST" +#endif + +typedef struct strlist { + const char *str; + struct strlist *next; +} strlist_t; + +typedef struct { + fpcfga_ret_t fp_err; + cfga_err_t cfga_err; +} errcvt_t; + +typedef struct { + fpcfga_cmd_t cmd; + int type; + int (*fcn)(const devctl_hdl_t); +} set_state_cmd_t; + +typedef struct { + fpcfga_cmd_t cmd; + int type; + int (*state_fcn)(const devctl_hdl_t, uint_t *); +} get_state_cmd_t; + +/* defines for nftw() */ +#define NFTW_DEPTH 1 +#define NFTW_CONTINUE 0 +#define NFTW_TERMINATE 1 +#define NFTW_ERROR -1 +#define MAX_RETRIES 10 + +/* Function prototypes */ +static int do_recurse_dev(const char *path, const struct stat *sbuf, + int type, struct FTW *ftwp); +static fpcfga_recur_t lookup_dev(const char *lpath, void *arg); +static void msg_common(char **err_msgpp, int append_newline, int l_errno, + va_list ap); +static void lunlist_free(struct luninfo_list *lunlist); + +/* Globals */ +struct { + mutex_t mp; + void *arg; + fpcfga_recur_t (*fcn)(const char *lpath, void *arg); +} nftw_arg = {DEFAULTMUTEX}; + +/* + * The string table contains most of the strings used by the fp cfgadm plugin. + * All strings which are to be internationalized must be in this table. + * Some strings which are not internationalized are also included here. + * Arguments to messages are NOT internationalized. + */ +msgcvt_t str_tbl[] = { + +/* + * The first element (ERR_UNKNOWN) MUST always be present in the array. + */ +#define UNKNOWN_ERR_IDX 0 /* Keep the index in sync */ + + +/* msg_code num_args, I18N msg_string */ + +/* ERRORS */ +{ERR_UNKNOWN, 0, 1, "unknown error"}, +{ERR_OP_FAILED, 0, 1, "operation failed"}, +{ERR_CMD_INVAL, 0, 1, "invalid command"}, +{ERR_NOT_BUSAPID, 0, 1, "not a FP bus apid"}, +{ERR_APID_INVAL, 0, 1, "invalid FP ap_id"}, +{ERR_NOT_BUSOP, 0, 1, "operation not supported for FC bus"}, +{ERR_NOT_DEVOP, 0, 1, "operation not supported for FC device"}, +{ERR_UNAVAILABLE, 0, 1, "unavailable"}, +{ERR_CTRLR_CRIT, 0, 1, "critical partition controlled by FC HBA"}, +{ERR_BUS_GETSTATE, 0, 1, "failed to get state for FC bus"}, +{ERR_BUS_NOTCONNECTED, 0, 1, "FC bus not connected"}, +{ERR_BUS_CONNECTED, 0, 1, "FC bus not disconnected"}, +{ERR_BUS_QUIESCE, 0, 1, "FC bus quiesce failed"}, +{ERR_BUS_UNQUIESCE, 0, 1, "FC bus unquiesce failed"}, +{ERR_BUS_CONFIGURE, 0, 1, "failed to configure devices on FC bus"}, +{ERR_BUS_UNCONFIGURE, 0, 1, "failed to unconfigure FC bus"}, +{ERR_DEV_CONFIGURE, 0, 1, "failed to configure FC device"}, +{ERR_DEV_UNCONFIGURE, 0, 1, "failed to unconfigure FC device"}, +{ERR_FCA_CONFIGURE, 0, 1, "failed to configure ANY device on FCA port"}, +{ERR_FCA_UNCONFIGURE, 0, 1, "failed to unconfigure ANY device on FCA port"}, +{ERR_DEV_REPLACE, 0, 1, "replace operation failed"}, +{ERR_DEV_INSERT, 0, 1, "insert operation failed"}, +{ERR_DEV_GETSTATE, 0, 1, "failed to get state for FC device"}, +{ERR_RESET, 0, 1, "reset failed"}, +{ERR_LIST, 0, 1, "list operation failed"}, +{ERR_SIG_STATE, 0, 1, "could not restore signal disposition"}, +{ERR_MAYBE_BUSY, 0, 1, "device may be busy"}, +{ERR_BUS_DEV_MISMATCH, 0, 1, "mismatched FC bus and device"}, +{ERR_MEM_ALLOC, 0, 1, "Failed to allocated memory"}, +{ERR_DEVCTL_OFFLINE, 0, 1, "failed to offline device"}, +{ERR_UPD_REP, 0, 1, "Repository update failed"}, +{ERR_CONF_OK_UPD_REP, 0, 1, + "Configuration successful, but Repository update failed"}, +{ERR_UNCONF_OK_UPD_REP, 0, 1, + "Unconfiguration successful, but Repository update failed"}, +{ERR_PARTIAL_SUCCESS, 0, 1, + "Operation partially successful. Some failures seen"}, +{ERR_HBA_LOAD_LIBRARY, 0, 1, + "HBA load library failed"}, +{ERR_MATCHING_HBA_PORT, 0, 1, + "No match HBA port found"}, +{ERR_NO_ADAPTER_FOUND, 0, 1, + "No Fibre Channel adpaters found"}, + +/* Errors with arguments */ +{ERRARG_OPT_INVAL, 1, 1, "invalid option: "}, +{ERRARG_HWCMD_INVAL, 1, 1, "invalid command: "}, +{ERRARG_DEVINFO, 1, 1, "libdevinfo failed on path: "}, +{ERRARG_NOT_IN_DEVLIST, 1, 1, "Device not found in fabric device list: "}, +{ERRARG_NOT_IN_DEVINFO, 1, 1, "Could not find entry in devinfo tree: "}, +{ERRARG_DI_GET_PROP, 1, 1, "Could not get libdevinfo property: "}, +{ERRARG_DC_DDEF_ALLOC, 1, 1, "failed to alloc ddef space: "}, +{ERRARG_DC_BYTE_ARRAY, 1, 1, "failed to add property: "}, +{ERRARG_DC_BUS_ACQUIRE, 1, 1, "failed to acquire bus handle: "}, +{ERRARG_BUS_DEV_CREATE, 1, 1, "failed to create device node: "}, +{ERRARG_BUS_DEV_CREATE_UNKNOWN, 1, 1, + "failed to create device node... Device may be unconfigurable: "}, +{ERRARG_DEV_ACQUIRE, 1, 1, "device acquire operation failed: "}, +{ERRARG_DEV_REMOVE, 1, 1, "remove operation failed: "}, + +/* Fibre Channel operation Errors */ +{ERR_FC, 0, 1, "FC error"}, +{ERR_FC_GET_DEVLIST, 0, 1, "Failed to get fabric device list"}, +{ERR_FC_GET_NEXT_DEV, 0, 1, "Failed to get next device on device map"}, +{ERR_FC_GET_FIRST_DEV, 0, 1, "Failed to get first device on device map"}, +{ERRARG_FC_DEV_MAP_INIT, 1, 1, + "Failed to initialize device map for: "}, +{ERRARG_FC_PROP_LOOKUP_BYTES, 1, 1, "Failed to get property of "}, +{ERRARG_FC_INQUIRY, 1, 1, "inquiry failed: "}, +{ERRARG_FC_REP_LUNS, 1, 1, "report LUNs failed: "}, +{ERRARG_FC_TOPOLOGY, 1, 1, "failed to get port topology: "}, +{ERRARG_PATH_TOO_LONG, 1, 1, "Path length exceeds max possible: "}, +{ERRARG_INVALID_PATH, 1, 1, "Invalid path: "}, +{ERRARG_OPENDIR, 1, 1, "failure opening directory: "}, + +/* MPXIO Errors */ +{ERRARG_VHCI_GET_PATHLIST, 1, 1, "failed to get path list from vHCI: "}, +{ERRARG_XPORT_NOT_IN_PHCI_LIST, 1, 1, "Transport not in pHCI list: "}, + +/* RCM Errors */ +{ERR_RCM_HANDLE, 0, 1, "cannot get RCM handle"}, +{ERRARG_RCM_SUSPEND, 1, 1, "failed to suspend: "}, +{ERRARG_RCM_RESUME, 1, 1, "failed to resume: "}, +{ERRARG_RCM_OFFLINE, 1, 1, "failed to offline: "}, +{ERRARG_RCM_ONLINE, 1, 1, "failed to online: "}, +{ERRARG_RCM_REMOVE, 1, 1, "failed to remove: "}, +{ERRARG_RCM_INFO, 1, 1, "failed to query: "}, + +/* Commands */ +{CMD_INSERT_DEV, 0, 0, "insert_device"}, +{CMD_REMOVE_DEV, 0, 0, "remove_device"}, +{CMD_REPLACE_DEV, 0, 0, "replace_device"}, +{CMD_RESET_DEV, 0, 0, "reset_device"}, +{CMD_RESET_BUS, 0, 0, "reset_bus"}, +{CMD_RESET_ALL, 0, 0, "reset_all"}, + +/* help messages */ +{MSG_HELP_HDR, 0, 1, "\nfp attachment point specific options:\n"}, +{MSG_HELP_USAGE, 0, 0, + "\t-c configure -o force_update ap_id [ap_id..]\n" + "\t-c configure -o no_update ap_id [ap_id...]\n" + "\t-c unconfigure -o force_update ap_id [ap_id... ]\n" + "\t-c unconfigure -o no_update ap_id [ap_id... ]\n"}, + +/* hotplug messages */ +{MSG_INSDEV, 1, 1, "Adding device to FC HBA: "}, +{MSG_RMDEV, 1, 1, "Removing FC device: "}, +{MSG_REPLDEV, 1, 1, "Replacing FC device: "}, + +/* Hotplugging confirmation prompts */ +{CONF_QUIESCE_1, 1, 1, + "This operation will suspend activity on FC bus: "}, + +{CONF_QUIESCE_2, 0, 1, "\nContinue"}, + +{CONF_UNQUIESCE, 0, 1, + "FC bus quiesced successfully.\n" + "It is now safe to proceed with hotplug operation." + "\nEnter y if operation is complete or n to abort"}, + +/* Misc. */ +{WARN_DISCONNECT, 0, 1, + "WARNING: Disconnecting critical partitions may cause system hang." + "\nContinue"} +}; + + +#define N_STRS (sizeof (str_tbl) / sizeof (str_tbl[0])) + +#define GET_MSG_NARGS(i) (str_tbl[msg_idx(i)].nargs) +#define GET_MSG_INTL(i) (str_tbl[msg_idx(i)].intl) + +static errcvt_t err_cvt_tbl[] = { + { FPCFGA_OK, CFGA_OK }, + { FPCFGA_LIB_ERR, CFGA_LIB_ERROR }, + { FPCFGA_APID_NOEXIST, CFGA_APID_NOEXIST }, + { FPCFGA_NACK, CFGA_NACK }, + { FPCFGA_BUSY, CFGA_BUSY }, + { FPCFGA_SYSTEM_BUSY, CFGA_SYSTEM_BUSY }, + { FPCFGA_OPNOTSUPP, CFGA_OPNOTSUPP }, + { FPCFGA_PRIV, CFGA_PRIV }, + { FPCFGA_UNKNOWN_ERR, CFGA_ERROR }, + { FPCFGA_ERR, CFGA_ERROR } +}; + +#define N_ERR_CVT_TBL (sizeof (err_cvt_tbl)/sizeof (err_cvt_tbl[0])) + +#define DEV_OP 0 +#define BUS_OP 1 +static set_state_cmd_t set_state_cmds[] = { + +{ FPCFGA_BUS_QUIESCE, BUS_OP, devctl_bus_quiesce }, +{ FPCFGA_BUS_UNQUIESCE, BUS_OP, devctl_bus_unquiesce }, +{ FPCFGA_BUS_CONFIGURE, BUS_OP, devctl_bus_configure }, +{ FPCFGA_BUS_UNCONFIGURE, BUS_OP, devctl_bus_unconfigure }, +{ FPCFGA_RESET_BUS, BUS_OP, devctl_bus_reset }, +{ FPCFGA_RESET_ALL, BUS_OP, devctl_bus_resetall }, +{ FPCFGA_DEV_CONFIGURE, DEV_OP, devctl_device_online }, +{ FPCFGA_DEV_UNCONFIGURE, DEV_OP, devctl_device_offline }, +{ FPCFGA_DEV_REMOVE, DEV_OP, devctl_device_remove }, +{ FPCFGA_RESET_DEV, DEV_OP, devctl_device_reset } + +}; + +#define N_SET_STATE_CMDS (sizeof (set_state_cmds)/sizeof (set_state_cmds[0])) + +static get_state_cmd_t get_state_cmds[] = { +{ FPCFGA_BUS_GETSTATE, BUS_OP, devctl_bus_getstate }, +{ FPCFGA_DEV_GETSTATE, DEV_OP, devctl_device_getstate } +}; + +#define N_GET_STATE_CMDS (sizeof (get_state_cmds)/sizeof (get_state_cmds[0])) + +/* Order is important. Earlier directories are searched first */ +static const char *dev_dir_hints[] = { + CFGA_DEV_DIR, + DEV_RMT, + DEV_DSK, + DEV_RDSK, + DEV_DIR +}; + +#define N_DEV_DIR_HINTS (sizeof (dev_dir_hints) / sizeof (dev_dir_hints[0])) + + +/* + * Routine to search the /dev directory or a subtree of /dev. + * If the entire /dev hierarchy is to be searched, the most likely directories + * are searched first. + */ +fpcfga_ret_t +recurse_dev( + const char *basedir, + void *arg, + fpcfga_recur_t (*fcn)(const char *lpath, void *arg)) +{ + int i, rv = NFTW_ERROR; + + (void) mutex_lock(&nftw_arg.mp); + + nftw_arg.arg = arg; + nftw_arg.fcn = fcn; + + if (strcmp(basedir, DEV_DIR)) { + errno = 0; + rv = nftw(basedir, do_recurse_dev, NFTW_DEPTH, FTW_PHYS); + goto out; + } + + /* + * Search certain selected subdirectories first if basedir == "/dev". + * Ignore errors as some of these directories may not exist. + */ + for (i = 0; i < N_DEV_DIR_HINTS; i++) { + errno = 0; + if ((rv = nftw(dev_dir_hints[i], do_recurse_dev, NFTW_DEPTH, + FTW_PHYS)) == NFTW_TERMINATE) { + break; + } + } + + /*FALLTHRU*/ +out: + (void) mutex_unlock(&nftw_arg.mp); + return (rv == NFTW_ERROR ? FPCFGA_ERR : FPCFGA_OK); +} + +/*ARGSUSED*/ +static int +do_recurse_dev( + const char *path, + const struct stat *sbuf, + int type, + struct FTW *ftwp) +{ + /* We want only VALID symlinks */ + if (type != FTW_SL) { + return (NFTW_CONTINUE); + } + + assert(nftw_arg.fcn != NULL); + + if (nftw_arg.fcn(path, nftw_arg.arg) == FPCFGA_TERMINATE) { + /* terminate prematurely, but may not be error */ + errno = 0; + return (NFTW_TERMINATE); + } else { + return (NFTW_CONTINUE); + } +} + +cfga_err_t +err_cvt(fpcfga_ret_t fp_err) +{ + int i; + + for (i = 0; i < N_ERR_CVT_TBL; i++) { + if (err_cvt_tbl[i].fp_err == fp_err) { + return (err_cvt_tbl[i].cfga_err); + } + } + + return (CFGA_ERROR); +} + +/* + * Removes duplicate slashes from a pathname and any trailing slashes. + * Returns "/" if input is "/" + */ +char * +pathdup(const char *path, int *l_errnop) +{ + int prev_was_slash = 0; + char c, *dp = NULL, *dup = NULL; + const char *sp = NULL; + + *l_errnop = 0; + + if (path == NULL) { + return (NULL); + } + + if ((dup = calloc(1, strlen(path) + 1)) == NULL) { + *l_errnop = errno; + return (NULL); + } + + prev_was_slash = 0; + for (sp = path, dp = dup; (c = *sp) != '\0'; sp++) { + if (!prev_was_slash || c != '/') { + *dp++ = c; + } + if (c == '/') { + prev_was_slash = 1; + } else { + prev_was_slash = 0; + } + } + + /* Remove trailing slash except if it is the first char */ + if (prev_was_slash && dp != dup && dp - 1 != dup) { + *(--dp) = '\0'; + } else { + *dp = '\0'; + } + + return (dup); +} + +fpcfga_ret_t +apidt_create(const char *ap_id, apid_t *apidp, char **errstring) +{ + char *xport_phys = NULL, *dyn = NULL; + char *dyncomp = NULL; + struct luninfo_list *lunlistp = NULL; + int l_errno = 0; + size_t len = 0; + fpcfga_ret_t ret; + + if ((xport_phys = pathdup(ap_id, &l_errno)) == NULL) { + cfga_err(errstring, l_errno, ERR_OP_FAILED, 0); + return (FPCFGA_LIB_ERR); + } + + /* Extract the base(hba) and dynamic(device) component if any */ + dyncomp = NULL; + if ((dyn = GET_DYN(xport_phys)) != NULL) { + len = strlen(DYN_TO_DYNCOMP(dyn)) + 1; + dyncomp = calloc(1, len); + if (dyncomp == NULL) { + cfga_err(errstring, errno, ERR_OP_FAILED, 0); + ret = FPCFGA_LIB_ERR; + goto err; + } + (void) strcpy(dyncomp, DYN_TO_DYNCOMP(dyn)); + if (GET_LUN_DYN(dyncomp)) { + ret = FPCFGA_APID_NOEXIST; + goto err; + } + + /* Remove the dynamic component from the base. */ + *dyn = '\0'; + } + + /* Get the path of dynamic attachment point if already configured. */ + if (dyncomp != NULL) { + ret = dyn_apid_to_path(xport_phys, dyncomp, + &lunlistp, &l_errno); + if ((ret != FPCFGA_OK) && (ret != FPCFGA_APID_NOCONFIGURE)) { + cfga_err(errstring, l_errno, ERR_OP_FAILED, 0); + goto err; + } + } + + assert(xport_phys != NULL); + + apidp->xport_phys = xport_phys; + apidp->dyncomp = dyncomp; + apidp->lunlist = lunlistp; + apidp->flags = 0; + + return (FPCFGA_OK); + +err: + S_FREE(xport_phys); + S_FREE(dyncomp); + lunlist_free(lunlistp); + return (ret); +} + +static void +lunlist_free(struct luninfo_list *lunlist) +{ +struct luninfo_list *lunp; + + while (lunlist != NULL) { + lunp = lunlist->next; + S_FREE(lunlist->path); + S_FREE(lunlist); + lunlist = lunp; + } +} + +void +apidt_free(apid_t *apidp) +{ + if (apidp == NULL) + return; + + S_FREE(apidp->xport_phys); + S_FREE(apidp->dyncomp); + lunlist_free(apidp->lunlist); +} + +fpcfga_ret_t +walk_tree( + const char *physpath, + void *arg, + uint_t init_flags, + walkarg_t *up, + fpcfga_cmd_t cmd, + int *l_errnop) +{ + int rv; + di_node_t root, tree_root, fpnode; + char *root_path, *cp = NULL; + char *devfs_fp_path; + size_t len; + fpcfga_ret_t ret; + int found = 0; + + *l_errnop = 0; + + if ((root_path = strdup(physpath)) == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + /* Fix up path for di_init() */ + len = strlen(DEVICES_DIR); + if (strncmp(root_path, DEVICES_DIR SLASH, + len + strlen(SLASH)) == 0) { + cp = root_path + len; + (void) memmove(root_path, cp, strlen(cp) + 1); + } else if (*root_path != '/') { + *l_errnop = 0; + ret = FPCFGA_ERR; + goto out; + } + + /* Remove dynamic component if any */ + if ((cp = GET_DYN(root_path)) != NULL) { + *cp = '\0'; + } + + /* Remove minor name if any */ + if ((cp = strrchr(root_path, ':')) != NULL) { + *cp = '\0'; + } + + /* + * If force_flag is set + * do di_init with DINFOFORCE flag and get to the input fp node + * from the device tree. + * + * In order to get the link between path_info node and scsi_vhci node + * it is required to take the snapshot of the whole device tree. + * this behavior of libdevinfo is inefficient. For a specific + * fca port DINFOPROP was sufficient on the fca path prior to + * scsi_vhci node support. + * + */ + if ((up->flags & FLAG_DEVINFO_FORCE) == FLAG_DEVINFO_FORCE) { + tree_root = di_init("/", init_flags | DINFOFORCE); + } else { + tree_root = di_init("/", init_flags); + } + + if (tree_root == DI_NODE_NIL) { + *l_errnop = errno; + ret = FPCFGA_LIB_ERR; + goto out; + } + + fpnode = di_drv_first_node("fp", tree_root); + + while (fpnode) { + devfs_fp_path = di_devfs_path(fpnode); + if ((devfs_fp_path) && !(strncmp(devfs_fp_path, + root_path, strlen(root_path)))) { + found = 1; + di_devfs_path_free(devfs_fp_path); + break; + } + di_devfs_path_free(devfs_fp_path); + fpnode = di_drv_next_node(fpnode); + } + if (!(found)) { + ret = FPCFGA_LIB_ERR; + goto out; + } else { + root = fpnode; + } + + /* Walk the tree */ + errno = 0; + if (cmd == FPCFGA_WALK_NODE) { + rv = di_walk_node(root, up->walkmode.node_args.flags, arg, + up->walkmode.node_args.fcn); + } else { + assert(cmd == FPCFGA_WALK_MINOR); + rv = di_walk_minor(root, up->walkmode.minor_args.nodetype, 0, + arg, up->walkmode.minor_args.fcn); + } + + if (rv != 0) { + *l_errnop = errno; + ret = FPCFGA_LIB_ERR; + } else { + if ((up->flags & FLAG_PATH_INFO_WALK) == FLAG_PATH_INFO_WALK) { + ret = stat_path_info_node(root, arg, l_errnop); + } else { + *l_errnop = 0; + ret = FPCFGA_OK; + } + } + + di_fini(tree_root); + + /*FALLTHRU*/ +out: + S_FREE(root_path); + return (ret); +} + + +int +msg_idx(msgid_t msgid) +{ + int idx = 0; + + /* The string table index and the error id may or may not be same */ + if (msgid >= 0 && msgid <= N_STRS - 1 && + str_tbl[msgid].msgid == msgid) { + idx = msgid; + } else { + for (idx = 0; idx < N_STRS; idx++) { + if (str_tbl[idx].msgid == msgid) + break; + } + if (idx >= N_STRS) { + idx = UNKNOWN_ERR_IDX; + } + } + + return (idx); +} + +/* + * cfga_err() accepts a variable number of message IDs and constructs + * a corresponding error string which is returned via the errstring argument. + * cfga_err() calls dgettext() to internationalize proper messages. + * May be called with a NULL argument. + */ +void +cfga_err(char **errstring, int l_errno, ...) +{ + va_list ap; + int append_newline = 0; + char *tmp_str, *tmp_err_str = NULL; + + if (errstring == NULL) { + return; + } + + /* + * Don't append a newline, the application (for example cfgadm) + * should do that. + */ + append_newline = 0; + + va_start(ap, l_errno); + msg_common(&tmp_err_str, append_newline, l_errno, ap); + va_end(ap); + + if (*errstring == NULL) { + *errstring = tmp_err_str; + return; + } + + /* + * *errstring != NULL + * There was something in errstring prior to this call. + * So, concatenate the old and new strings + */ + if ((tmp_str = calloc(1, + strlen(*errstring) + strlen(tmp_err_str) + 2)) == NULL) { + /* In case of error, retain only the earlier message */ + free(tmp_err_str); + return; + } + + sprintf(tmp_str, "%s\n%s", *errstring, tmp_err_str); + free(tmp_err_str); + free(*errstring); + *errstring = tmp_str; +} + +/* + * This routine accepts a variable number of message IDs and constructs + * a corresponding message string which is printed via the message print + * routine argument. + */ +void +cfga_msg(struct cfga_msg *msgp, ...) +{ + char *p = NULL; + int append_newline = 0, l_errno = 0; + va_list ap; + + if (msgp == NULL || msgp->message_routine == NULL) { + return; + } + + /* Append a newline after message */ + append_newline = 1; + l_errno = 0; + + va_start(ap, msgp); + msg_common(&p, append_newline, l_errno, ap); + va_end(ap); + + (void) (*msgp->message_routine)(msgp->appdata_ptr, p); + + S_FREE(p); +} + +/* + * Get internationalized string corresponding to message id + * Caller must free the memory allocated. + */ +char * +cfga_str(int append_newline, ...) +{ + char *p = NULL; + int l_errno = 0; + va_list ap; + + va_start(ap, append_newline); + msg_common(&p, append_newline, l_errno, ap); + va_end(ap); + + return (p); +} + +static void +msg_common(char **msgpp, int append_newline, int l_errno, va_list ap) +{ + int a = 0; + size_t len = 0; + int i = 0, n = 0; + char *s = NULL, *t = NULL; + strlist_t dummy; + strlist_t *savep = NULL, *sp = NULL, *tailp = NULL; + + if (*msgpp != NULL) { + return; + } + + dummy.next = NULL; + tailp = &dummy; + for (len = 0; (a = va_arg(ap, int)) != 0; ) { + n = GET_MSG_NARGS(a); /* 0 implies no additional args */ + for (i = 0; i <= n; i++) { + sp = calloc(1, sizeof (*sp)); + if (sp == NULL) { + goto out; + } + if (i == 0 && GET_MSG_INTL(a)) { + sp->str = dgettext(TEXT_DOMAIN, GET_MSG_STR(a)); + } else if (i == 0) { + sp->str = GET_MSG_STR(a); + } else { + sp->str = va_arg(ap, char *); + } + len += (strlen(sp->str)); + sp->next = NULL; + tailp->next = sp; + tailp = sp; + } + } + + len += 1; /* terminating NULL */ + + s = t = NULL; + if (l_errno) { + s = dgettext(TEXT_DOMAIN, ": "); + t = S_STR(strerror(l_errno)); + if (s != NULL && t != NULL) { + len += strlen(s) + strlen(t); + } + } + + if (append_newline) { + len++; + } + + if ((*msgpp = calloc(1, len)) == NULL) { + goto out; + } + + **msgpp = '\0'; + for (sp = dummy.next; sp != NULL; sp = sp->next) { + (void) strcat(*msgpp, sp->str); + } + + if (s != NULL && t != NULL) { + (void) strcat(*msgpp, s); + (void) strcat(*msgpp, t); + } + + if (append_newline) { + (void) strcat(*msgpp, dgettext(TEXT_DOMAIN, "\n")); + } + + /* FALLTHROUGH */ +out: + sp = dummy.next; + while (sp != NULL) { + savep = sp->next; + S_FREE(sp); + sp = savep; + } +} + +fpcfga_ret_t +devctl_cmd( + const char *physpath, + fpcfga_cmd_t cmd, + uint_t *statep, + int *l_errnop) +{ + int rv = -1, i, type; + devctl_hdl_t hdl = NULL; + char *cp = NULL, *path = NULL; + int (*func)(const devctl_hdl_t); + int (*state_func)(const devctl_hdl_t, uint_t *); + + *l_errnop = 0; + + if (statep != NULL) *statep = 0; + + func = NULL; + state_func = NULL; + type = 0; + + for (i = 0; i < N_GET_STATE_CMDS; i++) { + if (get_state_cmds[i].cmd == cmd) { + state_func = get_state_cmds[i].state_fcn; + type = get_state_cmds[i].type; + assert(statep != NULL); + break; + } + } + + if (state_func == NULL) { + for (i = 0; i < N_SET_STATE_CMDS; i++) { + if (set_state_cmds[i].cmd == cmd) { + func = set_state_cmds[i].fcn; + type = set_state_cmds[i].type; + assert(statep == NULL); + break; + } + } + } + + assert(type == BUS_OP || type == DEV_OP); + + if (func == NULL && state_func == NULL) { + return (FPCFGA_ERR); + } + + /* + * Fix up path for calling devctl. + */ + if ((path = strdup(physpath)) == NULL) { + *l_errnop = errno; + return (FPCFGA_LIB_ERR); + } + + /* Remove dynamic component if any */ + if ((cp = GET_DYN(path)) != NULL) { + *cp = '\0'; + } + + /* Remove minor name */ + if ((cp = strrchr(path, ':')) != NULL) { + *cp = '\0'; + } + + errno = 0; + + if (type == BUS_OP) { + hdl = devctl_bus_acquire(path, 0); + } else { + hdl = devctl_device_acquire(path, 0); + } + *l_errnop = errno; + + S_FREE(path); + + if (hdl == NULL) { + return (FPCFGA_ERR); + } + + errno = 0; + /* Only getstate functions require a second argument */ + if (func != NULL && statep == NULL) { + rv = func(hdl); + *l_errnop = errno; + } else if (state_func != NULL && statep != NULL) { + rv = state_func(hdl, statep); + *l_errnop = errno; + } else { + rv = -1; + *l_errnop = 0; + } + + devctl_release(hdl); + + return ((rv == -1) ? FPCFGA_ERR : FPCFGA_OK); +} + +/* + * Is device in a known state ? (One of BUSY, ONLINE, OFFLINE) + * BUSY --> One or more device special files are open. Implies online + * ONLINE --> driver attached + * OFFLINE --> CF1 with offline flag set. + * UNKNOWN --> None of the above + */ +int +known_state(di_node_t node) +{ + uint_t state; + + state = di_state(node); + + /* + * CF1 without offline flag set is considered unknown state. + * We are in a known state if either CF2 (driver attached) or + * offline. + */ + if ((state & DI_DEVICE_OFFLINE) == DI_DEVICE_OFFLINE || + (state & DI_DRIVER_DETACHED) != DI_DRIVER_DETACHED) { + return (1); + } + + return (0); +} + +void +list_free(ldata_list_t **llpp) +{ + ldata_list_t *lp, *olp; + + lp = *llpp; + while (lp != NULL) { + olp = lp; + lp = olp->next; + S_FREE(olp); + } + + *llpp = NULL; +} + +/* + * Obtain the devlink from a /devices path + */ +fpcfga_ret_t +physpath_to_devlink( + const char *basedir, + char *xport_phys, + char **xport_logpp, + int *l_errnop, + int match_minor) +{ + pathm_t pmt = {NULL}; + fpcfga_ret_t ret; + + pmt.phys = xport_phys; + pmt.ret = FPCFGA_NO_REC; + pmt.match_minor = match_minor; + + /* + * Search the /dev hierarchy starting at basedir. + */ + ret = recurse_dev(basedir, &pmt, lookup_dev); + if (ret == FPCFGA_OK && (ret = pmt.ret) == FPCFGA_OK) { + assert(pmt.log != NULL); + *xport_logpp = pmt.log; + } else { + if (pmt.log != NULL) { + S_FREE(pmt.log); + } + + *xport_logpp = NULL; + *l_errnop = pmt.l_errno; + } + + return (ret); +} + +static fpcfga_recur_t +lookup_dev(const char *lpath, void *arg) +{ + char ppath[PATH_MAX]; + pathm_t *pmtp = (pathm_t *)arg; + + if (realpath(lpath, ppath) == NULL) { + return (FPCFGA_CONTINUE); + } + + ppath[sizeof (ppath) - 1] = '\0'; + + /* Is this the physical path we are looking for */ + if (dev_cmp(ppath, pmtp->phys, pmtp->match_minor)) { + return (FPCFGA_CONTINUE); + } + + if ((pmtp->log = strdup(lpath)) == NULL) { + pmtp->l_errno = errno; + pmtp->ret = FPCFGA_LIB_ERR; + } else { + pmtp->ret = FPCFGA_OK; + } + + return (FPCFGA_TERMINATE); +} + +/* Compare HBA physical ap_id and device path */ +int +hba_dev_cmp(const char *hba, const char *devpath) +{ + char *cp = NULL; + int rv; + size_t hba_len, dev_len; + char l_hba[MAXPATHLEN], l_dev[MAXPATHLEN]; + + (void) snprintf(l_hba, sizeof (l_hba), "%s", hba); + (void) snprintf(l_dev, sizeof (l_dev), "%s", devpath); + + /* Remove dynamic component if any */ + if ((cp = GET_DYN(l_hba)) != NULL) { + *cp = '\0'; + } + + if ((cp = GET_DYN(l_dev)) != NULL) { + *cp = '\0'; + } + + + /* Remove minor names */ + if ((cp = strrchr(l_hba, ':')) != NULL) { + *cp = '\0'; + } + + if ((cp = strrchr(l_dev, ':')) != NULL) { + *cp = '\0'; + } + + hba_len = strlen(l_hba); + dev_len = strlen(l_dev); + + /* Check if HBA path is component of device path */ + if (rv = strncmp(l_hba, l_dev, hba_len)) { + return (rv); + } + + /* devpath must have '/' and 1 char in addition to hba path */ + if (dev_len >= hba_len + 2 && l_dev[hba_len] == '/') { + return (0); + } else { + return (-1); + } +} + +int +dev_cmp(const char *dev1, const char *dev2, int match_minor) +{ + char l_dev1[MAXPATHLEN], l_dev2[MAXPATHLEN]; + char *mn1, *mn2; + int rv; + + (void) snprintf(l_dev1, sizeof (l_dev1), "%s", dev1); + (void) snprintf(l_dev2, sizeof (l_dev2), "%s", dev2); + + if ((mn1 = GET_DYN(l_dev1)) != NULL) { + *mn1 = '\0'; + } + + if ((mn2 = GET_DYN(l_dev2)) != NULL) { + *mn2 = '\0'; + } + + /* Separate out the minor names */ + if ((mn1 = strrchr(l_dev1, ':')) != NULL) { + *mn1++ = '\0'; + } + + if ((mn2 = strrchr(l_dev2, ':')) != NULL) { + *mn2++ = '\0'; + } + + if ((rv = strcmp(l_dev1, l_dev2)) != 0 || !match_minor) { + return (rv); + } + + /* + * Compare minor names + */ + if (mn1 == NULL && mn2 == NULL) { + return (0); + } else if (mn1 == NULL) { + return (-1); + } else if (mn2 == NULL) { + return (1); + } else { + return (strcmp(mn1, mn2)); + } +} + +/* + * Returns non-zero on failure (aka, HBA_STATUS_ERROR_* + * Will handle retries if applicable. + */ +int +getAdapterAttrs(HBA_HANDLE handle, HBA_ADAPTERATTRIBUTES *attrs) +{ + int count = 0; + HBA_STATUS status = HBA_STATUS_ERROR_TRY_AGAIN; /* force first pass */ + + /* Loop as long as we have a retryable error */ + while ((status == HBA_STATUS_ERROR_TRY_AGAIN || + status == HBA_STATUS_ERROR_BUSY) && + count++ < HBA_MAX_RETRIES) { + status = HBA_GetAdapterAttributes(handle, attrs); + if (status == HBA_STATUS_OK) { + break; + } + sleep(1); + } + return (status); +} + +/* + * Returns non-zero on failure (aka, HBA_STATUS_ERROR_* + * Will handle retries if applicable. + */ +int +getPortAttrsByWWN(HBA_HANDLE handle, HBA_WWN wwn, HBA_PORTATTRIBUTES *attrs) +{ + int count = 0; + HBA_STATUS status = HBA_STATUS_ERROR_TRY_AGAIN; /* force first pass */ + + /* Loop as long as we have a retryable error */ + while ((status == HBA_STATUS_ERROR_TRY_AGAIN || + status == HBA_STATUS_ERROR_BUSY) && + count++ < HBA_MAX_RETRIES) { + status = HBA_GetPortAttributesByWWN(handle, wwn, attrs); + if (status == HBA_STATUS_OK) { + break; + } + + /* The odds of this occuring are very slim, but possible. */ + if (status == HBA_STATUS_ERROR_STALE_DATA) { + /* + * If we hit a stale data scenario, + * we'll just tell the user to try again. + */ + status = HBA_STATUS_ERROR_TRY_AGAIN; + break; + } + sleep(1); + } + return (status); +} + +/* + * Returns non-zero on failure (aka, HBA_STATUS_ERROR_* + * Will handle retries if applicable. + */ +int +getAdapterPortAttrs(HBA_HANDLE handle, int portIndex, + HBA_PORTATTRIBUTES *attrs) +{ + int count = 0; + HBA_STATUS status = HBA_STATUS_ERROR_TRY_AGAIN; /* force first pass */ + + /* Loop as long as we have a retryable error */ + while ((status == HBA_STATUS_ERROR_TRY_AGAIN || + status == HBA_STATUS_ERROR_BUSY) && + count++ < HBA_MAX_RETRIES) { + status = HBA_GetAdapterPortAttributes(handle, portIndex, attrs); + if (status == HBA_STATUS_OK) { + break; + } + + /* The odds of this occuring are very slim, but possible. */ + if (status == HBA_STATUS_ERROR_STALE_DATA) { + /* + * If we hit a stale data scenario, + * we'll just tell the user to try again. + */ + status = HBA_STATUS_ERROR_TRY_AGAIN; + break; + } + sleep(1); + } + return (status); +} + +/* + * Returns non-zero on failure (aka, HBA_STATUS_ERROR_* + * Will handle retries if applicable. + */ +int +getDiscPortAttrs(HBA_HANDLE handle, int portIndex, int discIndex, + HBA_PORTATTRIBUTES *attrs) +{ + int count = 0; + HBA_STATUS status = HBA_STATUS_ERROR_TRY_AGAIN; /* force first pass */ + + /* Loop as long as we have a retryable error */ + while ((status == HBA_STATUS_ERROR_TRY_AGAIN || + status == HBA_STATUS_ERROR_BUSY) && + count++ < HBA_MAX_RETRIES) { + status = HBA_GetDiscoveredPortAttributes(handle, portIndex, + discIndex, attrs); + if (status == HBA_STATUS_OK) { + break; + } + + /* The odds of this occuring are very slim, but possible. */ + if (status == HBA_STATUS_ERROR_STALE_DATA) { + /* + * If we hit a stale data scenario, we'll just tell the + * user to try again. + */ + status = HBA_STATUS_ERROR_TRY_AGAIN; + break; + } + sleep(1); + } + return (status); +} + +/* + * Find the Adapter port that matches the portPath. + * When the matching port is found the caller have to close handle + * and free library. + */ +fpcfga_ret_t +findMatchingAdapterPort(char *portPath, HBA_HANDLE *matchingHandle, + int *matchingPortIndex, HBA_PORTATTRIBUTES *matchingPortAttrs, + char **errstring) +{ + HBA_HANDLE handle; + HBA_ADAPTERATTRIBUTES hbaAttrs; + HBA_PORTATTRIBUTES portAttrs; + HBA_STATUS status = HBA_STATUS_OK; + int count, retry = 0, l_errno = 0; + int adapterIndex, portIndex; + char adapterName[256]; + char *cfg_ptr, *tmpPtr; + char *logical_apid = NULL; + + status = HBA_LoadLibrary(); + if (status != HBA_STATUS_OK) { + cfga_err(errstring, 0, ERR_HBA_LOAD_LIBRARY, 0); + return (FPCFGA_LIB_ERR); + } + count = HBA_GetNumberOfAdapters(); + if (count == 0) { + cfga_err(errstring, 0, ERR_NO_ADAPTER_FOUND, 0); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + + /* Loop over all HBAs */ + for (adapterIndex = 0; adapterIndex < count; adapterIndex ++) { + status = HBA_GetAdapterName(adapterIndex, (char *)&adapterName); + if (status != HBA_STATUS_OK) { + /* May have been DR'd */ + continue; + } + handle = HBA_OpenAdapter(adapterName); + if (handle == 0) { + /* May have been DR'd */ + continue; + } + + do { + if (getAdapterAttrs(handle, &hbaAttrs)) { + /* Should never happen */ + HBA_CloseAdapter(handle); + continue; + } + + /* Loop over all HBA Ports */ + for (portIndex = 0; + portIndex < hbaAttrs.NumberOfPorts; portIndex++) { + if ((status = getAdapterPortAttrs(handle, portIndex, + &portAttrs)) != HBA_STATUS_OK) { + /* Need to refresh adapter */ + if (status == HBA_STATUS_ERROR_STALE_DATA) { + HBA_RefreshInformation(handle); + break; + } else { + continue; + } + } + + /* + * check to see if OSDeviceName is a /dev/cfg link + * or the physical path + */ + if (strncmp(portAttrs.OSDeviceName, CFGA_DEV_DIR, + strlen(CFGA_DEV_DIR)) != 0) { + tmpPtr = strstr(portAttrs.OSDeviceName, MINOR_SEP); + if (tmpPtr != NULL) { + if (strncmp(portPath, + portAttrs.OSDeviceName, + strlen(portAttrs.OSDeviceName) - + strlen(tmpPtr)) == 0) { + if (matchingHandle) + *matchingHandle = handle; + if (matchingPortIndex) + *matchingPortIndex = portIndex; + if (matchingPortAttrs) + *matchingPortAttrs = portAttrs; + return (FPCFGA_OK); + } + } + } else { + /* + * strip off the /dev/cfg/ portion of the + * OSDeviceName + * make sure that the OSDeviceName is at least + * strlen("/dev/cfg") + 1 + 1 long. + * first 1 is for the / after /dev/cfg + * second 1 is to make sure there is somthing + * after + */ + if (strlen(portAttrs.OSDeviceName) < + (strlen(CFGA_DEV_DIR) + 1 + 1)) + continue; + cfg_ptr = portAttrs.OSDeviceName + + strlen(CFGA_DEV_DIR) + 1; + if (logical_apid == NULL) { + /* get the /dev/cfg link from the portPath */ + if (make_xport_logid(portPath, &logical_apid, + &l_errno) != FPCFGA_OK) { + cfga_err(errstring, l_errno, + ERR_LIST, 0); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); + } + } + /* compare logical ap_id */ + if (strcmp(logical_apid, cfg_ptr) == 0) { + if (matchingHandle) + *matchingHandle = handle; + if (matchingPortIndex) + *matchingPortIndex = portIndex; + if (matchingPortAttrs) + *matchingPortAttrs = portAttrs; + S_FREE(logical_apid); + return (FPCFGA_OK); + } + } + } + if (logical_apid != NULL) + S_FREE(logical_apid); + } while ((status == HBA_STATUS_ERROR_STALE_DATA) && + (retry++ < HBA_MAX_RETRIES)); + + HBA_CloseAdapter(handle); + } + free(logical_apid); + + /* Got here. No mathcing adatper port found. */ + cfga_err(errstring, 0, ERR_MATCHING_HBA_PORT, 0); + HBA_FreeLibrary(); + return (FPCFGA_LIB_ERR); +} diff --git a/usr/src/lib/cfgadm_plugins/fp/common/devices-fc-fabric.xml b/usr/src/lib/cfgadm_plugins/fp/common/devices-fc-fabric.xml new file mode 100644 index 0000000000..4d9b91031f --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/devices-fc-fabric.xml @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> +<!-- + +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 2008 Sun Microsystems, Inc. All rights reserved. +Use is subject to license terms. + + +--> + +<service_bundle type='manifest' name='devices-fc-fabric'> + +<service + name='system/device/fc-fabric' + type='service' + version='1'> + + <create_default_instance enabled='true' /> + + <single_instance/> + + <dependency + name='sysevent' + grouping='require_all' + restart_on='none' + type='service'> + <service_fmri value='svc:/system/sysevent' /> + </dependency> + + <dependent + name='devices' + grouping='require_all' + restart_on='none'> + <service_fmri value='svc:/milestone/devices' /> + </dependent> + + <exec_method + type='method' + name='start' + exec='/lib/svc/method/fc-fabric' + timeout_seconds='600' /> + + <exec_method + type='method' + name='stop' + exec=':true' + timeout_seconds='3' /> + + <property_group name='startd' type='framework'> + <propval name='duration' type='astring' + value='transient' /> + </property_group> + + <stability value='Unstable' /> + + <template> + <common_name> + <loctext xml:lang='C'> + Solaris FC fabric device configuration. + </loctext> + </common_name> + <documentation> + <manpage title='cfgadm_fp' section='1M' + manpath='/usr/share/man' /> + </documentation> + </template> +</service> + +</service_bundle> + diff --git a/usr/src/lib/cfgadm_plugins/fp/common/fc-fabric b/usr/src/lib/cfgadm_plugins/fp/common/fc-fabric new file mode 100644 index 0000000000..3b0a475563 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/fc-fabric @@ -0,0 +1,45 @@ +#!/sbin/sh +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This script is used to reconfigure fabric device(s) which were configured +# prior to boot. The luxadm command used below is an expert level command +# and has a restrictive usage for boot time reconfiguration of fabric +# devices. +# +# DO NOT USE the command for any other purpose. +# + +if [ -s /etc/cfg/fp/fabric_WWN_map ] +then + /usr/sbin/luxadm -e create_fabric_device \ + -f /etc/cfg/fp/fabric_WWN_map +else + if [ -s /etc/cfg/fp/fabric_WWN_map.old ] + then + /usr/sbin/luxadm -e create_fabric_device \ + -f /etc/cfg/fp/fabric_WWN_map.old + fi +fi diff --git a/usr/src/lib/cfgadm_plugins/fp/common/mapfile-vers b/usr/src/lib/cfgadm_plugins/fp/common/mapfile-vers new file mode 100644 index 0000000000..4dd3e93023 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/common/mapfile-vers @@ -0,0 +1,39 @@ +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +SUNWprivate_1.1 { + global: + cfga_ap_id_cmp; + cfga_change_state; + cfga_help; + cfga_list_ext; + cfga_private_func; + cfga_test; + cfga_version; + + local: + *; +}; diff --git a/usr/src/lib/cfgadm_plugins/fp/fp.xcl b/usr/src/lib/cfgadm_plugins/fp/fp.xcl new file mode 100644 index 0000000000..6748a7325d --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/fp.xcl @@ -0,0 +1,47 @@ +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# lib/cfgadm_plugins/fp/fp.xcl +# +msgid "%s%s" +msgid "\n" +msgid " " +msgid " " +msgid "-" +msgid "%%-%ds %%-%ds" +msgid "insert_device" +msgid "remove_device" +msgid "replace_device" +msgid "reset_device" +msgid "reset_bus" +msgid "reset_all" +msgid "\t-c configure -o force_update ap_id [ap_id..]\n" + "\t-c configure -o no_update ap_id [ap_id...]\n" + "\t-c unconfigure -o force_update ap_id [ap_id... ]\n" + "\t-c unconfigure -o no_update ap_id [ap_id... ]\n" +msgid "/" +msgid "fp" +msgid ": " +msgid "%s" diff --git a/usr/src/lib/cfgadm_plugins/fp/i386/Makefile b/usr/src/lib/cfgadm_plugins/fp/i386/Makefile new file mode 100644 index 0000000000..de6709e39c --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +.KEEP_STATE: + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTMANIFEST) $(ROOTSVCMETHOD) diff --git a/usr/src/lib/cfgadm_plugins/fp/sparc/Makefile b/usr/src/lib/cfgadm_plugins/fp/sparc/Makefile new file mode 100644 index 0000000000..272e508d0f --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/sparc/Makefile @@ -0,0 +1,31 @@ +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +include ../Makefile.com + +.KEEP_STATE: + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTMANIFEST) $(ROOTSVCMETHOD) diff --git a/usr/src/lib/cfgadm_plugins/fp/sparcv9/Makefile b/usr/src/lib/cfgadm_plugins/fp/sparcv9/Makefile new file mode 100644 index 0000000000..23dfadaca8 --- /dev/null +++ b/usr/src/lib/cfgadm_plugins/fp/sparcv9/Makefile @@ -0,0 +1,33 @@ +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + + +.KEEP_STATE: + +install: all $(ROOTLIBS64) $(ROOTLINKS64) |
