/*
 * node_device_conf.c: config handling for node devices
 *
 * Copyright (C) 2008 Virtual Iron Software, Inc.
 * Copyright (C) 2008 David F. Lively
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Author: David F. Lively <dlively@virtualiron.com>
 */

#include <config.h>

#include <unistd.h>
#include <errno.h>

#include "virterror_internal.h"
#include "datatypes.h"
#include "memory.h"

#include "node_device_conf.h"
#include "memory.h"
#include "xml.h"
#include "util.h"
#include "buf.h"
#include "uuid.h"

#define VIR_FROM_THIS VIR_FROM_NODEDEV

VIR_ENUM_IMPL(virNodeDevCap, VIR_NODE_DEV_CAP_LAST,
              "system",
              "pci",
              "usb_device",
              "usb",
              "net",
              "scsi_host",
              "scsi",
              "storage")

VIR_ENUM_IMPL(virNodeDevNetCap, VIR_NODE_DEV_CAP_NET_LAST,
              "80203",
              "80211")

VIR_ENUM_IMPL(virNodeDevHBACap, VIR_NODE_DEV_CAP_HBA_LAST,
              "fc_host",
              "vport_ops")


static int
virNodeDevCapsDefParseString(virConnectPtr conn,
                             const char *xpath,
                             xmlXPathContextPtr ctxt,
                             char **string,
                             virNodeDeviceDefPtr def,
                             const char *missing_error_fmt)
{
    char *s;

    s = virXPathString(conn, xpath, ctxt);
    if (s == NULL) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 missing_error_fmt,
                                 def->name);
        return -1;
    }

    *string = s;
    return 0;
}


virNodeDeviceObjPtr virNodeDeviceFindByName(const virNodeDeviceObjListPtr devs,
                                            const char *name)
{
    unsigned int i;

    for (i = 0; i < devs->count; i++) {
        virNodeDeviceObjLock(devs->objs[i]);
        if (STREQ(devs->objs[i]->def->name, name))
            return devs->objs[i];
        virNodeDeviceObjUnlock(devs->objs[i]);
    }

    return NULL;
}


void virNodeDeviceDefFree(virNodeDeviceDefPtr def)
{
    virNodeDevCapsDefPtr caps;

    if (!def)
        return;

    VIR_FREE(def->name);
    VIR_FREE(def->parent);
    VIR_FREE(def->driver);

    caps = def->caps;
    while (caps) {
        virNodeDevCapsDefPtr next = caps->next;
        virNodeDevCapsDefFree(caps);
        caps = next;
    }

    VIR_FREE(def);
}

void virNodeDeviceObjFree(virNodeDeviceObjPtr dev)
{
    if (!dev)
        return;

    VIR_FREE(dev->devicePath);
    virNodeDeviceDefFree(dev->def);
    if (dev->privateFree)
        (*dev->privateFree)(dev->privateData);

    virMutexDestroy(&dev->lock);

    VIR_FREE(dev);
}

void virNodeDeviceObjListFree(virNodeDeviceObjListPtr devs)
{
    unsigned int i;
    for (i = 0 ; i < devs->count ; i++)
        virNodeDeviceObjFree(devs->objs[i]);
    VIR_FREE(devs->objs);
    devs->count = 0;
}

virNodeDeviceObjPtr virNodeDeviceAssignDef(virConnectPtr conn,
                                           virNodeDeviceObjListPtr devs,
                                           const virNodeDeviceDefPtr def)
{
    virNodeDeviceObjPtr device;

    if ((device = virNodeDeviceFindByName(devs, def->name))) {
        virNodeDeviceDefFree(device->def);
        device->def = def;
        return device;
    }

    if (VIR_ALLOC(device) < 0) {
        virReportOOMError(conn);
        return NULL;
    }

    if (virMutexInit(&device->lock) < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 "%s", _("cannot initialize mutex"));
        VIR_FREE(device);
        return NULL;
    }
    virNodeDeviceObjLock(device);
    device->def = def;

    if (VIR_REALLOC_N(devs->objs, devs->count+1) < 0) {
        device->def = NULL;
        virNodeDeviceObjUnlock(device);
        virNodeDeviceObjFree(device);
        virReportOOMError(conn);
        return NULL;
    }
    devs->objs[devs->count++] = device;

    return device;

}

void virNodeDeviceObjRemove(virNodeDeviceObjListPtr devs,
                            const virNodeDeviceObjPtr dev)
{
    unsigned int i;

    virNodeDeviceObjUnlock(dev);

    for (i = 0; i < devs->count; i++) {
        virNodeDeviceObjLock(dev);
        if (devs->objs[i] == dev) {
            virNodeDeviceObjUnlock(dev);
            virNodeDeviceObjFree(devs->objs[i]);

            if (i < (devs->count - 1))
                memmove(devs->objs + i, devs->objs + i + 1,
                        sizeof(*(devs->objs)) * (devs->count - (i + 1)));

            if (VIR_REALLOC_N(devs->objs, devs->count - 1) < 0) {
                ; /* Failure to reduce memory allocation isn't fatal */
            }
            devs->count--;

            break;
        }
        virNodeDeviceObjUnlock(dev);
    }
}

