/*
 * virstorageobj.c: internal storage pool and volume objects handling
 *                  (derived from storage_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 "node_device_conf.h"
#include "node_device_util.h"
#include "virstorageobj.h"

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

#define VIR_FROM_THIS VIR_FROM_STORAGE

VIR_LOG_INIT("conf.virstorageobj");

static virClassPtr virStoragePoolObjClass;
static virClassPtr virStoragePoolObjListClass;
static virClassPtr virStorageVolObjClass;
static virClassPtr virStorageVolObjListClass;

static void
virStoragePoolObjDispose(void *opaque);
static void
virStoragePoolObjListDispose(void *opaque);
static void
virStorageVolObjDispose(void *opaque);
static void
virStorageVolObjListDispose(void *opaque);



typedef struct _virStorageVolObj virStorageVolObj;
typedef virStorageVolObj *virStorageVolObjPtr;
struct _virStorageVolObj {
    virObjectLockable parent;

    virStorageVolDefPtr voldef;
};

typedef struct _virStorageVolObjList virStorageVolObjList;
typedef virStorageVolObjList *virStorageVolObjListPtr;
struct _virStorageVolObjList {
    virObjectRWLockable parent;

    /* key string -> virStorageVolObj mapping
     * for (1), lockless lookup-by-key */
    GHashTable *objsKey;

    /* name string -> virStorageVolObj mapping
     * for (1), lockless lookup-by-name */
    GHashTable *objsName;

    /* path string -> virStorageVolObj mapping
     * for (1), lockless lookup-by-path */
    GHashTable *objsPath;
};

struct _virStoragePoolObj {
    virObjectLockable parent;

    char *configFile;
    char *autostartLink;
    bool active;
    bool starting;
    bool autostart;
    unsigned int asyncjobs;

    virStoragePoolDefPtr def;
    virStoragePoolDefPtr newDef;

    virStorageVolObjListPtr volumes;
};

struct _virStoragePoolObjList {
    virObjectRWLockable parent;

    /* uuid string -> virStoragePoolObj mapping
     * for (1), lockless lookup-by-uuid */
    GHashTable *objs;

    /* name string -> virStoragePoolObj mapping
     * for (1), lockless lookup-by-name */
    GHashTable *objsName;
};


static int
virStorageVolObjOnceInit(void)
{
    if (!VIR_CLASS_NEW(virStorageVolObj, virClassForObjectLockable()))
        return -1;

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

    return 0;
}

VIR_ONCE_GLOBAL_INIT(virStorageVolObj);


static virStorageVolObjPtr
virStorageVolObjNew(void)
{
    virStorageVolObjPtr obj;

    if (virStorageVolObjInitialize() < 0)
        return NULL;

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

    virObjectLock(obj);
    return obj;
}


