/*
 * node_device_udev.c: node device enumeration - libudev implementation
 *
 * Copyright (C) 2009-2015 Red Hat, Inc.
 *
 * 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
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <gio/gio.h>
#include <libudev.h>
#include <pciaccess.h>
#ifdef __linux__
# include <scsi/scsi.h>
#endif

#include "node_device_conf.h"
#include "node_device_event.h"
#include "node_device_driver.h"
#include "node_device_udev.h"
#include "virerror.h"
#include "driver.h"
#include "virlog.h"
#include "viralloc.h"
#include "viruuid.h"
#include "virfile.h"
#include "virccw.h"
#include "virpci.h"
#include "virpidfile.h"
#include "virstring.h"
#include "virnetdev.h"
#include "virmdev.h"
#include "virutil.h"
#include "virthreadpool.h"

#include "configmake.h"

#define VIR_FROM_THIS VIR_FROM_NODEDEV

VIR_LOG_INIT("node_device.node_device_udev");

#ifndef TYPE_RAID
# define TYPE_RAID 12
#endif

#define DMI_DEVPATH "/sys/devices/virtual/dmi/id"

typedef struct _udevEventData udevEventData;
struct _udevEventData {
    virObjectLockable parent;

    struct udev_monitor *udev_monitor;
    int watch;

    /* Udev thread data */
    virThread *udevThread;
    virCond udevThreadCond;
    bool udevThreadQuit;
    bool udevDataReady;

    /* Protects @mdevctlMonitors */
    virMutex mdevctlLock;
    GList *mdevctlMonitors;
    int mdevctlTimeout;

    /* Immutable pointer, self-locking APIs */
    virThreadPool *workerPool;
};
G_DEFINE_AUTOPTR_CLEANUP_FUNC(udevEventData, virObjectUnref);

static virClass *udevEventDataClass;

static void
udevEventDataDispose(void *obj)
{
    struct udev *udev = NULL;
    udevEventData *priv = obj;

    VIR_WITH_MUTEX_LOCK_GUARD(&priv->mdevctlLock) {
        g_list_free_full(g_steal_pointer(&priv->mdevctlMonitors), g_object_unref);
    }

    g_clear_pointer(&priv->udevThread, g_free);

    if (priv->udev_monitor) {
        udev = udev_monitor_get_udev(priv->udev_monitor);
        udev_monitor_unref(priv->udev_monitor);
        udev_unref(udev);
    }

    g_clear_pointer(&priv->workerPool, virThreadPoolFree);

    virMutexDestroy(&priv->mdevctlLock);

    virCondDestroy(&priv->udevThreadCond);
}


static int
udevEventDataOnceInit(void)
{
    if (!VIR_CLASS_NEW(udevEventData, virClassForObjectLockable()))
        return -1;

    return 0;
}

VIR_ONCE_GLOBAL_INIT(udevEventData);

static udevEventData *
udevEventDataNew(void)
{
    g_autoptr(udevEventData) ret = NULL;

    if (udevEventDataInitialize() < 0)
        return NULL;

    if (!(ret = virObjectLockableNew(udevEventDataClass)))
        return NULL;

    if (virCondInit(&ret->udevThreadCond) < 0)
        return NULL;

    if (virMutexInit(&ret->mdevctlLock) < 0)
        return NULL;

    ret->mdevctlTimeout = -1;
    ret->watch = -1;
    return g_steal_pointer(&ret);
}

typedef enum {
  NODE_DEVICE_EVENT_INIT = 0,
  NODE_DEVICE_EVENT_UDEV_ADD,
  NODE_DEVICE_EVENT_UDEV_REMOVE,
  NODE_DEVICE_EVENT_UDEV_CHANGE,
  NODE_DEVICE_EVENT_UDEV_MOVE,
  NODE_DEVICE_EVENT_MDEVCTL_CONFIG_CHANGED,

  NODE_DEVICE_EVENT_LAST
} nodeDeviceEventType;

struct _nodeDeviceEvent {
    nodeDeviceEventType eventType;
    void *data;
    virFreeCallback dataFreeFunc;
};
typedef struct _nodeDeviceEvent nodeDeviceEvent;

static void
nodeDeviceEventFree(nodeDeviceEvent *event)
{
    if (!event)
        return;

    if (event->dataFreeFunc)
        event->dataFreeFunc(event->data);
    g_free(event);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(nodeDeviceEvent, nodeDeviceEventFree);

 /**
  * nodeDeviceEventSubmit:
  * @eventType: the event to be processed
  * @data: additional data for the event processor (the pointer is stolen and it
  *        will be properly freed using @dataFreeFunc)
  * @dataFreeFunc: callback to free @data
  *
  * Submits @eventType to be processed by the asynchronous event handling
  * thread.
  */
static int nodeDeviceEventSubmit(nodeDeviceEventType eventType, void *data, virFreeCallback dataFreeFunc)
{
    nodeDeviceEvent *event = NULL;
    udevEventData *priv = NULL;

    if (!driver)
        return -1;

    priv = driver->privateData;

    event = g_new0(nodeDeviceEvent, 1);
    event->eventType = eventType;
    event->data = data;
    event->dataFreeFunc = dataFreeFunc;
    if (virThreadPoolSendJob(priv->workerPool, 0, event) < 0) {
        nodeDeviceEventFree(event);
        return -1;
    }
    return 0;
}


static bool
udevHasDeviceProperty(struct udev_device *dev,
                      const char *key)
{
    if (udev_device_get_property_value(dev, key))
        return true;

    return false;
}


static const char *
udevGetDeviceProperty(struct udev_device *udev_device,
                      const char *property_key)
{
    const char *ret = NULL;

    ret = udev_device_get_property_value(udev_device, property_key);

    VIR_DEBUG("Found property key '%s' value '%s' for device with sysname '%s' errno='%s'",
              property_key, NULLSTR(ret), udev_device_get_sysname(udev_device),
              ret ? "" : g_strerror(errno));

    return ret;
}


static void
udevGetStringProperty(struct udev_device *udev_device,
                      const char *property_key,
                      char **value)
{
    *value = g_strdup(udevGetDeviceProperty(udev_device, property_key));
}


static int
udevGetIntProperty(struct udev_device *udev_device,
                   const char *property_key,
                   int *value,
                   int base)
{
    const char *str = NULL;

    str = udevGetDeviceProperty(udev_device, property_key);
    if (!str) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Missing udev property '%1$s' on '%2$s'"),
                       property_key, udev_device_get_sysname(udev_device));
        return -1;
    }

    if (virStrToLong_i(str, NULL, base, value) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to parse int '%1$s' from udev property '%2$s' on '%3$s'"),
                       str, property_key, udev_device_get_sysname(udev_device));
        return -1;
    }
    return 0;
}


static int
udevGetUintProperty(struct udev_device *udev_device,
                    const char *property_key,
                    unsigned int *value,
                    int base)
{
    const char *str = NULL;

    str = udevGetDeviceProperty(udev_device, property_key);
    if (!str) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Missing udev property '%1$s' on '%2$s'"),
                       property_key, udev_device_get_sysname(udev_device));
        return -1;
    }

    if (virStrToLong_ui(str, NULL, base, value) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to parse uint '%1$s' from udev property '%2$s' on '%3$s'"),
                       str, property_key, udev_device_get_sysname(udev_device));
        return -1;
    }
    return 0;
}


static const char *
udevGetDeviceSysfsAttr(struct udev_device *udev_device,
                       const char *attr_name)
{
    const char *ret = NULL;

    ret = udev_device_get_sysattr_value(udev_device, attr_name);

    VIR_DEBUG("Found sysfs attribute '%s' value '%s' "
              "for device with sysname '%s'",
              attr_name, NULLSTR(ret),
              udev_device_get_sysname(udev_device));
    return ret;
}


static void
udevGetStringSysfsAttr(struct udev_device *udev_device,
                       const char *attr_name,
                       char **value)
{
    *value = g_strdup(udevGetDeviceSysfsAttr(udev_device, attr_name));

    virStringStripControlChars(*value);

    if (*value != NULL && (STREQ(*value, "")))
        VIR_FREE(*value);
}


static int
udevGetIntSysfsAttr(struct udev_device *udev_device,
                    const char *attr_name,
                    int *value,
                    int base)
{
    const char *str = NULL;

    str = udevGetDeviceSysfsAttr(udev_device, attr_name);

    if (str && virStrToLong_i(str, NULL, base, value) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to convert '%1$s' to int"), str);
        return -1;
    }

    return 0;
}


static int
udevGetUintSysfsAttr(struct udev_device *udev_device,
                     const char *attr_name,
                     unsigned int *value,
                     int base)
{
    const char *str = NULL;

    str = udevGetDeviceSysfsAttr(udev_device, attr_name);

    if (str && virStrToLong_ui(str, NULL, base, value) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to convert '%1$s' to unsigned int"), str);
        return -1;
    }

    return 0;
}


static int
udevGetUint64SysfsAttr(struct udev_device *udev_device,
                       const char *attr_name,
                       unsigned long long *value)
{
    const char *str = NULL;

    str = udevGetDeviceSysfsAttr(udev_device, attr_name);

    if (str && virStrToLong_ull(str, NULL, 0, value) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to convert '%1$s' to unsigned long long"), str);
        return -1;
    }

    return 0;
}


static void
udevGenerateDeviceName(struct udev_device *device,
                       virNodeDeviceDef *def,
                       const char *s)
{
    nodeDeviceGenerateName(def,
                           udev_device_get_subsystem(device),
                           udev_device_get_sysname(device), s);
}

static virMutex pciaccessMutex = VIR_MUTEX_INITIALIZER;

static void
udevTranslatePCIIds(unsigned int vendor,
                    unsigned int product,
                    char **vendor_string,
                    char **product_string)
{
    struct pci_id_match m;
    const char *vendor_name = NULL, *device_name = NULL;

    m.vendor_id = vendor;
    m.device_id = product;
    m.subvendor_id = PCI_MATCH_ANY;
    m.subdevice_id = PCI_MATCH_ANY;
    m.device_class = 0;
    m.device_class_mask = 0;
    m.match_data = 0;

    /* pci_get_strings returns void and unfortunately is not thread safe. */
    VIR_WITH_MUTEX_LOCK_GUARD(&pciaccessMutex) {
        pci_get_strings(&m, &device_name, &vendor_name, NULL, NULL);
    }

    *vendor_string = g_strdup(vendor_name);
    *product_string = g_strdup(device_name);
}