char *virNodeDeviceDefFormat(virConnectPtr conn,
                             const virNodeDeviceDefPtr def)
{
    virBuffer buf = VIR_BUFFER_INITIALIZER;
    virNodeDevCapsDefPtr caps = def->caps;
    char *tmp;

    virBufferAddLit(&buf, "<device>\n");
    virBufferEscapeString(&buf, "  <name>%s</name>\n", def->name);

    if (def->parent)
        virBufferEscapeString(&buf, "  <parent>%s</parent>\n", def->parent);
    if (def->driver) {
        virBufferAddLit(&buf, "  <driver>\n");
        virBufferEscapeString(&buf, "    <name>%s</name>\n", def->driver);
        virBufferAddLit(&buf, "  </driver>\n");
    }

    for (caps = def->caps; caps; caps = caps->next) {
        char uuidstr[VIR_UUID_STRING_BUFLEN];
        union _virNodeDevCapData *data = &caps->data;

        virBufferVSprintf(&buf, "  <capability type='%s'>\n",
                          virNodeDevCapTypeToString(caps->type));
        switch (caps->type) {
        case VIR_NODE_DEV_CAP_SYSTEM:
            if (data->system.product_name)
                virBufferEscapeString(&buf, "    <product>%s</product>\n",
                                      data->system.product_name);
            virBufferAddLit(&buf, "    <hardware>\n");
            if (data->system.hardware.vendor_name)
                virBufferEscapeString(&buf, "      <vendor>%s</vendor>\n",
                                      data->system.hardware.vendor_name);
            if (data->system.hardware.version)
                virBufferEscapeString(&buf, "      <version>%s</version>\n",
                                      data->system.hardware.version);
            if (data->system.hardware.serial)
                virBufferEscapeString(&buf, "      <serial>%s</serial>\n",
                                      data->system.hardware.serial);
            virUUIDFormat(data->system.hardware.uuid, uuidstr);
            virBufferVSprintf(&buf, "      <uuid>%s</uuid>\n", uuidstr);
            virBufferAddLit(&buf, "    </hardware>\n");
            virBufferAddLit(&buf, "    <firmware>\n");
            if (data->system.firmware.vendor_name)
                virBufferEscapeString(&buf, "      <vendor>%s</vendor>\n",
                                      data->system.firmware.vendor_name);
            if (data->system.firmware.version)
                virBufferEscapeString(&buf, "      <version>%s</version>\n",
                                      data->system.firmware.version);
            if (data->system.firmware.release_date)
                virBufferEscapeString(&buf,
                                      "      <release_date>%s</release_date>\n",
                                      data->system.firmware.release_date);
            virBufferAddLit(&buf, "    </firmware>\n");
            break;
        case VIR_NODE_DEV_CAP_PCI_DEV:
            virBufferVSprintf(&buf, "    <domain>%d</domain>\n",
                              data->pci_dev.domain);
            virBufferVSprintf(&buf, "    <bus>%d</bus>\n", data->pci_dev.bus);
            virBufferVSprintf(&buf, "    <slot>%d</slot>\n",
                              data->pci_dev.slot);
            virBufferVSprintf(&buf, "    <function>%d</function>\n",
                              data->pci_dev.function);
            virBufferVSprintf(&buf, "    <product id='0x%04x'",
                                  data->pci_dev.product);
            if (data->pci_dev.product_name)
                virBufferEscapeString(&buf, ">%s</product>\n",
                                      data->pci_dev.product_name);
            else
                virBufferAddLit(&buf, " />\n");
            virBufferVSprintf(&buf, "    <vendor id='0x%04x'",
                                  data->pci_dev.vendor);
            if (data->pci_dev.vendor_name)
                virBufferEscapeString(&buf, ">%s</vendor>\n",
                                      data->pci_dev.vendor_name);
            else
                virBufferAddLit(&buf, " />\n");
            break;
        case VIR_NODE_DEV_CAP_USB_DEV:
            virBufferVSprintf(&buf, "    <bus>%d</bus>\n", data->usb_dev.bus);
            virBufferVSprintf(&buf, "    <device>%d</device>\n",
                              data->usb_dev.device);
            virBufferVSprintf(&buf, "    <product id='0x%04x'",
                                  data->usb_dev.product);
            if (data->usb_dev.product_name)
                virBufferEscapeString(&buf, ">%s</product>\n",
                                      data->usb_dev.product_name);
            else
                virBufferAddLit(&buf, " />\n");
            virBufferVSprintf(&buf, "    <vendor id='0x%04x'",
                                  data->usb_dev.vendor);
            if (data->usb_dev.vendor_name)
                virBufferEscapeString(&buf, ">%s</vendor>\n",
                                      data->usb_dev.vendor_name);
            else
                virBufferAddLit(&buf, " />\n");
            break;
        case VIR_NODE_DEV_CAP_USB_INTERFACE:
            virBufferVSprintf(&buf, "    <number>%d</number>\n",
                              data->usb_if.number);
            virBufferVSprintf(&buf, "    <class>%d</class>\n",
                              data->usb_if._class);
            virBufferVSprintf(&buf, "    <subclass>%d</subclass>\n",
                              data->usb_if.subclass);
            virBufferVSprintf(&buf, "    <protocol>%d</protocol>\n",
                              data->usb_if.protocol);
            if (data->usb_if.description)
                virBufferVSprintf(&buf, "    <description>%s</description>\n",
                                  data->usb_if.description);
            break;
        case VIR_NODE_DEV_CAP_NET:
            virBufferVSprintf(&buf, "    <interface>%s</interface>\n",
                              data->net.ifname);
            if (data->net.address)
                virBufferVSprintf(&buf, "    <address>%s</address>\n",
                                  data->net.address);
            if (data->net.subtype != VIR_NODE_DEV_CAP_NET_LAST) {
                const char *subtyp =
                    virNodeDevNetCapTypeToString(data->net.subtype);
                virBufferVSprintf(&buf, "    <capability type='%s'/>\n", subtyp);
            }
            break;
        case VIR_NODE_DEV_CAP_SCSI_HOST:
            virBufferVSprintf(&buf, "    <host>%d</host>\n",
                              data->scsi_host.host);
            if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) {
                virBufferAddLit(&buf, "    <capability type='fc_host'>\n");
                virBufferVSprintf(&buf,
                                  "      <wwnn>%s</wwnn>\n", data->scsi_host.wwnn);
                virBufferVSprintf(&buf,
                                  "      <wwpn>%s</wwpn>\n", data->scsi_host.wwpn);
                virBufferAddLit(&buf, "    </capability>\n");
            }
            if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS) {
                virBufferAddLit(&buf, "    <capability type='vport_ops' />\n");
            }

            break;
        case VIR_NODE_DEV_CAP_SCSI:
            virBufferVSprintf(&buf, "    <host>%d</host>\n", data->scsi.host);
            virBufferVSprintf(&buf, "    <bus>%d</bus>\n", data->scsi.bus);
            virBufferVSprintf(&buf, "    <target>%d</target>\n",
                              data->scsi.target);
            virBufferVSprintf(&buf, "    <lun>%d</lun>\n", data->scsi.lun);
            if (data->scsi.type)
                virBufferVSprintf(&buf, "    <type>%s</type>\n",
                                  data->scsi.type);
            break;
        case VIR_NODE_DEV_CAP_STORAGE:
            virBufferVSprintf(&buf, "    <block>%s</block>\n",
                              data->storage.block);
            if (data->storage.bus)
                virBufferVSprintf(&buf, "    <bus>%s</bus>\n",
                                  data->storage.bus);
            if (data->storage.drive_type)
                virBufferVSprintf(&buf, "    <drive_type>%s</drive_type>\n",
                                  data->storage.drive_type);
            if (data->storage.model)
                virBufferVSprintf(&buf, "    <model>%s</model>\n",
                                  data->storage.model);
            if (data->storage.vendor)
                virBufferVSprintf(&buf, "    <vendor>%s</vendor>\n",
                                  data->storage.vendor);
            if (data->storage.serial)
                virBufferVSprintf(&buf, "    <serial>%s</serial>\n",
                                  data->storage.serial);
            if (data->storage.flags & VIR_NODE_DEV_CAP_STORAGE_REMOVABLE) {
                int avl = data->storage.flags &
                    VIR_NODE_DEV_CAP_STORAGE_REMOVABLE_MEDIA_AVAILABLE;
                virBufferAddLit(&buf, "    <capability type='removable'>\n");
                virBufferVSprintf(&buf,
                                  "      <media_available>%d"
                                  "</media_available>\n", avl ? 1 : 0);
                virBufferVSprintf(&buf, "      <media_size>%llu</media_size>\n",
                                  data->storage.removable_media_size);
                virBufferAddLit(&buf, "    </capability>\n");
            } else {
                virBufferVSprintf(&buf, "    <size>%llu</size>\n",
                                  data->storage.size);
            }
            if (data->storage.flags & VIR_NODE_DEV_CAP_STORAGE_HOTPLUGGABLE)
                virBufferAddLit(&buf,
                                "    <capability type='hotpluggable' />\n");
            break;
        case VIR_NODE_DEV_CAP_LAST:
            /* ignore special LAST value */
            break;
        }

        virBufferAddLit(&buf, "  </capability>\n");
    }

    virBufferAddLit(&buf, "</device>\n");

    if (virBufferError(&buf))
        goto no_memory;

    return virBufferContentAndReset(&buf);

 no_memory:
    virReportOOMError(conn);
    tmp = virBufferContentAndReset(&buf);
    VIR_FREE(tmp);
    return NULL;
}