static void
virStorageVolObjEndAPI(virStorageVolObjPtr *obj)
{
    if (!*obj)
        return;

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


static void
virStorageVolObjDispose(void *opaque)
{
    virStorageVolObjPtr obj = opaque;

    virStorageVolDefFree(obj->voldef);
}


static virStorageVolObjListPtr
virStorageVolObjListNew(void)
{
    virStorageVolObjListPtr vols;

    if (virStorageVolObjInitialize() < 0)
        return NULL;

    if (!(vols = virObjectRWLockableNew(virStorageVolObjListClass)))
        return NULL;

    if (!(vols->objsKey = virHashNew(virObjectFreeHashData)) ||
        !(vols->objsName = virHashNew(virObjectFreeHashData)) ||
        !(vols->objsPath = virHashNew(virObjectFreeHashData))) {
        virObjectUnref(vols);
        return NULL;
    }

    return vols;
}


static void
virStorageVolObjListDispose(void *opaque)
{
    virStorageVolObjListPtr vols = opaque;

    virHashFree(vols->objsKey);
    virHashFree(vols->objsName);
    virHashFree(vols->objsPath);
}


static int
virStoragePoolObjOnceInit(void)
{
    if (!VIR_CLASS_NEW(virStoragePoolObj, virClassForObjectLockable()))
        return -1;

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

    return 0;
}

VIR_ONCE_GLOBAL_INIT(virStoragePoolObj);


virStoragePoolObjPtr
virStoragePoolObjNew(void)
{
    virStoragePoolObjPtr obj;

    if (virStoragePoolObjInitialize() < 0)
        return NULL;

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

    if (!(obj->volumes = virStorageVolObjListNew())) {
        virObjectUnref(obj);
        return NULL;
    }

    virObjectLock(obj);
    obj->active = false;
    return obj;
}


void
virStoragePoolObjEndAPI(virStoragePoolObjPtr *obj)
{
    if (!*obj)
        return;

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


virStoragePoolDefPtr
virStoragePoolObjGetDef(virStoragePoolObjPtr obj)
{
    return obj->def;
}


void
virStoragePoolObjSetDef(virStoragePoolObjPtr obj,
                        virStoragePoolDefPtr def)
{
    virStoragePoolDefFree(obj->def);
    obj->def = def;
}


virStoragePoolDefPtr
virStoragePoolObjGetNewDef(virStoragePoolObjPtr obj)
{
    return obj->newDef;
}


void
virStoragePoolObjDefUseNewDef(virStoragePoolObjPtr obj)
{
    virStoragePoolDefFree(obj->def);
    obj->def = obj->newDef;
    obj->newDef = NULL;
}


const char *
virStoragePoolObjGetConfigFile(virStoragePoolObjPtr obj)
{
    return obj->configFile;
}


void
virStoragePoolObjSetConfigFile(virStoragePoolObjPtr obj,
                               char *configFile)
{
    VIR_FREE(obj->configFile);
    obj->configFile = configFile;
}


const char *
virStoragePoolObjGetAutostartLink(virStoragePoolObjPtr obj)
{
    return obj->autostartLink;
}


bool
virStoragePoolObjIsActive(virStoragePoolObjPtr obj)
{
    return obj->active;
}


void
virStoragePoolObjSetActive(virStoragePoolObjPtr obj,
                           bool active)
{
    obj->active = active;
}


void
virStoragePoolObjSetStarting(virStoragePoolObjPtr obj,
                             bool starting)
{
    obj->starting = starting;
}


bool
virStoragePoolObjIsStarting(virStoragePoolObjPtr obj)
{
    return obj->starting;
}


bool
virStoragePoolObjIsAutostart(virStoragePoolObjPtr obj)
{
    if (!obj->configFile)
        return false;

    return obj->autostart;
}


void
virStoragePoolObjSetAutostart(virStoragePoolObjPtr obj,
                              bool autostart)
{
    obj->autostart = autostart;
}


unsigned int
virStoragePoolObjGetAsyncjobs(virStoragePoolObjPtr obj)
{
    return obj->asyncjobs;
}


void
virStoragePoolObjIncrAsyncjobs(virStoragePoolObjPtr obj)
{
    obj->asyncjobs++;
}


void
virStoragePoolObjDecrAsyncjobs(virStoragePoolObjPtr obj)
{
    obj->asyncjobs--;
}


void
virStoragePoolObjDispose(void *opaque)
{
    virStoragePoolObjPtr obj = opaque;

    virStoragePoolObjClearVols(obj);
    virObjectUnref(obj->volumes);

    virStoragePoolDefFree(obj->def);
    virStoragePoolDefFree(obj->newDef);

    VIR_FREE(obj->configFile);
    VIR_FREE(obj->autostartLink);
}


void
virStoragePoolObjListDispose(void *opaque)
{
    virStoragePoolObjListPtr pools = opaque;

    virHashFree(pools->objs);
    virHashFree(pools->objsName);
}


virStoragePoolObjListPtr
virStoragePoolObjListNew(void)
{
    virStoragePoolObjListPtr pools;

    if (virStoragePoolObjInitialize() < 0)
        return NULL;

    if (!(pools = virObjectRWLockableNew(virStoragePoolObjListClass)))
        return NULL;

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

    return pools;
}


struct _virStoragePoolObjListForEachData {
    virStoragePoolObjListIterator iter;
    const void *opaque;
};

static int
virStoragePoolObjListForEachCb(void *payload,
                               const char *name G_GNUC_UNUSED,
                               void *opaque)
{
    virStoragePoolObjPtr obj = payload;
    struct _virStoragePoolObjListForEachData *data = opaque;

    /* Grab a reference so that we don't rely only on references grabbed by
     * hash table earlier. Remember, an iterator can remove object from the
     * hash table. */
    virObjectRef(obj);
    virObjectLock(obj);
    data->iter(obj, data->opaque);
    virStoragePoolObjEndAPI(&obj);

    return 0;
}


/**
 * virStoragePoolObjListForEach
 * @pools: Pointer to pools object
 * @iter: Callback iteration helper
 * @opaque: Opaque data to use as argument to helper
 *
 * For each object in @pools, call the @iter helper using @opaque as
 * an argument.  This function doesn't care whether the @iter fails or
 * not as it's being used for Autostart and UpdateAllState callers
 * that want to iterate over all the @pools objects not stopping if
 * one happens to fail.
 *
 * NB: We cannot take the Storage Pool lock here because it's possible
 *     that some action as part of Autostart or UpdateAllState will need
 *     to modify/destroy a transient pool. Since these paths only occur
 *     during periods in which the storageDriverLock is held (Initialization,
 *     AutoStart, or Reload) this is OK.
 */
void
virStoragePoolObjListForEach(virStoragePoolObjListPtr pools,
                             virStoragePoolObjListIterator iter,
                             const void *opaque)
{
    struct _virStoragePoolObjListForEachData data = { .iter = iter,
                                                      .opaque = opaque };

    virHashForEachSafe(pools->objs, virStoragePoolObjListForEachCb, &data);
}


struct _virStoragePoolObjListSearchData {
    virStoragePoolObjListSearcher searcher;
    const void *opaque;
};


static int
virStoragePoolObjListSearchCb(const void *payload,
                              const char *name G_GNUC_UNUSED,
                              const void *opaque)
{
    virStoragePoolObjPtr obj = (virStoragePoolObjPtr) payload;
    struct _virStoragePoolObjListSearchData *data =
        (struct _virStoragePoolObjListSearchData *)opaque;

    virObjectLock(obj);
    if (data->searcher(obj, data->opaque))
        return 1;
    virObjectUnlock(obj);
    return 0;
}


/**
 * virStoragePoolObjListSearch
 * @pools: Pointer to pools object
 * @search: Callback searcher helper
 * @opaque: Opaque data to use as argument to helper
 *
 * Search through the @pools objects calling the @search helper using
 * the @opaque data in order to find an object that matches some criteria
 * and return that object locked.
 *
 * Returns a locked and reffed object when found and NULL when not found
 */
virStoragePoolObjPtr
virStoragePoolObjListSearch(virStoragePoolObjListPtr pools,
                            virStoragePoolObjListSearcher searcher,
                            const void *opaque)
{
    virStoragePoolObjPtr obj = NULL;
    struct _virStoragePoolObjListSearchData data = { .searcher = searcher,
                                                     .opaque = opaque };

    virObjectRWLockRead(pools);
    obj = virHashSearch(pools->objs, virStoragePoolObjListSearchCb, &data, NULL);
    virObjectRWUnlock(pools);

    return virObjectRef(obj);
}


void
virStoragePoolObjRemove(virStoragePoolObjListPtr pools,
                        virStoragePoolObjPtr obj)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN];

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


static virStoragePoolObjPtr
virStoragePoolObjFindByUUIDLocked(virStoragePoolObjListPtr pools,
                                  const unsigned char *uuid)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    virUUIDFormat(uuid, uuidstr);

    return virObjectRef(virHashLookup(pools->objs, uuidstr));
}


/**
 * virStoragePoolObjFindByUUID
 * @pools: Storage pool object list pointer
 * @uuid: Storage object uuid to find
 *
 * Lock the @pools and lookup the object by @uuid
 *
 * Returns: Locked and reffed storage pool object or NULL if not found
 */
virStoragePoolObjPtr
virStoragePoolObjFindByUUID(virStoragePoolObjListPtr pools,
                            const unsigned char *uuid)
{
    virStoragePoolObjPtr obj;

    virObjectRWLockRead(pools);
    obj = virStoragePoolObjFindByUUIDLocked(pools, uuid);
    virObjectRWUnlock(pools);
    if (obj)
        virObjectLock(obj);

    return obj;
}


static virStoragePoolObjPtr
virStoragePoolObjFindByNameLocked(virStoragePoolObjListPtr pools,
                                  const char *name)
{
    return virObjectRef(virHashLookup(pools->objsName, name));
}


/**
 * virStoragePoolObjFindByName
 * @pools: Storage pool object list pointer
 * @name: Storage object name to find
 *
 * Lock the @pools and lookup the object by @name
 *
 * Returns: Locked and reffed storage pool object or NULL if not found
 */
virStoragePoolObjPtr
virStoragePoolObjFindByName(virStoragePoolObjListPtr pools,
                            const char *name)
{
    virStoragePoolObjPtr obj;

    virObjectRWLockRead(pools);
    obj = virStoragePoolObjFindByNameLocked(pools, name);
    virObjectRWUnlock(pools);
    if (obj)
        virObjectLock(obj);

    return obj;
}


static virStoragePoolObjPtr
virStoragePoolSourceFindDuplicateDevices(virStoragePoolObjPtr obj,
                                         virStoragePoolDefPtr def)
{
    size_t i, j;

    for (i = 0; i < obj->def->source.ndevice; i++) {
        for (j = 0; j < def->source.ndevice; j++) {
            if (STREQ(obj->def->source.devices[i].path, def->source.devices[j].path))
                return obj;
        }
    }

    return NULL;
}


void
virStoragePoolObjClearVols(virStoragePoolObjPtr obj)
{
    if (!obj->volumes)
        return;

    virHashRemoveAll(obj->volumes->objsKey);
    virHashRemoveAll(obj->volumes->objsName);
    virHashRemoveAll(obj->volumes->objsPath);
}