static int
udevProcessPCI(virNodeDeviceDriverState *driver_state,
               struct udev_device *device,
               virNodeDeviceDef *def)
{
    virNodeDevCapPCIDev *pci_dev = &def->caps->data.pci_dev;
    virPCIEDeviceInfo *pci_express = NULL;
    virPCIDevice *pciDev = NULL;
    virPCIDeviceAddress devAddr = { 0 };
    g_autofree char *linkpath = NULL;
    int ret = -1;
    char *p;

    linkpath = g_strdup_printf("%s/config", udev_device_get_syspath(device));
    if (virFileWaitForExists(linkpath, 10, 100) < 0) {
        virReportSystemError(errno,
                             _("failed to wait for file '%1$s' to appear"),
                             linkpath);
        goto cleanup;
    }

    pci_dev->klass = -1;
    if (udevGetIntProperty(device, "PCI_CLASS", &pci_dev->klass, 16) < 0)
        goto cleanup;

    if ((p = strrchr(def->sysfs_path, '/')) == NULL ||
        virStrToLong_ui(p + 1, &p, 16, &pci_dev->domain) < 0 || p == NULL ||
        virStrToLong_ui(p + 1, &p, 16, &pci_dev->bus) < 0 || p == NULL ||
        virStrToLong_ui(p + 1, &p, 16, &pci_dev->slot) < 0 || p == NULL ||
        virStrToLong_ui(p + 1, &p, 16, &pci_dev->function) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to parse the PCI address from sysfs path: '%1$s'"),
                       def->sysfs_path);
        goto cleanup;
    }

    if (udevGetUintSysfsAttr(device, "vendor", &pci_dev->vendor, 16) < 0)
        goto cleanup;

    if (udevGetUintSysfsAttr(device, "device", &pci_dev->product, 16) < 0)
        goto cleanup;

    udevTranslatePCIIds(pci_dev->vendor,
                        pci_dev->product,
                        &pci_dev->vendor_name,
                        &pci_dev->product_name);

    udevGenerateDeviceName(device, def, NULL);

    /* The default value is -1, because it can't be 0
     * as zero is valid node number. */
    pci_dev->numa_node = -1;
    if (udevGetIntSysfsAttr(device, "numa_node",
                            &pci_dev->numa_node, 10) < 0)
        goto cleanup;

    if (virNodeDeviceGetPCIDynamicCaps(def->sysfs_path, pci_dev) < 0)
        goto cleanup;

    devAddr.domain = pci_dev->domain;
    devAddr.bus = pci_dev->bus;
    devAddr.slot = pci_dev->slot;
    devAddr.function = pci_dev->function;

    if (!(pciDev = virPCIDeviceNew(&devAddr)))
        goto cleanup;

    /* We need to be root to read PCI device configs */
    if (driver_state->privileged) {
        if (virPCIGetHeaderType(pciDev, &pci_dev->hdrType) < 0)
            goto cleanup;

        if (virPCIDeviceIsPCIExpress(pciDev) > 0) {
            pci_express = g_new0(virPCIEDeviceInfo, 1);

            if (virPCIDeviceHasPCIExpressLink(pciDev) > 0) {
                pci_express->link_cap = g_new0(virPCIELink, 1);
                pci_express->link_sta = g_new0(virPCIELink, 1);

                if (virPCIDeviceGetLinkCapSta(pciDev,
                                              &pci_express->link_cap->port,
                                              &pci_express->link_cap->speed,
                                              &pci_express->link_cap->width,
                                              &pci_express->link_sta->speed,
                                              &pci_express->link_sta->width) < 0)
                    goto cleanup;

                pci_express->link_sta->port = -1; /* PCIe can't negotiate port. Yet :) */
            }
            pci_dev->flags |= VIR_NODE_DEV_CAP_FLAG_PCIE;
            pci_dev->pci_express = g_steal_pointer(&pci_express);
        }
    }

    ret = 0;

 cleanup:
    virPCIDeviceFree(pciDev);
    virPCIEDeviceInfoFree(pci_express);
    return ret;
}


static int
udevProcessMdevParent(struct udev_device *device,
                      virNodeDeviceDef *def)
{
    virNodeDevCapMdevParent *mdev_parent = &def->caps->data.mdev_parent;

    udevGenerateDeviceName(device, def, NULL);

    if (virMediatedDeviceParentGetAddress(def->sysfs_path, &mdev_parent->address) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to find address for mdev parent device '%1$s'"),
                       def->name);
        return -1;
    }

    if (virNodeDeviceGetMdevParentDynamicCaps(def->sysfs_path, mdev_parent) < 0)
        return -1;

    return 0;
}


static int
drmGetMinorType(int minor)
{
    int type = minor >> 6;

    if (minor < 0)
        return -1;

    switch (type) {
    case VIR_NODE_DEV_DRM_PRIMARY:
    case VIR_NODE_DEV_DRM_CONTROL:
    case VIR_NODE_DEV_DRM_RENDER:
        return type;
    default:
        return -1;
    }
}


static int
udevProcessDRMDevice(struct udev_device *device,
                     virNodeDeviceDef *def)
{
    virNodeDevCapDRM *drm = &def->caps->data.drm;
    int minor;

    udevGenerateDeviceName(device, def, NULL);

    if (udevGetIntProperty(device, "MINOR", &minor, 10) < 0)
        return -1;

    if ((minor = drmGetMinorType(minor)) == -1)
        return -1;

    drm->type = minor;

    return 0;
}


static int
udevProcessUSBDevice(struct udev_device *device,
                     virNodeDeviceDef *def)
{
    virNodeDevCapUSBDev *usb_dev = &def->caps->data.usb_dev;

    if (udevGetUintProperty(device, "BUSNUM", &usb_dev->bus, 10) < 0)
        return -1;
    if (udevGetUintProperty(device, "DEVNUM", &usb_dev->device, 10) < 0)
        return -1;
    if (udevGetUintProperty(device, "ID_VENDOR_ID", &usb_dev->vendor, 16) < 0)
        return -1;
    if (!usb_dev->port) {
        udevGetStringSysfsAttr(device, "devpath",
                               &usb_dev->port);
    }

    udevGetStringProperty(device,
                          "ID_VENDOR_FROM_DATABASE",
                          &usb_dev->vendor_name);

    if (!usb_dev->vendor_name) {
        udevGetStringSysfsAttr(device, "manufacturer",
                               &usb_dev->vendor_name);
    }

    if (udevGetUintProperty(device, "ID_MODEL_ID", &usb_dev->product, 16) < 0)
        return -1;

    udevGetStringProperty(device,
                          "ID_MODEL_FROM_DATABASE",
                          &usb_dev->product_name);

    if (!usb_dev->product_name) {
        udevGetStringSysfsAttr(device, "product",
                               &usb_dev->product_name);
    }

    udevGenerateDeviceName(device, def, NULL);

    return 0;
}


static int
udevProcessUSBInterface(struct udev_device *device,
                        virNodeDeviceDef *def)
{
    virNodeDevCapUSBIf *usb_if = &def->caps->data.usb_if;

    if (udevGetUintSysfsAttr(device, "bInterfaceNumber",
                             &usb_if->number, 16) < 0)
        return -1;

    if (udevGetUintSysfsAttr(device, "bInterfaceClass",
                             &usb_if->klass, 16) < 0)
        return -1;

    if (udevGetUintSysfsAttr(device, "bInterfaceSubClass",
                             &usb_if->subclass, 16) < 0)
        return -1;

    if (udevGetUintSysfsAttr(device, "bInterfaceProtocol",
                             &usb_if->protocol, 16) < 0)
        return -1;

    udevGenerateDeviceName(device, def, NULL);

    return 0;
}


static int
udevProcessNetworkInterface(struct udev_device *device,
                            virNodeDeviceDef *def)
{
    const char *devtype = udev_device_get_devtype(device);
    virNodeDevCapNet *net = &def->caps->data.net;

    if (devtype && STREQ(devtype, "wlan")) {
        net->subtype = VIR_NODE_DEV_CAP_NET_80211;
    } else {
        net->subtype = VIR_NODE_DEV_CAP_NET_80203;
    }

    udevGetStringProperty(device, "INTERFACE", &net->ifname);

    udevGetStringSysfsAttr(device, "address", &net->address);

    if (udevGetUintSysfsAttr(device, "addr_len", &net->address_len, 0) < 0)
        return -1;

    udevGenerateDeviceName(device, def, net->address);

    if (virNetDevGetLinkInfo(net->ifname, &net->lnk) < 0)
        return -1;

    if (virNetDevGetFeatures(net->ifname, &net->features) < 0)
        return -1;

    return 0;
}


static int
udevProcessSCSIHost(struct udev_device *device G_GNUC_UNUSED,
                    virNodeDeviceDef *def)
{
    virNodeDevCapSCSIHost *scsi_host = &def->caps->data.scsi_host;
    g_autofree char *filename = NULL;
    char *str;

    filename = g_path_get_basename(def->sysfs_path);

    if (!(str = STRSKIP(filename, "host")) ||
        virStrToLong_ui(str, NULL, 0, &scsi_host->host) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to parse SCSI host '%1$s'"),
                       filename);
        return -1;
    }

    virNodeDeviceGetSCSIHostCaps(&def->caps->data.scsi_host);

    udevGenerateDeviceName(device, def, NULL);

    return 0;
}


static int
udevProcessSCSITarget(struct udev_device *device,
                      virNodeDeviceDef *def)
{
    const char *sysname = NULL;
    virNodeDevCapSCSITarget *scsi_target = &def->caps->data.scsi_target;

    sysname = udev_device_get_sysname(device);

    scsi_target->name = g_strdup(sysname);

    virNodeDeviceGetSCSITargetCaps(def->sysfs_path, &def->caps->data.scsi_target);

    udevGenerateDeviceName(device, def, NULL);

    return 0;
}


static int
udevGetSCSIType(virNodeDeviceDef *def G_GNUC_UNUSED,
                unsigned int type,
                char **typestring)
{
    int ret = 0;
    int foundtype = 1;

    *typestring = NULL;

#ifdef __linux__
    /* These values are Linux specific. */
    switch (type) {
    case TYPE_DISK:
        *typestring = g_strdup("disk");
        break;
    case TYPE_TAPE:
        *typestring = g_strdup("tape");
        break;
    case TYPE_PROCESSOR:
        *typestring = g_strdup("processor");
        break;
    case TYPE_WORM:
        *typestring = g_strdup("worm");
        break;
    case TYPE_ROM:
        *typestring = g_strdup("cdrom");
        break;
    case TYPE_SCANNER:
        *typestring = g_strdup("scanner");
        break;
    case TYPE_MOD:
        *typestring = g_strdup("mod");
        break;
    case TYPE_MEDIUM_CHANGER:
        *typestring = g_strdup("changer");
        break;
    case TYPE_ENCLOSURE:
        *typestring = g_strdup("enclosure");
        break;
    case TYPE_RAID:
        *typestring = g_strdup("raid");
        break;
    case TYPE_NO_LUN:
    default:
        foundtype = 0;
        break;
    }
#else
    /* Implement me. */
    foundtype = 0;
#endif

    if (*typestring == NULL) {
        if (foundtype == 1) {
            ret = -1;
        } else {
            VIR_DEBUG("Failed to find SCSI device type %d for %s",
                      type, def->sysfs_path);
        }
    }

    return ret;
}