static int
virNodeDevCapsDefParseULong(virConnectPtr conn,
                            const char *xpath,
                            xmlXPathContextPtr ctxt,
                            unsigned *value,
                            virNodeDeviceDefPtr def,
                            const char *missing_error_fmt,
                            const char *invalid_error_fmt)
{
    int ret;
    unsigned long val;

    ret = virXPathULong(conn, xpath, ctxt, &val);
    if (ret < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 ret == -1 ? missing_error_fmt : invalid_error_fmt,
                                 def->name);
        return -1;
    }

    *value = val;
    return 0;
}

static int
virNodeDevCapsDefParseULongLong(virConnectPtr conn,
                                const char *xpath,
                                xmlXPathContextPtr ctxt,
                                unsigned long long *value,
                                virNodeDeviceDefPtr def,
                                const char *missing_error_fmt,
                                const char *invalid_error_fmt)
{
    int ret;
    unsigned long long val;

    ret = virXPathULongLong(conn, xpath, ctxt, &val);
    if (ret < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 ret == -1 ? missing_error_fmt : invalid_error_fmt,
                                 def->name);
        return -1;
    }

    *value = val;
    return 0;
}

static int
virNodeDevCapStorageParseXML(virConnectPtr conn,
                             xmlXPathContextPtr ctxt,
                             virNodeDeviceDefPtr def,
                             xmlNodePtr node,
                             union _virNodeDevCapData *data)
{
    xmlNodePtr orignode, *nodes = NULL;
    int i, n, ret = -1;
    unsigned long long val;