int
virStoragePoolObjAddVol(virStoragePoolObjPtr obj,
                        virStorageVolDefPtr voldef)
{
    virStorageVolObjPtr volobj = NULL;
    virStorageVolObjListPtr volumes = obj->volumes;

    virObjectRWLockWrite(volumes);

    if (!(volobj = virStorageVolObjNew()))
        goto error;

    if (virHashAddEntry(volumes->objsKey, voldef->key, volobj) < 0)
        goto error;
    virObjectRef(volobj);

    if (virHashAddEntry(volumes->objsName, voldef->name, volobj) < 0) {
        virHashRemoveEntry(volumes->objsKey, voldef->key);
        goto error;
    }
    virObjectRef(volobj);

    if (virHashAddEntry(volumes->objsPath, voldef->target.path, volobj) < 0) {
        virHashRemoveEntry(volumes->objsKey, voldef->key);
        virHashRemoveEntry(volumes->objsName, voldef->name);
        goto error;
    }
    virObjectRef(volobj);

    volobj->voldef = voldef;
    virObjectRWUnlock(volumes);
    virStorageVolObjEndAPI(&volobj);
    return 0;

 error:
    virStorageVolObjEndAPI(&volobj);
    virObjectRWUnlock(volumes);
    return -1;
}


void
virStoragePoolObjRemoveVol(virStoragePoolObjPtr obj,
                           virStorageVolDefPtr voldef)
{
    virStorageVolObjListPtr volumes = obj->volumes;
    virStorageVolObjPtr volobj;

    virObjectRWLockWrite(volumes);
    volobj = virHashLookup(volumes->objsName, voldef->name);
    if (!volobj) {
        VIR_INFO("Cannot find volume '%s' from storage pool '%s'",
                 voldef->name, obj->def->name);
        virObjectRWUnlock(volumes);
        return;
    }
    VIR_INFO("Deleting volume '%s' from storage pool '%s'",
             voldef->name, obj->def->name);

    virObjectRef(volobj);
    virObjectLock(volobj);
    virHashRemoveEntry(volumes->objsKey, voldef->key);
    virHashRemoveEntry(volumes->objsName, voldef->name);
    virHashRemoveEntry(volumes->objsPath, voldef->target.path);
    virStorageVolObjEndAPI(&volobj);

    virObjectRWUnlock(volumes);
}


size_t
virStoragePoolObjGetVolumesCount(virStoragePoolObjPtr obj)
{
    size_t nbElems;

    virObjectRWLockRead(obj->volumes);
    nbElems = virHashSize(obj->volumes->objsKey);
    virObjectRWUnlock(obj->volumes);

    return nbElems;
}


struct _virStoragePoolObjForEachVolData {
    virStorageVolObjListIterator iter;
    const void *opaque;
};

static int
virStoragePoolObjForEachVolumeCb(void *payload,
                                 const char *name G_GNUC_UNUSED,
                                 void *opaque)
{
    int ret = 0;
    virStorageVolObjPtr volobj = payload;
    struct _virStoragePoolObjForEachVolData *data = opaque;

    virObjectLock(volobj);
    if (data->iter(volobj->voldef, data->opaque) < 0)
        ret = -1;
    virObjectUnlock(volobj);

    return ret;
}


int
virStoragePoolObjForEachVolume(virStoragePoolObjPtr obj,
                               virStorageVolObjListIterator iter,
                               const void *opaque)
{
    struct _virStoragePoolObjForEachVolData data = {
        .iter = iter, .opaque = opaque };

    virObjectRWLockRead(obj->volumes);
    virHashForEachSafe(obj->volumes->objsKey, virStoragePoolObjForEachVolumeCb,
                   &data);
    virObjectRWUnlock(obj->volumes);
    return 0;
}


struct _virStoragePoolObjSearchVolData {
    virStorageVolObjListSearcher iter;
    const void *opaque;
};

static int
virStoragePoolObjSearchVolumeCb(const void *payload,
                                const char *name G_GNUC_UNUSED,
                                const void *opaque)
{
    virStorageVolObjPtr volobj = (virStorageVolObjPtr) payload;
    struct _virStoragePoolObjSearchVolData *data =
        (struct _virStoragePoolObjSearchVolData *) opaque;
    int found = 0;

    virObjectLock(volobj);
    if (data->iter(volobj->voldef, data->opaque))
        found = 1;
    virObjectUnlock(volobj);

    return found;
}


virStorageVolDefPtr
virStoragePoolObjSearchVolume(virStoragePoolObjPtr obj,
                              virStorageVolObjListSearcher iter,
                              const void *opaque)
{
    virStorageVolObjPtr volobj;
    struct _virStoragePoolObjSearchVolData data = {
        .iter = iter, .opaque = opaque };

    virObjectRWLockRead(obj->volumes);
    volobj = virHashSearch(obj->volumes->objsKey,
                           virStoragePoolObjSearchVolumeCb,
                           &data, NULL);
    virObjectRWUnlock(obj->volumes);

    if (volobj)
        return volobj->voldef;

    return NULL;
}


virStorageVolDefPtr
virStorageVolDefFindByKey(virStoragePoolObjPtr obj,
                          const char *key)
{
    virStorageVolObjPtr volobj;

    virObjectRWLockRead(obj->volumes);
    volobj = virHashLookup(obj->volumes->objsKey, key);
    virObjectRWUnlock(obj->volumes);

    if (volobj)
        return volobj->voldef;
    return NULL;
}


virStorageVolDefPtr
virStorageVolDefFindByPath(virStoragePoolObjPtr obj,
                           const char *path)
{
    virStorageVolObjPtr volobj;

    virObjectRWLockRead(obj->volumes);
    volobj = virHashLookup(obj->volumes->objsPath, path);
    virObjectRWUnlock(obj->volumes);

    if (volobj)
        return volobj->voldef;
    return NULL;
}


virStorageVolDefPtr
virStorageVolDefFindByName(virStoragePoolObjPtr obj,
                           const char *name)
{
    virStorageVolObjPtr volobj;

    virObjectRWLockRead(obj->volumes);
    volobj = virHashLookup(obj->volumes->objsName, name);
    virObjectRWUnlock(obj->volumes);

    if (volobj)
        return volobj->voldef;
    return NULL;
}


struct _virStorageVolObjCountData {
    virConnectPtr conn;
    virStoragePoolVolumeACLFilter filter;
    virStoragePoolDefPtr pooldef;
    int count;
};


static int
virStoragePoolObjNumOfVolumesCb(void *payload,
                                const char *name G_GNUC_UNUSED,
                                void *opaque)
{
    virStorageVolObjPtr volobj = payload;
    struct _virStorageVolObjCountData *data = opaque;

    virObjectLock(volobj);

    if (data->filter &&
        !data->filter(data->conn, data->pooldef, volobj->voldef))
        goto cleanup;

    data->count++;

 cleanup:
    virObjectUnlock(volobj);
    return 0;
}