static int
udevProcessSCSIDevice(struct udev_device *device,
                      virNodeDeviceDef *def)
{
    int ret = -1;
    unsigned int tmp = 0;
    virNodeDevCapSCSI *scsi = &def->caps->data.scsi;
    g_autofree char *filename = NULL;
    char *p = NULL;

    filename = g_path_get_basename(def->sysfs_path);

    if (virStrToLong_ui(filename, &p, 10, &scsi->host) < 0 || p == NULL ||
        virStrToLong_ui(p + 1, &p, 10, &scsi->bus) < 0 || p == NULL ||
        virStrToLong_ui(p + 1, &p, 10, &scsi->target) < 0 || p == NULL ||
        virStrToLong_ui(p + 1, &p, 10, &scsi->lun) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to parse the SCSI address from filename: '%1$s'"),
                       filename);
        return -1;
    }

    if (udev_device_get_sysattr_value(device, "type")) {
        if (udevGetUintSysfsAttr(device, "type", &tmp, 0) < 0)
            goto cleanup;

        if (udevGetSCSIType(def, tmp, &scsi->type) < 0)
            goto cleanup;
    }

    udevGenerateDeviceName(device, def, NULL);

    ret = 0;

 cleanup:
    if (ret != 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to process SCSI device with sysfs path '%1$s'"),
                       def->sysfs_path);
    }
    return ret;
}


static int
udevProcessDisk(struct udev_device *device,
                virNodeDeviceDef *def)
{
    virNodeDevCapStorage *storage = &def->caps->data.storage;

    if (udevGetUint64SysfsAttr(device, "size", &storage->num_blocks) < 0)
        return -1;

    if (udevGetUint64SysfsAttr(device, "queue/logical_block_size",
                               &storage->logical_block_size) < 0)
        return -1;

    storage->size = storage->num_blocks * storage->logical_block_size;

    return 0;
}


static int
udevProcessRemoveableMedia(struct udev_device *device,
                           virNodeDeviceDef *def,
                           int has_media)
{
    virNodeDevCapStorage *storage = &def->caps->data.storage;
    int is_removable = 0;

    if (udevGetIntSysfsAttr(device, "removable", &is_removable, 0) < 0)
        return -1;
    if (is_removable == 1)
        def->caps->data.storage.flags |= VIR_NODE_DEV_CAP_STORAGE_REMOVABLE;

    if (!has_media)
        return 0;

    def->caps->data.storage.flags |=
        VIR_NODE_DEV_CAP_STORAGE_REMOVABLE_MEDIA_AVAILABLE;

    udevGetStringProperty(device, "ID_FS_LABEL", &storage->media_label);

    if (udevGetUint64SysfsAttr(device, "size",
                               &storage->num_blocks) < 0)
        return -1;

    if (udevGetUint64SysfsAttr(device, "queue/logical_block_size",
                               &storage->logical_block_size) < 0)
        return -1;

    /* 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 0;
}


static int
udevProcessCDROM(struct udev_device *device,
                 virNodeDeviceDef *def)
{
    int has_media = 0;

    /* NB: the drive_type string provided by udev is different from
     * that provided by HAL; now it's "cd" instead of "cdrom" We
     * change it to cdrom to preserve compatibility with earlier
     * versions of libvirt.  */
    VIR_FREE(def->caps->data.storage.drive_type);
    def->caps->data.storage.drive_type = g_strdup("cdrom");

    if (udevHasDeviceProperty(device, "ID_CDROM_MEDIA") &&
        udevGetIntProperty(device, "ID_CDROM_MEDIA", &has_media, 0) < 0)
        return -1;

    return udevProcessRemoveableMedia(device, def, has_media);
}


static int
udevProcessFloppy(struct udev_device *device,
                  virNodeDeviceDef *def)
{
    int has_media = 0;

    if (udevHasDeviceProperty(device, "ID_FS_LABEL")) {
        /* Legacy floppy */
        has_media = 1;
    }

    return udevProcessRemoveableMedia(device, def, has_media);
}


static int
udevProcessSD(struct udev_device *device,
              virNodeDeviceDef *def)
{
    virNodeDevCapStorage *storage = &def->caps->data.storage;

    if (udevGetUint64SysfsAttr(device, "size",
                               &storage->num_blocks) < 0)
        return -1;

    if (udevGetUint64SysfsAttr(device, "queue/logical_block_size",
                               &storage->logical_block_size) < 0)
        return -1;

    storage->size = storage->num_blocks * storage->logical_block_size;

    return 0;
}


static int
udevProcessDASD(struct udev_device *device,
                virNodeDeviceDef *def)
{
    virNodeDevCapStorage *storage = &def->caps->data.storage;

    udevGetStringSysfsAttr(device, "device/uid", &storage->serial);

    if (!storage->serial)
        return -1;

    return udevProcessDisk(device, def);
}


static int
udevFixupStorageType(virNodeDeviceDef *def,
                     const char *prefix,
                     const char *subst)
{
    if (STRPREFIX(def->caps->data.storage.block, prefix)) {
        def->caps->data.storage.drive_type = g_strdup(subst);
        VIR_DEBUG("Found storage type '%s' for device with sysfs path '%s'",
                  def->caps->data.storage.drive_type,
                  def->sysfs_path);
        return 1;
    }

    return 0;
}


/* 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(virNodeDeviceDef *def)
{
    size_t i;
    const struct {
        const char *prefix;
        const char *subst;
    } fixups[] = {
        /* virtio disk */
        { "/dev/vd", "disk" },

        /* For Direct Access Storage Devices (DASDs) there are
         * currently no identifiers in udev besides ID_PATH. Since
         * ID_TYPE=disk does not exist on DASDs they fall through
         * the udevProcessStorage detection logic. */
        { "/dev/dasd", "dasd" },

        /* NVMe disk. While strictly speaking /dev/nvme is a
         * controller not a disk, this function is called if and
         * only if @def is of VIR_NODE_DEV_CAP_STORAGE type. */
        { "/dev/nvme", "disk" },
    };

    VIR_DEBUG("Could not find definitive storage type for device "
              "with sysfs path '%s', trying to guess it",
              def->sysfs_path);

    for (i = 0; i < G_N_ELEMENTS(fixups); i++) {
        if (udevFixupStorageType(def, fixups[i].prefix, fixups[i].subst))
            return 0;
    }

    VIR_DEBUG("Could not determine storage type "
              "for device with sysfs path '%s'", def->sysfs_path);
    return -1;
}


static int
udevProcessStorage(struct udev_device *device,
                   virNodeDeviceDef *def)
{
    virNodeDevCapStorage *storage = &def->caps->data.storage;
    int ret = -1;
    int rv;
    const char* devnode;

    devnode = udev_device_get_devnode(device);
    if (!devnode) {
        VIR_DEBUG("No devnode for '%s'", udev_device_get_devpath(device));
        goto cleanup;
    }

    storage->block = g_strdup(devnode);

    udevGetStringProperty(device, "ID_BUS", &storage->bus);
    udevGetStringProperty(device, "ID_SERIAL", &storage->serial);

    udevGetStringSysfsAttr(device, "device/vendor", &storage->vendor);
    if (def->caps->data.storage.vendor)
        virTrimSpaces(def->caps->data.storage.vendor, NULL);

    udevGetStringSysfsAttr(device, "device/model", &storage->model);
    if (def->caps->data.storage.model)
        virTrimSpaces(def->caps->data.storage.model, NULL);
    /* There is no equivalent of the hotpluggable property in libudev,
     * but storage is going toward a world in which hotpluggable is
     * expected, so I don't see a problem with not having a property
     * for it. */

    udevGetStringProperty(device, "ID_TYPE", &storage->drive_type);

    if (!storage->drive_type ||
        STREQ(def->caps->data.storage.drive_type, "generic")) {
        /* All floppy drives have the ID_DRIVE_FLOPPY prop. This is
         * needed since legacy floppies don't have a drive_type */
        if (udevHasDeviceProperty(device, "ID_DRIVE_FLOPPY"))
            storage->drive_type = g_strdup("floppy");
        else if (udevHasDeviceProperty(device, "ID_CDROM"))
            storage->drive_type = g_strdup("cd");
        else if (udevHasDeviceProperty(device, "ID_DRIVE_FLASH_SD"))
            storage->drive_type = g_strdup("sd");
        else if (udevKludgeStorageType(def) != 0)
            goto cleanup;
    } else {
        /* A detected disk might be a DASD */
        if (STREQ(def->caps->data.storage.drive_type, "disk"))
            udevFixupStorageType(def, "/dev/dasd", "dasd");
    }

    if (STREQ(def->caps->data.storage.drive_type, "cd") ||
        STREQ(def->caps->data.storage.drive_type, "cd/dvd")) {
        rv = udevProcessCDROM(device, def);
    } else if (STREQ(def->caps->data.storage.drive_type, "disk")) {
        rv = udevProcessDisk(device, def);
    } else if (STREQ(def->caps->data.storage.drive_type, "floppy")) {
        rv = udevProcessFloppy(device, def);
    } else if (STREQ(def->caps->data.storage.drive_type, "sd")) {
        rv = udevProcessSD(device, def);
    } else if (STREQ(def->caps->data.storage.drive_type, "dasd")) {
        rv = udevProcessDASD(device, def);
    } else {
        VIR_DEBUG("Unsupported storage type '%s'",
                  def->caps->data.storage.drive_type);
        goto cleanup;
    }

    if (rv < 0)
        goto cleanup;

    udevGenerateDeviceName(device, def, storage->serial);
    ret = 0;

 cleanup:
    VIR_DEBUG("Storage ret=%d", ret);
    return ret;
}


static int
udevProcessSCSIGeneric(struct udev_device *dev,
                       virNodeDeviceDef *def)
{
    udevGetStringProperty(dev, "DEVNAME", &def->caps->data.sg.path);

    if (!def->caps->data.sg.path)
        return -1;

    udevGenerateDeviceName(dev, def, NULL);

    return 0;
}


