--- /dev/null
+/*
+ * node_device_udev.c: node device enumeration - libudev implementation
+ *
+ * Copyright (C) 2009 Red Hat
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Dave Allan <dallan@redhat.com>
+ */
+
+#include <config.h>
+#include <libudev.h>
+#include <scsi/scsi.h>
+#include <c-ctype.h>
+
+#include "node_device_udev.h"
+#include "virterror_internal.h"
+#include "node_device_conf.h"
+#include "node_device_driver.h"
+#include "driver.h"
+#include "datatypes.h"
+#include "logging.h"
+#include "memory.h"
+#include "uuid.h"
+#include "util.h"
+#include "buf.h"
+#include "daemon/event.h"
+
+#define VIR_FROM_THIS VIR_FROM_NODEDEV
+
+static virDeviceMonitorStatePtr 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\n", s);
+ } else {
+ VIR_DEBUG("Converted '%s' to unsigned long %llu\n", 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\n", s);
+ } else {
+ VIR_DEBUG("Converted '%s' to unsigned int %u\n", 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\n", s);
+ } else {
+ VIR_DEBUG("Converted '%s' to int %u\n", 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_INFO(_("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(NULL);
+ ret = PROPERTY_ERROR;
+ goto out;
+ }
+
+ VIR_DEBUG("Found property key '%s' value '%s' "
+ "for device with sysname '%s'\n",
+ 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_INFO(_("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(NULL);
+ ret = PROPERTY_ERROR;
+ goto out;
+ }
+
+ VIR_DEBUG("Found sysfs attribute '%s' value '%s' "
+ "for device with sysname '%s'\n",
+ 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;
+
+ virBufferVSprintf(&buf, "%s_%s",
+ udev_device_get_subsystem(device),
+ udev_device_get_sysname(device));
+
+ if (s != NULL) {
+ virBufferVSprintf(&buf, "_%s", s);
+ }
+
+ if (virBufferError(&buf)) {
+ VIR_ERROR("Buffer error when generating device name for device "
+ "with sysname '%s'\n", 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(file, fn, line, fmt, args);
+}
+
+
+static int udevProcessPCI(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ const char *devpath = NULL;
+ union _virNodeDevCapData *data = &def->caps->data;
+ int ret = -1;
+
+ devpath = udev_device_get_devpath(device);
+
+ if (udevGetUintProperty(device,
+ "PCI_CLASS",
+ &data->pci_dev.class,
+ 16) == PROPERTY_ERROR) {
+ goto out;
+ }
+
+ char *p = strrchr(devpath, '/');
+
+ 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,
+ 0) == PROPERTY_ERROR) {
+ goto out;
+ }
+
+ if (udevGetUintSysfsAttr(device,
+ "device",
+ &data->pci_dev.product,
+ 0) == PROPERTY_ERROR) {
+ goto out;
+ }
+
+ /* XXX FIXME: to do the vendor name and product name, we have to
+ * parse /usr/share/hwdata/pci.ids. Use libpciaccess perhaps? */
+
+ if (udevGenerateDeviceName(device, def, NULL) != 0) {
+ goto out;
+ }
+
+ 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,
+ 0) == PROPERTY_ERROR) {
+ goto out;
+ }
+
+ if (udevGetUintProperty(device,
+ "DEVNUM",
+ &data->usb_dev.device,
+ 0) == 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,
+ 0) == 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;
+}
+
+
+/* XXX Is 10 the correct base for the Number/Class/SubClass/Protocol
+ * conversions? */
+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,
+ 10) == PROPERTY_ERROR) {
+ goto out;
+ }
+
+ if (udevGetUintSysfsAttr(device,
+ "bInterfaceClass",
+ &data->usb_if._class,
+ 10) == PROPERTY_ERROR) {
+ goto out;
+ }
+
+ if (udevGetUintSysfsAttr(device,
+ "bInterfaceSubClass",
+ &data->usb_if.subclass,
+ 10) == PROPERTY_ERROR) {
+ goto out;
+ }
+
+ if (udevGetUintSysfsAttr(device,
+ "bInterfaceProtocol",
+ &data->usb_if.protocol,
+ 10) == 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;
+ union _virNodeDevCapData *data = &def->caps->data;
+
+ 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 = basename(def->sysfs_path);
+
+ if (!STRPREFIX(filename, "host")) {
+ VIR_ERROR("SCSI host found, but its udev name '%s' does "
+ "not begin with 'host'\n", filename);
+ goto out;
+ }
+
+ if (udevStrToLong_ui(filename + strlen("host"),
+ NULL,
+ 0,
+ &data->scsi_host.host) == -1) {
+ goto out;
+ }
+
+ check_fc_host(&def->caps->data);
+ check_vport_capable(&def->caps->data);
+
+ if (udevGenerateDeviceName(device, def, NULL) != 0) {
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+ return ret;
+}
+
+
+static int udevGetSCSIType(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_NO_LUN:
+ default:
+ foundtype = 0;
+ break;
+ }
+
+ if (*typestring == NULL) {
+ if (foundtype == 1) {
+ ret = -1;
+ virReportOOMError(NULL);
+ } else {
+ VIR_ERROR("Failed to find SCSI device type %d\n", type);
+ }
+ }
+
+ 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 = basename(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(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'\n",
+ def->sysfs_path);
+ }
+ return ret;
+}
+
+
+static int udevProcessDisk(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ union _virNodeDevCapData *data = &def->caps->data;
+ int ret = 0;
+
+ data->storage.drive_type = strdup("disk");
+ if (data->storage.drive_type == NULL) {
+ virReportOOMError(NULL);
+ ret = -1;
+ 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;
+ }
+
+ data->storage.size = data->storage.num_blocks *
+ data->storage.logical_block_size;
+
+out:
+ return ret;
+}
+
+
+static int udevProcessCDROM(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ union _virNodeDevCapData *data = &def->caps->data;
+ int tmp_int = 0, ret = 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(NULL);
+ goto out;
+ }
+
+ if ((udevGetIntSysfsAttr(device, "removable", &tmp_int, 0) == PROPERTY_FOUND) &&
+ (tmp_int == 1)) {
+ def->caps->data.storage.flags |= VIR_NODE_DEV_CAP_STORAGE_REMOVABLE;
+ }
+
+ if ((udevGetIntProperty(device, "ID_CDROM_MEDIA", &tmp_int, 0)
+ == PROPERTY_FOUND) && (tmp_int == 1)) {
+
+ def->caps->data.storage.flags |=
+ VIR_NODE_DEV_CAP_STORAGE_REMOVABLE_MEDIA_AVAILABLE;
+
+ 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;
+}
+
+
+/* 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_INFO("Could not find definitive storage type for device "
+ "with sysfs path '%s', trying to guess it\n",
+ 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_INFO("Could not determine storage type for device "
+ "with sysfs path '%s'\n", def->sysfs_path);
+ } else {
+ VIR_DEBUG("Found storage type '%s' for device "
+ "with sysfs path '%s'\n",
+ 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;
+
+ data->storage.block = strdup(udev_device_get_devnode(device));
+ if (udevGetStringProperty(device,
+ "DEVNAME",
+ &data->storage.block) == PROPERTY_ERROR) {
+ goto out;
+ }
+ 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) {
+ /* 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 {
+ VIR_INFO("Unsupported storage type '%s'\n",
+ def->caps->data.storage.drive_type);
+ goto out;
+ }
+
+ if (udevGenerateDeviceName(device, def, data->storage.serial) != 0) {
+ goto out;
+ }
+
+out:
+ 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);
+
+ 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_device")) {
+ *type = VIR_NODE_DEV_CAP_SCSI;
+ goto out;
+ }
+
+ if (devtype != NULL && STREQ(devtype, "disk")) {
+ *type = VIR_NODE_DEV_CAP_STORAGE;
+ 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 network interfaces set the device type
+ * property. */
+ if (devtype == NULL &&
+ udevGetStringProperty(device,
+ "INTERFACE",
+ &tmp_string) == PROPERTY_FOUND) {
+ VIR_FREE(tmp_string);
+ *type = VIR_NODE_DEV_CAP_NET;
+ goto out;
+ }
+
+ VIR_INFO("Could not determine device type for device "
+ "with sysfs path '%s'\n",
+ 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:
+ ret = udevProcessSCSIDevice(device, def);
+ break;
+ case VIR_NODE_DEV_CAP_STORAGE:
+ ret = udevProcessStorage(device, def);
+ break;
+ default:
+ VIR_ERROR("Unknown device type %d\n", 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'\n",
+ dev->def->name, name);
+ virNodeDeviceObjRemove(&driverState->devs, dev);
+ } else {
+ VIR_INFO("Failed to find device to remove that has udev name '%s'\n",
+ 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 = udev_device_get_parent(device);
+ if (parent_device == NULL) {
+ VIR_INFO("Could not find udev parent for device with sysfs path '%s'\n",
+ udev_device_get_syspath(device));
+ goto out;
+ }
+
+ parent_sysfs_path = udev_device_get_syspath(parent_device);
+ if (parent_sysfs_path == NULL) {
+ VIR_INFO("Could not get syspath for parent of '%s'\n",
+ udev_device_get_syspath(device));
+ goto out;
+ }
+
+ def->parent_sysfs_path = strdup(parent_sysfs_path);
+ if (def->parent_sysfs_path == NULL) {
+ virReportOOMError(NULL);
+ goto out;
+ }
+
+ dev = virNodeDeviceFindBySysfsPath(&driverState->devs, parent_sysfs_path);
+ if (dev == NULL) {
+ def->parent = strdup("computer");
+ } else {
+ def->parent = strdup(dev->def->name);
+ virNodeDeviceObjUnlock(dev);
+ }
+
+ if (def->parent == NULL) {
+ virReportOOMError(NULL);
+ 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(NULL);
+ 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(NULL);
+ 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;
+ }
+
+ dev = virNodeDeviceAssignDef(NULL, &driverState->devs, def);
+ if (dev == NULL) {
+ VIR_ERROR("Failed to create device for '%s'\n", def->name);
+ virNodeDeviceDefFree(def);
+ goto out;
+ }
+
+ dev->devicePath = strdup(udev_device_get_devpath(device));
+ if (dev->devicePath == NULL) {
+ virReportOOMError(NULL);
+ virNodeDeviceObjRemove(&driverState->devs, dev);
+ goto out;
+ }
+
+ virNodeDeviceObjUnlock(dev);
+
+ ret = 0;
+
+out:
+ 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_INFO("Failed to create node device for udev device '%s'\n",
+ name);
+ }
+ udev_device_unref(device);
+ ret = 0;
+ }
+
+ return ret;
+}
+
+
+static int udevEnumerateDevices(struct udev *udev)
+{
+ struct udev_enumerate *udev_enumerate = NULL;
+ struct udev_list_entry *list_entry = NULL;
+ const char *name = 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\n", ret);
+ goto out;
+ }
+
+ udev_list_entry_foreach(list_entry,
+ udev_enumerate_get_list_entry(udev_enumerate)) {
+
+ udevProcessDeviceListEntry(udev, list_entry);
+ name = udev_list_entry_get_name(list_entry);
+ }
+
+out:
+ udev_enumerate_unref(udev_enumerate);
+ return ret;
+}
+
+
+static int udevDeviceMonitorShutdown(void)
+{
+ int ret = 0;
+
+ struct udev_monitor *udev_monitor = NULL;
+ struct udev *udev = NULL;
+
+ if (driverState) {
+
+ nodeDeviceLock(driverState);
+ 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);
+
+ } else {
+ ret = -1;
+ }
+
+ 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;
+
+ 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_ERROR0("udev_monitor_receive_device returned NULL\n");
+ goto out;
+ }
+
+ action = udev_device_get_action(device);
+ VIR_DEBUG("udev action: '%s'\n", action);
+
+ if (STREQ(action, "add") || STREQ(action, "change")) {
+ udevAddOneDevice(device);
+ goto out;
+ }
+
+ if (STREQ(action, "remove")) {
+ udevRemoveOneDevice(device);
+ goto out;
+ }
+
+out:
+ return;
+}
+
+
+static int udevSetupSystemDev(void)
+{
+ virNodeDeviceDefPtr def = NULL;
+ virNodeDeviceObjPtr dev = NULL;
+ struct udev *udev = NULL;
+ struct udev_device *device = NULL;
+ union _virNodeDevCapData *data = NULL;
+ char *tmp = NULL;
+ int ret = -1;
+
+ if (VIR_ALLOC(def) != 0) {
+ virReportOOMError(NULL);
+ goto out;
+ }
+
+ def->name = strdup("computer");
+ if (def->name == NULL) {
+ virReportOOMError(NULL);
+ goto out;
+ }
+
+ if (VIR_ALLOC(def->caps) != 0) {
+ virReportOOMError(NULL);
+ goto out;
+ }
+
+ udev = udev_monitor_get_udev(DRV_STATE_UDEV_MONITOR(driverState));
+ device = udev_device_new_from_syspath(udev, DMI_DEVPATH);
+ if (device == NULL) {
+ VIR_ERROR("Failed to get udev device for syspath '%s'\n", DMI_DEVPATH);
+ goto out;
+ }
+
+ data = &def->caps->data;
+
+ 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 (udevGetStringSysfsAttr(device,
+ "product_uuid",
+ &tmp) == PROPERTY_ERROR) {
+ goto out;
+ }
+ virUUIDParse(tmp, def->caps->data.system.hardware.uuid);
+ VIR_FREE(tmp);
+
+ 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;
+ }
+
+ udev_device_unref(device);
+
+ dev = virNodeDeviceAssignDef(NULL, &driverState->devs, def);
+ if (dev == NULL) {
+ VIR_ERROR("Failed to create device for '%s'\n", def->name);
+ virNodeDeviceDefFree(def);
+ goto out;
+ }
+
+ virNodeDeviceObjUnlock(dev);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int udevDeviceMonitorStartup(int privileged ATTRIBUTE_UNUSED)
+{
+ struct udev *udev = NULL;
+ struct udev_monitor *udev_monitor = NULL;
+ int ret = 0;
+
+ if (VIR_ALLOC(driverState) < 0) {
+ virReportOOMError(NULL);
+ ret = -1;
+ goto out;
+ }
+
+ if (virMutexInit(&driverState->lock) < 0) {
+ VIR_ERROR0("Failed to initialize mutex for driverState\n");
+ 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);
+
+ udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
+ if (udev_monitor == NULL) {
+ VIR_ERROR0("udev_monitor_new_from_netlink returned NULL\n");
+ ret = -1;
+ goto out;
+ }
+
+ udev_monitor_enable_receiving(udev_monitor);
+
+ /* udev can be retrieved from udev_monitor */
+ driverState->privateData = udev_monitor;
+ nodeDeviceUnlock(driverState);
+
+ /* 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. */
+ if (virEventAddHandleImpl(udev_monitor_get_fd(udev_monitor),
+ VIR_EVENT_HANDLE_READABLE,
+ udevEventHandleCallback,
+ NULL, NULL) == -1) {
+ ret = -1;
+ goto out;
+ }
+
+ /* Create a fictional 'computer' device to root the device tree. */
+ if (udevSetupSystemDev() != 0) {
+ ret = -1;
+ goto out;
+ }
+
+ /* Populate with known devices */
+
+ if (udevEnumerateDevices(udev) != 0) {
+ ret = -1;
+ goto out;
+ }
+
+out:
+ if (ret == -1) {
+ udevDeviceMonitorShutdown();
+ }
+ return ret;
+}
+
+
+static int udevDeviceMonitorReload(void)
+{
+ return 0;
+}
+
+
+static int udevDeviceMonitorActive(void)
+{
+ /* Always ready to deal with a shutdown */
+ return 0;
+}
+
+
+static virDrvOpenStatus udevNodeDrvOpen(virConnectPtr conn,
+ virConnectAuthPtr auth ATTRIBUTE_UNUSED,
+ int flags ATTRIBUTE_UNUSED)
+{
+ if (driverState == NULL) {
+ return VIR_DRV_OPEN_DECLINED;
+ }
+
+ conn->devMonPrivateData = driverState;
+
+ return VIR_DRV_OPEN_SUCCESS;
+}
+
+static int udevNodeDrvClose(virConnectPtr conn)
+{
+ conn->devMonPrivateData = NULL;
+ return 0;
+}
+
+static virDeviceMonitor udevDeviceMonitor = {
+ .name = "udevDeviceMonitor",
+ .open = udevNodeDrvOpen,
+ .close = udevNodeDrvClose,
+};
+
+static virStateDriver udevStateDriver = {
+ .initialize = udevDeviceMonitorStartup,
+ .cleanup = udevDeviceMonitorShutdown,
+ .reload = udevDeviceMonitorReload,
+ .active = udevDeviceMonitorActive,
+};
+
+int udevNodeRegister(void)
+{
+ VIR_DEBUG0("Registering udev node device backend\n");
+
+ registerCommonNodeFuncs(&udevDeviceMonitor);
+ if (virRegisterDeviceMonitor(&udevDeviceMonitor) < 0) {
+ return -1;
+ }
+
+ return virRegisterStateDriver(&udevStateDriver);
+}