]> xenbits.xensource.com Git - people/liuw/libxenctrl-split/libvirt.git/commitdiff
Asynchronous event for BlockJob completion
authorAdam Litke <agl@us.ibm.com>
Fri, 22 Jul 2011 05:57:42 +0000 (13:57 +0800)
committerDaniel Veillard <veillard@redhat.com>
Fri, 22 Jul 2011 05:57:42 +0000 (13:57 +0800)
When an operation started by virDomainBlockPull completes (either with
success or with failure), raise an event to indicate the final status.
This API allow users to avoid polling on virDomainGetBlockJobInfo if
they would prefer to use an event mechanism.

* daemon/remote.c: Dispatch events to client
* include/libvirt/libvirt.h.in: Define event ID and callback signature
* src/conf/domain_event.c, src/conf/domain_event.h,
  src/libvirt_private.syms: Extend API to handle the new event
* src/qemu/qemu_driver.c: Connect to the QEMU monitor event
  for block_stream completion and emit a libvirt block pull event
* src/remote/remote_driver.c: Receive and dispatch events to application
* src/remote/remote_protocol.x: Wire protocol definition for the event
* src/remote_protocol-structs: structure definitions for protocol verification
* src/qemu/qemu_monitor.c, src/qemu/qemu_monitor.h,
  src/qemu/qemu_monitor_json.c: Watch for BLOCK_STREAM_COMPLETED event
  from QEMU monitor

14 files changed:
daemon/remote.c
include/libvirt/libvirt.h.in
python/libvirt-override-virConnect.py
python/libvirt-override.c
src/conf/domain_event.c
src/conf/domain_event.h
src/libvirt_private.syms
src/qemu/qemu_monitor.c
src/qemu/qemu_monitor.h
src/qemu/qemu_monitor_json.c
src/qemu/qemu_process.c
src/remote/remote_driver.c
src/remote/remote_protocol.x
src/remote_protocol-structs

index b471abc092a400350dee0ac4ad48a4ae2e8b2651..939044c781f9113a99ce9f199f44692f14c79756 100644 (file)
@@ -339,6 +339,36 @@ static int remoteRelayDomainEventGraphics(virConnectPtr conn ATTRIBUTE_UNUSED,
     return 0;
 }
 
+static int remoteRelayDomainEventBlockJob(virConnectPtr conn ATTRIBUTE_UNUSED,
+                                          virDomainPtr dom,
+                                          const char *path,
+                                          int type,
+                                          int status,
+                                          void *opaque)
+{
+    virNetServerClientPtr client = opaque;
+    remote_domain_event_block_job_msg data;
+
+    if (!client)
+        return -1;
+
+    VIR_DEBUG("Relaying domain block job event %s %d %s %i, %i",
+              dom->name, dom->id, path, type, status);
+
+    /* build return data */
+    memset(&data, 0, sizeof data);
+    make_nonnull_domain(&data.dom, dom);
+    data.path = (char*)path;
+    data.type = type;
+    data.status = status;
+
+    remoteDispatchDomainEventSend(client, remoteProgram,
+                                  REMOTE_PROC_DOMAIN_EVENT_BLOCK_JOB,
+                                  (xdrproc_t)xdr_remote_domain_event_block_job_msg, &data);
+
+    return 0;
+}
+
 
 static int remoteRelayDomainEventControlError(virConnectPtr conn ATTRIBUTE_UNUSED,
                                               virDomainPtr dom,
@@ -373,6 +403,7 @@ static virConnectDomainEventGenericCallback domainEventCallbacks[] = {
     VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventGraphics),
     VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventIOErrorReason),
     VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventControlError),
+    VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventBlockJob),
 };
 
 verify(ARRAY_CARDINALITY(domainEventCallbacks) == VIR_DOMAIN_EVENT_ID_LAST);
index 5710ca2867c677a1cc0712ec5d485be3436f24dc..5771ba7fe671354ef9895dfbde31113da066fa1b 100644 (file)
@@ -2785,6 +2785,34 @@ typedef void (*virConnectDomainEventGraphicsCallback)(virConnectPtr conn,
                                                       virDomainEventGraphicsSubjectPtr subject,
                                                       void *opaque);
 