    orignode = ctxt->node;
    ctxt->node = node;

    data->storage.block = virXPathString(conn, "string(./block[1])", ctxt);
    if (!data->storage.block) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("no block device path supplied for '%s'"),
                                 def->name);
        goto out;
    }

    data->storage.bus        = virXPathString(conn, "string(./bus[1])", ctxt);
    data->storage.drive_type = virXPathString(conn, "string(./drive_type[1])", ctxt);
    data->storage.model      = virXPathString(conn, "string(./model[1])", ctxt);
    data->storage.vendor     = virXPathString(conn, "string(./vendor[1])", ctxt);
    data->storage.serial     = virXPathString(conn, "string(./serial[1])", ctxt);

    if ((n = virXPathNodeSet(conn, "./capability", ctxt, &nodes)) < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("error parsing storage capabilities for '%s'"),
                                 def->name);
        goto out;
    }

    for (i = 0 ; i < n ; i++) {
        char *type = virXMLPropString(nodes[i], "type");

        if (!type) {
            virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                     _("missing storage capability type for '%s'"),
                                     def->name);
            goto out;
        }

        if (STREQ(type, "hotpluggable"))
            data->storage.flags |= VIR_NODE_DEV_CAP_STORAGE_HOTPLUGGABLE;
        else if (STREQ(type, "removable")) {
            xmlNodePtr orignode2;

            data->storage.flags |= VIR_NODE_DEV_CAP_STORAGE_REMOVABLE;

            orignode2 = ctxt->node;
            ctxt->node = nodes[i];

            if (virXPathBoolean(conn, "count(./media_available[. = '1']) > 0", ctxt))
                data->storage.flags |= VIR_NODE_DEV_CAP_STORAGE_REMOVABLE_MEDIA_AVAILABLE;

            val = 0;
            if (virNodeDevCapsDefParseULongLong(conn, "number(./media_size[1])", ctxt, &val, def,
                                                _("no removable media size supplied for '%s'"),
                                                _("invalid removable media size supplied for '%s'")) < 0) {
                ctxt->node = orignode2;
                VIR_FREE(type);
                goto out;
            }
            data->storage.removable_media_size = val;

            ctxt->node = orignode2;
        } else {
            virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                     _("unknown storage capability type '%s' for '%s'"),
                                     type, def->name);
            VIR_FREE(type);
            goto out;
        }

        VIR_FREE(type);
    }

    if (!(data->storage.flags & VIR_NODE_DEV_CAP_STORAGE_REMOVABLE)) {
        val = 0;
        if (virNodeDevCapsDefParseULongLong(conn, "number(./size[1])", ctxt, &val, def,
                                            _("no size supplied for '%s'"),
                                            _("invalid size supplied for '%s'")) < 0)
            goto out;
        data->storage.size = val;
    }

    ret = 0;
out:
    VIR_FREE(nodes);
    ctxt->node = orignode;
    return ret;
}

static int
virNodeDevCapScsiParseXML(virConnectPtr conn,
                          xmlXPathContextPtr ctxt,
                          virNodeDeviceDefPtr def,
                          xmlNodePtr node,
                          union _virNodeDevCapData *data)
{
    xmlNodePtr orignode;
    int ret = -1;

    orignode = ctxt->node;
    ctxt->node = node;