int
virStoragePoolObjNumOfVolumes(virStoragePoolObjPtr obj,
                              virConnectPtr conn,
                              virStoragePoolVolumeACLFilter filter)
{
    virStorageVolObjListPtr volumes = obj->volumes;
    struct _virStorageVolObjCountData data = {
        .conn = conn, .filter = filter, .pooldef = obj->def, .count = 0 };

    virObjectRWLockRead(volumes);
    virHashForEach(volumes->objsName, virStoragePoolObjNumOfVolumesCb, &data);
    virObjectRWUnlock(volumes);

    return data.count;
}


struct _virStorageVolObjNameData {
    virConnectPtr conn;
    virStoragePoolVolumeACLFilter filter;
    virStoragePoolDefPtr pooldef;
    bool error;
    int nnames;
    int maxnames;
    char **const names;
};

static int
virStoragePoolObjVolumeGetNamesCb(void *payload,
                                  const char *name G_GNUC_UNUSED,
                                  void *opaque)
{
    virStorageVolObjPtr volobj = payload;
    struct _virStorageVolObjNameData *data = opaque;

    if (data->error)
        return 0;

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

    virObjectLock(volobj);

    if (data->filter &&
        !data->filter(data->conn, data->pooldef, volobj->voldef))
        goto cleanup;

    if (data->names)
        data->names[data->nnames] = g_strdup(volobj->voldef->name);

    data->nnames++;

 cleanup:
    virObjectUnlock(volobj);
    return 0;
}


int
virStoragePoolObjVolumeGetNames(virStoragePoolObjPtr obj,
                                virConnectPtr conn,
                                virStoragePoolVolumeACLFilter filter,
                                char **const names,
                                int maxnames)
{
    virStorageVolObjListPtr volumes = obj->volumes;
    struct _virStorageVolObjNameData data = {
        .conn = conn, .filter = filter, .pooldef = obj->def, .error = false,
        .nnames = 0, .maxnames = maxnames, .names = names };

    virObjectRWLockRead(volumes);
    virHashForEach(volumes->objsName, virStoragePoolObjVolumeGetNamesCb, &data);
    virObjectRWUnlock(volumes);

    if (data.error)
        goto error;

    return data.nnames;

 error:
    while (--data.nnames)
        VIR_FREE(data.names[data.nnames]);
    return -1;
}


typedef struct _virStoragePoolObjVolumeListExportData virStoragePoolObjVolumeListExportData;
typedef virStoragePoolObjVolumeListExportData *virStoragePoolObjVolumeListExportDataPtr;
struct _virStoragePoolObjVolumeListExportData {
    virConnectPtr conn;
    virStoragePoolVolumeACLFilter filter;
    virStoragePoolDefPtr pooldef;
    bool error;
    int nvols;
    virStorageVolPtr *vols;
};

static int
virStoragePoolObjVolumeListExportCallback(void *payload,
                                          const char *name G_GNUC_UNUSED,
                                          void *opaque)
{
    virStorageVolObjPtr volobj = payload;
    virStoragePoolObjVolumeListExportDataPtr data = opaque;
    virStorageVolPtr vol = NULL;

    if (data->error)
        return 0;

    virObjectLock(volobj);

    if (data->filter &&
        !data->filter(data->conn, data->pooldef, volobj->voldef))
        goto cleanup;

    if (data->vols) {
        if (!(vol = virGetStorageVol(data->conn, data->pooldef->name,
                                     volobj->voldef->name, volobj->voldef->key,
                                     NULL, NULL))) {
            data->error = true;
            goto cleanup;
        }
        data->vols[data->nvols] = vol;
    }

    data->nvols++;

 cleanup:
    virObjectUnlock(volobj);
    return 0;
}


int
virStoragePoolObjVolumeListExport(virConnectPtr conn,
                                  virStoragePoolObjPtr obj,
                                  virStorageVolPtr **vols,
                                  virStoragePoolVolumeACLFilter filter)
{
    virStorageVolObjListPtr volumes = obj->volumes;
    virStoragePoolObjVolumeListExportData data = {
        .conn = conn, .filter = filter, .pooldef = obj->def, .error = false,
        .nvols = 0, .vols = NULL };

    virObjectRWLockRead(volumes);

    if (!vols) {
        int ret = virHashSize(volumes->objsName);
        virObjectRWUnlock(volumes);
        return ret;
    }

    data.vols = g_new0(virStorageVolPtr, virHashSize(volumes->objsName) + 1);

    virHashForEach(volumes->objsName, virStoragePoolObjVolumeListExportCallback, &data);
    virObjectRWUnlock(volumes);

    if (data.error)
        goto error;

    *vols = data.vols;

    return data.nvols;

 error:
    virObjectListFree(data.vols);
    return -1;
}


/*
 * virStoragePoolObjIsDuplicate:
 * @doms : virStoragePoolObjListPtr to search
 * @def  : virStoragePoolDefPtr definition of pool to lookup
 * @check_active: If true, ensure that pool is not active
 * @objRet: returned pool object
 *
 * Assumes @pools is locked by caller already.
 *
 * Returns: -1 on error (name/uuid mismatch or check_active failure)
 *          0 if pool is new
 *          1 if pool is a duplicate (name and UUID match)
 */
static int
virStoragePoolObjIsDuplicate(virStoragePoolObjListPtr pools,
                             virStoragePoolDefPtr def,
                             bool check_active,
                             virStoragePoolObjPtr *objRet)
{
    int ret = -1;
    virStoragePoolObjPtr obj = NULL;

    /* See if a Pool with matching UUID already exists */
    obj = virStoragePoolObjFindByUUIDLocked(pools, def->uuid);
    if (obj) {
        virObjectLock(obj);

        /* UUID matches, but if names don't match, refuse it */
        if (STRNEQ(obj->def->name, def->name)) {
            char uuidstr[VIR_UUID_STRING_BUFLEN];
            virUUIDFormat(obj->def->uuid, uuidstr);
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("pool '%s' is already defined with uuid %s"),
                           obj->def->name, uuidstr);
            goto cleanup;
        }

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

            if (virStoragePoolObjIsStarting(obj)) {
                virReportError(VIR_ERR_OPERATION_INVALID,
                               _("pool '%s' is starting up"),
                               obj->def->name);
                goto cleanup;
            }
        }

        *objRet = g_steal_pointer(&obj);
        ret = 1;
    } else {
        /* UUID does not match, but if a name matches, refuse it */
        obj = virStoragePoolObjFindByNameLocked(pools, def->name);
        if (obj) {
            char uuidstr[VIR_UUID_STRING_BUFLEN];

            virObjectLock(obj);

            virUUIDFormat(obj->def->uuid, uuidstr);
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("pool '%s' already exists with uuid %s"),
                           def->name, uuidstr);
            goto cleanup;
        }
        ret = 0;
    }

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