+/**
+ * virConnectDomainEventBlockJobStatus:
+ *
+ * The final status of a virDomainBlockPullAll() operation
+ */
+typedef enum {
+    VIR_DOMAIN_BLOCK_JOB_COMPLETED = 0,
+    VIR_DOMAIN_BLOCK_JOB_FAILED = 1,
+} virConnectDomainEventBlockJobStatus;
+
+/**
+ * virConnectDomainEventBlockJobCallback:
+ * @conn: connection object
+ * @dom: domain on which the event occurred
+ * @path: fully-qualified filename of the affected disk
+ * @type: type of block job (virDomainBlockJobType)
+ * @status: final status of the operation (virConnectDomainEventBlockJobStatus)
+ *
+ * The callback signature to use when registering for an event of type
+ * VIR_DOMAIN_EVENT_ID_BLOCK_JOB with virConnectDomainEventRegisterAny()
+ */
+typedef void (*virConnectDomainEventBlockJobCallback)(virConnectPtr conn,
+                                                      virDomainPtr dom,
+                                                      const char *path,
+                                                      int type,
+                                                      int status,
+                                                      void *opaque);
+
 /**
  * VIR_DOMAIN_EVENT_CALLBACK:
  *
@@ -2803,6 +2831,7 @@ typedef enum {
     VIR_DOMAIN_EVENT_ID_GRAPHICS = 5,        /* virConnectDomainEventGraphicsCallback */
     VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON = 6, /* virConnectDomainEventIOErrorReasonCallback */
     VIR_DOMAIN_EVENT_ID_CONTROL_ERROR = 7,   /* virConnectDomainEventGenericCallback */
+    VIR_DOMAIN_EVENT_ID_BLOCK_JOB = 8,       /* virConnectDomainEventBlockJobCallback */
 
     /*
      * NB: this enum value will increase over time as new events are
index eeeedf9a167ccaea275ba17c256ed1b0280c83dd..65b53420c6e1ba7bd05992ff9423fa22d19efadf 100644 (file)
            authScheme, subject, opaque)
         return 0
 
+    def dispatchDomainEventBlockPullCallback(self, dom, path, type, status, cbData):
+        """Dispatches events to python user domain blockJob event callbacks
+        """
+        try:
+            cb = cbData["cb"]
+            opaque = cbData["opaque"]
+
+            cb(self, virDomain(self, _obj=dom), path, type, status, opaque)
+            return 0
+        except AttributeError:
+            pass
+
     def domainEventDeregisterAny(self, callbackID):
         """Removes a Domain Event Callback. De-registering for a
            domain callback will disable delivery of this event type """
index e89bc9760e0e6bddbe5bf0141ab8fa8f7f3cb0b7..db763153a3cabe92b6f0ccf7094faae0d6e5194d 100644 (file)
@@ -3582,6 +3582,55 @@ libvirt_virConnectDomainEventGraphicsCallback(virConnectPtr conn ATTRIBUTE_UNUSE
     return ret;
 }
 
+static int
+libvirt_virConnectDomainEventBlockJobCallback(virConnectPtr conn ATTRIBUTE_UNUSED,
+                                              virDomainPtr dom,
+                                              const char *path,
+                                              int type,
+                                              int status,
+                                              void *opaque)
+{
+    PyObject *pyobj_cbData = (PyObject*)opaque;
+    PyObject *pyobj_dom;
+    PyObject *pyobj_ret;
+    PyObject *pyobj_conn;
+    PyObject *dictKey;
+    int ret = -1;
+
+    LIBVIRT_ENSURE_THREAD_STATE;
+
+    /* Create a python instance of this virDomainPtr */
+    virDomainRef(dom);
+    pyobj_dom = libvirt_virDomainPtrWrap(dom);
+    Py_INCREF(pyobj_cbData);
+
+    dictKey = libvirt_constcharPtrWrap("conn");
+    pyobj_conn = PyDict_GetItem(pyobj_cbData, dictKey);
+    Py_DECREF(dictKey);
+
+    /* Call the Callback Dispatcher */
+    pyobj_ret = PyObject_CallMethod(pyobj_conn,
+                                    (char*)"dispatchDomainEventBlockPullCallback",
+                                    (char*)"OsiiO",
+                                    pyobj_dom, path, type, status, pyobj_cbData);
+
+    Py_DECREF(pyobj_cbData);
+    Py_DECREF(pyobj_dom);
+
+    if(!pyobj_ret) {
+#if DEBUG_ERROR
+        printf("%s - ret:%p\n", __FUNCTION__, pyobj_ret);
+#endif
+        PyErr_Print();
+    } else {
+        Py_DECREF(pyobj_ret);
+        ret = 0;
+    }
+
+    LIBVIRT_RELEASE_THREAD_STATE;
+    return ret;
+}
+
 static PyObject *
 libvirt_virConnectDomainEventRegisterAny(ATTRIBUTE_UNUSED PyObject * self,
                                          PyObject * args)
