/*
 * network_conf.c: network XML handling
 *
 * Copyright (C) 2006-2013 Red Hat, Inc.
 * Copyright (C) 2006-2008 Daniel P. Berrange
 *
 * 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/>.
 *
 * Author: Daniel P. Berrange <berrange@redhat.com>
 */

#include <config.h>

#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>

#include "virerror.h"
#include "datatypes.h"
#include "network_conf.h"
#include "netdev_vport_profile_conf.h"
#include "netdev_bandwidth_conf.h"
#include "netdev_vlan_conf.h"
#include "viralloc.h"
#include "virxml.h"
#include "viruuid.h"
#include "virbuffer.h"
#include "c-ctype.h"
#include "virfile.h"
#include "virstring.h"

#define MAX_BRIDGE_ID 256
#define VIR_FROM_THIS VIR_FROM_NETWORK
#define NEXT_FREE_CLASS_ID 3
/* currently, /sbin/tc implementation allows up to 16 bits for minor class size */
#define CLASS_ID_BITMAP_SIZE (1<<16)

VIR_ENUM_IMPL(virNetworkForward,
              VIR_NETWORK_FORWARD_LAST,
              "none", "nat", "route", "bridge", "private", "vepa", "passthrough", "hostdev")

VIR_ENUM_DECL(virNetworkForwardHostdevDevice)
VIR_ENUM_IMPL(virNetworkForwardHostdevDevice,
              VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_LAST,
              "none", "pci", "netdev")

VIR_ENUM_IMPL(virNetworkForwardDriverName,
              VIR_NETWORK_FORWARD_DRIVER_NAME_LAST,
              "default",
              "kvm",
              "vfio")

virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjListPtr nets,
                                      const unsigned char *uuid)
{
    size_t i;

    for (i = 0; i < nets->count; i++) {
        virNetworkObjLock(nets->objs[i]);
        if (!memcmp(nets->objs[i]->def->uuid, uuid, VIR_UUID_BUFLEN))
            return nets->objs[i];
        virNetworkObjUnlock(nets->objs[i]);
    }

    return NULL;
}

virNetworkObjPtr virNetworkFindByName(const virNetworkObjListPtr nets,
                                      const char *name)
{
    size_t i;

    for (i = 0; i < nets->count; i++) {
        virNetworkObjLock(nets->objs[i]);
        if (STREQ(nets->objs[i]->def->name, name))
            return nets->objs[i];
        virNetworkObjUnlock(nets->objs[i]);
    }

    return NULL;
}


static void
virPortGroupDefClear(virPortGroupDefPtr def)
{
    VIR_FREE(def->name);
    VIR_FREE(def->virtPortProfile);
    virNetDevBandwidthFree(def->bandwidth);
    virNetDevVlanClear(&def->vlan);
    def->bandwidth = NULL;
}

static void
virNetworkForwardIfDefClear(virNetworkForwardIfDefPtr def)
{
    if (def->type == VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV)
        VIR_FREE(def->device.dev);
}

static void
virNetworkForwardPfDefClear(virNetworkForwardPfDefPtr def)
{
    VIR_FREE(def->dev);
}

static void
virNetworkDHCPHostDefClear(virNetworkDHCPHostDefPtr def)
{
    VIR_FREE(def->mac);
    VIR_FREE(def->id);
    VIR_FREE(def->name);
}

static void
virNetworkIpDefClear(virNetworkIpDefPtr def)
{
    VIR_FREE(def->family);
    VIR_FREE(def->ranges);

    while (def->nhosts)
        virNetworkDHCPHostDefClear(&def->hosts[--def->nhosts]);

    VIR_FREE(def->hosts);
    VIR_FREE(def->tftproot);
    VIR_FREE(def->bootfile);
}

static void
virNetworkRouteDefClear(virNetworkRouteDefPtr def)
{
    VIR_FREE(def->family);
}

static void
virNetworkDNSTxtDefClear(virNetworkDNSTxtDefPtr def)
{
    VIR_FREE(def->name);
    VIR_FREE(def->value);
}

static void
virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def)
{
    while (def->nnames)
        VIR_FREE(def->names[--def->nnames]);
    VIR_FREE(def->names);
}

static void
virNetworkDNSSrvDefClear(virNetworkDNSSrvDefPtr def)
{
    VIR_FREE(def->domain);
    VIR_FREE(def->service);
    VIR_FREE(def->protocol);
    VIR_FREE(def->target);
}

static void
virNetworkDNSDefClear(virNetworkDNSDefPtr def)
{
    if (def->txts) {
        while (def->ntxts)
            virNetworkDNSTxtDefClear(&def->txts[--def->ntxts]);
        VIR_FREE(def->txts);
    }
    if (def->hosts) {
        while (def->nhosts)
            virNetworkDNSHostDefClear(&def->hosts[--def->nhosts]);
        VIR_FREE(def->hosts);
    }
    if (def->srvs) {
        while (def->nsrvs)
            virNetworkDNSSrvDefClear(&def->srvs[--def->nsrvs]);
        VIR_FREE(def->srvs);
    }
}

static void
virNetworkForwardDefClear(virNetworkForwardDefPtr def)
{
    size_t i;

    for (i = 0; i < def->npfs && def->pfs; i++) {
        virNetworkForwardPfDefClear(&def->pfs[i]);
    }
    VIR_FREE(def->pfs);

    for (i = 0; i < def->nifs && def->ifs; i++) {
        virNetworkForwardIfDefClear(&def->ifs[i]);
    }
    VIR_FREE(def->ifs);
    def->nifs = def->npfs = 0;
}

void
virNetworkDefFree(virNetworkDefPtr def)
{
    size_t i;

    if (!def)
        return;

    VIR_FREE(def->name);
    VIR_FREE(def->bridge);
    VIR_FREE(def->domain);

    virNetworkForwardDefClear(&def->forward);

    for (i = 0; i < def->nips && def->ips; i++) {
        virNetworkIpDefClear(&def->ips[i]);
    }
    VIR_FREE(def->ips);

    for (i = 0; i < def->nroutes && def->routes; i++) {
        virNetworkRouteDefClear(&def->routes[i]);
    }
    VIR_FREE(def->routes);

    for (i = 0; i < def->nPortGroups && def->portGroups; i++) {
        virPortGroupDefClear(&def->portGroups[i]);
    }
    VIR_FREE(def->portGroups);

    virNetworkDNSDefClear(&def->dns);

    VIR_FREE(def->virtPortProfile);

    virNetDevBandwidthFree(def->bandwidth);
    virNetDevVlanClear(&def->vlan);
    VIR_FREE(def);
}

void virNetworkObjFree(virNetworkObjPtr net)
{
    if (!net)
        return;

    virNetworkDefFree(net->def);
    virNetworkDefFree(net->newDef);
    virBitmapFree(net->class_id);

    virMutexDestroy(&net->lock);

    VIR_FREE(net);
}

void virNetworkObjListFree(virNetworkObjListPtr nets)
{
    size_t i;

    for (i = 0; i < nets->count; i++)
        virNetworkObjFree(nets->objs[i]);

    VIR_FREE(nets->objs);
    nets->count = 0;
}

/*
 * virNetworkObjAssignDef:
 * @network: the network object to update
 * @def: the new NetworkDef (will be consumed by this function iff successful)
 * @live: is this new def the "live" version, or the "persistent" version
 *
 * Replace the appropriate copy of the given network's NetworkDef
 * with def. Use "live" and current state of the network to determine
 * which to replace.
 *
 * Returns 0 on success, -1 on failure.
 */
int
virNetworkObjAssignDef(virNetworkObjPtr network,
                       const virNetworkDefPtr def,
                       bool live)
{
    if (virNetworkObjIsActive(network)) {
        if (live) {
            virNetworkDefFree(network->def);
            network->def = def;
        } else if (network->persistent) {
            /* save current configuration to be restored on network shutdown */
            virNetworkDefFree(network->newDef);
            network->newDef = def;
        } else {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("cannot save persistent config of transient "
                             "network '%s'"), network->def->name);
            return -1;
        }
    } else if (!live) {
        virNetworkDefFree(network->newDef);
        virNetworkDefFree(network->def);
        network->newDef = NULL;
        network->def = def;
    } else {
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("cannot save live config of inactive "
                         "network '%s'"), network->def->name);
        return -1;
    }
    return 0;
}

/*
 * virNetworkAssignDef:
 * @nets: list of all networks
 * @def: the new NetworkDef (will be consumed by this function iff successful)
 * @live: is this new def the "live" version, or the "persistent" version
 *
 * Either replace the appropriate copy of the NetworkDef with name
 * matching def->name or, if not found, create a new NetworkObj with
 * def. For an existing network, use "live" and current state of the
 * network to determine which to replace.
 *
 * Returns -1 on failure, 0 on success.
 */
virNetworkObjPtr
virNetworkAssignDef(virNetworkObjListPtr nets,
                    const virNetworkDefPtr def,
                    bool live)
{
    virNetworkObjPtr network;

    if ((network = virNetworkFindByName(nets, def->name))) {
        if (virNetworkObjAssignDef(network, def, live) < 0) {
            virNetworkObjUnlock(network);
            return NULL;
        }
        return network;
    }

    if (VIR_REALLOC_N(nets->objs, nets->count + 1) < 0)
        return NULL;

    if (VIR_ALLOC(network) < 0)
        return NULL;
    if (virMutexInit(&network->lock) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("cannot initialize mutex"));
        VIR_FREE(network);
        return NULL;
    }
    virNetworkObjLock(network);
    network->def = def;

    if (!(network->class_id = virBitmapNew(CLASS_ID_BITMAP_SIZE)))
        goto error;

    /* The first three class IDs are already taken */
    ignore_value(virBitmapSetBit(network->class_id, 0));
    ignore_value(virBitmapSetBit(network->class_id, 1));
    ignore_value(virBitmapSetBit(network->class_id, 2));

    network->def = def;
    nets->objs[nets->count] = network;
    nets->count++;

    return network;
error:
    virNetworkObjUnlock(network);
    virNetworkObjFree(network);
    return NULL;

}

/*
 * virNetworkObjSetDefTransient:
 * @network: network object pointer
 * @live: if true, run this operation even for an inactive network.
 *   this allows freely updated network->def with runtime defaults
 *   before starting the network, which will be discarded on network
 *   shutdown. Any cleanup paths need to be sure to handle newDef if
 *   the network is never started.
 *
 * Mark the active network config as transient. Ensures live-only update
 * operations do not persist past network destroy.
 *
 * Returns 0 on success, -1 on failure
 */
int
virNetworkObjSetDefTransient(virNetworkObjPtr network, bool live)
{
    if (!virNetworkObjIsActive(network) && !live)
        return 0;

    if (!network->persistent || network->newDef)
        return 0;

    network->newDef = virNetworkDefCopy(network->def, VIR_NETWORK_XML_INACTIVE);
    return network->newDef ? 0 : -1;
}

/* virNetworkObjUnsetDefTransient:
 *
 * This *undoes* what virNetworkObjSetDefTransient did.
 */
void
virNetworkObjUnsetDefTransient(virNetworkObjPtr network)
{
    if (network->newDef) {
        virNetworkDefFree(network->def);
        network->def = network->newDef;
        network->newDef = NULL;
    }
}

/*
 * virNetworkObjGetPersistentDef:
 * @network: network object pointer
 *
 * Return the persistent network configuration. If network is transient,
 * return the running config.
 *
 * Returns NULL on error, virNetworkDefPtr on success.
 */
virNetworkDefPtr
virNetworkObjGetPersistentDef(virNetworkObjPtr network)
{
    if (network->newDef)
        return network->newDef;
    else
        return network->def;
}

/*
 * virNetworkObjReplacePersistentDef:
 * @network: network object pointer
 * @def: new virNetworkDef to replace current persistent config
 *
 * Replace the "persistent" network configuration with the given new
 * virNetworkDef. This pays attention to whether or not the network
 * is active.
 *
 * Returns -1 on error, 0 on success
 */
int
virNetworkObjReplacePersistentDef(virNetworkObjPtr network,
                                  virNetworkDefPtr def)
{
    if (virNetworkObjIsActive(network)) {
        virNetworkDefFree(network->newDef);
        network->newDef = def;
    } else {
        virNetworkDefFree(network->def);
        network->def = def;
    }
    return 0;
}

/*
 * virNetworkDefCopy:
 * @def: NetworkDef to copy
 * @flags: VIR_NETWORK_XML_INACTIVE if appropriate
 *
 * make a deep copy of the given NetworkDef
 *
 * Returns a new NetworkDef on success, or NULL on failure.
 */
virNetworkDefPtr
virNetworkDefCopy(virNetworkDefPtr def, unsigned int flags)
{
    char *xml = NULL;
    virNetworkDefPtr newDef = NULL;

    if (!def) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("NULL NetworkDef"));
        return NULL;
    }

    /* deep copy with a format/parse cycle */
    if (!(xml = virNetworkDefFormat(def, flags)))
        goto cleanup;
    newDef = virNetworkDefParseString(xml);
cleanup:
    VIR_FREE(xml);
    return newDef;
}

/*
 * virNetworkConfigChangeSetup:
 *
 * 1) checks whether network state is consistent with the requested
 *    type of modification.
 *
 * 3) make sure there are separate "def" and "newDef" copies of
 *    networkDef if appropriate.
 *
 * Returns 0 on success, -1 on error.
 */
int
virNetworkConfigChangeSetup(virNetworkObjPtr network, unsigned int flags)
{
    bool isActive;
    int ret = -1;

    isActive = virNetworkObjIsActive(network);

    if (!isActive && (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE)) {
        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
                       _("network is not running"));
        goto cleanup;
    }

    if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) {
        if (!network->persistent) {
            virReportError(VIR_ERR_OPERATION_INVALID, "%s",
                           _("cannot change persistent config of a "
                             "transient network"));
            goto cleanup;
        }
        /* this should already have been done by the driver, but do it
         * anyway just in case.
         */
        if (isActive && (virNetworkObjSetDefTransient(network, false) < 0))
            goto cleanup;
    }

    ret = 0;
cleanup:
    return ret;
}

void virNetworkRemoveInactive(virNetworkObjListPtr nets,
                              const virNetworkObjPtr net)
{
    size_t i;

    virNetworkObjUnlock(net);
    for (i = 0; i < nets->count; i++) {
        virNetworkObjLock(nets->objs[i]);
        if (nets->objs[i] == net) {
            virNetworkObjUnlock(nets->objs[i]);
            virNetworkObjFree(nets->objs[i]);

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

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

            break;
        }
        virNetworkObjUnlock(nets->objs[i]);
    }
}

/* return ips[index], or NULL if there aren't enough ips */
virNetworkIpDefPtr
virNetworkDefGetIpByIndex(const virNetworkDefPtr def,
                          int family, size_t n)
{
    size_t i;

    if (!def->ips || n >= def->nips)
        return NULL;

    if (family == AF_UNSPEC) {
        return &def->ips[n];
    }

    /* find the nth ip of type "family" */
    for (i = 0; i < def->nips; i++) {
        if (VIR_SOCKET_ADDR_IS_FAMILY(&def->ips[i].address, family)
            && (n-- <= 0)) {
            return &def->ips[i];
        }
    }
    /* failed to find enough of the right family */
    return NULL;
}

/* return number of 1 bits in netmask for the network's ipAddress,
 * or -1 on error
 */
int virNetworkIpDefPrefix(const virNetworkIpDefPtr def)
{
    return virSocketAddrGetIpPrefix(&def->address,
                                    &def->netmask,
                                    def->prefix);
}