static int
getSCSIHostNumber(virStorageAdapterSCSIHostPtr scsi_host,
                  unsigned int *hostnum)
{
    int ret = -1;
    unsigned int num;
    char *name = NULL;

    if (scsi_host->has_parent) {
        virPCIDeviceAddressPtr addr = &scsi_host->parentaddr;
        unsigned int unique_id = scsi_host->unique_id;

        if (!(name = virSCSIHostGetNameByParentaddr(addr->domain,
                                                    addr->bus,
                                                    addr->slot,
                                                    addr->function,
                                                    unique_id)))
            goto cleanup;
        if (virSCSIHostGetNumber(name, &num) < 0)
            goto cleanup;
    } else {
        if (virSCSIHostGetNumber(scsi_host->name, &num) < 0)
            goto cleanup;
    }

    *hostnum = num;
    ret = 0;

 cleanup:
    VIR_FREE(name);
    return ret;
}


static bool
virStorageIsSameHostnum(const char *name,
                        unsigned int scsi_hostnum)
{
    unsigned int fc_hostnum;

    if (virSCSIHostGetNumber(name, &fc_hostnum) == 0 &&
        scsi_hostnum == fc_hostnum)
        return true;

    return false;
}


/*
 * matchFCHostToSCSIHost:
 *
 * @fchost: fc_host adapter ptr (either def or pool->def)
 * @scsi_hostnum: Already determined "scsi_pool" hostnum
 *
 * Returns true/false whether there is a match between the incoming
 *         fc_adapter host# and the scsi_host host#
 */
static bool
matchFCHostToSCSIHost(virStorageAdapterFCHostPtr fchost,
                      unsigned int scsi_hostnum)
{
    virConnectPtr conn = NULL;
    bool ret = false;
    char *name = NULL;
    char *scsi_host_name = NULL;
    char *parent_name = NULL;

    /* If we have a parent defined, get its hostnum, and compare to the
     * scsi_hostnum. If they are the same, then we have a match
     */
    if (fchost->parent &&
        virStorageIsSameHostnum(fchost->parent, scsi_hostnum))
        return true;

    /* If we find an fc adapter name, then either libvirt created a vHBA
     * for this fc_host or a 'virsh nodedev-create' generated a vHBA.
     */
    if ((name = virVHBAGetHostByWWN(NULL, fchost->wwnn, fchost->wwpn))) {

        /* Get the scsi_hostN for the vHBA in order to see if it
         * matches our scsi_hostnum
         */
        if (virStorageIsSameHostnum(name, scsi_hostnum)) {
            ret = true;
            goto cleanup;
        }

        /* We weren't provided a parent, so we have to query the node
         * device driver in order to ascertain the parent of the vHBA.
         * If the parent fc_hostnum is the same as the scsi_hostnum, we
         * have a match.
         */
        if (!fchost->parent &&
            (conn = virGetConnectNodeDev())) {
            scsi_host_name = g_strdup_printf("scsi_%s", name);
            if ((parent_name = virNodeDeviceGetParentName(conn,
                                                          scsi_host_name))) {
                if (virStorageIsSameHostnum(parent_name, scsi_hostnum)) {
                    ret = true;
                    goto cleanup;
                }
            } else {
                /* Throw away the error and fall through */
                virResetLastError();
                VIR_DEBUG("Could not determine parent vHBA");
            }
        }
    }

    /* NB: Lack of a name means that this vHBA hasn't yet been created,
     *     which means our scsi_host cannot be using the vHBA. Furthermore,
     *     lack of a provided parent means libvirt is going to choose the
     *     "best" fc_host capable adapter based on availability. That could
     *     conflict with an existing scsi_host definition, but there's no
     *     way to know that now.
     */

 cleanup:
    VIR_FREE(name);
    VIR_FREE(parent_name);
    VIR_FREE(scsi_host_name);
    virConnectClose(conn);
    return ret;
}


static bool
matchSCSIAdapterParent(virStorageAdapterSCSIHostPtr pool_scsi_host,
                       virStorageAdapterSCSIHostPtr def_scsi_host)
{
    virPCIDeviceAddressPtr pooladdr = &pool_scsi_host->parentaddr;
    virPCIDeviceAddressPtr defaddr = &def_scsi_host->parentaddr;

    if (pooladdr->domain == defaddr->domain &&
        pooladdr->bus == defaddr->bus &&
        pooladdr->slot == defaddr->slot &&
        pooladdr->function == defaddr->function &&
        pool_scsi_host->unique_id == def_scsi_host->unique_id)
        return true;

    return false;
}


static bool
virStoragePoolSourceMatchSingleHost(virStoragePoolSourcePtr poolsrc,
                                    virStoragePoolSourcePtr defsrc)
{
    if (poolsrc->nhost != 1 && defsrc->nhost != 1)
        return false;

    if (defsrc->hosts[0].port &&
        poolsrc->hosts[0].port != defsrc->hosts[0].port)
        return false;

    return STREQ(poolsrc->hosts[0].name, defsrc->hosts[0].name);
}


static bool
virStoragePoolSourceISCSIMatch(virStoragePoolObjPtr obj,
                               virStoragePoolDefPtr def)
{
    virStoragePoolSourcePtr poolsrc = &obj->def->source;
    virStoragePoolSourcePtr defsrc = &def->source;

    /* NB: Do not check the source host name */
    if (STRNEQ_NULLABLE(poolsrc->initiator.iqn, defsrc->initiator.iqn))
        return false;

    return true;
}


static virStoragePoolObjPtr
virStoragePoolObjSourceMatchTypeDIR(virStoragePoolObjPtr obj,
                                    virStoragePoolDefPtr def)
{
    if (obj->def->type == VIR_STORAGE_POOL_DIR) {
        if (STREQ(obj->def->target.path, def->target.path))
            return obj;
    } else if (obj->def->type == VIR_STORAGE_POOL_GLUSTER) {
        if (STREQ(obj->def->source.name, def->source.name) &&
            STREQ_NULLABLE(obj->def->source.dir, def->source.dir) &&
            virStoragePoolSourceMatchSingleHost(&obj->def->source,
                                                &def->source))
            return obj;
    } else if (obj->def->type == VIR_STORAGE_POOL_NETFS) {
        if (STREQ(obj->def->source.dir, def->source.dir) &&
            virStoragePoolSourceMatchSingleHost(&obj->def->source,
                                                &def->source))
            return obj;
    }

    return NULL;
}


