/*
* virscsihost.c: Generic scsi_host 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
#include "viralloc.h"
#include "virerror.h"
#include "virfile.h"
#include "virlog.h"
#include "virscsihost.h"
#include "virstring.h"
#define VIR_FROM_THIS VIR_FROM_NONE
VIR_LOG_INIT("util.scsi_host");
#ifdef __linux__
# define SYSFS_SCSI_HOST_PATH "/sys/class/scsi_host"
/* virSCSIHostGetUniqueId:
* @sysfs_prefix: "scsi_host" sysfs path, defaults to SYSFS_SCSI_HOST_PATH
* @host: Host number, E.g. 5 of "scsi_host/host5"
*
* Read the value of the "scsi_host" unique_id file.
*
* Returns the value on success or -1 on failure.
*
* No errors are reported.
*/
int
virSCSIHostGetUniqueId(const char *sysfs_prefix,
int host)
{
g_autofree char *sysfs_path = NULL;
char *p = NULL;
g_autofree char *buf = NULL;
int unique_id;
sysfs_path = g_strdup_printf("%s/host%d/unique_id",
sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_HOST_PATH, host);
if (virFileReadAllQuiet(sysfs_path, 1024, &buf) < 0)
return -1;
if ((p = strchr(buf, '\n')))
*p = '\0';
if (virStrToLong_i(buf, NULL, 10, &unique_id) < 0) {
VIR_DEBUG("unable to parse unique_id: '%s'", buf);
return -1;
}
return unique_id;
}
/* virSCSIHostFindByPCI:
* @sysfs_prefix: "scsi_host" sysfs path, defaults to SYSFS_SCSI_HOST_PATH
* @parentaddr: string of the PCI address "scsi_host" device to be found
* @unique_id: unique_id value of the to be found "scsi_host" device
* @result: Return the host# of the matching "scsi_host" device
*
* Iterate over the SYSFS_SCSI_HOST_PATH entries looking for a matching
* PCI Address in the expected format (dddd:bb:ss.f, where 'dddd' is the
* 'domain' value, 'bb' is the 'bus' value, 'ss' is the 'slot' value, and
* 'f' is the 'function' value from the PCI address) with a unique_id file
* entry having the value expected. Unlike virReadSCSIUniqueId() we don't
* have a host number yet and that's what we're looking for.
*
* Returns the host name of the "scsi_host" which must be freed by the caller,
* or NULL on failure
*/
char *
virSCSIHostFindByPCI(const char *sysfs_prefix,
const char *parentaddr,
unsigned int unique_id)
{
const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_HOST_PATH;
struct dirent *entry = NULL;
g_autoptr(DIR) dir = NULL;
char *host_link = NULL;
char *host_path = NULL;
char *p = NULL;
char *ret = NULL;
char *buf = NULL;
char *unique_path = NULL;
unsigned int read_unique_id;
if (virDirOpen(&dir, prefix) < 0)
return NULL;
while (virDirRead(dir, &entry, prefix) > 0) {
if (!virFileIsLink(entry->d_name))
continue;
host_link = g_strdup_printf("%s/%s", prefix, entry->d_name);
if (virFileResolveLink(host_link, &host_path) < 0)
goto cleanup;
if (!strstr(host_path, parentaddr)) {
VIR_FREE(host_link);
VIR_FREE(host_path);
continue;
}
VIR_FREE(host_link);
VIR_FREE(host_path);
unique_path = g_strdup_printf("%s/%s/unique_id", prefix, entry->d_name);
if (!virFileExists(unique_path)) {
VIR_FREE(unique_path);
continue;
}
if (virFileReadAll(unique_path, 1024, &buf) < 0)
goto cleanup;
if ((p = strchr(buf, '\n')))
*p = '\0';
if (virStrToLong_ui(buf, NULL, 10, &read_unique_id) < 0)
goto cleanup;
VIR_FREE(buf);
if (read_unique_id != unique_id) {
VIR_FREE(unique_path);
continue;
}
ret = g_strdup(entry->d_name);
break;
}
cleanup:
VIR_FREE(unique_path);
VIR_FREE(host_link);
VIR_FREE(host_path);
VIR_FREE(buf);
return ret;
}
/* virSCSIHostGetNumber:
* @adapter_name: Name of the host adapter
* @result: Return the entry value as unsigned int
*
* Convert the various forms of scsi_host names into the numeric
* host# value that can be used in order to scan sysfs looking for
* the specific host.
*
* Names can be either "scsi_host#" or just "host#", where
* "host#" is the back-compat format, but both equate to
* the same source adapter. First check if both pool and def
* are using same format (easier) - if so, then compare
*
* Returns 0 on success, and @result has the host number.
* Otherwise returns -1.
*/
int
virSCSIHostGetNumber(const char *adapter_name,
unsigned int *result)
{
/* Specifying adapter like 'host5' is still supported for
* back-compat reason.
*/
if (STRPREFIX(adapter_name, "scsi_host")) {
adapter_name += strlen("scsi_host");
} else if (STRPREFIX(adapter_name, "fc_host")) {
adapter_name += strlen("fc_host");
} else if (STRPREFIX(adapter_name, "host")) {
adapter_name += strlen("host");
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid adapter name '%s' for SCSI pool"),
adapter_name);
return -1;
}
if (virStrToLong_ui(adapter_name, NULL, 10, result) == -1) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid adapter name '%s' for SCSI pool"),
adapter_name);
return -1;
}
return 0;
}
/* virSCSIHostGetNameByParentaddr:
* @domain: The domain from the scsi_host parentaddr
* @bus: The bus from the scsi_host parentaddr
* @slot: The slot from the scsi_host parentaddr
* @function: The function from the scsi_host parentaddr
* @unique_id: The unique id value for parentaddr
*
* Generate a parentaddr and find the scsi_host host# for
* the provided parentaddr PCI address fields.
*
* Returns the "host#" string which must be free'd by
* the caller or NULL on error
*/
char *
virSCSIHostGetNameByParentaddr(unsigned int domain,
unsigned int bus,
unsigned int slot,
unsigned int function,
unsigned int unique_id)
{
char *name = NULL;
char *parentaddr = NULL;
parentaddr = g_strdup_printf("%04x:%02x:%02x.%01x", domain, bus, slot,
function);
if (!(name = virSCSIHostFindByPCI(NULL, parentaddr, unique_id))) {
virReportError(VIR_ERR_XML_ERROR,
_("Failed to find scsi_host using PCI '%s' "
"and unique_id='%u'"),
parentaddr, unique_id);
goto cleanup;
}
cleanup:
VIR_FREE(parentaddr);
return name;
}
#else
int
virSCSIHostGetUniqueId(const char *sysfs_prefix G_GNUC_UNUSED,
int host G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
return -1;
}
char *
virSCSIHostFindByPCI(const char *sysfs_prefix G_GNUC_UNUSED,
const char *parentaddr G_GNUC_UNUSED,
unsigned int unique_id G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
return NULL;
}
int
virSCSIHostGetNumber(const char *adapter_name G_GNUC_UNUSED,
unsigned int *result G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
return -1;
}
char *
virSCSIHostGetNameByParentaddr(unsigned int domain G_GNUC_UNUSED,
unsigned int bus G_GNUC_UNUSED,
unsigned int slot G_GNUC_UNUSED,
unsigned int function G_GNUC_UNUSED,
unsigned int unique_id G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
return NULL;
}
#endif /* __linux__ */