    if (virNodeDevCapsDefParseULong(conn, "number(./host[1])", ctxt,
                                    &data->scsi.host, def,
                                    _("no SCSI host ID supplied for '%s'"),
                                    _("invalid SCSI host ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./bus[1])", ctxt,
                                    &data->scsi.bus, def,
                                    _("no SCSI bus ID supplied for '%s'"),
                                    _("invalid SCSI bus ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./target[1])", ctxt,
                                    &data->scsi.target, def,
                                    _("no SCSI target ID supplied for '%s'"),
                                    _("invalid SCSI target ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./lun[1])", ctxt,
                                    &data->scsi.lun, def,
                                    _("no SCSI LUN ID supplied for '%s'"),
                                    _("invalid SCSI LUN ID supplied for '%s'")) < 0)
        goto out;

    data->scsi.type = virXPathString(conn, "string(./type[1])", ctxt);

    ret = 0;
out:
    ctxt->node = orignode;
    return ret;
}

static int
virNodeDevCapScsiHostParseXML(virConnectPtr conn,
                              xmlXPathContextPtr ctxt,
                              virNodeDeviceDefPtr def,
                              xmlNodePtr node,
                              union _virNodeDevCapData *data,
                              int create)
{
    xmlNodePtr orignode, *nodes = NULL;
    int ret = -1, n = 0, i;
    char *type = NULL;

    orignode = ctxt->node;
    ctxt->node = node;

    if (create == EXISTING_DEVICE &&
        virNodeDevCapsDefParseULong(conn, "number(./host[1])", ctxt,
                                    &data->scsi_host.host, def,
                                    _("no SCSI host ID supplied for '%s'"),
                                    _("invalid SCSI host ID supplied for '%s'")) < 0) {
        goto out;
    }

    if ((n = virXPathNodeSet(conn, "./capability", ctxt, &nodes)) < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("error parsing SCSI host capabilities for '%s'"),
                                 def->name);
        goto out;
    }

    for (i = 0 ; i < n ; i++) {
        type = virXMLPropString(nodes[i], "type");

        if (!type) {
            virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                     _("missing SCSI host capability type for '%s'"),
                                     def->name);
            goto out;
        }

        if (STREQ(type, "vport_ops")) {

            data->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS;

        } else if (STREQ(type, "fc_host")) {

            xmlNodePtr orignode2;

            data->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST;

            orignode2 = ctxt->node;
            ctxt->node = nodes[i];

            if (virNodeDevCapsDefParseString(conn, "string(./wwnn[1])",
                                             ctxt,
                                             &data->scsi_host.wwnn,
                                             def,
                                             _("no WWNN supplied for '%s'")) < 0) {
                goto out;
            }

            if (virNodeDevCapsDefParseString(conn, "string(./wwpn[1])",
                                             ctxt,
                                             &data->scsi_host.wwpn,
                                             def,
                                             _("no WWPN supplied for '%s'")) < 0) {
                goto out;
            }

            ctxt->node = orignode2;

        } else {
            virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                     _("unknown SCSI host capability type '%s' for '%s'"),
                                     type, def->name);
            goto out;
        }

        VIR_FREE(type);
    }

    ret = 0;

out:
    VIR_FREE(type);
    ctxt->node = orignode;
    return ret;
}


static int
virNodeDevCapNetParseXML(virConnectPtr conn,
                         xmlXPathContextPtr ctxt,
                         virNodeDeviceDefPtr def,
                         xmlNodePtr node,
                         union _virNodeDevCapData *data)
{
    xmlNodePtr orignode;
    int ret = -1;
    char *tmp;

    orignode = ctxt->node;
    ctxt->node = node;

    data->net.ifname = virXPathString(conn, "string(./interface[1])", ctxt);
    if (!data->net.ifname) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("no network interface supplied for '%s'"),
                                 def->name);
        goto out;
    }

    data->net.address = virXPathString(conn, "string(./address[1])", ctxt);

    data->net.subtype = VIR_NODE_DEV_CAP_NET_LAST;

    tmp = virXPathString(conn, "string(./capability/@type)", ctxt);
    if (tmp) {
        int val = virNodeDevNetCapTypeFromString(tmp);
        VIR_FREE(tmp);
        if (val < 0) {
            virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                     _("invalid network type supplied for '%s'"),
                                     def->name);
            goto out;
        }
        data->net.subtype = val;
    }

    ret = 0;
out:
    ctxt->node = orignode;
    return ret;
}

static int
virNodeDevCapUsbInterfaceParseXML(virConnectPtr conn,
                                  xmlXPathContextPtr ctxt,
                                  virNodeDeviceDefPtr def,
                                  xmlNodePtr node,
                                  union _virNodeDevCapData *data)
{
    xmlNodePtr orignode;
    int ret = -1;

    orignode = ctxt->node;
    ctxt->node = node;

