From 3ad6dcf3dcf390feff8e0a42b8ae36441ab8b316 Mon Sep 17 00:00:00 2001 From: David Allan Date: Thu, 12 Nov 2009 22:48:24 +0100 Subject: [PATCH] Implement a node device backend using libudev * configure.in: add new --with-udev, disabled by default, and requiring libudev > 145 * src/node_device/node_device_udev.c src/node_device/node_device_udev.h: the new node device backend * src/node_device/node_device_linux_sysfs.c: moved node_device_hal_linux.c to a better file name * src/conf/node_device_conf.c src/conf/node_device_conf.h: add a couple of fields in node device definitions, and an API to look them up, remove a couple of unused fields from previous patch. * src/node_device/node_device_driver.c src/node_device/node_device_driver.h: plug the new driver * po/POTFILES.in src/Makefile.am src/libvirt_private.syms: add the new files and symbols * src/util/util.h src/util/util.c: add a new convenience macro virBuildPath and virBuildPathInternal() function --- configure.in | 47 +- daemon/libvirtd.c | 3 +- po/POTFILES.in | 3 +- src/Makefile.am | 16 +- src/conf/node_device_conf.c | 46 +- src/conf/node_device_conf.h | 8 +- src/libvirt_private.syms | 1 + src/node_device/node_device_driver.c | 12 +- src/node_device/node_device_driver.h | 22 + src/node_device/node_device_hal.h | 19 - ..._hal_linux.c => node_device_linux_sysfs.c} | 0 src/node_device/node_device_udev.c | 1519 +++++++++++++++++ src/node_device/node_device_udev.h | 31 + src/util/util.c | 28 + src/util/util.h | 3 + 15 files changed, 1711 insertions(+), 47 deletions(-) rename src/node_device/{node_device_hal_linux.c => node_device_linux_sysfs.c} (100%) create mode 100644 src/node_device/node_device_udev.c create mode 100644 src/node_device/node_device_udev.h diff --git a/configure.in b/configure.in index 7ad1a90188..dc1e43e5e5 100644 --- a/configure.in +++ b/configure.in @@ -1654,7 +1654,7 @@ test "$enable_shared" = no && lt_cv_objdir=. LV_LIBTOOL_OBJDIR=${lt_cv_objdir-.} AC_SUBST([LV_LIBTOOL_OBJDIR]) -dnl HAL or DeviceKit library for host device enumeration +dnl HAL, DeviceKit, or libudev library for host device enumeration HAL_REQUIRED=0.0 HAL_CFLAGS= HAL_LIBS= @@ -1748,8 +1748,46 @@ AM_CONDITIONAL([HAVE_DEVKIT], [test "x$with_devkit" = "xyes"]) AC_SUBST([DEVKIT_CFLAGS]) AC_SUBST([DEVKIT_LIBS]) +UDEV_REQUIRED=145 +UDEV_CFLAGS= +UDEV_LIBS= +AC_ARG_WITH([udev], + [ --with-udev use libudev for host device enumeration], + [], + [with_udev=check]) + +if test "$with_libvirtd" = "no" ; then + with_udev=no +fi +if test "x$with_udev" = "xyes" -o "x$with_udev" = "xcheck"; then + PKG_CHECK_MODULES(UDEV, libudev >= $UDEV_REQUIRED, + [with_udev=yes], [ + if test "x$with_udev" = "xcheck" ; then + with_udev=no + else + AC_MSG_ERROR( + [You must install libudev-devel >= $UDEV_REQUIRED to compile libvirt]) + fi + ]) + if test "x$with_udev" = "xyes" ; then + AC_DEFINE_UNQUOTED([HAVE_UDEV], 1, + [use UDEV for host device enumeration]) + + old_CFLAGS=$CFLAGS + old_LDFLAGS=$LDFLAGS + CFLAGS="$CFLAGS $UDEV_CFLAGS" + LDFLAGS="$LDFLAGS $UDEV_LIBS" + AC_CHECK_FUNCS([udev_new],,[with_udev=no]) + CFLAGS="$old_CFLAGS" + LDFLAGS="$old_LDFLAGS" + fi +fi +AM_CONDITIONAL([HAVE_UDEV], [test "x$with_udev" = "xyes"]) +AC_SUBST([UDEV_CFLAGS]) +AC_SUBST([UDEV_LIBS]) + with_nodedev=no; -if test "$with_devkit" = "yes" -o "$with_hal" = "yes"; +if test "$with_devkit" = "yes" -o "$with_hal" = "yes" -o "$with_udev" = "yes"; then with_nodedev=yes AC_DEFINE_UNQUOTED([WITH_NODE_DEVICES], 1, [with node device driver]) @@ -1914,6 +1952,11 @@ AC_MSG_NOTICE([ devkit: $DEVKIT_CFLAGS $DEVKIT_LIBS]) else AC_MSG_NOTICE([ devkit: no]) fi +if test "$with_udev" = "yes" ; then +AC_MSG_NOTICE([ udev: $UDEV_CFLAGS $UDEV_LIBS]) +else +AC_MSG_NOTICE([ udev: no]) +fi if test "$with_netcf" = "yes" ; then AC_MSG_NOTICE([ netcf: $NETCF_CFLAGS $NETCF_LIBS]) else diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index 01c9bbc677..ef07460fdc 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -877,8 +877,7 @@ static struct qemud_server *qemudInitialize(void) { #ifdef WITH_STORAGE_DIR storageRegister(); #endif -#if defined(WITH_NODE_DEVICES) && \ - (defined(HAVE_HAL) || defined(HAVE_DEVKIT)) +#if defined(WITH_NODE_DEVICES) nodedevRegister(); #endif secretRegister(); diff --git a/po/POTFILES.in b/po/POTFILES.in index 000be09217..266f70c735 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,7 +17,8 @@ src/lxc/lxc_controller.c src/lxc/lxc_driver.c src/network/bridge_driver.c src/node_device/node_device_driver.c -src/node_device/node_device_hal_linux.c +src/node_device/node_device_linux_sysfs.c +src/node_device/node_device_udev.c src/nodeinfo.c src/opennebula/one_conf.c src/opennebula/one_driver.c diff --git a/src/Makefile.am b/src/Makefile.am index 92dbae4c70..afe7eac0ea 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -259,16 +259,20 @@ SECURITY_DRIVER_APPARMOR_SOURCES = \ NODE_DEVICE_DRIVER_SOURCES = \ - node_device/node_device_driver.c node_device/node_device_driver.h + node_device/node_device_driver.c \ + node_device/node_device_driver.h \ + node_device/node_device_linux_sysfs.c NODE_DEVICE_DRIVER_HAL_SOURCES = \ node_device/node_device_hal.c \ - node_device/node_device_hal.h \ - node_device/node_device_hal_linux.c + node_device/node_device_hal.h NODE_DEVICE_DRIVER_DEVKIT_SOURCES = \ node_device/node_device_devkit.c +NODE_DEVICE_DRIVER_UDEV_SOURCES = \ + node_device/node_device_udev.c + ######################### # @@ -647,6 +651,11 @@ libvirt_driver_nodedev_la_SOURCES += $(NODE_DEVICE_DRIVER_DEVKIT_SOURCES) libvirt_driver_nodedev_la_CFLAGS += $(DEVKIT_CFLAGS) libvirt_driver_nodedev_la_LDFLAGS += $(DEVKIT_LIBS) endif +if HAVE_UDEV +libvirt_driver_nodedev_la_SOURCES += $(NODE_DEVICE_DRIVER_UDEV_SOURCES) +libvirt_driver_nodedev_la_CFLAGS += $(UDEV_CFLAGS) +libvirt_driver_nodedev_la_LDFLAGS += $(UDEV_LIBS) +endif if WITH_DRIVER_MODULES libvirt_driver_nodedev_la_LDFLAGS += -module -avoid-version @@ -696,6 +705,7 @@ EXTRA_DIST += \ $(NODE_DEVICE_DRIVER_SOURCES) \ $(NODE_DEVICE_DRIVER_HAL_SOURCES) \ $(NODE_DEVICE_DRIVER_DEVKIT_SOURCES) \ + $(NODE_DEVICE_DRIVER_UDEV_SOURCES) \ $(SECURITY_DRIVER_SELINUX_SOURCES) \ $(SECURITY_DRIVER_APPARMOR_SOURCES) \ $(SECRET_DRIVER_SOURCES) \ diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index 626c8aae05..379787dbb1 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -91,6 +91,26 @@ int virNodeDeviceHasCap(const virNodeDeviceObjPtr dev, const char *cap) return 0; } + +virNodeDeviceObjPtr +virNodeDeviceFindBySysfsPath(const virNodeDeviceObjListPtr devs, + const char *sysfs_path) +{ + unsigned int i; + + for (i = 0; i < devs->count; i++) { + virNodeDeviceObjLock(devs->objs[i]); + if ((devs->objs[i]->def->sysfs_path != NULL) && + (STREQ(devs->objs[i]->def->sysfs_path, sysfs_path))) { + return devs->objs[i]; + } + virNodeDeviceObjUnlock(devs->objs[i]); + } + + return NULL; +} + + virNodeDeviceObjPtr virNodeDeviceFindByName(const virNodeDeviceObjListPtr devs, const char *name) { @@ -117,6 +137,8 @@ void virNodeDeviceDefFree(virNodeDeviceDefPtr def) VIR_FREE(def->name); VIR_FREE(def->parent); VIR_FREE(def->driver); + VIR_FREE(def->sysfs_path); + VIR_FREE(def->parent_sysfs_path); caps = def->caps; while (caps) { @@ -228,9 +250,17 @@ char *virNodeDeviceDefFormat(virConnectPtr conn, virBufferAddLit(&buf, "\n"); virBufferEscapeString(&buf, " %s\n", def->name); - - if (def->parent) + if (def->sysfs_path != NULL) { + virBufferEscapeString(&buf, " %s\n", + def->sysfs_path); + } + if (def->parent) { virBufferEscapeString(&buf, " %s\n", def->parent); + } + if (def->parent_sysfs_path != NULL) { + virBufferEscapeString(&buf, " %s\n", + def->parent_sysfs_path); + } if (def->driver) { virBufferAddLit(&buf, " \n"); virBufferEscapeString(&buf, " %s\n", def->driver); @@ -248,12 +278,6 @@ char *virNodeDeviceDefFormat(virConnectPtr conn, if (data->system.product_name) virBufferEscapeString(&buf, " %s\n", data->system.product_name); - if (data->system.dmi_devpath) - virBufferEscapeString(&buf, " %s\n", - data->system.dmi_devpath); - if (data->system.description) - virBufferEscapeString(&buf, " %s\n", - data->system.description); virBufferAddLit(&buf, " \n"); if (data->system.hardware.vendor_name) virBufferEscapeString(&buf, " %s\n", @@ -331,9 +355,6 @@ char *virNodeDeviceDefFormat(virConnectPtr conn, data->usb_if.subclass); virBufferVSprintf(&buf, " %d\n", data->usb_if.protocol); - if (data->usb_if.interface_name) - virBufferVSprintf(&buf, " %s\n", - data->usb_if.interface_name); if (data->usb_if.description) virBufferVSprintf(&buf, " %s\n", data->usb_if.description); @@ -1340,8 +1361,6 @@ void virNodeDevCapsDefFree(virNodeDevCapsDefPtr caps) switch (caps->type) { case VIR_NODE_DEV_CAP_SYSTEM: VIR_FREE(data->system.product_name); - VIR_FREE(data->system.dmi_devpath); - VIR_FREE(data->system.description); VIR_FREE(data->system.hardware.vendor_name); VIR_FREE(data->system.hardware.version); VIR_FREE(data->system.hardware.serial); @@ -1358,7 +1377,6 @@ void virNodeDevCapsDefFree(virNodeDevCapsDefPtr caps) VIR_FREE(data->usb_dev.vendor_name); break; case VIR_NODE_DEV_CAP_USB_INTERFACE: - VIR_FREE(data->usb_if.interface_name); VIR_FREE(data->usb_if.description); break; case VIR_NODE_DEV_CAP_NET: diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index f70184d3d5..e97242ada9 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -82,8 +82,6 @@ struct _virNodeDevCapsDef { union _virNodeDevCapData { struct { char *product_name; - char *description; - char *dmi_devpath; struct { char *vendor_name; char *version; @@ -120,7 +118,6 @@ struct _virNodeDevCapsDef { unsigned _class; /* "class" is reserved in C */ unsigned subclass; unsigned protocol; - char *interface_name; char *description; } usb_if; struct { @@ -164,7 +161,9 @@ typedef struct _virNodeDeviceDef virNodeDeviceDef; typedef virNodeDeviceDef *virNodeDeviceDefPtr; struct _virNodeDeviceDef { char *name; /* device name (unique on node) */ + char *sysfs_path; /* udev name/sysfs path */ char *parent; /* optional parent device name */ + char *parent_sysfs_path; /* udev parent name/sysfs path */ char *driver; /* optional driver name */ virNodeDevCapsDefPtr caps; /* optional device capabilities */ }; @@ -206,6 +205,9 @@ int virNodeDeviceHasCap(const virNodeDeviceObjPtr dev, const char *cap); virNodeDeviceObjPtr virNodeDeviceFindByName(const virNodeDeviceObjListPtr devs, const char *name); +virNodeDeviceObjPtr +virNodeDeviceFindBySysfsPath(const virNodeDeviceObjListPtr devs, + const char *sysfs_path); virNodeDeviceObjPtr virNodeDeviceAssignDef(virConnectPtr conn, virNodeDeviceObjListPtr devs, diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 05068672cb..c473d499c5 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -341,6 +341,7 @@ virNodeDeviceHasCap; virNodeDeviceObjRemove; virNodeDevCapTypeToString; virNodeDeviceFindByName; +virNodeDeviceFindBySysfsPath; virNodeDeviceObjListFree; virNodeDeviceDefFree; virNodeDevCapsDefFree; diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index f33ff48639..c13990737c 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -70,7 +70,10 @@ static int update_caps(virNodeDeviceObjPtr dev) } -#ifdef __linux__ +#if defined (__linux__) && defined (HAVE_HAL) +/* Under libudev changes to the driver name should be picked up as + * "change" events, so we don't call update driver name unless we're + * using the HAL backend. */ static int update_driver_name(virConnectPtr conn, virNodeDeviceObjPtr dev) { @@ -658,10 +661,10 @@ void registerCommonNodeFuncs(virDeviceMonitorPtr driver) int nodedevRegister(void) { -#if defined(HAVE_HAL) && defined(HAVE_DEVKIT) +#if defined(HAVE_HAL) && defined(HAVE_UDEV) /* Register only one of these two - they conflict */ if (halNodeRegister() == -1) - return devkitNodeRegister(); + return udevNodeRegister(); return 0; #else #ifdef HAVE_HAL @@ -670,5 +673,8 @@ int nodedevRegister(void) { #ifdef HAVE_DEVKIT return devkitNodeRegister(); #endif +#ifdef HAVE_UDEV + return udevNodeRegister(); +#endif #endif } diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index db016244ef..5be07812e5 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -45,6 +45,9 @@ int halNodeRegister(void); #ifdef HAVE_DEVKIT int devkitNodeRegister(void); #endif +#ifdef HAVE_UDEV +int udevNodeRegister(void); +#endif void nodeDeviceLock(virDeviceMonitorStatePtr driver); void nodeDeviceUnlock(virDeviceMonitorStatePtr driver); @@ -53,4 +56,23 @@ void registerCommonNodeFuncs(virDeviceMonitorPtr mon); int nodedevRegister(void); +#ifdef __linux__ + +#define check_fc_host(d) check_fc_host_linux(d) +int check_fc_host_linux(union _virNodeDevCapData *d); + +#define check_vport_capable(d) check_vport_capable_linux(d) +int check_vport_capable_linux(union _virNodeDevCapData *d); + +#define read_wwn(host, file, wwn) read_wwn_linux(host, file, wwn) +int read_wwn_linux(int host, const char *file, char **wwn); + +#else /* __linux__ */ + +#define check_fc_host(d) +#define check_vport_capable(d) +#define read_wwn(host, file, wwn) + +#endif /* __linux__ */ + #endif /* __VIR_NODE_DEVICE_H__ */ diff --git a/src/node_device/node_device_hal.h b/src/node_device/node_device_hal.h index c859fe382e..8ac8a3539f 100644 --- a/src/node_device/node_device_hal.h +++ b/src/node_device/node_device_hal.h @@ -22,23 +22,4 @@ #ifndef __VIR_NODE_DEVICE_HAL_H__ #define __VIR_NODE_DEVICE_HAL_H__ -#ifdef __linux__ - -#define check_fc_host(d) check_fc_host_linux(d) -int check_fc_host_linux(union _virNodeDevCapData *d); - -#define check_vport_capable(d) check_vport_capable_linux(d) -int check_vport_capable_linux(union _virNodeDevCapData *d); - -#define read_wwn(host, file, wwn) read_wwn_linux(host, file, wwn) -int read_wwn_linux(int host, const char *file, char **wwn); - -#else /* __linux__ */ - -#define check_fc_host(d) -#define check_vport_capable(d) -#define read_wwn(host, file, wwn) - -#endif /* __linux__ */ - #endif /* __VIR_NODE_DEVICE_HAL_H__ */ diff --git a/src/node_device/node_device_hal_linux.c b/src/node_device/node_device_linux_sysfs.c similarity index 100% rename from src/node_device/node_device_hal_linux.c rename to src/node_device/node_device_linux_sysfs.c diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c new file mode 100644 index 0000000000..d608b764ea --- /dev/null +++ b/src/node_device/node_device_udev.c @@ -0,0 +1,1519 @@ +/* + * 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 + */ + +#include +#include +#include +#include + +#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); +} diff --git a/src/node_device/node_device_udev.h b/src/node_device/node_device_udev.h new file mode 100644 index 0000000000..0fd39ae62f --- /dev/null +++ b/src/node_device/node_device_udev.h @@ -0,0 +1,31 @@ +/* + * node_device_udev.h: 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 + */ + +#include +#include + +#define SYSFS_DATA_SIZE 4096 +#define DRV_STATE_UDEV_MONITOR(ds) ((struct udev_monitor *)((ds)->privateData)) +#define DMI_DEVPATH "/sys/devices/virtual/dmi/id" +#define PROPERTY_FOUND 0 +#define PROPERTY_MISSING 1 +#define PROPERTY_ERROR -1 diff --git a/src/util/util.c b/src/util/util.c index 853d3a0a94..e472e0c2cf 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -2128,3 +2128,31 @@ void virFileWaitForDevices(virConnectPtr conn) void virFileWaitForDevices(virConnectPtr conn ATTRIBUTE_UNUSED) {} #endif #endif + +int virBuildPathInternal(char **path, ...) +{ + char *path_component = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + va_list ap; + int ret = 0; + + va_start(ap, *path); + + path_component = va_arg(ap, char *); + virBufferAdd(&buf, path_component, -1); + + while ((path_component = va_arg(ap, char *)) != NULL) + { + virBufferAddChar(&buf, '/'); + virBufferAdd(&buf, path_component, -1); + } + + va_end(ap); + + *path = virBufferContentAndReset(&buf); + if (*path == NULL) { + ret = -1; + } + + return ret; +} diff --git a/src/util/util.h b/src/util/util.h index f4e395ec4e..8c9d401187 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -248,4 +248,7 @@ char *virFileFindMountPoint(const char *type); void virFileWaitForDevices(virConnectPtr conn); +#define virBuildPath(path, ...) virBuildPathInternal(path, __VA_ARGS__, NULL) +int virBuildPathInternal(char **path, ...) ATTRIBUTE_SENTINEL; + #endif /* __VIR_UTIL_H__ */ -- GitLab