/* Fill in a virSocketAddr with the proper netmask for this
 * definition, based on either the definition's netmask, or its
 * prefix. Return -1 on error (and set the netmask family to AF_UNSPEC)
 */
int virNetworkIpDefNetmask(const virNetworkIpDefPtr def,
                           virSocketAddrPtr netmask)
{
    if (VIR_SOCKET_ADDR_IS_FAMILY(&def->netmask, AF_INET)) {
        *netmask = def->netmask;
        return 0;
    }

    return virSocketAddrPrefixToNetmask(virNetworkIpDefPrefix(def), netmask,
                                        VIR_SOCKET_ADDR_FAMILY(&def->address));
}


static int
virSocketAddrRangeParseXML(const char *networkName,
                               xmlNodePtr node,
                               virSocketAddrRangePtr range)
{


    char *start = NULL, *end = NULL;
    int ret = -1;

    if (!(start = virXMLPropString(node, "start"))) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Missing 'start' attribute in dhcp range for network '%s'"),
                       networkName);
        goto cleanup;
    }
    if (virSocketAddrParse(&range->start, start, AF_UNSPEC) < 0)
        goto cleanup;

    if (!(end = virXMLPropString(node, "end"))) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Missing 'end' attribute in dhcp range for network '%s'"),
                       networkName);
        goto cleanup;
    }
    if (virSocketAddrParse(&range->end, end, AF_UNSPEC) < 0)
        goto cleanup;

    /* do a sanity check of the range */
    if (virSocketAddrGetRange(&range->start, &range->end) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid dhcp range '%s' to '%s' in network '%s'"),
                       start, end, networkName);
        goto cleanup;
    }

    ret = 0;

cleanup:
    VIR_FREE(start);
    VIR_FREE(end);
    return ret;
}

static int
virNetworkDHCPHostDefParseXML(const char *networkName,
                              const virNetworkIpDefPtr def,
                              xmlNodePtr node,
                              virNetworkDHCPHostDefPtr host,
                              bool partialOkay)
{
    char *mac = NULL, *name = NULL, *ip = NULL, *id = NULL;
    virMacAddr addr;
    virSocketAddr inaddr;
    int ret = -1;

    mac = virXMLPropString(node, "mac");
    if (mac != NULL) {
        if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid to specify MAC address '%s' "
                             "in network '%s' IPv6 static host definition"),
                           mac, networkName);
            goto cleanup;
        }
        if (virMacAddrParse(mac, &addr) < 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Cannot parse MAC address '%s' in network '%s'"),
                           mac, networkName);
            goto cleanup;
        }
        if (virMacAddrIsMulticast(&addr)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("expected unicast mac address, found "
                             "multicast '%s' in network '%s'"),
                           (const char *)mac, networkName);
            goto cleanup;
        }
    }

    id = virXMLPropString(node, "id");
    if (id) {
        char *cp = id + strspn(id, "0123456789abcdefABCDEF:");
        if (*cp) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid character '%c' in id '%s' of network '%s'"),
                           *cp, id, networkName);
        }
    }

    name = virXMLPropString(node, "name");
    if (name && (!c_isalpha(name[0]))) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Cannot use host name '%s' in network '%s'"),
                       name, networkName);
        goto cleanup;
    }

    ip = virXMLPropString(node, "ip");
    if (ip && (virSocketAddrParse(&inaddr, ip, AF_UNSPEC) < 0)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid IP address in static host definition "
                         "for network '%s'"),
                       networkName);
        goto cleanup;
    }

    if (partialOkay) {
        /* for search/match, you just need one of the three */
        if (!(mac || name || ip)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("At least one of name, mac, or ip attribute "
                             "must be specified for static host definition "
                             "in network '%s' "),
                           networkName);
        }
    } else {
        /* normal usage - you need at least name (IPv6) or one of MAC
         * address or name (IPv4)
         */
        if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) {
            if (!(id || name)) {
                virReportError(VIR_ERR_XML_ERROR,
                           _("Static host definition in IPv6 network '%s' "
                             "must have id or name attribute"),
                           networkName);
                goto cleanup;
            }
        } else if (!(mac || name)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Static host definition in IPv4 network '%s' "
                             "must have mac or name attribute"),
                           networkName);
            goto cleanup;
        }
        if (!ip) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Missing IP address in static host definition "
                             "for network '%s'"),
                           networkName);
            goto cleanup;
        }
    }

    host->mac = mac;
    mac = NULL;
    host->id = id;
    id = NULL;
    host->name = name;
    name = NULL;
    if (ip)
        host->ip = inaddr;
    ret = 0;

cleanup:
    VIR_FREE(mac);
    VIR_FREE(id);
    VIR_FREE(name);
    VIR_FREE(ip);
    return ret;
}

static int
virNetworkDHCPDefParseXML(const char *networkName,
                          xmlNodePtr node,
                          virNetworkIpDefPtr def)
{

    xmlNodePtr cur;

    cur = node->children;
    while (cur != NULL) {
        if (cur->type == XML_ELEMENT_NODE &&
            xmlStrEqual(cur->name, BAD_CAST "range")) {

            if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0)
                return -1;
            if (virSocketAddrRangeParseXML(networkName, cur,
                                               &def->ranges[def->nranges]) < 0) {
                return -1;
            }
            def->nranges++;

        } else if (cur->type == XML_ELEMENT_NODE &&
            xmlStrEqual(cur->name, BAD_CAST "host")) {

            if (VIR_REALLOC_N(def->hosts, def->nhosts + 1) < 0)
                return -1;
            if (virNetworkDHCPHostDefParseXML(networkName, def, cur,
                                              &def->hosts[def->nhosts],
                                              false) < 0) {
                return -1;
            }
            def->nhosts++;

        } else if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) &&
                   cur->type == XML_ELEMENT_NODE &&
                   xmlStrEqual(cur->name, BAD_CAST "bootp")) {
            char *file;
            char *server;
            virSocketAddr inaddr;
            memset(&inaddr, 0, sizeof(inaddr));

            if (!(file = virXMLPropString(cur, "file"))) {
                cur = cur->next;
                continue;
            }
            server = virXMLPropString(cur, "server");

            if (server &&
                virSocketAddrParse(&inaddr, server, AF_UNSPEC) < 0) {
                VIR_FREE(file);
                VIR_FREE(server);
                return -1;
            }

            def->bootfile = file;
            def->bootserver = inaddr;
            VIR_FREE(server);
        }

        cur = cur->next;
    }

    return 0;
}

static int
virNetworkDNSHostDefParseXML(const char *networkName,
                             xmlNodePtr node,
                             virNetworkDNSHostDefPtr def,
                             bool partialOkay)
{
    xmlNodePtr cur;
    char *ip;

    if (!(ip = virXMLPropString(node, "ip")) && !partialOkay) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Missing IP address in network '%s' DNS HOST record"),
                       networkName);
        goto error;
    }

    if (ip && (virSocketAddrParse(&def->ip, ip, AF_UNSPEC) < 0)) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Invalid IP address in network '%s' DNS HOST record"),
                       networkName);
        VIR_FREE(ip);
        goto error;
    }
    VIR_FREE(ip);

    cur = node->children;
    while (cur != NULL) {
        if (cur->type == XML_ELEMENT_NODE &&
            xmlStrEqual(cur->name, BAD_CAST "hostname")) {
              if (cur->children != NULL) {
                  if (VIR_REALLOC_N(def->names, def->nnames + 1) < 0)
                      goto error;
                  def->names[def->nnames++] = (char *)xmlNodeGetContent(cur);
                  if (!def->names[def->nnames - 1]) {
                      virReportError(VIR_ERR_XML_DETAIL,
                                     _("Missing hostname in network '%s' DNS HOST record"),
                                     networkName);
                  }
              }
        }
        cur = cur->next;
    }
    if (def->nnames == 0 && !partialOkay) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Missing hostname in network '%s' DNS HOST record"),
                       networkName);
        goto error;
    }

    if (!VIR_SOCKET_ADDR_VALID(&def->ip) && def->nnames == 0) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Missing ip and hostname in network '%s' DNS HOST record"),
                       networkName);
        goto error;
    }

    return 0;

error:
    virNetworkDNSHostDefClear(def);
    return -1;
}

static int
virNetworkDNSSrvDefParseXML(const char *networkName,
                            xmlNodePtr node,
                            xmlXPathContextPtr ctxt,
                            virNetworkDNSSrvDefPtr def,
                            bool partialOkay)
{
    if (!(def->service = virXMLPropString(node, "service")) && !partialOkay) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Missing required service attribute in DNS SRV record "
                         "of network %s"), networkName);
        goto error;
    }
    if (def->service && strlen(def->service) > DNS_RECORD_LENGTH_SRV) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Service name '%s' in network %s is too long, limit is %d bytes"),
                       def->service, networkName, DNS_RECORD_LENGTH_SRV);
        goto error;
    }

    if (!(def->protocol = virXMLPropString(node, "protocol")) && !partialOkay) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Missing required protocol attribute "
                         "in dns srv record '%s' of network %s"),
                       def->service, networkName);
        goto error;
    }

    /* Check whether protocol value is the supported one */
    if (def->protocol && STRNEQ(def->protocol, "tcp") &&
        (STRNEQ(def->protocol, "udp"))) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Invalid protocol attribute value '%s' "
                         "in DNS SRV record of network %s"),
                       def->protocol, networkName);
        goto error;
    }

    /* Following attributes are optional */
    if ((def->target = virXMLPropString(node, "target")) &&
        (def->domain = virXMLPropString(node, "domain"))) {
        xmlNodePtr save_ctxt = ctxt->node;

        ctxt->node = node;
        if (virXPathUInt("string(./@port)", ctxt, &def->port) < 0 ||
            def->port > 65535) {
            virReportError(VIR_ERR_XML_DETAIL,
                           _("Missing or invalid port attribute "
                             "in network %s"), networkName);
            goto error;
        }

        if (virXPathUInt("string(./@priority)", ctxt, &def->priority) < 0 ||
            def->priority > 65535) {
            virReportError(VIR_ERR_XML_DETAIL,
                           _("Missing or invalid priority attribute "
                             "in network %s"), networkName);
            goto error;
        }

        if (virXPathUInt("string(./@weight)", ctxt, &def->weight) < 0 ||
            def->weight > 65535) {
            virReportError(VIR_ERR_XML_DETAIL,
                           _("Missing or invalid weight attribute "
                             "in network %s"), networkName);
            goto error;
        }

        ctxt->node = save_ctxt;
    }

    if (!(def->service || def->protocol)) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Missing required service attribute or protocol "
                         "in DNS SRV record of network %s"), networkName);
        goto error;
    }
    return 0;

error:
    virNetworkDNSSrvDefClear(def);
    return -1;
}

static int
virNetworkDNSTxtDefParseXML(const char *networkName,
                            xmlNodePtr node,
                            virNetworkDNSTxtDefPtr def,
                            bool partialOkay)
{
    if (!(def->name = virXMLPropString(node, "name"))) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("missing required name attribute in DNS TXT record "
                         "of network %s"), networkName);
        goto error;
    }
    if (strchr(def->name, ' ') != NULL) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("prohibited space character in DNS TXT record "
                         "name '%s' of network %s"), def->name, networkName);
        goto error;
    }
    if (!(def->value = virXMLPropString(node, "value")) && !partialOkay) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("missing required value attribute in DNS TXT record "
                         "named '%s' of network %s"), def->name, networkName);
        goto error;
    }

    if (!(def->name || def->value)) {
        virReportError(VIR_ERR_XML_DETAIL,
                       _("Missing required name or value "
                         "in DNS TXT record of network %s"), networkName);
        goto error;
    }
    return 0;

error:
    virNetworkDNSTxtDefClear(def);
    return -1;
}

static int
virNetworkDNSDefParseXML(const char *networkName,
                         xmlNodePtr node,
                         xmlXPathContextPtr ctxt,
                         virNetworkDNSDefPtr def)
{
    xmlNodePtr *hostNodes = NULL;
    xmlNodePtr *srvNodes = NULL;
    xmlNodePtr *txtNodes = NULL;
    int nhosts, nsrvs, ntxts;
    size_t i;
    int ret = -1;
    xmlNodePtr save = ctxt->node;

    ctxt->node = node;

    nhosts = virXPathNodeSet("./host", ctxt, &hostNodes);
    if (nhosts < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <host> element found in <dns> of network %s"),
                       networkName);
        goto cleanup;
    }
    if (nhosts > 0) {
        if (VIR_ALLOC_N(def->hosts, nhosts) < 0)
            goto cleanup;

        for (i = 0; i < nhosts; i++) {
            if (virNetworkDNSHostDefParseXML(networkName, hostNodes[i],
                                             &def->hosts[def->nhosts], false) < 0) {
                goto cleanup;
            }
            def->nhosts++;
        }
    }

    nsrvs = virXPathNodeSet("./srv", ctxt, &srvNodes);
    if (nsrvs < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <srv> element found in <dns> of network %s"),
                       networkName);
        goto cleanup;
    }
    if (nsrvs > 0) {
        if (VIR_ALLOC_N(def->srvs, nsrvs) < 0)
            goto cleanup;

        for (i = 0; i < nsrvs; i++) {
            if (virNetworkDNSSrvDefParseXML(networkName, srvNodes[i], ctxt,
                                            &def->srvs[def->nsrvs], false) < 0) {
                goto cleanup;
            }
            def->nsrvs++;
        }
    }

    ntxts = virXPathNodeSet("./txt", ctxt, &txtNodes);
    if (ntxts < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <txt> element found in <dns> of network %s"),
                       networkName);
        goto cleanup;
    }
    if (ntxts > 0) {
        if (VIR_ALLOC_N(def->txts, ntxts) < 0)
            goto cleanup;

        for (i = 0; i < ntxts; i++) {
            if (virNetworkDNSTxtDefParseXML(networkName, txtNodes[i],
                                            &def->txts[def->ntxts], false) < 0) {
                goto cleanup;
            }
            def->ntxts++;
        }
    }

    ret = 0;
cleanup:
    VIR_FREE(hostNodes);
    VIR_FREE(srvNodes);
    VIR_FREE(txtNodes);
    ctxt->node = save;
    return ret;
}