    if (virNodeDevCapsDefParseULong(conn, "number(./number[1])", ctxt,
                                    &data->usb_if.number, def,
                                    _("no USB interface number supplied for '%s'"),
                                    _("invalid USB interface number supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./class[1])", ctxt,
                                    &data->usb_if._class, def,
                                    _("no USB interface class supplied for '%s'"),
                                    _("invalid USB interface class supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./subclass[1])", ctxt,
                                    &data->usb_if.subclass, def,
                                    _("no USB interface subclass supplied for '%s'"),
                                    _("invalid USB interface subclass supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./protocol[1])", ctxt,
                                    &data->usb_if.protocol, def,
                                    _("no USB interface protocol supplied for '%s'"),
                                    _("invalid USB interface protocol supplied for '%s'")) < 0)
        goto out;

    data->usb_if.description = virXPathString(conn, "string(./description[1])", ctxt);

    ret = 0;
out:
    ctxt->node = orignode;
    return ret;
}

static int
virNodeDevCapsDefParseHexId(virConnectPtr conn,
                            const char *xpath,
                            xmlXPathContextPtr ctxt,
                            unsigned *value,
                            virNodeDeviceDefPtr def,
                            const char *missing_error_fmt,
                            const char *invalid_error_fmt)
{
    int ret;
    unsigned long val;

    ret = virXPathULongHex(conn, xpath, ctxt, &val);
    if (ret < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 ret == -1 ? missing_error_fmt : invalid_error_fmt,
                                 def->name);
        return -1;
    }

    *value = val;
    return 0;
}

static int
virNodeDevCapUsbDevParseXML(virConnectPtr conn,
                            xmlXPathContextPtr ctxt,
                            virNodeDeviceDefPtr def,
                            xmlNodePtr node,
                            union _virNodeDevCapData *data)
{
    xmlNodePtr orignode;
    int ret = -1;

    orignode = ctxt->node;
    ctxt->node = node;

    if (virNodeDevCapsDefParseULong(conn, "number(./bus[1])", ctxt,
                                    &data->usb_dev.bus, def,
                                    _("no USB bus number supplied for '%s'"),
                                    _("invalid USB bus number supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./device[1])", ctxt,
                                    &data->usb_dev.device, def,
                                    _("no USB device number supplied for '%s'"),
                                    _("invalid USB device number supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseHexId(conn, "string(./vendor[1]/@id)", ctxt,
                                    &data->usb_dev.vendor, def,
                                    _("no USB vendor ID supplied for '%s'"),
                                    _("invalid USB vendor ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseHexId(conn, "string(./product[1]/@id)", ctxt,
                                    &data->usb_dev.product, def,
                                    _("no USB product ID supplied for '%s'"),
                                    _("invalid USB product ID supplied for '%s'")) < 0)
        goto out;

    data->usb_dev.vendor_name  = virXPathString(conn, "string(./vendor[1])", ctxt);
    data->usb_dev.product_name = virXPathString(conn, "string(./product[1])", ctxt);

    ret = 0;
out:
    ctxt->node = orignode;
    return ret;
}

static int
virNodeDevCapPciDevParseXML(virConnectPtr conn,
                            xmlXPathContextPtr ctxt,
                            virNodeDeviceDefPtr def,
                            xmlNodePtr node,
                            union _virNodeDevCapData *data)
{
    xmlNodePtr orignode;
    int ret = -1;

    orignode = ctxt->node;
    ctxt->node = node;

    if (virNodeDevCapsDefParseULong(conn, "number(./domain[1])", ctxt,
                                    &data->pci_dev.domain, def,
                                    _("no PCI domain ID supplied for '%s'"),
                                    _("invalid PCI domain ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./bus[1])", ctxt,
                                    &data->pci_dev.bus, def,
                                    _("no PCI bus ID supplied for '%s'"),
                                    _("invalid PCI bus ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./slot[1])", ctxt,
                                    &data->pci_dev.slot, def,
                                    _("no PCI slot ID supplied for '%s'"),
                                    _("invalid PCI slot ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseULong(conn, "number(./function[1])", ctxt,
                                    &data->pci_dev.function, def,
                                    _("no PCI function ID supplied for '%s'"),
                                    _("invalid PCI function ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseHexId(conn, "string(./vendor[1]/@id)", ctxt,
                                    &data->pci_dev.vendor, def,
                                    _("no PCI vendor ID supplied for '%s'"),
                                    _("invalid PCI vendor ID supplied for '%s'")) < 0)
        goto out;

    if (virNodeDevCapsDefParseHexId(conn, "string(./product[1]/@id)", ctxt,
                                    &data->pci_dev.product, def,
                                    _("no PCI product ID supplied for '%s'"),
                                    _("invalid PCI product ID supplied for '%s'")) < 0)
        goto out;

    data->pci_dev.vendor_name  = virXPathString(conn, "string(./vendor[1])", ctxt);
    data->pci_dev.product_name = virXPathString(conn, "string(./product[1])", ctxt);

    ret = 0;
out:
    ctxt->node = orignode;
    return ret;
}

static int
virNodeDevCapSystemParseXML(virConnectPtr conn,
                            xmlXPathContextPtr ctxt,
                            virNodeDeviceDefPtr def,
                            xmlNodePtr node,
                            union _virNodeDevCapData *data)
{
    xmlNodePtr orignode;
    int ret = -1;
    char *tmp;

    orignode = ctxt->node;
    ctxt->node = node;

    data->system.product_name = virXPathString(conn, "string(./product[1])", ctxt);

    data->system.hardware.vendor_name = virXPathString(conn, "string(./hardware/vendor[1])", ctxt);
    data->system.hardware.version     = virXPathString(conn, "string(./hardware/version[1])", ctxt);
    data->system.hardware.serial      = virXPathString(conn, "string(./hardware/serial[1])", ctxt);

    tmp = virXPathString(conn, "string(./hardware/uuid[1])", ctxt);
    if (!tmp) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("no system UUID supplied for '%s'"), def->name);
        goto out;
    }

    if (virUUIDParse(tmp, data->system.hardware.uuid) < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("malformed uuid element for '%s'"), def->name);
        VIR_FREE(tmp);
        goto out;
    }
    VIR_FREE(tmp);

    data->system.firmware.vendor_name  = virXPathString(conn, "string(./firmware/vendor[1])", ctxt);
    data->system.firmware.version      = virXPathString(conn, "string(./firmware/version[1])", ctxt);
    data->system.firmware.release_date = virXPathString(conn, "string(./firmware/release_date[1])", ctxt);

    ret = 0;
out:
    ctxt->node = orignode;
    return ret;
}

static virNodeDevCapsDefPtr
virNodeDevCapsDefParseXML(virConnectPtr conn,
                          xmlXPathContextPtr ctxt,
                          virNodeDeviceDefPtr def,
                          xmlNodePtr node,
                          int create)
{
    virNodeDevCapsDefPtr caps;
    char *tmp;
    int val, ret;

    if (VIR_ALLOC(caps) < 0) {
        virReportOOMError(conn);
        return NULL;
    }

    tmp = virXMLPropString(node, "type");
    if (!tmp) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 "%s", _("missing capability type"));
        goto error;
    }

    if ((val = virNodeDevCapTypeFromString(tmp)) < 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("unknown capability type '%s'"), tmp);
        VIR_FREE(tmp);
        goto error;
    }
    caps->type = val;
    VIR_FREE(tmp);

    switch (caps->type) {
    case VIR_NODE_DEV_CAP_SYSTEM:
        ret = virNodeDevCapSystemParseXML(conn, ctxt, def, node, &caps->data);
        break;
    case VIR_NODE_DEV_CAP_PCI_DEV:
        ret = virNodeDevCapPciDevParseXML(conn, ctxt, def, node, &caps->data);
        break;
    case VIR_NODE_DEV_CAP_USB_DEV:
        ret = virNodeDevCapUsbDevParseXML(conn, ctxt, def, node, &caps->data);
        break;
    case VIR_NODE_DEV_CAP_USB_INTERFACE:
        ret = virNodeDevCapUsbInterfaceParseXML(conn, ctxt, def, node, &caps->data);
        break;
    case VIR_NODE_DEV_CAP_NET:
        ret = virNodeDevCapNetParseXML(conn, ctxt, def, node, &caps->data);
        break;
    case VIR_NODE_DEV_CAP_SCSI_HOST:
        ret = virNodeDevCapScsiHostParseXML(conn, ctxt, def, node, &caps->data, create);
        break;
    case VIR_NODE_DEV_CAP_SCSI:
        ret = virNodeDevCapScsiParseXML(conn, ctxt, def, node, &caps->data);
        break;
    case VIR_NODE_DEV_CAP_STORAGE:
        ret = virNodeDevCapStorageParseXML(conn, ctxt, def, node, &caps->data);
        break;
    default:
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("unknown capability type '%d' for '%s'"),
                                 caps->type, def->name);
        ret = -1;
        break;
    }

    if (ret < 0)
        goto error;
    return caps;

error:
    virNodeDevCapsDefFree(caps);
    return NULL;
}

static virNodeDeviceDefPtr
virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt, int create)
{
    virNodeDeviceDefPtr def;
    virNodeDevCapsDefPtr *next_cap;
    xmlNodePtr *nodes;
    int n, i;

    if (VIR_ALLOC(def) < 0) {
        virReportOOMError(conn);
        return NULL;
    }

    /* Extract device name */
    if (create == EXISTING_DEVICE) {
        def->name = virXPathString(conn, "string(./name[1])", ctxt);
    } else {
        def->name = strdup("new device");
    }

    if (!def->name) {
        virNodeDeviceReportError(conn, VIR_ERR_NO_NAME, NULL);
        goto error;
    }

    /* Extract device parent, if any */
    def->parent = virXPathString(conn, "string(./parent[1])", ctxt);

    /* Parse device capabilities */
    nodes = NULL;
    if ((n = virXPathNodeSet(conn, "./capability", ctxt, &nodes)) <= 0) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 _("no device capabilities for '%s'"),
                                 def->name);
        goto error;
    }

    next_cap = &def->caps;
    for (i = 0 ; i < n ; i++) {
        *next_cap = virNodeDevCapsDefParseXML(conn, ctxt, def, nodes[i], create);
        if (!*next_cap) {
            VIR_FREE(nodes);
            goto error;
        }

        next_cap = &(*next_cap)->next;
    }
    VIR_FREE(nodes);

    return def;

 error:
    virNodeDeviceDefFree(def);
    return NULL;
}

