/*
 * virnetworkobj.c: handle network objects
 *                  (derived from network_conf.c)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <dirent.h>

#include "datatypes.h"
#include "virnetworkobj.h"

#include "viralloc.h"
#include "virerror.h"
#include "virfile.h"
#include "virhash.h"
#include "virlog.h"
#include "virstring.h"

#define VIR_FROM_THIS VIR_FROM_NETWORK

VIR_LOG_INIT("conf.virnetworkobj");

/* Currently, /sbin/tc implementation allows up to 16 bits for
 * minor class size. But the initial bitmap doesn't have to be
 * that big. */
#define INIT_CLASS_ID_BITMAP_SIZE (1<<4)

struct _virNetworkObj {
    virObjectLockable parent;

    pid_t dnsmasqPid;
    pid_t radvdPid;
    bool active;
    bool autostart;
    bool persistent;

    virNetworkDefPtr def; /* The current definition */
    virNetworkDefPtr newDef; /* New definition to activate at shutdown */

    virBitmapPtr classIdMap; /* bitmap of class IDs for QoS */
    unsigned long long floor_sum; /* sum of all 'floor'-s of attached NICs */

    unsigned int taint;

    /* Immutable pointer, self locking APIs */
    virMacMapPtr macmap;

    GHashTable *ports; /* uuid -> virNetworkPortDefPtr */
};

struct _virNetworkObjList {
    virObjectRWLockable parent;

    GHashTable *objs;
};

static virClassPtr virNetworkObjClass;
static virClassPtr virNetworkObjListClass;
static void virNetworkObjDispose(void *obj);
static void virNetworkObjListDispose(void *obj);

static int
virNetworkObjOnceInit(void)
{
    if (!VIR_CLASS_NEW(virNetworkObj, virClassForObjectLockable()))
        return -1;

    if (!VIR_CLASS_NEW(virNetworkObjList, virClassForObjectRWLockable()))
        return -1;

    return 0;
}


VIR_ONCE_GLOBAL_INIT(virNetworkObj);

static int
virNetworkObjLoadAllPorts(virNetworkObjPtr net,
                          const char *stateDir);


static void
virNetworkObjPortFree(void *val)
{
    virNetworkPortDefFree(val);
}

virNetworkObjPtr
virNetworkObjNew(void)
{
    virNetworkObjPtr obj;

    if (virNetworkObjInitialize() < 0)
        return NULL;

    if (!(obj = virObjectLockableNew(virNetworkObjClass)))
        return NULL;

    obj->classIdMap = virBitmapNew(INIT_CLASS_ID_BITMAP_SIZE);

    /* The first three class IDs are already taken */
    if (virBitmapSetBitExpand(obj->classIdMap, 0) < 0 ||
        virBitmapSetBitExpand(obj->classIdMap, 1) < 0 ||
        virBitmapSetBitExpand(obj->classIdMap, 2) < 0)
        goto error;

    if (!(obj->ports = virHashNew(virNetworkObjPortFree)))
        goto error;

    virObjectLock(obj);

    return obj;

 error:
    virObjectUnref(obj);
    return NULL;
}


void
virNetworkObjEndAPI(virNetworkObjPtr *obj)
{
    if (!*obj)
        return;

    virObjectUnlock(*obj);
    virObjectUnref(*obj);
    *obj = NULL;
}


virNetworkDefPtr
virNetworkObjGetDef(virNetworkObjPtr obj)
{
    return obj->def;
}


void
virNetworkObjSetDef(virNetworkObjPtr obj,
                    virNetworkDefPtr def)
{
    obj->def = def;
}


virNetworkDefPtr
virNetworkObjGetNewDef(virNetworkObjPtr obj)
{
    return obj->newDef;
}


bool
virNetworkObjIsActive(virNetworkObjPtr obj)
{
    return obj->active;
}


void
virNetworkObjSetActive(virNetworkObjPtr obj,
                       bool active)
{
    obj->active = active;
}


bool
virNetworkObjIsPersistent(virNetworkObjPtr obj)
{
    return obj->persistent;
}


bool
virNetworkObjIsAutostart(virNetworkObjPtr obj)
{
    return obj->autostart;
}


void
virNetworkObjSetAutostart(virNetworkObjPtr obj,
                          bool autostart)
{
    obj->autostart = autostart;
}


pid_t
virNetworkObjGetDnsmasqPid(virNetworkObjPtr obj)
{
    return obj->dnsmasqPid;
}


void
virNetworkObjSetDnsmasqPid(virNetworkObjPtr obj,
                           pid_t dnsmasqPid)
{
    obj->dnsmasqPid = dnsmasqPid;
}


pid_t
virNetworkObjGetRadvdPid(virNetworkObjPtr obj)
{
    return obj->radvdPid;
}


void
virNetworkObjSetRadvdPid(virNetworkObjPtr obj,
                         pid_t radvdPid)
{
    obj->radvdPid = radvdPid;
}


virBitmapPtr
virNetworkObjGetClassIdMap(virNetworkObjPtr obj)
{
    return obj->classIdMap;
}


virMacMapPtr
virNetworkObjGetMacMap(virNetworkObjPtr obj)
{
    return obj->macmap;
}


