/* * 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 VIR_LOG_INIT("util.closecallbacks"); 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); }