static int
virNetworkIPDefParseXML(const char *networkName,
                        xmlNodePtr node,
                        xmlXPathContextPtr ctxt,
                        virNetworkIpDefPtr def)
{
    /*
     * virNetworkIpDef object is already allocated as part of an array.
     * On failure clear it out, but don't free it.
     */

    xmlNodePtr cur, save;
    char *address = NULL, *netmask = NULL;
    unsigned long prefix = 0;
    int prefixRc;
    int result = -1;

    save = ctxt->node;
    ctxt->node = node;

    /* grab raw data from XML */
    def->family = virXPathString("string(./@family)", ctxt);

    address = virXPathString("string(./@address)", ctxt);
    if (!address) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Missing required address attribute in network '%s'"),
                       networkName);
        goto cleanup;
    }
    if (virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid address '%s' in network '%s'"),
                       address, networkName);
        goto cleanup;
    }

    netmask = virXPathString("string(./@netmask)", ctxt);
    if (netmask &&
        (virSocketAddrParse(&def->netmask, netmask, AF_UNSPEC) < 0)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid netmask '%s' in network '%s'"),
                       netmask, networkName);
        goto cleanup;
    }

    prefixRc = virXPathULong("string(./@prefix)", ctxt, &prefix);
    if (prefixRc == -2) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid ULong value specified for prefix in definition of network '%s'"),
                       networkName);
        goto cleanup;
    }
    if (prefixRc < 0)
        def->prefix = 0;
    else
        def->prefix = prefix;

    /* validate address, etc. for each family */
    if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) {
        if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) ||
              VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC))) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("%s family specified for non-IPv4 address '%s' in network '%s'"),
                           def->family == NULL? "no" : "ipv4", address, networkName);
            goto cleanup;
        }
        if (netmask) {
            if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->netmask, AF_INET)) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("Invalid netmask '%s' for address '%s' "
                                 "in network '%s' (both must be IPv4)"),
                               netmask, address, networkName);
                goto cleanup;
            }
            if (def->prefix > 0) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("Network '%s' IP address cannot have "
                                 "both a prefix and a netmask"), networkName);
                goto cleanup;
            }
        } else if (def->prefix > 32) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Invalid IPv4 prefix '%lu' in network '%s'"),
                           prefix, networkName);
            goto cleanup;
        }
    } else if (STREQ(def->family, "ipv6")) {
        if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Family 'ipv6' specified for non-IPv6 address '%s' in network '%s'"),
                           address, networkName);
            goto cleanup;
        }
        if (netmask) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("netmask not allowed for IPv6 address '%s' in network '%s'"),
                           address, networkName);
            goto cleanup;
        }
        if (def->prefix > 128) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Invalid IPv6 prefix '%lu' in network '%s'"),
                           prefix, networkName);
            goto cleanup;
        }
    } else {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Unrecognized family '%s' in network '%s'"),
                       def->family, networkName);
        goto cleanup;
    }

    cur = node->children;
    while (cur != NULL) {
        if (cur->type == XML_ELEMENT_NODE &&
            xmlStrEqual(cur->name, BAD_CAST "dhcp")) {
            if (virNetworkDHCPDefParseXML(networkName, cur, def) < 0)
                goto cleanup;
        } else if (cur->type == XML_ELEMENT_NODE &&
                   xmlStrEqual(cur->name, BAD_CAST "tftp")) {
            char *root;

            if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("Unsupported <tftp> element in an IPv6 element in network '%s'"),
                               networkName);
                goto cleanup;
            }
            if (!(root = virXMLPropString(cur, "root"))) {
                cur = cur->next;
                continue;
            }
            def->tftproot = (char *)root;
        }
        cur = cur->next;
    }

    result = 0;

cleanup:
    if (result < 0) {
        virNetworkIpDefClear(def);
    }
    VIR_FREE(address);
    VIR_FREE(netmask);

    ctxt->node = save;
    return result;
}

static int
virNetworkRouteDefParseXML(const char *networkName,
                           xmlNodePtr node,
                           xmlXPathContextPtr ctxt,
                           virNetworkRouteDefPtr def)
{
    /*
     * virNetworkRouteDef object is already allocated as part
     * of an array.  On failure clear: it out, but don't free it.
     */

    xmlNodePtr save;
    char *address = NULL, *netmask = NULL;
    char *gateway = NULL;
    unsigned long prefix = 0, metric = 0;
    int result = -1;
    int prefixRc, metricRc;
    virSocketAddr testAddr;

    save = ctxt->node;
    ctxt->node = node;

    /* grab raw data from XML */
    def->family = virXPathString("string(./@family)", ctxt);
    address = virXPathString("string(./@address)", ctxt);
    netmask = virXPathString("string(./@netmask)", ctxt);
    gateway = virXPathString("string(./@gateway)", ctxt);
    prefixRc = virXPathULong("string(./@prefix)", ctxt, &prefix);
    if (prefixRc == -2) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid prefix specified "
                         "in route definition of network '%s'"),
                       networkName);
        goto cleanup;
    }
    def->has_prefix = (prefixRc == 0);
    def->prefix = prefix;
    metricRc = virXPathULong("string(./@metric)", ctxt, &metric);
    if (metricRc == -2) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid metric specified "
                         "in route definition of network '%s'"),
                       networkName);
        goto cleanup;
    }
    if (metricRc == 0) {
        def->has_metric = true;
        if (metric == 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid metric value, must be > 0 "
                             "in route definition of network '%s'"),
                           networkName);
            goto cleanup;
        }
    }
    def->metric = metric;

    /* Note: both network and gateway addresses must be specified */

    if (!address) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Missing required address attribute "
                         "in route definition of network '%s'"),
                       networkName);
        goto cleanup;
    }

    if (!gateway) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Missing required gateway attribute "
                         "in route definition of network '%s'"),
                       networkName);
        goto cleanup;
    }

    if (virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Bad network address '%s' "
                         "in route definition of network '%s'"),
                       address, networkName);
        goto cleanup;
    }

    if (virSocketAddrParse(&def->gateway, gateway, AF_UNSPEC) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Bad gateway address '%s' "
                         "in route definition of network '%s'"),
                       gateway, networkName);
        goto cleanup;
    }

    /* validate network address, etc. for each family */
    if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) {
        if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) ||
              VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC))) {
            virReportError(VIR_ERR_XML_ERROR,
                           def->family == NULL ?
                           _("No family specified for non-IPv4 address '%s' "
                             "in route definition of network '%s'") :
                           _("IPv4 family specified for non-IPv4 address '%s' "
                             "in route definition of network '%s'"),
                           address, networkName);
            goto cleanup;
        }
        if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->gateway, AF_INET)) {
            virReportError(VIR_ERR_XML_ERROR,
                           def->family == NULL ?
                           _("No family specified for non-IPv4 gateway '%s' "
                             "in route definition of network '%s'") :
                           _("IPv4 family specified for non-IPv4 gateway '%s' "
                             "in route definition of network '%s'"),
                           address, networkName);
            goto cleanup;
        }
        if (netmask) {
            if (virSocketAddrParse(&def->netmask, netmask, AF_UNSPEC) < 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Bad netmask address '%s' "
                                 "in route definition of network '%s'"),
                               netmask, networkName);
                goto cleanup;
            }
            if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->netmask, AF_INET)) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Network '%s' has invalid netmask '%s' "
                                 "for address '%s' (both must be IPv4)"),
                               networkName, netmask, address);
                goto cleanup;
            }
            if (def->has_prefix) {
                /* can't have both netmask and prefix at the same time */
                virReportError(VIR_ERR_XML_ERROR,
                               _("Route definition '%s' cannot have both "
                                 "a prefix and a netmask"),
                               networkName);
                goto cleanup;
            }
        }
        if (def->prefix > 32) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid prefix %u specified "
                             "in route definition of network '%s', "
                             "must be 0 - 32"),
                           def->prefix, networkName);
            goto cleanup;
        }
    } else if (STREQ(def->family, "ipv6")) {
        if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("ipv6 family specified for non-IPv6 address '%s' "
                             "in route definition of network '%s'"),
                           address, networkName);
            goto cleanup;
        }
        if (netmask) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Specifying netmask invalid for IPv6 address '%s' "
                             "in route definition of network '%s'"),
                           address, networkName);
            goto cleanup;
        }
        if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->gateway, AF_INET6)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("ipv6 specified for non-IPv6 gateway address '%s' "
                             "in route definition of network '%s'"),
                           gateway, networkName);
            goto cleanup;
        }
        if (def->prefix > 128) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid prefix %u specified "
                             "in route definition of network '%s', "
                             "must be 0 - 128"),
                           def->prefix, networkName);
            goto cleanup;
        }
    } else {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Unrecognized family '%s' "
                         "in route definition of network'%s'"),
                       def->family, networkName);
        goto cleanup;
    }

    /* make sure the address is a network address */
    if (netmask) {
        if (virSocketAddrMask(&def->address, &def->netmask, &testAddr) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("error converting address '%s' with netmask '%s' "
                             "to network-address "
                             "in route definition of network '%s'"),
                           address, netmask, networkName);
            goto cleanup;
        }
    } else {
        if (virSocketAddrMaskByPrefix(&def->address,
                                      def->prefix, &testAddr) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("error converting address '%s' with prefix %u "
                             "to network-address "
                             "in route definition of network '%s'"),
                           address, def->prefix, networkName);
            goto cleanup;
        }
    }
    if (!virSocketAddrEqual(&def->address, &testAddr)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("address '%s' in route definition of network '%s' "
                         "is not a network address"),
                       address, networkName);
        goto cleanup;
    }

    result = 0;

cleanup:
    if (result < 0) {
        virNetworkRouteDefClear(def);
    }
    VIR_FREE(address);
    VIR_FREE(netmask);
    VIR_FREE(gateway);

    ctxt->node = save;
    return result;
}

static int
virNetworkPortGroupParseXML(virPortGroupDefPtr def,
                            xmlNodePtr node,
                            xmlXPathContextPtr ctxt)
{
    /*
     * virPortGroupDef object is already allocated as part of an array.
     * On failure clear it out, but don't free it.
     */

    xmlNodePtr save;
    xmlNodePtr virtPortNode;
    xmlNodePtr vlanNode;
    xmlNodePtr bandwidth_node;
    char *isDefault = NULL;

    int result = -1;

    save = ctxt->node;
    ctxt->node = node;

    /* grab raw data from XML */
    def->name = virXPathString("string(./@name)", ctxt);
    if (!def->name) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("Missing required name attribute in portgroup"));
        goto cleanup;
    }

    isDefault = virXPathString("string(./@default)", ctxt);
    def->isDefault = isDefault && STRCASEEQ(isDefault, "yes");

    virtPortNode = virXPathNode("./virtualport", ctxt);
    if (virtPortNode &&
        (!(def->virtPortProfile = virNetDevVPortProfileParse(virtPortNode, 0)))) {
        goto cleanup;
    }

    bandwidth_node = virXPathNode("./bandwidth", ctxt);
    if (bandwidth_node &&
        !(def->bandwidth = virNetDevBandwidthParse(bandwidth_node, -1))) {
        goto cleanup;
    }

    vlanNode = virXPathNode("./vlan", ctxt);
    if (vlanNode && virNetDevVlanParse(vlanNode, ctxt, &def->vlan) < 0)
        goto cleanup;

    result = 0;
cleanup:
    if (result < 0) {
        virPortGroupDefClear(def);
    }
    VIR_FREE(isDefault);

    ctxt->node = save;
    return result;
}

static int
virNetworkForwardNatDefParseXML(const char *networkName,
                                xmlNodePtr node,
                                xmlXPathContextPtr ctxt,
                                virNetworkForwardDefPtr def)
{
    int ret = -1;
    xmlNodePtr *natAddrNodes = NULL;
    xmlNodePtr *natPortNodes = NULL;
    int nNatAddrs, nNatPorts;
    char *addrStart = NULL;
    char *addrEnd = NULL;
    xmlNodePtr save = ctxt->node;

    ctxt->node = node;

    if (def->type != VIR_NETWORK_FORWARD_NAT) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("The <nat> element can only be used when <forward> 'mode' is 'nat' in network %s"),
                       networkName);
        goto cleanup;
    }

    /* addresses for SNAT */
    nNatAddrs = virXPathNodeSet("./address", ctxt, &natAddrNodes);
    if (nNatAddrs < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <address> element found in <forward> of "
                         "network %s"), networkName);
        goto cleanup;
    } else if (nNatAddrs > 1) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Only one <address> element is allowed in <nat> in "
                         "<forward> in network %s"), networkName);
        goto cleanup;
    } else if (nNatAddrs == 1) {
        addrStart = virXMLPropString(*natAddrNodes, "start");
        if (addrStart == NULL) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("missing 'start' attribute in <address> element in <nat> in "
                             "<forward> in network %s"), networkName);
            goto cleanup;
        }
        addrEnd = virXMLPropString(*natAddrNodes, "end");
        if (addrEnd == NULL) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("missing 'end' attribute in <address> element in <nat> in "
                             "<forward> in network %s"), networkName);
            goto cleanup;
        }
    }

    if (addrStart && virSocketAddrParse(&def->addr.start, addrStart, AF_INET) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Bad ipv4 start address '%s' in <nat> in <forward> in "
                         "network '%s'"), addrStart, networkName);
        goto cleanup;
    }

    if (addrEnd && virSocketAddrParse(&def->addr.end, addrEnd, AF_INET) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Bad ipv4 end address '%s' in <nat> in <forward> in "
                         "network '%s'"), addrEnd, networkName);
        goto cleanup;
    }

    /* ports for SNAT and MASQUERADE */
    nNatPorts = virXPathNodeSet("./port", ctxt, &natPortNodes);
    if (nNatPorts < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <port> element found in <forward> of "
                         "network %s"), networkName);
        goto cleanup;
    } else if (nNatPorts > 1) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Only one <port> element is allowed in <nat> in "
                         "<forward> in network %s"), networkName);
        goto cleanup;
    } else if (nNatPorts == 1) {
        if (virXPathUInt("string(./port[1]/@start)", ctxt, &def->port.start) < 0
            || def->port.start > 65535) {

            virReportError(VIR_ERR_XML_DETAIL,
                           _("Missing or invalid 'start' attribute in <port> "
                             "in <nat> in <forward> in network %s"),
                             networkName);
            goto cleanup;
        }
        if (virXPathUInt("string(./port[1]/@end)", ctxt, &def->port.end) < 0
            || def->port.end > 65535 || def->port.end < def->port.start) {
            virReportError(VIR_ERR_XML_DETAIL,
                           _("Missing or invalid 'end' attribute in <port> in "
                             "<nat> in <forward> in network %s"), networkName);
            goto cleanup;
        }
    }
    ret = 0;

cleanup:
    VIR_FREE(addrStart);
    VIR_FREE(addrEnd);
    VIR_FREE(natAddrNodes);
    VIR_FREE(natPortNodes);
    ctxt->node = save;
    return ret;
}