unsigned long long
virNetworkObjGetFloorSum(virNetworkObjPtr obj)
{
    return obj->floor_sum;
}


void
virNetworkObjSetFloorSum(virNetworkObjPtr obj,
                         unsigned long long floor_sum)
{
    obj->floor_sum = floor_sum;
}


void
virNetworkObjSetMacMap(virNetworkObjPtr obj,
                       virMacMapPtr macmap)
{
    obj->macmap = macmap;
}


void
virNetworkObjUnrefMacMap(virNetworkObjPtr obj)
{
    virObjectUnref(obj->macmap);
    obj->macmap = NULL;
}


int
virNetworkObjMacMgrAdd(virNetworkObjPtr obj,
                       const char *dnsmasqStateDir,
                       const char *domain,
                       const virMacAddr *mac)
{
    char macStr[VIR_MAC_STRING_BUFLEN];
    char *file = NULL;
    int ret = -1;

    if (!obj->macmap)
        return 0;

    virMacAddrFormat(mac, macStr);

    if (!(file = virMacMapFileName(dnsmasqStateDir, obj->def->bridge)))
        goto cleanup;

    if (virMacMapAdd(obj->macmap, domain, macStr) < 0)
        goto cleanup;

    if (virMacMapWriteFile(obj->macmap, file) < 0)
        goto cleanup;

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


int
virNetworkObjMacMgrDel(virNetworkObjPtr obj,
                       const char *dnsmasqStateDir,
                       const char *domain,
                       const virMacAddr *mac)
{
    char macStr[VIR_MAC_STRING_BUFLEN];
    char *file = NULL;
    int ret = -1;

    if (!obj->macmap)
        return 0;

    virMacAddrFormat(mac, macStr);

    if (!(file = virMacMapFileName(dnsmasqStateDir, obj->def->bridge)))
        goto cleanup;

    if (virMacMapRemove(obj->macmap, domain, macStr) < 0)
        goto cleanup;

    if (virMacMapWriteFile(obj->macmap, file) < 0)
        goto cleanup;

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


virNetworkObjListPtr
virNetworkObjListNew(void)
{
    virNetworkObjListPtr nets;

    if (virNetworkObjInitialize() < 0)
        return NULL;

    if (!(nets = virObjectRWLockableNew(virNetworkObjListClass)))
        return NULL;

    if (!(nets->objs = virHashNew(virObjectFreeHashData))) {
        virObjectUnref(nets);
        return NULL;
    }

    return nets;
}


static virNetworkObjPtr
virNetworkObjFindByUUIDLocked(virNetworkObjListPtr nets,
                              const unsigned char *uuid)
{
    virNetworkObjPtr obj = NULL;
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    virUUIDFormat(uuid, uuidstr);

    obj = virHashLookup(nets->objs, uuidstr);
    if (obj)
        virObjectRef(obj);
    return obj;
}


/**
 * virNetworkObjFindByUUID:
 * @nets: list of network objects
 * @uuid: network uuid to find
 *
 * This functions locks @nets and find network object which
 * corresponds to @uuid.
 *
 * Returns: locked and ref'd network object.
 */
virNetworkObjPtr
virNetworkObjFindByUUID(virNetworkObjListPtr nets,
                        const unsigned char *uuid)
{
    virNetworkObjPtr obj;

    virObjectRWLockRead(nets);
    obj = virNetworkObjFindByUUIDLocked(nets, uuid);
    virObjectRWUnlock(nets);
    if (obj)
        virObjectLock(obj);
    return obj;
}


static int
virNetworkObjSearchName(const void *payload,
                        const char *name G_GNUC_UNUSED,
                        const void *data)
{
    virNetworkObjPtr obj = (virNetworkObjPtr) payload;
    int want = 0;

    virObjectLock(obj);
    if (STREQ(obj->def->name, (const char *)data))
        want = 1;
    virObjectUnlock(obj);
    return want;
}


static virNetworkObjPtr
virNetworkObjFindByNameLocked(virNetworkObjListPtr nets,
                              const char *name)
{
    virNetworkObjPtr obj = NULL;

    obj = virHashSearch(nets->objs, virNetworkObjSearchName, name, NULL);
    if (obj)
        virObjectRef(obj);
    return obj;
}


/**
 * virNetworkObjFindByName:
 * @nets: list of network objects
 * @name: network name to find
 *
 * This functions locks @nets and find network object which
 * corresponds to @name.
 *
 * Returns: locked and ref'd network object.
 */
virNetworkObjPtr
virNetworkObjFindByName(virNetworkObjListPtr nets,
                        const char *name)
{
    virNetworkObjPtr obj;

    virObjectRWLockRead(nets);
    obj = virNetworkObjFindByNameLocked(nets, name);
    virObjectRWUnlock(nets);
    if (obj)
        virObjectLock(obj);
    return obj;
}


bool
virNetworkObjTaint(virNetworkObjPtr obj,
                   virNetworkTaintFlags taint)
{
    unsigned int flag = (1 << taint);

    if (obj->taint & flag)
        return false;

    obj->taint |= flag;
    return true;
}


static void
virNetworkObjDispose(void *opaque)
{
    virNetworkObjPtr obj = opaque;

    virHashFree(obj->ports);
    virNetworkDefFree(obj->def);
    virNetworkDefFree(obj->newDef);
    virBitmapFree(obj->classIdMap);
    virObjectUnref(obj->macmap);
}


static void
virNetworkObjListDispose(void *opaque)
{
    virNetworkObjListPtr nets = opaque;

    virHashFree(nets->objs);
}


/*
 * virNetworkObjUpdateAssignDef:
 * @network: the network object to update
 * @def: the new NetworkDef (will be consumed by this function)
 * @live: is this new def the "live" version, or the "persistent" version
 *
 * Replace the appropriate copy of the given network's def or newDef
 * with def. Use "live" and current state of the network to determine
 * which to replace and what to do with the old defs. When a non-live
 * def is set, indicate that the network is now persistent.
 *
 * NB: a persistent network can be made transient by calling with:
 * virNetworkObjAssignDef(network, NULL, false) (i.e. set the
 * persistent def to NULL)
 *
 */
void
virNetworkObjUpdateAssignDef(virNetworkObjPtr obj,
                             virNetworkDefPtr def,
                             bool live)
{
    if (live) {
        /* before setting new live def, save (into newDef) any
         * existing persistent (!live) def to be restored when the
         * network is destroyed, unless there is one already saved.
         */
        if (obj->persistent && !obj->newDef)
            obj->newDef = obj->def;
        else
            virNetworkDefFree(obj->def);
        obj->def = def;
    } else { /* !live */
        virNetworkDefFree(obj->newDef);
        if (virNetworkObjIsActive(obj)) {
            /* save new configuration to be restored on network
             * shutdown, leaving current live def alone
             */
            obj->newDef = def;
        } else { /* !live and !active */
            if (obj->def && !obj->persistent) {
                /* network isn't (yet) marked active or persistent,
                 * but already has a "live" def set. This means we are
                 * currently setting the persistent def as a part of
                 * the process of starting the network, so we need to
                 * preserve the "not yet live" def in network->def.
                 */
                obj->newDef = def;
            } else {
                /* either there is no live def set, or this network
                 * was already set as persistent, so the proper thing
                 * is to overwrite network->def.
                 */
                obj->newDef = NULL;
                virNetworkDefFree(obj->def);
                obj->def = def;
            }
        }
        obj->persistent = !!def;
    }
}


/*
 * If flags & VIR_NETWORK_OBJ_LIST_ADD_CHECK_LIVE then this will
 * refuse updating an existing def if the current def is live
 *
 * If flags & VIR_NETWORK_OBJ_LIST_ADD_LIVE then the @def being
 * added is assumed to represent a live config, not a future
 * inactive config
 *
 * If flags is zero, network is considered as inactive and persistent.
 */
static virNetworkObjPtr
virNetworkObjAssignDefLocked(virNetworkObjListPtr nets,
                             virNetworkDefPtr def,
                             unsigned int flags)
{
    virNetworkObjPtr obj;
    virNetworkObjPtr ret = NULL;
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    /* See if a network with matching UUID already exists */
    if ((obj = virNetworkObjFindByUUIDLocked(nets, def->uuid))) {
        virObjectLock(obj);
        /* UUID matches, but if names don't match, refuse it */
        if (STRNEQ(obj->def->name, def->name)) {
            virUUIDFormat(obj->def->uuid, uuidstr);
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("network '%s' is already defined with uuid %s"),
                           obj->def->name, uuidstr);
            goto cleanup;
        }

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

        virNetworkObjUpdateAssignDef(obj, def,
                                     !!(flags & VIR_NETWORK_OBJ_LIST_ADD_LIVE));
    } else {
        /* UUID does not match, but if a name matches, refuse it */
        if ((obj = virNetworkObjFindByNameLocked(nets, def->name))) {
            virObjectLock(obj);
            virUUIDFormat(obj->def->uuid, uuidstr);
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("network '%s' already exists with uuid %s"),
                           def->name, uuidstr);
            goto cleanup;
        }

        if (!(obj = virNetworkObjNew()))
              goto cleanup;

        virUUIDFormat(def->uuid, uuidstr);
        if (virHashAddEntry(nets->objs, uuidstr, obj) < 0)
            goto cleanup;
        virObjectRef(obj);

        obj->def = def;
        obj->persistent = !(flags & VIR_NETWORK_OBJ_LIST_ADD_LIVE);
    }

    ret = g_steal_pointer(&obj);

 cleanup:
    virNetworkObjEndAPI(&obj);
    return ret;
}


