libvirt/src/storage/storage_backend_scsi.c
Daniel P. Berrangé 600462834f Remove all Author(s): lines from source file headers
In many files there are header comments that contain an Author:
statement, supposedly reflecting who originally wrote the code.
In a large collaborative project like libvirt, any non-trivial
file will have been modified by a large number of different
contributors. IOW, the Author: comments are quickly out of date,
omitting people who have made significant contribitions.

In some places Author: lines have been added despite the person
merely being responsible for creating the file by moving existing
code out of another file. IOW, the Author: lines give an incorrect
record of authorship.

With this all in mind, the comments are useless as a means to identify
who to talk to about code in a particular file. Contributors will always
be better off using 'git log' and 'git blame' if they need to  find the
author of a particular bit of code.

This commit thus deletes all Author: comments from the source and adds
a rule to prevent them reappearing.

The Copyright headers are similarly misleading and inaccurate, however,
we cannot delete these as they have legal meaning, despite being largely
inaccurate. In addition only the copyright holder is permitted to change
their respective copyright statement.

Reviewed-by: Erik Skultety <eskultet@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
2018-12-13 16:08:38 +00:00

496 lines
14 KiB
C

/*
* storage_backend_scsi.c: storage backend for SCSI handling
*
* Copyright (C) 2007-2008, 2013-2014 Red Hat, Inc.
* Copyright (C) 2007-2008 Daniel P. Berrange
*
* 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 <unistd.h>
#include <fcntl.h>
#include "virerror.h"
#include "storage_backend_scsi.h"
#include "viralloc.h"
#include "virlog.h"
#include "virfile.h"
#include "vircommand.h"
#include "virstring.h"
#include "storage_util.h"
#include "node_device_conf.h"
#include "node_device_util.h"
#include "driver.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
VIR_LOG_INIT("storage.storage_backend_scsi");
#define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host"
#define LINUX_SYSFS_SCSI_HOST_POSTFIX "device"
#define LINUX_SYSFS_SCSI_HOST_SCAN_STRING "- - -"
typedef struct _virStoragePoolFCRefreshInfo virStoragePoolFCRefreshInfo;
typedef virStoragePoolFCRefreshInfo *virStoragePoolFCRefreshInfoPtr;
struct _virStoragePoolFCRefreshInfo {
char *fchost_name;
unsigned char pool_uuid[VIR_UUID_BUFLEN];
};
static int
virStorageBackendSCSITriggerRescan(uint32_t host)
{
int fd = -1;
int retval = 0;
char *path;
VIR_DEBUG("Triggering rescan of host %d", host);
if (virAsprintf(&path, "%s/host%u/scan",
LINUX_SYSFS_SCSI_HOST_PREFIX, host) < 0) {
retval = -1;
goto out;
}
VIR_DEBUG("Scan trigger path is '%s'", path);
fd = open(path, O_WRONLY);
if (fd < 0) {
virReportSystemError(errno,
_("Could not open '%s' to trigger host scan"),
path);
retval = -1;
goto free_path;
}
if (safewrite(fd,
LINUX_SYSFS_SCSI_HOST_SCAN_STRING,
sizeof(LINUX_SYSFS_SCSI_HOST_SCAN_STRING)) < 0) {
VIR_FORCE_CLOSE(fd);
virReportSystemError(errno,
_("Write to '%s' to trigger host scan failed"),
path);
retval = -1;
}
VIR_FORCE_CLOSE(fd);
free_path:
VIR_FREE(path);
out:
VIR_DEBUG("Rescan of host %d complete", host);
return retval;
}
/**
* Frees opaque data
*
* @opaque Data to be freed
*/
static void
virStoragePoolFCRefreshDataFree(void *opaque)
{
virStoragePoolFCRefreshInfoPtr cbdata = opaque;
VIR_FREE(cbdata->fchost_name);
VIR_FREE(cbdata);
}
/**
* Thread to handle the pool refresh after a VPORT_CREATE is done. In this
* case the 'udevEventHandleCallback' will be executed asynchronously as a
* result of the node device driver callback routine to handle when udev
* notices some sort of device change (such as adding a new device). It takes
* some amount of time (usually a few seconds) for udev to go through the
* process of setting up the new device. Unfortunately, there is nothing
* that says "when" it's done. The immediate virStorageBackendSCSIRefreshPool
* done after virStorageBackendSCSIStartPool (and createVport) occurs too
* quickly to find any devices.
*
* So this thread is designed to wait a few seconds (5), then make the query
* to find the LUs for the pool. If none yet exist, we'll try once more
* to find the LUs before giving up.
*
* Attempting to find devices prior to allowing udev to settle down may result
* in finding devices that then get deleted.
*
* @opaque Pool's Refresh Info containing name and pool object pointer
*/
static void
virStoragePoolFCRefreshThread(void *opaque)
{
virStoragePoolFCRefreshInfoPtr cbdata = opaque;
const char *fchost_name = cbdata->fchost_name;
const unsigned char *pool_uuid = cbdata->pool_uuid;
virStoragePoolObjPtr pool = NULL;
virStoragePoolDefPtr def;
unsigned int host;
int found = 0;
int tries = 2;
do {
sleep(5); /* Give it time */
/* Let's see if the pool still exists - */
if (!(pool = virStoragePoolObjFindPoolByUUID(pool_uuid)))
break;
def = virStoragePoolObjGetDef(pool);
/* Return with pool lock, if active, we can get the host number,
* successfully, rescan, and find LUN's, then we are happy
*/
VIR_DEBUG("Attempt FC Refresh for pool='%s' name='%s' tries='%d'",
def->name, fchost_name, tries);
def->allocation = def->capacity = def->available = 0;
if (virStoragePoolObjIsActive(pool) &&
virSCSIHostGetNumber(fchost_name, &host) == 0 &&
virStorageBackendSCSITriggerRescan(host) == 0) {
virStoragePoolObjClearVols(pool);
found = virStorageBackendSCSIFindLUs(pool, host);
}
virStoragePoolObjEndAPI(&pool);
} while (!found && --tries);
if (pool && !found)
VIR_DEBUG("FC Refresh Thread failed to find LU's");
virStoragePoolFCRefreshDataFree(cbdata);
}
static char *
getAdapterName(virStorageAdapterPtr adapter)
{
char *name = NULL;
char *parentaddr = NULL;
if (adapter->type == VIR_STORAGE_ADAPTER_TYPE_SCSI_HOST) {
virStorageAdapterSCSIHostPtr scsi_host = &adapter->data.scsi_host;
if (scsi_host->has_parent) {
virPCIDeviceAddressPtr addr = &scsi_host->parentaddr;
unsigned int unique_id = scsi_host->unique_id;
if (!(name = virSCSIHostGetNameByParentaddr(addr->domain,
addr->bus,
addr->slot,
addr->function,
unique_id)))
goto cleanup;
} else {
ignore_value(VIR_STRDUP(name, scsi_host->name));
}
} else if (adapter->type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
virStorageAdapterFCHostPtr fchost = &adapter->data.fchost;
if (!(name = virVHBAGetHostByWWN(NULL, fchost->wwnn, fchost->wwpn))) {
virReportError(VIR_ERR_XML_ERROR,
_("Failed to find SCSI host with wwnn='%s', "
"wwpn='%s'"), fchost->wwnn, fchost->wwpn);
}
}
cleanup:
VIR_FREE(parentaddr);
return name;
}
/**
* @name: Name from a wwnn/wwpn lookup
*
* Validate that the @name fetched from the wwnn/wwpn is a vHBA
* and not an HBA as that should be a configuration error. It's only
* possible to use an existing wwnn/wwpn of a vHBA because that's
* what someone would have created using the node device create via XML
* functionality. Using the HBA "just because" it has a wwnn/wwpn and
* the characteristics of a vHBA is just not valid
*
* Returns true if the @name is OK, false on error
*/
static bool
checkName(const char *name)
{
unsigned int host_num;
if (virSCSIHostGetNumber(name, &host_num) &&
virVHBAIsVportCapable(NULL, host_num))
return true;
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("the wwnn/wwpn for '%s' are assigned to an HBA"), name);
return false;
}
/*
* Using the host# name found via wwnn/wwpn lookup in the fc_host
* sysfs tree to get the parent 'scsi_host#' to ensure it matches.
*/
static bool
checkParent(const char *name,
const char *parent_name)
{
unsigned int host_num;
char *scsi_host_name = NULL;
char *vhba_parent = NULL;
bool retval = false;
virConnectPtr conn = NULL;
VIR_DEBUG("name=%s, parent_name=%s", name, parent_name);
conn = virGetConnectNodeDev();
if (!conn)
goto cleanup;
if (virSCSIHostGetNumber(parent_name, &host_num) < 0) {
virReportError(VIR_ERR_XML_ERROR,
_("parent '%s' is not properly formatted"),
parent_name);
goto cleanup;
}
if (!virVHBAPathExists(NULL, host_num)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("parent '%s' is not an fc_host for the wwnn/wwpn"),
parent_name);
goto cleanup;
}
if (virAsprintf(&scsi_host_name, "scsi_%s", name) < 0)
goto cleanup;
if (!(vhba_parent = virNodeDeviceGetParentName(conn, scsi_host_name)))
goto cleanup;
if (STRNEQ(parent_name, vhba_parent)) {
virReportError(VIR_ERR_XML_ERROR,
_("Parent attribute '%s' does not match parent '%s' "
"determined for the '%s' wwnn/wwpn lookup."),
parent_name, vhba_parent, name);
goto cleanup;
}
retval = true;
cleanup:
virObjectUnref(conn);
VIR_FREE(vhba_parent);
VIR_FREE(scsi_host_name);
return retval;
}
static int
createVport(virStoragePoolDefPtr def,
const char *configFile,
virStorageAdapterFCHostPtr fchost)
{
char *name = NULL;
virStoragePoolFCRefreshInfoPtr cbdata = NULL;
virThread thread;
int ret = -1;
VIR_DEBUG("configFile='%s' parent='%s', wwnn='%s' wwpn='%s'",
NULLSTR(configFile), NULLSTR(fchost->parent),
fchost->wwnn, fchost->wwpn);
/* If we find an existing HBA/vHBA within the fc_host sysfs
* using the wwnn/wwpn, then a nodedev is already created for
* this pool and we don't have to create the vHBA
*/
if ((name = virVHBAGetHostByWWN(NULL, fchost->wwnn, fchost->wwpn))) {
if (!(checkName(name)))
goto cleanup;
/* If a parent was provided, let's make sure the 'name' we've
* retrieved has the same parent. If not this will cause failure. */
if (!fchost->parent || checkParent(name, fchost->parent))
ret = 0;
goto cleanup;
}
/* Since we're creating the vHBA, then we need to manage removing it
* as well. Since we need this setting to "live" through a libvirtd
* restart, we need to save the persistent configuration. So if not
* already defined as YES, then force the issue.
*/
if (fchost->managed != VIR_TRISTATE_BOOL_YES) {
fchost->managed = VIR_TRISTATE_BOOL_YES;
if (configFile) {
if (virStoragePoolSaveConfig(configFile, def) < 0)
goto cleanup;
}
}
if (!(name = virNodeDeviceCreateVport(fchost)))
goto cleanup;
/* Creating our own VPORT didn't leave enough time to find any LUN's,
* so, let's create a thread whose job it is to call the FindLU's with
* retry logic set to true. If the thread isn't created, then no big
* deal since it's still possible to refresh the pool later.
*/
if (VIR_ALLOC(cbdata) == 0) {
memcpy(cbdata->pool_uuid, def->uuid, VIR_UUID_BUFLEN);
VIR_STEAL_PTR(cbdata->fchost_name, name);
if (virThreadCreate(&thread, false, virStoragePoolFCRefreshThread,
cbdata) < 0) {
/* Oh well - at least someone can still refresh afterwards */
VIR_DEBUG("Failed to create FC Pool Refresh Thread");
virStoragePoolFCRefreshDataFree(cbdata);
}
}
ret = 0;
cleanup:
VIR_FREE(name);
return ret;
}
static int
virStorageBackendSCSICheckPool(virStoragePoolObjPtr pool,
bool *isActive)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
char *path = NULL;
char *name = NULL;
unsigned int host;
int ret = -1;
*isActive = false;
if (!(name = getAdapterName(&def->source.adapter))) {
/* It's normal for the pool with "fc_host" type source
* adapter fails to get the adapter name, since the vHBA
* the adapter based on might be not created yet.
*/
if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
virResetLastError();
return 0;
} else {
return -1;
}
}
if (virSCSIHostGetNumber(name, &host) < 0)
goto cleanup;
if (virAsprintf(&path, "%s/host%d",
LINUX_SYSFS_SCSI_HOST_PREFIX, host) < 0)
goto cleanup;
*isActive = virFileExists(path);
ret = 0;
cleanup:
VIR_FREE(path);
VIR_FREE(name);
return ret;
}
static int
virStorageBackendSCSIRefreshPool(virStoragePoolObjPtr pool)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
char *name = NULL;
unsigned int host;
int ret = -1;
def->allocation = def->capacity = def->available = 0;
if (!(name = getAdapterName(&def->source.adapter)))
return -1;
if (virSCSIHostGetNumber(name, &host) < 0)
goto out;
VIR_DEBUG("Scanning host%u", host);
if (virStorageBackendSCSITriggerRescan(host) < 0)
goto out;
if (virStorageBackendSCSIFindLUs(pool, host) < 0)
goto out;
ret = 0;
out:
VIR_FREE(name);
return ret;
}
static int
virStorageBackendSCSIStartPool(virStoragePoolObjPtr pool)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
const char *configFile = virStoragePoolObjGetConfigFile(pool);
if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST)
return createVport(def, configFile,
&def->source.adapter.data.fchost);
return 0;
}
static int
virStorageBackendSCSIStopPool(virStoragePoolObjPtr pool)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
virConnectPtr conn;
int ret;
conn = virGetConnectNodeDev();
if (!conn)
return -1;
ret = virNodeDeviceDeleteVport(conn,
&def->source.adapter.data.fchost);
virObjectUnref(conn);
return ret;
}
return 0;
}
virStorageBackend virStorageBackendSCSI = {
.type = VIR_STORAGE_POOL_SCSI,
.checkPool = virStorageBackendSCSICheckPool,
.refreshPool = virStorageBackendSCSIRefreshPool,
.startPool = virStorageBackendSCSIStartPool,
.stopPool = virStorageBackendSCSIStopPool,
.uploadVol = virStorageBackendVolUploadLocal,
.downloadVol = virStorageBackendVolDownloadLocal,
.wipeVol = virStorageBackendVolWipeLocal,
};
int
virStorageBackendSCSIRegister(void)
{
return virStorageBackendRegister(&virStorageBackendSCSI);
}