static int
virNetworkForwardDefParseXML(const char *networkName,
                             xmlNodePtr node,
                             xmlXPathContextPtr ctxt,
                             virNetworkForwardDefPtr def)
{
    size_t i;
    int ret = -1;
    xmlNodePtr *forwardIfNodes = NULL;
    xmlNodePtr *forwardPfNodes = NULL;
    xmlNodePtr *forwardAddrNodes = NULL;
    xmlNodePtr *forwardNatNodes = NULL;
    int nForwardIfs, nForwardAddrs, nForwardPfs, nForwardNats;
    char *forwardDev = NULL;
    char *forwardManaged = NULL;
    char *forwardDriverName = NULL;
    char *type = NULL;
    xmlNodePtr save = ctxt->node;

    ctxt->node = node;

    if (!(type = virXPathString("string(./@mode)", ctxt))) {
        def->type = VIR_NETWORK_FORWARD_NAT;
    } else {
        if ((def->type = virNetworkForwardTypeFromString(type)) < 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("unknown forwarding type '%s'"), type);
            goto cleanup;
        }
        VIR_FREE(type);
    }

    forwardManaged = virXPathString("string(./@managed)", ctxt);
    if (forwardManaged != NULL &&
        STRCASEEQ(forwardManaged, "yes")) {
        def->managed = true;
    }

    forwardDriverName = virXPathString("string(./driver/@name)", ctxt);
    if (forwardDriverName) {
        int driverName
            = virNetworkForwardDriverNameTypeFromString(forwardDriverName);

        if (driverName <= 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Unknown forward <driver name='%s'/> "
                             "in network %s"),
                           forwardDriverName, networkName);
            goto cleanup;
        }
        def->driverName = driverName;
    }

    /* bridge and hostdev modes can use a pool of physical interfaces */
    nForwardIfs = virXPathNodeSet("./interface", ctxt, &forwardIfNodes);
    if (nForwardIfs < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <interface> element found in <forward> of network %s"),
                       networkName);
        goto cleanup;
    }

    nForwardAddrs = virXPathNodeSet("./address", ctxt, &forwardAddrNodes);
    if (nForwardAddrs < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <address> element found in <forward> of network %s"),
                       networkName);
        goto cleanup;
    }

    nForwardPfs = virXPathNodeSet("./pf", ctxt, &forwardPfNodes);
    if (nForwardPfs < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <pf> element found in <forward> of network %s"),
                       networkName);
        goto cleanup;
    }

    nForwardNats = virXPathNodeSet("./nat", ctxt, &forwardNatNodes);
    if (nForwardNats < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid <nat> element found in <forward> of network %s"),
                       networkName);
        goto cleanup;
    } else if (nForwardNats > 1) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Only one <nat> element is allowed in <forward> of network %s"),
                       networkName);
        goto cleanup;
    } else if (nForwardNats == 1) {
        if (virNetworkForwardNatDefParseXML(networkName,
                                            *forwardNatNodes,
                                            ctxt, def) < 0)
            goto cleanup;
    }

    if (((nForwardIfs > 0) + (nForwardAddrs > 0) + (nForwardPfs > 0)) > 1) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("<address>, <interface>, and <pf> elements in <forward> "
                         "of network %s are mutually exclusive"),
                       networkName);
        goto cleanup;
    }

    forwardDev = virXPathString("string(./@dev)", ctxt);
    if (forwardDev && (nForwardAddrs > 0 || nForwardPfs > 0)) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("the <forward> 'dev' attribute cannot be used when "
                         "<address> or <pf> sub-elements are present "
                         "in network %s"));
        goto cleanup;
    }

    if (nForwardIfs > 0 || forwardDev) {
        if (VIR_ALLOC_N(def->ifs, MAX(nForwardIfs, 1)) < 0)
            goto cleanup;

        if (forwardDev) {
            def->ifs[0].device.dev = forwardDev;
            def->ifs[0].type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV;
            forwardDev = NULL;
            def->nifs++;
        }

        /* parse each <interface> */
        for (i = 0; i < nForwardIfs; i++) {
            forwardDev = virXMLPropString(forwardIfNodes[i], "dev");
            if (!forwardDev) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Missing required dev attribute in "
                                 "<forward> <interface> element of network %s"),
                               networkName);
                goto cleanup;
            }

            if ((i == 0) && (def->nifs == 1)) {
                /* both <forward dev='x'> and <interface dev='x'/> are
                 * present.  If they don't match, it's an error.
                 */
                if (STRNEQ(forwardDev, def->ifs[0].device.dev)) {
                    virReportError(VIR_ERR_XML_ERROR,
                                   _("<forward dev='%s'> must match first "
                                     "<interface dev='%s'/> in network %s"),
                                   def->ifs[0].device.dev,
                                   forwardDev, networkName);
                    goto cleanup;
                }
                VIR_FREE(forwardDev);
                continue;
            }

            def->ifs[i].device.dev = forwardDev;
            forwardDev = NULL;
            def->ifs[i].type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV;
            def->nifs++;
        }

    } else if (nForwardAddrs > 0) {
        if (VIR_ALLOC_N(def->ifs, nForwardAddrs) < 0)
            goto cleanup;

        for (i = 0; i < nForwardAddrs; i++) {
            if (!(type = virXMLPropString(forwardAddrNodes[i], "type"))) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("missing address type in network %s"),
                               networkName);
                goto cleanup;
            }

            if ((def->ifs[i].type = virNetworkForwardHostdevDeviceTypeFromString(type)) < 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("unknown address type '%s' in network %s"),
                               type, networkName);
                goto cleanup;
            }

            switch (def->ifs[i].type) {
            case VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI:
                if (virDevicePCIAddressParseXML(forwardAddrNodes[i],
                                                &def->ifs[i].device.pci) < 0) {
                    goto cleanup;
                }
                break;

            /* Add USB case here if we ever find a reason to support it */

            default:
                virReportError(VIR_ERR_XML_ERROR,
                               _("unsupported address type '%s' in network %s"),
                               type, networkName);
                goto cleanup;
            }
            VIR_FREE(type);
            def->nifs++;
        }

    } else if (nForwardPfs > 1) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Only one <pf> element is allowed in <forward> of network %s"),
                       networkName);
        goto cleanup;
    } else if (nForwardPfs == 1) {
        if (VIR_ALLOC_N(def->pfs, nForwardPfs) < 0)
            goto cleanup;

        forwardDev = virXMLPropString(*forwardPfNodes, "dev");
        if (!forwardDev) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Missing required dev attribute "
                             "in <pf> element of network '%s'"),
                           networkName);
            goto cleanup;
        }

        def->pfs->dev = forwardDev;
        forwardDev = NULL;
        def->npfs++;
    }

    ret = 0;
cleanup:
    VIR_FREE(type);
    VIR_FREE(forwardDev);
    VIR_FREE(forwardManaged);
    VIR_FREE(forwardDriverName);
    VIR_FREE(forwardPfNodes);
    VIR_FREE(forwardIfNodes);
    VIR_FREE(forwardAddrNodes);
    VIR_FREE(forwardNatNodes);
    ctxt->node = save;
    return ret;
}

static virNetworkDefPtr
virNetworkDefParseXML(xmlXPathContextPtr ctxt)
{
    virNetworkDefPtr def;
    char *tmp;
    char *stp = NULL;
    xmlNodePtr *ipNodes = NULL;
    xmlNodePtr *routeNodes = NULL;
    xmlNodePtr *portGroupNodes = NULL;
    int nIps, nPortGroups, nRoutes;
    xmlNodePtr dnsNode = NULL;
    xmlNodePtr virtPortNode = NULL;
    xmlNodePtr forwardNode = NULL;
    char *ipv6nogwStr = NULL;
    xmlNodePtr save = ctxt->node;
    xmlNodePtr bandwidthNode = NULL;
    xmlNodePtr vlanNode;

    if (VIR_ALLOC(def) < 0)
        return NULL;

    /* Extract network name */
    def->name = virXPathString("string(./name[1])", ctxt);
    if (!def->name) {
        virReportError(VIR_ERR_NO_NAME, NULL);
        goto error;
    }

    /* Extract network uuid */
    tmp = virXPathString("string(./uuid[1])", ctxt);
    if (!tmp) {
        if (virUUIDGenerate(def->uuid)) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("Failed to generate UUID"));
            goto error;
        }
    } else {
        if (virUUIDParse(tmp, def->uuid) < 0) {
            VIR_FREE(tmp);
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("malformed uuid element"));
            goto error;
        }
        VIR_FREE(tmp);
        def->uuid_specified = true;
    }

    /* check if definitions with no IPv6 gateway addresses is to
     * allow guest-to-guest communications.
     */
    ipv6nogwStr = virXPathString("string(./@ipv6)", ctxt);
    if (ipv6nogwStr) {
        if (STREQ(ipv6nogwStr, "yes")) {
            def->ipv6nogw = true;
        } else if (STRNEQ(ipv6nogwStr, "no")) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid ipv6 setting '%s' in network '%s'"),
                           ipv6nogwStr, def->name);
            goto error;
        }
        VIR_FREE(ipv6nogwStr);
    }

    /* Parse network domain information */
    def->domain = virXPathString("string(./domain[1]/@name)", ctxt);

    if ((bandwidthNode = virXPathNode("./bandwidth", ctxt)) != NULL &&
        (def->bandwidth = virNetDevBandwidthParse(bandwidthNode, -1)) == NULL)
        goto error;

    vlanNode = virXPathNode("./vlan", ctxt);
    if (vlanNode && virNetDevVlanParse(vlanNode, ctxt, &def->vlan) < 0)
        goto error;

    /* Parse bridge information */
    def->bridge = virXPathString("string(./bridge[1]/@name)", ctxt);
    stp = virXPathString("string(./bridge[1]/@stp)", ctxt);
    def->stp = (stp && STREQ(stp, "off")) ? false : true;

    if (virXPathULong("string(./bridge[1]/@delay)", ctxt, &def->delay) < 0)
        def->delay = 0;

    tmp = virXPathString("string(./mac[1]/@address)", ctxt);
    if (tmp) {
        if (virMacAddrParse(tmp, &def->mac) < 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid bridge mac address '%s' in network '%s'"),
                           tmp, def->name);
            VIR_FREE(tmp);
            goto error;
        }
        if (virMacAddrIsMulticast(&def->mac)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid multicast bridge mac address '%s' in network '%s'"),
                           tmp, def->name);
            VIR_FREE(tmp);
            goto error;
        }
        VIR_FREE(tmp);
        def->mac_specified = true;
    }

    dnsNode = virXPathNode("./dns", ctxt);
    if (dnsNode != NULL &&
        virNetworkDNSDefParseXML(def->name, dnsNode, ctxt, &def->dns) < 0) {
        goto error;
    }

    virtPortNode = virXPathNode("./virtualport", ctxt);
    if (virtPortNode &&
        (!(def->virtPortProfile = virNetDevVPortProfileParse(virtPortNode,
                                                             VIR_VPORT_XML_REQUIRE_TYPE)))) {
        goto error;
    }

    nPortGroups = virXPathNodeSet("./portgroup", ctxt, &portGroupNodes);
    if (nPortGroups < 0)
        goto error;

    if (nPortGroups > 0) {
        size_t i;

        /* allocate array to hold all the portgroups */
        if (VIR_ALLOC_N(def->portGroups, nPortGroups) < 0)
            goto error;
        /* parse each portgroup */
        for (i = 0; i < nPortGroups; i++) {
            int ret = virNetworkPortGroupParseXML(&def->portGroups[i],
                                                  portGroupNodes[i], ctxt);
            if (ret < 0)
                goto error;
            def->nPortGroups++;
        }
    }
    VIR_FREE(portGroupNodes);

    nIps = virXPathNodeSet("./ip", ctxt, &ipNodes);
    if (nIps < 0)
        goto error;

    if (nIps > 0) {
        size_t i;

        /* allocate array to hold all the addrs */
        if (VIR_ALLOC_N(def->ips, nIps) < 0)
            goto error;
        /* parse each addr */
        for (i = 0; i < nIps; i++) {
            int ret = virNetworkIPDefParseXML(def->name, ipNodes[i],
                                              ctxt, &def->ips[i]);
            if (ret < 0)
                goto error;
            def->nips++;
        }
    }
    VIR_FREE(ipNodes);

    nRoutes = virXPathNodeSet("./route", ctxt, &routeNodes);
    if (nRoutes < 0)
        goto error;

    if (nRoutes > 0) {
        size_t i;

        /* allocate array to hold all the route definitions */
        if (VIR_ALLOC_N(def->routes, nRoutes) < 0)
            goto error;
        /* parse each definition */
        for (i = 0; i < nRoutes; i++) {
            int ret = virNetworkRouteDefParseXML(def->name, routeNodes[i],
                                              ctxt, &def->routes[i]);
            if (ret < 0)
                goto error;
            def->nroutes++;
        }

        /* now validate the correctness of any static route gateways specified
         *
         * note: the parameters within each definition are verified/assumed valid;
         * the question being asked and answered here is if the specified gateway
         * is directly reachable from this bridge.
         */
        nRoutes = def->nroutes;
        nIps = def->nips;
        for (i = 0; i < nRoutes; i++) {
            size_t j;
            virSocketAddr testAddr, testGw;
            bool addrMatch;
            virNetworkRouteDefPtr gwdef = &def->routes[i];
            addrMatch = false;
            for (j = 0; j < nIps; j++) {
                virNetworkIpDefPtr def2 = &def->ips[j];
                if (VIR_SOCKET_ADDR_FAMILY(&gwdef->gateway)
                    != VIR_SOCKET_ADDR_FAMILY(&def2->address)) {
                    continue;
                }
                int prefix = virNetworkIpDefPrefix(def2);
                virSocketAddrMaskByPrefix(&def2->address, prefix, &testAddr);
                virSocketAddrMaskByPrefix(&gwdef->gateway, prefix, &testGw);
                if (VIR_SOCKET_ADDR_VALID(&testAddr) &&
                    VIR_SOCKET_ADDR_VALID(&testGw) &&
                    virSocketAddrEqual(&testAddr, &testGw)) {
                    addrMatch = true;
                    break;
                }
            }
            if (!addrMatch) {
                char *gw = virSocketAddrFormat(&gwdef->gateway);
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("unreachable static route gateway '%s' specified for network '%s'"),
                               gw, def->name);
                VIR_FREE(gw);
                goto error;
            }
        }
    }
    VIR_FREE(routeNodes);

    forwardNode = virXPathNode("./forward", ctxt);
    if (forwardNode &&
        virNetworkForwardDefParseXML(def->name, forwardNode, ctxt, &def->forward) < 0) {
        goto error;
    }

    /* Validate some items in the main NetworkDef that need to align
     * with the chosen forward mode.
     */
    switch (def->forward.type) {
    case VIR_NETWORK_FORWARD_NONE:
        break;

    case VIR_NETWORK_FORWARD_ROUTE:
    case VIR_NETWORK_FORWARD_NAT:
        /* It's pointless to specify L3 forwarding without specifying
         * the network we're on.
         */
        if (def->nips == 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("%s forwarding requested, "
                             "but no IP address provided for network '%s'"),
                           virNetworkForwardTypeToString(def->forward.type),
                           def->name);
            goto error;
        }
        if (def->forward.nifs > 1) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("multiple forwarding interfaces specified "
                             "for network '%s', only one is supported"),
                           def->name);
            goto error;
        }
        break;

    case VIR_NETWORK_FORWARD_PRIVATE:
    case VIR_NETWORK_FORWARD_VEPA:
    case VIR_NETWORK_FORWARD_PASSTHROUGH:
    case VIR_NETWORK_FORWARD_HOSTDEV:
        if (def->bridge) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("bridge name not allowed in %s mode (network '%s')"),
                           virNetworkForwardTypeToString(def->forward.type),
                           def->name);
            goto error;
        }
        /* fall through to next case */
    case VIR_NETWORK_FORWARD_BRIDGE:
        if (def->delay || stp) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("bridge delay/stp options only allowed in route, nat, and isolated mode, not in %s (network '%s')"),
                           virNetworkForwardTypeToString(def->forward.type),
                           def->name);
            goto error;
        }
        if (def->bridge && (def->forward.nifs || def->forward.npfs)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("A network with forward mode='%s' can specify "
                             "a bridge name or a forward dev, but not "
                             "both (network '%s')"),
                           virNetworkForwardTypeToString(def->forward.type),
                           def->name);
            goto error;
        }
        break;
    }

    VIR_FREE(stp);
    ctxt->node = save;
    return def;

error:
    VIR_FREE(routeNodes);
    VIR_FREE(stp);
    virNetworkDefFree(def);
    VIR_FREE(ipNodes);
    VIR_FREE(portGroupNodes);
    VIR_FREE(ipv6nogwStr);
    ctxt->node = save;
    return NULL;
}

static virNetworkDefPtr
virNetworkDefParse(const char *xmlStr,
                   const char *filename)
{
    xmlDocPtr xml;
    virNetworkDefPtr def = NULL;

    if ((xml = virXMLParse(filename, xmlStr, _("(network_definition)")))) {
        def = virNetworkDefParseNode(xml, xmlDocGetRootElement(xml));
        xmlFreeDoc(xml);
    }

    return def;
}

virNetworkDefPtr virNetworkDefParseString(const char *xmlStr)
{
    return virNetworkDefParse(xmlStr, NULL);
}

virNetworkDefPtr virNetworkDefParseFile(const char *filename)
{
    return virNetworkDefParse(NULL, filename);
}


