diff --git a/configure.in b/configure.in
index 66d271a..3441742 100644
--- a/configure.in
+++ b/configure.in
@@ -1048,6 +1048,93 @@ 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
+HAL_REQUIRED=0.0
+HAL_CFLAGS=
+HAL_LIBS=
+AC_ARG_WITH([hal],
+  [  --with-hal         use HAL for host device enumeration],
+  [],
+  [with_hal=check])
+
+if test "x$with_hal" = "xyes" -o "x$with_hal" = "xcheck"; then
+  PKG_CHECK_MODULES(HAL, hal >= $HAL_REQUIRED,
+    [with_hal=yes], [
+    if test "x$with_hal" = "xcheck" ; then
+       with_hal=no
+    else
+       AC_MSG_ERROR(
+         [You must install hal-devel >= $HAL_REQUIRED to compile libvirt])
+    fi
+  ])
+  if test "x$with_hal" = "xyes" ; then
+    AC_DEFINE_UNQUOTED([HAVE_HAL], 1,
+      [use HAL for host device enumeration])
+
+    old_CFLAGS=$CFLAGS
+    old_LDFLAGS=$LDFLAGS
+    CFLAGS="$CFLAGS $HAL_CFLAGS"
+    LDFLAGS="$LDFLAGS $HAL_LIBS"
+    AC_CHECK_FUNCS([libhal_get_all_devices],,[with_hal=no])
+    CFLAGS="$old_CFLAGS"
+    LDFLAGS="$old_LDFLAGS"
+  fi
+fi
+AM_CONDITIONAL([HAVE_HAL], [test "x$with_hal" = "xyes"])
+AC_SUBST([HAL_CFLAGS])
+AC_SUBST([HAL_LIBS])
+
+DEVKIT_REQUIRED=0.0
+DEVKIT_CFLAGS=
+DEVKIT_LIBS=
+AC_ARG_WITH([devkit],
+  [  --with-devkit      use DeviceKit for host device enumeration],
+  [],
+  [with_devkit=check])
+
+dnl Extra check needed while devkit pkg-config info missing glib2 dependency
+PKG_CHECK_MODULES(GLIB2, glib-2.0 >= 0.0,,[
+  if test "x$with_devkit" = "xcheck"; then
+    with_devkit=no
+  elif test "x$with_devkit" = "xyes"; then
+    AC_MSG_ERROR([required package DeviceKit requires package glib-2.0])
+  fi])
+
+if test "x$with_devkit" = "xyes" -o "x$with_devkit" = "xcheck"; then
+  PKG_CHECK_MODULES(DEVKIT, devkit-gobject >= $DEVKIT_REQUIRED,
+    [with_devkit=yes], [
+    if test "x$with_devkit" = "xcheck" ; then
+       with_devkit=no
+    else
+       AC_MSG_ERROR(
+         [You must install DeviceKit-devel >= $DEVKIT_REQUIRED to compile libvirt])
+    fi
+  ])
+  if test "x$with_devkit" = "xyes" ; then
+    AC_DEFINE_UNQUOTED([HAVE_DEVKIT], 1,
+      [use DeviceKit for host device enumeration])
+
+    dnl Add glib2 flags explicitly while devkit pkg-config info missing glib2 dependency
+    DEVKIT_CFLAGS="$GLIB2_CFLAGS $DEVKIT_CFLAGS"
+    DEVKIT_LIBS="$GLIB2_LIBS $DEVKIT_LIBS"
+
+    dnl Add more flags apparently required for devkit to work properly
+    DEVKIT_CFLAGS="$DEVKIT_CFLAGS -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT"
+
+    old_CFLAGS=$CFLAGS
+    old_LDFLAGS=$LDFLAGS
+    CFLAGS="$CFLAGS $DEVKIT_CFLAGS"
+    LDFLAGS="$LDFLAGS $DEVKIT_LIBS"
+    AC_CHECK_FUNCS([devkit_client_connect],,[with_devkit=no])
+    CFLAGS="$old_CFLAGS"
+    LDFLAGS="$old_LDFLAGS"
+  fi
+fi
+AM_CONDITIONAL([HAVE_DEVKIT], [test "x$with_devkit" = "xyes"])
+AC_SUBST([DEVKIT_CFLAGS])
+AC_SUBST([DEVKIT_LIBS])
+
+
 # very annoying
 rm -f COPYING
 cp COPYING.LIB COPYING
@@ -1123,6 +1210,16 @@ AC_MSG_NOTICE([  numactl: $NUMACTL_CFLAGS $NUMACTL_LIBS])
 else
 AC_MSG_NOTICE([  numactl: no])
 fi
+if test "$with_hal" = "yes" ; then
+AC_MSG_NOTICE([  hal: $HAL_CFLAGS $HAL_LIBS])
+else
+AC_MSG_NOTICE([  hal: no])
+fi
+if test "$with_devkit" = "yes" ; then
+AC_MSG_NOTICE([  devkit: $DEVKIT_CFLAGS $DEVKIT_LIBS])
+else
+AC_MSG_NOTICE([  devkit: no])
+fi
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([Test suite])
 AC_MSG_NOTICE([])
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f671155..684d300 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -12,6 +12,7 @@ src/lxc_controller.c
 src/lxc_driver.c
 src/network_conf.c
 src/network_driver.c
+src/node_device.c
 src/openvz_conf.c
 src/openvz_driver.c
 src/proxy_internal.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 5a769f8..963a307 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,22 @@
 ## Process this file with automake to produce Makefile.in
 
+if WITH_LIBVIRTD
+NODE_DEVICE_SOURCES = node_device.c node_device.h \
+		node_device_conf.c node_device_conf.h
+NODE_DEVICE_CFLAGS =
+NODE_DEVICE_LIBS =
+if HAVE_HAL
+NODE_DEVICE_CFLAGS += $(HAL_CFLAGS)
+NODE_DEVICE_LIBS += $(HAL_LIBS)
+NODE_DEVICE_SOURCES += node_device_hal.c
+endif
+if HAVE_DEVKIT
+NODE_DEVICE_CFLAGS += $(DEVKIT_CFLAGS)
+NODE_DEVICE_LIBS += $(DEVKIT_LIBS)
+NODE_DEVICE_SOURCES += node_device_devkit.c
+endif
+endif
+
 INCLUDES = \
 	   -I$(top_srcdir)/gnulib/lib -I../gnulib/lib \
 	   -I../include \
@@ -10,6 +27,7 @@ INCLUDES = \
 	   $(SASL_CFLAGS) \
 	   $(SELINUX_CFLAGS) \
 	   $(NUMACTL_CFLAGS) \
+	   $(NODE_DEVICE_CFLAGS) \
 	   -DBINDIR=\""$(libexecdir)"\" \
 	   -DSBINDIR=\""$(sbindir)"\" \
 	   -DSYSCONF_DIR="\"$(sysconfdir)\"" \
@@ -146,6 +164,7 @@ libvirt_la_SOURCES =						\
 		libvirt.c					\
 		$(GENERIC_LIB_SOURCES)				\
 		$(DOMAIN_CONF_SOURCES)				\