/*
 * virNetworkObjAssignDef:
 * @nets: list of all networks
 * @def: the new NetworkDef (will be consumed by this function iff successful)
 * @flags: bitwise-OR of VIR_NETWORK_OBJ_LIST_ADD_* flags
 *
 * 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.
 *
 * Look at virNetworkObjAssignDefLocked() for @flags description.
 *
 * Returns NULL on error, virNetworkObjPtr on success.
 */
virNetworkObjPtr
virNetworkObjAssignDef(virNetworkObjListPtr nets,
                       virNetworkDefPtr def,
                       unsigned int flags)
{
    virNetworkObjPtr obj;

    virObjectRWLockWrite(nets);
    obj = virNetworkObjAssignDefLocked(nets, def, flags);
    virObjectRWUnlock(nets);
    return obj;
}


/*
 * 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 obj,
                             bool live,
                             virNetworkXMLOptionPtr xmlopt)
{
    if (!virNetworkObjIsActive(obj) && !live)
        return 0;

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

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


/* virNetworkObjUnsetDefTransient:
 *
 * This *undoes* what virNetworkObjSetDefTransient did.
 */
void
virNetworkObjUnsetDefTransient(virNetworkObjPtr obj)
{
    if (obj->newDef) {
        virNetworkDefFree(obj->def);
        obj->def = obj->newDef;
        obj->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 obj)
{
    if (obj->newDef)
        return obj->newDef;
    else
        return obj->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 obj,
                                  virNetworkDefPtr def)
{
    if (virNetworkObjIsActive(obj)) {
        virNetworkDefFree(obj->newDef);
        obj->newDef = def;
    } else {
        virNetworkDefFree(obj->def);
        obj->def = def;
    }
    return 0;
}


/*
 * virNetworkObjConfigChangeSetup:
 *
 * 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.
 */
static int
virNetworkObjConfigChangeSetup(virNetworkObjPtr obj,
                               virNetworkXMLOptionPtr xmlopt,
                               unsigned int flags)
{
    bool isActive;

    isActive = virNetworkObjIsActive(obj);

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

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

    return 0;
}


void
virNetworkObjRemoveInactive(virNetworkObjListPtr nets,
                            virNetworkObjPtr obj)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    virUUIDFormat(obj->def->uuid, uuidstr);
    virObjectRef(obj);
    virObjectUnlock(obj);
    virObjectRWLockWrite(nets);
    virObjectLock(obj);
    virHashRemoveEntry(nets->objs, uuidstr);
    virObjectRWUnlock(nets);
    virObjectUnref(obj);
}


