/* * node_device_hal.c: node device enumeration - HAL-based implementation * * Copyright (C) 2011-2015 Red Hat, Inc. * 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, see * . */ #include #include #include "node_device_conf.h" #include "node_device_driver.h" #include "node_device_hal.h" #include "virerror.h" #include "driver.h" #include "datatypes.h" #include "viralloc.h" #include "viruuid.h" #include "virfile.h" #include "virpci.h" #include "virpidfile.h" #include "virlog.h" #include "virdbus.h" #include "virstring.h" #include "configmake.h" #define VIR_FROM_THIS VIR_FROM_NODEDEV VIR_LOG_INIT("node_device.node_device_hal"); /* * Host device enumeration (HAL implementation) */ #define DRV_STATE_HAL_CTX(ds) ((LibHalContext *)((ds)->privateData)) static const char * hal_name(const char *udi) { const char *name = strrchr(udi, '/'); if (name) return name + 1; return udi; } 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, virNodeDevCapDataPtr d) { char *sysfs_path; if (get_str_prop(ctx, udi, "pci.linux.sysfs_path", &sysfs_path) == 0) { char *p = strrchr(sysfs_path, '/'); if (p) { ignore_value(virStrToLong_ui(p+1, &p, 16, &d->pci_dev.domain)); ignore_value(virStrToLong_ui(p+1, &p, 16, &d->pci_dev.bus)); ignore_value(virStrToLong_ui(p+1, &p, 16, &d->pci_dev.slot)); ignore_value(virStrToLong_ui(p+1, &p, 16, &d->pci_dev.function)); } if (virNodeDeviceGetPCIDynamicCaps(sysfs_path, &d->pci_dev) < 0) { VIR_FREE(sysfs_path); return -1; } 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, virNodeDevCapDataPtr d) { (void)get_int_prop(ctx, udi, "usb.interface.number", (int *)&d->usb_if.number); (void)get_int_prop(ctx, udi, "usb.interface.class", (int *)&d->usb_if.klass); (void)get_int_prop(ctx, udi, "usb.interface.subclass", (int *)&d->usb_if.subclass); (void)get_int_prop(ctx, udi, "usb.interface.protocol", (int *)&d->usb_if.protocol); (void)get_str_prop(ctx, udi, "usb.interface.description", &d->usb_if.description); return 0; } static int gather_usb_device_cap(LibHalContext *ctx, const char *udi, virNodeDevCapDataPtr 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, virNodeDevCapDataPtr d) { unsigned long long dummy; (void)get_str_prop(ctx, udi, "net.interface", &d->net.ifname); (void)get_str_prop(ctx, udi, "net.address", &d->net.address); if (get_uint64_prop(ctx, udi, "net.80203.mac_address", &dummy) == 0) d->net.subtype = VIR_NODE_DEV_CAP_NET_80203; else if (get_uint64_prop(ctx, udi, "net.80211.mac_address", &dummy) == 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_scsi_host_cap(LibHalContext *ctx, const char *udi, virNodeDevCapDataPtr d) { int retval = 0; (void)get_int_prop(ctx, udi, "scsi_host.host", (int *)&d->scsi_host.host); retval = virNodeDeviceGetSCSIHostCaps(&d->scsi_host); if (retval == -1) goto out; out: return retval; } static int gather_scsi_cap(LibHalContext *ctx, const char *udi, virNodeDevCapDataPtr 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, virNodeDevCapDataPtr d) { int val; (void)get_str_prop(ctx, udi, "block.device", &d->storage.block); (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); (void)get_str_prop(ctx, udi, "storage.serial", &d->storage.serial); 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) == 0 && 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_scsi_generic_cap(LibHalContext *ctx, const char *udi, virNodeDevCapDataPtr d) { (void)get_str_prop(ctx, udi, "scsi_generic.device", &d->sg.path); return 0; } static int gather_system_cap(LibHalContext *ctx, const char *udi, virNodeDevCapDataPtr 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) { ignore_value(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; virNodeDevCapType type; int (*gather_fn)(LibHalContext *ctx, const char *udi, virNodeDevCapDataPtr 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_INTERFACE, gather_usb_cap }, { "usb_device", VIR_NODE_DEV_CAP_USB_DEV, gather_usb_device_cap }, { "net", VIR_NODE_DEV_CAP_NET, gather_net_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 }, { "scsi_generic", VIR_NODE_DEV_CAP_SCSI_GENERIC, gather_scsi_generic_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) { caps_tbl_entry *entry; entry = bsearch(&cap_name, caps_tbl, G_N_ELEMENTS(caps_tbl), sizeof(caps_tbl[0]), cmpstringp); if (entry) { virNodeDevCapsDefPtr caps; if (VIR_ALLOC(caps) < 0) return ENOMEM; caps->data.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 = NULL; int rv; size_t 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 || get_str_prop(ctx, udi, "linux.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 dev_create(const char *udi) { LibHalContext *ctx; char *parent_key = NULL; virNodeDeviceObjPtr obj = NULL; virNodeDeviceDefPtr def = NULL; virNodeDeviceDefPtr objdef; const char *name = hal_name(udi); int rv; char *devicePath = NULL; nodeDeviceLock(); ctx = DRV_STATE_HAL_CTX(driver); if (VIR_ALLOC(def) < 0) goto failure; def->name = g_strdup(name); if (get_str_prop(ctx, udi, "info.parent", &parent_key) == 0) { def->parent = g_strdup(hal_name(parent_key)); VIR_FREE(parent_key); } rv = gather_capabilities(ctx, udi, &def->caps); if (rv != 0) goto failure; if (def->caps == NULL) goto cleanup; /* Some devices don't have a path in sysfs, so ignore failure */ (void)get_str_prop(ctx, udi, "linux.sysfs_path", &devicePath); if (!(obj = virNodeDeviceObjListAssignDef(driver->devs, def))) { VIR_FREE(devicePath); goto failure; } objdef = virNodeDeviceObjGetDef(obj); objdef->sysfs_path = devicePath; virNodeDeviceObjEndAPI(&obj); nodeDeviceUnlock(); return; failure: VIR_DEBUG("FAILED TO ADD dev %s", name); cleanup: virNodeDeviceDefFree(def); nodeDeviceUnlock(); } static void dev_refresh(const char *udi) { const char *name = hal_name(udi); virNodeDeviceObjPtr obj; if ((obj = virNodeDeviceObjListFindByName(driver->devs, name))) { /* Simply "rediscover" device -- incrementally handling changes * to sub-capabilities (like net.80203) is nasty ... so avoid it. */ virNodeDeviceObjListRemove(driver->devs, obj); virObjectUnref(obj); dev_create(udi); } else { VIR_DEBUG("no device named %s", name); } } static void device_added(LibHalContext *ctx G_GNUC_UNUSED, const char *udi) { VIR_DEBUG("%s", hal_name(udi)); dev_create(udi); } static void device_removed(LibHalContext *ctx G_GNUC_UNUSED, const char *udi) { const char *name = hal_name(udi); virNodeDeviceObjPtr obj; obj = virNodeDeviceObjListFindByName(driver->devs, name); VIR_DEBUG("%s", name); if (obj) virNodeDeviceObjListRemove(driver->devs, obj); else VIR_DEBUG("no device named %s", name); virObjectUnref(obj); } static void device_cap_added(LibHalContext *ctx, const char *udi, const char *cap) { const char *name = hal_name(udi); virNodeDeviceObjPtr obj; virNodeDeviceDefPtr def; VIR_DEBUG("%s %s", cap, name); if ((obj = virNodeDeviceObjListFindByName(driver->devs, name))) { def = virNodeDeviceObjGetDef(obj); (void)gather_capability(ctx, udi, cap, &def->caps); virNodeDeviceObjEndAPI(&obj); } else { VIR_DEBUG("no device named %s", name); } } static void device_cap_lost(LibHalContext *ctx G_GNUC_UNUSED, const char *udi, const char *cap) { const char *name = hal_name(udi); VIR_DEBUG("%s %s", cap, name); dev_refresh(udi); } static void device_prop_modified(LibHalContext *ctx G_GNUC_UNUSED, const char *udi, const char *key, dbus_bool_t is_removed G_GNUC_UNUSED, dbus_bool_t is_added G_GNUC_UNUSED) { const char *name = hal_name(udi); VIR_DEBUG("%s %s", name, key); dev_refresh(udi); } static int nodeStateInitialize(bool privileged G_GNUC_UNUSED, virStateInhibitCallback callback G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED) { LibHalContext *hal_ctx = NULL; char **udi = NULL; int num_devs; size_t i; int ret = VIR_DRV_STATE_INIT_ERROR; DBusConnection *sysbus; DBusError err; /* Ensure caps_tbl is sorted by capability name */ qsort(caps_tbl, G_N_ELEMENTS(caps_tbl), sizeof(caps_tbl[0]), cmpstringp); if (VIR_ALLOC(driver) < 0) return VIR_DRV_STATE_INIT_ERROR; driver->lockFD = -1; if (virMutexInit(&driver->lock) < 0) { VIR_FREE(driver); return VIR_DRV_STATE_INIT_ERROR; } nodeDeviceLock(); if (privileged) { driver->stateDir = g_strdup_printf("%s/libvirt/nodedev", RUNSTATEDIR); } else { g_autofree char *rundir = NULL; if (!(rundir = virGetUserRuntimeDirectory())) goto failure; driver->stateDir = g_strdup_printf("%s/nodedev/run", rundir); } if (virFileMakePathWithMode(driver->stateDir, S_IRWXU) < 0) { virReportSystemError(errno, _("cannot create state directory '%s'"), driver->stateDir); goto failure; } if ((driver->lockFD = virPidFileAcquire(driver->stateDir, "driver", false, getpid())) < 0) goto failure; if (!(driver->devs = virNodeDeviceObjListNew())) goto failure; dbus_error_init(&err); if (!(sysbus = virDBusGetSystemBus())) { virReportError(VIR_ERR_INTERNAL_ERROR, _("DBus not available, disabling HAL driver: %s"), virGetLastErrorMessage()); ret = VIR_DRV_STATE_INIT_SKIPPED; goto failure; } hal_ctx = libhal_ctx_new(); if (hal_ctx == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("libhal_ctx_new returned NULL")); goto failure; } if (!libhal_ctx_set_dbus_connection(hal_ctx, sysbus)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("libhal_ctx_set_dbus_connection failed")); goto failure; } if (!libhal_ctx_init(hal_ctx, &err)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("libhal_ctx_init failed, haldaemon is probably " "not running")); /* We don't want to show a fatal error here, otherwise entire libvirtd shuts down when hald isn't running */ ret = VIR_DRV_STATE_INIT_SKIPPED; goto failure; } /* Populate with known devices */ driver->privateData = hal_ctx; /* We need to unlock state now, since setting these callbacks cause * a dbus RPC call, and while this call is waiting for the reply, * a signal may already arrive, triggering the callback and thus * requiring the lock ! */ nodeDeviceUnlock(); /* Register HAL event callbacks */ if (!libhal_ctx_set_device_added(hal_ctx, device_added) || !libhal_ctx_set_device_removed(hal_ctx, device_removed) || !libhal_ctx_set_device_new_capability(hal_ctx, device_cap_added) || !libhal_ctx_set_device_lost_capability(hal_ctx, device_cap_lost) || !libhal_ctx_set_device_property_modified(hal_ctx, device_prop_modified) || !libhal_device_property_watch_all(hal_ctx, &err)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("setting up HAL callbacks failed")); goto failure; } udi = libhal_get_all_devices(hal_ctx, &num_devs, &err); if (udi == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("libhal_get_all_devices failed")); goto failure; } for (i = 0; i < num_devs; i++) { dev_create(udi[i]); VIR_FREE(udi[i]); } VIR_FREE(udi); return VIR_DRV_STATE_INIT_COMPLETE; failure: if (dbus_error_is_set(&err)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: %s"), err.name, err.message); dbus_error_free(&err); } virNodeDeviceObjListFree(driver->devs); if (hal_ctx) (void)libhal_ctx_free(hal_ctx); nodeDeviceUnlock(); VIR_FREE(driver); return ret; } static int nodeStateCleanup(void) { if (driver) { nodeDeviceLock(); LibHalContext *hal_ctx = DRV_STATE_HAL_CTX(driver); virNodeDeviceObjListFree(driver->devs); (void)libhal_ctx_shutdown(hal_ctx, NULL); (void)libhal_ctx_free(hal_ctx); if (driver->lockFD != -1) virPidFileRelease(driver->stateDir, "driver", driver->lockFD); VIR_FREE(driver->stateDir); nodeDeviceUnlock(); virMutexDestroy(&driver->lock); VIR_FREE(driver); return 0; } return -1; } static int nodeStateReload(void) { DBusError err; char **udi = NULL; int num_devs; size_t i; LibHalContext *hal_ctx; VIR_INFO("Reloading HAL device state"); nodeDeviceLock(); VIR_INFO("Removing existing objects"); virNodeDeviceObjListFree(driver->devs); nodeDeviceUnlock(); hal_ctx = DRV_STATE_HAL_CTX(driver); VIR_INFO("Creating new objects"); dbus_error_init(&err); udi = libhal_get_all_devices(hal_ctx, &num_devs, &err); if (udi == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("libhal_get_all_devices failed")); return -1; } for (i = 0; i < num_devs; i++) { dev_create(udi[i]); VIR_FREE(udi[i]); } VIR_FREE(udi); VIR_INFO("HAL device reload complete"); return 0; } static virNodeDeviceDriver halNodeDeviceDriver = { .name = "HAL", .nodeNumOfDevices = nodeNumOfDevices, /* 0.5.0 */ .nodeListDevices = nodeListDevices, /* 0.5.0 */ .connectListAllNodeDevices = nodeConnectListAllNodeDevices, /* 0.10.2 */ .nodeDeviceLookupByName = nodeDeviceLookupByName, /* 0.5.0 */ .nodeDeviceLookupSCSIHostByWWN = nodeDeviceLookupSCSIHostByWWN, /* 1.0.2 */ .nodeDeviceGetXMLDesc = nodeDeviceGetXMLDesc, /* 0.5.0 */ .nodeDeviceGetParent = nodeDeviceGetParent, /* 0.5.0 */ .nodeDeviceNumOfCaps = nodeDeviceNumOfCaps, /* 0.5.0 */ .nodeDeviceListCaps = nodeDeviceListCaps, /* 0.5.0 */ .nodeDeviceCreateXML = nodeDeviceCreateXML, /* 0.6.5 */ .nodeDeviceDestroy = nodeDeviceDestroy, /* 0.6.5 */ }; static virHypervisorDriver halHypervisorDriver = { .name = "nodedev", .connectOpen = nodeConnectOpen, /* 4.1.0 */ .connectClose = nodeConnectClose, /* 4.1.0 */ .connectIsEncrypted = nodeConnectIsEncrypted, /* 4.1.0 */ .connectIsSecure = nodeConnectIsSecure, /* 4.1.0 */ .connectIsAlive = nodeConnectIsAlive, /* 4.1.0 */ }; static virConnectDriver halConnectDriver = { .localOnly = true, .uriSchemes = (const char *[]){ "nodedev", NULL }, .hypervisorDriver = &halHypervisorDriver, .nodeDeviceDriver = &halNodeDeviceDriver, }; static virStateDriver halStateDriver = { .name = "HAL", .stateInitialize = nodeStateInitialize, /* 0.5.0 */ .stateCleanup = nodeStateCleanup, /* 0.5.0 */ .stateReload = nodeStateReload, /* 0.5.0 */ }; int halNodeRegister(void) { if (virRegisterConnectDriver(&halConnectDriver, false) < 0) return -1; if (virSetSharedNodeDeviceDriver(&halNodeDeviceDriver) < 0) return -1; return virRegisterStateDriver(&halStateDriver); }