diff --git a/src/hypervisor/virclosecallbacks.c b/src/hypervisor/virclosecallbacks.c index a08464438a..fd6226304e 100644 --- a/src/hypervisor/virclosecallbacks.c +++ b/src/hypervisor/virclosecallbacks.c @@ -310,3 +310,339 @@ virCloseCallbacksRun(virCloseCallbacks *closeCallbacks, VIR_FREE(list->entries); VIR_FREE(list); } + + +struct _virCloseCallbacksDomainData { + virConnectPtr conn; + virCloseCallback cb; +}; +typedef struct _virCloseCallbacksDomainData virCloseCallbacksDomainData; + + +static void +virCloseCallbacksDomainDataFree(virCloseCallbacksDomainData* data) +{ + g_free(data); +} +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virCloseCallbacksDomainData, virCloseCallbacksDomainDataFree); + + +virClass *virCloseCallbacksDomainListClass; + +struct _virCloseCallbacksDomainList { + virObjectLockable parent; + + GList *callbacks; +}; +typedef struct _virCloseCallbacksDomainList virCloseCallbacksDomainList; + + +static void +virCloseCallbacksDomainListDispose(void *obj G_GNUC_UNUSED) +{ + virCloseCallbacksDomainList *cc = obj; + + g_list_free_full(cc->callbacks, (GDestroyNotify) virCloseCallbacksDomainDataFree); +} + + +static int +virCloseCallbacksDomainListOnceInit(void) +{ + if (!(VIR_CLASS_NEW(virCloseCallbacksDomainList, virClassForObjectLockable()))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virCloseCallbacksDomainList); + + +/** + * virCloseCallbacksDomainAlloc: + * + * Allocates and returns a data structure for holding close callback data in + * a virDomainObj. + */ +virObject * +virCloseCallbacksDomainAlloc(void) +{ + if (virCloseCallbacksDomainListInitialize() < 0) + abort(); + + return virObjectNew(virCloseCallbacksDomainListClass); +} + + +/** + * virCloseCallbacksDomainAdd: + * @vm: domain object + * @conn: pointer to the connection which should trigger the close callback + * @cb: pointer to the callback function + * + * Registers @cb as a connection close callback for the @conn connection with + * the @vm domain. Duplicate registrations are ignored. + * + * Caller must hold lock on @vm. + */ +void +virCloseCallbacksDomainAdd(virDomainObj *vm, + virConnectPtr conn, + virCloseCallback cb) +{ + virCloseCallbacksDomainList *cc = (virCloseCallbacksDomainList *) vm->closecallbacks; + + if (!conn || !cb) + return; + + VIR_WITH_OBJECT_LOCK_GUARD(cc) { + virCloseCallbacksDomainData *data; + GList *n; + + for (n = cc->callbacks; n; n = n->next) { + data = n->data; + + if (data->cb == cb && data->conn == conn) + return; + } + + data = g_new0(virCloseCallbacksDomainData, 1); + data->conn = conn; + data->cb = cb; + + cc->callbacks = g_list_prepend(cc->callbacks, data); + } +} + + +/** + * virCloseCallbacksDomainMatch: + * @data: pointer to a close callback data structure + * @conn: connection pointer matched against @data + * @cb: callback pointer matched against @data + * + * Returns true if the @data callback structure matches the requested @conn + * and/or @cb parameters. If either of @conn/@cb is NULL it is interpreted as + * a wildcard. + */ +static bool +virCloseCallbacksDomainMatch(virCloseCallbacksDomainData *data, + virConnectPtr conn, + virCloseCallback cb) +{ + if (conn && cb) + return data->conn == conn && data->cb == cb; + + if (conn) + return data->conn == conn; + + if (cb) + return data->cb == cb; + + return true; +} + + +/** + * virCloseCallbacksDomainIsRegistered: + * @vm: domain object + * @conn: connection pointer + * @cb: callback pointer + * + * Returns true if @vm has one or more matching (see virCloseCallbacksDomainMatch) + * callback(s) registered. Caller must hold lock on @vm. + */ +bool +virCloseCallbacksDomainIsRegistered(virDomainObj *vm, + virConnectPtr conn, + virCloseCallback cb) +{ + virCloseCallbacksDomainList *cc = (virCloseCallbacksDomainList *) vm->closecallbacks; + + VIR_WITH_OBJECT_LOCK_GUARD(cc) { + GList *n; + + for (n = cc->callbacks; n; n = n->next) { + virCloseCallbacksDomainData *data = n->data; + + if (virCloseCallbacksDomainMatch(data, conn, cb)) + return true; + } + } + + return false; +} + + +/** + * virCloseCallbacksDomainRemove: + * @vm: domain object + * @conn: connection pointer + * @cb: callback pointer + * + * Removes all the registered matching (see virCloseCallbacksDomainMatch) + * callbacks for @vm. Caller must hold lock on @vm. + */ +void +virCloseCallbacksDomainRemove(virDomainObj *vm, + virConnectPtr conn, + virCloseCallback cb) +{ + virCloseCallbacksDomainList *cc = (virCloseCallbacksDomainList *) vm->closecallbacks; + + VIR_WITH_OBJECT_LOCK_GUARD(cc) { + GList *n = cc->callbacks; + + while (n) { + GList *cur = n; + + n = n->next; + + if (virCloseCallbacksDomainMatch(cur->data, conn, cb)) { + cc->callbacks = g_list_remove_link(cc->callbacks, cur); + g_list_free_full(cur, (GDestroyNotify) virCloseCallbacksDomainDataFree); + } + } + } +} + + +/** + * virCloseCallbacksDomainFetchForConn: + * @vm: domain object + * @conn: pointer to connection being closed + * + * Fetches connection close callbacks for @conn from @vm. The fetched close + * callbacks are removed from the list of callbacks of @vm. This function + * must be called with lock on @vm held. Caller is responsible for freeing the + * returned list. + */ +static GList * +virCloseCallbacksDomainFetchForConn(virDomainObj *vm, + virConnectPtr conn) +{ + virCloseCallbacksDomainList *cc = (virCloseCallbacksDomainList *) vm->closecallbacks; + GList *conncallbacks = NULL; + + VIR_WITH_OBJECT_LOCK_GUARD(cc) { + GList *n; + + for (n = cc->callbacks; n;) { + virCloseCallbacksDomainData *data = n->data; + GList *cur = n; + + n = n->next; + + if (data->conn == conn) { + cc->callbacks = g_list_remove_link(cc->callbacks, cur); + conncallbacks = g_list_concat(cur, conncallbacks); + } + } + } + + return conncallbacks; +} + + +/** + * virCloseCallbacksDomainRun + * @vm: domain object + * @conn: pointer to connection being closed + * + * Fetches and sequentially calls all connection close callbacks for @conn from + * @vm. This function must be called with lock on @vm held. + */ +static void +virCloseCallbacksDomainRun(virDomainObj *vm, + virConnectPtr conn) +{ + g_autolist(virCloseCallbacksDomainData) callbacks = NULL; + GList *n; + + callbacks = virCloseCallbacksDomainFetchForConn(vm, conn); + + for (n = callbacks; n; n = n->next) { + virCloseCallbacksDomainData *data = n->data; + + VIR_DEBUG("vm='%s' cb='%p'", vm->def->name, data->cb); + + (data->cb)(vm, conn); + } +} + + +/** + * virCloseCallbacksDomainHasCallbackForConn: + * @vm: domain object + * @conn: connection being closed + * + * Returns true if @vm has a callback registered for the @conn connection. This + * function doesn't require a lock being held on @vm. + */ +static bool +virCloseCallbacksDomainHasCallbackForConn(virDomainObj *vm, + virConnectPtr conn) +{ + /* we can access vm->closecallbacks as it's a immutable pointer */ + virCloseCallbacksDomainList *cc = (virCloseCallbacksDomainList *) vm->closecallbacks; + + if (!cc) + return false; + + VIR_WITH_OBJECT_LOCK_GUARD(cc) { + GList *n; + + for (n = cc->callbacks; n; n = n->next) { + virCloseCallbacksDomainData *data = n->data; + + if (data->conn == conn) + return true; + } + } + + return false; +} + + +/** + * virCloseCallbacksDomainRunForConn: + * @domains: domain list object + * @conn: connection being closed + * + * Finds all domains in @domains which registered one or more connection close + * callbacks for @conn and calls the callbacks. This function is designed to + * be called from virDrvConnectClose function of individual drivers. + * + * To minimize lock contention the function first fetches a list of all domain + * objects, then checks whether a connect close callback is actually registered + * for the domain object and just then acquires the lock on the VM object. + */ +void +virCloseCallbacksDomainRunForConn(virDomainObjList *domains, + virConnectPtr conn) +{ + virDomainObj **vms = NULL; + size_t nvms; + size_t i; + + VIR_DEBUG("conn=%p", conn); + + virDomainObjListCollectAll(domains, &vms, &nvms); + + for (i = 0; i < nvms; i++) { + virDomainObj *vm = vms[i]; + + if (!virCloseCallbacksDomainHasCallbackForConn(vm, conn)) + continue; + + VIR_WITH_OBJECT_LOCK_GUARD(vm) { + /* VIR_WITH_OBJECT_LOCK_GUARD is a for loop, so this break applies to that */ + if (vm->removing) + break; + + virCloseCallbacksDomainRun(vm, conn); + } + } + + virObjectListFreeCount(vms, nvms); +} diff --git a/src/hypervisor/virclosecallbacks.h b/src/hypervisor/virclosecallbacks.h index 7afb0e5640..0f4c08572c 100644 --- a/src/hypervisor/virclosecallbacks.h +++ b/src/hypervisor/virclosecallbacks.h @@ -49,3 +49,27 @@ void virCloseCallbacksRun(virCloseCallbacks *closeCallbacks, virConnectPtr conn, virDomainObjList *domains); + +/* ---- */ + +virObject * +virCloseCallbacksDomainAlloc(void); + +void +virCloseCallbacksDomainAdd(virDomainObj *vm, + virConnectPtr conn, + virCloseCallback cb); + +void +virCloseCallbacksDomainRemove(virDomainObj *vm, + virConnectPtr conn, + virCloseCallback cb); + +bool +virCloseCallbacksDomainIsRegistered(virDomainObj *vm, + virConnectPtr conn, + virCloseCallback cb); + +void +virCloseCallbacksDomainRunForConn(virDomainObjList *domains, + virConnectPtr conn); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 8f50f9fa1e..b1fa23729a 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1607,6 +1607,11 @@ virDomainDriverSetupPersistentDefBlkioParams; # hypervisor/virclosecallbacks.h +virCloseCallbacksDomainAdd; +virCloseCallbacksDomainAlloc; +virCloseCallbacksDomainIsRegistered; +virCloseCallbacksDomainRemove; +virCloseCallbacksDomainRunForConn; virCloseCallbacksGet; virCloseCallbacksNew; virCloseCallbacksRun;