static char *
virNetworkObjFormat(virNetworkObjPtr obj,
                    virNetworkXMLOptionPtr xmlopt,
                    unsigned int flags)
{
    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
    char *classIdStr = virBitmapFormat(obj->classIdMap);
    size_t i;

    if (!classIdStr)
        return NULL;

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

    for (i = 0; i < VIR_NETWORK_TAINT_LAST; i++) {
        if (obj->taint & (1 << i))
            virBufferAsprintf(&buf, "<taint flag='%s'/>\n",
                              virNetworkTaintTypeToString(i));
    }

    if (virNetworkDefFormatBuf(&buf, obj->def, xmlopt, flags) < 0)
        return NULL;

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

    return virBufferContentAndReset(&buf);
}


int
virNetworkObjSaveStatus(const char *statusDir,
                        virNetworkObjPtr obj,
                        virNetworkXMLOptionPtr xmlopt)
{
    int ret = -1;
    int flags = 0;
    char *xml;

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

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

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


static virNetworkObjPtr
virNetworkLoadState(virNetworkObjListPtr nets,
                    const char *stateDir,
                    const char *name,
                    virNetworkXMLOptionPtr xmlopt)
{
    char *configFile = NULL;
    virNetworkDefPtr def = NULL;
    virNetworkObjPtr obj = NULL;
    xmlDocPtr xml = NULL;
    xmlNodePtr node = NULL, *nodes = NULL;
    xmlXPathContextPtr ctxt = NULL;
    virBitmapPtr classIdMap = NULL;
    unsigned long long floor_sum_val = 0;
    unsigned int taint = 0;
    int n;
    size_t i;


    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, xmlopt)))
        goto error;

    if (STRNEQ(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 (virXMLNodeNameEqual(node, "networkstatus")) {
        /* Newer network status file. Contains useful
         * info which are not to be found in bare config XML */
        char *classIdStr = NULL;
        char *floor_sum = NULL;

        ctxt->node = node;
        if ((classIdStr = virXPathString("string(./class_id[1]/@bitmap)",
                                         ctxt))) {
            if (!(classIdMap = virBitmapParseUnlimited(classIdStr))) {
                VIR_FREE(classIdStr);
                goto error;
            }
        }
        VIR_FREE(classIdStr);

        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);
            goto error;
        }
        VIR_FREE(floor_sum);

        if ((n = virXPathNodeSet("./taint", ctxt, &nodes)) < 0)
            goto error;

        for (i = 0; i < n; i++) {
            char *str = virXMLPropString(nodes[i], "flag");
            if (str) {
                int flag = virNetworkTaintTypeFromString(str);
                if (flag < 0) {
                    virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                                   _("Unknown taint flag %s"), str);
                    VIR_FREE(str);
                    goto error;
                }
                VIR_FREE(str);
                /* Compute taint mask here. The network object does not
                 * exist yet, so we can't use virNetworkObjtTaint. */
                taint |= (1 << flag);
            }
        }
        VIR_FREE(nodes);
    }

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

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

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

    obj->taint = taint;
    obj->active = true; /* network with a state file is by definition active */

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

 error:
    VIR_FREE(nodes);
    virBitmapFree(classIdMap);
    virNetworkDefFree(def);
    goto cleanup;
}


