/* * virvhba.c: Generic vHBA management utility functions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include "viralloc.h" #include "virerror.h" #include "virfile.h" #include "virlog.h" #include "virstring.h" #include "virvhba.h" #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("util.vhba"); #ifdef __linux__ # define SYSFS_SCSI_HOST_PATH "/sys/class/scsi_host" # define SYSFS_FC_HOST_PATH "/sys/class/fc_host" # define PORT_STATE_ONLINE "Online" /* virVHBAPathExists: * @sysfs_prefix: "fc_host" sysfs path, defaults to SYSFS_FC_HOST_PATH * @host: Host number, E.g. 5 of "fc_host/host5" * * Check if the "fc_host" to provided host# exists. This path may be either * a vHBA capable path or a vHBA itself. * * Returns true if it does, false if not */ bool virVHBAPathExists(const char *sysfs_prefix, int host) { char *sysfs_path = NULL; bool ret = false; sysfs_path = g_strdup_printf("%s/host%d", sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH, host); if (virFileExists(sysfs_path)) ret = true; VIR_FREE(sysfs_path); return ret; } /* virVHBAIsVportCapable: * @sysfs_prefix: "fc_host" sysfs path, defaults to SYSFS_FC_HOST_PATH * @host: Host number, E.g. 5 of "fc_host/host5" * * Not all vHBA paths can create/delete a vport - only the parent NPIV * capable HBA has the "vport_create" and "vport_delete" functions. * A vHBA created path does not have the function files. * * NB: Checks both the "fc_host" and "scsi_host" paths. * * Returns true if capable, false if not */ bool virVHBAIsVportCapable(const char *sysfs_prefix, int host) { char *scsi_host_path = NULL; char *fc_host_path = NULL; bool ret = false; fc_host_path = g_strdup_printf("%s/host%d/%s", sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH, host, "vport_create"); scsi_host_path = g_strdup_printf("%s/host%d/%s", sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_HOST_PATH, host, "vport_create"); if (virFileExists(fc_host_path) || virFileExists(scsi_host_path)) ret = true; VIR_FREE(fc_host_path); VIR_FREE(scsi_host_path); return ret; } /* virVHBAGetConfig: * @sysfs_prefix: "fc_host" sysfs path, defaults to SYSFS_FC_HOST_PATH * @host: Host number, E.g. 5 of "fc_host/host5" * @entry: Name of the FC sysfs entry to read * * Read the value of a vHBA sysfs "fc_host" entry (if it exists). * * Returns result as a string on success, caller is responsible for * freeing the @result; otherwise returns NULL on failure. */ char * virVHBAGetConfig(const char *sysfs_prefix, int host, const char *entry) { char *sysfs_path = NULL; char *p = NULL; char *buf = NULL; char *result = NULL; sysfs_path = g_strdup_printf("%s/host%d/%s", sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH, host, entry); if (!virFileExists(sysfs_path)) goto cleanup; if (virFileReadAll(sysfs_path, 1024, &buf) < 0) goto cleanup; if ((p = strchr(buf, '\n'))) *p = '\0'; if ((p = strstr(buf, "0x"))) p += strlen("0x"); else p = buf; result = g_strdup(p); cleanup: VIR_FREE(sysfs_path); VIR_FREE(buf); return result; } /* virVHBAFindVportHost: * * Iterate over the sysfs and find out the first online HBA which * supports vport, and is not saturated. Returns the host name (e.g. * host5) on success, or NULL on failure. * * It's up to the caller to free the returned string. */ char * virVHBAFindVportHost(const char *sysfs_prefix) { const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH; g_autoptr(DIR) dir = NULL; struct dirent *entry = NULL; char *max_vports = NULL; char *vports = NULL; char *state = NULL; char *ret = NULL; if (virDirOpen(&dir, prefix) < 0) return NULL; while (virDirRead(dir, &entry, prefix) > 0) { unsigned int host; char *p = NULL; p = entry->d_name + strlen("host"); if (virStrToLong_ui(p, NULL, 10, &host) == -1) { VIR_DEBUG("Failed to parse host number from '%s'", entry->d_name); continue; } if (!virVHBAPathExists(prefix, host)) continue; if (!(state = virVHBAGetConfig(prefix, host, "port_state"))) { VIR_DEBUG("Failed to read port_state for host%d", host); continue; } /* Skip the not online FC host */ if (STRNEQ(state, PORT_STATE_ONLINE)) { VIR_FREE(state); continue; } VIR_FREE(state); if (!(max_vports = virVHBAGetConfig(prefix, host, "max_npiv_vports"))) { VIR_DEBUG("Failed to read max_npiv_vports for host%d", host); continue; } if (!(vports = virVHBAGetConfig(prefix, host, "npiv_vports_inuse"))) { VIR_DEBUG("Failed to read npiv_vports_inuse for host%d", host); VIR_FREE(max_vports); continue; } /* Compare from the strings directly, instead of converting * the strings to integers first */ if ((strlen(max_vports) >= strlen(vports)) || ((strlen(max_vports) == strlen(vports)) && strcmp(max_vports, vports) > 0)) { ret = g_strdup(entry->d_name); goto cleanup; } VIR_FREE(max_vports); VIR_FREE(vports); } cleanup: VIR_FREE(max_vports); VIR_FREE(vports); return ret; } /* virVHBAManageVport: * @sysfs_prefix: "fc_host" sysfs path, defaults to SYSFS_FC_HOST_PATH * @wwnn: world wide node name used to create/delete the vport * @wwpn: world wide port name used to create/delete the vport * @operation: create or delete * * NB: Checks both the "fc_host" and "scsi_host" paths. * Returns true if capable, false if not */ int virVHBAManageVport(const int parent_host, const char *wwpn, const char *wwnn, int operation) { int ret = -1; char *operation_path = NULL, *vport_name = NULL; const char *operation_file = NULL; switch (operation) { case VPORT_CREATE: operation_file = "vport_create"; break; case VPORT_DELETE: operation_file = "vport_delete"; break; default: virReportError(VIR_ERR_OPERATION_INVALID, _("Invalid vport operation (%d)"), operation); goto cleanup; } operation_path = g_strdup_printf("%s/host%d/%s", SYSFS_FC_HOST_PATH, parent_host, operation_file); if (!virFileExists(operation_path)) { VIR_FREE(operation_path); operation_path = g_strdup_printf("%s/host%d/%s", SYSFS_SCSI_HOST_PATH, parent_host, operation_file); if (!virFileExists(operation_path)) { virReportError(VIR_ERR_OPERATION_INVALID, _("vport operation '%s' is not supported " "for host%d"), operation_file, parent_host); goto cleanup; } } /* Create/Delete is handled through the file passing the wwpn:wwnn as * a parameter. This results in the kernel managing the port. For udev, * an event is posted and handled in udevEventHandleCallback resulting * in calling either the Add or Remove device functions. This translates * into either adding or removing a node device object and a node device * lifecycle event for applications to consume. */ vport_name = g_strdup_printf("%s:%s", wwpn, wwnn); if (virFileWriteStr(operation_path, vport_name, 0) == 0) ret = 0; else virReportSystemError(errno, _("Write of '%s' to '%s' during " "vport create/delete failed"), vport_name, operation_path); cleanup: VIR_FREE(vport_name); VIR_FREE(operation_path); return ret; } /* vhbaReadCompareWWN * @prefix: path to the wwn file * @d_name: name of the current directory * @f_name: file name to read * * Read/compare the on-disk file with the passed wwn value. * * Returns: * -1 : Error * 0 : No match * 1 : Match */ static int vhbaReadCompareWWN(const char *prefix, const char *d_name, const char *f_name, const char *wwn) { char *path; char *buf = NULL; char *p; int ret = -1; path = g_strdup_printf("%s/%s/%s", prefix, d_name, f_name); if (!virFileExists(path)) { ret = 0; goto cleanup; } if (virFileReadAll(path, 1024, &buf) < 0) goto cleanup; if ((p = strchr(buf, '\n'))) *p = '\0'; if (STRPREFIX(buf, "0x")) p = buf + strlen("0x"); else p = buf; if (STRNEQ(wwn, p)) ret = 0; else ret = 1; cleanup: VIR_FREE(path); VIR_FREE(buf); return ret; } /* virVHBAGetHostByWWN: * * Iterate over the sysfs tree to get FC host name (e.g. host5) * by the provided "wwnn,wwpn" pair. * * Returns the FC host name which must be freed by the caller, * or NULL on failure. */ char * virVHBAGetHostByWWN(const char *sysfs_prefix, const char *wwnn, const char *wwpn) { const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH; struct dirent *entry = NULL; g_autoptr(DIR) dir = NULL; if (virDirOpen(&dir, prefix) < 0) return NULL; while (virDirRead(dir, &entry, prefix) > 0) { int rc; if ((rc = vhbaReadCompareWWN(prefix, entry->d_name, "node_name", wwnn)) < 0) return NULL; if (rc == 0) continue; if ((rc = vhbaReadCompareWWN(prefix, entry->d_name, "port_name", wwpn)) < 0) return NULL; if (rc == 0) continue; return g_strdup(entry->d_name); } return NULL; } /* virVHBAGetHostByFabricWWN: * * Iterate over the sysfs tree to get FC host name (e.g. host5) * by the provided "fabric_wwn". This would find a host on a SAN. * * Returns the FC host name which must be freed by the caller, * or NULL on failure. */ char * virVHBAGetHostByFabricWWN(const char *sysfs_prefix, const char *fabric_wwn) { const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH; struct dirent *entry = NULL; g_autoptr(DIR) dir = NULL; char *vport_create_path = NULL; char *ret = NULL; if (virDirOpen(&dir, prefix) < 0) return NULL; while (virDirRead(dir, &entry, prefix) > 0) { int rc; VIR_FREE(vport_create_path); /* Existing vHBA's will have the same fabric_name, but won't * have the vport_create file - so we check for both */ vport_create_path = g_strdup_printf("%s/%s/vport_create", prefix, entry->d_name); if (!virFileExists(vport_create_path)) continue; if ((rc = vhbaReadCompareWWN(prefix, entry->d_name, "fabric_name", fabric_wwn)) < 0) goto cleanup; if (rc == 0) continue; ret = g_strdup(entry->d_name); break; } cleanup: VIR_FREE(vport_create_path); return ret; } #else bool virVHBAPathExists(const char *sysfs_prefix G_GNUC_UNUSED, int host G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return false; } bool virVHBAIsVportCapable(const char *sysfs_prefix G_GNUC_UNUSED, int host G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return false; } char * virVHBAGetConfig(const char *sysfs_prefix G_GNUC_UNUSED, int host G_GNUC_UNUSED, const char *entry G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return NULL; } char * virVHBAFindVportHost(const char *sysfs_prefix G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return NULL; } int virVHBAManageVport(const int parent_host G_GNUC_UNUSED, const char *wwpn G_GNUC_UNUSED, const char *wwnn G_GNUC_UNUSED, int operation G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return -1; } char * virVHBAGetHostByWWN(const char *sysfs_prefix G_GNUC_UNUSED, const char *wwnn G_GNUC_UNUSED, const char *wwpn G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return NULL; } char * virVHBAGetHostByFabricWWN(const char *sysfs_prefix G_GNUC_UNUSED, const char *fabric_wwn G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return NULL; } #endif /* __linux__ */