@@ -3636,6 +3685,9 @@ libvirt_virConnectDomainEventRegisterAny(ATTRIBUTE_UNUSED PyObject * self,
     case VIR_DOMAIN_EVENT_ID_CONTROL_ERROR:
         cb = VIR_DOMAIN_EVENT_CALLBACK(libvirt_virConnectDomainEventGenericCallback);
         break;
+    case VIR_DOMAIN_EVENT_ID_BLOCK_JOB:
+        cb = VIR_DOMAIN_EVENT_CALLBACK(libvirt_virConnectDomainEventBlockJobCallback);
+        break;
     }
 
     if (!cb) {
index d43331fb8e4e2cd5b25ad0550ea032a7798ab2f6..2e0524bd84f0ce4fc7cf00292a2d11015348fdd6 100644 (file)
@@ -83,6 +83,11 @@ struct _virDomainEvent {
             char *authScheme;
             virDomainEventGraphicsSubjectPtr subject;
         } graphics;
+        struct {
+            char *path;
+            int type;
+            int status;
+        } blockJob;
     } data;
 };
 
@@ -499,6 +504,11 @@ void virDomainEventFree(virDomainEventPtr event)
             }
             VIR_FREE(event->data.graphics.subject);
         }
+        break;
+
+    case VIR_DOMAIN_EVENT_ID_BLOCK_JOB:
+        VIR_FREE(event->data.blockJob.path);
+        break;
     }
 
     VIR_FREE(event->dom.name);
@@ -874,6 +884,44 @@ virDomainEventPtr virDomainEventGraphicsNewFromObj(virDomainObjPtr obj,
     return ev;
 }
 
