/*
* virclosecallbacks.c: Connection close callbacks routines
*
* Copyright (C) 2013 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*
* Authors:
* Daniel P. Berrange
* Michal Privoznik
*/
#include
#include "viralloc.h"
#include "virclosecallbacks.h"
#include "virlog.h"
#include "virobject.h"
#define VIR_FROM_THIS VIR_FROM_NONE
typedef struct _virDriverCloseDef virDriverCloseDef;
typedef virDriverCloseDef *virDriverCloseDefPtr;
struct _virDriverCloseDef {
virConnectPtr conn;
virCloseCallback cb;
};
struct _virCloseCallbacks {
virObjectLockable parent;
/* UUID string to qemuDriverCloseDef mapping */
virHashTablePtr list;
};
static virClassPtr virCloseCallbacksClass;
static void virCloseCallbacksDispose(void *obj);
static int virCloseCallbacksOnceInit(void)
{
virCloseCallbacksClass = virClassNew(virClassForObjectLockable(),
"virCloseCallbacks",
sizeof(virCloseCallbacks),
virCloseCallbacksDispose);
if (!virCloseCallbacksClass)
return -1;
else
return 0;
}
VIR_ONCE_GLOBAL_INIT(virCloseCallbacks)
static void
virCloseCallbacksFreeData(void *payload,
const void *name ATTRIBUTE_UNUSED)
{
VIR_FREE(payload);
}
virCloseCallbacksPtr
virCloseCallbacksNew(void)
{
virCloseCallbacksPtr closeCallbacks;
if (virCloseCallbacksInitialize() < 0)
return NULL;
if (!(closeCallbacks = virObjectLockableNew(virCloseCallbacksClass)))
return NULL;
closeCallbacks->list = virHashCreate(5, virCloseCallbacksFreeData);
if (!closeCallbacks->list) {
virObjectUnref(closeCallbacks);
return NULL;
}
return closeCallbacks;
}
static void
virCloseCallbacksDispose(void *obj)
{
virCloseCallbacksPtr closeCallbacks = obj;
virHashFree(closeCallbacks->list);
}
int
virCloseCallbacksSet(virCloseCallbacksPtr closeCallbacks,
virDomainObjPtr vm,
virConnectPtr conn,
virCloseCallback cb)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virDriverCloseDefPtr closeDef;
int ret = -1;
virUUIDFormat(vm->def->uuid, uuidstr);
VIR_DEBUG("vm=%s, uuid=%s, conn=%p, cb=%p",
vm->def->name, uuidstr, conn, cb);
virObjectLock(closeCallbacks);
closeDef = virHashLookup(closeCallbacks->list, uuidstr);
if (closeDef) {
if (closeDef->conn != conn) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Close callback for domain %s already registered"
" with another connection %p"),
vm->def->name, closeDef->conn);
goto cleanup;
}
if (closeDef->cb && closeDef->cb != cb) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Another close callback is already defined for"
" domain %s"), vm->def->name);
goto cleanup;
}
closeDef->cb = cb;
} else {
if (VIR_ALLOC(closeDef) < 0)
goto cleanup;
closeDef->conn = conn;
closeDef->cb = cb;
if (virHashAddEntry(closeCallbacks->list, uuidstr, closeDef) < 0) {
VIR_FREE(closeDef);
goto cleanup;
}
}
ret = 0;
cleanup:
virObjectUnlock(closeCallbacks);
return ret;
}
int
virCloseCallbacksUnset(virCloseCallbacksPtr closeCallbacks,
virDomainObjPtr vm,
virCloseCallback cb)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virDriverCloseDefPtr closeDef;
int ret = -1;
virUUIDFormat(vm->def->uuid, uuidstr);
VIR_DEBUG("vm=%s, uuid=%s, cb=%p",
vm->def->name, uuidstr, cb);
virObjectLock(closeCallbacks);
closeDef = virHashLookup(closeCallbacks->list, uuidstr);
if (!closeDef)
goto cleanup;
if (closeDef->cb && closeDef->cb != cb) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Trying to remove mismatching close callback for"
" domain %s"), vm->def->name);
goto cleanup;
}
ret = virHashRemoveEntry(closeCallbacks->list, uuidstr);
cleanup:
virObjectUnlock(closeCallbacks);
return ret;
}
virCloseCallback
virCloseCallbacksGet(virCloseCallbacksPtr closeCallbacks,
virDomainObjPtr vm,
virConnectPtr conn)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virDriverCloseDefPtr closeDef;
virCloseCallback cb = NULL;
virUUIDFormat(vm->def->uuid, uuidstr);
VIR_DEBUG("vm=%s, uuid=%s, conn=%p",
vm->def->name, uuidstr, conn);
virObjectLock(closeCallbacks);
closeDef = virHashLookup(closeCallbacks->list, uuidstr);
if (closeDef && (!conn || closeDef->conn == conn))
cb = closeDef->cb;
virObjectUnlock(closeCallbacks);
VIR_DEBUG("cb=%p", cb);
return cb;
}
virConnectPtr
virCloseCallbacksGetConn(virCloseCallbacksPtr closeCallbacks,
virDomainObjPtr vm)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virDriverCloseDefPtr closeDef;
virConnectPtr conn = NULL;
virUUIDFormat(vm->def->uuid, uuidstr);
VIR_DEBUG("vm=%s, uuid=%s", vm->def->name, uuidstr);
virObjectLock(closeCallbacks);
closeDef = virHashLookup(closeCallbacks->list, uuidstr);
if (closeDef)
conn = closeDef->conn;
virObjectUnlock(closeCallbacks);
VIR_DEBUG("conn=%p", conn);
return conn;
}
typedef struct _virCloseCallbacksListEntry virCloseCallbacksListEntry;
typedef virCloseCallbacksListEntry *virCloseCallbacksListEntryPtr;
struct _virCloseCallbacksListEntry {
unsigned char uuid[VIR_UUID_BUFLEN];
virCloseCallback callback;
};
typedef struct _virCloseCallbacksList virCloseCallbacksList;
typedef virCloseCallbacksList *virCloseCallbacksListPtr;
struct _virCloseCallbacksList {
size_t nentries;
virCloseCallbacksListEntryPtr entries;
};
struct virCloseCallbacksData {
virConnectPtr conn;
virCloseCallbacksListPtr list;
bool oom;
};
static void
virCloseCallbacksGetOne(void *payload,
const void *key,
void *opaque)
{
struct virCloseCallbacksData *data = opaque;
virDriverCloseDefPtr closeDef = payload;
const char *uuidstr = key;
unsigned char uuid[VIR_UUID_BUFLEN];
if (virUUIDParse(uuidstr, uuid) < 0)
return;
VIR_DEBUG("conn=%p, thisconn=%p, uuid=%s, cb=%p",
closeDef->conn, data->conn, uuidstr, closeDef->cb);
if (data->conn != closeDef->conn || !closeDef->cb)
return;
if (VIR_EXPAND_N(data->list->entries,
data->list->nentries, 1) < 0) {
data->oom = true;
return;
}
memcpy(data->list->entries[data->list->nentries - 1].uuid,
uuid, VIR_UUID_BUFLEN);
data->list->entries[data->list->nentries - 1].callback = closeDef->cb;
}
static virCloseCallbacksListPtr
virCloseCallbacksGetForConn(virCloseCallbacksPtr closeCallbacks,
virConnectPtr conn)
{
virCloseCallbacksListPtr list = NULL;
struct virCloseCallbacksData data;
if (VIR_ALLOC(list) < 0)
return NULL;
data.conn = conn;
data.list = list;
data.oom = false;
virHashForEach(closeCallbacks->list, virCloseCallbacksGetOne, &data);
if (data.oom) {
VIR_FREE(list->entries);
VIR_FREE(list);
virReportOOMError();
return NULL;
}
return list;
}
void
virCloseCallbacksRun(virCloseCallbacksPtr closeCallbacks,
virConnectPtr conn,
virDomainObjListPtr domains,
void *opaque)
{
virCloseCallbacksListPtr list;
size_t i;
VIR_DEBUG("conn=%p", conn);
/* We must not hold the lock while running the callbacks,
* so first we obtain the list of callbacks, then remove
* them all from the hash. At that point we can release
* the lock and run the callbacks safely. */
virObjectLock(closeCallbacks);
list = virCloseCallbacksGetForConn(closeCallbacks, conn);
if (!list)
return;
for (i = 0; i < list->nentries; i++) {
virHashRemoveEntry(closeCallbacks->list,
list->entries[i].uuid);
}
virObjectUnlock(closeCallbacks);
for (i = 0; i < list->nentries; i++) {
virDomainObjPtr vm;
if (!(vm = virDomainObjListFindByUUID(domains,
list->entries[i].uuid))) {
char uuidstr[VIR_UUID_STRING_BUFLEN];
virUUIDFormat(list->entries[i].uuid, uuidstr);
VIR_DEBUG("No domain object with UUID %s", uuidstr);
continue;
}
vm = list->entries[i].callback(vm, conn, opaque);
if (vm)
virObjectUnlock(vm);
}
VIR_FREE(list->entries);
VIR_FREE(list);
}