/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "cfga_scsi.h" struct larg { int ndevs; int nelem; char *dev; char **dev_list; }; #define ETC_VFSTAB "/etc/vfstab" #define SCFGA_LOCK "/var/run/cfgadm_scsi" /* Function prototypes */ static scfga_ret_t quiesce_confirm(apid_t *apidp, msgid_t cmd_msg, prompt_t *pt, int *okp, int *quiesce, int *l_errnop); static scfga_ret_t dev_hotplug(apid_t *apidp, prompt_t *pt, cfga_flags_t flags, int quiesce, char **errstring); static int disconnect(struct cfga_confirm *confp); static int critical_ctrlr(const char *hba_phys); static cfga_stat_t bus_devctl_to_recep_state(uint_t bus_dc_state); static int get_hba_children(char *bus_path, char *dev_excl, char ***dev_list); static char *get_node_path(char *minor_path); static void free_dev_list_elements(char **dev_list); static void free_dev_list(char **dev_list); static int alloc_dev_list(struct larg *largp); /* * Single thread all implicit quiesce operations */ static mutex_t quiesce_mutex = DEFAULTMUTEX; /*ARGSUSED*/ scfga_ret_t bus_change_state( cfga_cmd_t state_change_cmd, apid_t *apidp, struct cfga_confirm *confp, cfga_flags_t flags, char **errstring) { int l_errno = 0, force; uint_t state = 0; cfga_stat_t bus_state; scfga_cmd_t cmd; msgid_t errid; cfga_stat_t prereq; scfga_ret_t ret; char **dev_list = NULL; assert(apidp->path != NULL); assert(apidp->hba_phys != NULL); /* * No dynamic components allowed */ if (apidp->dyncomp != NULL) { cfga_err(errstring, 0, ERR_NOT_BUSAPID, 0); return (SCFGA_ERR); } /* Get bus state */ if (devctl_cmd(apidp->path, SCFGA_BUS_GETSTATE, &state, &l_errno) != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_BUS_GETSTATE, 0); return (SCFGA_ERR); } bus_state = bus_devctl_to_recep_state(state); force = ((flags & CFGA_FLAG_FORCE) == CFGA_FLAG_FORCE) ? 1 : 0; assert(confp->confirm != NULL); switch (state_change_cmd) { case CFGA_CMD_DISCONNECT: /* quiesce bus */ /* * If force flag not specified, check if controller is * critical. */ if (!force) { /* * This check is not foolproof, get user confirmation * if test passes. */ if (critical_ctrlr(apidp->path)) { cfga_err(errstring, 0, ERR_CTRLR_CRIT, 0); ret = SCFGA_ERR; break; } else if (!disconnect(confp)) { ret = SCFGA_NACK; break; } } cmd = SCFGA_BUS_QUIESCE; errid = ERR_BUS_QUIESCE; prereq = CFGA_STAT_CONNECTED; goto common; case CFGA_CMD_CONNECT: /* unquiesce bus */ cmd = SCFGA_BUS_UNQUIESCE; errid = ERR_BUS_UNQUIESCE; prereq = CFGA_STAT_DISCONNECTED; goto common; case CFGA_CMD_CONFIGURE: cmd = SCFGA_BUS_CONFIGURE; errid = ERR_BUS_CONFIGURE; prereq = CFGA_STAT_CONNECTED; goto common; case CFGA_CMD_UNCONFIGURE: cmd = SCFGA_BUS_UNCONFIGURE; errid = ERR_BUS_UNCONFIGURE; prereq = CFGA_STAT_CONNECTED; /* FALLTHROUGH */ common: if (bus_state != prereq) { cfga_err(errstring, 0, (prereq == CFGA_STAT_CONNECTED) ? ERR_BUS_NOTCONNECTED : ERR_BUS_CONNECTED, 0); ret = SCFGA_ERR; break; } /* * When quiescing or unconfiguring a bus, first suspend or * offline it through RCM. * For unquiescing, we simple build the dev_list for * resume notification. */ if (((apidp->flags & FLAG_DISABLE_RCM) == 0) && ((cmd == SCFGA_BUS_QUIESCE) || (cmd == SCFGA_BUS_UNQUIESCE) || (cmd == SCFGA_BUS_UNCONFIGURE))) { ret = get_hba_children(apidp->path, NULL, &dev_list); if (ret != SCFGA_OK) { break; } if (cmd == SCFGA_BUS_QUIESCE) { if ((ret = scsi_rcm_suspend(dev_list, errstring, flags, 1)) != SCFGA_OK) { break; } } else if (cmd == SCFGA_BUS_UNCONFIGURE) { if ((ret = scsi_rcm_offline(dev_list, errstring, flags)) != SCFGA_OK) { break; } } } ret = devctl_cmd(apidp->path, cmd, NULL, &l_errno); if (ret != SCFGA_OK) { /* * EIO when child devices are busy may confuse user. * So explain it. */ if (cmd == SCFGA_BUS_UNCONFIGURE && l_errno == EIO) errid = ERR_MAYBE_BUSY; cfga_err(errstring, l_errno, errid, 0); /* * If the bus was suspended in RCM, then cancel the RCM * operation. Discard RCM failures here because the * devctl's failure is what is most relevant. */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { if (cmd == SCFGA_BUS_QUIESCE) (void) scsi_rcm_resume(dev_list, errstring, (flags & (~CFGA_FLAG_FORCE)), 1); else if (cmd == SCFGA_BUS_UNCONFIGURE) { (void) devctl_cmd(apidp->path, SCFGA_BUS_CONFIGURE, NULL, &l_errno); (void) scsi_rcm_online(dev_list, errstring, (flags & (~CFGA_FLAG_FORCE))); } } break; } /* * When unquiescing or configuring a bus, resume or online it * in RCM when the devctl command is complete. * When unconfiguring a bus, notify removal of devices. */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { if (cmd == SCFGA_BUS_UNQUIESCE) { ret = scsi_rcm_resume(dev_list, errstring, (flags & (~CFGA_FLAG_FORCE)), 1); } else if (cmd == SCFGA_BUS_UNCONFIGURE) { ret = scsi_rcm_remove(dev_list, errstring, (flags & (~CFGA_FLAG_FORCE))); } } break; case CFGA_CMD_LOAD: case CFGA_CMD_UNLOAD: ret = SCFGA_OPNOTSUPP; break; default: cfga_err(errstring, 0, ERR_CMD_INVAL, 0); ret = SCFGA_ERR; break; } free_dev_list(dev_list); return (ret); } scfga_ret_t dev_change_state( cfga_cmd_t state_change_cmd, apid_t *apidp, cfga_flags_t flags, char **errstring) { uint_t state = 0; int l_errno = 0; cfga_stat_t bus_state; scfga_cmd_t cmd; msgid_t errid; scfga_ret_t ret; char *dev_list[2] = {NULL}; assert(apidp->path != NULL); assert(apidp->hba_phys != NULL); /* * For a device, dynamic component must be present */ if (apidp->dyncomp == NULL) { cfga_err(errstring, 0, ERR_APID_INVAL, 0); return (SCFGA_ERR); } /* Get bus state */ if (devctl_cmd(apidp->hba_phys, SCFGA_BUS_GETSTATE, &state, &l_errno) != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_BUS_GETSTATE, 0); return (SCFGA_ERR); } bus_state = bus_devctl_to_recep_state(state); switch (state_change_cmd) { case CFGA_CMD_CONFIGURE: /* online device */ cmd = SCFGA_DEV_CONFIGURE; errid = ERR_DEV_CONFIGURE; goto common; case CFGA_CMD_UNCONFIGURE: /* offline device */ cmd = SCFGA_DEV_UNCONFIGURE; errid = ERR_DEV_UNCONFIGURE; /* FALLTHROUGH */ common: if (bus_state != CFGA_STAT_CONNECTED) { cfga_err(errstring, 0, ERR_BUS_NOTCONNECTED, 0); ret = SCFGA_ERR; break; } if (apidp->dyntype == PATH_APID) { /* call a scsi_vhci ioctl to do online/offline path. */ ret = path_apid_state_change(apidp, cmd, flags, errstring, &l_errno, errid); } else { /* * When unconfiguring a device, first offline it * through RCM. */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { if (cmd == SCFGA_DEV_UNCONFIGURE) { dev_list[0] = get_node_path(apidp->path); if (dev_list[0] == NULL) { ret = SCFGA_ERR; break; } if ((ret = scsi_rcm_offline(dev_list, errstring, flags)) != SCFGA_OK) { break; } } } ret = devctl_cmd(apidp->path, cmd, NULL, &l_errno); if (ret != SCFGA_OK) { cfga_err(errstring, l_errno, errid, 0); /* * If an unconfigure fails, cancel the RCM offline. * Discard any RCM failures so that the devctl * failure will still be reported. */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { if (cmd == SCFGA_DEV_UNCONFIGURE) (void) scsi_rcm_online(dev_list, errstring, flags); } break; } if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { /* * Unconfigure succeeded, call the RCM notify_remove. */ if (cmd == SCFGA_DEV_UNCONFIGURE) (void) scsi_rcm_remove(dev_list, errstring, flags); } } break; /* * Cannot disconnect/connect individual devices without affecting * other devices on the bus. So we don't support these ops. */ case CFGA_CMD_DISCONNECT: case CFGA_CMD_CONNECT: cfga_err(errstring, 0, ERR_NOT_DEVOP, 0); ret = SCFGA_ERR; break; case CFGA_CMD_LOAD: case CFGA_CMD_UNLOAD: ret = SCFGA_OPNOTSUPP; break; default: cfga_err(errstring, 0, ERR_CMD_INVAL, 0); ret = SCFGA_ERR; break; } free_dev_list_elements(dev_list); return (ret); } /*ARGSUSED*/ scfga_ret_t dev_remove( const char *func, scfga_cmd_t cmd, apid_t *apidp, prompt_t *prp, cfga_flags_t flags, char **errstring) { int proceed, l_errno = 0; scfga_ret_t ret; int do_quiesce; char *dev_list[2] = {NULL}; assert(apidp->hba_phys != NULL); assert(apidp->path != NULL); /* device operation only */ if (apidp->dyncomp == NULL) { cfga_err(errstring, 0, ERR_NOT_BUSOP, 0); return (SCFGA_ERR); } proceed = 1; ret = quiesce_confirm(apidp, MSG_RMDEV, prp, &proceed, &do_quiesce, &l_errno); if (ret != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_DEV_REMOVE, 0); return (ret); } if (!proceed) { return (SCFGA_NACK); } /* * Offline the device in RCM */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { dev_list[0] = get_node_path(apidp->path); if (dev_list[0] == NULL) return (SCFGA_ERR); if ((ret = scsi_rcm_offline(dev_list, errstring, flags)) != SCFGA_OK) { free_dev_list_elements(dev_list); return (ret); } } /* * Offline the device */ ret = devctl_cmd(apidp->path, SCFGA_DEV_UNCONFIGURE, NULL, &l_errno); if (ret != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_DEV_REMOVE, 0); /* * Cancel the RCM offline. Discard the RCM failures so that * the above devctl failure is still reported. */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) (void) scsi_rcm_online(dev_list, errstring, flags); free_dev_list_elements(dev_list); return (ret); } /* Do the physical removal */ ret = dev_hotplug(apidp, prp, flags, do_quiesce, errstring); if (ret == SCFGA_OK) { /* * Complete the remove. * Since the device is already offlined, remove shouldn't * fail. Even if remove fails, there is no side effect. */ (void) devctl_cmd(apidp->path, SCFGA_DEV_REMOVE, NULL, &l_errno); if ((apidp->flags & FLAG_DISABLE_RCM) == 0) ret = scsi_rcm_remove(dev_list, errstring, flags); } else { /* * Reconfigure the device and restore the device's RCM state. * If reconfigure succeeds, restore the state to online. * If reconfigure fails (e.g. a typo from user), we treat * the device as removed. */ if (devctl_cmd(apidp->path, SCFGA_DEV_CONFIGURE, NULL, &l_errno) == SCFGA_OK) { if ((apidp->flags & FLAG_DISABLE_RCM) == 0) (void) scsi_rcm_online(dev_list, errstring, flags); } else { char *cp = strrchr(apidp->path, ':'); if (cp) *cp = '\0'; cfga_err(errstring, l_errno, ERR_DEV_RECONFIGURE, apidp->path, 0); if (cp) *cp = ':'; if ((apidp->flags & FLAG_DISABLE_RCM) == 0) (void) scsi_rcm_remove(dev_list, errstring, flags); } } free_dev_list_elements(dev_list); return (ret); } /*ARGSUSED*/ scfga_ret_t dev_insert( const char *func, scfga_cmd_t cmd, apid_t *apidp, prompt_t *prp, cfga_flags_t flags, char **errstring) { int proceed, l_errno = 0; scfga_ret_t ret; int do_quiesce; assert(apidp->hba_phys != NULL); assert(apidp->path != NULL); /* Currently, insert operation only allowed for bus */ if (apidp->dyncomp != NULL) { cfga_err(errstring, 0, ERR_NOT_DEVOP, 0); return (SCFGA_ERR); } proceed = 1; ret = quiesce_confirm(apidp, MSG_INSDEV, prp, &proceed, &do_quiesce, &l_errno); if (ret != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_DEV_INSERT, 0); return (ret); } if (!proceed) { return (SCFGA_NACK); } /* Do the physical addition */ ret = dev_hotplug(apidp, prp, flags, do_quiesce, errstring); if (ret != SCFGA_OK) { return (ret); } /* * Configure bus to online new device(s). * Previously offlined devices will not be onlined. */ ret = devctl_cmd(apidp->hba_phys, SCFGA_BUS_CONFIGURE, NULL, &l_errno); if (ret != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_DEV_INSERT, 0); return (SCFGA_ERR); } return (SCFGA_OK); } /*ARGSUSED*/ scfga_ret_t dev_replace( const char *func, scfga_cmd_t cmd, apid_t *apidp, prompt_t *prp, cfga_flags_t flags, char **errstring) { int proceed, l_errno = 0; scfga_ret_t ret, ret2; int do_quiesce; char *dev_list[2] = {NULL}; assert(apidp->hba_phys != NULL); assert(apidp->path != NULL); /* device operation only */ if (apidp->dyncomp == NULL) { cfga_err(errstring, 0, ERR_NOT_BUSOP, 0); return (SCFGA_ERR); } proceed = 1; ret = quiesce_confirm(apidp, MSG_REPLDEV, prp, &proceed, &do_quiesce, &l_errno); if (ret != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_DEV_REPLACE, 0); return (ret); } if (!proceed) { return (SCFGA_NACK); } /* Offline the device in RCM */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { dev_list[0] = get_node_path(apidp->path); if (dev_list[0] == NULL) return (SCFGA_ERR); if ((ret = scsi_rcm_offline(dev_list, errstring, flags)) != SCFGA_OK) { free_dev_list_elements(dev_list); return (ret); } } ret = devctl_cmd(apidp->path, SCFGA_DEV_REMOVE, NULL, &l_errno); if (ret != SCFGA_OK) { /* * Cancel the RCM offline. Discard any RCM failures so that * the devctl failure can still be reported. */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) (void) scsi_rcm_online(dev_list, errstring, flags); cfga_err(errstring, l_errno, ERR_DEV_REPLACE, 0); free_dev_list_elements(dev_list); return (ret); } /* do the physical replace */ ret = dev_hotplug(apidp, prp, flags, do_quiesce, errstring); /* Online the replacement, or restore state on error */ ret2 = devctl_cmd(apidp->path, SCFGA_DEV_CONFIGURE, NULL, &l_errno); if (ret2 != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_DEV_REPLACE, 0); } /* * Remove the replaced device in RCM, or online the device in RCM * to recover. */ if ((apidp->flags & FLAG_DISABLE_RCM) == 0) { if (ret == SCFGA_OK) ret = scsi_rcm_remove(dev_list, errstring, flags); else if (ret2 == SCFGA_OK) ret2 = scsi_rcm_online(dev_list, errstring, flags); } free_dev_list_elements(dev_list); return (ret == SCFGA_OK ? ret2 : ret); } #pragma weak plat_dev_led /*ARGSUSED*/ scfga_ret_t dev_led( const char *func, scfga_cmd_t cmd, apid_t *apidp, prompt_t *prp, cfga_flags_t flags, char **errstring) { /* * The implementation of the led command is platform-specific, so * the default behavior is to say that the functionality is not * available for this device. */ if (plat_dev_led) { return (plat_dev_led(func, cmd, apidp, prp, flags, errstring)); } cfga_err(errstring, 0, ERR_UNAVAILABLE, 0); return (SCFGA_ERR); } /*ARGSUSED*/ scfga_ret_t reset_common( const char *func, scfga_cmd_t cmd, apid_t *apidp, prompt_t *prp, cfga_flags_t flags, char **errstring) { int l_errno = 0; scfga_ret_t ret; assert(apidp->path != NULL); assert(apidp->hba_phys != NULL); switch (cmd) { case SCFGA_RESET_DEV: if (apidp->dyncomp == NULL) { cfga_err(errstring, 0, ERR_NOT_BUSOP, 0); return (SCFGA_ERR); } break; case SCFGA_RESET_BUS: case SCFGA_RESET_ALL: if (apidp->dyncomp != NULL) { cfga_err(errstring, 0, ERR_NOT_DEVOP, 0); return (SCFGA_ERR); } break; default: cfga_err(errstring, 0, ERR_CMD_INVAL, 0); return (SCFGA_ERR); } ret = devctl_cmd(apidp->path, cmd, NULL, &l_errno); if (ret != SCFGA_OK) { cfga_err(errstring, l_errno, ERR_RESET, 0); } return (ret); } static int disconnect(struct cfga_confirm *confp) { int ans, append_newline; char *cq; append_newline = 0; cq = cfga_str(append_newline, WARN_DISCONNECT, 0); ans = confp->confirm(confp->appdata_ptr, cq); S_FREE(cq); return (ans == 1); } /* * Check for "scsi-no-quiesce" property * Return code: -1 error, 0 quiesce not required, 1 quiesce required */ static int quiesce_required(apid_t *apidp, int *l_errnop) { di_node_t bus_node, dev_node; char *bus_path, *bus_end; char *dev_path, *dev_end; int *propval; /* take libdevinfo snapshot of subtree at hba */ bus_path = apidp->hba_phys + strlen(DEVICES_DIR); bus_end = strrchr(bus_path, ':'); if (bus_end) *bus_end = '\0'; bus_node = di_init(bus_path, DINFOSUBTREE|DINFOPROP); if (bus_end) *bus_end = ':'; if (bus_node == DI_NODE_NIL) { *l_errnop = errno; return (-1); /* error */ } /* check bus node for property */ if (di_prop_lookup_ints(DDI_DEV_T_ANY, bus_node, SCSI_NO_QUIESCE, &propval) == 1) { di_fini(bus_node); return (0); /* quiesce not required */ } /* if this ap is HBA, return with quiesce required */ if (apidp->dyncomp == NULL) { di_fini(bus_node); return (1); } /* check device node for property */ dev_path = apidp->path + strlen(DEVICES_DIR); dev_end = strrchr(dev_path, ':'); if (dev_end) *dev_end = '\0'; dev_node = di_child_node(bus_node); while (dev_node != DI_NODE_NIL) { char *child_path; child_path = di_devfs_path(dev_node); if (strcmp(child_path, dev_path) == 0) { di_devfs_path_free(child_path); break; } di_devfs_path_free(child_path); dev_node = di_sibling_node(dev_node); } if (dev_end) *dev_end = ':'; if (dev_node == DI_NODE_NIL) { di_fini(bus_node); return (1); /* dev not found (insert case) */ } /* check child node for property */ if (di_prop_lookup_ints(DDI_DEV_T_ANY, dev_node, "scsi-no-quiesce", &propval) == 1) { di_fini(bus_node); return (0); /* quiesce not required */ } return (1); /* quiesce required */ } static scfga_ret_t quiesce_confirm( apid_t *apidp, msgid_t cmd_msg, prompt_t *prp, int *okp, int *quiesce, int *l_errnop) { char *buf = NULL, *hbap = NULL, *cq1 = NULL, *cq2 = NULL; char *cp; size_t len = 0; int i = 0, append_newline; scfga_ret_t ret; assert(apidp->path != NULL); assert(apidp->hba_phys != NULL); *quiesce = quiesce_required(apidp, l_errnop); if (*quiesce == -1) return (SCFGA_ERR); else if (*quiesce == 0) return (SCFGA_OK); /* * Try to create HBA logical ap_id. * If that fails use physical path */ ret = make_hba_logid(apidp->hba_phys, &hbap, &i); if (ret != SCFGA_OK) { if ((hbap = get_node_path(apidp->hba_phys)) == NULL) { *l_errnop = errno; return (SCFGA_LIB_ERR); } } assert(hbap != NULL); append_newline = 0; cq1 = cfga_str(append_newline, CONF_QUIESCE_1, hbap, 0); cq2 = cfga_str(append_newline, CONF_QUIESCE_2, 0); len = strlen(cq1) + strlen(cq2) + 1; /* Includes term. NULL */ if ((buf = calloc(1, len)) == NULL) { *l_errnop = errno; ret = SCFGA_LIB_ERR; S_FREE(cq1); S_FREE(cq2); goto out; } (void) strcpy(buf, cq1); (void) strcat(buf, cq2); S_FREE(cq1); S_FREE(cq2); /* Remove minor name (if any) from phys path */ if ((cp = strrchr(apidp->path, ':')) != NULL) { *cp = '\0'; } /* describe operation being attempted */ cfga_msg(prp->msgp, cmd_msg, apidp->path, 0); /* Restore minor name */ if (cp != NULL) { *cp = ':'; } /* request permission to quiesce */ assert(prp->confp != NULL && prp->confp->confirm != NULL); *okp = prp->confp->confirm(prp->confp->appdata_ptr, buf); ret = SCFGA_OK; /*FALLTHRU*/ out: S_FREE(buf); S_FREE(hbap); return (ret); } static scfga_ret_t suspend_in_rcm( apid_t *apidp, char ***suspend_list_ptr, char **errstring, cfga_flags_t flags) { scfga_ret_t ret; char *bus_path = NULL; char *dev_path = NULL; char **suspend_list = NULL; *suspend_list_ptr = NULL; /* Suspend the bus through RCM */ if (apidp->flags & FLAG_DISABLE_RCM) return (SCFGA_OK); /* The bus_path is the HBA path without its minor */ if ((bus_path = get_node_path(apidp->hba_phys)) == NULL) return (SCFGA_ERR); /* * The dev_path is already initialized to NULL. If the AP Id * path differs from the HBA path, then the dev_path should * instead be set to the AP Id path without its minor. */ if (strcmp(apidp->hba_phys, apidp->path) != 0) { if ((dev_path = get_node_path(apidp->path)) == NULL) { ret = SCFGA_ERR; goto out; } } if ((ret = get_hba_children(bus_path, dev_path, &suspend_list)) != SCFGA_OK) { free_dev_list(suspend_list); goto out; } if (scsi_rcm_suspend(suspend_list, errstring, flags, 0) != SCFGA_OK) { ret = SCFGA_ERR; free_dev_list(suspend_list); } else { ret = SCFGA_OK; *suspend_list_ptr = suspend_list; } /*FALLTHROUGH*/ out: S_FREE(bus_path); S_FREE(dev_path); return (ret); } /* * Resume the bus through RCM if it successfully * unquiesced. */ static void resume_in_rcm( apid_t *apidp, char **suspend_list, char **errstring, cfga_flags_t flags) { if (apidp->flags & FLAG_DISABLE_RCM) return; (void) scsi_rcm_resume(suspend_list, errstring, flags, 0); free_dev_list(suspend_list); } static scfga_ret_t wait_for_hotplug(prompt_t *pt, int msg) { char *cu = NULL; int append_newline = 0; scfga_ret_t ret; cu = cfga_str(append_newline, msg, 0); if (pt->confp->confirm(pt->confp->appdata_ptr, cu) != 1) { ret = SCFGA_NACK; } else { ret = SCFGA_OK; } S_FREE(cu); return (ret); } static scfga_ret_t bus_quiesce(apid_t *apidp, prompt_t *pt, char **errstring, cfga_flags_t flags) { int l_errno; scfga_ret_t ret; scfga_ret_t hpret; char **suspend_list = NULL; ret = suspend_in_rcm(apidp, &suspend_list, errstring, flags); if (ret != SCFGA_OK) { return (ret); } /* * If the quiesce fails, then cancel the RCM suspend. * Discard any RCM failures so that the devctl failure * can still be reported. */ l_errno = 0; ret = devctl_cmd(apidp->hba_phys, SCFGA_BUS_QUIESCE, NULL, &l_errno); if (ret != SCFGA_OK) { resume_in_rcm(apidp, suspend_list, errstring, flags); cfga_err(errstring, l_errno, ERR_BUS_QUIESCE, 0); return (ret); } /* * Prompt user to proceed with physical hotplug * and wait until they are done. */ hpret = wait_for_hotplug(pt, CONF_UNQUIESCE); /* * The unquiesce may fail with EALREADY (which is ok) * or some other error (which is not ok). */ l_errno = 0; ret = devctl_cmd(apidp->hba_phys, SCFGA_BUS_UNQUIESCE, NULL, &l_errno); if (ret != SCFGA_OK && l_errno != EALREADY) { free_dev_list(suspend_list); cfga_err(errstring, l_errno, ERR_BUS_UNQUIESCE, 0); return (SCFGA_ERR); } resume_in_rcm(apidp, suspend_list, errstring, flags); return (hpret); } #define MAX_LOCK_TRIES 20 #define MAX_UNLINK_TRIES 60 #define s_getpid (int)getpid /* else lint causes problems */ static void s_unlink(char *file) { int count = 0; retry: if (unlink(file) == -1) { if (errno != EINTR && errno != EAGAIN) { CFGA_TRACE1((stdout, "s_unlink[%d]: unlink failed: " "%s: %s\n", s_getpid(), file, strerror(errno))); return; } if (++count < MAX_UNLINK_TRIES) { (void) sleep(1); goto retry; } CFGA_TRACE1((stdout, "s_unlink[%d]: retry limit: %s\n", s_getpid(), file)); } else { CFGA_TRACE3((stdout, "s_unlink[%d]: unlinked: %s\n", s_getpid(), file)); } } static scfga_ret_t create_lock(int *fdp, struct cfga_msg *msgp, char **errstring) { FILE *fp; int count; struct extmnttab ent; int mnted; *fdp = -1; /* * Check that /var/run is mounted. In the unlikely event * that the lock file is left behind, we want it * cleared on the next reboot. */ errno = 0; if ((fp = fopen(MNTTAB, "r")) == NULL) { cfga_err(errstring, errno, ERRARG_OPEN, MNTTAB, 0); return (SCFGA_LIB_ERR); } resetmnttab(fp); mnted = 0; while (getextmntent(fp, &ent, sizeof (ent)) == 0) { if (strcmp(ent.mnt_mountp, "/var/run") == 0) { mnted = 1; break; } } (void) fclose(fp); if (!mnted) { cfga_err(errstring, 0, ERR_VAR_RUN, 0); return (SCFGA_LIB_ERR); } /* * Wait for a short period of time if we cannot O_EXCL create * lock file. If some other cfgadm process is finishing up, we * can get in. If the wait required is long however, just * return SYSTEM_BUSY to the user - a hotplug operation is * probably in progress. */ count = 0; retry: *fdp = open(SCFGA_LOCK, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); if (*fdp == -1 && errno == EEXIST) { if (++count < MAX_LOCK_TRIES) { if (count == 1) cfga_msg(msgp, MSG_WAIT_LOCK, 0); (void) sleep(1); goto retry; } } if (*fdp == -1 && errno == EEXIST) { cfga_err(errstring, 0, ERRARG_QUIESCE_LOCK, SCFGA_LOCK, 0); return (SCFGA_SYSTEM_BUSY); } else if (*fdp == -1) { cfga_err(errstring, errno, ERRARG_QUIESCE_LOCK, SCFGA_LOCK, 0); return (SCFGA_LIB_ERR); } CFGA_TRACE3((stdout, "create_lock[%d]: created lockfile: %s\n", s_getpid(), SCFGA_LOCK)); return (SCFGA_OK); } static scfga_ret_t syslock(int fd, char **errstring) { struct flock lock; int count; int rval; assert(fd != -1); CFGA_TRACE3((stdout, "syslock[%d]: trying lock: %s\n", s_getpid(), SCFGA_LOCK)); lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; count = 0; while ((rval = fcntl(fd, F_SETLKW, &lock)) == -1 && errno == EINTR) { if (++count >= MAX_LOCK_TRIES) { CFGA_TRACE1((stdout, "syslock[%d]: retry limit: %s\n", s_getpid(), SCFGA_LOCK)); goto badlock; } (void) sleep(1); } if (rval != -1) { CFGA_TRACE3((stdout, "syslock[%d]: locked file: %s\n", s_getpid(), SCFGA_LOCK)); return (SCFGA_OK); } /*FALLTHROUGH*/ badlock: cfga_err(errstring, errno, ERRARG_LOCK, SCFGA_LOCK, 0); /* trace message to display pid */ CFGA_TRACE1((stdout, "syslock[%d]: cannot lock %s\n", s_getpid(), SCFGA_LOCK)); return (SCFGA_LIB_ERR); } static void wait_for_child(pid_t cpid) { int status; pid_t rval; CFGA_TRACE2((stdout, "wait_for_child[%d]: child[%d]\n", s_getpid(), (int)cpid)); for (;;) { while ((rval = waitpid(cpid, &status, 0)) != cpid) { if (errno == ECHILD) { CFGA_TRACE1((stdout, "waitpid[%d]: child[%d] " "doesn't exist\n", s_getpid(), (int)cpid)); return; } CFGA_TRACE3((stdout, "waitpid[%d]: returned: %d" ": errno: %s\n", s_getpid(), (int)rval, strerror(errno))); } if (WIFEXITED(status)) { CFGA_TRACE2((stdout, "waitpid[%d]: child[%d]: " "normal exit\n", s_getpid(), (int)cpid)); return; } if (WIFSIGNALED(status)) { CFGA_TRACE2((stdout, "waitpid[%d]: child[%d]: " "signal exit\n", s_getpid(), (int)cpid)); return; } /* * The child has not terminated. We received status * because the child was either stopped or continued. * Wait for child termination by calling waitpid() again. */ } } static void wait_and_cleanup(int fd, apid_t *apidp) { int l_errno; scfga_ret_t ret; /* This is the child */ CFGA_TRACE2((stdout, "child[%d]: Entering wait_cleanup\n", s_getpid())); if (syslock(fd, NULL) != SCFGA_OK) { CFGA_TRACE1((stdout, "child[%d]: lock failure " " - _exit(1)\n", s_getpid())); /* * As a last resort, unlink the lock file. This is relatively * safe as the child doesn't unquiesce the bus in this case. */ s_unlink(SCFGA_LOCK); _exit(1); } l_errno = 0; ret = devctl_cmd(apidp->hba_phys, SCFGA_BUS_UNQUIESCE, NULL, &l_errno); if (ret != SCFGA_OK) { if (l_errno == EALREADY) CFGA_TRACE3((stdout, "child[%d]: bus already " "unquiesced: %s\n", s_getpid(), apidp->hba_phys)); else CFGA_TRACE1((stdout, "child[%d]: unquiesce failed: " "%s\n", s_getpid(), strerror(l_errno))); } else { CFGA_TRACE1((stdout, "child[%d]: unquiesced bus: %s\n", s_getpid(), apidp->hba_phys)); } s_unlink(SCFGA_LOCK); CFGA_TRACE2((stdout, "child[%d]: _exit(0)\n", s_getpid())); _exit(0); } static void sigblk(sigset_t *osp) { sigset_t set; (void) sigemptyset(&set); (void) sigemptyset(osp); (void) sigaddset(&set, SIGHUP); (void) sigaddset(&set, SIGINT); (void) sigaddset(&set, SIGQUIT); (void) sigaddset(&set, SIGTERM); (void) sigaddset(&set, SIGUSR1); (void) sigaddset(&set, SIGUSR2); (void) sigprocmask(SIG_BLOCK, &set, osp); } static void sigunblk(sigset_t *osp) { (void) sigprocmask(SIG_SETMASK, osp, NULL); } /* * Here is the algorithm used to ensure that a SCSI bus is not * left in the quiesced state: * * lock quiesce mutex // single threads this code * open(O_CREAT|O_EXCL) lock file // only 1 process at a time * exclusive record lock on lock file * fork1() * quiesce bus * do the physical hotplug operation * unquiesce bus * unlock record lock * -> *child* * -> wait for record lock * -> unconditionally unquiesce bus * -> unlink lock file * -> exit * wait for child to exit * unlock quiesce mutex * * NOTE1: since record locks are per-process and a close() can * release a lock, to keep things MT-safe we need a quiesce mutex. * * NOTE2: To ensure that the child does not unquiesce a bus quiesced * by an unrelated cfgadm_scsi operation, exactly 1 process in the * system can be doing an implicit quiesce operation The exclusive * creation of the lock file guarantees this. * * NOTE3: This works even if the parent process dumps core and/or is * abnormally terminated. If the parent dies before the child is * forked, the bus is not quiesced. If the parent dies after the * bus is quiesced, the child process will ensure that the bus is * unquiesced. */ static scfga_ret_t dev_hotplug( apid_t *apidp, prompt_t *pt, cfga_flags_t flags, int do_quiesce, char **errstring) { scfga_ret_t ret; pid_t cpid; int fd; sigset_t oset; assert(apidp->hba_phys != NULL); assert(apidp->path != NULL); /* If no quiesce required, prompt the user to do the operation */ if (!do_quiesce) return (wait_for_hotplug(pt, CONF_NO_QUIESCE)); (void) mutex_lock(&quiesce_mutex); ret = create_lock(&fd, pt->msgp, errstring); if (ret != SCFGA_OK) { (void) mutex_unlock(&quiesce_mutex); return (ret); } ret = syslock(fd, errstring); if (ret != SCFGA_OK) { goto bad; } /* * block signals in the child. Parent may * exit, causing signal to be sent to child. */ sigblk(&oset); switch (cpid = fork1()) { case 0: /* child */ wait_and_cleanup(fd, apidp); _exit(0); /* paranoia */ /*NOTREACHED*/ case -1: cfga_err(errstring, errno, ERR_FORK, 0); sigunblk(&oset); ret = SCFGA_LIB_ERR; goto bad; default: /* parent */ break; } sigunblk(&oset); /* We have forked successfully - this is the parent */ ret = bus_quiesce(apidp, pt, errstring, flags); (void) close(fd); /* also unlocks */ wait_for_child(cpid); (void) mutex_unlock(&quiesce_mutex); return (ret); bad: (void) close(fd); s_unlink(SCFGA_LOCK); (void) mutex_unlock(&quiesce_mutex); return (ret); } /* * Checks if HBA controls a critical file-system (/, /usr or swap) * This routine reads /etc/vfstab and is NOT foolproof. * If an error occurs, assumes that controller is NOT critical. */ static int critical_ctrlr(const char *hba_phys) { FILE *fp; struct vfstab vfst; int vfsret = 1, rv = -1; char *bufp; const size_t buflen = PATH_MAX; char mount[MAXPATHLEN], fstype[MAXPATHLEN], spec[MAXPATHLEN]; if ((bufp = calloc(1, buflen)) == NULL) { return (0); } fp = NULL; if ((fp = fopen(ETC_VFSTAB, "r")) == NULL) { rv = 0; goto out; } while ((vfsret = getvfsent(fp, &vfst)) == 0) { (void) strcpy(mount, S_STR(vfst.vfs_mountp)); (void) strcpy(fstype, S_STR(vfst.vfs_fstype)); (void) strcpy(spec, S_STR(vfst.vfs_special)); /* Ignore non-critical entries */ if (strcmp(mount, "/") && strcmp(mount, "/usr") && strcmp(fstype, "swap")) { continue; } /* get physical path */ if (realpath(spec, bufp) == NULL) { continue; } /* Check if critical partition is on the HBA */ if (!(rv = hba_dev_cmp(hba_phys, bufp))) { break; } } rv = !vfsret; /*FALLTHRU*/ out: S_FREE(bufp); if (fp != NULL) { (void) fclose(fp); } return (rv); } /* * Convert bus state to receptacle state */ static cfga_stat_t bus_devctl_to_recep_state(uint_t bus_dc_state) { cfga_stat_t rs; switch (bus_dc_state) { case BUS_ACTIVE: rs = CFGA_STAT_CONNECTED; break; case BUS_QUIESCED: case BUS_SHUTDOWN: rs = CFGA_STAT_DISCONNECTED; break; default: rs = CFGA_STAT_NONE; break; } return (rs); } static int add_dev(di_node_t node, void *arg) { int ndevs, len; char *path, *p; struct larg *largp = (struct larg *)arg; /* ignore hba itself and all detached nodes */ if (di_parent_node(node) == DI_NODE_NIL || di_node_state(node) < DS_ATTACHED) return (DI_WALK_CONTINUE); if ((path = di_devfs_path(node)) == NULL) { largp->ndevs = -1; return (DI_WALK_TERMINATE); } /* sizeof (DEVICES_DIR) includes the null terminator */ len = strlen(path) + sizeof (DEVICES_DIR); if ((p = malloc(len)) == NULL) { di_devfs_path_free(path); largp->ndevs = -1; return (DI_WALK_TERMINATE); } (void) snprintf(p, len, "%s%s", DEVICES_DIR, path); di_devfs_path_free(path); /* ignore device to be excluded */ if (largp->dev && strcmp(largp->dev, p) == 0) { free(p); return (DI_WALK_CONTINUE); } /* grow dev_list to allow room for one more device */ if (alloc_dev_list(largp) != 0) { free(p); return (DI_WALK_TERMINATE); } ndevs = largp->ndevs; largp->ndevs++; largp->dev_list[ndevs] = p; largp->dev_list[ndevs + 1] = NULL; return (DI_WALK_CONTINUE); } /* * Get list of children excluding dev_excl (if not null). */ static int get_hba_children(char *bus_path, char *dev_excl, char ***dev_listp) { int err, ret; walkarg_t u; struct larg larg; *dev_listp = NULL; u.node_args.flags = DI_WALK_CLDFIRST; u.node_args.fcn = add_dev; larg.ndevs = 0; larg.nelem = 0; larg.dev = dev_excl; larg.dev_list = NULL; ret = walk_tree(bus_path, &larg, DINFOSUBTREE, &u, SCFGA_WALK_NODE, &err); if (larg.ndevs == -1) { free_dev_list(larg.dev_list); return (SCFGA_ERR); } *dev_listp = larg.dev_list; return (ret); } static char * get_node_path(char *minor_path) { char *path, *cp; if ((path = strdup(minor_path)) == NULL) return (NULL); if ((cp = strrchr(path, ':')) != NULL) *cp = '\0'; return (path); } /* * Ensure largp->dev_list has room for one more device. * Returns 0 on success, -1 on failure. */ static int alloc_dev_list(struct larg *largp) { int nelem; char **p; if (largp->nelem > largp->ndevs + 2) /* +1 for NULL termination */ return (0); nelem = largp->nelem + 16; p = realloc(largp->dev_list, nelem * sizeof (char *)); if (p == NULL) return (-1); largp->dev_list = p; largp->nelem = nelem; return (0); } static void free_dev_list_elements(char **dev_list) { while (*dev_list) { free(*dev_list); dev_list++; } } static void free_dev_list(char **dev_list) { if (dev_list == NULL) return; free_dev_list_elements(dev_list); free(dev_list); }