static virNetworkObjPtr
virNetworkLoadConfig(virNetworkObjListPtr nets,
                     const char *configDir,
                     const char *autostartDir,
                     const char *name,
                     virNetworkXMLOptionPtr xmlopt)
{
    char *configFile = NULL, *autostartLink = NULL;
    virNetworkDefPtr def = NULL;
    virNetworkObjPtr obj;
    bool saveConfig = false;
    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, xmlopt)))
        goto error;

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

    switch ((virNetworkForwardType) def->forward.type) {
    case VIR_NETWORK_FORWARD_NONE:
    case VIR_NETWORK_FORWARD_NAT:
    case VIR_NETWORK_FORWARD_ROUTE:
    case VIR_NETWORK_FORWARD_OPEN:
        if (!def->mac_specified) {
            virNetworkSetBridgeMacAddr(def);
            /* We just generated a new MAC address, and we need to persist
             * the configuration to disk to avoid the network getting a
             * different one the next time the daemon is started */
            saveConfig = true;
        }
        break;

    case VIR_NETWORK_FORWARD_BRIDGE:
    case VIR_NETWORK_FORWARD_PRIVATE:
    case VIR_NETWORK_FORWARD_VEPA:
    case VIR_NETWORK_FORWARD_PASSTHROUGH:
    case VIR_NETWORK_FORWARD_HOSTDEV:
        /* Throw away MAC address for other forward types,
         * which could have been generated by older libvirt RPMs */
        def->mac_specified = false;
        break;

    case VIR_NETWORK_FORWARD_LAST:
    default:
        virReportEnumRangeError(virNetworkForwardType, def->forward.type);
        goto error;
    }

    /* The network didn't have a UUID so we generated a new one, and
     * we need to persist the configuration to disk to avoid the network
     * getting a different one the next time the daemon is started */
    if (!def->uuid_specified)
        saveConfig = true;

    if (saveConfig &&
        virNetworkSaveConfig(configDir, def, xmlopt) < 0) {
        goto error;
    }

    if (!(obj = virNetworkObjAssignDef(nets, def, 0)))
        goto error;

    obj->autostart = (autostart == 1);

    VIR_FREE(configFile);
    VIR_FREE(autostartLink);

    return obj;

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


int
virNetworkObjLoadAllState(virNetworkObjListPtr nets,
                          const char *stateDir,
                          virNetworkXMLOptionPtr xmlopt)
{
    g_autoptr(DIR) dir = NULL;
    struct dirent *entry;
    int ret = -1;
    int rc;

    if ((rc = virDirOpenIfExists(&dir, stateDir)) <= 0)
        return rc;

    while ((ret = virDirRead(dir, &entry, stateDir)) > 0) {
        virNetworkObjPtr obj;

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

        obj = virNetworkLoadState(nets, stateDir, entry->d_name, xmlopt);

        if (obj &&
            virNetworkObjLoadAllPorts(obj, stateDir) < 0) {
            virNetworkObjEndAPI(&obj);
            return -1;
        }
        virNetworkObjEndAPI(&obj);
    }

    return ret;
}


int
virNetworkObjLoadAllConfigs(virNetworkObjListPtr nets,
                            const char *configDir,
                            const char *autostartDir,
                            virNetworkXMLOptionPtr xmlopt)
{
    g_autoptr(DIR) dir = NULL;
    struct dirent *entry;
    int ret = -1;
    int rc;

    if ((rc = virDirOpenIfExists(&dir, configDir)) <= 0)
        return rc;

    while ((ret = virDirRead(dir, &entry, configDir)) > 0) {
        virNetworkObjPtr obj;

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

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

    return ret;
}


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

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

    /* Not fatal if this doesn't work */
    unlink(autostartLink);
    obj->autostart = false;

    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;
}


struct virNetworkObjBridgeInUseHelperData {
    const char *bridge;
    const char *skipname;
};

static int
virNetworkObjBridgeInUseHelper(const void *payload,
                               const char *name G_GNUC_UNUSED,
                               const void *opaque)
{
    int ret;
    virNetworkObjPtr obj = (virNetworkObjPtr) payload;
    const struct virNetworkObjBridgeInUseHelperData *data = opaque;

    virObjectLock(obj);
    if (data->skipname &&
        ((obj->def && STREQ(obj->def->name, data->skipname)) ||
         (obj->newDef && STREQ(obj->newDef->name, data->skipname))))
        ret = 0;
    else if ((obj->def && obj->def->bridge &&
              STREQ(obj->def->bridge, data->bridge)) ||
             (obj->newDef && obj->newDef->bridge &&
              STREQ(obj->newDef->bridge, data->bridge)))
        ret = 1;
    else
        ret = 0;
    virObjectUnlock(obj);
    return ret;
}


bool
virNetworkObjBridgeInUse(virNetworkObjListPtr nets,
                         const char *bridge,
                         const char *skipname)
{
    virNetworkObjPtr obj;
    struct virNetworkObjBridgeInUseHelperData data = {bridge, skipname};

    virObjectRWLockRead(nets);
    obj = virHashSearch(nets->objs, virNetworkObjBridgeInUseHelper, &data, NULL);
    virObjectRWUnlock(nets);

    return obj != NULL;
}