static virStoragePoolObjPtr
virStoragePoolObjSourceMatchTypeISCSI(virStoragePoolObjPtr obj,
                                      virStoragePoolDefPtr def)
{
    virStorageAdapterPtr pool_adapter = &obj->def->source.adapter;
    virStorageAdapterPtr def_adapter = &def->source.adapter;
    virStorageAdapterSCSIHostPtr pool_scsi_host;
    virStorageAdapterSCSIHostPtr def_scsi_host;
    virStorageAdapterFCHostPtr pool_fchost;
    virStorageAdapterFCHostPtr def_fchost;
    unsigned int pool_hostnum;
    unsigned int def_hostnum;
    unsigned int scsi_hostnum;

    if (pool_adapter->type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST &&
        def_adapter->type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
        pool_fchost = &pool_adapter->data.fchost;
        def_fchost = &def_adapter->data.fchost;

        if (STREQ(pool_fchost->wwnn, def_fchost->wwnn) &&
            STREQ(pool_fchost->wwpn, def_fchost->wwpn))
            return obj;
    } else if (pool_adapter->type == VIR_STORAGE_ADAPTER_TYPE_SCSI_HOST &&
               def_adapter->type == VIR_STORAGE_ADAPTER_TYPE_SCSI_HOST) {
        pool_scsi_host = &pool_adapter->data.scsi_host;
        def_scsi_host = &def_adapter->data.scsi_host;

        if (pool_scsi_host->has_parent &&
            def_scsi_host->has_parent &&
            matchSCSIAdapterParent(pool_scsi_host, def_scsi_host))
            return obj;

        if (getSCSIHostNumber(pool_scsi_host, &pool_hostnum) < 0 ||
            getSCSIHostNumber(def_scsi_host, &def_hostnum) < 0)
            return NULL;
        if (pool_hostnum == def_hostnum)
            return obj;
    } else if (pool_adapter->type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST &&
               def_adapter->type == VIR_STORAGE_ADAPTER_TYPE_SCSI_HOST) {
        pool_fchost = &pool_adapter->data.fchost;
        def_scsi_host = &def_adapter->data.scsi_host;

        /* Get the scsi_hostN for the scsi_host source adapter def */
        if (getSCSIHostNumber(def_scsi_host, &scsi_hostnum) < 0)
            return NULL;

        if (matchFCHostToSCSIHost(pool_fchost, scsi_hostnum))
            return obj;

    } else if (pool_adapter->type == VIR_STORAGE_ADAPTER_TYPE_SCSI_HOST &&
               def_adapter->type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
        pool_scsi_host = &pool_adapter->data.scsi_host;
        def_fchost = &def_adapter->data.fchost;

        if (getSCSIHostNumber(pool_scsi_host, &scsi_hostnum) < 0)
            return NULL;

        if (matchFCHostToSCSIHost(def_fchost, scsi_hostnum))
            return obj;
    }

    return NULL;
}


static virStoragePoolObjPtr
virStoragePoolObjSourceMatchTypeDEVICE(virStoragePoolObjPtr obj,
                                       virStoragePoolDefPtr def)
{
    virStoragePoolObjPtr matchobj = NULL;

    if (obj->def->type == VIR_STORAGE_POOL_ISCSI) {
        if (def->type != VIR_STORAGE_POOL_ISCSI)
            return NULL;

        if ((matchobj = virStoragePoolSourceFindDuplicateDevices(obj, def))) {
            if (!virStoragePoolSourceISCSIMatch(matchobj, def))
                return NULL;
        }
        return matchobj;
    }

    if (def->type == VIR_STORAGE_POOL_ISCSI)
        return NULL;

    /* VIR_STORAGE_POOL_FS
     * VIR_STORAGE_POOL_LOGICAL
     * VIR_STORAGE_POOL_DISK
     * VIR_STORAGE_POOL_ZFS */
    return virStoragePoolSourceFindDuplicateDevices(obj, def);
}


struct _virStoragePoolObjFindDuplicateData {
    virStoragePoolDefPtr def;
};

static int
virStoragePoolObjSourceFindDuplicateCb(const void *payload,
                                       const char *name G_GNUC_UNUSED,
                                       const void *opaque)
{
    virStoragePoolObjPtr obj = (virStoragePoolObjPtr) payload;
    struct _virStoragePoolObjFindDuplicateData *data =
        (struct _virStoragePoolObjFindDuplicateData *) opaque;

    /* Don't match against ourself if re-defining existing pool ! */
    if (STREQ(obj->def->name, data->def->name))
        return 0;

    switch ((virStoragePoolType)obj->def->type) {
    case VIR_STORAGE_POOL_DIR:
    case VIR_STORAGE_POOL_GLUSTER:
    case VIR_STORAGE_POOL_NETFS:
        if (data->def->type == obj->def->type &&
            virStoragePoolObjSourceMatchTypeDIR(obj, data->def))
            return 1;
        break;

    case VIR_STORAGE_POOL_SCSI:
        if (data->def->type == obj->def->type &&
            virStoragePoolObjSourceMatchTypeISCSI(obj, data->def))
            return 1;
        break;

    case VIR_STORAGE_POOL_ISCSI:
    case VIR_STORAGE_POOL_ISCSI_DIRECT:
    case VIR_STORAGE_POOL_FS:
    case VIR_STORAGE_POOL_LOGICAL:
    case VIR_STORAGE_POOL_DISK:
    case VIR_STORAGE_POOL_ZFS:
        if ((data->def->type == VIR_STORAGE_POOL_ISCSI ||
             data->def->type == VIR_STORAGE_POOL_ISCSI_DIRECT ||
             data->def->type == VIR_STORAGE_POOL_FS ||
             data->def->type == VIR_STORAGE_POOL_LOGICAL ||
             data->def->type == VIR_STORAGE_POOL_DISK ||
             data->def->type == VIR_STORAGE_POOL_ZFS) &&
            virStoragePoolObjSourceMatchTypeDEVICE(obj, data->def))
            return 1;
        break;

    case VIR_STORAGE_POOL_SHEEPDOG:
        if (data->def->type == obj->def->type &&
            virStoragePoolSourceMatchSingleHost(&obj->def->source,
                                                &data->def->source))
            return 1;
        break;

    case VIR_STORAGE_POOL_MPATH:
        /* Only one mpath pool is valid per host */
        if (data->def->type == obj->def->type)
            return 1;
        break;

    case VIR_STORAGE_POOL_VSTORAGE:
        if (data->def->type == obj->def->type &&
            STREQ(obj->def->source.name, data->def->source.name))
            return 1;
        break;

    case VIR_STORAGE_POOL_RBD:
    case VIR_STORAGE_POOL_LAST:
        break;
    }

    return 0;
}


static int
virStoragePoolObjSourceFindDuplicate(virStoragePoolObjListPtr pools,
                                     virStoragePoolDefPtr def)
{
    struct _virStoragePoolObjFindDuplicateData data = {.def = def};
    virStoragePoolObjPtr obj = NULL;

    obj = virHashSearch(pools->objs, virStoragePoolObjSourceFindDuplicateCb,
                        &data, NULL);

    if (obj) {
        virReportError(VIR_ERR_OPERATION_FAILED,
                       _("Storage source conflict with pool: '%s'"),
                       obj->def->name);
        return -1;
    }

    return 0;
}


static void
virStoragePoolObjAssignDef(virStoragePoolObjPtr obj,
                           virStoragePoolDefPtr def,
                           unsigned int flags)
{
    if (virStoragePoolObjIsActive(obj) ||
        virStoragePoolObjIsStarting(obj)) {
        virStoragePoolDefFree(obj->newDef);
        obj->newDef = def;
    } else {
        if (!obj->newDef &&
            flags & VIR_STORAGE_POOL_OBJ_LIST_ADD_LIVE)
            obj->newDef = g_steal_pointer(&obj->def);

        virStoragePoolDefFree(obj->def);
        obj->def = def;
    }
}