+		$(NODE_DEVICE_SOURCES)				\
 		$(NETWORK_CONF_SOURCES)				\
 		$(STORAGE_CONF_SOURCES)
 
@@ -212,7 +231,7 @@ EXTRA_DIST +=							\
 
 
 libvirt_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(SASL_LIBS) $(SELINUX_LIBS) \
-                    $(NUMACTL_LIBS) \
+                    $(NUMACTL_LIBS) $(NODE_DEVICE_LIBS) \
 		    @CYGWIN_EXTRA_LIBADD@ ../gnulib/lib/libgnu.la
 libvirt_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libvirt_sym.version \
                      -version-info @LIBVIRT_VERSION_INFO@ \
@@ -334,7 +353,7 @@ libvirt_lxc_SOURCES =						\
 		$(GENERIC_LIB_SOURCES) 				\
 		$(DOMAIN_CONF_SOURCES)
 libvirt_lxc_LDFLAGS = $(WARN_CFLAGS) $(COVERAGE_LDCFLAGS)
-libvirt_lxc_LDADD = $(LIBXML_LIBS) ../gnulib/lib/libgnu.la
+libvirt_lxc_LDADD = $(LIBXML_LIBS) ../gnulib/lib/libgnu.la $(NODE_DEVICE_LIBS)
 libvirt_lxc_CFLAGS =  $(LIBPARTED_CFLAGS)
 endif
 endif
diff --git a/src/driver.h b/src/driver.h
index 0540f80..494e2ea 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -601,6 +601,73 @@ struct _virStateDriver {
 };
 #endif
 