static int
udevProcessMediatedDevice(struct udev_device *dev,
                          virNodeDeviceDef *def)
{
    int iommugrp = -1;
    g_autofree char *linkpath = NULL;
    g_autofree char *canonicalpath = NULL;
    virNodeDevCapMdev *data = &def->caps->data.mdev;
    struct udev_device *parent_device = NULL;

    /* Because of a kernel uevent race, we might get the 'add' event prior to
     * the sysfs tree being ready, so any attempt to access any sysfs attribute
     * would result in ENOENT and us dropping the device, so let's work around
     * it by waiting for the attributes to become available.
     */

    linkpath = g_strdup_printf("%s/mdev_type", udev_device_get_syspath(dev));

    if (virFileWaitForExists(linkpath, 10, 100) < 0) {
        virReportSystemError(errno,
                             _("failed to wait for file '%1$s' to appear"),
                             linkpath);
        return -1;
    }

    if (virFileResolveLink(linkpath, &canonicalpath) < 0) {
        virReportSystemError(errno, _("failed to resolve '%1$s'"), linkpath);
        return -1;
    }

    data->active_config.type = g_path_get_basename(canonicalpath);

    data->uuid = g_strdup(udev_device_get_sysname(dev));
    if ((iommugrp = virMediatedDeviceGetIOMMUGroupNum(data->uuid)) < 0)
        return -1;

    /* lookup the address of parent device */
    parent_device = udev_device_get_parent(dev);
    if (parent_device) {
        const char *parent_sysfs_path = udev_device_get_syspath(parent_device);
        if (parent_sysfs_path)
            data->parent_addr = g_path_get_basename(parent_sysfs_path);
    }

    if (!data->parent_addr) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not get parent of '%1$s'"),
                       udev_device_get_syspath(dev));
        return -1;
    }

    udevGenerateDeviceName(dev, def, data->parent_addr);

    data->iommuGroupNumber = iommugrp;

    return 0;
}


static int
udevGetCCWAddress(const char *sysfs_path,
                  virNodeDevCapData *data)
{
    const char *p;
    g_autofree virCCWDeviceAddress *ccw_addr = g_new0(virCCWDeviceAddress, 1);

    if ((p = strrchr(sysfs_path, '/')) == NULL ||
        virCCWDeviceAddressParseFromString(p + 1,
                                           &ccw_addr->cssid,
                                           &ccw_addr->ssid,
                                           &ccw_addr->devno) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to parse the CCW address from sysfs path: '%1$s'"),
                       sysfs_path);
        return -1;
    }

    data->ccw_dev.dev_addr = g_steal_pointer(&ccw_addr);

    return 0;
}


static int
udevCCWGetState(struct udev_device *device,
                virNodeDevCCWStateType *state)
{
    int online = 0;

    if (udevGetIntSysfsAttr(device, "online", &online, 0) < 0 || online < 0)
        return -1;

    switch (online) {
    case VIR_NODE_DEV_CCW_STATE_OFFLINE:
    case VIR_NODE_DEV_CCW_STATE_ONLINE:
        *state = online;
        break;
    default:
        *state = VIR_NODE_DEV_CCW_STATE_LAST;
        break;
    }

    return 0;
}


static int
udevProcessCCW(struct udev_device *device,
               virNodeDeviceDef *def)
{
    /* process only online devices to keep the list sane */
    if (udevCCWGetState(device, &def->caps->data.ccw_dev.state) < 0)
        return -1;

    if (udevGetCCWAddress(def->sysfs_path, &def->caps->data) < 0)
        return -1;

    udevGenerateDeviceName(device, def, NULL);

    if (virNodeDeviceGetCCWDynamicCaps(def->sysfs_path, &def->caps->data.ccw_dev) < 0)
        return -1;

    return 0;
}


static int
udevProcessCSS(struct udev_device *device,
               virNodeDeviceDef *def)
{
    g_autofree char *dev_busid = NULL;

    /* only process IO subchannel and vfio-ccw devices to keep the list sane */
    if (!def->driver ||
        (STRNEQ(def->driver, "io_subchannel") &&
         STRNEQ(def->driver, "vfio_ccw")))
        return -1;

    if (udevGetCCWAddress(def->sysfs_path, &def->caps->data) < 0)
        return -1;

    udevGenerateDeviceName(device, def, NULL);

    /* process optional channel devices information */
    udevGetStringSysfsAttr(device, "dev_busid", &dev_busid);

    if (dev_busid != NULL && STRNEQ(dev_busid, "none"))
        def->caps->data.ccw_dev.channel_dev_addr = virCCWDeviceAddressFromString(dev_busid);

    if (virNodeDeviceGetCSSDynamicCaps(def->sysfs_path, &def->caps->data.ccw_dev) < 0)
        return -1;

    return 0;
}


static int
udevGetVDPACharDev(const char *sysfs_path,
                   virNodeDevCapData *data)
{
    struct dirent *entry;
    g_autoptr(DIR) dir = NULL;
    int direrr;

    if (virDirOpenIfExists(&dir, sysfs_path) <= 0)
        return -1;

    while ((direrr = virDirRead(dir, &entry, NULL)) > 0) {
        if (g_str_has_prefix(entry->d_name, "vhost-vdpa")) {
            g_autofree char *chardev = g_strdup_printf("/dev/%s", entry->d_name);

            if (!virFileExists(chardev)) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("vDPA chardev path '%1$s' does not exist"),
                               chardev);
                return -1;
            }
            VIR_DEBUG("vDPA chardev is at '%s'", chardev);

            data->vdpa.chardev = g_steal_pointer(&chardev);
            break;
        }
    }
    if (direrr < 0)
        return -1;

    return 0;
}

static int
udevProcessVDPA(struct udev_device *device,
                virNodeDeviceDef *def)
{
    udevGenerateDeviceName(device, def, NULL);

    if (udevGetVDPACharDev(def->sysfs_path, &def->caps->data) < 0)
        return -1;

    return 0;
}


static int
udevProcessAPCard(struct udev_device *device,
                  virNodeDeviceDef *def)
{
    char *c;
    virNodeDevCapData *data = &def->caps->data;

    /* The sysfs path would be in the format /sys/bus/ap/devices/cardXX,
       where XX is the ap adapter id */
    if ((c = strrchr(def->sysfs_path, '/')) == NULL ||
        virStrToLong_ui(c + 1 + strlen("card"), NULL, 16,
                        &data->ap_card.ap_adapter) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to parse the AP Card from sysfs path: '%1$s'"),
                       def->sysfs_path);
        return -1;
    }

    udevGenerateDeviceName(device, def, NULL);

    return 0;
}


static int
udevProcessAPQueue(struct udev_device *device,
                   virNodeDeviceDef *def)
{
    char *c;
    virNodeDevCapData *data = &def->caps->data;

    /* The sysfs path would be in the format /sys/bus/ap/devices
       /XX.YYYY, where XX is the ap adapter id and YYYY is the ap
       domain id  */
    if ((c = strrchr(def->sysfs_path, '/')) == NULL ||
        virStrToLong_ui(c + 1, &c, 16, &data->ap_queue.ap_adapter) < 0 ||
        virStrToLong_ui(c + 1, &c, 16, &data->ap_queue.ap_domain) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to parse the AP Queue from sysfs path: '%1$s'"),
                       def->sysfs_path);
        return -1;
    }

    udevGenerateDeviceName(device, def, NULL);

    return 0;
}


static int
udevProcessAPMatrix(struct udev_device *device,
                    virNodeDeviceDef *def)
{
    /* Both udev_device_get_sysname and udev_device_get_subsystem return
     * "matrix" for an AP matrix device, so in order to prevent confusion in
     * naming, let's fallback to hardcoding the name.
     */
    virNodeDevCapData *data = &def->caps->data;

    data->ap_matrix.addr =  g_strdup(udev_device_get_sysname(device));
    def->name = g_strdup("ap_matrix");

    if (virNodeDeviceGetAPMatrixDynamicCaps(def->sysfs_path,
                                            &data->ap_matrix) < 0)
        return -1;

    return 0;
}


static int
udevProcessCCWGroup(struct udev_device *device,
                    virNodeDeviceDef *def)
{
    const char *devtype = udev_device_get_devtype(device);
    virNodeDevCapData *data = &def->caps->data;
    int tmp;

    data->ccwgroup_dev.address = virCCWDeviceAddressFromString(udev_device_get_sysname(device));

    udevCCWGetState(device, &data->ccwgroup_dev.state);

    udevGenerateDeviceName(device, def, NULL);

    if ((tmp = virNodeDevCCWGroupCapTypeFromString(devtype)) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to find a supported CCW group capability type '%1$s'"),
                       devtype);
        return -1;
    }

    data->ccwgroup_dev.type = tmp;

    switch (data->ccwgroup_dev.type) {
    case VIR_NODE_DEV_CAP_CCWGROUP_QETH_GENERIC:
    case VIR_NODE_DEV_CAP_CCWGROUP_QETH_LAYER2:
    case VIR_NODE_DEV_CAP_CCWGROUP_QETH_LAYER3:
        {
            virCCWGroupTypeQeth *qeth = &data->ccwgroup_dev.qeth;
            /* process qeth device information */
            udevGetStringSysfsAttr(device, "card_type", &qeth->card_type);
            udevGetStringSysfsAttr(device, "chpid", &qeth->chpid);
        }
        break;
    case VIR_NODE_DEV_CAP_CCWGROUP_LAST:
        return -1;
    }

    if (virNodeDeviceGetCCWGroupDynamicCaps(def->sysfs_path,
                                            &data->ccwgroup_dev) < 0)
        return -1;

    return 0;
}


static int
udevGetDeviceNodes(struct udev_device *device,
                   virNodeDeviceDef *def)
{
    const char *devnode = NULL;
    struct udev_list_entry *list_entry = NULL;
    int n = 0;

    devnode = udev_device_get_devnode(device);

    def->devnode = g_strdup(devnode);

    udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device))
        n++;

    def->devlinks = g_new0(char *, n + 1);

    n = 0;
    udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device)) {
        def->devlinks[n++] = g_strdup(udev_list_entry_get_name(list_entry));
    }

    return 0;
}