static virNodeDeviceDefPtr
virNodeDeviceDefParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root, int create)
{
    xmlXPathContextPtr ctxt = NULL;
    virNodeDeviceDefPtr def = NULL;

    if (!xmlStrEqual(root->name, BAD_CAST "device")) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 "%s", _("incorrect root element"));
        return NULL;
    }

    ctxt = xmlXPathNewContext(xml);
    if (ctxt == NULL) {
        virReportOOMError(conn);
        goto cleanup;
    }

    ctxt->node = root;
    def = virNodeDeviceDefParseXML(conn, ctxt, create);

cleanup:
    xmlXPathFreeContext(ctxt);
    return def;
}

/* Called from SAX on parsing errors in the XML. */
static void
catchXMLError(void *ctx, const char *msg ATTRIBUTE_UNUSED, ...)
{
    xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) ctx;

    if (ctxt) {
        virConnectPtr conn = ctxt->_private;

        if (virGetLastError() == NULL &&
            ctxt->lastError.level == XML_ERR_FATAL &&
            ctxt->lastError.message != NULL) {
            virNodeDeviceReportError(conn,  VIR_ERR_XML_DETAIL,
                                     _("at line %d: %s"),
                                     ctxt->lastError.line,
                                     ctxt->lastError.message);
        }
    }
}

