/*
* virscsi.c: helper APIs for managing host SCSI devices
*
* Copyright (C) 2013-2014 Red Hat, Inc.
* Copyright (C) 2013 Fujitsu, Inc.
*
* 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
* .
*
* Authors:
* Han Cheng
* Osier Yang
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "virlog.h"
#include "virscsi.h"
#include "viralloc.h"
#include "virfile.h"
#include "virutil.h"
#include "virstring.h"
#include "virerror.h"
#define SYSFS_SCSI_DEVICES "/sys/bus/scsi/devices"
/* For virReportOOMError() and virReportSystemError() */
#define VIR_FROM_THIS VIR_FROM_NONE
VIR_LOG_INIT("util.scsi");
struct _virUsedByInfo {
char *drvname; /* which driver */
char *domname; /* which domain */
};
typedef struct _virUsedByInfo *virUsedByInfoPtr;
struct _virSCSIDevice {
unsigned int adapter;
unsigned int bus;
unsigned int target;
unsigned long long unit;
char *name; /* adapter:bus:target:unit */
char *id; /* model:vendor */
char *sg_path; /* e.g. /dev/sg2 */
virUsedByInfoPtr *used_by; /* driver:domain(s) using this dev */
size_t n_used_by; /* how many domains are using this dev */
bool readonly;
bool shareable;
};
struct _virSCSIDeviceList {
virObjectLockable parent;
size_t count;
virSCSIDevicePtr *devs;
};
static virClassPtr virSCSIDeviceListClass;
static void virSCSIDeviceListDispose(void *obj);
static int
virSCSIOnceInit(void)
{
if (!VIR_CLASS_NEW(virSCSIDeviceList, virClassForObjectLockable()))
return -1;
return 0;
}
VIR_ONCE_GLOBAL_INIT(virSCSI)
static int
virSCSIDeviceGetAdapterId(const char *adapter,
unsigned int *adapter_id)
{
if (STRPREFIX(adapter, "scsi_host") &&
virStrToLong_ui(adapter + strlen("scsi_host"),
NULL, 0, adapter_id) == 0)
return 0;
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot parse adapter '%s'"), adapter);
return -1;
}
char *
virSCSIDeviceGetSgName(const char *sysfs_prefix,
const char *adapter,
unsigned int bus,
unsigned int target,
unsigned long long unit)
{
DIR *dir = NULL;
struct dirent *entry;
char *path = NULL;
char *sg = NULL;
unsigned int adapter_id;
const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_DEVICES;
if (virSCSIDeviceGetAdapterId(adapter, &adapter_id) < 0)
return NULL;
if (virAsprintf(&path,
"%s/%d:%u:%u:%llu/scsi_generic",
prefix, adapter_id, bus, target, unit) < 0)
return NULL;
if (virDirOpen(&dir, path) < 0)
goto cleanup;
while (virDirRead(dir, &entry, path) > 0) {
/* Assume a single directory entry */
ignore_value(VIR_STRDUP(sg, entry->d_name));
break;
}
cleanup:
VIR_DIR_CLOSE(dir);
VIR_FREE(path);
return sg;
}
/* Returns device name (e.g. "sdc") on success, or NULL
* on failure.
*/
char *
virSCSIDeviceGetDevName(const char *sysfs_prefix,
const char *adapter,
unsigned int bus,
unsigned int target,
unsigned long long unit)
{
DIR *dir = NULL;
struct dirent *entry;
char *path = NULL;
char *name = NULL;
unsigned int adapter_id;
const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_DEVICES;
if (virSCSIDeviceGetAdapterId(adapter, &adapter_id) < 0)
return NULL;
if (virAsprintf(&path,
"%s/%d:%u:%u:%llu/block",
prefix, adapter_id, bus, target, unit) < 0)
return NULL;
if (virDirOpen(&dir, path) < 0)
goto cleanup;
while (virDirRead(dir, &entry, path) > 0) {
ignore_value(VIR_STRDUP(name, entry->d_name));
break;
}
cleanup:
VIR_DIR_CLOSE(dir);
VIR_FREE(path);
return name;
}
virSCSIDevicePtr
virSCSIDeviceNew(const char *sysfs_prefix,
const char *adapter,
unsigned int bus,
unsigned int target,
unsigned long long unit,
bool readonly,
bool shareable)
{
virSCSIDevicePtr dev, ret = NULL;
char *sg = NULL;
char *vendor_path = NULL;
char *model_path = NULL;
char *vendor = NULL;
char *model = NULL;
const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_DEVICES;
if (VIR_ALLOC(dev) < 0)
return NULL;
dev->bus = bus;
dev->target = target;
dev->unit = unit;
dev->readonly = readonly;
dev->shareable = shareable;
if (!(sg = virSCSIDeviceGetSgName(prefix, adapter, bus, target, unit)))
goto cleanup;
if (virSCSIDeviceGetAdapterId(adapter, &dev->adapter) < 0)
goto cleanup;
if (virAsprintf(&dev->name, "%d:%u:%u:%llu", dev->adapter,
dev->bus, dev->target, dev->unit) < 0 ||
virAsprintf(&dev->sg_path, "%s/%s",
sysfs_prefix ? sysfs_prefix : "/dev", sg) < 0)
goto cleanup;
if (!virFileExists(dev->sg_path)) {
virReportSystemError(errno,
_("SCSI device '%s': could not access %s"),
dev->name, dev->sg_path);
goto cleanup;
}
if (virAsprintf(&vendor_path,
"%s/%s/vendor", prefix, dev->name) < 0 ||
virAsprintf(&model_path,
"%s/%s/model", prefix, dev->name) < 0)
goto cleanup;
if (virFileReadAll(vendor_path, 1024, &vendor) < 0)
goto cleanup;
if (virFileReadAll(model_path, 1024, &model) < 0)
goto cleanup;
virTrimSpaces(vendor, NULL);
virTrimSpaces(model, NULL);
if (virAsprintf(&dev->id, "%s:%s", vendor, model) < 0)
goto cleanup;
ret = dev;
cleanup:
VIR_FREE(sg);
VIR_FREE(vendor);
VIR_FREE(model);
VIR_FREE(vendor_path);
VIR_FREE(model_path);
if (!ret)
virSCSIDeviceFree(dev);
return ret;
}
static void
virSCSIDeviceUsedByInfoFree(virUsedByInfoPtr used_by)
{
VIR_FREE(used_by->drvname);
VIR_FREE(used_by->domname);
VIR_FREE(used_by);
}
void
virSCSIDeviceFree(virSCSIDevicePtr dev)
{
size_t i;
if (!dev)
return;
VIR_FREE(dev->id);
VIR_FREE(dev->name);
VIR_FREE(dev->sg_path);
for (i = 0; i < dev->n_used_by; i++)
virSCSIDeviceUsedByInfoFree(dev->used_by[i]);
VIR_FREE(dev->used_by);
VIR_FREE(dev);
}
int
virSCSIDeviceSetUsedBy(virSCSIDevicePtr dev,
const char *drvname,
const char *domname)
{
virUsedByInfoPtr copy;
if (VIR_ALLOC(copy) < 0)
return -1;
if (VIR_STRDUP(copy->drvname, drvname) < 0 ||
VIR_STRDUP(copy->domname, domname) < 0)
goto cleanup;
if (VIR_APPEND_ELEMENT(dev->used_by, dev->n_used_by, copy) < 0)
goto cleanup;
return 0;
cleanup:
virSCSIDeviceUsedByInfoFree(copy);
return -1;
}
bool
virSCSIDeviceIsAvailable(virSCSIDevicePtr dev)
{
return dev->n_used_by == 0;
}
const char *
virSCSIDeviceGetName(virSCSIDevicePtr dev)
{
return dev->name;
}
const char *
virSCSIDeviceGetPath(virSCSIDevicePtr dev)
{
return dev->sg_path;
}
unsigned int
virSCSIDeviceGetAdapter(virSCSIDevicePtr dev)
{
return dev->adapter;
}
unsigned int
virSCSIDeviceGetBus(virSCSIDevicePtr dev)
{
return dev->bus;
}
unsigned int
virSCSIDeviceGetTarget(virSCSIDevicePtr dev)
{
return dev->target;
}
unsigned long long
virSCSIDeviceGetUnit(virSCSIDevicePtr dev)
{
return dev->unit;
}
bool
virSCSIDeviceGetReadonly(virSCSIDevicePtr dev)
{
return dev->readonly;
}
bool
virSCSIDeviceGetShareable(virSCSIDevicePtr dev)
{
return dev->shareable;
}
int
virSCSIDeviceFileIterate(virSCSIDevicePtr dev,
virSCSIDeviceFileActor actor,
void *opaque)
{
return (actor)(dev, dev->sg_path, opaque);
}
virSCSIDeviceListPtr
virSCSIDeviceListNew(void)
{
virSCSIDeviceListPtr list;
if (virSCSIInitialize() < 0)
return NULL;
if (!(list = virObjectLockableNew(virSCSIDeviceListClass)))
return NULL;
return list;
}
static void
virSCSIDeviceListDispose(void *obj)
{
virSCSIDeviceListPtr list = obj;
size_t i;
for (i = 0; i < list->count; i++)
virSCSIDeviceFree(list->devs[i]);
VIR_FREE(list->devs);
}
int
virSCSIDeviceListAdd(virSCSIDeviceListPtr list,
virSCSIDevicePtr dev)
{
if (virSCSIDeviceListFind(list, dev)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Device %s already exists"),
dev->name);
return -1;
}
return VIR_APPEND_ELEMENT(list->devs, list->count, dev);
}
virSCSIDevicePtr
virSCSIDeviceListGet(virSCSIDeviceListPtr list, int idx)
{
if (idx >= list->count || idx < 0)
return NULL;
return list->devs[idx];
}
size_t
virSCSIDeviceListCount(virSCSIDeviceListPtr list)
{
return list->count;
}
virSCSIDevicePtr
virSCSIDeviceListSteal(virSCSIDeviceListPtr list,
virSCSIDevicePtr dev)
{
virSCSIDevicePtr ret = NULL;
size_t i;
for (i = 0; i < list->count; i++) {
if (list->devs[i]->adapter == dev->adapter &&
list->devs[i]->bus == dev->bus &&
list->devs[i]->target == dev->target &&
list->devs[i]->unit == dev->unit) {
ret = list->devs[i];
VIR_DELETE_ELEMENT(list->devs, i, list->count);
break;
}
}
return ret;
}
void
virSCSIDeviceListDel(virSCSIDeviceListPtr list,
virSCSIDevicePtr dev,
const char *drvname,
const char *domname)
{
virSCSIDevicePtr tmp = NULL;
size_t i;
for (i = 0; i < dev->n_used_by; i++) {
if (STREQ_NULLABLE(dev->used_by[i]->drvname, drvname) &&
STREQ_NULLABLE(dev->used_by[i]->domname, domname)) {
if (dev->n_used_by > 1) {
virSCSIDeviceUsedByInfoFree(dev->used_by[i]);
VIR_DELETE_ELEMENT(dev->used_by, i, dev->n_used_by);
} else {
tmp = virSCSIDeviceListSteal(list, dev);
virSCSIDeviceFree(tmp);
}
break;
}
}
}
virSCSIDevicePtr
virSCSIDeviceListFind(virSCSIDeviceListPtr list,
virSCSIDevicePtr dev)
{
size_t i;
for (i = 0; i < list->count; i++) {
if (list->devs[i]->adapter == dev->adapter &&
list->devs[i]->bus == dev->bus &&
list->devs[i]->target == dev->target &&
list->devs[i]->unit == dev->unit)
return list->devs[i];
}
return NULL;
}