/*
 * 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 obj,
                    unsigned int command, /* virNetworkUpdateCommand */
                    unsigned int section, /* virNetworkUpdateSection */
                    int parentIndex,
                    const char *xml,
                    virNetworkXMLOptionPtr xmlopt,
                    unsigned int flags)  /* virNetworkUpdateFlags */
{
    int ret = -1;
    virNetworkDefPtr livedef = NULL, configdef = NULL;

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

    if (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE) {
        virNetworkDefPtr checkdef;

        /* work on a copy of the def */
        if (!(livedef = virNetworkDefCopy(obj->def, xmlopt, 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, xmlopt, 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(obj),
                                            xmlopt,
                                            VIR_NETWORK_XML_INACTIVE))) {
            goto cleanup;
        }
        if (virNetworkDefUpdateSection(configdef, command, section,
                                       parentIndex, xml, flags) < 0) {
            goto cleanup;
        }
        if (!(checkdef = virNetworkDefCopy(configdef,
                                           xmlopt,
                                           VIR_NETWORK_XML_INACTIVE))) {
            goto cleanup;
        }
        virNetworkDefFree(checkdef);
    }

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

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


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

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

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

    return true;
}
#undef MATCH


typedef struct _virNetworkObjListExportData virNetworkObjListExportData;
typedef virNetworkObjListExportData *virNetworkObjListExportDataPtr;
struct _virNetworkObjListExportData {
    virConnectPtr conn;
    virNetworkPtr *nets;
    virNetworkObjListFilter filter;
    unsigned int flags;
    int nnets;
    bool error;
};

static int
virNetworkObjListExportCallback(void *payload,
                                const char *name G_GNUC_UNUSED,
                                void *opaque)
{
    virNetworkObjListExportDataPtr data = opaque;
    virNetworkObjPtr obj = payload;
    virNetworkPtr net = NULL;

    if (data->error)
        return 0;

    virObjectLock(obj);

    if (data->filter &&
        !data->filter(data->conn, obj->def))
        goto cleanup;

    if (!virNetworkObjMatch(obj, data->flags))
        goto cleanup;

    if (!data->nets) {
        data->nnets++;
        goto cleanup;
    }

    if (!(net = virGetNetwork(data->conn, obj->def->name, obj->def->uuid))) {
        data->error = true;
        goto cleanup;
    }

    data->nets[data->nnets++] = net;

 cleanup:
    virObjectUnlock(obj);
    return 0;
}


int
virNetworkObjListExport(virConnectPtr conn,
                        virNetworkObjListPtr netobjs,
                        virNetworkPtr **nets,
                        virNetworkObjListFilter filter,
                        unsigned int flags)
{
    int ret = -1;
    virNetworkObjListExportData data = {
        .conn = conn, .nets = NULL, .filter = filter, .flags = flags,
        .nnets = 0, .error = false };

    virObjectRWLockRead(netobjs);
    if (nets)
        data.nets = g_new0(virNetworkPtr, virHashSize(netobjs->objs) + 1);

    virHashForEach(netobjs->objs, virNetworkObjListExportCallback, &data);

    if (data.error)
        goto cleanup;

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

    ret = data.nnets;
 cleanup:
    virObjectRWUnlock(netobjs);
    while (data.nets && data.nnets)
        virObjectUnref(data.nets[--data.nnets]);

    VIR_FREE(data.nets);
    return ret;
}


struct virNetworkObjListForEachHelperData {
    virNetworkObjListIterator callback;
    void *opaque;
    int ret;
};

static int
virNetworkObjListForEachHelper(void *payload,
                               const char *name G_GNUC_UNUSED,
                               void *opaque)
{
    struct virNetworkObjListForEachHelperData *data = opaque;

    if (data->callback(payload, data->opaque) < 0)
        data->ret = -1;
    return 0;
}


/**
 * virNetworkObjListForEach:
 * @nets: a list of network objects
 * @callback: function to call over each of object in the list
 * @opaque: pointer to pass to the @callback
 *
 * Function iterates over the list of network objects and calls
 * passed callback over each one of them. You should avoid
 * calling those virNetworkObjList APIs, which lock the list
 * again in favor of their virNetworkObj*Locked variants.
 *
 * Returns: 0 on success, -1 otherwise.
 */
int
virNetworkObjListForEach(virNetworkObjListPtr nets,
                         virNetworkObjListIterator callback,
                         void *opaque)
{
    struct virNetworkObjListForEachHelperData data = {
        .callback = callback, .opaque = opaque, .ret = 0};
    virObjectRWLockRead(nets);
    virHashForEachSafe(nets->objs, virNetworkObjListForEachHelper, &data);
    virObjectRWUnlock(nets);
    return data.ret;
}


struct virNetworkObjListGetHelperData {
    virConnectPtr conn;
    virNetworkObjListFilter filter;
    char **names;
    int nnames;
    int maxnames;
    bool active;
    bool error;
};