static int
udevGetDeviceType(struct udev_device *device,
                  virNodeDevCapType *type)
{
    const char *devtype = NULL;
    char *subsystem = NULL;
    int ret = -1;

    devtype = udev_device_get_devtype(device);
    *type = 0;

    if (devtype) {
        if (STREQ(devtype, "usb_device"))
            *type = VIR_NODE_DEV_CAP_USB_DEV;
        else if (STREQ(devtype, "usb_interface"))
            *type = VIR_NODE_DEV_CAP_USB_INTERFACE;
        else if (STREQ(devtype, "scsi_host"))
            *type = VIR_NODE_DEV_CAP_SCSI_HOST;
        else if (STREQ(devtype, "scsi_target"))
            *type = VIR_NODE_DEV_CAP_SCSI_TARGET;
        else if (STREQ(devtype, "scsi_device"))
            *type = VIR_NODE_DEV_CAP_SCSI;
        else if (STREQ(devtype, "disk"))
            *type = VIR_NODE_DEV_CAP_STORAGE;
        else if (STREQ(devtype, "wlan"))
            *type = VIR_NODE_DEV_CAP_NET;
        else if (STREQ(devtype, "drm_minor"))
            *type = VIR_NODE_DEV_CAP_DRM;
        else if (STREQ(devtype, "ap_card"))
            *type = VIR_NODE_DEV_CAP_AP_CARD;
        else if (STREQ(devtype, "ap_queue"))
            *type = VIR_NODE_DEV_CAP_AP_QUEUE;
        else if (STREQ(devtype, "qeth_generic") ||
                 STREQ(devtype, "qeth_layer2") ||
                 STREQ(devtype, "qeth_layer3"))
            *type = VIR_NODE_DEV_CAP_CCWGROUP_DEV;
    } else {
        /* PCI devices don't set the DEVTYPE property. */
        if (udevHasDeviceProperty(device, "PCI_CLASS"))
            *type = VIR_NODE_DEV_CAP_PCI_DEV;

        /* Wired network interfaces don't set the DEVTYPE property,
         * USB devices also have an INTERFACE property, but they do
         * set DEVTYPE, so if devtype is NULL and the INTERFACE
         * property exists, we have a network device. */
        if (udevHasDeviceProperty(device, "INTERFACE"))
            *type = VIR_NODE_DEV_CAP_NET;

        /* The following devices do not set the DEVTYPE property, therefore
         * we need to rely on the SUBSYSTEM property */
        udevGetStringProperty(device, "SUBSYSTEM", &subsystem);

        if (STREQ_NULLABLE(subsystem, "scsi_generic"))
            *type = VIR_NODE_DEV_CAP_SCSI_GENERIC;
        else if (STREQ_NULLABLE(subsystem, "mdev"))
            *type = VIR_NODE_DEV_CAP_MDEV;
        else if (STREQ_NULLABLE(subsystem, "ccw"))
            *type = VIR_NODE_DEV_CAP_CCW_DEV;
        else if (STREQ_NULLABLE(subsystem, "css"))
            *type = VIR_NODE_DEV_CAP_CSS_DEV;
        else if (STREQ_NULLABLE(subsystem, "vdpa"))
            *type = VIR_NODE_DEV_CAP_VDPA;
        else if (STREQ_NULLABLE(subsystem, "matrix"))
            *type = VIR_NODE_DEV_CAP_AP_MATRIX;
        else if (STREQ_NULLABLE(subsystem, "mtty"))
            *type = VIR_NODE_DEV_CAP_MDEV_TYPES;

        VIR_FREE(subsystem);
    }

    if (!*type)
        VIR_DEBUG("Could not determine device type for device "
                  "with sysfs name '%s'",
                  udev_device_get_sysname(device));
    else
        ret = 0;

    return ret;
}


static int
udevGetDeviceDetails(virNodeDeviceDriverState *driver_state,
                     struct udev_device *device,
                     virNodeDeviceDef *def)
{
    switch (def->caps->data.type) {
    case VIR_NODE_DEV_CAP_PCI_DEV:
        return udevProcessPCI(driver_state, device, def);
    case VIR_NODE_DEV_CAP_USB_DEV:
        return udevProcessUSBDevice(device, def);
    case VIR_NODE_DEV_CAP_USB_INTERFACE:
        return udevProcessUSBInterface(device, def);
    case VIR_NODE_DEV_CAP_NET:
        return udevProcessNetworkInterface(device, def);
    case VIR_NODE_DEV_CAP_SCSI_HOST:
        return udevProcessSCSIHost(device, def);
    case VIR_NODE_DEV_CAP_SCSI_TARGET:
        return udevProcessSCSITarget(device, def);
    case VIR_NODE_DEV_CAP_SCSI:
        return udevProcessSCSIDevice(device, def);
    case VIR_NODE_DEV_CAP_STORAGE:
        return udevProcessStorage(device, def);
    case VIR_NODE_DEV_CAP_SCSI_GENERIC:
        return udevProcessSCSIGeneric(device, def);
    case VIR_NODE_DEV_CAP_DRM:
        return udevProcessDRMDevice(device, def);
    case VIR_NODE_DEV_CAP_MDEV:
        return udevProcessMediatedDevice(device, def);
    case VIR_NODE_DEV_CAP_CCW_DEV:
        return udevProcessCCW(device, def);
    case VIR_NODE_DEV_CAP_CSS_DEV:
        return udevProcessCSS(device, def);
    case VIR_NODE_DEV_CAP_VDPA:
        return udevProcessVDPA(device, def);
    case VIR_NODE_DEV_CAP_AP_CARD:
        return udevProcessAPCard(device, def);
    case VIR_NODE_DEV_CAP_AP_QUEUE:
        return udevProcessAPQueue(device, def);
    case VIR_NODE_DEV_CAP_AP_MATRIX:
        return udevProcessAPMatrix(device, def);
    case VIR_NODE_DEV_CAP_MDEV_TYPES:
        return udevProcessMdevParent(device, def);
    case VIR_NODE_DEV_CAP_CCWGROUP_DEV:
        return udevProcessCCWGroup(device, def);
    case VIR_NODE_DEV_CAP_CCWGROUP_MEMBER:
    case VIR_NODE_DEV_CAP_VPD:
    case VIR_NODE_DEV_CAP_SYSTEM:
    case VIR_NODE_DEV_CAP_FC_HOST:
    case VIR_NODE_DEV_CAP_VPORTS:
    case VIR_NODE_DEV_CAP_LAST:
        break;
    }

    return 0;
}


static int
processNodeDeviceRemoveEvent(virNodeDeviceDriverState *driver_state,
                             const char *path)
{
    virNodeDeviceObj *obj = NULL;
    virNodeDeviceDef *def;
    virObjectEvent *event = NULL;

    if (!(obj = virNodeDeviceObjListFindBySysfsPath(driver_state->devs, path))) {
        VIR_DEBUG("Failed to find device to remove that has udev path '%s'",
                  path);
        return -1;
    }
    def = virNodeDeviceObjGetDef(obj);

    event = virNodeDeviceEventLifecycleNew(def->name,
                                           VIR_NODE_DEVICE_EVENT_DELETED,
                                           0);

    /* If the device is a mediated device that has been 'stopped', it may still
     * be defined by mdevctl and can therefore be started again. Don't drop it
     * from the list of node devices */
    if (virNodeDeviceObjIsPersistent(obj)) {
        VIR_FREE(def->sysfs_path);
        virNodeDeviceObjSetActive(obj, false);
        nodeDeviceDefResetMdevActiveConfig(def);
    } else {
        VIR_DEBUG("Removing device '%s' with sysfs path '%s'",
                  def->name, path);
        virNodeDeviceObjListRemove(driver_state->devs, obj);
    }
    virNodeDeviceObjEndAPI(&obj);

    /* cannot check for mdev_types since they have already been removed */
    if (nodeDeviceUpdateMediatedDevices(driver_state) < 0)
        VIR_WARN("mdevctl failed to update mediated devices");

    virObjectEventStateQueue(driver_state->nodeDeviceEventState, event);
    return 0;
}

static int
udevSetParent(virNodeDeviceDriverState *driver_state,
              struct udev_device *device,
              virNodeDeviceDef *def)
{
    struct udev_device *parent_device = NULL;
    const char *parent_sysfs_path = NULL;
    virNodeDeviceObj *obj = NULL;
    virNodeDeviceDef *objdef;

    parent_device = device;
    do {

        parent_device = udev_device_get_parent(parent_device);
        if (parent_device == NULL)
            break;

        parent_sysfs_path = udev_device_get_syspath(parent_device);
        if (parent_sysfs_path == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Could not get syspath for parent of '%1$s'"),
                           udev_device_get_syspath(parent_device));
            return -1;
        }

        if ((obj = virNodeDeviceObjListFindBySysfsPath(driver_state->devs,
                                                       parent_sysfs_path))) {
            objdef = virNodeDeviceObjGetDef(obj);
            def->parent = g_strdup(objdef->name);
            virNodeDeviceObjEndAPI(&obj);

            def->parent_sysfs_path = g_strdup(parent_sysfs_path);
        }

    } while (def->parent == NULL && parent_device != NULL);

    if (!def->parent)
        def->parent = g_strdup("computer");

    return 0;
}

static int
processNodeDeviceAddAndChangeEvent(virNodeDeviceDriverState *driver_state,
                                   struct udev_device *device)
{
    g_autofree char *sysfs_path = NULL;
    virNodeDeviceDef *def = NULL;
    virNodeDeviceObj *obj = NULL;
    virNodeDeviceDef *objdef;
    virObjectEvent *event = NULL;
    bool new_device = true;
    int ret = -1;
    bool persistent = false;
    bool autostart = false;
    bool is_mdev;
    bool has_mdev_types = false;

    def = g_new0(virNodeDeviceDef, 1);

    def->sysfs_path = g_strdup(udev_device_get_syspath(device));
    /* Create a copy of sysfs_path so it can be safely accessed, even without
     * holding the @obj lock during the VIR_WARN(...) call at the end. */
    sysfs_path = g_strdup(def->sysfs_path);

    udevGetStringProperty(device, "DRIVER", &def->driver);

    def->caps = g_new0(virNodeDevCapsDef, 1);

    if (udevGetDeviceType(device, &def->caps->data.type) != 0)
        goto cleanup;

    if (udevGetDeviceNodes(device, def) != 0)
        goto cleanup;

    if (udevGetDeviceDetails(driver_state, device, def) != 0)
        goto cleanup;

    if (udevSetParent(driver_state, device, def) != 0)
        goto cleanup;

    is_mdev = def->caps->data.type == VIR_NODE_DEV_CAP_MDEV;