virNetworkDefPtr virNetworkDefParseNode(xmlDocPtr xml,
                                        xmlNodePtr root)
{
    xmlXPathContextPtr ctxt = NULL;
    virNetworkDefPtr def = NULL;

    if (!xmlStrEqual(root->name, BAD_CAST "network")) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("unexpected root element <%s>, "
                         "expecting <network>"),
                       root->name);
        return NULL;
    }

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

    ctxt->node = root;
    def = virNetworkDefParseXML(ctxt);

cleanup:
    xmlXPathFreeContext(ctxt);
    return def;
}


static int
virNetworkDNSDefFormat(virBufferPtr buf,
                       virNetworkDNSDefPtr def)
{
    int result = 0;
    size_t i, j;

    if (!(def->nhosts || def->nsrvs || def->ntxts))
        goto out;

    virBufferAddLit(buf, "<dns>\n");
    virBufferAdjustIndent(buf, 2);

    for (i = 0; i < def->ntxts; i++) {
        virBufferAsprintf(buf, "<txt name='%s' value='%s' />\n",
                              def->txts[i].name,
                              def->txts[i].value);
    }

    for (i = 0; i < def->nsrvs; i++) {
        if (def->srvs[i].service && def->srvs[i].protocol) {
            virBufferAsprintf(buf, "<srv service='%s' protocol='%s'",
                                  def->srvs[i].service,
                                  def->srvs[i].protocol);

            if (def->srvs[i].domain)
                virBufferAsprintf(buf, " domain='%s'", def->srvs[i].domain);
            if (def->srvs[i].target)
                virBufferAsprintf(buf, " target='%s'", def->srvs[i].target);
            if (def->srvs[i].port)
                virBufferAsprintf(buf, " port='%d'", def->srvs[i].port);
            if (def->srvs[i].priority)
                virBufferAsprintf(buf, " priority='%d'", def->srvs[i].priority);
            if (def->srvs[i].weight)
                virBufferAsprintf(buf, " weight='%d'", def->srvs[i].weight);

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

    if (def->nhosts) {
        for (i = 0; i < def->nhosts; i++) {
            char *ip = virSocketAddrFormat(&def->hosts[i].ip);

            virBufferAsprintf(buf, "<host ip='%s'>\n", ip);
            virBufferAdjustIndent(buf, 2);
            for (j = 0; j < def->hosts[i].nnames; j++)
                virBufferAsprintf(buf, "<hostname>%s</hostname>\n",
                                  def->hosts[i].names[j]);

            virBufferAdjustIndent(buf, -2);
            virBufferAddLit(buf, "</host>\n");
            VIR_FREE(ip);
        }
    }
    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</dns>\n");
out:
    return result;
}

static int
virNetworkIpDefFormat(virBufferPtr buf,
                      const virNetworkIpDefPtr def)
{
    int result = -1;

    virBufferAddLit(buf, "<ip");

    if (def->family) {
        virBufferAsprintf(buf, " family='%s'", def->family);
    }
    if (VIR_SOCKET_ADDR_VALID(&def->address)) {
        char *addr = virSocketAddrFormat(&def->address);
        if (!addr)
            goto error;
        virBufferAsprintf(buf, " address='%s'", addr);
        VIR_FREE(addr);
    }
    if (VIR_SOCKET_ADDR_VALID(&def->netmask)) {
        char *addr = virSocketAddrFormat(&def->netmask);
        if (!addr)
            goto error;
        virBufferAsprintf(buf, " netmask='%s'", addr);
        VIR_FREE(addr);
    }
    if (def->prefix > 0) {
        virBufferAsprintf(buf," prefix='%u'", def->prefix);
    }
    virBufferAddLit(buf, ">\n");
    virBufferAdjustIndent(buf, 2);

    if (def->tftproot) {
        virBufferEscapeString(buf, "<tftp root='%s' />\n",
                              def->tftproot);
    }
    if ((def->nranges || def->nhosts)) {
        size_t i;
        virBufferAddLit(buf, "<dhcp>\n");
        virBufferAdjustIndent(buf, 2);

        for (i = 0; i < def->nranges; i++) {
            char *saddr = virSocketAddrFormat(&def->ranges[i].start);
            if (!saddr)
                goto error;
            char *eaddr = virSocketAddrFormat(&def->ranges[i].end);
            if (!eaddr) {
                VIR_FREE(saddr);
                goto error;
            }
            virBufferAsprintf(buf, "<range start='%s' end='%s' />\n",
                              saddr, eaddr);
            VIR_FREE(saddr);
            VIR_FREE(eaddr);
        }
        for (i = 0; i < def->nhosts; i++) {
            virBufferAddLit(buf, "<host ");
            if (def->hosts[i].mac)
                virBufferAsprintf(buf, "mac='%s' ", def->hosts[i].mac);
            if (def->hosts[i].id)
                virBufferAsprintf(buf, "id='%s' ", def->hosts[i].id);
            if (def->hosts[i].name)
                virBufferAsprintf(buf, "name='%s' ", def->hosts[i].name);
            if (VIR_SOCKET_ADDR_VALID(&def->hosts[i].ip)) {
                char *ipaddr = virSocketAddrFormat(&def->hosts[i].ip);
                if (!ipaddr)
                    goto error;
                virBufferAsprintf(buf, "ip='%s' ", ipaddr);
                VIR_FREE(ipaddr);
            }
            virBufferAddLit(buf, "/>\n");
        }
        if (def->bootfile) {
            virBufferEscapeString(buf, "<bootp file='%s' ",
                                  def->bootfile);
            if (VIR_SOCKET_ADDR_VALID(&def->bootserver)) {
                char *ipaddr = virSocketAddrFormat(&def->bootserver);
                if (!ipaddr)
                    goto error;
                virBufferEscapeString(buf, "server='%s' ", ipaddr);
                VIR_FREE(ipaddr);
            }
            virBufferAddLit(buf, "/>\n");

        }

        virBufferAdjustIndent(buf, -2);
        virBufferAddLit(buf, "</dhcp>\n");
    }

    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</ip>\n");

    result = 0;
error:
    return result;
}

static int
virNetworkRouteDefFormat(virBufferPtr buf,
                      const virNetworkRouteDefPtr def)
{
    int result = -1;

    virBufferAddLit(buf, "<route");

    if (def->family) {
        virBufferAsprintf(buf, " family='%s'", def->family);
    }
    if (VIR_SOCKET_ADDR_VALID(&def->address)) {
        char *addr = virSocketAddrFormat(&def->address);

        if (!addr)
            goto error;
        virBufferAsprintf(buf, " address='%s'", addr);
        VIR_FREE(addr);
    }
    if (VIR_SOCKET_ADDR_VALID(&def->netmask)) {
        char *addr = virSocketAddrFormat(&def->netmask);

        if (!addr)
            goto error;
        virBufferAsprintf(buf, " netmask='%s'", addr);
        VIR_FREE(addr);
    }
    if (def->has_prefix) {
        virBufferAsprintf(buf," prefix='%u'", def->prefix);
    }
    if (VIR_SOCKET_ADDR_VALID(&def->gateway)) {
        char *addr = virSocketAddrFormat(&def->gateway);
        if (!addr)
            goto error;
        virBufferAsprintf(buf, " gateway='%s'", addr);
        VIR_FREE(addr);
    }
    if (def->has_metric && def->metric > 0) {
        virBufferAsprintf(buf," metric='%u'", def->metric);
    }
    virBufferAddLit(buf, "/>\n");

    result = 0;
error:
    return result;
}

static int
virPortGroupDefFormat(virBufferPtr buf,
                      const virPortGroupDefPtr def)
{
    virBufferAsprintf(buf, "<portgroup name='%s'", def->name);
    if (def->isDefault) {
        virBufferAddLit(buf, " default='yes'");
    }
    virBufferAddLit(buf, ">\n");
    virBufferAdjustIndent(buf, 2);
    if (virNetDevVlanFormat(&def->vlan, buf) < 0)
        return -1;
    if (virNetDevVPortProfileFormat(def->virtPortProfile, buf) < 0)
        return -1;
    virNetDevBandwidthFormat(def->bandwidth, buf);
    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</portgroup>\n");
    return 0;
}

static int
virNetworkForwardNatDefFormat(virBufferPtr buf,
                              const virNetworkForwardDefPtr fwd)
{
    char *addrStart = NULL;
    char *addrEnd = NULL;
    int ret = -1;

    if (VIR_SOCKET_ADDR_VALID(&fwd->addr.start)) {
        addrStart = virSocketAddrFormat(&fwd->addr.start);
        if (!addrStart)
            goto cleanup;
    }

    if (VIR_SOCKET_ADDR_VALID(&fwd->addr.end)) {
        addrEnd = virSocketAddrFormat(&fwd->addr.end);
        if (!addrEnd)
            goto cleanup;
    }

    if (!addrEnd && !addrStart && !fwd->port.start && !fwd->port.end)
        return 0;

    virBufferAddLit(buf, "<nat>\n");
    virBufferAdjustIndent(buf, 2);

    if (addrStart) {
        virBufferAsprintf(buf, "<address start='%s'", addrStart);
        if (addrEnd)
            virBufferAsprintf(buf, " end='%s'", addrEnd);
        virBufferAddLit(buf, "/>\n");
    }

    if (fwd->port.start || fwd->port.end) {
        virBufferAsprintf(buf, "<port start='%d'", fwd->port.start);
        if (fwd->port.end)
            virBufferAsprintf(buf, " end='%d'", fwd->port.end);
        virBufferAddLit(buf, "/>\n");
    }

    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</nat>\n");
    ret = 0;

cleanup:
    VIR_FREE(addrStart);
    VIR_FREE(addrEnd);
    return ret;
}

static int
virNetworkDefFormatInternal(virBufferPtr buf,
                            const virNetworkDefPtr def,
                            unsigned int flags)
{
    unsigned char *uuid;
    char uuidstr[VIR_UUID_STRING_BUFLEN];
    size_t i;
    int shortforward;

    virBufferAddLit(buf, "<network");
    if (!(flags & VIR_NETWORK_XML_INACTIVE) && (def->connections > 0)) {
        virBufferAsprintf(buf, " connections='%d'", def->connections);
    }
    if (def->ipv6nogw)
        virBufferAddLit(buf, " ipv6='yes'");
    virBufferAddLit(buf, ">\n");
    virBufferAdjustIndent(buf, 2);
    virBufferEscapeString(buf, "<name>%s</name>\n", def->name);

    uuid = def->uuid;
    virUUIDFormat(uuid, uuidstr);
    virBufferAsprintf(buf, "<uuid>%s</uuid>\n", uuidstr);

    if (def->forward.type != VIR_NETWORK_FORWARD_NONE) {
        const char *dev = NULL;
        if (!def->forward.npfs)
            dev = virNetworkDefForwardIf(def, 0);
        const char *mode = virNetworkForwardTypeToString(def->forward.type);

        if (!mode) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Unknown forward type %d in network '%s'"),
                           def->forward.type, def->name);
            goto error;
        }
        virBufferAddLit(buf, "<forward");
        virBufferEscapeString(buf, " dev='%s'", dev);
        virBufferAsprintf(buf, " mode='%s'", mode);
        if (def->forward.type == VIR_NETWORK_FORWARD_HOSTDEV) {
            if (def->forward.managed)
                virBufferAddLit(buf, " managed='yes'");
            else
                virBufferAddLit(buf, " managed='no'");
        }
        shortforward = !(def->forward.nifs || def->forward.npfs
                         || VIR_SOCKET_ADDR_VALID(&def->forward.addr.start)
                         || VIR_SOCKET_ADDR_VALID(&def->forward.addr.end)
                         || def->forward.port.start
                         || def->forward.port.end
                         || (def->forward.driverName
                             != VIR_NETWORK_FORWARD_DRIVER_NAME_DEFAULT));
        virBufferAsprintf(buf, "%s>\n", shortforward ? "/" : "");
        virBufferAdjustIndent(buf, 2);

        if (def->forward.driverName
            != VIR_NETWORK_FORWARD_DRIVER_NAME_DEFAULT) {
            const char *driverName
                = virNetworkForwardDriverNameTypeToString(def->forward.driverName);
            if (!driverName) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("unexpected hostdev driver name type %d "),
                               def->forward.driverName);
                goto error;
            }
            virBufferAsprintf(buf, "<driver name='%s'/>\n", driverName);
        }
        if (def->forward.type == VIR_NETWORK_FORWARD_NAT) {
            if (virNetworkForwardNatDefFormat(buf, &def->forward) < 0)
                goto error;
        }

        /* For now, hard-coded to at most 1 forward.pfs */
        if (def->forward.npfs)
            virBufferEscapeString(buf, "<pf dev='%s'/>\n",
                                  def->forward.pfs[0].dev);

        if (def->forward.nifs &&
            (!def->forward.npfs || !(flags & VIR_NETWORK_XML_INACTIVE))) {
            for (i = 0; i < def->forward.nifs; i++) {
                if (def->forward.type != VIR_NETWORK_FORWARD_HOSTDEV) {
                    virBufferEscapeString(buf, "<interface dev='%s'",
                                          def->forward.ifs[i].device.dev);
                    if (!(flags & VIR_NETWORK_XML_INACTIVE) &&
                        (def->forward.ifs[i].connections > 0)) {
                        virBufferAsprintf(buf, " connections='%d'",
                                          def->forward.ifs[i].connections);
                    }
                    virBufferAddLit(buf, "/>\n");
                }
                else {
                    if (def->forward.ifs[i].type ==  VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI) {
                        if (virDevicePCIAddressFormat(buf,
                                                      def->forward.ifs[i].device.pci,
                                                      true) < 0)
                            goto error;
                    }
                }
            }
        }
        virBufferAdjustIndent(buf, -2);
        if (!shortforward)
            virBufferAddLit(buf, "</forward>\n");
    }

    if (def->forward.type == VIR_NETWORK_FORWARD_NONE ||
         def->forward.type == VIR_NETWORK_FORWARD_NAT ||
         def->forward.type == VIR_NETWORK_FORWARD_ROUTE) {

        virBufferAddLit(buf, "<bridge");
        if (def->bridge)
            virBufferEscapeString(buf, " name='%s'", def->bridge);
        virBufferAsprintf(buf, " stp='%s' delay='%ld' />\n",
                          def->stp ? "on" : "off",
                          def->delay);
    } else if (def->forward.type == VIR_NETWORK_FORWARD_BRIDGE &&
               def->bridge) {
        virBufferEscapeString(buf, "<bridge name='%s' />\n", def->bridge);
    }


    if (def->mac_specified) {
        char macaddr[VIR_MAC_STRING_BUFLEN];
        virMacAddrFormat(&def->mac, macaddr);
        virBufferAsprintf(buf, "<mac address='%s'/>\n", macaddr);
    }

    if (def->domain)
        virBufferAsprintf(buf, "<domain name='%s'/>\n", def->domain);

    if (virNetworkDNSDefFormat(buf, &def->dns) < 0)
        goto error;

    if (virNetDevVlanFormat(&def->vlan, buf) < 0)
        goto error;
    if (virNetDevBandwidthFormat(def->bandwidth, buf) < 0)
        goto error;

    for (i = 0; i < def->nips; i++) {
        if (virNetworkIpDefFormat(buf, &def->ips[i]) < 0)
            goto error;
    }

    for (i = 0; i < def->nroutes; i++) {
        if (virNetworkRouteDefFormat(buf, &def->routes[i]) < 0)
            goto error;
    }

    if (virNetDevVPortProfileFormat(def->virtPortProfile, buf) < 0)
        goto error;

    for (i = 0; i < def->nPortGroups; i++)
        if (virPortGroupDefFormat(buf, &def->portGroups[i]) < 0)
            goto error;

    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</network>\n");

    return 0;

