mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-27 16:15:23 +00:00
85c8c29214
After converting all DIR* to g_autoptr(DIR), many cleanup: labels ended up just having "return ret", and every place that set ret would just immediately goto cleanup. Remove the cleanup label and its return, and just return the set value immediately, thus eliminating the need for the return variable itself. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
513 lines
14 KiB
C
513 lines
14 KiB
C
/*
|
|
* 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#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__ */
|