    if ((obj = virNodeDeviceObjListFindByName(driver_state->devs, def->name))) {
        objdef = virNodeDeviceObjGetDef(obj);

        if (is_mdev)
            nodeDeviceDefCopyFromMdevctl(def, objdef, true);

        persistent = virNodeDeviceObjIsPersistent(obj);
        autostart = virNodeDeviceObjIsAutostart(obj);

        /* If the device was defined by mdevctl and was never instantiated, it
         * won't have a sysfs path. We need to emit a CREATED event... */
        new_device = (objdef->sysfs_path == NULL);

        virNodeDeviceObjEndAPI(&obj);
    }

    /* If this is a device change, the old definition will be freed
     * and the current definition will take its place. */
    if (!(obj = virNodeDeviceObjListAssignDef(driver_state->devs, def)))
        goto cleanup;
    /* @def is now owned by @obj */
    def = NULL;
    virNodeDeviceObjSetPersistent(obj, persistent);
    virNodeDeviceObjSetAutostart(obj, autostart);
    objdef = virNodeDeviceObjGetDef(obj);

    if (new_device)
        event = virNodeDeviceEventLifecycleNew(objdef->name,
                                               VIR_NODE_DEVICE_EVENT_CREATED,
                                               0);
    else
        event = virNodeDeviceEventUpdateNew(objdef->name);

    virNodeDeviceObjSetActive(obj, true);
    has_mdev_types = virNodeDeviceObjHasCap(obj, VIR_NODE_DEV_CAP_MDEV_TYPES);
    virNodeDeviceObjEndAPI(&obj);

    /* The added mdev needs an immediate active config update before the event
     * is issued so that full device information is available at the time that
     * the 'created' event is emitted. */
    if ((has_mdev_types || is_mdev) && (nodeDeviceUpdateMediatedDevices(driver_state) < 0)) {
        VIR_WARN("Update of mediated device %s failed",
                 NULLSTR_EMPTY(sysfs_path));
    }

    ret = 0;

 cleanup:
    virObjectEventStateQueue(driver_state->nodeDeviceEventState, event);

    if (ret != 0) {
        VIR_DEBUG("Discarding device %d %p %s", ret, def,
                  def ? NULLSTR(def->sysfs_path) : "");
        virNodeDeviceDefFree(def);
    }

    return ret;
}


static int
udevProcessDeviceListEntry(virNodeDeviceDriverState *driver_state,
                           struct udev *udev,
                           struct udev_list_entry *list_entry)
{
    struct udev_device *device;
    const char *name = NULL;
    int ret = -1;

    name = udev_list_entry_get_name(list_entry);

    device = udev_device_new_from_syspath(udev, name);

    if (device != NULL) {
        if (processNodeDeviceAddAndChangeEvent(driver_state, device) != 0) {
            VIR_DEBUG("Failed to create node device for udev device '%s'",
                      name);
        }
        ret = 0;
    }

    udev_device_unref(device);

    return ret;
}


/* We do not care about every device (see udevGetDeviceType).
 * Do not bother enumerating over subsystems that do not
 * contain interesting devices.
 */
const char *subsystem_ignored[] = {
    "acpi", "tty", "vc", "i2c",
};

static int
udevEnumerateAddMatches(struct udev_enumerate *udev_enumerate)
{
    size_t i;

    for (i = 0; i < G_N_ELEMENTS(subsystem_ignored); i++) {
        const char *s = subsystem_ignored[i];
        if (udev_enumerate_add_nomatch_subsystem(udev_enumerate, s) < 0) {
            virReportSystemError(errno, "%s", _("failed to add subsystem filter"));
            return -1;
        }
    }
    return 0;
}


static int
udevEnumerateDevices(virNodeDeviceDriverState *driver_state,
                     struct udev *udev)
{
    struct udev_enumerate *udev_enumerate = NULL;
    struct udev_list_entry *list_entry = NULL;
    int ret = -1;

    udev_enumerate = udev_enumerate_new(udev);
    if (udevEnumerateAddMatches(udev_enumerate) < 0)
        goto cleanup;

    if (udev_enumerate_scan_devices(udev_enumerate) < 0)
        VIR_WARN("udev scan devices failed");

    udev_list_entry_foreach(list_entry,
                            udev_enumerate_get_list_entry(udev_enumerate)) {

        udevProcessDeviceListEntry(driver_state, udev, list_entry);
    }

    ret = 0;
 cleanup:
    udev_enumerate_unref(udev_enumerate);
    return ret;
}


static void
udevPCITranslateDeinit(void)
{
#if defined __s390__ || defined __s390x_
    /* Nothing was initialized, nothing needs to be cleaned up */
#else
    /* pci_system_cleanup returns void */
    pci_system_cleanup();
#endif
    return;
}


static int
nodeStateCleanup(void)
{
    if (!driver)
        return -1;

    virObjectUnref(driver->privateData);
    virObjectUnref(driver->nodeDeviceEventState);

    virNodeDeviceObjListFree(driver->devs);

    if (driver->lockFD != -1)
        virPidFileRelease(driver->stateDir, "driver", driver->lockFD);

    VIR_FREE(driver->stateDir);
    virCondDestroy(&driver->initCond);
    virMutexDestroy(&driver->lock);
    VIR_FREE(driver);

    udevPCITranslateDeinit();
    return 0;
}


static int
udevHandleOneDevice(struct udev_device *device)
{
    const char *action = udev_device_get_action(device);

    VIR_DEBUG("udev action: '%s': %s", action, udev_device_get_syspath(device));

    /* Reference is either released via workerpool logic or at the end of this
     * function. */
    device = udev_device_ref(device);
    if (STREQ(action, "add")) {
        return nodeDeviceEventSubmit(NODE_DEVICE_EVENT_UDEV_ADD, device,
                                     (virFreeCallback)udev_device_unref);
    } else if (STREQ(action, "change")) {
        return nodeDeviceEventSubmit(NODE_DEVICE_EVENT_UDEV_CHANGE, device,
                                     (virFreeCallback)udev_device_unref);
    } else if (STREQ(action, "remove")) {
        return nodeDeviceEventSubmit(NODE_DEVICE_EVENT_UDEV_REMOVE, device,
                                     (virFreeCallback)udev_device_unref);
    } else if (STREQ(action, "move")) {
        return nodeDeviceEventSubmit(NODE_DEVICE_EVENT_UDEV_MOVE, device,
                                     (virFreeCallback)udev_device_unref);
    }
    udev_device_unref(device);

    return 0;
}


/* the caller must be holding the udevEventData object lock prior to calling
 * this function
 */
static bool
udevEventMonitorSanityCheck(udevEventData *priv,
                            int fd)
{
    int rc = -1;

    rc = udev_monitor_get_fd(priv->udev_monitor);
    if (fd != rc) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("File descriptor returned by udev %1$d does not match node device file descriptor %2$d"),
                       fd, rc);

        /* this is a non-recoverable error, let's remove the handle, so that we
         * don't get in here again because of some spurious behaviour and report
         * the same error multiple times
         */
        virEventRemoveHandle(priv->watch);
        priv->watch = -1;

        return false;
    }

    return true;
}


/**
 * udevEventHandleThread
 * @opaque: udevEventData
 *
 * Thread to handle the udevEventHandleCallback processing when udev
 * tells us there's a device change for us (add, modify, delete, etc).
 *
 * Once notified there is data to be processed, the actual @device
 * data retrieval by libudev may be delayed due to how threads are
 * scheduled. In fact, the event loop could be scheduled earlier than
 * the handler thread, thus potentially emitting the very same event
 * the handler thread is currently trying to process, simply because
 * the data hadn't been retrieved from the socket.
 *
 * NB: Some older distros, such as CentOS 6, libudev opens sockets
 * without the NONBLOCK flag which might cause issues with event
 * based algorithm. Although the issue can be mitigated by resetting
 * priv->dataReady for each event found; however, the scheduler issues
 * would still come into play.
 */
static void
udevEventHandleThread(void *opaque)
{
    g_autoptr(udevEventData) priv = opaque;
    struct udev_device *device = NULL;

    /* continue rather than break from the loop on non-fatal errors */
    while (1) {
        VIR_WITH_OBJECT_LOCK_GUARD(priv) {
            while (!priv->udevDataReady && !priv->udevThreadQuit) {
                if (virCondWait(&priv->udevThreadCond, &priv->parent.lock)) {
                    virReportSystemError(errno, "%s",
                                         _("handler failed to wait on condition"));
                    return;
                }
            }

            if (priv->udevThreadQuit)
                return;

            errno = 0;
            device = udev_monitor_receive_device(priv->udev_monitor);
        }

        if (!device) {
            if (errno == 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("failed to receive device from udev monitor"));
                return;
            }

            /* POSIX allows both EAGAIN and EWOULDBLOCK to be used
             * interchangeably when the read would block or timeout was fired.
             * EINVAL might happen on too large udev entries, ignore those for
             * the robustness of udevEventHandleThread.
             */
            VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
            if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINVAL) {
            VIR_WARNINGS_RESET
                virReportSystemError(errno, "%s",
                                     _("failed to receive device from udev monitor"));
                return;
            }

            /* Trying to move the reset of the @priv->dataReady flag to
             * after the udev_monitor_receive_device wouldn't help much
             * due to event mgmt and scheduler timing. */
            VIR_WITH_OBJECT_LOCK_GUARD(priv) {
                priv->udevDataReady = false;
            }

            continue;
        }

        udevHandleOneDevice(device);
        udev_device_unref(device);

        /* Instead of waiting for the next event after processing @device
         * data, let's keep reading from the udev monitor and only wait
         * for the next event once either a EAGAIN or a EWOULDBLOCK error
         * is encountered. */
    }
}


static void
udevEventHandleCallback(int watch G_GNUC_UNUSED,
                        int fd,
                        int events G_GNUC_UNUSED,
                        void *data)
{
    udevEventData *priv = data;
    VIR_LOCK_GUARD lock = virObjectLockGuard(priv);

    if (!udevEventMonitorSanityCheck(priv, fd))
        priv->udevThreadQuit = true;
    else
        priv->udevDataReady = true;

    virCondSignal(&priv->udevThreadCond);
}