error:
    return -1;
}

char *
virNetworkDefFormat(virNetworkDefPtr def,
                    unsigned int flags)
{
    virBuffer buf = VIR_BUFFER_INITIALIZER;

    if (virNetworkDefFormatInternal(&buf, def, flags) < 0)
        goto error;

    if (virBufferError(&buf))
        goto no_memory;

    return virBufferContentAndReset(&buf);

no_memory:
    virReportOOMError();
error:
    virBufferFreeAndReset(&buf);
    return NULL;
}

static char *
virNetworkObjFormat(virNetworkObjPtr net,
                    unsigned int flags)
{
    virBuffer buf = VIR_BUFFER_INITIALIZER;
    char *class_id = virBitmapFormat(net->class_id);

    if (!class_id)
        goto no_memory;

    virBufferAddLit(&buf, "<networkstatus>\n");
    virBufferAsprintf(&buf, "  <class_id bitmap='%s'/>\n", class_id);
    virBufferAsprintf(&buf, "  <floor sum='%llu'/>\n", net->floor_sum);
    VIR_FREE(class_id);

    virBufferAdjustIndent(&buf, 2);
    if (virNetworkDefFormatInternal(&buf, net->def, flags) < 0)
        goto error;

    virBufferAdjustIndent(&buf, -2);
    virBufferAddLit(&buf, "</networkstatus>");

    if (virBufferError(&buf))
        goto no_memory;

    return virBufferContentAndReset(&buf);

no_memory:
    virReportOOMError();
error:
    virBufferFreeAndReset(&buf);
    return NULL;
}

virPortGroupDefPtr virPortGroupFindByName(virNetworkDefPtr net,
                                          const char *portgroup)
{
    size_t i;
    for (i = 0; i < net->nPortGroups; i++) {
        if (portgroup) {
            if (STREQ(portgroup, net->portGroups[i].name))
                return &net->portGroups[i];
        } else {
            if (net->portGroups[i].isDefault)
                return &net->portGroups[i];
        }
    }
    return NULL;
}

int virNetworkSaveXML(const char *configDir,
                      virNetworkDefPtr def,
                      const char *xml)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN];
    char *configFile = NULL;
    int ret = -1;

    if ((configFile = virNetworkConfigFile(configDir, def->name)) == NULL)
        goto cleanup;

    if (virFileMakePath(configDir) < 0) {
        virReportSystemError(errno,
                             _("cannot create config directory '%s'"),
                             configDir);
        goto cleanup;
    }

    virUUIDFormat(def->uuid, uuidstr);
    ret = virXMLSaveFile(configFile,
                         virXMLPickShellSafeComment(def->name, uuidstr),
                         "net-edit", xml);

 cleanup:
    VIR_FREE(configFile);
    return ret;
}

int virNetworkSaveConfig(const char *configDir,
                         virNetworkDefPtr def)
{
    int ret = -1;
    char *xml;

    if (!(xml = virNetworkDefFormat(def, VIR_NETWORK_XML_INACTIVE)))
        goto cleanup;

    if (virNetworkSaveXML(configDir, def, xml))
        goto cleanup;

    ret = 0;
cleanup:
    VIR_FREE(xml);
    return ret;
}


int virNetworkSaveStatus(const char *statusDir,
                         virNetworkObjPtr network)
{
    int ret = -1;
    int flags = 0;
    char *xml;

    if (!(xml = virNetworkObjFormat(network, flags)))
        goto cleanup;

    if (virNetworkSaveXML(statusDir, network->def, xml))
        goto cleanup;

    ret = 0;
cleanup:
    VIR_FREE(xml);
    return ret;
}

virNetworkObjPtr
virNetworkLoadState(virNetworkObjListPtr nets,
                    const char *stateDir,
                    const char *name)
{
    char *configFile = NULL;
    virNetworkDefPtr def = NULL;
    virNetworkObjPtr net = NULL;
    xmlDocPtr xml = NULL;
    xmlNodePtr node = NULL;
    xmlXPathContextPtr ctxt = NULL;
    virBitmapPtr class_id_map = NULL;
    unsigned long long floor_sum_val = 0;


    if ((configFile = virNetworkConfigFile(stateDir, name)) == NULL)
        goto error;

    if (!(xml = virXMLParseCtxt(configFile, NULL, _("(network status)"), &ctxt)))
        goto error;

    if (!(node = virXPathNode("//network", ctxt))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not find any 'network' element in status file"));
        goto error;
    }

    /* parse the definition first */
    ctxt->node = node;
    if (!(def = virNetworkDefParseXML(ctxt)))
        goto error;

    if (!STREQ(name, def->name)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Network config filename '%s'"
                         " does not match network name '%s'"),
                       configFile, def->name);
        goto error;
    }

    /* now parse possible status data */
    node = xmlDocGetRootElement(xml);
    if (xmlStrEqual(node->name, BAD_CAST "networkstatus")) {
        /* Newer network status file. Contains useful
         * info which are not to be found in bare config XML */
        char *class_id = NULL;
        char *floor_sum = NULL;

        ctxt->node = node;
        if ((class_id = virXPathString("string(./class_id[1]/@bitmap)", ctxt))) {
            if (virBitmapParse(class_id, 0, &class_id_map,
                               CLASS_ID_BITMAP_SIZE) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Malformed 'class_id' attribute: %s"),
                               class_id);
                VIR_FREE(class_id);
                goto error;
            }
        }
        VIR_FREE(class_id);

        floor_sum = virXPathString("string(./floor[1]/@sum)", ctxt);
        if (floor_sum &&
            virStrToLong_ull(floor_sum, NULL, 10, &floor_sum_val) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Malformed 'floor_sum' attribute: %s"),
                           floor_sum);
            VIR_FREE(floor_sum);
        }
        VIR_FREE(floor_sum);
    }

    /* create the object */
    if (!(net = virNetworkAssignDef(nets, def, true)))
        goto error;
    /* do not put any "goto error" below this comment */

    /* assign status data stored in the network object */
    if (class_id_map) {
        virBitmapFree(net->class_id);
        net->class_id = class_id_map;
    }

    if (floor_sum_val > 0)
        net->floor_sum = floor_sum_val;

cleanup:
    VIR_FREE(configFile);
    xmlFreeDoc(xml);
    xmlXPathFreeContext(ctxt);
    return net;

error:
    virBitmapFree(class_id_map);
    virNetworkDefFree(def);
    goto cleanup;
}

virNetworkObjPtr virNetworkLoadConfig(virNetworkObjListPtr nets,
                                      const char *configDir,
                                      const char *autostartDir,
                                      const char *name)
{
    char *configFile = NULL, *autostartLink = NULL;
    virNetworkDefPtr def = NULL;
    virNetworkObjPtr net;
    int autostart;

    if ((configFile = virNetworkConfigFile(configDir, name)) == NULL)
        goto error;
    if ((autostartLink = virNetworkConfigFile(autostartDir, name)) == NULL)
        goto error;

    if ((autostart = virFileLinkPointsTo(autostartLink, configFile)) < 0)
        goto error;

    if (!(def = virNetworkDefParseFile(configFile)))
        goto error;

    if (!STREQ(name, def->name)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Network config filename '%s'"
                         " does not match network name '%s'"),
                       configFile, def->name);
        goto error;
    }

    if (def->forward.type == VIR_NETWORK_FORWARD_NONE ||
        def->forward.type == VIR_NETWORK_FORWARD_NAT ||
        def->forward.type == VIR_NETWORK_FORWARD_ROUTE) {

        /* Generate a bridge if none is specified, but don't check for collisions
         * if a bridge is hardcoded, so the network is at least defined.
         */
        if (virNetworkSetBridgeName(nets, def, 0))
            goto error;
    }

    if (!(net = virNetworkAssignDef(nets, def, false)))
        goto error;

    net->autostart = autostart;
    net->persistent = 1;

    VIR_FREE(configFile);
    VIR_FREE(autostartLink);

    return net;

error:
    VIR_FREE(configFile);
    VIR_FREE(autostartLink);
    virNetworkDefFree(def);
    return NULL;
}


int
virNetworkLoadAllState(virNetworkObjListPtr nets,
                       const char *stateDir)
{
    DIR *dir;
    struct dirent *entry;

    if (!(dir = opendir(stateDir))) {
        if (errno == ENOENT)
            return 0;

        virReportSystemError(errno, _("Failed to open dir '%s'"), stateDir);
        return -1;
    }

    while ((entry = readdir(dir))) {
        virNetworkObjPtr net;

        if (entry->d_name[0] == '.')
            continue;

        if (!virFileStripSuffix(entry->d_name, ".xml"))
            continue;

        if ((net = virNetworkLoadState(nets, stateDir, entry->d_name)))
            virNetworkObjUnlock(net);
    }

    closedir(dir);
    return 0;
}


int virNetworkLoadAllConfigs(virNetworkObjListPtr nets,
                             const char *configDir,
                             const char *autostartDir)
{
    DIR *dir;
    struct dirent *entry;

    if (!(dir = opendir(configDir))) {
        if (errno == ENOENT)
            return 0;
        virReportSystemError(errno,
                             _("Failed to open dir '%s'"),
                             configDir);
        return -1;
    }

    while ((entry = readdir(dir))) {
        virNetworkObjPtr net;

        if (entry->d_name[0] == '.')
            continue;

        if (!virFileStripSuffix(entry->d_name, ".xml"))
            continue;

        /* NB: ignoring errors, so one malformed config doesn't
           kill the whole process */
        net = virNetworkLoadConfig(nets,
                                   configDir,
                                   autostartDir,
                                   entry->d_name);
        if (net)
            virNetworkObjUnlock(net);
    }

    closedir(dir);

    return 0;
}

int virNetworkDeleteConfig(const char *configDir,
                           const char *autostartDir,
                           virNetworkObjPtr net)
{
    char *configFile = NULL;
    char *autostartLink = NULL;
    int ret = -1;

    if ((configFile = virNetworkConfigFile(configDir, net->def->name)) == NULL)
        goto error;
    if ((autostartLink = virNetworkConfigFile(autostartDir, net->def->name)) == NULL)
        goto error;

    /* Not fatal if this doesn't work */
    unlink(autostartLink);

    if (unlink(configFile) < 0) {
        virReportSystemError(errno,
                             _("cannot remove config file '%s'"),
                             configFile);
        goto error;
    }

    ret = 0;

error:
    VIR_FREE(configFile);
    VIR_FREE(autostartLink);
    return ret;
}

char *virNetworkConfigFile(const char *dir,
                           const char *name)
{
    char *ret = NULL;

    ignore_value(virAsprintf(&ret, "%s/%s.xml", dir, name));
    return ret;
}

int virNetworkBridgeInUse(const virNetworkObjListPtr nets,
                          const char *bridge,
                          const char *skipname)
{
    size_t i;
    unsigned int ret = 0;

    for (i = 0; i < nets->count; i++) {
        virNetworkObjLock(nets->objs[i]);
        if (nets->objs[i]->def->bridge &&
            STREQ(nets->objs[i]->def->bridge, bridge) &&
            !(skipname && STREQ(nets->objs[i]->def->name, skipname)))
                ret = 1;
        virNetworkObjUnlock(nets->objs[i]);
    }

    return ret;
}

char *virNetworkAllocateBridge(const virNetworkObjListPtr nets,
                               const char *template)
{

    int id = 0;
    char *newname;

    if (!template)
        template = "virbr%d";

    do {
        if (virAsprintf(&newname, template, id) < 0)
            return NULL;
        if (!virNetworkBridgeInUse(nets, newname, NULL)) {
            return newname;
        }
        VIR_FREE(newname);

        id++;
    } while (id <= MAX_BRIDGE_ID);

    virReportError(VIR_ERR_INTERNAL_ERROR,
                   _("Bridge generation exceeded max id %d"),
                   MAX_BRIDGE_ID);
    return NULL;
}

int virNetworkSetBridgeName(const virNetworkObjListPtr nets,
                            virNetworkDefPtr def,
                            int check_collision) {

    int ret = -1;

    if (def->bridge && !strstr(def->bridge, "%d")) {
        /* We may want to skip collision detection in this case (ex. when
         * loading configs at daemon startup, so the network is at least
         * defined. */
        if (check_collision &&
            virNetworkBridgeInUse(nets, def->bridge, def->name)) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("bridge name '%s' already in use."),
                           def->bridge);
            goto error;
        }
    } else {
        /* Allocate a bridge name */
        if (!(def->bridge = virNetworkAllocateBridge(nets, def->bridge)))
            goto error;
    }

    ret = 0;
error:
    return ret;
}


void virNetworkSetBridgeMacAddr(virNetworkDefPtr def)
{
    if (!def->mac_specified) {
        /* if the bridge doesn't have a mac address explicitly defined,
         * autogenerate a random one.
         */
        virMacAddrGenerate((unsigned char[]){ 0x52, 0x54, 0 },
                           &def->mac);
        def->mac_specified = true;
    }
}

/* NetworkObj backend of the virNetworkUpdate API */

static void
virNetworkDefUpdateNoSupport(virNetworkDefPtr def, const char *section)
{
    virReportError(VIR_ERR_NO_SUPPORT,
                   _("can't update '%s' section of network '%s'"),
                   section, def->name);
}
static void
virNetworkDefUpdateUnknownCommand(unsigned int command)
{
    virReportError(VIR_ERR_NO_SUPPORT,
                   _("unrecognized network update command code %d"), command);
}

static int
virNetworkDefUpdateCheckElementName(virNetworkDefPtr def,
                                    xmlNodePtr node,
                                    const char *section)
{
    if (!xmlStrEqual(node->name, BAD_CAST section)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("unexpected element <%s>, expecting <%s>, "
                         "while updating network '%s'"),
                       node->name, section, def->name);
        return -1;
    }
    return 0;
}

static int
virNetworkDefUpdateBridge(virNetworkDefPtr def,
                          unsigned int command ATTRIBUTE_UNUSED,
                          int parentIndex ATTRIBUTE_UNUSED,
                          xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                          /* virNetworkUpdateFlags */
                          unsigned int fflags ATTRIBUTE_UNUSED)
{
    virNetworkDefUpdateNoSupport(def, "bridge");
    return -1;
}

static int
virNetworkDefUpdateDomain(virNetworkDefPtr def,
                          unsigned int command ATTRIBUTE_UNUSED,
                          int parentIndex ATTRIBUTE_UNUSED,
                          xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                          /* virNetworkUpdateFlags */
                          unsigned int fflags ATTRIBUTE_UNUSED)
{
    virNetworkDefUpdateNoSupport(def, "domain");
    return -1;
}

static int
virNetworkDefUpdateIP(virNetworkDefPtr def,
                      unsigned int command ATTRIBUTE_UNUSED,
                      int parentIndex ATTRIBUTE_UNUSED,
                      xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                      /* virNetworkUpdateFlags */
                      unsigned int fflags ATTRIBUTE_UNUSED)
{
    virNetworkDefUpdateNoSupport(def, "ip");
    return -1;
}