static int
virNetworkObjListGetHelper(void *payload,
                           const char *name G_GNUC_UNUSED,
                           void *opaque)
{
    struct virNetworkObjListGetHelperData *data = opaque;
    virNetworkObjPtr obj = payload;

    if (data->error)
        return 0;

    if (data->maxnames >= 0 &&
        data->nnames == data->maxnames)
        return 0;

    virObjectLock(obj);

    if (data->filter &&
        !data->filter(data->conn, obj->def))
        goto cleanup;

    if ((data->active && virNetworkObjIsActive(obj)) ||
        (!data->active && !virNetworkObjIsActive(obj))) {
        if (data->names)
            data->names[data->nnames] = g_strdup(obj->def->name);
        data->nnames++;
    }

 cleanup:
    virObjectUnlock(obj);
    return 0;
}


int
virNetworkObjListGetNames(virNetworkObjListPtr nets,
                          bool active,
                          char **names,
                          int maxnames,
                          virNetworkObjListFilter filter,
                          virConnectPtr conn)
{
    int ret = -1;

    struct virNetworkObjListGetHelperData data = {
        .conn = conn, .filter = filter, .names = names, .nnames = 0,
        .maxnames = maxnames, .active = active, .error = false};

    virObjectRWLockRead(nets);
    virHashForEach(nets->objs, virNetworkObjListGetHelper, &data);
    virObjectRWUnlock(nets);

    if (data.error)
        goto cleanup;

    ret = data.nnames;
 cleanup:
    if (ret < 0) {
        while (data.nnames)
            VIR_FREE(data.names[--data.nnames]);
    }
    return ret;
}


int
virNetworkObjListNumOfNetworks(virNetworkObjListPtr nets,
                               bool active,
                               virNetworkObjListFilter filter,
                               virConnectPtr conn)
{
    struct virNetworkObjListGetHelperData data = {
        .conn = conn, .filter = filter, .names = NULL, .nnames = 0,
        .maxnames = -1, .active = active, .error = false};

    virObjectRWLockRead(nets);
    virHashForEach(nets->objs, virNetworkObjListGetHelper, &data);
    virObjectRWUnlock(nets);

    return data.nnames;
}


struct virNetworkObjListPruneHelperData {
    unsigned int flags;
};

static int
virNetworkObjListPruneHelper(const void *payload,
                             const char *name G_GNUC_UNUSED,
                             const void *opaque)
{
    const struct virNetworkObjListPruneHelperData *data = opaque;
    virNetworkObjPtr obj = (virNetworkObjPtr) payload;
    int want = 0;

    virObjectLock(obj);
    want = virNetworkObjMatch(obj, data->flags);
    virObjectUnlock(obj);
    return want;
}


/**
 * virNetworkObjListPrune:
 * @nets: a list of network objects
 * @flags: bitwise-OR of virConnectListAllNetworksFlags
 *
 * Iterate over list of network objects and remove the desired
 * ones from it.
 */
void
virNetworkObjListPrune(virNetworkObjListPtr nets,
                       unsigned int flags)
{
    struct virNetworkObjListPruneHelperData data = {flags};

    virObjectRWLockWrite(nets);
    virHashRemoveSet(nets->objs, virNetworkObjListPruneHelper, &data);
    virObjectRWUnlock(nets);
}


char *
virNetworkObjGetPortStatusDir(virNetworkObjPtr net,
                              const char *stateDir)
{
    char *ret;
    ret = g_strdup_printf("%s/%s/ports", stateDir, net->def->name);
    return ret;
}

int
virNetworkObjAddPort(virNetworkObjPtr net,
                     virNetworkPortDefPtr portdef,
                     const char *stateDir)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN];
    g_autofree char *dir = NULL;

    virUUIDFormat(portdef->uuid, uuidstr);

    if (virHashLookup(net->ports, uuidstr)) {
        virReportError(VIR_ERR_NETWORK_PORT_EXIST,
                       _("Network port with UUID %s already exists"),
                       uuidstr);
        return -1;
    }

    if (!(dir = virNetworkObjGetPortStatusDir(net, stateDir)))
        return -1;

    if (virHashAddEntry(net->ports, uuidstr, portdef) < 0)
        return -1;

    if (virNetworkPortDefSaveStatus(portdef, dir) < 0) {
        virHashRemoveEntry(net->ports, uuidstr);
        return -1;
    }

    return 0;
}


virNetworkPortDefPtr
virNetworkObjLookupPort(virNetworkObjPtr net,
                        const unsigned char *uuid)
{
    virNetworkPortDefPtr ret = NULL;
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    virUUIDFormat(uuid, uuidstr);

    if (!(ret = virHashLookup(net->ports, uuidstr))) {
        virReportError(VIR_ERR_NO_NETWORK_PORT,
                       _("Network port with UUID %s does not exist"),
                       uuidstr);
        return NULL;
    }

    return ret;
}


int
virNetworkObjDeletePort(virNetworkObjPtr net,
                        const unsigned char *uuid,
                        const char *stateDir)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN];
    g_autofree char *dir = NULL;
    virNetworkPortDefPtr portdef;

    virUUIDFormat(uuid, uuidstr);

    if (!(portdef = virHashLookup(net->ports, uuidstr))) {
        virReportError(VIR_ERR_NO_NETWORK_PORT,
                       _("Network port with UUID %s does not exist"),
                       uuidstr);
        return -1;
    }

    if (!(dir = virNetworkObjGetPortStatusDir(net, stateDir)))
        return -1;

    if (virNetworkPortDefDeleteStatus(portdef, dir) < 0)
        return -1;

    if (virHashRemoveEntry(net->ports, uuidstr) < 0)
        return -1;

    return 0;
}