/* DMI is intel-compatible specific */
#if defined(__x86_64__) || defined(__i386__) || defined(__amd64__)
static void
udevGetDMIData(virNodeDevCapSystem *syscap)
{
    udevEventData *priv = driver->privateData;
    struct udev *udev = NULL;
    struct udev_device *device = NULL;
    virNodeDevCapSystemHardware *hardware = &syscap->hardware;
    virNodeDevCapSystemFirmware *firmware = &syscap->firmware;

    VIR_WITH_OBJECT_LOCK_GUARD(priv) {
        udev = udev_monitor_get_udev(priv->udev_monitor);

        device = udev_device_new_from_syspath(udev, DMI_DEVPATH);
        if (device == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Failed to get udev device for syspath '%1$s'"),
                           DMI_DEVPATH);
            return;
        }
    }

    udevGetStringSysfsAttr(device, "product_name", &syscap->product_name);
    udevGetStringSysfsAttr(device, "sys_vendor", &hardware->vendor_name);
    udevGetStringSysfsAttr(device, "product_version", &hardware->version);
    udevGetStringSysfsAttr(device, "product_serial", &hardware->serial);

    if (virGetHostUUID(hardware->uuid))
        goto cleanup;

    udevGetStringSysfsAttr(device, "bios_vendor", &firmware->vendor_name);
    udevGetStringSysfsAttr(device, "bios_version", &firmware->version);
    udevGetStringSysfsAttr(device, "bios_date", &firmware->release_date);

 cleanup:
    if (device != NULL)
        udev_device_unref(device);
    return;
}
#endif


static int
udevSetupSystemDev(void)
{
    virNodeDeviceDef *def = NULL;
    virNodeDeviceObj *obj = NULL;
    int ret = -1;

    def = g_new0(virNodeDeviceDef, 1);

    def->name = g_strdup("computer");
    def->caps = g_new0(virNodeDevCapsDef, 1);

#if defined(__x86_64__) || defined(__i386__) || defined(__amd64__)
    udevGetDMIData(&def->caps->data.system);
#endif

    if (!(obj = virNodeDeviceObjListAssignDef(driver->devs, def)))
        goto cleanup;

    /* @def is now owned by @obj */
    def = NULL;
    virNodeDeviceObjSetActive(obj, true);
    virNodeDeviceObjSetAutostart(obj, true);
    virNodeDeviceObjSetPersistent(obj, true);

    virNodeDeviceObjEndAPI(&obj);

    ret = 0;

 cleanup:
    if (ret == -1)
        virNodeDeviceDefFree(def);

    return ret;
}


static void
processNodeStateInitializeEnumerate(virNodeDeviceDriverState *driver_state,
                                    void *opaque)
{
    struct udev *udev = opaque;
    udevEventData *priv = driver_state->privateData;

    /* Populate with known devices */
    if (udevEnumerateDevices(driver_state, udev) != 0)
        goto error;
    /* Load persistent mdevs (which might not be activated yet) and additional
     * information about active mediated devices from mdevctl */
    if (nodeDeviceUpdateMediatedDevices(driver_state) != 0)
        goto error;

 cleanup:
    VIR_WITH_MUTEX_LOCK_GUARD(&driver_state->lock) {
        driver_state->initialized = true;
        virCondBroadcast(&driver_state->initCond);
    }

    return;

 error:
    VIR_WITH_OBJECT_LOCK_GUARD(priv) {
        ignore_value(virEventRemoveHandle(priv->watch));
        priv->watch = -1;
        priv->udevThreadQuit = true;
        virCondSignal(&priv->udevThreadCond);
    }

    goto cleanup;
}


static int
udevPCITranslateInit(bool privileged G_GNUC_UNUSED)
{
#if defined __s390__ || defined __s390x_
    /* On s390(x) system there is no PCI bus.
     * Therefore there is nothing to initialize here. */
#else
    int rc;

    if ((rc = pci_system_init()) != 0) {
        /* Ignore failure as non-root; udev is not as helpful in that
         * situation, but a non-privileged user won't benefit much
         * from udev in the first place.  */
        if (errno != ENOENT && (privileged  || errno != EACCES)) {
            virReportSystemError(rc, "%s",
                                 _("Failed to initialize libpciaccess"));
            return -1;
        }
    }
#endif
    return 0;
}


static void
submitMdevctlUpdate(int timer G_GNUC_UNUSED, void *opaque)
{
    udevEventData *priv = opaque;

    if (priv->mdevctlTimeout != -1) {
        virEventRemoveTimeout(priv->mdevctlTimeout);
        priv->mdevctlTimeout = -1;
    }

    nodeDeviceEventSubmit(NODE_DEVICE_EVENT_MDEVCTL_CONFIG_CHANGED,  NULL, NULL);
}


static void
mdevctlEventHandleCallback(GFileMonitor *monitor G_GNUC_UNUSED,
                           GFile *file,
                           GFile *other_file G_GNUC_UNUSED,
                           GFileMonitorEvent event_type,
                           gpointer user_data);


/* Recursively monitors a directory and its subdirectories for file changes and
 * returns a GList of GFileMonitor objects */
static GList*
monitorFileRecursively(udevEventData *udev,
                       GFile *file)
{
    GList *monitors = NULL;
    g_autoptr(GError) error = NULL;
    g_autoptr(GFileEnumerator) children = NULL;
    GFileMonitor *mon;

    if (!(children = g_file_enumerate_children(file, "standard::*",
                                               G_FILE_QUERY_INFO_NONE, NULL, &error)))
        goto error;

    if (!(mon = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, &error)))
        goto error;

    g_signal_connect(mon, "changed",
                     G_CALLBACK(mdevctlEventHandleCallback), udev);

    monitors = g_list_append(monitors, mon);

    while (true) {
        GFileInfo *info = NULL;
        GFile *child = NULL;
        GList *child_monitors = NULL;

        if (!g_file_enumerator_iterate(children, &info, &child, NULL, &error))
            goto error;

        if (!info)
            break;

        if (g_file_query_file_type(child, G_FILE_QUERY_INFO_NONE, NULL) ==
            G_FILE_TYPE_DIRECTORY) {

            child_monitors = monitorFileRecursively(udev, child);
            if (child_monitors)
                monitors = g_list_concat(monitors, child_monitors);
        }
    }

    return monitors;

 error:
    g_list_free_full(monitors, g_object_unref);
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   _("Unable to monitor directory: %1$s"), error->message);
    g_clear_error(&error);
    return NULL;
}


static int
mdevctlEnableMonitor(udevEventData *priv)
{
    g_autoptr(GFile) mdevctlConfigDir = NULL;
    const char *mdevctlDir = "/etc/mdevctl.d";

    if (!virFileExists(mdevctlDir))
        return 0;

    mdevctlConfigDir = g_file_new_for_path(mdevctlDir);

    /* mdevctl may add notification events in the future:
     * https://github.com/mdevctl/mdevctl/issues/27. For now, fall back to
     * monitoring the mdevctl configuration directory for changes.
     * mdevctl configuration is stored in a directory tree within
     * /etc/mdevctl.d/. There is a directory for each parent device, which
     * contains a file defining each mediated device */
    VIR_WITH_MUTEX_LOCK_GUARD(&priv->mdevctlLock) {
        if (!(priv->mdevctlMonitors = monitorFileRecursively(priv, mdevctlConfigDir)))
            return -1;
    }

    return 0;
}


/* Schedules an mdevctl update for 100ms in the future, canceling any existing
 * timeout that may have been set. In this way, multiple update requests in
 * quick succession can be collapsed into a single update. */
static void
scheduleMdevctlUpdate(udevEventData *data)
{
    if (data->mdevctlTimeout != -1)
        virEventRemoveTimeout(data->mdevctlTimeout);
    data->mdevctlTimeout = virEventAddTimeout(100, submitMdevctlUpdate,
                                              data, NULL);
    if (data->mdevctlTimeout < 0) {
        VIR_WARN("Unable to add mdev update timer");
    }
}


static void
mdevctlEventHandleCallback(GFileMonitor *monitor G_GNUC_UNUSED,
                           GFile *file,
                           GFile *other_file G_GNUC_UNUSED,
                           GFileMonitorEvent event_type,
                           gpointer user_data)
{
    udevEventData *priv = user_data;
    /* if a new directory appears, monitor that directory for changes */
    if (event_type == G_FILE_MONITOR_EVENT_CREATED) {
        GFileType file_type = g_file_query_file_type(file,
                                                     G_FILE_QUERY_INFO_NONE,
                                                     NULL);
        if (file_type == G_FILE_TYPE_DIRECTORY) {
            GList *newmonitors = monitorFileRecursively(priv, file);

            VIR_WITH_MUTEX_LOCK_GUARD(&priv->mdevctlLock) {
                priv->mdevctlMonitors = g_list_concat(priv->mdevctlMonitors,
                                                      newmonitors);
            }
        }
    }

    /* When mdevctl creates a device, it can result in multiple notify events
     * emitted for a single logical change (e.g. several CHANGED events, or a
     * CREATED and CHANGED event followed by CHANGES_DONE_HINT). To avoid
     * spawning a mdevctl thread multiple times for a single logical
     * configuration change, try to coalesce these changes by waiting for the
     * CHANGES_DONE_HINT event. As a fallback,  add a timeout to trigger the
     * signal if that event never comes */
    VIR_WITH_OBJECT_LOCK_GUARD(priv) {
        if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
            submitMdevctlUpdate(-1, priv);
        } else {
            scheduleMdevctlUpdate(priv);
        }
    }
}


static void nodeDeviceEventHandler(void *data, void *opaque)
{
    virNodeDeviceDriverState *driver_state = opaque;
    g_autoptr(nodeDeviceEvent) processEvent = data;

    switch (processEvent->eventType) {
    case NODE_DEVICE_EVENT_INIT:
    {
        struct udev *udev = processEvent->data;

        processNodeStateInitializeEnumerate(driver_state, udev);
    }
    break;
    case NODE_DEVICE_EVENT_UDEV_ADD:
    case NODE_DEVICE_EVENT_UDEV_CHANGE:
    {
        struct udev_device *device = processEvent->data;

        processNodeDeviceAddAndChangeEvent(driver_state, device);
    }
    break;
    case NODE_DEVICE_EVENT_UDEV_REMOVE:
    {
        struct udev_device *device = processEvent->data;
        const char *path = udev_device_get_syspath(device);

        processNodeDeviceRemoveEvent(driver_state, path);
    }
    break;
    case NODE_DEVICE_EVENT_UDEV_MOVE:
    {
        struct udev_device *device = processEvent->data;
        const char *devpath_old = udevGetDeviceProperty(device, "DEVPATH_OLD");

        if (devpath_old) {
            g_autofree char *devpath_old_fixed = g_strdup_printf("/sys%s", devpath_old);

            processNodeDeviceRemoveEvent(driver_state, devpath_old_fixed);
        }

        processNodeDeviceAddAndChangeEvent(driver_state, device);
    }
    break;
    case NODE_DEVICE_EVENT_MDEVCTL_CONFIG_CHANGED:
    {
        if (nodeDeviceUpdateMediatedDevices(driver_state) < 0)
            VIR_WARN("mdevctl failed to update mediated devices");
    }
    break;
    case NODE_DEVICE_EVENT_LAST:
        g_assert_not_reached();
        break;
    }
}