static virNetworkIpDefPtr
virNetworkIpDefByIndex(virNetworkDefPtr def, int parentIndex)
{
    virNetworkIpDefPtr ipdef = NULL;
    size_t i;

    /* first find which ip element's dhcp host list to work on */
    if (parentIndex >= 0) {
        ipdef = virNetworkDefGetIpByIndex(def, AF_UNSPEC, parentIndex);
        if (!(ipdef)) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't update dhcp host entry - no <ip> "
                             "element found at index %d in network '%s'"),
                           parentIndex, def->name);
        }
        return ipdef;
    }

    /* -1 means "find the most appropriate", which in this case
     * means the one and only <ip> that has <dhcp> element
     */
    for (i = 0;
         (ipdef = virNetworkDefGetIpByIndex(def, AF_UNSPEC, i));
         i++) {
        if (ipdef->nranges || ipdef->nhosts)
            break;
    }
    if (!ipdef) {
        ipdef = virNetworkDefGetIpByIndex(def, AF_INET, 0);
        if (!ipdef)
            ipdef = virNetworkDefGetIpByIndex(def, AF_INET6, 0);
    }
    if (!ipdef) {
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("couldn't update dhcp host entry - no <ip> "
                         "element found in network '%s'"), def->name);
    }
    return ipdef;
}

static int
virNetworkDefUpdateIPDHCPHost(virNetworkDefPtr def,
                              unsigned int command,
                              int parentIndex,
                              xmlXPathContextPtr ctxt,
                              /* virNetworkUpdateFlags */
                              unsigned int fflags ATTRIBUTE_UNUSED)
{
    size_t i;
    int ret = -1;
    virNetworkIpDefPtr ipdef = virNetworkIpDefByIndex(def, parentIndex);
    virNetworkDHCPHostDef host;

    memset(&host, 0, sizeof(host));

    if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "host") < 0)
        goto cleanup;

    /* ipdef is the ip element that needs its host array updated */
    if (!ipdef)
        goto cleanup;

    /* parse the xml into a virNetworkDHCPHostDef */
    if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) {

        if (virNetworkDHCPHostDefParseXML(def->name, ipdef,
                                          ctxt->node, &host, false) < 0) {
            goto cleanup;
        }

        /* search for the entry with this (mac|name),
         * and update the IP+(mac|name) */
        for (i = 0; i < ipdef->nhosts; i++) {
            if ((host.mac &&
                 !virMacAddrCompare(host.mac, ipdef->hosts[i].mac)) ||
                (host.name &&
                 STREQ_NULLABLE(host.name, ipdef->hosts[i].name))) {
                break;
            }
        }

        if (i == ipdef->nhosts) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't locate an existing dhcp host entry with "
                             "\"mac='%s'\" in network '%s'"),
                           host.mac, def->name);
            goto cleanup;
        }

        /* clear the existing hosts entry, move the new one in its place,
         * then clear out the extra copy to get rid of the duplicate pointers
         * to its data (mac and name strings).
         */
        virNetworkDHCPHostDefClear(&ipdef->hosts[i]);
        ipdef->hosts[i] = host;
        memset(&host, 0, sizeof(host));

    } else if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) ||
               (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) {

        if (virNetworkDHCPHostDefParseXML(def->name, ipdef,
                                          ctxt->node, &host, true) < 0) {
            goto cleanup;
        }

        /* log error if an entry with same name/address/ip already exists */
        for (i = 0; i < ipdef->nhosts; i++) {
            if ((host.mac &&
                 !virMacAddrCompare(host.mac, ipdef->hosts[i].mac)) ||
                (host.name &&
                 STREQ_NULLABLE(host.name, ipdef->hosts[i].name)) ||
                (VIR_SOCKET_ADDR_VALID(&host.ip) &&
                 virSocketAddrEqual(&host.ip, &ipdef->hosts[i].ip))) {
                char *ip = virSocketAddrFormat(&host.ip);

                virReportError(VIR_ERR_OPERATION_INVALID,
                               _("there is an existing dhcp host entry in "
                                 "network '%s' that matches "
                                 "\"<host mac='%s' name='%s' ip='%s'/>\""),
                               def->name, host.mac, host.name,
                               ip ? ip : "unknown");
                VIR_FREE(ip);
                goto cleanup;
            }
        }

        /* add to beginning/end of list */
        if (VIR_INSERT_ELEMENT(ipdef->hosts,
                               command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST
                               ? 0 : ipdef->nhosts,
                               ipdef->nhosts, host) < 0)
            goto cleanup;
    } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) {

        if (virNetworkDHCPHostDefParseXML(def->name, ipdef,
                                          ctxt->node, &host, false) < 0) {
            goto cleanup;
        }

        /* find matching entry - all specified attributes must match */
        for (i = 0; i < ipdef->nhosts; i++) {
            if ((!host.mac ||
                 !virMacAddrCompare(host.mac, ipdef->hosts[i].mac)) &&
                (!host.name ||
                 STREQ_NULLABLE(host.name, ipdef->hosts[i].name)) &&
                (!VIR_SOCKET_ADDR_VALID(&host.ip) ||
                 virSocketAddrEqual(&host.ip, &ipdef->hosts[i].ip))) {
                break;
            }
        }
        if (i == ipdef->nhosts) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't locate a matching dhcp host entry "
                             "in network '%s'"), def->name);
            goto cleanup;
        }

        /* remove it */
        virNetworkDHCPHostDefClear(&ipdef->hosts[i]);
        VIR_DELETE_ELEMENT(ipdef->hosts, i, ipdef->nhosts);

    } else {
        virNetworkDefUpdateUnknownCommand(command);
        goto cleanup;
    }

    ret = 0;
cleanup:
    virNetworkDHCPHostDefClear(&host);
    return ret;
}

static int
virNetworkDefUpdateIPDHCPRange(virNetworkDefPtr def,
                               unsigned int command,
                               int parentIndex ATTRIBUTE_UNUSED,
                               xmlXPathContextPtr ctxt,
                               /* virNetworkUpdateFlags */
                               unsigned int fflags ATTRIBUTE_UNUSED)
{
    size_t i;
    int ret = -1;
    virNetworkIpDefPtr ipdef = virNetworkIpDefByIndex(def, parentIndex);
    virSocketAddrRange range;

    memset(&range, 0, sizeof(range));

    if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "range") < 0)
        goto cleanup;

    /* ipdef is the ip element that needs its range array updated */
    if (!ipdef)
        goto cleanup;

    /* parse the xml into a virSocketAddrRange */
    if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) {

        virReportError(VIR_ERR_NO_SUPPORT, "%s",
                       _("dhcp ranges cannot be modified, "
                         "only added or deleted"));
        goto cleanup;
    }

    if (virSocketAddrRangeParseXML(def->name, ctxt->node, &range) < 0)
        goto cleanup;

    /* check if an entry with same name/address/ip already exists */
    for (i = 0; i < ipdef->nranges; i++) {
        if (virSocketAddrEqual(&range.start, &ipdef->ranges[i].start) &&
            virSocketAddrEqual(&range.end, &ipdef->ranges[i].end)) {
            break;
        }
    }

    if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) ||
        (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) {

        if (i < ipdef->nranges) {
            char *startip = virSocketAddrFormat(&range.start);
            char *endip = virSocketAddrFormat(&range.end);

            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("there is an existing dhcp range entry in "
                             "network '%s' that matches "
                             "\"<range start='%s' end='%s'/>\""),
                           def->name,
                           startip ? startip : "unknown",
                           endip ? endip : "unknown");
            goto cleanup;
        }

        /* add to beginning/end of list */
        if (VIR_INSERT_ELEMENT(ipdef->ranges,
                               command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST
                               ? 0 : ipdef->nranges,
                               ipdef->nranges, range) < 0)
            goto cleanup;
    } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) {

        if (i == ipdef->nranges) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't locate a matching dhcp range entry "
                             "in network '%s'"), def->name);
            goto cleanup;
        }

        /* remove it */
        /* NB: nothing to clear from a RangeDef that's being freed */
        VIR_DELETE_ELEMENT(ipdef->ranges, i, ipdef->nranges);

    } else {
        virNetworkDefUpdateUnknownCommand(command);
        goto cleanup;
    }

    ret = 0;
cleanup:
    return ret;
}

static int
virNetworkDefUpdateForward(virNetworkDefPtr def,
                           unsigned int command ATTRIBUTE_UNUSED,
                           int parentIndex ATTRIBUTE_UNUSED,
                           xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                           /* virNetworkUpdateFlags */
                           unsigned int fflags ATTRIBUTE_UNUSED)
{
    virNetworkDefUpdateNoSupport(def, "forward");
    return -1;
}

static int
virNetworkDefUpdateForwardInterface(virNetworkDefPtr def,
                                    unsigned int command,
                                    int parentIndex ATTRIBUTE_UNUSED,
                                    xmlXPathContextPtr ctxt,
                                    /* virNetworkUpdateFlags */
                                    unsigned int fflags ATTRIBUTE_UNUSED)
{
    size_t i;
    int ret = -1;
    virNetworkForwardIfDef iface;

    memset(&iface, 0, sizeof(iface));

    if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "interface") < 0)
        goto cleanup;

    if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) {
        virReportError(VIR_ERR_NO_SUPPORT, "%s",
                       _("forward interface entries cannot be modified, "
                         "only added or deleted"));
        goto cleanup;
    }

    /* parsing this is so simple that it doesn't have its own function */
    iface.type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV;
    if (!(iface.device.dev = virXMLPropString(ctxt->node, "dev"))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("missing dev attribute in <interface> element"));
        goto cleanup;
    }

    /* check if an <interface> with same dev name already exists */
    for (i = 0; i < def->forward.nifs; i++) {
        if (def->forward.ifs[i].type
            == VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV &&
            STREQ(iface.device.dev, def->forward.ifs[i].device.dev))
            break;
    }

    if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) ||
        (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) {

        if (i < def->forward.nifs) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("there is an existing interface entry "
                             "in network '%s' that matches "
                             "\"<interface dev='%s'>\""),
                           def->name, iface.device.dev);
            goto cleanup;
        }

        /* add to beginning/end of list */
        if (VIR_INSERT_ELEMENT(def->forward.ifs,
                               command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST
                               ? 0 : def->forward.nifs,
                               def->forward.nifs, iface) < 0)
            goto cleanup;
    } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) {

        if (i == def->forward.nifs) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't find an interface entry "
                             "in network '%s' matching <interface dev='%s'>"),
                           def->name, iface.device.dev);
            goto cleanup;
        }

        /* fail if the interface is being used */
        if (def->forward.ifs[i].connections > 0) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("unable to delete interface '%s' "
                             "in network '%s'. It is currently being used "
                             " by %d domains."),
                           iface.device.dev, def->name,
                           def->forward.ifs[i].connections);
            goto cleanup;
        }

        /* remove it */
        virNetworkForwardIfDefClear(&def->forward.ifs[i]);
        VIR_DELETE_ELEMENT(def->forward.ifs, i, def->forward.nifs);

    } else {
        virNetworkDefUpdateUnknownCommand(command);
        goto cleanup;
    }

    ret = 0;
cleanup:
    virNetworkForwardIfDefClear(&iface);
    return ret;
}

static int
virNetworkDefUpdateForwardPF(virNetworkDefPtr def,
                             unsigned int command ATTRIBUTE_UNUSED,
                             int parentIndex ATTRIBUTE_UNUSED,
                             xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                             /* virNetworkUpdateFlags */
                             unsigned int fflags ATTRIBUTE_UNUSED)
{
    virNetworkDefUpdateNoSupport(def, "forward pf");
    return -1;
}

static int
virNetworkDefUpdatePortGroup(virNetworkDefPtr def,
                             unsigned int command,
                             int parentIndex ATTRIBUTE_UNUSED,
                             xmlXPathContextPtr ctxt,
                             /* virNetworkUpdateFlags */
                             unsigned int fflags ATTRIBUTE_UNUSED)
{
    size_t i;
    int foundName = -1, foundDefault = -1;
    int ret = -1;
    virPortGroupDef portgroup;

    memset(&portgroup, 0, sizeof(portgroup));

    if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "portgroup") < 0)
        goto cleanup;

    if (virNetworkPortGroupParseXML(&portgroup, ctxt->node, ctxt) < 0)
        goto cleanup;

    /* check if a portgroup with same name already exists */
    for (i = 0; i < def->nPortGroups; i++) {
        if (STREQ(portgroup.name, def->portGroups[i].name))
            foundName = i;
        if (def->portGroups[i].isDefault)
            foundDefault = i;
    }
    if (foundName == -1 &&
        ((command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) ||
         (command == VIR_NETWORK_UPDATE_COMMAND_DELETE))) {
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("couldn't find a portgroup entry "
                         "in network '%s' matching <portgroup name='%s'>"),
                       def->name, portgroup.name);
        goto cleanup;
    } else if (foundName >= 0 &&
               ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) ||
                (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST))) {
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("there is an existing portgroup entry in "
                         "network '%s' that matches "
                         "\"<portgroup name='%s'>\""),
                       def->name, portgroup.name);
        goto cleanup;
    }

    /* if there is already a different default, we can't make this
     * one the default.
     */
    if (command != VIR_NETWORK_UPDATE_COMMAND_DELETE &&
        portgroup.isDefault &&
        foundDefault >= 0 && foundDefault != foundName) {
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("a different portgroup entry in "
                         "network '%s' is already set as the default. "
                         "Only one default is allowed."),
                       def->name);
        goto cleanup;
    }

    if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) {

        /* replace existing entry */
        virPortGroupDefClear(&def->portGroups[foundName]);
        def->portGroups[foundName] = portgroup;
        memset(&portgroup, 0, sizeof(portgroup));

    } else if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) ||
        (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) {

        /* add to beginning/end of list */
        if (VIR_INSERT_ELEMENT(def->portGroups,
                               command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST
                               ? 0 : def->nPortGroups,
                               def->nPortGroups, portgroup) < 0)
            goto cleanup;
    } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) {

        /* remove it */
        virPortGroupDefClear(&def->portGroups[foundName]);
        VIR_DELETE_ELEMENT(def->portGroups, foundName, def->nPortGroups);

    } else {
        virNetworkDefUpdateUnknownCommand(command);
        goto cleanup;
    }

    ret = 0;
cleanup:
    virPortGroupDefClear(&portgroup);
    return ret;
}

static int
virNetworkDefUpdateDNSHost(virNetworkDefPtr def,
                           unsigned int command ATTRIBUTE_UNUSED,
                           int parentIndex ATTRIBUTE_UNUSED,
                           xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                           /* virNetworkUpdateFlags */
                           unsigned int fflags ATTRIBUTE_UNUSED)
{
    size_t i, j, k;
    int foundIdx = -1, ret = -1;
    virNetworkDNSDefPtr dns = &def->dns;
    virNetworkDNSHostDef host;
    bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ||
                  command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST);
    int foundCt = 0;

    memset(&host, 0, sizeof(host));

    if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) {
        virReportError(VIR_ERR_NO_SUPPORT, "%s",
                       _("DNS HOST records cannot be modified, "
                         "only added or deleted"));
        goto cleanup;
    }

    if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "host") < 0)
        goto cleanup;

    if (virNetworkDNSHostDefParseXML(def->name, ctxt->node, &host, !isAdd) < 0)
        goto cleanup;

    for (i = 0; i < dns->nhosts; i++) {
        bool foundThisTime = false;

        if (virSocketAddrEqual(&host.ip, &dns->hosts[i].ip))
            foundThisTime = true;

        for (j = 0; j < host.nnames && !foundThisTime; j++) {
            for (k = 0; k < dns->hosts[i].nnames && !foundThisTime; k++) {
                if (STREQ(host.names[j], dns->hosts[i].names[k]))
                    foundThisTime = true;
            }
        }
        if (foundThisTime) {
            foundCt++;
            foundIdx = i;
        }
    }

    if (isAdd) {

        if (foundCt > 0) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("there is already at least one DNS HOST "
                             "record with a matching field in network %s"),
                           def->name);
            goto cleanup;
        }

        /* add to beginning/end of list */
        if (VIR_INSERT_ELEMENT(dns->hosts,
                               command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST
                               ? 0 : dns->nhosts, dns->nhosts, host) < 0)
            goto cleanup;
    } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) {

        if (foundCt == 0) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't locate a matching DNS HOST "
                             "record in network %s"), def->name);
            goto cleanup;
        }
        if (foundCt > 1) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("multiple matching DNS HOST records were "
                             "found in network %s"), def->name);
            goto cleanup;
        }

        /* remove it */
        virNetworkDNSHostDefClear(&dns->hosts[foundIdx]);
        VIR_DELETE_ELEMENT(dns->hosts, foundIdx, dns->nhosts);

    } else {
        virNetworkDefUpdateUnknownCommand(command);
        goto cleanup;
    }

    ret = 0;
