/*
* node_device_udev.c: node device enumeration - libudev implementation
*
* Copyright (C) 2009-2013 Red Hat, 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
* .
*
* Author: Dave Allan
*/
#include
#include
#include
#include
#include
#include "dirname.h"
#include "node_device_udev.h"
#include "virerror.h"
#include "node_device_conf.h"
#include "node_device_driver.h"
#include "driver.h"
#include "datatypes.h"
#include "virlog.h"
#include "viralloc.h"
#include "viruuid.h"
#include "virutil.h"
#include "virbuffer.h"
#include "virpci.h"
#define VIR_FROM_THIS VIR_FROM_NODEDEV
#ifndef TYPE_RAID
# define TYPE_RAID 12
#endif
struct _udevPrivate {
struct udev_monitor *udev_monitor;
int watch;
};
static virNodeDeviceDriverStatePtr driverState = NULL;
static int udevStrToLong_ull(char const *s,
char **end_ptr,
int base,
unsigned long long *result)
{
int ret = 0;
ret = virStrToLong_ull(s, end_ptr, base, result);
if (ret != 0) {
VIR_ERROR(_("Failed to convert '%s' to unsigned long long"), s);
} else {
VIR_DEBUG("Converted '%s' to unsigned long %llu", s, *result);
}
return ret;
}
static int udevStrToLong_ui(char const *s,
char **end_ptr,
int base,
unsigned int *result)
{
int ret = 0;
ret = virStrToLong_ui(s, end_ptr, base, result);
if (ret != 0) {
VIR_ERROR(_("Failed to convert '%s' to unsigned int"), s);
} else {
VIR_DEBUG("Converted '%s' to unsigned int %u", s, *result);
}
return ret;
}
static int udevStrToLong_i(char const *s,
char **end_ptr,
int base,
int *result)
{
int ret = 0;
ret = virStrToLong_i(s, end_ptr, base, result);
if (ret != 0) {
VIR_ERROR(_("Failed to convert '%s' to int"), s);
} else {
VIR_DEBUG("Converted '%s' to int %u", s, *result);
}
return ret;
}
/* This function allocates memory from the heap for the property
* value. That memory must be later freed by some other code. */
static int udevGetDeviceProperty(struct udev_device *udev_device,
const char *property_key,
char **property_value)
{
const char *udev_value = NULL;
int ret = PROPERTY_FOUND;
udev_value = udev_device_get_property_value(udev_device, property_key);
if (udev_value == NULL) {
VIR_DEBUG("udev reports device '%s' does not have property '%s'",
udev_device_get_sysname(udev_device), property_key);
ret = PROPERTY_MISSING;
goto out;
}
/* If this allocation is changed, the comment at the beginning
* of the function must also be changed. */
*property_value = strdup(udev_value);
if (*property_value == NULL) {
VIR_ERROR(_("Failed to allocate memory for property value for "
"property key '%s' on device with sysname '%s'"),
property_key, udev_device_get_sysname(udev_device));
virReportOOMError();
ret = PROPERTY_ERROR;
goto out;
}
VIR_DEBUG("Found property key '%s' value '%s' "
"for device with sysname '%s'",
property_key, *property_value,
udev_device_get_sysname(udev_device));
out:
return ret;
}
static int udevGetStringProperty(struct udev_device *udev_device,
const char *property_key,
char **value)
{
return udevGetDeviceProperty(udev_device, property_key, value);
}
static int udevGetIntProperty(struct udev_device *udev_device,
const char *property_key,
int *value,
int base)
{
char *udev_value = NULL;
int ret = PROPERTY_FOUND;
ret = udevGetDeviceProperty(udev_device, property_key, &udev_value);
if (ret == PROPERTY_FOUND) {
if (udevStrToLong_i(udev_value, NULL, base, value) != 0) {
ret = PROPERTY_ERROR;
}
}
VIR_FREE(udev_value);
return ret;
}
static int udevGetUintProperty(struct udev_device *udev_device,
const char *property_key,
unsigned int *value,
int base)
{
char *udev_value = NULL;
int ret = PROPERTY_FOUND;
ret = udevGetDeviceProperty(udev_device, property_key, &udev_value);
if (ret == PROPERTY_FOUND) {
if (udevStrToLong_ui(udev_value, NULL, base, value) != 0) {
ret = PROPERTY_ERROR;
}
}
VIR_FREE(udev_value);
return ret;
}
/* This function allocates memory from the heap for the property
* value. That memory must be later freed by some other code. */
static int udevGetDeviceSysfsAttr(struct udev_device *udev_device,
const char *attr_name,
char **attr_value)
{
const char *udev_value = NULL;
int ret = PROPERTY_FOUND;
udev_value = udev_device_get_sysattr_value(udev_device, attr_name);
if (udev_value == NULL) {
VIR_DEBUG("udev reports device '%s' does not have sysfs attr '%s'",
udev_device_get_sysname(udev_device), attr_name);
ret = PROPERTY_MISSING;
goto out;
}
/* If this allocation is changed, the comment at the beginning
* of the function must also be changed. */
*attr_value = strdup(udev_value);
if (*attr_value == NULL) {
VIR_ERROR(_("Failed to allocate memory for sysfs attribute value for "
"sysfs attribute '%s' on device with sysname '%s'"),
attr_name, udev_device_get_sysname(udev_device));
virReportOOMError();
ret = PROPERTY_ERROR;
goto out;
}
VIR_DEBUG("Found sysfs attribute '%s' value '%s' "
"for device with sysname '%s'",
attr_name, *attr_value,
udev_device_get_sysname(udev_device));
out:
return ret;
}
static int udevGetStringSysfsAttr(struct udev_device *udev_device,
const char *attr_name,
char **value)
{
char *tmp = NULL;
int ret = PROPERTY_MISSING;
ret = udevGetDeviceSysfsAttr(udev_device, attr_name, &tmp);
if (tmp != NULL && (STREQ(tmp, ""))) {
VIR_FREE(tmp);
tmp = NULL;
ret = PROPERTY_MISSING;
}
*value = tmp;
return ret;
}
static int udevGetIntSysfsAttr(struct udev_device *udev_device,
const char *attr_name,
int *value,
int base)
{
char *udev_value = NULL;
int ret = PROPERTY_FOUND;
ret = udevGetDeviceSysfsAttr(udev_device, attr_name, &udev_value);
if (ret == PROPERTY_FOUND) {
if (udevStrToLong_i(udev_value, NULL, base, value) != 0) {
ret = PROPERTY_ERROR;
}
}
VIR_FREE(udev_value);
return ret;
}
static int udevGetUintSysfsAttr(struct udev_device *udev_device,
const char *attr_name,
unsigned int *value,
int base)
{
char *udev_value = NULL;
int ret = PROPERTY_FOUND;
ret = udevGetDeviceSysfsAttr(udev_device, attr_name, &udev_value);
if (ret == PROPERTY_FOUND) {
if (udevStrToLong_ui(udev_value, NULL, base, value) != 0) {
ret = PROPERTY_ERROR;
}
}
VIR_FREE(udev_value);
return ret;
}
static int udevGetUint64SysfsAttr(struct udev_device *udev_device,
const char *attr_name,
unsigned long long *value)
{
char *udev_value = NULL;
int ret = PROPERTY_FOUND;
ret = udevGetDeviceSysfsAttr(udev_device, attr_name, &udev_value);
if (ret == PROPERTY_FOUND) {
if (udevStrToLong_ull(udev_value, NULL, 0, value) != 0) {
ret = PROPERTY_ERROR;
}
}
VIR_FREE(udev_value);
return ret;
}
static int udevGenerateDeviceName(struct udev_device *device,
virNodeDeviceDefPtr def,
const char *s)
{
int ret = 0, i = 0;
virBuffer buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, "%s_%s",
udev_device_get_subsystem(device),
udev_device_get_sysname(device));
if (s != NULL) {
virBufferAsprintf(&buf, "_%s", s);
}
if (virBufferError(&buf)) {
virBufferFreeAndReset(&buf);
VIR_ERROR(_("Buffer error when generating device name for device "
"with sysname '%s'"), udev_device_get_sysname(device));
ret = -1;
}
def->name = virBufferContentAndReset(&buf);
for (i = 0; i < strlen(def->name) ; i++) {
if (!(c_isalnum(*(def->name + i)))) {
*(def->name + i) = '_';
}
}
return ret;
}
static void udevLogFunction(struct udev *udev ATTRIBUTE_UNUSED,
int priority ATTRIBUTE_UNUSED,
const char *file,
int line,
const char *fn,
const char *fmt,
va_list args)
{
VIR_ERROR_INT(VIR_LOG_FROM_LIBRARY, file, line, fn, fmt, args);
}
static int udevTranslatePCIIds(unsigned int vendor,
unsigned int product,
char **vendor_string,
char **product_string)
{
int ret = -1;
struct pci_id_match m;
const char *vendor_name = NULL, *device_name = NULL;
m.vendor_id = vendor;
m.device_id = product;
m.subvendor_id = PCI_MATCH_ANY;
m.subdevice_id = PCI_MATCH_ANY;
m.device_class = 0;
m.device_class_mask = 0;
m.match_data = 0;
/* pci_get_strings returns void */
pci_get_strings(&m,
&device_name,
&vendor_name,
NULL,
NULL);
if (vendor_name != NULL) {
*vendor_string = strdup(vendor_name);
if (*vendor_string == NULL) {
virReportOOMError();
goto out;
}
}
if (device_name != NULL) {
*product_string = strdup(device_name);
if (*product_string == NULL) {
virReportOOMError();
goto out;
}
}
ret = 0;
out:
return ret;
}
static int udevProcessPCI(struct udev_device *device,
virNodeDeviceDefPtr def)
{
const char *syspath = NULL;
union _virNodeDevCapData *data = &def->caps->data;
int ret = -1;
char *p;
int rc;
syspath = udev_device_get_syspath(device);
if (udevGetUintProperty(device,
"PCI_CLASS",
&data->pci_dev.class,
16) == PROPERTY_ERROR) {
goto out;
}
p = strrchr(syspath, '/');
if ((p == NULL) || (udevStrToLong_ui(p+1,
&p,
16,
&data->pci_dev.domain) == -1)) {
goto out;
}
if ((p == NULL) || (udevStrToLong_ui(p+1,
&p,
16,
&data->pci_dev.bus) == -1)) {
goto out;
}
if ((p == NULL) || (udevStrToLong_ui(p+1,
&p,
16,
&data->pci_dev.slot) == -1)) {
goto out;
}
if ((p == NULL) || (udevStrToLong_ui(p+1,
&p,
16,
&data->pci_dev.function) == -1)) {
goto out;
}
if (udevGetUintSysfsAttr(device,
"vendor",
&data->pci_dev.vendor,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintSysfsAttr(device,
"device",
&data->pci_dev.product,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevTranslatePCIIds(data->pci_dev.vendor,
data->pci_dev.product,
&data->pci_dev.vendor_name,
&data->pci_dev.product_name) != 0) {
goto out;
}
if (udevGenerateDeviceName(device, def, NULL) != 0) {
goto out;
}
if (!virPCIGetPhysicalFunction(syspath, &data->pci_dev.physical_function))
data->pci_dev.flags |= VIR_NODE_DEV_CAP_FLAG_PCI_PHYSICAL_FUNCTION;
rc = virPCIGetVirtualFunctions(syspath,
&data->pci_dev.virtual_functions,
&data->pci_dev.num_virtual_functions);
/* Out of memory */
if (rc < 0)
goto out;
else if (!rc && (data->pci_dev.num_virtual_functions > 0))
data->pci_dev.flags |= VIR_NODE_DEV_CAP_FLAG_PCI_VIRTUAL_FUNCTION;
ret = 0;
out:
return ret;
}
static int udevProcessUSBDevice(struct udev_device *device,
virNodeDeviceDefPtr def)
{
union _virNodeDevCapData *data = &def->caps->data;
int ret = -1;
if (udevGetUintProperty(device,
"BUSNUM",
&data->usb_dev.bus,
10) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintProperty(device,
"DEVNUM",
&data->usb_dev.device,
10) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintProperty(device,
"ID_VENDOR_ID",
&data->usb_dev.vendor,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"manufacturer",
&data->usb_dev.vendor_name) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintProperty(device,
"ID_MODEL_ID",
&data->usb_dev.product,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"product",
&data->usb_dev.product_name) == PROPERTY_ERROR) {
goto out;
}
if (udevGenerateDeviceName(device, def, NULL) != 0) {
goto out;
}
ret = 0;
out:
return ret;
}
static int udevProcessUSBInterface(struct udev_device *device,
virNodeDeviceDefPtr def)
{
int ret = -1;
union _virNodeDevCapData *data = &def->caps->data;
if (udevGetUintSysfsAttr(device,
"bInterfaceNumber",
&data->usb_if.number,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintSysfsAttr(device,
"bInterfaceClass",
&data->usb_if._class,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintSysfsAttr(device,
"bInterfaceSubClass",
&data->usb_if.subclass,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintSysfsAttr(device,
"bInterfaceProtocol",
&data->usb_if.protocol,
16) == PROPERTY_ERROR) {
goto out;
}
if (udevGenerateDeviceName(device, def, NULL) != 0) {
goto out;
}
ret = 0;
out:
return ret;
}
static int udevProcessNetworkInterface(struct udev_device *device,
virNodeDeviceDefPtr def)
{
int ret = -1;
const char *devtype = udev_device_get_devtype(device);
union _virNodeDevCapData *data = &def->caps->data;
if (devtype && STREQ(devtype, "wlan")) {
data->net.subtype = VIR_NODE_DEV_CAP_NET_80211;
} else {
data->net.subtype = VIR_NODE_DEV_CAP_NET_80203;
}
if (udevGetStringProperty(device,
"INTERFACE",
&data->net.ifname) == PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"address",
&data->net.address) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUintSysfsAttr(device,
"addr_len",
&data->net.address_len,
0) == PROPERTY_ERROR) {
goto out;
}
if (udevGenerateDeviceName(device, def, data->net.address) != 0) {
goto out;
}
ret = 0;
out:
return ret;
}
static int udevProcessSCSIHost(struct udev_device *device ATTRIBUTE_UNUSED,
virNodeDeviceDefPtr def)
{
int ret = -1;
union _virNodeDevCapData *data = &def->caps->data;
char *filename = NULL;
filename = last_component(def->sysfs_path);
if (!STRPREFIX(filename, "host")) {
VIR_ERROR(_("SCSI host found, but its udev name '%s' does "
"not begin with 'host'"), filename);
goto out;
}
if (udevStrToLong_ui(filename + strlen("host"),
NULL,
0,
&data->scsi_host.host) == -1) {
goto out;
}
detect_scsi_host_caps(&def->caps->data);
if (udevGenerateDeviceName(device, def, NULL) != 0) {
goto out;
}
ret = 0;
out:
return ret;
}
static int udevProcessSCSITarget(struct udev_device *device ATTRIBUTE_UNUSED,
virNodeDeviceDefPtr def)
{
int ret = -1;
const char *sysname = NULL;
union _virNodeDevCapData *data = &def->caps->data;
sysname = udev_device_get_sysname(device);
data->scsi_target.name = strdup(sysname);
if (data->scsi_target.name == NULL) {
virReportOOMError();
goto out;
}
if (udevGenerateDeviceName(device, def, NULL) != 0) {
goto out;
}
ret = 0;
out:
return ret;
}
static int udevGetSCSIType(virNodeDeviceDefPtr def ATTRIBUTE_UNUSED,
unsigned int type, char **typestring)
{
int ret = 0;
int foundtype = 1;
*typestring = NULL;
switch (type) {
case TYPE_DISK:
*typestring = strdup("disk");
break;
case TYPE_TAPE:
*typestring = strdup("tape");
break;
case TYPE_PROCESSOR:
*typestring = strdup("processor");
break;
case TYPE_WORM:
*typestring = strdup("worm");
break;
case TYPE_ROM:
*typestring = strdup("cdrom");
break;
case TYPE_SCANNER:
*typestring = strdup("scanner");
break;
case TYPE_MOD:
*typestring = strdup("mod");
break;
case TYPE_MEDIUM_CHANGER:
*typestring = strdup("changer");
break;
case TYPE_ENCLOSURE:
*typestring = strdup("enclosure");
break;
case TYPE_RAID:
*typestring = strdup("raid");
break;
case TYPE_NO_LUN:
default:
foundtype = 0;
break;
}
if (*typestring == NULL) {
if (foundtype == 1) {
ret = -1;
virReportOOMError();
} else {
VIR_DEBUG("Failed to find SCSI device type %d for %s",
type, def->sysfs_path);
}
}
return ret;
}
static int udevProcessSCSIDevice(struct udev_device *device ATTRIBUTE_UNUSED,
virNodeDeviceDefPtr def)
{
int ret = -1;
unsigned int tmp = 0;
union _virNodeDevCapData *data = &def->caps->data;
char *filename = NULL, *p = NULL;
filename = last_component(def->sysfs_path);
if (udevStrToLong_ui(filename, &p, 10, &data->scsi.host) == -1) {
goto out;
}
if ((p == NULL) || (udevStrToLong_ui(p+1,
&p,
10,
&data->scsi.bus) == -1)) {
goto out;
}
if ((p == NULL) || (udevStrToLong_ui(p+1,
&p,
10,
&data->scsi.target) == -1)) {
goto out;
}
if ((p == NULL) || (udevStrToLong_ui(p+1,
&p,
10,
&data->scsi.lun) == -1)) {
goto out;
}
switch (udevGetUintSysfsAttr(device, "type", &tmp, 0)) {
case PROPERTY_FOUND:
if (udevGetSCSIType(def, tmp, &data->scsi.type) == -1) {
goto out;
}
break;
case PROPERTY_MISSING:
break; /* No type is not an error */
case PROPERTY_ERROR:
default:
goto out;
break;
}
if (udevGenerateDeviceName(device, def, NULL) != 0) {
goto out;
}
ret = 0;
out:
if (ret != 0) {
VIR_ERROR(_("Failed to process SCSI device with sysfs path '%s'"),
def->sysfs_path);
}
return ret;
}
static int udevProcessDisk(struct udev_device *device,
virNodeDeviceDefPtr def)
{
union _virNodeDevCapData *data = &def->caps->data;
int ret = 0;
if (udevGetUint64SysfsAttr(device,
"size",
&data->storage.num_blocks) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUint64SysfsAttr(device,
"queue/logical_block_size",
&data->storage.logical_block_size)
== PROPERTY_ERROR) {
goto out;
}
data->storage.size = data->storage.num_blocks *
data->storage.logical_block_size;
out:
return ret;
}
static int udevProcessRemoveableMedia(struct udev_device *device,
virNodeDeviceDefPtr def,
int has_media)
{
union _virNodeDevCapData *data = &def->caps->data;
int tmp_int = 0, ret = 0;
if ((udevGetIntSysfsAttr(device, "removable", &tmp_int, 0) == PROPERTY_FOUND) &&
(tmp_int == 1)) {
def->caps->data.storage.flags |= VIR_NODE_DEV_CAP_STORAGE_REMOVABLE;
}
if (has_media) {
def->caps->data.storage.flags |=
VIR_NODE_DEV_CAP_STORAGE_REMOVABLE_MEDIA_AVAILABLE;
if (udevGetStringProperty(device, "ID_FS_LABEL",
&data->storage.media_label) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUint64SysfsAttr(device,
"size",
&data->storage.num_blocks) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUint64SysfsAttr(device,
"queue/logical_block_size",
&data->storage.logical_block_size) == PROPERTY_ERROR) {
goto out;
}
/* XXX This calculation is wrong for the qemu virtual cdrom
* which reports the size in 512 byte blocks, but the logical
* block size as 2048. I don't have a physical cdrom on a
* devel system to see how they behave. */
def->caps->data.storage.removable_media_size =
def->caps->data.storage.num_blocks *
def->caps->data.storage.logical_block_size;
}
out:
return ret;
}
static int udevProcessCDROM(struct udev_device *device,
virNodeDeviceDefPtr def)
{
int ret = -1;
int tmp_int = 0;
int has_media = 0;
/* NB: the drive_type string provided by udev is different from
* that provided by HAL; now it's "cd" instead of "cdrom" We
* change it to cdrom to preserve compatibility with earlier
* versions of libvirt. */
VIR_FREE(def->caps->data.storage.drive_type);
def->caps->data.storage.drive_type = strdup("cdrom");
if (def->caps->data.storage.drive_type == NULL) {
virReportOOMError();
goto out;
}
if ((udevGetIntProperty(device, "ID_CDROM_MEDIA",
&tmp_int, 0) == PROPERTY_FOUND))
has_media = tmp_int;
ret = udevProcessRemoveableMedia(device, def, has_media);
out:
return ret;
}
static int udevProcessFloppy(struct udev_device *device,
virNodeDeviceDefPtr def)
{
int tmp_int = 0;
int has_media = 0;
char *tmp_str = NULL;
if ((udevGetIntProperty(device, "DKD_MEDIA_AVAILABLE",
&tmp_int, 0) == PROPERTY_FOUND))
/* USB floppy */
has_media = tmp_int;
else if (udevGetStringProperty(device, "ID_FS_LABEL",
&tmp_str) == PROPERTY_FOUND) {
/* Legacy floppy */
has_media = 1;
VIR_FREE(tmp_str);
}
return udevProcessRemoveableMedia(device, def, has_media);
}
static int udevProcessSD(struct udev_device *device,
virNodeDeviceDefPtr def)
{
union _virNodeDevCapData *data = &def->caps->data;
int ret = 0;
if (udevGetUint64SysfsAttr(device,
"size",
&data->storage.num_blocks) == PROPERTY_ERROR) {
goto out;
}
if (udevGetUint64SysfsAttr(device,
"queue/logical_block_size",
&data->storage.logical_block_size)
== PROPERTY_ERROR) {
goto out;
}
data->storage.size = data->storage.num_blocks *
data->storage.logical_block_size;
out:
return ret;
}
/* This function exists to deal with the case in which a driver does
* not provide a device type in the usual place, but udev told us it's
* a storage device, and we can make a good guess at what kind of
* storage device it is from other information that is provided. */
static int udevKludgeStorageType(virNodeDeviceDefPtr def)
{
int ret = -1;
VIR_DEBUG("Could not find definitive storage type for device "
"with sysfs path '%s', trying to guess it",
def->sysfs_path);
if (STRPREFIX(def->caps->data.storage.block, "/dev/vd")) {
/* virtio disk */
def->caps->data.storage.drive_type = strdup("disk");
if (def->caps->data.storage.drive_type != NULL) {
ret = 0;
}
}
if (ret != 0) {
VIR_DEBUG("Could not determine storage type for device "
"with sysfs path '%s'", def->sysfs_path);
} else {
VIR_DEBUG("Found storage type '%s' for device "
"with sysfs path '%s'",
def->caps->data.storage.drive_type,
def->sysfs_path);
}
return ret;
}
static void udevStripSpaces(char *s)
{
if (s == NULL) {
return;
}
while (virFileStripSuffix(s, " ")) {
/* do nothing */
;
}
return;
}
static int udevProcessStorage(struct udev_device *device,
virNodeDeviceDefPtr def)
{
union _virNodeDevCapData *data = &def->caps->data;
int ret = -1;
const char* devnode;
devnode = udev_device_get_devnode(device);
if (!devnode) {
VIR_DEBUG("No devnode for '%s'", udev_device_get_devpath(device));
goto out;
}
data->storage.block = strdup(devnode);
if (udevGetStringProperty(device,
"ID_BUS",
&data->storage.bus) == PROPERTY_ERROR) {
goto out;
}
if (udevGetStringProperty(device,
"ID_SERIAL",
&data->storage.serial) == PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"device/vendor",
&data->storage.vendor) == PROPERTY_ERROR) {
goto out;
}
udevStripSpaces(def->caps->data.storage.vendor);
if (udevGetStringSysfsAttr(device,
"device/model",
&data->storage.model) == PROPERTY_ERROR) {
goto out;
}
udevStripSpaces(def->caps->data.storage.model);
/* There is no equivalent of the hotpluggable property in libudev,
* but storage is going toward a world in which hotpluggable is
* expected, so I don't see a problem with not having a property
* for it. */
if (udevGetStringProperty(device,
"ID_TYPE",
&data->storage.drive_type) != PROPERTY_FOUND) {
int tmp_int = 0;
/* All floppy drives have the ID_DRIVE_FLOPPY prop. This is
* needed since legacy floppies don't have a drive_type */
if ((udevGetIntProperty(device, "ID_DRIVE_FLOPPY",
&tmp_int, 0) == PROPERTY_FOUND) &&
(tmp_int == 1)) {
data->storage.drive_type = strdup("floppy");
if (!data->storage.drive_type)
goto out;
} else if ((udevGetIntProperty(device, "ID_DRIVE_FLASH_SD",
&tmp_int, 0) == PROPERTY_FOUND) &&
(tmp_int == 1)) {
data->storage.drive_type = strdup("sd");
if (!data->storage.drive_type)
goto out;
} else {
/* If udev doesn't have it, perhaps we can guess it. */
if (udevKludgeStorageType(def) != 0) {
goto out;
}
}
}
if (STREQ(def->caps->data.storage.drive_type, "cd")) {
ret = udevProcessCDROM(device, def);
} else if (STREQ(def->caps->data.storage.drive_type, "disk")) {
ret = udevProcessDisk(device, def);
} else if (STREQ(def->caps->data.storage.drive_type, "floppy")) {
ret = udevProcessFloppy(device, def);
} else if (STREQ(def->caps->data.storage.drive_type, "sd")) {
ret = udevProcessSD(device, def);
} else {
VIR_DEBUG("Unsupported storage type '%s'",
def->caps->data.storage.drive_type);
goto out;
}
if (udevGenerateDeviceName(device, def, data->storage.serial) != 0) {
goto out;
}
out:
VIR_DEBUG("Storage ret=%d", ret);
return ret;
}
static int udevGetDeviceType(struct udev_device *device,
enum virNodeDevCapType *type)
{
const char *devtype = NULL;
char *tmp_string = NULL;
unsigned int tmp = 0;
int ret = 0;
devtype = udev_device_get_devtype(device);
VIR_DEBUG("Found device type '%s' for device '%s'",
NULLSTR(devtype), udev_device_get_sysname(device));
if (devtype != NULL && STREQ(devtype, "usb_device")) {
*type = VIR_NODE_DEV_CAP_USB_DEV;
goto out;
}
if (devtype != NULL && STREQ(devtype, "usb_interface")) {
*type = VIR_NODE_DEV_CAP_USB_INTERFACE;
goto out;
}
if (devtype != NULL && STREQ(devtype, "scsi_host")) {
*type = VIR_NODE_DEV_CAP_SCSI_HOST;
goto out;
}
if (devtype != NULL && STREQ(devtype, "scsi_target")) {
*type = VIR_NODE_DEV_CAP_SCSI_TARGET;
goto out;
}
if (devtype != NULL && STREQ(devtype, "scsi_device")) {
*type = VIR_NODE_DEV_CAP_SCSI;
goto out;
}
if (devtype != NULL && STREQ(devtype, "disk")) {
*type = VIR_NODE_DEV_CAP_STORAGE;
goto out;
}
if (devtype != NULL && STREQ(devtype, "wlan")) {
*type = VIR_NODE_DEV_CAP_NET;
goto out;
}
if (udevGetUintProperty(device, "PCI_CLASS", &tmp, 16) == PROPERTY_FOUND) {
*type = VIR_NODE_DEV_CAP_PCI_DEV;
goto out;
}
/* It does not appear that wired network interfaces set the
* DEVTYPE property. USB devices also have an INTERFACE property,
* but they do set DEVTYPE, so if devtype is NULL and the
* INTERFACE property exists, we have a network device. */
if (devtype == NULL &&
udevGetStringProperty(device,
"INTERFACE",
&tmp_string) == PROPERTY_FOUND) {
VIR_FREE(tmp_string);
*type = VIR_NODE_DEV_CAP_NET;
goto out;
}
VIR_DEBUG("Could not determine device type for device "
"with sysfs path '%s'",
udev_device_get_sysname(device));
ret = -1;
out:
return ret;
}
static int udevGetDeviceDetails(struct udev_device *device,
virNodeDeviceDefPtr def)
{
int ret = 0;
switch (def->caps->type) {
case VIR_NODE_DEV_CAP_SYSTEM:
/* There's no libudev equivalent of system, so ignore it. */
break;
case VIR_NODE_DEV_CAP_PCI_DEV:
ret = udevProcessPCI(device, def);
break;
case VIR_NODE_DEV_CAP_USB_DEV:
ret = udevProcessUSBDevice(device, def);
break;
case VIR_NODE_DEV_CAP_USB_INTERFACE:
ret = udevProcessUSBInterface(device, def);
break;
case VIR_NODE_DEV_CAP_NET:
ret = udevProcessNetworkInterface(device, def);
break;
case VIR_NODE_DEV_CAP_SCSI_HOST:
ret = udevProcessSCSIHost(device, def);
break;
case VIR_NODE_DEV_CAP_SCSI_TARGET:
ret = udevProcessSCSITarget(device, def);
break;
case VIR_NODE_DEV_CAP_SCSI:
ret = udevProcessSCSIDevice(device, def);
break;
case VIR_NODE_DEV_CAP_STORAGE:
ret = udevProcessStorage(device, def);
break;
default:
VIR_ERROR(_("Unknown device type %d"), def->caps->type);
ret = -1;
break;
}
return ret;
}
static int udevRemoveOneDevice(struct udev_device *device)
{
virNodeDeviceObjPtr dev = NULL;
const char *name = NULL;
int ret = 0;
name = udev_device_get_syspath(device);
dev = virNodeDeviceFindBySysfsPath(&driverState->devs, name);
if (dev != NULL) {
VIR_DEBUG("Removing device '%s' with sysfs path '%s'",
dev->def->name, name);
virNodeDeviceObjRemove(&driverState->devs, dev);
} else {
VIR_DEBUG("Failed to find device to remove that has udev name '%s'",
name);
ret = -1;
}
return ret;
}
static int udevSetParent(struct udev_device *device,
virNodeDeviceDefPtr def)
{
struct udev_device *parent_device = NULL;
const char *parent_sysfs_path = NULL;
virNodeDeviceObjPtr dev = NULL;
int ret = -1;
parent_device = device;
do {
parent_device = udev_device_get_parent(parent_device);
if (parent_device == NULL) {
break;
}
parent_sysfs_path = udev_device_get_syspath(parent_device);
if (parent_sysfs_path == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Could not get syspath for parent of '%s'"),
udev_device_get_syspath(parent_device));
goto out;
}
dev = virNodeDeviceFindBySysfsPath(&driverState->devs,
parent_sysfs_path);
if (dev != NULL) {
def->parent = strdup(dev->def->name);
virNodeDeviceObjUnlock(dev);
if (def->parent == NULL) {
virReportOOMError();
goto out;
}
def->parent_sysfs_path = strdup(parent_sysfs_path);
if (def->parent_sysfs_path == NULL) {
virReportOOMError();
goto out;
}
}
} while (def->parent == NULL && parent_device != NULL);
if (def->parent == NULL) {
def->parent = strdup("computer");
}
if (def->parent == NULL) {
virReportOOMError();
goto out;
}
ret = 0;
out:
return ret;
}
static int udevAddOneDevice(struct udev_device *device)
{
virNodeDeviceDefPtr def = NULL;
virNodeDeviceObjPtr dev = NULL;
int ret = -1;
if (VIR_ALLOC(def) != 0) {
virReportOOMError();
goto out;
}
def->sysfs_path = strdup(udev_device_get_syspath(device));
if (udevGetStringProperty(device,
"DRIVER",
&def->driver) == PROPERTY_ERROR) {
goto out;
}
if (VIR_ALLOC(def->caps) != 0) {
virReportOOMError();
goto out;
}
if (udevGetDeviceType(device, &def->caps->type) != 0) {
goto out;
}
if (udevGetDeviceDetails(device, def) != 0) {
goto out;
}
if (udevSetParent(device, def) != 0) {
goto out;
}
/* If this is a device change, the old definition will be freed
* and the current definition will take its place. */
dev = virNodeDeviceAssignDef(&driverState->devs, def);
if (dev == NULL) {
VIR_ERROR(_("Failed to create device for '%s'"), def->name);
goto out;
}
virNodeDeviceObjUnlock(dev);
ret = 0;
out:
if (ret != 0) {
VIR_DEBUG("Discarding device %d %p %s", ret, def,
def ? def->sysfs_path : "");
virNodeDeviceDefFree(def);
}
return ret;
}
static int udevProcessDeviceListEntry(struct udev *udev,
struct udev_list_entry *list_entry)
{
struct udev_device *device;
const char *name = NULL;
int ret = -1;
name = udev_list_entry_get_name(list_entry);
device = udev_device_new_from_syspath(udev, name);
if (device != NULL) {
if (udevAddOneDevice(device) != 0) {
VIR_DEBUG("Failed to create node device for udev device '%s'",
name);
}
ret = 0;
}
udev_device_unref(device);
return ret;
}
static int udevEnumerateDevices(struct udev *udev)
{
struct udev_enumerate *udev_enumerate = NULL;
struct udev_list_entry *list_entry = NULL;
int ret = 0;
udev_enumerate = udev_enumerate_new(udev);
ret = udev_enumerate_scan_devices(udev_enumerate);
if (0 != ret) {
VIR_ERROR(_("udev scan devices returned %d"), ret);
goto out;
}
udev_list_entry_foreach(list_entry,
udev_enumerate_get_list_entry(udev_enumerate)) {
udevProcessDeviceListEntry(udev, list_entry);
}
out:
udev_enumerate_unref(udev_enumerate);
return ret;
}
static int nodeDeviceStateCleanup(void)
{
int ret = 0;
udevPrivate *priv = NULL;
struct udev_monitor *udev_monitor = NULL;
struct udev *udev = NULL;
if (driverState) {
nodeDeviceLock(driverState);
priv = driverState->privateData;
if (priv->watch != -1)
virEventRemoveHandle(priv->watch);
udev_monitor = DRV_STATE_UDEV_MONITOR(driverState);
if (udev_monitor != NULL) {
udev = udev_monitor_get_udev(udev_monitor);
udev_monitor_unref(udev_monitor);
}
if (udev != NULL) {
udev_unref(udev);
}
virNodeDeviceObjListFree(&driverState->devs);
nodeDeviceUnlock(driverState);
virMutexDestroy(&driverState->lock);
VIR_FREE(driverState);
VIR_FREE(priv);
} else {
ret = -1;
}
#if defined __s390__ || defined __s390x_
/* Nothing was initialized, nothing needs to be cleaned up */
#else
/* pci_system_cleanup returns void */
pci_system_cleanup();
#endif
return ret;
}
static void udevEventHandleCallback(int watch ATTRIBUTE_UNUSED,
int fd,
int events ATTRIBUTE_UNUSED,
void *data ATTRIBUTE_UNUSED)
{
struct udev_device *device = NULL;
struct udev_monitor *udev_monitor = DRV_STATE_UDEV_MONITOR(driverState);
const char *action = NULL;
int udev_fd = -1;
nodeDeviceLock(driverState);
udev_fd = udev_monitor_get_fd(udev_monitor);
if (fd != udev_fd) {
VIR_ERROR(_("File descriptor returned by udev %d does not "
"match node device file descriptor %d"), fd, udev_fd);
goto out;
}
device = udev_monitor_receive_device(udev_monitor);
if (device == NULL) {
VIR_ERROR(_("udev_monitor_receive_device returned NULL"));
goto out;
}
action = udev_device_get_action(device);
VIR_DEBUG("udev action: '%s'", action);
if (STREQ(action, "add") || STREQ(action, "change")) {
udevAddOneDevice(device);
goto out;
}
if (STREQ(action, "remove")) {
udevRemoveOneDevice(device);
goto out;
}
out:
udev_device_unref(device);
nodeDeviceUnlock(driverState);
return;
}
/* DMI is intel-compatible specific */
#if defined(__x86_64__) || defined(__i386__) || defined(__amd64__)
static void
udevGetDMIData(union _virNodeDevCapData *data)
{
struct udev *udev = NULL;
struct udev_device *device = NULL;
char *tmp = NULL;
udev = udev_monitor_get_udev(DRV_STATE_UDEV_MONITOR(driverState));
device = udev_device_new_from_syspath(udev, DMI_DEVPATH);
if (device == NULL) {
device = udev_device_new_from_syspath(udev, DMI_DEVPATH_FALLBACK);
if (device == NULL) {
VIR_ERROR(_("Failed to get udev device for syspath '%s' or '%s'"),
DMI_DEVPATH, DMI_DEVPATH_FALLBACK);
goto out;
}
}
if (udevGetStringSysfsAttr(device,
"product_name",
&data->system.product_name) == PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"sys_vendor",
&data->system.hardware.vendor_name)
== PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"product_version",
&data->system.hardware.version)
== PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"product_serial",
&data->system.hardware.serial)
== PROPERTY_ERROR) {
goto out;
}
if (virGetHostUUID(data->system.hardware.uuid))
goto out;
if (udevGetStringSysfsAttr(device,
"bios_vendor",
&data->system.firmware.vendor_name)
== PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"bios_version",
&data->system.firmware.version)
== PROPERTY_ERROR) {
goto out;
}
if (udevGetStringSysfsAttr(device,
"bios_date",
&data->system.firmware.release_date)
== PROPERTY_ERROR) {
goto out;
}
out:
VIR_FREE(tmp);
if (device != NULL) {
udev_device_unref(device);
}
return;
}
#endif
static int udevSetupSystemDev(void)
{
virNodeDeviceDefPtr def = NULL;
virNodeDeviceObjPtr dev = NULL;
int ret = -1;
if (VIR_ALLOC(def) != 0) {
virReportOOMError();
goto out;
}
def->name = strdup("computer");
if (def->name == NULL) {
virReportOOMError();
goto out;
}
if (VIR_ALLOC(def->caps) != 0) {
virReportOOMError();
goto out;
}
#if defined(__x86_64__) || defined(__i386__) || defined(__amd64__)
udevGetDMIData(&def->caps->data);
#endif
dev = virNodeDeviceAssignDef(&driverState->devs, def);
if (dev == NULL) {
VIR_ERROR(_("Failed to create device for '%s'"), def->name);
goto out;
}
virNodeDeviceObjUnlock(dev);
ret = 0;
out:
if (ret == -1) {
virNodeDeviceDefFree(def);
}
return ret;
}
static int nodeDeviceStateInitialize(bool privileged ATTRIBUTE_UNUSED,
virStateInhibitCallback callback ATTRIBUTE_UNUSED,
void *opaque ATTRIBUTE_UNUSED)
{
udevPrivate *priv = NULL;
struct udev *udev = NULL;
int ret = 0;
#if defined __s390__ || defined __s390x_
/* On s390(x) system there is no PCI bus.
* Therefore there is nothing to initialize here. */
#else
int pciret;
if ((pciret = pci_system_init()) != 0) {
/* Ignore failure as non-root; udev is not as helpful in that
* situation, but a non-privileged user won't benefit much
* from udev in the first place. */
if (errno != ENOENT && (privileged || errno != EACCES)) {
char ebuf[256];
VIR_ERROR(_("Failed to initialize libpciaccess: %s"),
virStrerror(pciret, ebuf, sizeof(ebuf)));
ret = -1;
goto out;
}
}
#endif
if (VIR_ALLOC(priv) < 0) {
virReportOOMError();
ret = -1;
goto out;
}
priv->watch = -1;
if (VIR_ALLOC(driverState) < 0) {
virReportOOMError();
VIR_FREE(priv);
ret = -1;
goto out;
}
if (virMutexInit(&driverState->lock) < 0) {
VIR_ERROR(_("Failed to initialize mutex for driverState"));
VIR_FREE(priv);
VIR_FREE(driverState);
ret = -1;
goto out;
}
nodeDeviceLock(driverState);
/*
* http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/libudev-udev.html#udev-new
*
* indicates no return value other than success, so we don't check
* its return value.
*/
udev = udev_new();
udev_set_log_fn(udev, udevLogFunction);
priv->udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (priv->udev_monitor == NULL) {
VIR_FREE(priv);
VIR_ERROR(_("udev_monitor_new_from_netlink returned NULL"));
ret = -1;
goto out_unlock;
}
udev_monitor_enable_receiving(priv->udev_monitor);
/* udev can be retrieved from udev_monitor */
driverState->privateData = priv;
/* We register the monitor with the event callback so we are
* notified by udev of device changes before we enumerate existing
* devices because libvirt will simply recreate the device if we
* try to register it twice, i.e., if the device appears between
* the time we register the callback and the time we begin
* enumeration. The alternative is to register the callback after
* we enumerate, in which case we will fail to create any devices
* that appear while the enumeration is taking place. */
priv->watch = virEventAddHandle(udev_monitor_get_fd(priv->udev_monitor),
VIR_EVENT_HANDLE_READABLE,
udevEventHandleCallback, NULL, NULL);
if (priv->watch == -1) {
ret = -1;
goto out_unlock;
}
/* Create a fictional 'computer' device to root the device tree. */
if (udevSetupSystemDev() != 0) {
ret = -1;
goto out_unlock;
}
/* Populate with known devices */
if (udevEnumerateDevices(udev) != 0) {
ret = -1;
goto out_unlock;
}
out_unlock:
nodeDeviceUnlock(driverState);
out:
if (ret == -1) {
nodeDeviceStateCleanup();
}
return ret;
}
static int nodeDeviceStateReload(void)
{
return 0;
}
static virDrvOpenStatus nodeDeviceOpen(virConnectPtr conn,
virConnectAuthPtr auth ATTRIBUTE_UNUSED,
unsigned int flags)
{
virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR);
if (driverState == NULL) {
return VIR_DRV_OPEN_DECLINED;
}
conn->nodeDevicePrivateData = driverState;
return VIR_DRV_OPEN_SUCCESS;
}
static int nodeDeviceClose(virConnectPtr conn)
{
conn->nodeDevicePrivateData = NULL;
return 0;
}
static virNodeDeviceDriver udevNodeDeviceDriver = {
.name = "udevNodeDeviceDriver",
.nodeDeviceOpen = nodeDeviceOpen, /* 0.7.3 */
.nodeDeviceClose = nodeDeviceClose, /* 0.7.3 */
.nodeNumOfDevices = nodeNumOfDevices, /* 0.7.3 */
.nodeListDevices = nodeListDevices, /* 0.7.3 */
.connectListAllNodeDevices = nodeListAllNodeDevices, /* 0.10.2 */
.nodeDeviceLookupByName = nodeDeviceLookupByName, /* 0.7.3 */
.nodeDeviceLookupSCSIHostByWWN = nodeDeviceLookupSCSIHostByWWN, /* 1.0.2 */
.nodeDeviceGetXMLDesc = nodeDeviceGetXMLDesc, /* 0.7.3 */
.nodeDeviceGetParent = nodeDeviceGetParent, /* 0.7.3 */
.nodeDeviceNumOfCaps = nodeDeviceNumOfCaps, /* 0.7.3 */
.nodeDeviceListCaps = nodeDeviceListCaps, /* 0.7.3 */
.nodeDeviceCreateXML = nodeDeviceCreateXML, /* 0.7.3 */
.nodeDeviceDestroy = nodeDeviceDestroy, /* 0.7.3 */
};
static virStateDriver udevStateDriver = {
.name = "udev",
.stateInitialize = nodeDeviceStateInitialize, /* 0.7.3 */
.stateCleanup = nodeDeviceStateCleanup, /* 0.7.3 */
.stateReload = nodeDeviceStateReload, /* 0.7.3 */
};
int udevNodeRegister(void)
{
VIR_DEBUG("Registering udev node device backend");
if (virRegisterNodeDeviceDriver(&udevNodeDeviceDriver) < 0) {
return -1;
}
return virRegisterStateDriver(&udevStateDriver);
}