+
+typedef struct _virNodeDriver virNodeDriver;
+typedef virNodeDriver *virNodeDriverPtr;
+
+typedef int (*virDrvNodeNumOfDevices)(virConnectPtr conn,
+                                      unsigned int flags);
+
+typedef int (*virDrvNodeListDevices)(virConnectPtr conn,
+                                     char **const names,
+                                     int maxnames,
+                                     unsigned int flags);
+
+typedef int (*virDrvNodeNumOfDevicesByCap)(virConnectPtr conn,
+                                           const char *cap,
+                                           unsigned int flags);
+
+typedef int (*virDrvNodeListDevicesByCap)(virConnectPtr conn,
+                                          const char *cap,
+                                          char **const names,
+                                          int maxnames,
+                                          unsigned int flags);
+
+typedef virNodeDevicePtr (*virDrvNodeDeviceLookupByName)(virConnectPtr conn,
+                                                         const char *name);
+
+typedef char * (*virDrvNodeDeviceDumpXML)(virNodeDevicePtr dev,
+                                          unsigned int flags);
+
+typedef char * (*virDrvNodeDeviceGetParent)(virNodeDevicePtr dev);
+
+typedef int (*virDrvNodeDeviceNumOfCaps)(virNodeDevicePtr dev);
+
+typedef int (*virDrvNodeDeviceListCaps)(virNodeDevicePtr dev,
+                                        char **const names,
+                                        int maxnames);
+
+typedef virNodeDevicePtr (*virDrvNodeDeviceCreate)(virConnectPtr conn,
+                                                   const char *xml,
+                                                   unsigned int flags);
+
+typedef int (*virDrvNodeDeviceDestroy)(virNodeDevicePtr dev,
+                                       unsigned int flags);
+
+/**
+ * _virNodeDriver:
+ *
+ * Structure associated with a virtualized node, independent of
+ * the hypervisor(s) in use, and entry points for it.
+ *
+ */
+struct _virNodeDriver {
+    const char * name;    /* the name of the driver */
+    virDrvOpen open;
+    virDrvClose close;
+    virDrvNodeNumOfDevices numOfDevices;
+    virDrvNodeListDevices listDevices;
+    virDrvNodeNumOfDevicesByCap numOfDevicesByCap;
+    virDrvNodeListDevicesByCap listDevicesByCap;
+    virDrvNodeDeviceLookupByName deviceLookupByName;
+    virDrvNodeDeviceDumpXML deviceDumpXML;
+    virDrvNodeDeviceGetParent deviceGetParent;
+    virDrvNodeDeviceNumOfCaps deviceNumOfCaps;
+    virDrvNodeDeviceListCaps deviceListCaps;
+    virDrvNodeDeviceCreate deviceCreate;
+    virDrvNodeDeviceDestroy deviceDestroy;
+};
+
 /*
  * Registration
  * TODO: also need ways to (des)activate a given driver
@@ -609,6 +676,7 @@ struct _virStateDriver {
 int virRegisterDriver(virDriverPtr);
 int virRegisterNetworkDriver(virNetworkDriverPtr);
 int virRegisterStorageDriver(virStorageDriverPtr);
+int virRegisterNodeDriver(virNodeDriverPtr);
 #ifdef WITH_LIBVIRTD
 int virRegisterStateDriver(virStateDriverPtr);
 #endif
diff --git a/src/node_device.c b/src/node_device.c
new file mode 100644
index 0000000..691228f
--- /dev/null
+++ b/src/node_device.c
@@ -0,0 +1,222 @@
+/*
+ * node_device.c: node device enumeration
+ *
+ * Copyright (C) 2008 Virtual Iron Software, Inc.
+ * Copyright (C) 2008 David F. Lively
+ *
+ * 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: David F. Lively <dlively@virtualiron.com>
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <errno.h>
+
+#include "internal.h"
+#include "memory.h"
+
+#include "node_device_conf.h"
+
+static int dev_has_cap(const virNodeDeviceObjPtr dev, const char *cap)
+{
+    virNodeDevCapsDefPtr caps = dev->def->caps;
+    while (caps) {
+        if (STREQ(cap, virNodeDevCapTypeToString(caps->type)))
+            return 1;
+        caps = caps->next;
+    }
+    return 0;
+}
+
+
+static int nodeNumOfDevices(virConnectPtr conn,
+                            unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virNodeDriverStatePtr driver = conn->nodePrivateData;
+    return driver->devs.count;
+}
+
+static int
+nodeListDevices(virConnectPtr conn, char **const names, int maxnames,
+                unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virNodeDriverStatePtr driver = conn->nodePrivateData;
+    int ndevs = 0;
+    unsigned int i;
+
+    for (i = 0; i < driver->devs.count && ndevs < maxnames; i++)
+        if ((names[ndevs++] = strdup(driver->devs.objs[i]->def->name)) == NULL)
+            goto failure;
+
+    return ndevs;
+
+ failure:
+    --ndevs;
+    while (--ndevs >= 0)
+        VIR_FREE(names[ndevs]);
+    return -1;
+}
+
+
+static int nodeNumOfDevicesByCap(virConnectPtr conn,
+                                 const char *cap,
+                                 unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virNodeDriverStatePtr driver = conn->nodePrivateData;
+    int ndevs = 0;
+    unsigned int i;
+
+    for (i = 0; i < driver->devs.count; i++)
+        if (dev_has_cap(driver->devs.objs[i], cap))
+            ++ndevs;
+
+    return ndevs;
+}
+
+static int nodeListDevicesByCap(virConnectPtr conn,
+                                const char *cap,
+                                char **const names,
+                                int maxnames,
+                                unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virNodeDriverStatePtr driver = conn->nodePrivateData;
+    int ndevs = 0;
+    unsigned int i;
+
+    for (i = 0; i < driver->devs.count && ndevs < maxnames; i++)
+        if (dev_has_cap(driver->devs.objs[i], cap))
+            if ((names[ndevs++] = strdup(driver->devs.objs[i]->def->name)) == NULL)
+                goto failure;
+
+    return ndevs;
+
+ failure:
+    --ndevs;
+    while (--ndevs >= 0)
+        VIR_FREE(names[ndevs]);
+    return -1;
+}
+
+static virNodeDevicePtr nodeDeviceLookupByName(virConnectPtr conn,
+                                               const char *name)
+{
+    virNodeDriverStatePtr driver = conn->nodePrivateData;
+    virNodeDeviceObjPtr obj = virNodeDeviceFindByName(&driver->devs, name);
+
+    if (!obj) {
+        virNodeDeviceReportError(conn, VIR_ERR_INVALID_NODE_DEVICE,
+                                 "%s", _("no node device with matching name"));
+        return NULL;
+    }
+
+    return virGetNodeDevice(conn, name);
+
+}
+
+static char *nodeDeviceDumpXML(virNodeDevicePtr dev,
+                               unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virNodeDriverStatePtr driver = dev->conn->nodePrivateData;
+    virNodeDeviceObjPtr obj = virNodeDeviceFindByName(&driver->devs, dev->name);
+
+    if (!obj) {
+        virNodeDeviceReportError(dev->conn, VIR_ERR_INVALID_NODE_DEVICE,
+                              "%s", _("no node device with matching name"));
+        return NULL;
+    }
+
+    return virNodeDeviceDefFormat(dev->conn, obj->def);
+}
+
+
+static char *nodeDeviceGetParent(virNodeDevicePtr dev)
+{
+    virNodeDriverStatePtr driver = dev->conn->nodePrivateData;
+    virNodeDeviceObjPtr obj = virNodeDeviceFindByName(&driver->devs, dev->name);
+
+    if (!obj) {
+        virNodeDeviceReportError(dev->conn, VIR_ERR_INVALID_NODE_DEVICE,
+                              "%s", _("no node device with matching name"));
+        return NULL;
+    }
+
+    return obj->def->parent;
+}
+
+
+static int nodeDeviceNumOfCaps(virNodeDevicePtr dev)
+{
+    virNodeDriverStatePtr driver = dev->conn->nodePrivateData;
+    virNodeDeviceObjPtr obj = virNodeDeviceFindByName(&driver->devs, dev->name);
+    virNodeDevCapsDefPtr caps;
+    int ncaps = 0;
+
+    if (!obj) {
+        virNodeDeviceReportError(dev->conn, VIR_ERR_INVALID_NODE_DEVICE,
+                              "%s", _("no node device with matching name"));
+        return -1;
+    }
+
+    for (caps = obj->def->caps; caps; caps = caps->next)
+        ++ncaps;
+
+    return ncaps;
+}
+
+
+static int
+nodeDeviceListCaps(virNodeDevicePtr dev, char **const names, int maxnames)
+{
+    virNodeDriverStatePtr driver = dev->conn->nodePrivateData;
+    virNodeDeviceObjPtr obj = virNodeDeviceFindByName(&driver->devs, dev->name);
+    virNodeDevCapsDefPtr caps;
+    int ncaps = 0;
+
+    if (!obj) {
+        virNodeDeviceReportError(dev->conn, VIR_ERR_INVALID_NODE_DEVICE,
+                              "%s", _("no node device with matching name"));
+        return -1;
+    }
+
+    for (caps = obj->def->caps; caps && ncaps < maxnames; caps = caps->next) {
+        names[ncaps] = strdup(virNodeDevCapTypeToString(caps->type));
+        if (names[ncaps++] == NULL)
+            goto failure;
+    }
+
+    return ncaps;
+
+ failure:
+    --ncaps;
+    while (--ncaps >= 0)
+        VIR_FREE(names[ncaps]);
+    return -1;
+}
+
+
+void registerCommonNodeFuncs(virNodeDriverPtr driver)
+{
+    driver->numOfDevices = nodeNumOfDevices;
+    driver->listDevices = nodeListDevices;
+    driver->numOfDevicesByCap = nodeNumOfDevicesByCap;
+    driver->listDevicesByCap = nodeListDevicesByCap;
+    driver->deviceLookupByName = nodeDeviceLookupByName;
+    driver->deviceDumpXML = nodeDeviceDumpXML;
+    driver->deviceGetParent = nodeDeviceGetParent;
+    driver->deviceNumOfCaps = nodeDeviceNumOfCaps;
+    driver->deviceListCaps = nodeDeviceListCaps;
+}
diff --git a/src/node_device.h b/src/node_device.h
new file mode 100644
index 0000000..2550e4b
--- /dev/null
+++ b/src/node_device.h
@@ -0,0 +1,38 @@
+/*
+ * node_device.h: node device enumeration
+ *
+ * Copyright (C) 2008 Virtual Iron Software, Inc.
+ * Copyright (C) 2008 David F. Lively
+ *
+ * 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: David F. Lively <dlively@virtualiron.com>
+ */
+
+#ifndef __VIR_NODE_DEVICE_H__
+#define __VIR_NODE_DEVICE_H__
+
+#include "libvirt/libvirt.h"
+
+#ifdef HAVE_HAL
+int halNodeRegister(void);
+#endif
+#ifdef HAVE_DEVKIT
+int devkitNodeRegister(void);
+#endif
+
+void registerCommonNodeFuncs(virNodeDriverPtr driver);
+
+#endif /* __VIR_NODE_DEVICE_H__ */
diff --git a/src/node_device_devkit.c b/src/node_device_devkit.c
new file mode 100644
index 0000000..c728ca1
--- /dev/null
+++ b/src/node_device_devkit.c
@@ -0,0 +1,418 @@
+/*
+ * node_device_devkit.c: node device enumeration - DeviceKit-based implementation
+ *
+ * Copyright (C) 2008 Virtual Iron Software, Inc.
+ * Copyright (C) 2008 David F. Lively
+ *
+ * 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: David F. Lively <dlively@virtualiron.com>
+ */
+
+#include <config.h>
+#include <devkit-gobject.h>
+
+#include "node_device_conf.h"
+#include "internal.h"
+#include "memory.h"
+#include "uuid.h"
+
+/*
+ * Host device enumeration (DeviceKit implementation)
+ */
+
+static virNodeDriverStatePtr driverState;
+
+#define DEBUG(fmt,...) VIR_DEBUG(__FILE__, fmt, __VA_ARGS__)
+#define DEBUG0(msg) VIR_DEBUG(__FILE__, "%s", msg)
+
+#define CONN_DRV_STATE(conn) ((virNodeDriverStatePtr)((conn)->nodePrivateData))
+#define DRV_STATE_DKCLIENT(ds) ((DevkitClient *)((ds)->privateData))
+#define CONN_DKCLIENT(conn) DRV_STATE_DKCLIENT(CONN_DRV_STATE(conn))
+
+#define NODE_DEV_DKDEV(obj) ((DevkitDevice *)((obj)->privateData)
+
+static int get_str_prop(DevkitDevice *dkdev, const char *prop, char **val_p)
+{
+    char *val = devkit_device_dup_property_as_str(dkdev, prop);
+
+    if (val) {
+        if (*val) {
+            *val_p = val;
+            return 0;
+        } else {
+            /* Treat empty strings as NULL values */
+            VIR_FREE(val);
+        }
+    }
+
+    return -1;
+}
+
+#if 0
+static int get_int_prop(DevkitDevice *dkdev, const char *prop, int *val_p)
+{
+    if (! devkit_device_has_property(dkdev, prop))
+        return -1;
+    *val_p = devkit_device_get_property_as_int(dkdev, prop);
+    return 0;
+}
+
+static int get_uint64_prop(DevkitDevice *dkdev, const char *prop,
+                           unsigned long long *val_p)
+{
+    if (! devkit_device_has_property(dkdev, prop))
+        return -1;
+    *val_p = devkit_device_get_property_as_uint64(dkdev, prop);
+    return 0;
+}
+#endif
+
+static int gather_pci_cap(DevkitDevice *dkdev,
+                          union _virNodeDevCapData *d)
+{
+    const char *sysfs_path = devkit_device_get_native_path(dkdev);
+
+    if (sysfs_path != NULL) {
+        char *p = strrchr(sysfs_path, '/');
+        if (p) {
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.domain);
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.bus);
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.slot);
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.function);
+        }
+    }
+    return 0;
+}
+
+
+static int gather_usb_cap(DevkitDevice *dkdev,
+                          union _virNodeDevCapData *d)
+{
+    (void)get_str_prop(dkdev, "ID_VENDOR", &d->usb_dev.vendor_name);
+    (void)get_str_prop(dkdev, "ID_MODEL", &d->usb_dev.product_name);
+    return 0;
+}
+
+
+static int gather_net_cap(DevkitDevice *dkdev,
+                          union _virNodeDevCapData *d)
+{
+    const char *sysfs_path = devkit_device_get_native_path(dkdev);
+    const char *interface;
+
+    if (sysfs_path == NULL)
+        return -1;
+    interface = strrchr(sysfs_path, '/');
+    if (!interface && *interface && *(++interface))
+        return -1;
+    if ((d->net.interface = strdup(interface)) == NULL)
+        return -1;
+
+    d->net.subtype = VIR_NODE_DEV_CAP_NET_LAST;
+
+    return 0;
+}
+
+
+static int gather_block_cap(DevkitDevice *dkdev,
+                          union _virNodeDevCapData *d)
+{
+    const char *device = devkit_device_get_device_file(dkdev);
+
+    if (device && ((d->block.device = strdup(device)) == NULL))
+        return -1;
+
+    return 0;
+}
+
+
+struct _caps_tbl_entry {
+    const char *cap_name;
+    enum virNodeDevCapType type;
+    int (*gather_fn)(DevkitDevice *dkdev,
+                     union _virNodeDevCapData *data);
+};
+
+typedef struct _caps_tbl_entry caps_tbl_entry;
+
+static caps_tbl_entry caps_tbl[] = {
+    { "pci",        VIR_NODE_DEV_CAP_PCI_DEV,   gather_pci_cap },
+    { "usb",        VIR_NODE_DEV_CAP_USB_DEV,   gather_usb_cap },
+    { "net",        VIR_NODE_DEV_CAP_NET,       gather_net_cap },
+    { "block",      VIR_NODE_DEV_CAP_BLOCK,     gather_block_cap },
+    // TODO: more caps!
+};
+
+
+/* qsort/bsearch string comparator */
+static int cmpstringp(const void *p1, const void *p2)
+{
+    /* from man 3 qsort */
+    return strcmp(* (char * const *) p1, * (char * const *) p2);
+}
+
+
+static int gather_capability(DevkitDevice *dkdev,
+                             const char *cap_name,
+                             virNodeDevCapsDefPtr *caps_p)
+{
+    size_t caps_tbl_len = sizeof(caps_tbl) / sizeof(caps_tbl[0]);
+    caps_tbl_entry *entry;
+
+    entry = bsearch(&cap_name, caps_tbl, caps_tbl_len,
+                    sizeof(caps_tbl[0]), cmpstringp);
+
+    if (entry) {
+        virNodeDevCapsDefPtr caps;
+        if (VIR_ALLOC(caps) < 0)
+            return ENOMEM;
+        caps->type = entry->type;
+        if (entry->gather_fn) {
+            int rv = (*entry->gather_fn)(dkdev, &caps->data);
+            if (rv != 0) {
+                virNodeDevCapsDefFree(caps);
+                return rv;
+            }
+        }
+        caps->next = *caps_p;
+        *caps_p = caps;
+    }
+
+    return 0;
+}
+
+
+static int gather_capabilities(DevkitDevice *dkdev,
+                               virNodeDevCapsDefPtr *caps_p)
+{
+    const char *subsys = devkit_device_get_subsystem(dkdev);
+    const char *bus_name = devkit_device_get_property(dkdev, "ID_BUS");
+    virNodeDevCapsDefPtr caps = NULL;
+    int rv;
+
+    if (subsys) {
+        rv = gather_capability(dkdev, subsys, &caps);
+        if (rv != 0) goto failure;
+    }
+
+    if (bus_name && (subsys == NULL || !STREQ(bus_name, subsys))) {
+        rv = gather_capability(dkdev, bus_name, &caps);
+        if (rv != 0) goto failure;
+    }
+
+    *caps_p = caps;
+    return 0;
+
+ failure:
+    while (caps) {
+        virNodeDevCapsDefPtr next = caps->next;
+        virNodeDevCapsDefFree(caps);
+        caps = next;
+    }
+    return rv;
+}
+
+static void dev_create(void *_dkdev, void *_dkclient ATTRIBUTE_UNUSED)
+{
+    DevkitDevice *dkdev = _dkdev;
+    const char *sysfs_path = devkit_device_get_native_path(dkdev);
+    virNodeDeviceObjPtr dev = NULL;
+    const char *name;
+    int rv;
+
+    if (sysfs_path == NULL)
+        /* Currently using basename(sysfs_path) as device name (key) */
+        return;
+
+    name = strrchr(sysfs_path, '/');
+    if (name == NULL)
+        name = sysfs_path;
+    else
+        ++name;
+
+    if (VIR_ALLOC(dev) < 0 || VIR_ALLOC(dev->def) < 0)
+        goto failure;
+
+    dev->privateData = dkdev;
+
+    if ((dev->def->name = strdup(name)) == NULL)
+        goto failure;
+
+    // TODO: Find device parent, if any
+
+    rv = gather_capabilities(dkdev, &dev->def->caps);
+    if (rv != 0) goto failure;
+
+    if (VIR_REALLOC_N(driverState->devs.objs, driverState->devs.count + 1) < 0)
+        goto failure;
+
+    driverState->devs.objs[driverState->devs.count++] = dev;
+
+    return;
+
+ failure:
+    DEBUG("FAILED TO ADD dev %s", name);
+    if (dev)
+        virNodeDeviceDefFree(dev->def);
+    VIR_FREE(dev);
+}
+
+
+static int devkitNodeDriverStartup(void)
+{
+    size_t caps_tbl_len = sizeof(caps_tbl) / sizeof(caps_tbl[0]);
+    DevkitClient *devkit_client = NULL;
+    GError *err = NULL;
+    GList *devs;
+
+    /* Ensure caps_tbl is sorted by capability name */
+    qsort(caps_tbl, caps_tbl_len, sizeof(caps_tbl[0]), cmpstringp);
+
+    if (VIR_ALLOC(driverState) < 0)
+        return -1;
+
+    // TODO: Is it really ok to call this multiple times??
+    //       Is there something analogous to call on close?
+    g_type_init();
+
+    /* Get new devkit_client and connect to daemon */
+    devkit_client = devkit_client_new(NULL);
+    if (devkit_client == NULL) {
+        DEBUG0("devkit_client_new returned NULL");
+        goto failure;
+    }
+    if (!devkit_client_connect(devkit_client, &err)) {
+        DEBUG0("devkit_client_connect failed");
+        goto failure;
+    }
+
+    /* Populate with known devices */
+    devs = devkit_client_enumerate_by_subsystem(devkit_client, NULL, &err);
+    if (err) {
+        DEBUG0("devkit_client_enumerate_by_subsystem failed");
+        devs = NULL;
+        goto failure;
+    }
+    g_list_foreach(devs, dev_create, devkit_client);
+
+    driverState->privateData = devkit_client;
+
+    // TODO: Register to get DeviceKit events on device changes and
+    //       coordinate updates with queries and other operations.
+
+    return 0;
+
+ failure:
+    if (err) {
+        DEBUG("\terror[%d]: %s", err->code, err->message);
+        g_error_free(err);
+    }
+    if (devs) {
+        g_list_foreach(devs, (GFunc)g_object_unref, NULL);
+        g_list_free(devs);
+    }
+    if (devkit_client)
+        g_object_unref(devkit_client);
+    VIR_FREE(driverState);
+
+    return -1;
+}
+
+
+static int devkitNodeDriverShutdown(void)
+{
+    if (driverState) {
+        DevkitClient *devkit_client = DRV_STATE_DKCLIENT(driverState);
+        virNodeDeviceObjListFree(&driverState->devs);
+        if (devkit_client)
+            g_object_unref(devkit_client);
+        VIR_FREE(driverState);
+        return 0;
+    }
+    return -1;
+}
+
+
+static int devkitNodeDriverReload(void)
+{
+    (void)devkitNodeDriverShutdown();
+    return devkitNodeDriverStartup();
+}
+
+
+static int devkitNodeDriverActive(void)
+{
+    /* Always ready to deal with a shutdown */
+    return 0;
+}
+
+
+static virDrvOpenStatus devkitNodeDrvOpen(virConnectPtr conn,
+                                       xmlURIPtr uri ATTRIBUTE_UNUSED,
+                                       virConnectAuthPtr auth ATTRIBUTE_UNUSED,
+                                       int flags ATTRIBUTE_UNUSED)
+{
+    if (driverState == NULL)
+        return VIR_DRV_OPEN_DECLINED;
+
+    conn->nodePrivateData = driverState;
+
+    return VIR_DRV_OPEN_SUCCESS;
+}
+
+static int devkitNodeDrvClose(virConnectPtr conn ATTRIBUTE_UNUSED)
+{
+    conn->nodePrivateData = NULL;
+    return 0;
+}
+
+
+static virNodeDevicePtr devkitNodeDeviceCreate(virConnectPtr conn ATTRIBUTE_UNUSED,
+                                            const char *xml ATTRIBUTE_UNUSED,
+                                            unsigned int flags ATTRIBUTE_UNUSED)
+{
+    return NULL;
+}
+
+static int devkitNodeDeviceDestroy(virNodeDevicePtr dev ATTRIBUTE_UNUSED,
+                                unsigned int flags ATTRIBUTE_UNUSED)
+{
+    return -1;
+}
+
+static virNodeDriver devkitNodeDriver = {
+    .name = "devkitNodeDriver",
+    .open = devkitNodeDrvOpen,
+    .close = devkitNodeDrvClose,
+    .deviceCreate = devkitNodeDeviceCreate,
+    .deviceDestroy = devkitNodeDeviceDestroy,
+};
+
+
+static virStateDriver devkitStateDriver = {
+    .initialize = devkitNodeDriverStartup,
+    .cleanup = devkitNodeDriverShutdown,
+    .reload = devkitNodeDriverReload,
+    .active = devkitNodeDriverActive,
+};
+
+int devkitNodeRegister(void)
+{
+    registerCommonNodeFuncs(&devkitNodeDriver);
+    if (virRegisterNodeDriver(&devkitNodeDriver) < 0)
+        return -1;
+    return virRegisterStateDriver(&devkitStateDriver);
+}
diff --git a/src/node_device_hal.c b/src/node_device_hal.c
new file mode 100644
index 0000000..b2ca14c
--- /dev/null
+++ b/src/node_device_hal.c
@@ -0,0 +1,600 @@
+/*
+ * node_device_hal.c: node device enumeration - HAL-based implementation
+ *
+ * Copyright (C) 2008 Virtual Iron Software, Inc.
+ * Copyright (C) 2008 David F. Lively
+ *
+ * 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: David F. Lively <dlively@virtualiron.com>
+ */
+
+#include <config.h>
+#include <libhal.h>
+
+#include "node_device_conf.h"
+#include "internal.h"
+#include "memory.h"
+#include "uuid.h"
+
+/*
+ * Host device enumeration (HAL implementation)
+ */
+
+static virNodeDriverStatePtr driverState;
+
+#define DEBUG(fmt,...) VIR_DEBUG(__FILE__, fmt, __VA_ARGS__)
+#define DEBUG0(msg) VIR_DEBUG(__FILE__, "%s", msg)
+
+#define CONN_DRV_STATE(conn) ((virNodeDriverStatePtr)((conn)->nodePrivateData))
+#define DRV_STATE_HAL_CTX(ds) ((LibHalContext *)((ds)->privateData))
+#define CONN_HAL_CTX(conn) DRV_STATE_HAL_CTX(CONN_DRV_STATE(conn))
+
+#define NODE_DEV_UDI(obj) ((const char *)((obj)->privateData)
+
+static int get_str_prop(LibHalContext *ctxt, const char *udi,
+                        const char *prop, char **val_p)
+{
+    char *val = libhal_device_get_property_string(ctxt, udi, prop, NULL);
+
+    if (val) {
+        if (*val) {
+            *val_p = val;
+            return 0;
+        } else {
+            /* Treat empty strings as NULL values */
+            VIR_FREE(val);
+        }
+    }
+
+    return -1;
+}
+
+static int get_int_prop(LibHalContext *ctxt, const char *udi,
+                        const char *prop, int *val_p)
+{
+    DBusError err;
+    int val;
+    int rv;
+
+    dbus_error_init(&err);
+    val = libhal_device_get_property_int(ctxt, udi, prop, &err);
+    rv = dbus_error_is_set(&err);
+    dbus_error_free(&err);
+    if (rv == 0)
+        *val_p = val;
+
+    return rv;
+}
+
+static int get_bool_prop(LibHalContext *ctxt, const char *udi,
+                         const char *prop, int *val_p)
+{
+    DBusError err;
+    int val;
+    int rv;
+
+    dbus_error_init(&err);
+    val = libhal_device_get_property_bool(ctxt, udi, prop, &err);
+    rv = dbus_error_is_set(&err);
+    dbus_error_free(&err);
+    if (rv == 0)
+        *val_p = val;
+
+    return rv;
+}
+
+static int get_uint64_prop(LibHalContext *ctxt, const char *udi,
+                           const char *prop, unsigned long long *val_p)
+{
+    DBusError err;
+    unsigned long long val;
+    int rv;
+
+    dbus_error_init(&err);
+    val = libhal_device_get_property_uint64(ctxt, udi, prop, &err);
+    rv = dbus_error_is_set(&err);
+    dbus_error_free(&err);
+    if (rv == 0)
+        *val_p = val;
+
+    return rv;
+}
+
+static int gather_pci_cap(LibHalContext *ctx, const char *udi,
+                          union _virNodeDevCapData *d)
+{
+    char *sysfs_path;
+
+    if (get_str_prop(ctx, udi, "pci.linux.sysfs_path", &sysfs_path) == 0) {
+        char *p = strrchr(sysfs_path, '/');
+        if (p) {
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.domain);
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.bus);
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.slot);
+            (void)virStrToLong_ui(p+1, &p, 16, &d->pci_dev.function);
+        }
+        VIR_FREE(sysfs_path);
+    }
+    (void)get_int_prop(ctx, udi, "pci.vendor_id", (int *)&d->pci_dev.vendor);
+    if (get_str_prop(ctx, udi, "pci.vendor", &d->pci_dev.vendor_name) != 0)
+        (void)get_str_prop(ctx, udi, "info.vendor", &d->pci_dev.vendor_name);
+    (void)get_int_prop(ctx, udi, "pci.product_id", (int *)&d->pci_dev.product);
+    if (get_str_prop(ctx, udi, "pci.product", &d->pci_dev.product_name) != 0)
+        (void)get_str_prop(ctx, udi, "info.product", &d->pci_dev.product_name);
+    return 0;
+}
+
+
+static int gather_usb_cap(LibHalContext *ctx, const char *udi,
+                          union _virNodeDevCapData *d)
+{
+    (void)get_int_prop(ctx, udi, "usb.bus_number", (int *)&d->usb_dev.bus);
+    (void)get_int_prop(ctx, udi, "usb.linux.device_number",
+                       (int *)&d->usb_dev.device);
+    (void)get_int_prop(ctx, udi, "usb.vendor_id", (int *)&d->usb_dev.vendor);
+    if (get_str_prop(ctx, udi, "usb.vendor", &d->usb_dev.vendor_name) != 0)
+        (void)get_str_prop(ctx, udi, "info.vendor", &d->usb_dev.vendor_name);
+    (void)get_int_prop(ctx, udi, "usb.product_id", (int *)&d->usb_dev.product);
+    if (get_str_prop(ctx, udi, "usb.product", &d->usb_dev.product_name) != 0)
+        (void)get_str_prop(ctx, udi, "info.product", &d->usb_dev.product_name);
+    return 0;
+}
+
+
+static int gather_usb_device_cap(LibHalContext *ctx, const char *udi,
+                          union _virNodeDevCapData *d)
+{
+    (void)get_int_prop(ctx, udi, "usb_device.bus_number", (int *)&d->usb_dev.bus);
+    (void)get_int_prop(ctx, udi, "usb_device.linux.device_number",
+                       (int *)&d->usb_dev.device);
+    (void)get_int_prop(ctx, udi, "usb_device.vendor_id", (int *)&d->usb_dev.vendor);
+    if (get_str_prop(ctx, udi, "usb_device.vendor", &d->usb_dev.vendor_name) != 0)
+        (void)get_str_prop(ctx, udi, "info.vendor", &d->usb_dev.vendor_name);
+    (void)get_int_prop(ctx, udi, "usb_device.product_id", (int *)&d->usb_dev.product);
+    if (get_str_prop(ctx, udi, "usb_device.product", &d->usb_dev.product_name) != 0)
+        (void)get_str_prop(ctx, udi, "info.product", &d->usb_dev.product_name);
+    return 0;
+}
+
+
+static int gather_net_cap(LibHalContext *ctx, const char *udi,
+                          union _virNodeDevCapData *d)
+{
+    (void)get_str_prop(ctx, udi, "net.interface", &d->net.interface);
+    (void)get_str_prop(ctx, udi, "net.address", &d->net.address);
+    if (get_uint64_prop(ctx, udi, "net.80203.mac_address",
+                        &d->net.data.ieee80203.mac_address) == 0)
+        d->net.subtype = VIR_NODE_DEV_CAP_NET_80203;
+    else if (get_uint64_prop(ctx, udi, "net.80211.mac_address",
+                        &d->net.data.ieee80211.mac_address) == 0)
+        d->net.subtype = VIR_NODE_DEV_CAP_NET_80211;
+    else
+        d->net.subtype = VIR_NODE_DEV_CAP_NET_LAST;
+
+    return 0;
+}
+
+
+static int gather_block_cap(LibHalContext *ctx, const char *udi,
+                            union _virNodeDevCapData *d)
+{
+    (void)get_str_prop(ctx, udi, "block.device", &d->block.device);
+    return 0;
+}
+
+
+static int gather_scsi_host_cap(LibHalContext *ctx, const char *udi,
+                                union _virNodeDevCapData *d)
+{
+    (void)get_int_prop(ctx, udi, "scsi_host.host", (int *)&d->scsi_host.host);
+    return 0;
+}
+
+
+static int gather_scsi_cap(LibHalContext *ctx, const char *udi,
+                           union _virNodeDevCapData *d)
+{
+    (void)get_int_prop(ctx, udi, "scsi.host", (int *)&d->scsi.host);
+    (void)get_int_prop(ctx, udi, "scsi.bus", (int *)&d->scsi.bus);
+    (void)get_int_prop(ctx, udi, "scsi.target", (int *)&d->scsi.target);
+    (void)get_int_prop(ctx, udi, "scsi.lun", (int *)&d->scsi.lun);
+    (void)get_str_prop(ctx, udi, "scsi.type", &d->scsi.type);
+    return 0;
+}
+
+
+static int gather_storage_cap(LibHalContext *ctx, const char *udi,
+                              union _virNodeDevCapData *d)
+{
+    int val;
+    (void)get_str_prop(ctx, udi, "storage.bus", &d->storage.bus);
+    (void)get_str_prop(ctx, udi, "storage.drive_type", &d->storage.drive_type);
+    (void)get_str_prop(ctx, udi, "storage.model", &d->storage.model);
+    (void)get_str_prop(ctx, udi, "storage.vendor", &d->storage.vendor);
+    if (get_bool_prop(ctx, udi, "storage.removable", &val) == 0 && val) {
+        d->storage.flags |= VIR_NODE_DEV_CAP_STORAGE_REMOVABLE;
+        if (get_bool_prop(ctx, udi,
+                          "storage.removable.media_available", &val) && val) {
+            d->storage.flags |=
+                VIR_NODE_DEV_CAP_STORAGE_REMOVABLE_MEDIA_AVAILABLE;
+            (void)get_uint64_prop(ctx, udi, "storage.removable.media_size",
+                                  &d->storage.removable_media_size);
+        }
+    } else {
+        (void)get_uint64_prop(ctx, udi, "storage.size", &d->storage.size);
+    }
+    if (get_bool_prop(ctx, udi, "storage.hotpluggable", &val) == 0 && val)
+        d->storage.flags |= VIR_NODE_DEV_CAP_STORAGE_HOTPLUGGABLE;
+    return 0;
+}
+
+
+static int gather_system_cap(LibHalContext *ctx, const char *udi,
+                             union _virNodeDevCapData *d)
+{
+    char *uuidstr;
+
+    (void)get_str_prop(ctx, udi, "system.product", &d->system.product_name);
+    (void)get_str_prop(ctx, udi, "system.hardware.vendor",
+                       &d->system.hardware.vendor_name);
+    (void)get_str_prop(ctx, udi, "system.hardware.version",
+                       &d->system.hardware.version);
+    (void)get_str_prop(ctx, udi, "system.hardware.serial",
+                       &d->system.hardware.serial);
+    if (get_str_prop(ctx, udi, "system.hardware.uuid", &uuidstr) == 0) {
+        (void)virUUIDParse(uuidstr, d->system.hardware.uuid);
+        VIR_FREE(uuidstr);
+    }
+    (void)get_str_prop(ctx, udi, "system.firmware.vendor",
+                       &d->system.firmware.vendor_name);
+    (void)get_str_prop(ctx, udi, "system.firmware.version",
+                       &d->system.firmware.version);
+    (void)get_str_prop(ctx, udi, "system.firmware.release_date",
+                       &d->system.firmware.release_date);
+    return 0;
+}
+
+
+struct _caps_tbl_entry {
+    const char *cap_name;
+    enum virNodeDevCapType type;
+    int (*gather_fn)(LibHalContext *ctx,
+                     const char *udi,
+                     union _virNodeDevCapData *data);
+};
+
+typedef struct _caps_tbl_entry caps_tbl_entry;
+
+static caps_tbl_entry caps_tbl[] = {
+    { "system",     VIR_NODE_DEV_CAP_SYSTEM,    gather_system_cap },
+    { "pci",        VIR_NODE_DEV_CAP_PCI_DEV,   gather_pci_cap },
+    { "usb",        VIR_NODE_DEV_CAP_USB_DEV,   gather_usb_cap },
+    { "usb_device", VIR_NODE_DEV_CAP_USB_DEV,   gather_usb_device_cap },
+    { "net",        VIR_NODE_DEV_CAP_NET,       gather_net_cap },
+    { "block",      VIR_NODE_DEV_CAP_BLOCK,     gather_block_cap },
+    { "scsi_host",  VIR_NODE_DEV_CAP_SCSI_HOST, gather_scsi_host_cap },
+    { "scsi",       VIR_NODE_DEV_CAP_SCSI,      gather_scsi_cap },
+    { "storage",    VIR_NODE_DEV_CAP_STORAGE,   gather_storage_cap },
+};
+
+
+/* qsort/bsearch string comparator */
+static int cmpstringp(const void *p1, const void *p2)
+{
+    /* from man 3 qsort */
+    return strcmp(* (char * const *) p1, * (char * const *) p2);
+}
+
+
+static int gather_capability(LibHalContext *ctx, const char *udi,
+                             const char *cap_name,
+                             virNodeDevCapsDefPtr *caps_p)
+{
+    size_t caps_tbl_len = sizeof(caps_tbl) / sizeof(caps_tbl[0]);
+    caps_tbl_entry *entry;
+
+    entry = bsearch(&cap_name, caps_tbl, caps_tbl_len,
+                    sizeof(caps_tbl[0]), cmpstringp);
+
+    if (entry) {
+        virNodeDevCapsDefPtr caps;
+        if (VIR_ALLOC(caps) < 0)
+            return ENOMEM;
+        caps->type = entry->type;
+        if (entry->gather_fn) {
+            int rv = (*entry->gather_fn)(ctx, udi, &caps->data);
+            if (rv != 0) {
+                virNodeDevCapsDefFree(caps);
+                return rv;
+            }
+        }
+        caps->next = *caps_p;
+        *caps_p = caps;
+    }
+
+    return 0;
+}
+
+
+static int gather_capabilities(LibHalContext *ctx, const char *udi,
+                               virNodeDevCapsDefPtr *caps_p)
+{
+    char *bus_name = NULL;
+    virNodeDevCapsDefPtr caps = NULL;
+    char **hal_cap_names;
+    int rv, i;
+
+    if (STREQ(udi, "/org/freedesktop/Hal/devices/computer")) {
+        rv = gather_capability(ctx, udi, "system", &caps);
+        if (rv != 0)
+            goto failure;
+    }
+
+    if (get_str_prop(ctx, udi, "info.subsystem", &bus_name) == 0) {
+        rv = gather_capability(ctx, udi, bus_name, &caps);
+        if (rv != 0)
+            goto failure;
+    }
+
+    hal_cap_names = libhal_device_get_property_strlist(ctx, udi,
+                                                       "info.capabilities",
+                                                       NULL);
+    if (hal_cap_names) {
+        for (i = 0; hal_cap_names[i]; i++) {
+            if (! (bus_name && STREQ(hal_cap_names[i], bus_name))) {
+                rv = gather_capability(ctx, udi, hal_cap_names[i], &caps);
+                if (rv != 0)
+                    goto failure;
+            }
+        }
+        for (i = 0; hal_cap_names[i]; i++)
+            VIR_FREE(hal_cap_names[i]);
+        VIR_FREE(hal_cap_names);
+    }
+    VIR_FREE(bus_name);
+
+    *caps_p = caps;
+    return 0;
+
+ failure:
+    VIR_FREE(bus_name);
+    if (hal_cap_names) {
+        for (i = 0; hal_cap_names[i]; i++)
+            VIR_FREE(hal_cap_names[i]);
+        VIR_FREE(hal_cap_names);
+    }
+    while (caps) {
+        virNodeDevCapsDefPtr next = caps->next;
+        virNodeDevCapsDefFree(caps);
+        caps = next;
+    }
+    return rv;
+}
+
+static void free_udi(void *udi)
+{
+    VIR_FREE(udi);
+}
+
+static void dev_create(char *udi)
+{
+    LibHalContext *ctx = DRV_STATE_HAL_CTX(driverState);
+    char *parent_key = NULL;
+    virNodeDeviceObjPtr dev;
+    const char *name, *parent;
+    int rv;
+
+    name = strrchr(udi, '/');
+    if (name == NULL)
+        name = udi;
+    else
+        ++name;
+
+    if (VIR_ALLOC(dev) < 0 || VIR_ALLOC(dev->def) < 0)
+        goto failure;
+
+    dev->privateData = udi;
+    dev->privateFree = free_udi;
+
+    if ((dev->def->name = strdup(name)) == NULL)
+        goto failure;
+
+    if (get_str_prop(ctx, udi, "info.parent", &parent_key) == 0) {
+        parent = strrchr(parent_key, '/');
+        if (parent == NULL)
+            parent = parent_key;
+        else
+            ++parent;
+        dev->def->parent = strdup(parent);
+        VIR_FREE(parent_key);
+        if (dev->def->parent == NULL)
+            goto failure;
+    }
+
+    rv = gather_capabilities(ctx, udi, &dev->def->caps);
+    if (rv != 0) goto failure;
+
+    if (VIR_REALLOC_N(driverState->devs.objs, driverState->devs.count + 1) < 0)
+        goto failure;
+
+    driverState->devs.objs[driverState->devs.count++] = dev;
+
+    return;
+
+ failure:
+    DEBUG("FAILED TO ADD dev %s", name);
+    if (dev)
+        virNodeDeviceDefFree(dev->def);
+    VIR_FREE(dev);
+    VIR_FREE(udi);
+}
+
+
+static int halNodeDriverStartup(void)
+{
+    size_t caps_tbl_len = sizeof(caps_tbl) / sizeof(caps_tbl[0]);
+    LibHalContext *hal_ctx = NULL;
+    DBusConnection *dbus_conn = NULL;
+    DBusError err;
+    char **udi = NULL;
+    int num_devs, i;
+
+    /* Ensure caps_tbl is sorted by capability name */
+    qsort(caps_tbl, caps_tbl_len, sizeof(caps_tbl[0]), cmpstringp);
+
+    if (VIR_ALLOC(driverState) < 0)
+        return -1;
+
+    /* Allocate and initialize a new HAL context */
+    dbus_error_init(&err);
+    hal_ctx = libhal_ctx_new();
+    if (hal_ctx == NULL) {
+        fprintf(stderr, "%s: libhal_ctx_new returned NULL\n", __FUNCTION__);
+        goto failure;
+    }
+    dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+    if (dbus_conn == NULL) {
+        fprintf(stderr, "%s: dbus_bus_get failed\n", __FUNCTION__);
+        goto failure;
+    }
+    if (!libhal_ctx_set_dbus_connection(hal_ctx, dbus_conn)) {
+        fprintf(stderr, "%s: libhal_ctx_set_dbus_connection failed\n",
+                __FUNCTION__);
+        goto failure;
+    }
+    if (!libhal_ctx_init(hal_ctx, &err)) {
+        fprintf(stderr, "%s: libhal_ctx_init failed\n", __FUNCTION__);
+        goto failure;
+    }
+    driverState->privateData = hal_ctx;
+
+    /* Populate with known devices */
+    udi = libhal_get_all_devices(hal_ctx, &num_devs, &err);
+    if (udi == NULL) {
+        fprintf(stderr, "%s: libhal_get_all_devices failed\n", __FUNCTION__);
+        goto failure;
+    }
+    for (i = 0; i < num_devs; i++)
+        dev_create(udi[i]);
+    free(udi);
+
+    // TODO: register to get HAL notifications on device changes
+    //       and coordinate with this initial scan and subsequent
+    //       queries and other operations
+
+    return 0;
+
+ failure:
+    if (dbus_error_is_set(&err)) {
+        fprintf(stderr, "\t%s: %s\n", err.name, err.message);
+        dbus_error_free(&err);
+    }
+    virNodeDeviceObjListFree(&driverState->devs);
+    if (hal_ctx)
+        (void)libhal_ctx_free(hal_ctx);
+    if (udi) {
+        for (i = 0; i < num_devs; i++)
+            free(udi[i]);
+        free(udi);
+    }
+    VIR_FREE(driverState);
+
+    return -1;
+}
+
+
+static int halNodeDriverShutdown(void)
+{
+    if (driverState) {
+        virNodeDeviceObjListFree(&driverState->devs);
+        (void)libhal_ctx_free(DRV_STATE_HAL_CTX(driverState));
+        VIR_FREE(driverState);
+        return 0;
+    }
+    return -1;
+}
+
+
+static int halNodeDriverReload(void)
+{
+    (void)halNodeDriverShutdown();
+    return halNodeDriverStartup();
+}
+
+
+static int halNodeDriverActive(void)
+{
+    /* Always ready to deal with a shutdown */
+    return 0;
+}
+
+
+static virDrvOpenStatus halNodeDrvOpen(virConnectPtr conn,
+                                       xmlURIPtr uri ATTRIBUTE_UNUSED,
+                                       virConnectAuthPtr auth ATTRIBUTE_UNUSED,
+                                       int flags ATTRIBUTE_UNUSED)
+{
+    if (driverState == NULL)
+        return VIR_DRV_OPEN_DECLINED;
+
+    conn->nodePrivateData = driverState;
+
+    return VIR_DRV_OPEN_SUCCESS;
+}
+
+static int halNodeDrvClose(virConnectPtr conn ATTRIBUTE_UNUSED)
+{
+    conn->nodePrivateData = NULL;
+    return 0;
+}
+
+
+static virNodeDevicePtr halNodeDeviceCreate(virConnectPtr conn ATTRIBUTE_UNUSED,
+                                            const char *xml ATTRIBUTE_UNUSED,
+                                            unsigned int flags ATTRIBUTE_UNUSED)
+{
+    return NULL;
+}
+
+static int halNodeDeviceDestroy(virNodeDevicePtr dev ATTRIBUTE_UNUSED,
+                                unsigned int flags ATTRIBUTE_UNUSED)
+{
+    return -1;
+}
+
+static virNodeDriver halNodeDriver = {
+    .name = "halNodeDriver",
+    .open = halNodeDrvOpen,
+    .close = halNodeDrvClose,
+    .deviceCreate = halNodeDeviceCreate,
+    .deviceDestroy = halNodeDeviceDestroy,
+};
+
+
+static virStateDriver halStateDriver = {
+    .initialize = halNodeDriverStartup,
+    .cleanup = halNodeDriverShutdown,
+    .reload = halNodeDriverReload,
+    .active = halNodeDriverActive,
+};
+
+int halNodeRegister(void)
+{
+    registerCommonNodeFuncs(&halNodeDriver);
+    if (virRegisterNodeDriver(&halNodeDriver) < 0)
+        return -1;
+    return virRegisterStateDriver(&halStateDriver);
+}
