[libvirt] [PATCH] tests: Initialize virRandom in for test suite.
by Cole Robinson
Otherwise any virRandom calls will result in a segfault.
Signed-off-by: Cole Robinson <crobinso(a)redhat.com>
---
tests/testutils.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/tests/testutils.c b/tests/testutils.c
index 536441a..e6f5e61 100644
--- a/tests/testutils.c
+++ b/tests/testutils.c
@@ -340,7 +340,8 @@ int virtTestMain(int argc,
#endif
if (virThreadInitialize() < 0 ||
- virErrorInitialize() < 0)
+ virErrorInitialize() < 0 ||
+ virRandomInitialize(time(NULL) ^ getpid()))
return 1;
if ((debugStr = getenv("VIR_TEST_DEBUG")) != NULL) {
--
1.6.0.6
15 years, 2 months
[libvirt] error: unable to connect to '/usr/local/var/run/libvirt/libvirt-sock': Connection refused
by Liu, Zhentao
Hello,
I updated libvirt-bin to version 0.7.0. I still got a error:
-------------------------------------------------------------------------------------------
root@forest:/tmp/libvirt-0.7.0# virsh -c qemu:///system list
error: unable to connect to '/usr/local/var/run/libvirt/libvirt-sock': Connection refused
error: failed to connect to the hypervisor
-------------------------------------------------------------------------------------------
Any helps?
Regards
Zhentao
15 years, 2 months
[libvirt] [PATCH] Better error reporting for failed migration.
by Chris Lalancette
The comment in the code says most of it, but when the destination
hostname resolution is screwed up, print a proper error instead
of the very unhelpful "unknown error".
Note that I'm not overly fond of the wording in the error message,
so I'm open to suggestions.
Signed-off-by: Chris Lalancette <clalance(a)redhat.com>
---
src/qemu/qemu_driver.c | 16 ++++++++++++++++
1 files changed, 16 insertions(+), 0 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index d37b184..02bb5cb 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -6288,6 +6288,22 @@ qemudDomainMigratePrepare2 (virConnectPtr dconn,
goto cleanup;
}
+ /* Remember that we are running on the destination. The hostname that
+ * we resolve here will be used on the source machine in the "migrate"
+ * monitor command. Because of that, localhost is almost always the
+ * wrong thing. Adding this check explicitly breaks localhost
+ * migration, but only for those machines that have improperly
+ * configured hostname resolution.
+ */
+ if (STREQ(hostname, "localhost")) {
+ VIR_FREE(hostname);
+ qemudReportError(dconn, NULL, NULL, VIR_ERR_INVALID_ARG, "%s",
+ _("Could not resolve destination hostname; "
+ "either fix destination to resolve hostname, "
+ "or use the optional URI migration parameter"));
+ goto cleanup;
+ }
+
/* XXX this really should have been a properly well-formed
* URI, but we can't add in tcp:// now without breaking
* compatability with old targets. We at least make the
--
1.6.0.6
15 years, 2 months
[libvirt] error: failed to connect to the hypervisor
by Liu, Zhentao
Hello,
I have reinstalled libvirt-bin on ubuntu 9.04. Then I got a error:
------------------------------------------------------------------------
root@forest:/usr/local/etc/logrotate.d# virsh -c qemu:///system list
error: failed to connect to the hypervisor
-----------------------------------------------------------------------
ps: I have installed kvm on ubuntu.
Any ideas?
Regards
Zhentao
15 years, 2 months
[libvirt] error: failed to connect to the hypervisor
by Liu, Zhentao
Hello,
I have installed libvirt-bin 0.6.1 on the ubuntu 9.04. When I run "virsh -c qemu:///system list", I get this error:
----------------------------------------------------------------------------
unable to connect to '/var/run/libvirt/libvirt-sock': Permission denied
failed to connect to the hypervisor
-----------------------------------------------------------------------------
Then I search in the Internet, it may be a bug of version 0.6.1. So I update libvirt-bin to version 0.6.3, but I still get
a error:
---------------------------------------------------------------------------------
root@forest:/home/zli# virsh -c qemu:///system list
error: failed to connect to the hypervisor
----------------------------------------------------------------------------------
I have no idea about the reason. Any helps?
Regards
Zhentao
15 years, 2 months
[libvirt] [PATCH] util: Make sure random data is initialized when in virRandom
by Cole Robinson
When writing some tests, I mistakenly attempted to auto-generate a UUID,
which caused a segfault: virRandom was being used without calling
virRandomInitialize. Make sure this case can't happen.
Signed-off-by: Cole Robinson <crobinso(a)redhat.com>
---
src/util/util.c | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/src/util/util.c b/src/util/util.c
index e5135fc..1554097 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -1910,6 +1910,7 @@ int virKillProcess(pid_t pid, int sig)
}
+static int random_initialized = 0;
static char randomState[128];
static struct random_data randomData;
static virMutex randomLock;
@@ -1925,6 +1926,7 @@ int virRandomInitialize(unsigned int seed)
&randomData) < 0)
return -1;
+ random_initialized = 1;
return 0;
}
@@ -1932,6 +1934,12 @@ int virRandom(int max)
{
int32_t ret;
+ if (!random_initialized) {
+ /* This can error, but what's worse? Unnoticed bogus random data or
+ * a segfault? */
+ virRandomInitialize(time(NULL) ^ getpid());
+ }
+
virMutexLock(&randomLock);
random_r(&randomData, &ret);
virMutexUnlock(&randomLock);
--
1.6.5.rc2
15 years, 2 months
[libvirt] node device libudev backend second look
by Dave Allan
Attached is a patch against the current head containing an
implementation of node device enumeration using libudev. It is complete
except for the monitor, but I'm submitting it now as I have a few
questions about the implementation that I'd like advice on. They are
marked XXX in comments in the patch.
The other thing that's not clear to me is how the code generates the
tree structure for nodedev-list --tree. I'm setting the parent pointer
to what I think is correct, but the tree output is broken. I can dig
through it until I understand it, but if anyone is familiar with the
implementation and would be willing to take a few minutes to walk me
through it, it would save me a bunch of time.
I think it's also important that people get the code installed on a
variety of systems as soon as possible to shake out the inevitable bugs
that will arise from differing device models and code versions, and I'll
have the final version with the monitor shortly.
Dave
>From efedc8fee8127b32fb5b761ce5035902d168a207 Mon Sep 17 00:00:00 2001
From: David Allan <dallan(a)redhat.com>
Date: Fri, 25 Sep 2009 10:59:28 -0400
Subject: [PATCH 1/1] A second look at the libudev node device backend.
This patch is a very much cleaned up version of the libudev based node device backend with all the device types implemented, although I need some guidance on the USB interface code as I am not especially familiar with the terminology and HAL and udev display the details quite differently. This version of the code uses libudev to retrieve all the device information, so there's no manual mucking about in sysfs except for a little bit of existing code for fibre channel adapters.
There is a lot of detail work in this code, so we should try to get people running it on a wide variety of hardware so we can shake out the differences in implementation between the HAL and libudev backends.
---
configure.in | 47 ++-
daemon/libvirtd.c | 3 +-
src/Makefile.am | 16 +-
src/conf/node_device_conf.c | 15 +-
src/conf/node_device_conf.h | 5 +
src/node_device/node_device_driver.c | 12 +-
src/node_device/node_device_driver.h | 22 +
src/node_device/node_device_hal.h | 19 -
...evice_hal_linux.c => node_device_linux_sysfs.c} | 0
src/node_device/node_device_udev.c | 800 ++++++++++++++++++++
src/node_device/node_device_udev.h | 27 +
src/util/util.c | 28 +
src/util/util.h | 3 +
13 files changed, 967 insertions(+), 30 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 2f9db72..25ed55f 100644
--- a/configure.in
+++ b/configure.in
@@ -1640,7 +1640,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=
@@ -1734,8 +1734,46 @@ AM_CONDITIONAL([HAVE_DEVKIT], [test "x$with_devkit" = "xyes"])
AC_SUBST([DEVKIT_CFLAGS])
AC_SUBST([DEVKIT_LIBS])
+UDEV_REQUIRED=143
+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 udev-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])
@@ -1900,6 +1938,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 78dfb2d..0c03aae 100644
--- a/daemon/libvirtd.c
+++ b/daemon/libvirtd.c
@@ -833,8 +833,7 @@ static struct qemud_server *qemudInitialize(int sigread) {
#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/src/Makefile.am b/src/Makefile.am
index d0ef7d1..f6f63a2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -254,16 +254,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
+
#########################
#
@@ -642,6 +646,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
@@ -689,6 +698,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 f09f814..2322819 100644
--- a/src/conf/node_device_conf.c
+++ b/src/conf/node_device_conf.c
@@ -325,6 +325,9 @@ char *virNodeDeviceDefFormat(virConnectPtr conn,
data->usb_if.subclass);
virBufferVSprintf(&buf, " <protocol>%d</protocol>\n",
data->usb_if.protocol);
+ if (data->usb_if.interface_name)
+ virBufferVSprintf(&buf, " <interface_name>%s</interface_name>\n",
+ data->usb_if.interface_name);
if (data->usb_if.description)
virBufferVSprintf(&buf, " <description>%s</description>\n",
data->usb_if.description);
@@ -394,10 +397,19 @@ char *virNodeDeviceDefFormat(virConnectPtr conn,
"</media_available>\n", avl ? 1 : 0);
virBufferVSprintf(&buf, " <media_size>%llu</media_size>\n",
data->storage.removable_media_size);
- virBufferAddLit(&buf, " </capability>\n");
+ virBufferVSprintf(&buf, " <logical_block_size>%llu"
+ "</logical_block_size>\n",
+ data->storage.logical_block_size);
+ virBufferVSprintf(&buf, " <num_blocks>%llu</num_blocks>\n",
+ data->storage.num_blocks);
} else {
virBufferVSprintf(&buf, " <size>%llu</size>\n",
data->storage.size);
+ virBufferVSprintf(&buf, " <logical_block_size>%llu"
+ "</logical_block_size>\n",
+ data->storage.logical_block_size);
+ virBufferVSprintf(&buf, " <num_blocks>%llu</num_blocks>\n",
+ data->storage.num_blocks);
}
if (data->storage.flags & VIR_NODE_DEV_CAP_STORAGE_HOTPLUGGABLE)
virBufferAddLit(&buf,
@@ -1239,6 +1251,7 @@ 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 29a4d43..95910c5 100644
--- a/src/conf/node_device_conf.h
+++ b/src/conf/node_device_conf.h
@@ -101,6 +101,7 @@ struct _virNodeDevCapsDef {
unsigned function;
unsigned product;
unsigned vendor;
+ unsigned class;
char *product_name;
char *vendor_name;
} pci_dev;
@@ -117,10 +118,12 @@ struct _virNodeDevCapsDef {
unsigned _class; /* "class" is reserved in C */
unsigned subclass;
unsigned protocol;
+ char *interface_name;
char *description;
} usb_if;
struct {
char *address;
+ unsigned address_len;
char *ifname;
enum virNodeDevNetCapType subtype; /* LAST -> no subtype */
} net;
@@ -139,6 +142,8 @@ struct _virNodeDevCapsDef {
} scsi;
struct {
unsigned long long size;
+ unsigned long long num_blocks;
+ unsigned long long logical_block_size;
unsigned long long removable_media_size;
char *block;
char *bus;
diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c
index 14b3098..f3bd45d 100644
--- a/src/node_device/node_device_driver.c
+++ b/src/node_device/node_device_driver.c
@@ -70,9 +70,12 @@ static int update_caps(virNodeDeviceObjPtr dev)
}
-#ifdef __linux__
-static int update_driver_name(virConnectPtr conn,
- virNodeDeviceObjPtr dev)
+#if defined (__linux__) && defined (HAVE_HAL)
+/* XXX Why does this function exist? Are there devices that change
+ * their drivers while running? Under libudev, most devices seem to
+ * provide their driver name as a property "DRIVER" */
+static int update_driver_name_hal_linux(virConnectPtr conn,
+ virNodeDeviceObjPtr dev)
{
char *driver_link = NULL;
char devpath[PATH_MAX];
@@ -754,5 +757,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 db01624..5be0781 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 c859fe3..8ac8a35 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 0000000..32fde52
--- /dev/null
+++ b/src/node_device/node_device_udev.c
@@ -0,0 +1,800 @@
+/*
+ * 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(a)redhat.com>
+ */
+
+#include <config.h>
+#include <libudev.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 "util.h"
+#include "buf.h"
+
+#define VIR_FROM_THIS VIR_FROM_NODEDEV
+
+static virDeviceMonitorStatePtr driverState = NULL;
+
+/* 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 = -1;
+
+ udev_value = udev_device_get_property_value(udev_device, property_key);
+ if (udev_value == NULL) {
+ VIR_ERROR(_("udev reports property '%s' does not exist"),
+ property_key);
+ 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 '%s' on device '%s'"),
+ property_key, udev_device_get_sysname(udev_device));
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+ if (ret != 0) {
+ VIR_ERROR(_("Failed to get udev property '%s' for device '%s'"),
+ property_key, udev_device_get_sysname(udev_device));
+ }
+
+ 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 = 0;
+
+ ret = udevGetDeviceProperty(udev_device, property_key, &udev_value);
+
+ if (ret == 0) {
+ ret = virStrToLong_i(udev_value, NULL, base, value);
+ }
+
+ 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 = 0;
+
+ ret = udevGetDeviceProperty(udev_device, property_key, &udev_value);
+
+ if (ret == 0) {
+ ret = virStrToLong_ui(udev_value, NULL, base, value);
+ }
+
+ VIR_FREE(udev_value);
+ return ret;
+}
+
+
+static int udevGetStringSysfsAttr(struct udev_device *udev_device,
+ const char *attr_name,
+ char **value)
+{
+ const char *udev_value = NULL;
+ int ret = -1;
+
+ udev_value = udev_device_get_sysattr_value(udev_device, attr_name);
+
+ if (udev_value != NULL) {
+ *value = strdup(udev_value);
+ if (*value != NULL) {
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+
+static int udevGetIntSysfsAttr(struct udev_device *udev_device,
+ const char *attr_name,
+ int *value,
+ int base)
+{
+ const char *udev_value = NULL;
+ int ret = -1;
+
+ udev_value = udev_device_get_sysattr_value(udev_device, attr_name);
+
+ if (udev_value != NULL) {
+ ret = virStrToLong_i(udev_value, NULL, base, value);
+ }
+
+ return ret;
+}
+
+
+static int udevGetUintSysfsAttr(struct udev_device *udev_device,
+ const char *attr_name,
+ unsigned int *value,
+ int base)
+{
+ const char *udev_value = NULL;
+ int ret = -1;
+
+ udev_value = udev_device_get_sysattr_value(udev_device, attr_name);
+
+ if (udev_value != NULL) {
+ ret = virStrToLong_ui(udev_value, NULL, base, value);
+ }
+
+ return ret;
+}
+
+
+static int udevGetUint64SysfsAttr(struct udev_device *udev_device,
+ const char *attr_name,
+ uint64_t *value)
+{
+ const char *udev_value = NULL;
+ int ret = -1;
+
+ udev_value = udev_device_get_sysattr_value(udev_device, attr_name);
+
+ if (udev_value != NULL) {
+ ret = virStrToLong_ull(udev_value, NULL, 0, value);
+ }
+
+ 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;
+ int ret = -1;
+
+ devpath = udev_device_get_devpath(device);
+
+ char *p = strrchr(devpath, '/');
+ if (p) {
+ virStrToLong_ui(p+1, &p, 16, &def->caps->data.pci_dev.domain);
+ virStrToLong_ui(p+1, &p, 16, &def->caps->data.pci_dev.bus);
+ virStrToLong_ui(p+1, &p, 16, &def->caps->data.pci_dev.slot);
+ virStrToLong_ui(p+1, &p, 16, &def->caps->data.pci_dev.function);
+ }
+
+ udevGetUintSysfsAttr(device, "vendor", &def->caps->data.pci_dev.vendor, 0);
+ udevGetUintSysfsAttr(device, "product", &def->caps->data.pci_dev.product, 0);
+
+ /* FIXME: to do the vendor name and product name, we have to
+ * parse /usr/share/hwdata/pci.ids. Look in hal/hald/ids.c
+ */
+
+ ret = 0;
+
+ return ret;
+}
+
+
+static int udevProcessUSBDevice(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ int ret = 0;
+ udevGetUintProperty(device, "BUSNUM", &def->caps->data.usb_dev.bus, 0);
+ udevGetUintProperty(device, "DEVNUM", &def->caps->data.usb_dev.device, 0);
+ udevGetUintProperty(device, "ID_VENDOR_ID", &def->caps->data.usb_dev.vendor, 16);
+ udevGetStringProperty(device, "ID_VENDOR", &def->caps->data.usb_dev.vendor_name);
+ udevGetUintProperty(device, "ID_MODEL_ID", &def->caps->data.usb_dev.product, 0);
+ udevGetStringProperty(device, "ID_MODEL", &def->caps->data.usb_dev.product_name);
+
+ return ret;
+}
+
+
+/* XXX I don't fully understand the properties here. Advice needed. */
+static int udevProcessUSBInterface(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ int ret = 0;
+ char *tmp = NULL, *p = NULL;
+
+ udevGetStringProperty(device,
+ "INTERFACE",
+ &def->caps->data.usb_if.interface_name);
+
+ tmp = strdup(def->caps->data.usb_if.interface_name);
+
+ p = strrchr(tmp, '/');
+ if (p) {
+ *p = '\0';
+ virStrToLong_ui(tmp, NULL, 0, &def->caps->data.usb_if.number);
+ virStrToLong_ui(p+1, &p, 0, &def->caps->data.usb_if._class);
+ virStrToLong_ui(p+1, &p, 0, &def->caps->data.usb_if.subclass);
+ }
+
+ VIR_FREE(tmp);
+ return ret;
+}
+
+
+static int udevProcessNetworkInterface(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ int ret = 0;
+
+ udevGetStringSysfsAttr(device, "address", &def->caps->data.net.address);
+ udevGetUintSysfsAttr(device, "addr_len", &def->caps->data.net.address_len, 0);
+
+ return ret;
+}
+
+
+static int udevProcessSCSIHost(struct udev_device *device ATTRIBUTE_UNUSED,
+ virNodeDeviceDefPtr def)
+{
+ int ret = 0;
+ char *filename = NULL;
+
+ filename = basename(def->name);
+
+ if (STRPREFIX(filename, "host")) {
+ /* XXX There's really no better way to get the host #? */
+ ret = virStrToLong_ui(filename + strlen("host"),
+ NULL,
+ 0,
+ &def->caps->data.scsi_host.host);
+ check_fc_host(&def->caps->data);
+ check_vport_capable(&def->caps->data);
+ }
+
+ return ret;
+}
+
+
+static int udevProcessSCSIDevice(struct udev_device *device ATTRIBUTE_UNUSED,
+ virNodeDeviceDefPtr def)
+{
+ int ret = 0;
+ char *filename = NULL, *p = NULL;
+
+ filename = basename(def->name);
+
+ virStrToLong_ui(filename, &p, 10, &def->caps->data.scsi.host);
+ virStrToLong_ui(p+1, &p, 10, &def->caps->data.scsi.bus);
+ virStrToLong_ui(p+1, &p, 10, &def->caps->data.scsi.target);
+ virStrToLong_ui(p+1, &p, 10, &def->caps->data.scsi.lun);
+
+ return ret;
+}
+
+
+static int udevProcessDisk(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ int ret = 0;
+
+ def->caps->data.storage.drive_type = strdup("disk");
+ if (def->caps->data.storage.drive_type == NULL) {
+ ret = -1;
+ goto out;
+ }
+
+ udevGetUint64SysfsAttr(device, "size", &def->caps->data.storage.num_blocks);
+ udevGetUint64SysfsAttr(device, "queue/logical_block_size",
+ &def->caps->data.storage.logical_block_size);
+
+ def->caps->data.storage.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 tmp_int = 0, ret = 0;
+
+ if ((udevGetIntSysfsAttr(device, "removable", &tmp_int, 0) == 0) &&
+ (tmp_int == 1)) {
+
+ def->caps->data.storage.flags |= VIR_NODE_DEV_CAP_STORAGE_REMOVABLE;
+
+ if ((udevGetIntProperty(device, "ID_CDROM_MEDIA", &tmp_int, 0) == 0) &&
+ (tmp_int == 1)) {
+
+ def->caps->data.storage.flags |=
+ VIR_NODE_DEV_CAP_STORAGE_REMOVABLE_MEDIA_AVAILABLE;
+
+ udevGetUint64SysfsAttr(device, "size", &def->caps->data.storage.num_blocks);
+ udevGetUint64SysfsAttr(device, "queue/logical_block_size",
+ &def->caps->data.storage.logical_block_size);
+
+ /* 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;
+ }
+ }
+
+ 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;
+
+ 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;
+ }
+ }
+
+ 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)
+{
+ int ret = -1;
+
+ def->caps->data.storage.block = strdup(udev_device_get_devnode(device));
+ udevGetStringProperty(device, "DEVNAME", &def->caps->data.storage.block);
+ udevGetStringProperty(device, "ID_BUS", &def->caps->data.storage.bus);
+ udevGetStringProperty(device, "ID_SERIAL", &def->caps->data.storage.serial);
+ udevGetStringSysfsAttr(device, "device/vendor", &def->caps->data.storage.vendor);
+ udevStripSpaces(def->caps->data.storage.vendor);
+ udevGetStringSysfsAttr(device, "device/model", &def->caps->data.storage.model);
+ udevStripSpaces(def->caps->data.storage.model);
+ /* XXX Is there an equivalent of the HAL hotpluggable property?
+ * Is the hotpluggable property still relevant? */
+
+ if (udevGetStringProperty(device,
+ "ID_TYPE",
+ &def->caps->data.storage.drive_type) != 0) {
+ /* If udev doesn't have it, perhaps we can guess it. */
+ if (udevKludgeStorageType(def) != 0) {
+ goto out;
+ }
+ }
+
+ /* NB: drive_type has changed from HAL; now it's "cd" instead of "cdrom" */
+ 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 {
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+
+static int udevSetDeviceType(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ const char *devtype = NULL;
+ int ret = 0;
+
+ if (VIR_ALLOC(def->caps) != 0) {
+ ret = -1;
+ goto out;
+ }
+
+ devtype = udev_device_get_devtype(device);
+
+ if (udevGetUintProperty(device,
+ "PCI_CLASS",
+ &def->caps->data.pci_dev.class,
+ 0) == 0) {
+ def->caps->type = VIR_NODE_DEV_CAP_PCI_DEV;
+ goto out;
+ }
+
+ if (devtype != NULL && STREQ(devtype, "usb_device")) {
+ def->caps->type = VIR_NODE_DEV_CAP_USB_DEV;
+ goto out;
+ }
+
+ if (devtype != NULL && STREQ(devtype, "usb_interface")) {
+ def->caps->type = VIR_NODE_DEV_CAP_USB_INTERFACE;
+ goto out;
+ }
+
+ /* XXX Do some network interfaces set the device type property? */
+ if (udevGetStringProperty(device,
+ "INTERFACE",
+ &def->caps->data.net.ifname) == 0) {
+
+ def->caps->type = VIR_NODE_DEV_CAP_NET;
+ goto out;
+ }
+
+ if (devtype != NULL && STREQ(devtype, "scsi_host")) {
+ def->caps->type = VIR_NODE_DEV_CAP_SCSI_HOST;
+ goto out;
+ }
+
+ if (devtype != NULL && STREQ(devtype, "scsi_device")) {
+ def->caps->type = VIR_NODE_DEV_CAP_SCSI;
+ goto out;
+ }
+
+ if (devtype != NULL && STREQ(devtype, "disk")) {
+ def->caps->type = VIR_NODE_DEV_CAP_STORAGE;
+ goto out;
+ }
+
+ 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:
+ /* What do we want to do with system--there's no libudev equivalent. */
+ 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:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+
+static int udevProcessOneDevice(struct udev_device *device,
+ virNodeDeviceDefPtr def)
+{
+ struct udev_device *parent = NULL;
+ virNodeDeviceObjPtr dev = NULL;
+ const char *parent_name = NULL;
+ int ret = -1;
+
+ def->name = strdup(udev_device_get_syspath(device));
+ udevGetStringProperty(device, "DRIVER", &def->driver);
+
+ /* How to handle parent devices? Shall we change the meaning of
+ * the parent pointer for the udev backend? What does client code
+ * use the parent relationship for? We really want to preserve
+ * the behavior of the HAL backend, but that may be difficult in
+ * practice. */
+ parent = udev_device_get_parent(device);
+ if (parent != NULL) {
+ parent_name = udev_device_get_syspath(parent);
+ if (parent_name != NULL) {
+ def->parent = strdup(parent_name);
+ }
+ }
+
+ if (udevSetDeviceType(device, def) == 0) {
+ ret = udevGetDeviceDetails(device, def);
+ }
+
+ if (ret == 0) {
+ dev = virNodeDeviceAssignDef(NULL, &driverState->devs, def);
+ dev->devicePath = strdup(udev_device_get_devpath(device));
+ virNodeDeviceObjUnlock(dev);
+ } else {
+ virNodeDeviceDefFree(def);
+ }
+
+ return ret;
+}
+
+
+static int udevProcessDeviceListEntry(struct udev *udev,
+ struct udev_list_entry *list_entry)
+{
+ struct udev_device *device;
+ virNodeDeviceDefPtr def = NULL;
+ const char *name = NULL;
+ int ret = -1;
+
+ name = udev_list_entry_get_name(list_entry);
+
+ if (VIR_ALLOC(def) != 0) {
+ goto out;
+ }
+
+ device = udev_device_new_from_syspath(udev, name);
+ if (device != NULL) {
+ udevProcessOneDevice(device, def);
+ udev_device_unref(device);
+ ret = 0;
+ }
+
+out:
+ 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 int udevDeviceMonitorStartup(int privileged ATTRIBUTE_UNUSED)
+{
+ struct udev *udev = NULL;
+ struct udev_monitor *udev_monitor = NULL;
+ int ret = 0;
+
+ if (VIR_ALLOC(driverState) < 0) {
+ ret = -1;
+ goto out;
+ }
+
+ if (virMutexInit(&driverState->lock) < 0) {
+ VIR_FREE(driverState);
+ ret = -1;
+ goto out;
+ }
+
+ nodeDeviceLock(driverState);
+
+ /*
+ * http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/libudev-udev...
+ *
+ * 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) {
+ ret = -1;
+ goto out;
+ }
+
+ /* udev can be retrieved from udev_monitor */
+ driverState->privateData = udev_monitor;
+ nodeDeviceUnlock(driverState);
+
+ /* 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_ERROR0("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 0000000..bac628d
--- /dev/null
+++ b/src/node_device/node_device_udev.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <dallan(a)redhat.com>
+ */
+
+#include <libudev.h>
+#include <stdint.h>
+
+#define SYSFS_DATA_SIZE 4096
+#define DRV_STATE_UDEV_MONITOR(ds) ((struct udev_monitor *)((ds)->privateData))
diff --git a/src/util/util.c b/src/util/util.c
index 98f8a14..f63bd39 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -2114,3 +2114,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 8679636..da410e1 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__ */
--
1.6.4.4
15 years, 2 months