/* Note: It must be safe to call this function even if the driver was not
 *       successfully initialized. This must be considered when changing this
 *       function. */
static int
nodeStateShutdownPrepare(void)
{
    udevEventData *priv = NULL;

    if (!driver)
        return 0;

    priv = driver->privateData;
    if (!priv)
        return 0;

    VIR_WITH_MUTEX_LOCK_GUARD(&priv->mdevctlLock) {
        GList *tmp;
        for (tmp = priv->mdevctlMonitors; tmp; tmp = tmp->next)
            g_signal_handlers_disconnect_by_data(tmp->data, priv);
    }

    VIR_WITH_OBJECT_LOCK_GUARD(priv) {
        if (priv->mdevctlTimeout != -1) {
            virEventRemoveTimeout(priv->mdevctlTimeout);
            priv->mdevctlTimeout = -1;
        }

        if (priv->watch) {
            virEventRemoveHandle(priv->watch);
            priv->watch = -1;
        }

        priv->udevThreadQuit = true;
        virCondSignal(&priv->udevThreadCond);
    }

    if (priv->workerPool)
        virThreadPoolStop(priv->workerPool);
    return 0;
}


/* Note: It must be safe to call this function even if the driver was not
 *       successfully initialized. This must be considered when changing this
 *       function. */
static int
nodeStateShutdownWait(void)
{
    udevEventData *priv = NULL;

    if (!driver)
        return 0;

    priv = driver->privateData;
    if (!priv)
        return 0;

    VIR_WITH_OBJECT_LOCK_GUARD(priv) {
        if (priv->udevThread)
            virThreadJoin(priv->udevThread);
    }

    if (priv->workerPool)
        virThreadPoolDrain(priv->workerPool);
    return 0;
}


static virDrvStateInitResult
nodeStateInitialize(bool privileged,
                    const char *root,
                    bool monolithic G_GNUC_UNUSED,
                    virStateInhibitCallback callback G_GNUC_UNUSED,
                    void *opaque G_GNUC_UNUSED)
{
    udevEventData *priv = NULL;
    struct udev *udev = NULL;

    if (root != NULL) {
        virReportError(VIR_ERR_INVALID_ARG, "%s",
                       _("Driver does not support embedded mode"));
        return -1;
    }

    driver = g_new0(virNodeDeviceDriverState, 1);

    driver->lockFD = -1;
    if (virMutexInit(&driver->lock) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to initialize mutex"));
        VIR_FREE(driver);
        return VIR_DRV_STATE_INIT_ERROR;
    }
    if (virCondInit(&driver->initCond) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to initialize condition variable"));
        virMutexDestroy(&driver->lock);
        VIR_FREE(driver);
        return VIR_DRV_STATE_INIT_ERROR;
    }

    driver->privileged = privileged;

    if (privileged) {
        driver->stateDir = g_strdup_printf("%s/libvirt/nodedev", RUNSTATEDIR);
    } else {
        g_autofree char *rundir = NULL;

        rundir = virGetUserRuntimeDirectory();
        driver->stateDir = g_strdup_printf("%s/nodedev/run", rundir);
    }

    if (g_mkdir_with_parents(driver->stateDir, S_IRWXU) < 0) {
        virReportSystemError(errno, _("cannot create state directory '%1$s'"),
                             driver->stateDir);
        goto cleanup;
    }

    if ((driver->lockFD =
         virPidFileAcquire(driver->stateDir, "driver", getpid())) < 0)
        goto cleanup;

    if (!(driver->devs = virNodeDeviceObjListNew()) ||
        !(priv = udevEventDataNew()))
        goto cleanup;

    virObjectLock(priv);

    driver->privateData = priv;
    driver->nodeDeviceEventState = virObjectEventStateNew();

    driver->parserCallbacks.postParse = nodeDeviceDefPostParse;
    driver->parserCallbacks.validate = nodeDeviceDefValidate;

    /* With the current design, we can only have exactly *one* worker thread as
     * otherwise we cannot guarantee that the 'order(udev_events) ==
     * order(nodedev_events)' is preserved. The worker pool must be initialized
     * before trying to reconnect to all the running mdevs since there might
     * occur some mdevctl monitor events that will be dispatched to the worker
     * pool. */
    priv->workerPool = virThreadPoolNewFull(1, 1, 0, nodeDeviceEventHandler,
                                            "nodev-device-event",
                                            NULL,
                                            driver);
    if (!priv->workerPool)
        goto unlock;

    if (udevPCITranslateInit(privileged) < 0)
        goto unlock;

    udev = udev_new();
    if (!udev) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("failed to create udev context"));
        goto unlock;
    }

    priv->udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
    if (!priv->udev_monitor) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("udev_monitor_new_from_netlink returned NULL"));
        goto unlock;
    }

    udev_monitor_enable_receiving(priv->udev_monitor);

    /* mimic udevd's behaviour and override the systems rmem_max limit in case
     * there's a significant number of device 'add' events
     */
    if (geteuid() == 0)
        udev_monitor_set_receive_buffer_size(priv->udev_monitor,
                                             128 * 1024 * 1024);

    priv->udevThread = g_new0(virThread, 1);
    if (virThreadCreateFull(priv->udevThread, true, udevEventHandleThread,
                            "udev-event", false, virObjectRef(priv)) < 0) {
        virReportSystemError(errno, "%s",
                             _("failed to create udev handler thread"));
        goto unlock;
    }

    /* We register the monitor with the event callback so we are
     * notified by udev of device changes before we enumerate existing
     * devices because libvirt will simply recreate the device if we
     * try to register it twice, i.e., if the device appears between
     * the time we register the callback and the time we begin
     * enumeration.  The alternative is to register the callback after
     * we enumerate, in which case we will fail to create any devices
     * that appear while the enumeration is taking place.  */
    priv->watch = virEventAddHandle(udev_monitor_get_fd(priv->udev_monitor),
                                    VIR_EVENT_HANDLE_READABLE,
                                    udevEventHandleCallback, virObjectRef(priv), virObjectUnref);
    if (priv->watch == -1) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unable to add watch on udev FD %1$d"),
                       udev_monitor_get_fd(priv->udev_monitor));
        goto unlock;
    }

    if (mdevctlEnableMonitor(priv) < 0)
        goto unlock;

    virObjectUnlock(priv);

    /* Create a fictional 'computer' device to root the device tree. */
    if (udevSetupSystemDev() != 0)
        goto cleanup;

    nodeDeviceEventSubmit(NODE_DEVICE_EVENT_INIT, udev_ref(udev), (virFreeCallback)udev_unref);

    return VIR_DRV_STATE_INIT_COMPLETE;

 cleanup:
    nodeStateShutdownPrepare();
    nodeStateShutdownWait();
    nodeStateCleanup();
    return VIR_DRV_STATE_INIT_ERROR;

 unlock:
    virObjectUnlock(priv);
    goto cleanup;
}


static int
nodeStateReload(void)
{
    return 0;
}


static virNodeDeviceDriver udevNodeDeviceDriver = {
    .name = "udev",
    .nodeNumOfDevices = nodeNumOfDevices, /* 0.7.3 */
    .nodeListDevices = nodeListDevices, /* 0.7.3 */
    .connectListAllNodeDevices = nodeConnectListAllNodeDevices, /* 0.10.2 */
    .connectNodeDeviceEventRegisterAny = nodeConnectNodeDeviceEventRegisterAny, /* 2.2.0 */
    .connectNodeDeviceEventDeregisterAny = nodeConnectNodeDeviceEventDeregisterAny, /* 2.2.0 */
    .nodeDeviceLookupByName = nodeDeviceLookupByName, /* 0.7.3 */
    .nodeDeviceLookupSCSIHostByWWN = nodeDeviceLookupSCSIHostByWWN, /* 1.0.2 */
    .nodeDeviceGetXMLDesc = nodeDeviceGetXMLDesc, /* 0.7.3 */
    .nodeDeviceGetParent = nodeDeviceGetParent, /* 0.7.3 */
    .nodeDeviceNumOfCaps = nodeDeviceNumOfCaps, /* 0.7.3 */
    .nodeDeviceListCaps = nodeDeviceListCaps, /* 0.7.3 */
    .nodeDeviceCreateXML = nodeDeviceCreateXML, /* 0.7.3 */
    .nodeDeviceDestroy = nodeDeviceDestroy, /* 0.7.3 */
    .nodeDeviceDefineXML = nodeDeviceDefineXML, /* 7.3.0 */
    .nodeDeviceUndefine = nodeDeviceUndefine, /* 7.3.0 */
    .nodeDeviceCreate = nodeDeviceCreate, /* 7.3.0 */
    .nodeDeviceSetAutostart = nodeDeviceSetAutostart, /* 7.8.0 */
    .nodeDeviceGetAutostart = nodeDeviceGetAutostart, /* 7.8.0 */
    .nodeDeviceIsPersistent = nodeDeviceIsPersistent, /* 7.8.0 */
    .nodeDeviceIsActive = nodeDeviceIsActive, /* 7.8.0 */
    .nodeDeviceUpdate = nodeDeviceUpdate, /* 10.1.0 */
};


static virHypervisorDriver udevHypervisorDriver = {
    .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 udevConnectDriver = {
    .localOnly = true,
    .uriSchemes = (const char *[]){ "nodedev", NULL },
    .hypervisorDriver = &udevHypervisorDriver,
    .nodeDeviceDriver = &udevNodeDeviceDriver,
};


static virStateDriver udevStateDriver = {
    .name = "udev",
    .stateInitialize = nodeStateInitialize, /* 0.7.3 */
    .stateCleanup = nodeStateCleanup, /* 0.7.3 */
    .stateReload = nodeStateReload, /* 0.7.3 */
    .stateShutdownPrepare = nodeStateShutdownPrepare, /* 10.3.0 */
    .stateShutdownWait = nodeStateShutdownWait, /* 10.3.0 */
};


int
udevNodeRegister(void)
{
    VIR_DEBUG("Registering udev node device backend");

    if (virRegisterConnectDriver(&udevConnectDriver, false) < 0)
        return -1;
    if (virSetSharedNodeDeviceDriver(&udevNodeDeviceDriver) < 0)
        return -1;

    return virRegisterStateDriver(&udevStateDriver);
}