cleanup:
    virNetworkDNSHostDefClear(&host);
    return ret;
}

static int
virNetworkDefUpdateDNSSrv(virNetworkDefPtr def,
                          unsigned int command ATTRIBUTE_UNUSED,
                          int parentIndex ATTRIBUTE_UNUSED,
                          xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                          /* virNetworkUpdateFlags */
                          unsigned int fflags ATTRIBUTE_UNUSED)
{
    size_t i;
    int foundIdx = -1, ret = -1;
    virNetworkDNSDefPtr dns = &def->dns;
    virNetworkDNSSrvDef srv;
    bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ||
                  command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST);
    int foundCt = 0;

    memset(&srv, 0, sizeof(srv));

    if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) {
        virReportError(VIR_ERR_NO_SUPPORT, "%s",
                       _("DNS SRV records cannot be modified, "
                         "only added or deleted"));
        goto cleanup;
    }

    if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "srv") < 0)
        goto cleanup;

    if (virNetworkDNSSrvDefParseXML(def->name, ctxt->node, ctxt, &srv, !isAdd) < 0)
        goto cleanup;

    for (i = 0; i < dns->nsrvs; i++) {
        if ((!srv.domain || STREQ_NULLABLE(srv.domain, dns->srvs[i].domain)) &&
            (!srv.service || STREQ_NULLABLE(srv.service, dns->srvs[i].service)) &&
            (!srv.protocol || STREQ_NULLABLE(srv.protocol, dns->srvs[i].protocol)) &&
            (!srv.target || STREQ_NULLABLE(srv.target, dns->srvs[i].target))) {
            foundCt++;
            foundIdx = i;
        }
    }

    if (isAdd) {

        if (foundCt > 0) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("there is already at least one DNS SRV "
                             "record matching all specified fields in network %s"),
                           def->name);
            goto cleanup;
        }

        /* add to beginning/end of list */
        if (VIR_INSERT_ELEMENT(dns->srvs,
                               command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST
                               ? 0 : dns->nsrvs, dns->nsrvs, srv) < 0)
            goto cleanup;
    } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) {

        if (foundCt == 0) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't locate a matching DNS SRV "
                             "record in network %s"), def->name);
            goto cleanup;
        }
        if (foundCt > 1) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("multiple DNS SRV records matching all specified "
                             "fields were found in network %s"), def->name);
            goto cleanup;
        }

        /* remove it */
        virNetworkDNSSrvDefClear(&dns->srvs[foundIdx]);
        VIR_DELETE_ELEMENT(dns->srvs, foundIdx, dns->nsrvs);

    } else {
        virNetworkDefUpdateUnknownCommand(command);
        goto cleanup;
    }

    ret = 0;
cleanup:
    virNetworkDNSSrvDefClear(&srv);
    return ret;
}

static int
virNetworkDefUpdateDNSTxt(virNetworkDefPtr def,
                          unsigned int command ATTRIBUTE_UNUSED,
                          int parentIndex ATTRIBUTE_UNUSED,
                          xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED,
                          /* virNetworkUpdateFlags */
                          unsigned int fflags ATTRIBUTE_UNUSED)
{
    int foundIdx, ret = -1;
    virNetworkDNSDefPtr dns = &def->dns;
    virNetworkDNSTxtDef txt;
    bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ||
                  command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST);

    memset(&txt, 0, sizeof(txt));

    if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) {
        virReportError(VIR_ERR_NO_SUPPORT, "%s",
                       _("DNS TXT records cannot be modified, "
                         "only added or deleted"));
        goto cleanup;
    }

    if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "txt") < 0)
        goto cleanup;

    if (virNetworkDNSTxtDefParseXML(def->name, ctxt->node, &txt, !isAdd) < 0)
        goto cleanup;

    for (foundIdx = 0; foundIdx < dns->ntxts; foundIdx++) {
        if (STREQ(txt.name, dns->txts[foundIdx].name))
            break;
    }

    if (isAdd) {

        if (foundIdx < dns->ntxts) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("there is already a DNS TXT record "
                             "with name '%s' in network %s"),
                           txt.name, def->name);
            goto cleanup;
        }

        /* add to beginning/end of list */
        if (VIR_INSERT_ELEMENT(dns->txts,
                               command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST
                               ? 0 : dns->ntxts, dns->ntxts, txt) < 0)
            goto cleanup;
    } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) {

        if (foundIdx == dns->ntxts) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("couldn't locate a matching DNS TXT "
                             "record in network %s"), def->name);
            goto cleanup;
        }

        /* remove it */
        virNetworkDNSTxtDefClear(&dns->txts[foundIdx]);
        VIR_DELETE_ELEMENT(dns->txts, foundIdx, dns->ntxts);

    } else {
        virNetworkDefUpdateUnknownCommand(command);
        goto cleanup;
    }

    ret = 0;
cleanup:
    virNetworkDNSTxtDefClear(&txt);
    return ret;
}

static int
virNetworkDefUpdateSection(virNetworkDefPtr def,
                           unsigned int command, /* virNetworkUpdateCommand */
                           unsigned int section, /* virNetworkUpdateSection */
                           int parentIndex,
                           const char *xml,
                           unsigned int flags)  /* virNetworkUpdateFlags */
{
    int ret = -1;
    xmlDocPtr doc;
    xmlXPathContextPtr ctxt = NULL;

    if (!(doc = virXMLParseStringCtxt(xml, _("network_update_xml"), &ctxt)))
        goto cleanup;

    switch (section) {
    case VIR_NETWORK_SECTION_BRIDGE:
       ret = virNetworkDefUpdateBridge(def, command, parentIndex, ctxt, flags);
        break;

    case VIR_NETWORK_SECTION_DOMAIN:
        ret = virNetworkDefUpdateDomain(def, command, parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_IP:
        ret = virNetworkDefUpdateIP(def, command, parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_IP_DHCP_HOST:
        ret = virNetworkDefUpdateIPDHCPHost(def, command,
                                            parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_IP_DHCP_RANGE:
        ret = virNetworkDefUpdateIPDHCPRange(def, command,
                                             parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_FORWARD:
        ret = virNetworkDefUpdateForward(def, command,
                                         parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_FORWARD_INTERFACE:
        ret = virNetworkDefUpdateForwardInterface(def, command,
                                                  parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_FORWARD_PF:
        ret = virNetworkDefUpdateForwardPF(def, command,
                                           parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_PORTGROUP:
        ret = virNetworkDefUpdatePortGroup(def, command,
                                           parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_DNS_HOST:
        ret = virNetworkDefUpdateDNSHost(def, command,
                                         parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_DNS_TXT:
        ret = virNetworkDefUpdateDNSTxt(def, command, parentIndex, ctxt, flags);
        break;
    case VIR_NETWORK_SECTION_DNS_SRV:
        ret = virNetworkDefUpdateDNSSrv(def, command, parentIndex, ctxt, flags);
        break;
    default:
        virReportError(VIR_ERR_NO_SUPPORT, "%s",
                       _("can't update unrecognized section of network"));
        break;
    }

cleanup:
    xmlFreeDoc(doc);
    xmlXPathFreeContext(ctxt);
    return ret;
}

/*
 * virNetworkObjUpdate:
 *
 * Apply the supplied update to the given virNetworkObj. Except for
 * @network pointing to an actual network object rather than the
 * opaque virNetworkPtr, parameters are identical to the public API
 * virNetworkUpdate.
 *
 * The original virNetworkDefs are copied, and all modifications made
 * to these copies. The originals are replaced with the copies only
 * after success has been guaranteed.
 *
 * Returns: -1 on error, 0 on success.
 */
int
virNetworkObjUpdate(virNetworkObjPtr network,
                    unsigned int command, /* virNetworkUpdateCommand */
                    unsigned int section, /* virNetworkUpdateSection */
                    int parentIndex,
                    const char *xml,
                    unsigned int flags)  /* virNetworkUpdateFlags */
{
    int ret = -1;
    virNetworkDefPtr livedef = NULL, configdef = NULL;

    /* normalize config data, and check for common invalid requests. */
    if (virNetworkConfigChangeSetup(network, flags) < 0)
       goto cleanup;

    if (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE) {
        virNetworkDefPtr checkdef;

        /* work on a copy of the def */
        if (!(livedef = virNetworkDefCopy(network->def, 0)))
            goto cleanup;
        if (virNetworkDefUpdateSection(livedef, command, section,
                                       parentIndex, xml, flags) < 0) {
            goto cleanup;
        }
        /* run a final format/parse cycle to make sure we didn't
         * add anything illegal to the def
         */
        if (!(checkdef = virNetworkDefCopy(livedef, 0)))
            goto cleanup;
        virNetworkDefFree(checkdef);
    }

    if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) {
        virNetworkDefPtr checkdef;

        /* work on a copy of the def */
        if (!(configdef = virNetworkDefCopy(virNetworkObjGetPersistentDef(network),
                                            VIR_NETWORK_XML_INACTIVE))) {
            goto cleanup;
        }
        if (virNetworkDefUpdateSection(configdef, command, section,
                                       parentIndex, xml, flags) < 0) {
            goto cleanup;
        }
        if (!(checkdef = virNetworkDefCopy(configdef,
                                           VIR_NETWORK_XML_INACTIVE))) {
            goto cleanup;
        }
        virNetworkDefFree(checkdef);
    }

    if (configdef) {
        /* successfully modified copy, now replace original */
        if (virNetworkObjReplacePersistentDef(network, configdef) < 0)
           goto cleanup;
        configdef = NULL;
    }
    if (livedef) {
        /* successfully modified copy, now replace original */
        virNetworkDefFree(network->def);
        network->def = livedef;
        livedef = NULL;
    }

    ret = 0;
cleanup:
    virNetworkDefFree(livedef);
    virNetworkDefFree(configdef);
    return ret;
}

/*
 * virNetworkObjIsDuplicate:
 * @doms : virNetworkObjListPtr to search
 * @def  : virNetworkDefPtr definition of network to lookup
 * @check_active: If true, ensure that network is not active
 *
 * Returns: -1 on error
 *          0 if network is new
 *          1 if network is a duplicate
 */
int
virNetworkObjIsDuplicate(virNetworkObjListPtr doms,
                         virNetworkDefPtr def,
                         bool check_active)
{
    int ret = -1;
    virNetworkObjPtr vm = NULL;

    /* See if a VM with matching UUID already exists */
    vm = virNetworkFindByUUID(doms, def->uuid);
    if (vm) {
        /* UUID matches, but if names don't match, refuse it */
        if (STRNEQ(vm->def->name, def->name)) {
            char uuidstr[VIR_UUID_STRING_BUFLEN];
            virUUIDFormat(vm->def->uuid, uuidstr);
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("network '%s' is already defined with uuid %s"),
                           vm->def->name, uuidstr);
            goto cleanup;
        }

        if (check_active) {
            /* UUID & name match, but if VM is already active, refuse it */
            if (virNetworkObjIsActive(vm)) {
                virReportError(VIR_ERR_OPERATION_INVALID,
                               _("network is already active as '%s'"),
                               vm->def->name);
                goto cleanup;
            }
        }

        ret = 1;
    } else {
        /* UUID does not match, but if a name matches, refuse it */
        vm = virNetworkFindByName(doms, def->name);
        if (vm) {
            char uuidstr[VIR_UUID_STRING_BUFLEN];
            virUUIDFormat(vm->def->uuid, uuidstr);
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("network '%s' already exists with uuid %s"),
                           def->name, uuidstr);
            goto cleanup;
        }
        ret = 0;
    }

cleanup:
    if (vm)
        virNetworkObjUnlock(vm);
    return ret;
}


void virNetworkObjLock(virNetworkObjPtr obj)
{
    virMutexLock(&obj->lock);
}

void virNetworkObjUnlock(virNetworkObjPtr obj)
{
    virMutexUnlock(&obj->lock);
}

#define MATCH(FLAG) (flags & (FLAG))
static bool
virNetworkMatch(virNetworkObjPtr netobj,
                unsigned int flags)
{
    /* filter by active state */
    if (MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_ACTIVE) &&
        !((MATCH(VIR_CONNECT_LIST_NETWORKS_ACTIVE) &&
           virNetworkObjIsActive(netobj)) ||
          (MATCH(VIR_CONNECT_LIST_NETWORKS_INACTIVE) &&
           !virNetworkObjIsActive(netobj))))
        return false;

    /* filter by persistence */
    if (MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_PERSISTENT) &&
        !((MATCH(VIR_CONNECT_LIST_NETWORKS_PERSISTENT) &&
           netobj->persistent) ||
          (MATCH(VIR_CONNECT_LIST_NETWORKS_TRANSIENT) &&
           !netobj->persistent)))
        return false;

    /* filter by autostart option */
    if (MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_AUTOSTART) &&
        !((MATCH(VIR_CONNECT_LIST_NETWORKS_AUTOSTART) &&
           netobj->autostart) ||
          (MATCH(VIR_CONNECT_LIST_NETWORKS_NO_AUTOSTART) &&
           !netobj->autostart)))
        return false;

    return true;
}
#undef MATCH

int
virNetworkObjListExport(virConnectPtr conn,
                        virNetworkObjList netobjs,
                        virNetworkPtr **nets,
                        virNetworkObjListFilter filter,
                        unsigned int flags)
{
    virNetworkPtr *tmp_nets = NULL;
    virNetworkPtr net = NULL;
    int nnets = 0;
    int ret = -1;
    size_t i;

    if (nets && VIR_ALLOC_N(tmp_nets, netobjs.count + 1) < 0)
        goto cleanup;

    for (i = 0; i < netobjs.count; i++) {
        virNetworkObjPtr netobj = netobjs.objs[i];
        virNetworkObjLock(netobj);
        if ((!filter || filter(conn, netobj->def)) &&
            virNetworkMatch(netobj, flags)) {
            if (nets) {
                if (!(net = virGetNetwork(conn,
                                          netobj->def->name,
                                          netobj->def->uuid))) {
                    virNetworkObjUnlock(netobj);
                    goto cleanup;
                }
                tmp_nets[nnets] = net;
            }
            nnets++;
        }
        virNetworkObjUnlock(netobj);
    }

    if (tmp_nets) {
        /* trim the array to the final size */
        ignore_value(VIR_REALLOC_N(tmp_nets, nnets + 1));
        *nets = tmp_nets;
        tmp_nets = NULL;
    }

    ret = nnets;

cleanup:
    if (tmp_nets) {
        for (i = 0; i < nnets; i++) {
            if (tmp_nets[i])
                virNetworkFree(tmp_nets[i]);
        }
    }

    VIR_FREE(tmp_nets);
    return ret;
}