/**
 * virStoragePoolObjListAdd:
 * @pools: Storage Pool object list pointer
 * @def: Storage pool definition to add or update
 * @flags: bitwise-OR of VIR_STORAGE_POOL_OBJ_LIST_* flags
 *
 * Lookup the @def to see if it already exists in the @pools in order
 * to either update or add if it does not exist.
 *
 * Use VIR_STORAGE_POOL_OBJ_LIST_ADD_LIVE to denote that @def
 * refers to an active definition and thus any possible inactive
 * definition found should be saved to ->newDef (in case of
 * future restore).
 *
 * If VIR_STORAGE_POOL_OBJ_LIST_ADD_CHECK_LIVE is set in @flags
 * then this will fail if the pool exists and is active.
 *
 * Returns locked and reffed object pointer or NULL on error
 */
virStoragePoolObjPtr
virStoragePoolObjListAdd(virStoragePoolObjListPtr pools,
                         virStoragePoolDefPtr def,
                         unsigned int flags)
{
    virStoragePoolObjPtr obj = NULL;
    char uuidstr[VIR_UUID_STRING_BUFLEN];
    int rc;

    virObjectRWLockWrite(pools);

    if (virStoragePoolObjSourceFindDuplicate(pools, def) < 0)
        goto error;

    rc = virStoragePoolObjIsDuplicate(pools, def,
                                      !!(flags & VIR_STORAGE_POOL_OBJ_LIST_ADD_CHECK_LIVE),
                                      &obj);

    if (rc < 0)
        goto error;
    if (rc > 0) {
        virStoragePoolObjAssignDef(obj, def, flags);
        virObjectRWUnlock(pools);
        return obj;
    }

    if (!(obj = virStoragePoolObjNew()))
        goto error;

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

    if (virHashAddEntry(pools->objsName, def->name, obj) < 0) {
        virHashRemoveEntry(pools->objs, uuidstr);
        goto error;
    }
    virObjectRef(obj);
    obj->def = def;
    virObjectRWUnlock(pools);
    return obj;

 error:
    virStoragePoolObjEndAPI(&obj);
    virObjectRWUnlock(pools);
    return NULL;
}


static virStoragePoolObjPtr
virStoragePoolObjLoad(virStoragePoolObjListPtr pools,
                      const char *file,
                      const char *path,
                      const char *autostartLink)
{
    virStoragePoolObjPtr obj;
    g_autoptr(virStoragePoolDef) def = NULL;

    if (!(def = virStoragePoolDefParseFile(path)))
        return NULL;

    if (!virStringMatchesNameSuffix(file, def->name, ".xml")) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Storage pool config filename '%s' does "
                         "not match pool name '%s'"),
                       path, def->name);
        return NULL;
    }

    if (!(obj = virStoragePoolObjListAdd(pools, def, 0)))
        return NULL;
    def = NULL;

    VIR_FREE(obj->configFile);  /* for driver reload */
    obj->configFile = g_strdup(path);
    VIR_FREE(obj->autostartLink); /* for driver reload */
    obj->autostartLink = g_strdup(autostartLink);

    obj->autostart = virFileLinkPointsTo(obj->autostartLink,
                                         obj->configFile);

    return obj;
}


static virStoragePoolObjPtr
virStoragePoolObjLoadState(virStoragePoolObjListPtr pools,
                           const char *stateDir,
                           const char *name)
{
    char *stateFile = NULL;
    virStoragePoolObjPtr obj = NULL;
    xmlDocPtr xml = NULL;
    xmlXPathContextPtr ctxt = NULL;
    xmlNodePtr node = NULL;
    g_autoptr(virStoragePoolDef) def = NULL;

    if (!(stateFile = virFileBuildPath(stateDir, name, ".xml")))
        return NULL;

    if (!(xml = virXMLParseCtxt(stateFile, NULL, _("(pool state)"), &ctxt)))
        goto cleanup;

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

    ctxt->node = node;
    if (!(def = virStoragePoolDefParseXML(ctxt)))
        goto cleanup;

    if (STRNEQ(name, def->name)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Storage pool state file '%s' does not match "
                         "pool name '%s'"),
                       stateFile, def->name);
        goto cleanup;
    }

    /* create the object */
    if (!(obj = virStoragePoolObjListAdd(pools, def,
                                         VIR_STORAGE_POOL_OBJ_LIST_ADD_CHECK_LIVE)))
        goto cleanup;
    def = NULL;

    /* XXX: future handling of some additional useful status data,
     * for now, if a status file for a pool exists, the pool will be marked
     * as active
     */

    obj->active = true;

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


int
virStoragePoolObjLoadAllState(virStoragePoolObjListPtr pools,
                              const char *stateDir)
{
    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) {
        virStoragePoolObjPtr obj;

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

        if (!(obj = virStoragePoolObjLoadState(pools, stateDir, entry->d_name)))
            continue;
        virStoragePoolObjEndAPI(&obj);
    }

    return ret;
}


int
virStoragePoolObjLoadAllConfigs(virStoragePoolObjListPtr pools,
                                const char *configDir,
                                const char *autostartDir)
{
    g_autoptr(DIR) dir = NULL;
    struct dirent *entry;
    int ret;
    int rc;

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

    while ((ret = virDirRead(dir, &entry, configDir)) > 0) {
        char *path;
        char *autostartLink;
        virStoragePoolObjPtr obj;

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

        if (!(path = virFileBuildPath(configDir, entry->d_name, NULL)))
            continue;

        if (!(autostartLink = virFileBuildPath(autostartDir, entry->d_name,
                                               NULL))) {
            VIR_FREE(path);
            continue;
        }

        obj = virStoragePoolObjLoad(pools, entry->d_name, path, autostartLink);
        virStoragePoolObjEndAPI(&obj);

        VIR_FREE(path);
        VIR_FREE(autostartLink);
    }

    return ret;
}


int
virStoragePoolObjSaveDef(virStorageDriverStatePtr driver,
                         virStoragePoolObjPtr obj,
                         virStoragePoolDefPtr def)
{
    if (!obj->configFile) {
        if (virFileMakePath(driver->configDir) < 0) {
            virReportSystemError(errno,
                                 _("cannot create config directory %s"),
                                 driver->configDir);
            return -1;
        }

        if (!(obj->configFile = virFileBuildPath(driver->configDir,
                                                 def->name, ".xml"))) {
            return -1;
        }

        if (!(obj->autostartLink = virFileBuildPath(driver->autostartDir,
                                                    def->name, ".xml"))) {
            VIR_FREE(obj->configFile);
            return -1;
        }
    }

    return virStoragePoolSaveConfig(obj->configFile, def);
}


int
virStoragePoolObjDeleteDef(virStoragePoolObjPtr obj)
{
    if (!obj->configFile) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("no config file for %s"), obj->def->name);
        return -1;
    }

    if (unlink(obj->configFile) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("cannot remove config for %s"),
                       obj->def->name);
        return -1;
    }

    return 0;
}


struct _virStoragePoolCountData {
    virConnectPtr conn;
    virStoragePoolObjListACLFilter filter;
    bool wantActive;
    int count;
};


