diff --git a/include/libvirt/libvirt-admin.h b/include/libvirt/libvirt-admin.h index d22d24770c..a921b2b15c 100644 --- a/include/libvirt/libvirt-admin.h +++ b/include/libvirt/libvirt-admin.h @@ -61,6 +61,25 @@ int virAdmGetVersion(unsigned long long *libVer); char *virAdmConnectGetURI(virAdmConnectPtr conn); +/** + * virAdmConnectCloseFunc: + * @conn: virAdmConnect connection + * @reason: reason why the connection was closed (see virConnectCloseReason) + * @opaque: opaque client data + * + * A callback to be registered, in case a connection was closed. + */ +typedef void (*virAdmConnectCloseFunc)(virAdmConnectPtr conn, + int reason, + void *opaque); + +int virAdmConnectRegisterCloseCallback(virAdmConnectPtr conn, + virAdmConnectCloseFunc cb, + void *opaque, + virFreeCallback freecb); +int virAdmConnectUnregisterCloseCallback(virAdmConnectPtr conn, + virAdmConnectCloseFunc cb); + # ifdef __cplusplus } # endif diff --git a/src/admin/admin_remote.c b/src/admin/admin_remote.c index 2c02ba6ec1..7b40ea16e1 100644 --- a/src/admin/admin_remote.c +++ b/src/admin/admin_remote.c @@ -101,6 +101,28 @@ call(virAdmConnectPtr conn, #include "admin_client.h" +static void +remoteAdminClientCloseFunc(virNetClientPtr client ATTRIBUTE_UNUSED, + int reason, + void *opaque) +{ + virAdmConnectCloseCallbackDataPtr cbdata = opaque; + + virObjectLock(cbdata); + + if (cbdata->callback) { + VIR_DEBUG("Triggering connection close callback %p reason=%d, opaque=%p", + cbdata->callback, reason, cbdata->opaque); + cbdata->callback(cbdata->conn, reason, cbdata->opaque); + + if (cbdata->freeCallback) + cbdata->freeCallback(cbdata->opaque); + cbdata->callback = NULL; + cbdata->freeCallback = NULL; + } + virObjectUnlock(cbdata); +} + static int remoteAdminConnectOpen(virAdmConnectPtr conn, unsigned int flags) { @@ -112,6 +134,17 @@ remoteAdminConnectOpen(virAdmConnectPtr conn, unsigned int flags) args.flags = flags; + if (virNetClientRegisterAsyncIO(priv->client) < 0) { + VIR_DEBUG("Failed to add event watch, disabling events and support for" + " keepalive messages"); + virResetLastError(); + } + + virObjectRef(conn->closeCallback); + virNetClientSetCloseCallback(priv->client, remoteAdminClientCloseFunc, + conn->closeCallback, + virObjectFreeCallback); + if (call(conn, 0, ADMIN_PROC_CONNECT_OPEN, (xdrproc_t)xdr_admin_connect_open_args, (char *)&args, (xdrproc_t)xdr_void, (char *)NULL) == -1) { @@ -139,6 +172,8 @@ remoteAdminConnectClose(virAdmConnectPtr conn) goto done; } + virNetClientSetCloseCallback(priv->client, NULL, NULL, NULL); + rv = 0; done: diff --git a/src/datatypes.c b/src/datatypes.c index aeac3018ec..c832d80585 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -60,8 +60,10 @@ static void virStorageVolDispose(void *obj); static void virStoragePoolDispose(void *obj); virClassPtr virAdmConnectClass; +virClassPtr virAdmConnectCloseCallbackDataClass; static void virAdmConnectDispose(void *obj); +static void virAdmConnectCloseCallbackDataDispose(void *obj); static int virDataTypesOnceInit(void) @@ -91,6 +93,7 @@ virDataTypesOnceInit(void) DECLARE_CLASS(virStoragePool); DECLARE_CLASS_LOCKABLE(virAdmConnect); + DECLARE_CLASS_LOCKABLE(virAdmConnectCloseCallbackData); #undef DECLARE_CLASS_COMMON #undef DECLARE_CLASS_LOCKABLE @@ -822,7 +825,14 @@ virAdmConnectNew(void) if (!(ret = virObjectLockableNew(virAdmConnectClass))) return NULL; + if (!(ret->closeCallback = virObjectLockableNew(virAdmConnectCloseCallbackDataClass))) + goto error; + return ret; + + error: + virObjectUnref(ret); + return NULL; } static void @@ -834,4 +844,18 @@ virAdmConnectDispose(void *obj) conn->privateDataFreeFunc(conn); virURIFree(conn->uri); + virObjectUnref(conn->closeCallback); +} + +static void +virAdmConnectCloseCallbackDataDispose(void *obj) +{ + virAdmConnectCloseCallbackDataPtr cb_data = obj; + + virObjectLock(cb_data); + + if (cb_data->freeCallback) + cb_data->freeCallback(cb_data->opaque); + + virObjectUnlock(cb_data); } diff --git a/src/datatypes.h b/src/datatypes.h index b16bdb196c..1b1777daa8 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -331,9 +331,11 @@ extern virClassPtr virAdmConnectClass; typedef struct _virConnectCloseCallbackData virConnectCloseCallbackData; typedef virConnectCloseCallbackData *virConnectCloseCallbackDataPtr; +typedef struct _virAdmConnectCloseCallbackData virAdmConnectCloseCallbackData; +typedef virAdmConnectCloseCallbackData *virAdmConnectCloseCallbackDataPtr; /** - * Internal structure holding data related to connection close callbacks. + * Internal structures holding data related to connection close callbacks. */ struct _virConnectCloseCallbackData { virObjectLockable parent; @@ -344,6 +346,15 @@ struct _virConnectCloseCallbackData { virFreeCallback freeCallback; }; +struct _virAdmConnectCloseCallbackData { + virObjectLockable parent; + + virAdmConnectPtr conn; + virAdmConnectCloseFunc callback; + void *opaque; + virFreeCallback freeCallback; +}; + /** * _virConnect: * @@ -401,6 +412,9 @@ struct _virAdmConnect { void *privateData; virFreeCallback privateDataFreeFunc; + + /* Per-connection close callback */ + virAdmConnectCloseCallbackDataPtr closeCallback; }; diff --git a/src/libvirt-admin.c b/src/libvirt-admin.c index b82b745a7a..9a5ee303f9 100644 --- a/src/libvirt-admin.c +++ b/src/libvirt-admin.c @@ -411,3 +411,115 @@ virAdmConnectGetURI(virAdmConnectPtr conn) return uri; } + +/** + * virAdmConnectRegisterCloseCallback: + * @conn: connection to admin server + * @cb: callback to be invoked upon connection close + * @opaque: user data to pass to @cb + * @freecb: callback to free @opaque + * + * Registers a callback to be invoked when the connection + * is closed. This callback is invoked when there is any + * condition that causes the socket connection to the + * hypervisor to be closed. + * + * The @freecb must not invoke any other libvirt public + * APIs, since it is not called from a re-entrant safe + * context. + * + * Returns 0 on success, -1 on error + */ +int virAdmConnectRegisterCloseCallback(virAdmConnectPtr conn, + virAdmConnectCloseFunc cb, + void *opaque, + virFreeCallback freecb) +{ + VIR_DEBUG("conn=%p", conn); + + virResetLastError(); + + virCheckAdmConnectReturn(conn, -1); + + virObjectRef(conn); + + virObjectLock(conn); + virObjectLock(conn->closeCallback); + + virCheckNonNullArgGoto(cb, error); + + if (conn->closeCallback->callback) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("A close callback is already registered")); + goto error; + } + + conn->closeCallback->conn = conn; + conn->closeCallback->callback = cb; + conn->closeCallback->opaque = opaque; + conn->closeCallback->freeCallback = freecb; + + virObjectUnlock(conn->closeCallback); + virObjectUnlock(conn); + + return 0; + + error: + virObjectUnlock(conn->closeCallback); + virObjectUnlock(conn); + virDispatchError(NULL); + virObjectUnref(conn); + return -1; + +} + +/** + * virAdmConnectUnregisterCloseCallback: + * @conn: pointer to connection object + * @cb: pointer to the current registered callback + * + * Unregisters the callback previously set with the + * virAdmConnectRegisterCloseCallback method. The callback + * will no longer receive notifications when the connection + * closes. If a virFreeCallback was provided at time of + * registration, it will be invoked. + * + * Returns 0 on success, -1 on error + */ +int virAdmConnectUnregisterCloseCallback(virAdmConnectPtr conn, + virAdmConnectCloseFunc cb) +{ + VIR_DEBUG("conn=%p", conn); + + virResetLastError(); + + virCheckAdmConnectReturn(conn, -1); + + virObjectLock(conn); + virObjectLock(conn->closeCallback); + + virCheckNonNullArgGoto(cb, error); + + if (conn->closeCallback->callback != cb) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("A different callback was requested")); + goto error; + } + + conn->closeCallback->callback = NULL; + if (conn->closeCallback->freeCallback) + conn->closeCallback->freeCallback(conn->closeCallback->opaque); + conn->closeCallback->freeCallback = NULL; + + virObjectUnlock(conn->closeCallback); + virObjectUnlock(conn); + virObjectUnref(conn); + + return 0; + + error: + virObjectUnlock(conn->closeCallback); + virObjectUnlock(conn); + virDispatchError(NULL); + return -1; +} diff --git a/src/libvirt_admin_public.syms b/src/libvirt_admin_public.syms index 6f5fcea842..511f6ad27f 100644 --- a/src/libvirt_admin_public.syms +++ b/src/libvirt_admin_public.syms @@ -18,4 +18,6 @@ LIBVIRT_ADMIN_1.3.0 { virAdmGetVersion; virAdmConnectIsAlive; virAdmConnectGetURI; + virAdmConnectRegisterCloseCallback; + virAdmConnectUnregisterCloseCallback; }; diff --git a/tools/virt-admin.c b/tools/virt-admin.c index 3ba52aede3..9afb332a36 100644 --- a/tools/virt-admin.c +++ b/tools/virt-admin.c @@ -52,6 +52,53 @@ static char *progname; static const vshCmdGrp cmdGroups[]; static const vshClientHooks hooks; +/* + * vshAdmCatchDisconnect: + * + * We get here when the connection was closed. Unlike virsh, we do not save + * the fact that the event was raised, sice there is virAdmConnectIsAlive to + * check if the communication channel has not been closed by remote party. + */ +static void +vshAdmCatchDisconnect(virAdmConnectPtr conn ATTRIBUTE_UNUSED, + int reason, + void *opaque) +{ + vshControl *ctl = opaque; + const char *str = "unknown reason"; + virErrorPtr error; + char *uri = NULL; + + if (reason == VIR_CONNECT_CLOSE_REASON_CLIENT) + return; + + error = virSaveLastError(); + uri = virAdmConnectGetURI(conn); + + switch ((virConnectCloseReason) reason) { + case VIR_CONNECT_CLOSE_REASON_ERROR: + str = N_("Disconnected from %s due to I/O error"); + break; + case VIR_CONNECT_CLOSE_REASON_EOF: + str = N_("Disconnected from %s due to end of file"); + break; + case VIR_CONNECT_CLOSE_REASON_KEEPALIVE: + str = N_("Disconnected from %s due to keepalive timeout"); + break; + /* coverity[dead_error_condition] */ + case VIR_CONNECT_CLOSE_REASON_CLIENT: + case VIR_CONNECT_CLOSE_REASON_LAST: + break; + } + + vshError(ctl, _(str), NULLSTR(uri)); + + if (error) { + virSetError(error); + virFreeError(error); + } +} + static int vshAdmConnect(vshControl *ctl, unsigned int flags) { @@ -66,6 +113,10 @@ vshAdmConnect(vshControl *ctl, unsigned int flags) vshError(ctl, "%s", _("Failed to connect to the admin server")); return -1; } else { + if (virAdmConnectRegisterCloseCallback(priv->conn, vshAdmCatchDisconnect, + NULL, NULL) < 0) + vshError(ctl, "%s", _("Unable to register disconnect callback")); + if (priv->wantReconnect) vshPrint(ctl, "%s\n", _("Reconnected to the admin server")); else @@ -84,6 +135,7 @@ vshAdmDisconnect(vshControl *ctl) if (!priv->conn) return ret; + virAdmConnectUnregisterCloseCallback(priv->conn, vshAdmCatchDisconnect); ret = virAdmConnectClose(priv->conn); if (ret < 0) vshError(ctl, "%s", _("Failed to disconnect from the admin server"));