+static virDomainEventPtr
+virDomainEventBlockJobNew(int id, const char *name, unsigned char *uuid,
+                          const char *path, int type, int status)
+{
+    virDomainEventPtr ev =
+        virDomainEventNewInternal(VIR_DOMAIN_EVENT_ID_BLOCK_JOB,
+                                  id, name, uuid);
+
+    if (ev) {
+        if (!(ev->data.blockJob.path = strdup(path))) {
+            virReportOOMError();
+            virDomainEventFree(ev);
+            return NULL;
+        }
+        ev->data.blockJob.type = type;
+        ev->data.blockJob.status = status;
+    }
+
+    return ev;
+}
+
+virDomainEventPtr virDomainEventBlockJobNewFromObj(virDomainObjPtr obj,
+                                                   const char *path,
+                                                   int type,
+                                                   int status)
+{
+    return virDomainEventBlockJobNew(obj->def->id, obj->def->name,
+                                     obj->def->uuid, path, type, status);
+}
+
+virDomainEventPtr virDomainEventBlockJobNewFromDom(virDomainPtr dom,
+                                                   const char *path,
+                                                   int type,
+                                                   int status)
+{
+    return virDomainEventBlockJobNew(dom->id, dom->name, dom->uuid,
+                                     path, type, status);
+}
 
 virDomainEventPtr virDomainEventControlErrorNewFromDom(virDomainPtr dom)
 {
@@ -1027,6 +1075,14 @@ void virDomainEventDispatchDefaultFunc(virConnectPtr conn,
              cbopaque);
         break;
 
+    case VIR_DOMAIN_EVENT_ID_BLOCK_JOB:
+        ((virConnectDomainEventBlockJobCallback)cb)(conn, dom,
+                                                    event->data.blockJob.path,
+                                                    event->data.blockJob.type,
+                                                    event->data.blockJob.status,
+                                                    cbopaque);
+        break;
+
     default:
         VIR_WARN("Unexpected event ID %d", event->eventID);
         break;
index f56408f615a7eb456a927628ba12306faf725e0f..b06be1617b214473cd8a4c33d2c16b009a217840 100644 (file)
@@ -169,7 +169,14 @@ virDomainEventPtr virDomainEventGraphicsNewFromObj(virDomainObjPtr obj,
 virDomainEventPtr virDomainEventControlErrorNewFromDom(virDomainPtr dom);
 virDomainEventPtr virDomainEventControlErrorNewFromObj(virDomainObjPtr obj);
 
-
+virDomainEventPtr virDomainEventBlockJobNewFromObj(virDomainObjPtr obj,
+                                                    const char *path,
+                                                    int type,
+                                                    int status);
+virDomainEventPtr virDomainEventBlockJobNewFromDom(virDomainPtr dom,
+                                                    const char *path,
+                                                    int type,
+                                                    int status);
 
 int virDomainEventQueuePush(virDomainEventQueuePtr evtQueue,
                             virDomainEventPtr event);
index e7d25799df6e13ff25bf001d67397f676bba35d6..acf7bb1edc9242054c21e01618d197ef96561533 100644 (file)
@@ -414,6 +414,8 @@ virDomainWatchdogModelTypeToString;
 
 
 # domain_event.h
+virDomainEventBlockJobNewFromObj;
+virDomainEventBlockJobNewFromDom;
 virDomainEventCallbackListAdd;
 virDomainEventCallbackListAddID;
 virDomainEventCallbackListCount;
index 5fe6d0598e260b7b0618e3f80fb1284819f22baa..db6107c247e2e8e6b6ce74a05885470a3000639c 100644 (file)
@@ -957,6 +957,19 @@ int qemuMonitorEmitGraphics(qemuMonitorPtr mon,
     return ret;
 }
 
+int qemuMonitorEmitBlockJob(qemuMonitorPtr mon,
+                            const char *diskAlias,
+                            int type,
+                            int status)
+{
+    int ret = -1;
+    VIR_DEBUG("mon=%p", mon);
+
+    QEMU_MONITOR_CALLBACK(mon, ret, domainBlockJob, mon->vm,
+                          diskAlias, type, status);
+    return ret;
+}
+
 
 
 int qemuMonitorSetCapabilities(qemuMonitorPtr mon)
index a93905eb0dc7a194881274dac4e7a14dfc492dab..f241c9e4d3466165ef2d99a2c2b86c4e240f1449 100644 (file)
@@ -117,6 +117,11 @@ struct _qemuMonitorCallbacks {
                           const char *authScheme,
                           const char *x509dname,
                           const char *saslUsername);
+    int (*domainBlockJob)(qemuMonitorPtr mon,
+                          virDomainObjPtr vm,
+                          const char *diskAlias,
+                          int type,
+                          int status);
 };
 
 
@@ -179,6 +184,11 @@ int qemuMonitorEmitGraphics(qemuMonitorPtr mon,
                             const char *authScheme,
                             const char *x509dname,
                             const char *saslUsername);
+int qemuMonitorEmitBlockJob(qemuMonitorPtr mon,
+                            const char *diskAlias,
+                            int type,
+                            int status);
+
 
 
 int qemuMonitorStartCPUs(qemuMonitorPtr mon,
index b19f993fe619ba82f6d2730f1fe577cb8f3206ea..b7a6a129ea93acebc0c0f33d21acaec2d3061be5 100644 (file)
@@ -56,6 +56,7 @@ static void qemuMonitorJSONHandleIOError(qemuMonitorPtr mon, virJSONValuePtr dat
 static void qemuMonitorJSONHandleVNCConnect(qemuMonitorPtr mon, virJSONValuePtr data);
 static void qemuMonitorJSONHandleVNCInitialize(qemuMonitorPtr mon, virJSONValuePtr data);
 static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValuePtr data);
+static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr data);
 
 struct {
     const char *type;
@@ -71,6 +72,7 @@ struct {
     { "VNC_CONNECTED", qemuMonitorJSONHandleVNCConnect, },
     { "VNC_INITIALIZED", qemuMonitorJSONHandleVNCInitialize, },
     { "VNC_DISCONNECTED", qemuMonitorJSONHandleVNCDisconnect, },
+    { "BLOCK_JOB_COMPLETED", qemuMonitorJSONHandleBlockJob, },
 };
 
 
@@ -678,6 +680,44 @@ static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValueP
     qemuMonitorJSONHandleVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
 }
 
+static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr data)
+{
+    const char *device;
+    const char *type_str;
+    int type = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
+    unsigned long long offset, len;
+    int status = VIR_DOMAIN_BLOCK_JOB_FAILED;
+
+    if ((device = virJSONValueObjectGetString(data, "device")) == NULL) {
+        VIR_WARN("missing device in block job event");
+        goto out;
+    }
+
+    if (virJSONValueObjectGetNumberUlong(data, "offset", &offset) < 0) {
+        VIR_WARN("missing offset in block job event");
+        goto out;
+    }
+
+    if (virJSONValueObjectGetNumberUlong(data, "len", &len) < 0) {
+        VIR_WARN("missing len in block job event");
+        goto out;
+    }
+
+    if ((type_str = virJSONValueObjectGetString(data, "type")) == NULL) {
+        VIR_WARN("missing type in block job event");
+        goto out;
+    }
+
+    if (STREQ(type_str, "stream"))
+        type = VIR_DOMAIN_BLOCK_JOB_TYPE_PULL;
+
+    if (offset != 0 && offset == len)
+        status = VIR_DOMAIN_BLOCK_JOB_COMPLETED;
+
+out:
+    qemuMonitorEmitBlockJob(mon, device, type, status);
+}
+
 
 int
 qemuMonitorJSONHumanCommandWithFd(qemuMonitorPtr mon,
index a0ac4fa3f6fc85dc68f5d6fa4b15891e57b18f95..646215e0ac44e670eed61f8ee9ed8cc66c437e68 100644 (file)
@@ -662,6 +662,36 @@ qemuProcessHandleIOError(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
     return 0;
 }
 
+static int
+qemuProcessHandleBlockJob(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
+                          virDomainObjPtr vm,
+                          const char *diskAlias,
+                          int type,
+                          int status)
+{
+    struct qemud_driver *driver = qemu_driver;
+    virDomainEventPtr event = NULL;
+    const char *path;
+    virDomainDiskDefPtr disk;
+
+    virDomainObjLock(vm);
+    disk = qemuProcessFindDomainDiskByAlias(vm, diskAlias);
+
+    if (disk) {
+        path = disk->src;
+        event = virDomainEventBlockJobNewFromObj(vm, path, type, status);
+    }
+
+    virDomainObjUnlock(vm);
+
+    if (event) {
+        qemuDriverLock(driver);
+        qemuDomainEventQueue(driver, event);
+        qemuDriverUnlock(driver);
+    }
+
+    return 0;
+}
 
 static int
 qemuProcessHandleGraphics(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
@@ -779,6 +809,7 @@ static qemuMonitorCallbacks monitorCallbacks = {
     .domainWatchdog = qemuProcessHandleWatchdog,
     .domainIOError = qemuProcessHandleIOError,
     .domainGraphics = qemuProcessHandleGraphics,
+    .domainBlockJob = qemuProcessHandleBlockJob,
 };
 
 static int
index d96b75eb1235cb2b05d37bec6405ffffee4d9267..ec4133b2bcf7610aa895fd510f0b65a10179ac06 100644 (file)
@@ -223,6 +223,11 @@ remoteDomainBuildEventControlError(virNetClientProgramPtr prog,
                                    virNetClientPtr client,
                                    void *evdata, void *opaque);
 
+static void
+remoteDomainBuildEventBlockJob(virNetClientProgramPtr prog,
+                               virNetClientPtr client,
+                               void *evdata, void *opaque);
+
 static virNetClientProgramEvent remoteDomainEvents[] = {
     { REMOTE_PROC_DOMAIN_EVENT_RTC_CHANGE,
       remoteDomainBuildEventRTCChange,
@@ -256,6 +261,10 @@ static virNetClientProgramEvent remoteDomainEvents[] = {
       remoteDomainBuildEventControlError,
       sizeof(remote_domain_event_control_error_msg),
       (xdrproc_t)xdr_remote_domain_event_control_error_msg },
+    { REMOTE_PROC_DOMAIN_EVENT_BLOCK_JOB,
+      remoteDomainBuildEventBlockJob,
+      sizeof(remote_domain_event_block_job_msg),
+      (xdrproc_t)xdr_remote_domain_event_block_job_msg },
 };
 
 enum virDrvOpenRemoteFlags {
@@ -3095,6 +3104,28 @@ remoteDomainBuildEventIOErrorReason(virNetClientProgramPtr prog ATTRIBUTE_UNUSED
     remoteDomainEventQueue(priv, event);
 }
 
+static void
+remoteDomainBuildEventBlockJob(virNetClientProgramPtr prog ATTRIBUTE_UNUSED,
+                               virNetClientPtr client ATTRIBUTE_UNUSED,
+                               void *evdata, void *opaque)
+{
+    virConnectPtr conn = opaque;
+    struct private_data *priv = conn->privateData;
+    remote_domain_event_block_job_msg *msg = evdata;
+    virDomainPtr dom;
+    virDomainEventPtr event = NULL;
+
+    dom = get_nonnull_domain(conn, msg->dom);
+    if (!dom)
+        return;
+
+    event = virDomainEventBlockJobNewFromDom(dom, msg->path, msg->type,
+                                             msg->status);
+
+    virDomainFree(dom);
+
+    remoteDomainEventQueue(priv, event);
+}
 
 static void
 remoteDomainBuildEventGraphics(virNetClientProgramPtr prog ATTRIBUTE_UNUSED,
index ed1fc57be6deefb8e398a98e9d51c325be5cd334..8f6880873734b83628abb099da9a37c7ffe011a0 100644 (file)
@@ -1972,6 +1972,13 @@ struct remote_domain_event_graphics_msg {
     remote_domain_event_graphics_identity subject<REMOTE_DOMAIN_EVENT_GRAPHICS_IDENTITY_MAX>;
 };
 
+struct remote_domain_event_block_job_msg {
+    remote_nonnull_domain dom;
+    remote_nonnull_string path;
+    int type;
+    int status;
+};
+
 struct remote_domain_managed_save_args {
     remote_nonnull_domain dom;
     unsigned int flags;
@@ -2466,7 +2473,9 @@ enum remote_procedure {
     REMOTE_PROC_DOMAIN_BLOCK_JOB_ABORT = 237, /* autogen autogen */
     REMOTE_PROC_DOMAIN_GET_BLOCK_JOB_INFO = 238, /* skipgen skipgen */
     REMOTE_PROC_DOMAIN_BLOCK_JOB_SET_SPEED = 239, /* autogen autogen */
-    REMOTE_PROC_DOMAIN_BLOCK_PULL = 240 /* autogen autogen */
+    REMOTE_PROC_DOMAIN_BLOCK_PULL = 240, /* autogen autogen */
+
+    REMOTE_PROC_DOMAIN_EVENT_BLOCK_JOB = 241 /* skipgen skipgen */
 
     /*
      * Notice how the entries are grouped in sets of 10 ?
index c10fae4e06b179d12bdc665cb24f4eb6ad3955a4..91b3ca53b23e29daadc8547e93df3379a0853009 100644 (file)
@@ -1479,6 +1479,12 @@ struct remote_domain_event_graphics_msg {
                 remote_domain_event_graphics_identity * subject_val;
         } subject;
 };
+struct remote_domain_event_block_job_msg {
+        remote_nonnull_domain      dom;
+        remote_nonnull_string      path;
+        int                        type;
+        int                        status;
+};
 struct remote_domain_managed_save_args {
         remote_nonnull_domain      dom;
         u_int                      flags;
@@ -1929,4 +1935,5 @@ enum remote_procedure {
         REMOTE_PROC_DOMAIN_GET_BLOCK_JOB_INFO = 238,
         REMOTE_PROC_DOMAIN_BLOCK_JOB_SET_SPEED = 239,
         REMOTE_PROC_DOMAIN_BLOCK_PULL = 240,
+        REMOTE_PROC_DOMAIN_EVENT_BLOCK_JOB = 241,
 };