virNodeDeviceDefPtr
virNodeDeviceDefParseString(virConnectPtr conn, const char *str, int create)
{
    xmlParserCtxtPtr pctxt;
    xmlDocPtr xml = NULL;
    xmlNodePtr root;
    virNodeDeviceDefPtr def = NULL;

    /* Set up a parser context so we can catch the details of XML errors. */
    pctxt = xmlNewParserCtxt ();
    if (!pctxt || !pctxt->sax)
        goto cleanup;
    pctxt->sax->error = catchXMLError;
    pctxt->_private = conn;

    if (conn) virResetError (&conn->err);
    xml = xmlCtxtReadDoc(pctxt, BAD_CAST str, "device.xml", NULL,
                         XML_PARSE_NOENT | XML_PARSE_NONET |
                         XML_PARSE_NOWARNING);
    if (!xml) {
        if (conn && conn->err.code == VIR_ERR_NONE)
              virNodeDeviceReportError(conn, VIR_ERR_XML_ERROR,
                                       "%s", _("failed to parse xml document"));
        goto cleanup;
    }

    if ((root = xmlDocGetRootElement(xml)) == NULL) {
        virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                 "%s", _("missing root element"));
        goto cleanup;
    }

    def = virNodeDeviceDefParseNode(conn, xml, root, create);

cleanup:
    xmlFreeParserCtxt(pctxt);
    xmlFreeDoc(xml);
    return def;
}

void virNodeDevCapsDefFree(virNodeDevCapsDefPtr caps)
{
    union _virNodeDevCapData *data = &caps->data;

    switch (caps->type) {
    case VIR_NODE_DEV_CAP_SYSTEM:
        VIR_FREE(data->system.product_name);
        VIR_FREE(data->system.hardware.vendor_name);
        VIR_FREE(data->system.hardware.version);
        VIR_FREE(data->system.hardware.serial);
        VIR_FREE(data->system.firmware.vendor_name);
        VIR_FREE(data->system.firmware.version);
        VIR_FREE(data->system.firmware.release_date);
        break;
    case VIR_NODE_DEV_CAP_PCI_DEV:
        VIR_FREE(data->pci_dev.product_name);
        VIR_FREE(data->pci_dev.vendor_name);
        break;
    case VIR_NODE_DEV_CAP_USB_DEV:
        VIR_FREE(data->usb_dev.product_name);
        VIR_FREE(data->usb_dev.vendor_name);
        break;
    case VIR_NODE_DEV_CAP_USB_INTERFACE:
        VIR_FREE(data->usb_if.description);
        break;
    case VIR_NODE_DEV_CAP_NET:
        VIR_FREE(data->net.ifname);
        VIR_FREE(data->net.address);
        break;
    case VIR_NODE_DEV_CAP_SCSI_HOST:
        VIR_FREE(data->scsi_host.wwnn);
        VIR_FREE(data->scsi_host.wwpn);
        break;
    case VIR_NODE_DEV_CAP_SCSI:
        VIR_FREE(data->scsi.type);
        break;
    case VIR_NODE_DEV_CAP_STORAGE:
        VIR_FREE(data->storage.block);
        VIR_FREE(data->storage.bus);
        VIR_FREE(data->storage.drive_type);
        VIR_FREE(data->storage.model);
        VIR_FREE(data->storage.vendor);
        VIR_FREE(data->storage.serial);
        break;
    case VIR_NODE_DEV_CAP_LAST:
        /* This case is here to shutup the compiler */
        break;
    }

    VIR_FREE(caps);
}


void virNodeDeviceObjLock(virNodeDeviceObjPtr obj)
{
    virMutexLock(&obj->lock);
}

void virNodeDeviceObjUnlock(virNodeDeviceObjPtr obj)
{
    virMutexUnlock(&obj->lock);
}