int
virNetworkObjDeleteAllPorts(virNetworkObjPtr net,
                            const char *stateDir)
{
    g_autofree char *dir = NULL;
    g_autoptr(DIR) dh = NULL;
    struct dirent *de;
    int rc;

    if (!(dir = virNetworkObjGetPortStatusDir(net, stateDir)))
        return -1;

    if ((rc = virDirOpenIfExists(&dh, dir)) <= 0)
        return rc;

    while ((rc = virDirRead(dh, &de, dir)) > 0) {
        char *file = NULL;

        if (!virStringStripSuffix(de->d_name, ".xml"))
            continue;

        file = g_strdup_printf("%s/%s.xml", dir, de->d_name);

        if (unlink(file) < 0 && errno != ENOENT)
            VIR_WARN("Unable to delete %s", file);

        VIR_FREE(file);
    }

    virHashRemoveAll(net->ports);
    return 0;
}


typedef struct _virNetworkObjPortListExportData virNetworkObjPortListExportData;
typedef virNetworkObjPortListExportData *virNetworkObjPortListExportDataPtr;
struct _virNetworkObjPortListExportData {
    virNetworkPtr net;
    virNetworkDefPtr def;
    virNetworkPortPtr *ports;
    virNetworkPortListFilter filter;
    int nports;
    bool error;
};

static int
virNetworkObjPortListExportCallback(void *payload,
                                    const char *name G_GNUC_UNUSED,
                                    void *opaque)
{
    virNetworkObjPortListExportDataPtr data = opaque;
    virNetworkPortDefPtr def = payload;
    virNetworkPortPtr port;

    if (data->error)
        return 0;

    if (data->filter &&
        !data->filter(data->net->conn, data->def, def))
        return 0;

    if (!data->ports) {
        data->nports++;
        return 0;
    }

    if (!(port = virGetNetworkPort(data->net, def->uuid))) {
        data->error = true;
        return 0;
    }

    data->ports[data->nports++] = port;

    return 0;
}


int
virNetworkObjPortListExport(virNetworkPtr net,
                            virNetworkObjPtr obj,
                            virNetworkPortPtr **ports,
                            virNetworkPortListFilter filter)
{
    virNetworkObjPortListExportData data = {
        net, obj->def, NULL, filter, 0, false,
    };
    int ret = -1;

    if (ports) {
        *ports = NULL;

        data.ports = g_new0(virNetworkPortPtr, virHashSize(obj->ports) + 1);
    }

    virHashForEach(obj->ports, virNetworkObjPortListExportCallback, &data);

    if (data.error)
        goto cleanup;

    if (data.ports) {
        /* trim the array to the final size */
        ignore_value(VIR_REALLOC_N(data.ports, data.nports + 1));
        *ports = data.ports;
        data.ports = NULL;
    }

    ret = data.nports;
 cleanup:
    while (data.ports && data.nports)
        virObjectUnref(data.ports[--data.nports]);

    VIR_FREE(data.ports);
    return ret;
}


typedef struct _virNetworkObjPortListForEachData virNetworkObjPortListForEachData;
struct _virNetworkObjPortListForEachData {
    virNetworkPortListIter iter;
    void *opaque;
    bool err;
};

static int
virNetworkObjPortForEachCallback(void *payload,
                                 const char *name G_GNUC_UNUSED,
                                 void *opaque)
{
    virNetworkObjPortListForEachData *data = opaque;

    if (!data->iter(payload, data->opaque))
        data->err = true;

    return 0;
}

int
virNetworkObjPortForEach(virNetworkObjPtr obj,
                         virNetworkPortListIter iter,
                         void *opaque)
{
    virNetworkObjPortListForEachData data = { iter, opaque, false };
    virHashForEachSafe(obj->ports, virNetworkObjPortForEachCallback, &data);
    if (data.err)
        return -1;
    return 0;
}


static int
virNetworkObjLoadAllPorts(virNetworkObjPtr net,
                          const char *stateDir)
{
    g_autofree char *dir = NULL;
    g_autoptr(DIR) dh = NULL;
    struct dirent *de;
    int rc;
    char uuidstr[VIR_UUID_STRING_BUFLEN];
    g_autoptr(virNetworkPortDef) portdef = NULL;

    if (!(dir = virNetworkObjGetPortStatusDir(net, stateDir)))
        return -1;

    if ((rc = virDirOpenIfExists(&dh, dir)) <= 0)
        return rc;

    while ((rc = virDirRead(dh, &de, dir)) > 0) {
        g_autofree char *file = NULL;

        if (!virStringStripSuffix(de->d_name, ".xml"))
            continue;

        file = g_strdup_printf("%s/%s.xml", dir, de->d_name);

        portdef = virNetworkPortDefParseFile(file);
        if (!portdef) {
            VIR_WARN("Cannot parse port %s", file);
            continue;
        }

        virUUIDFormat(portdef->uuid, uuidstr);
        if (virHashAddEntry(net->ports, uuidstr, portdef) < 0)
            return -1;

        portdef = NULL;
    }

    return 0;
}