static int
virStoragePoolObjNumOfStoragePoolsCb(void *payload,
                                     const char *name G_GNUC_UNUSED,
                                     void *opaque)
{
    virStoragePoolObjPtr obj = payload;
    struct _virStoragePoolCountData *data = opaque;

    virObjectLock(obj);

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

    if (data->wantActive != virStoragePoolObjIsActive(obj))
        goto cleanup;

    data->count++;

 cleanup:
    virObjectUnlock(obj);
    return 0;
}


int
virStoragePoolObjNumOfStoragePools(virStoragePoolObjListPtr pools,
                                   virConnectPtr conn,
                                   bool wantActive,
                                   virStoragePoolObjListACLFilter filter)
{
    struct _virStoragePoolCountData data = {
        .conn = conn, .filter = filter, .wantActive = wantActive, .count = 0 };

    virObjectRWLockRead(pools);
    virHashForEach(pools->objs, virStoragePoolObjNumOfStoragePoolsCb, &data);
    virObjectRWUnlock(pools);

    return data.count;
}


struct _virStoragePoolNameData {
    virConnectPtr conn;
    virStoragePoolObjListACLFilter filter;
    bool wantActive;
    bool error;
    int nnames;
    int maxnames;
    char **const names;
};


static int
virStoragePoolObjGetNamesCb(void *payload,
                            const char *name G_GNUC_UNUSED,
                            void *opaque)
{
    virStoragePoolObjPtr obj = payload;
    struct _virStoragePoolNameData *data = opaque;

    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->wantActive != virStoragePoolObjIsActive(obj))
        goto cleanup;

    if (data->names)
        data->names[data->nnames] = g_strdup(obj->def->name);

    data->nnames++;

 cleanup:
    virObjectUnlock(obj);
    return 0;
}


int
virStoragePoolObjGetNames(virStoragePoolObjListPtr pools,
                          virConnectPtr conn,
                          bool wantActive,
                          virStoragePoolObjListACLFilter filter,
                          char **const names,
                          int maxnames)
{
    struct _virStoragePoolNameData data = {
        .conn = conn, .filter = filter, .wantActive = wantActive,
        .error = false, .nnames = 0, .maxnames = maxnames, .names = names };

    virObjectRWLockRead(pools);
    virHashForEach(pools->objs, virStoragePoolObjGetNamesCb, &data);
    virObjectRWUnlock(pools);

    if (data.error)
        goto error;

    return data.nnames;

 error:
    while (data.nnames)
        VIR_FREE(data.names[--data.nnames]);
    return -1;
}


#define MATCH(FLAG) (flags & (FLAG))
static bool
virStoragePoolObjMatch(virStoragePoolObjPtr obj,
                       unsigned int flags)
{
    /* filter by active state */
    if (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_FILTERS_ACTIVE) &&
        !((MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE) &&
           virStoragePoolObjIsActive(obj)) ||
          (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_INACTIVE) &&
           !virStoragePoolObjIsActive(obj))))
        return false;

    /* filter by persistence */
    if (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_FILTERS_PERSISTENT) &&
        !((MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_PERSISTENT) &&
           obj->configFile) ||
          (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_TRANSIENT) &&
           !obj->configFile)))
        return false;

    /* filter by autostart option */
    if (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_FILTERS_AUTOSTART) &&
        !((MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_AUTOSTART) &&
           obj->autostart) ||
          (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_NO_AUTOSTART) &&
           !obj->autostart)))
        return false;

    /* filter by pool type */
    if (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_FILTERS_POOL_TYPE)) {
        if (!((MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_DIR) &&
               (obj->def->type == VIR_STORAGE_POOL_DIR))     ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_FS) &&
               (obj->def->type == VIR_STORAGE_POOL_FS))      ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_NETFS) &&
               (obj->def->type == VIR_STORAGE_POOL_NETFS))   ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_LOGICAL) &&
               (obj->def->type == VIR_STORAGE_POOL_LOGICAL)) ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_DISK) &&
               (obj->def->type == VIR_STORAGE_POOL_DISK))    ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI) &&
               (obj->def->type == VIR_STORAGE_POOL_ISCSI))   ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_SCSI) &&
               (obj->def->type == VIR_STORAGE_POOL_SCSI))    ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_MPATH) &&
               (obj->def->type == VIR_STORAGE_POOL_MPATH))   ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_RBD) &&
               (obj->def->type == VIR_STORAGE_POOL_RBD))     ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG) &&
               (obj->def->type == VIR_STORAGE_POOL_SHEEPDOG)) ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER) &&
               (obj->def->type == VIR_STORAGE_POOL_GLUSTER)) ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_ZFS) &&
               (obj->def->type == VIR_STORAGE_POOL_ZFS))     ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_VSTORAGE) &&
               (obj->def->type == VIR_STORAGE_POOL_VSTORAGE)) ||
              (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI_DIRECT) &&
               (obj->def->type == VIR_STORAGE_POOL_ISCSI_DIRECT))))
            return false;
    }

    return true;
}
#undef MATCH


typedef struct _virStoragePoolObjListExportData virStoragePoolObjListExportData;
typedef virStoragePoolObjListExportData *virStoragePoolObjListExportDataPtr;
struct _virStoragePoolObjListExportData {
    virConnectPtr conn;
    virStoragePoolObjListACLFilter filter;
    bool checkActive;
    bool wantActive;
    bool checkMatch;
    unsigned int flags;
    bool error;
    int nPools;
    virStoragePoolPtr *pools;
};


static int
virStoragePoolObjListExportCallback(void *payload,
                                    const char *name G_GNUC_UNUSED,
                                    void *opaque)
{
    virStoragePoolObjPtr obj = payload;
    virStoragePoolObjListExportDataPtr data = opaque;
    virStoragePoolPtr pool = NULL;

    if (data->error)
        return 0;

    virObjectLock(obj);

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

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

    if (data->pools) {
        if (!(pool = virGetStoragePool(data->conn, obj->def->name,
                                       obj->def->uuid, NULL, NULL))) {
            data->error = true;
            goto cleanup;
        }
        data->pools[data->nPools] = pool;
    }

    data->nPools++;

 cleanup:
    virObjectUnlock(obj);
    return 0;
}


int
virStoragePoolObjListExport(virConnectPtr conn,
                            virStoragePoolObjListPtr poolobjs,
                            virStoragePoolPtr **pools,
                            virStoragePoolObjListFilter filter,
                            unsigned int flags)
{
    virStoragePoolObjListExportData data = {
        .conn = conn, .filter = filter, .flags = flags, .error = false,
        .nPools = 0, .pools = NULL };

    virObjectRWLockRead(poolobjs);

    if (!pools) {
        int ret = virHashSize(poolobjs->objs);
        virObjectRWUnlock(poolobjs);
        return ret;
    }

    data.pools = g_new0(virStoragePoolPtr, virHashSize(poolobjs->objs) + 1);

    virHashForEach(poolobjs->objs, virStoragePoolObjListExportCallback, &data);
    virObjectRWUnlock(poolobjs);

    if (data.error)
        goto error;

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

    return data.nPools;

 error:
    virObjectListFree(data.pools);
    return -1;
}
