[libvirt] [PATCH v3 0/8] Add support for taking screenshots of domain console

This series adds support for taking screenshots of a running domain console. The iohelper was added a new argument - delete file after transfer. This is needed, because the screenshot is written to file and asynchronously transferred to client. New API is accessible via virsh screenshot <domain> <path>; For now, we just save the file in format as returned by hypervisor: PPM for Qemu, PNG for VirtualBox. diff to v2: - rebase Michal Privoznik (8): screenshot: Defining the public API screenshot: Defining the internal API screenshot: Implementing the public API screenshot: Implementing the remote protocol screenshot: Expose the new API in virsh virFDStream: Add option for delete file after it's opening qemu: Implement the driver methods vbox: Implement the driver methods daemon/remote.c | 57 +++++++++++++++++++++ daemon/remote_generator.pl | 2 + include/libvirt/libvirt.h.in | 7 +++ src/driver.h | 5 ++ src/esx/esx_driver.c | 1 + src/fdstream.c | 29 ++++++++--- src/fdstream.h | 6 ++- src/libvirt.c | 56 ++++++++++++++++++++ src/libvirt_public.syms | 5 ++ src/libxl/libxl_driver.c | 1 + src/lxc/lxc_driver.c | 4 +- src/openvz/openvz_driver.c | 1 + src/phyp/phyp_driver.c | 1 + src/qemu/qemu_driver.c | 82 ++++++++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 20 +++++++ src/qemu/qemu_monitor.h | 3 + src/qemu/qemu_monitor_json.c | 23 ++++++++ src/qemu/qemu_monitor_json.h | 4 ++ src/qemu/qemu_monitor_text.c | 31 +++++++++++ src/qemu/qemu_monitor_text.h | 2 + src/remote/remote_driver.c | 39 ++++++++++++++ src/remote/remote_protocol.x | 12 ++++- src/remote_protocol-structs | 7 +++ src/storage/storage_driver.c | 4 +- src/test/test_driver.c | 1 + src/uml/uml_driver.c | 4 +- src/util/iohelper.c | 12 ++++- src/vbox/vbox_tmpl.c | 114 ++++++++++++++++++++++++++++++++++++++++++ src/vmware/vmware_driver.c | 1 + src/xen/xen_driver.c | 4 +- src/xen/xen_driver.h | 1 + src/xen/xen_hypervisor.c | 1 + src/xen/xen_inotify.c | 1 + src/xen/xend_internal.c | 1 + src/xen/xm_internal.c | 1 + src/xen/xs_internal.c | 1 + src/xenapi/xenapi_driver.c | 1 + tools/virsh.c | 90 +++++++++++++++++++++++++++++++++ tools/virsh.pod | 4 ++ 39 files changed, 621 insertions(+), 18 deletions(-) -- 1.7.5.rc3

Add public API for taking screenshots of current domain console. * include/libvirt/libvirt.h.in: add virDomainScreenshot * src/libvirt_public.syms: Export new symbol --- include/libvirt/libvirt.h.in | 7 +++++++ src/libvirt_public.syms | 5 +++++ 2 files changed, 12 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 5783303..0113629 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -670,6 +670,13 @@ int virDomainCoreDump (virDomainPtr domain, int flags); /* + * Screenshot of current domain console + */ +char * virDomainScreenshot (virDomainPtr domain, + virStreamPtr stream, + unsigned int flags); + +/* * Domain runtime information */ int virDomainGetInfo (virDomainPtr domain, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index b4aed41..5c9e6b4 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -436,4 +436,9 @@ LIBVIRT_0.9.0 { virStorageVolUpload; } LIBVIRT_0.8.8; +LIBVIRT_0.9.1 { + global: + virDomainScreenshot; +} LIBVIRT_0.9.0; + # .... define new API here using predicted next version number .... -- 1.7.5.rc3

2011/5/10 Michal Privoznik <mprivozn@redhat.com>:
Add public API for taking screenshots of current domain console.
* include/libvirt/libvirt.h.in: add virDomainScreenshot * src/libvirt_public.syms: Export new symbol --- include/libvirt/libvirt.h.in | 7 +++++++ src/libvirt_public.syms | 5 +++++ 2 files changed, 12 insertions(+), 0 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 5783303..0113629 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -670,6 +670,13 @@ int virDomainCoreDump (virDomainPtr domain, int flags);
/* + * Screenshot of current domain console + */ +char * virDomainScreenshot (virDomainPtr domain, + virStreamPtr stream, + unsigned int flags); + +/*
How do intent to handle multi-head domains here? The domain XML video element has a heads argument and for example VirtualBox supports it. Do we only support taking screenshots of the first monitor or do we take an aggregated screenshot overall monitors?
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index b4aed41..5c9e6b4 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -436,4 +436,9 @@ LIBVIRT_0.9.0 { virStorageVolUpload; } LIBVIRT_0.8.8;
+LIBVIRT_0.9.1 { + global: + virDomainScreenshot; +} LIBVIRT_0.9.0; +
0.9.1 is already released so this should be 0.9.2 here. Matthias

* src/driver.h: Stub code for new API * src/esx/esx_driver.c, src/libxl/libxl_driver.c, src/lxc/lxc_driver.c, src/openvz/openvz_driver.c, src/phyp/phyp_driver.c, src/qemu/qemu_driver.c, rc/remote/remote_driver.c, rc/test/test_driver.c, src/uml/uml_driver.c, src/vbox/vbox_tmpl.c, src/vmware/vmware_driver.c, src/xen/xen_driver.c, src/xen/xen_driver.h, src/xen/xen_hypervisor.c, src/xen/xen_inotify.c, src/xen/xend_internal.c, src/xen/xm_internal.c, src/xen/xs_internal.c, src/xenapi/xenapi_driver.c: Add dummy entries in driver table for new APIs --- src/driver.h | 5 +++++ src/esx/esx_driver.c | 1 + src/libxl/libxl_driver.c | 1 + src/lxc/lxc_driver.c | 1 + src/openvz/openvz_driver.c | 1 + src/phyp/phyp_driver.c | 1 + src/qemu/qemu_driver.c | 1 + src/remote/remote_driver.c | 1 + src/test/test_driver.c | 1 + src/uml/uml_driver.c | 1 + src/vbox/vbox_tmpl.c | 1 + src/vmware/vmware_driver.c | 1 + src/xen/xen_driver.c | 1 + src/xen/xen_driver.h | 1 + src/xen/xen_hypervisor.c | 1 + src/xen/xen_inotify.c | 1 + src/xen/xend_internal.c | 1 + src/xen/xm_internal.c | 1 + src/xen/xs_internal.c | 1 + src/xenapi/xenapi_driver.c | 1 + 20 files changed, 24 insertions(+), 0 deletions(-) diff --git a/src/driver.h b/src/driver.h index a8b79e6..b89cfdb 100644 --- a/src/driver.h +++ b/src/driver.h @@ -176,6 +176,10 @@ typedef int const char *to, int flags); typedef char * + (*virDrvDomainScreenshot) (virDomainPtr domain, + virStreamPtr stream, + unsigned int flags); +typedef char * (*virDrvDomainDumpXML) (virDomainPtr dom, int flags); typedef char * @@ -566,6 +570,7 @@ struct _virDriver { virDrvDomainSave domainSave; virDrvDomainRestore domainRestore; virDrvDomainCoreDump domainCoreDump; + virDrvDomainScreenshot domainScreenshot; virDrvDomainSetVcpus domainSetVcpus; virDrvDomainSetVcpusFlags domainSetVcpusFlags; virDrvDomainGetVcpusFlags domainGetVcpusFlags; diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index 7933f11..4939dc0 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4626,6 +4626,7 @@ static virDriver esxDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ esxDomainSetVcpus, /* domainSetVcpus */ esxDomainSetVcpusFlags, /* domainSetVcpusFlags */ esxDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index a2c8467..5a0a224 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -2717,6 +2717,7 @@ static virDriver libxlDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ libxlDomainSetVcpus, /* domainSetVcpus */ libxlDomainSetVcpusFlags, /* domainSetVcpusFlags */ libxlDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index b94941d..e88f834 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2832,6 +2832,7 @@ static virDriver lxcDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainSetVcpus */ NULL, /* domainSetVcpusFlags */ NULL, /* domainGetVcpusFlags */ diff --git a/src/openvz/openvz_driver.c b/src/openvz/openvz_driver.c index 0bd007a..2c64766 100644 --- a/src/openvz/openvz_driver.c +++ b/src/openvz/openvz_driver.c @@ -1594,6 +1594,7 @@ static virDriver openvzDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ openvzDomainSetVcpus, /* domainSetVcpus */ openvzDomainSetVcpusFlags, /* domainSetVcpusFlags */ openvzDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/phyp/phyp_driver.c b/src/phyp/phyp_driver.c index 30d4adf..1d71c0f 100644 --- a/src/phyp/phyp_driver.c +++ b/src/phyp/phyp_driver.c @@ -3755,6 +3755,7 @@ static virDriver phypDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ phypDomainSetCPU, /* domainSetVcpus */ phypDomainSetVcpusFlags, /* domainSetVcpusFlags */ phypDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 0fd0f10..a0cd7b1 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7119,6 +7119,7 @@ static virDriver qemuDriver = { qemudDomainSave, /* domainSave */ qemuDomainRestore, /* domainRestore */ qemudDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ qemudDomainSetVcpus, /* domainSetVcpus */ qemudDomainSetVcpusFlags, /* domainSetVcpusFlags */ qemudDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index d076a90..67e91c3 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -6420,6 +6420,7 @@ static virDriver remote_driver = { remoteDomainSave, /* domainSave */ remoteDomainRestore, /* domainRestore */ remoteDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ remoteDomainSetVcpus, /* domainSetVcpus */ remoteDomainSetVcpusFlags, /* domainSetVcpusFlags */ remoteDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/test/test_driver.c b/src/test/test_driver.c index 0978214..a10151b 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -5374,6 +5374,7 @@ static virDriver testDriver = { testDomainSave, /* domainSave */ testDomainRestore, /* domainRestore */ testDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ testSetVcpus, /* domainSetVcpus */ testDomainSetVcpusFlags, /* domainSetVcpusFlags */ testDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 33849a0..00ab62d 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -2180,6 +2180,7 @@ static virDriver umlDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainSetVcpus */ NULL, /* domainSetVcpusFlags */ NULL, /* domainGetVcpusFlags */ diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 8241d34..9a110f9 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -8569,6 +8569,7 @@ virDriver NAME(Driver) = { vboxDomainSave, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ vboxDomainSetVcpus, /* domainSetVcpus */ vboxDomainSetVcpusFlags, /* domainSetVcpusFlags */ vboxDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/vmware/vmware_driver.c b/src/vmware/vmware_driver.c index bbfb1a4..d0d9804 100644 --- a/src/vmware/vmware_driver.c +++ b/src/vmware/vmware_driver.c @@ -934,6 +934,7 @@ static virDriver vmwareDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainSetVcpus */ NULL, /* domainSetVcpusFlags */ NULL, /* domainGetVcpusFlags */ diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c index dd94fbc..5c5d789 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -2135,6 +2135,7 @@ static virDriver xenUnifiedDriver = { xenUnifiedDomainSave, /* domainSave */ xenUnifiedDomainRestore, /* domainRestore */ xenUnifiedDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenUnifiedDomainSetVcpus, /* domainSetVcpus */ xenUnifiedDomainSetVcpusFlags, /* domainSetVcpusFlags */ xenUnifiedDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/xen/xen_driver.h b/src/xen/xen_driver.h index 58b8561..8fb5832 100644 --- a/src/xen/xen_driver.h +++ b/src/xen/xen_driver.h @@ -93,6 +93,7 @@ struct xenUnifiedDriver { virDrvDomainSave domainSave; virDrvDomainRestore domainRestore; virDrvDomainCoreDump domainCoreDump; + virDrvDomainScreenshot domainScreenshot; virDrvDomainPinVcpu domainPinVcpu; virDrvDomainGetVcpus domainGetVcpus; virDrvListDefinedDomains listDefinedDomains; diff --git a/src/xen/xen_hypervisor.c b/src/xen/xen_hypervisor.c index 9a5b41d..25d2dc7 100644 --- a/src/xen/xen_hypervisor.c +++ b/src/xen/xen_hypervisor.c @@ -824,6 +824,7 @@ struct xenUnifiedDriver xenHypervisorDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenHypervisorPinVcpu, /* domainPinVcpu */ xenHypervisorGetVcpus, /* domainGetVcpus */ NULL, /* listDefinedDomains */ diff --git a/src/xen/xen_inotify.c b/src/xen/xen_inotify.c index 9dde72c..beae24c 100644 --- a/src/xen/xen_inotify.c +++ b/src/xen/xen_inotify.c @@ -72,6 +72,7 @@ struct xenUnifiedDriver xenInotifyDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainPinVcpu */ NULL, /* domainGetVcpus */ NULL, /* listDefinedDomains */ diff --git a/src/xen/xend_internal.c b/src/xen/xend_internal.c index a4420d8..be33b54 100644 --- a/src/xen/xend_internal.c +++ b/src/xen/xend_internal.c @@ -3864,6 +3864,7 @@ struct xenUnifiedDriver xenDaemonDriver = { xenDaemonDomainSave, /* domainSave */ xenDaemonDomainRestore, /* domainRestore */ xenDaemonDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenDaemonDomainPinVcpu, /* domainPinVcpu */ xenDaemonDomainGetVcpus, /* domainGetVcpus */ xenDaemonListDefinedDomains, /* listDefinedDomains */ diff --git a/src/xen/xm_internal.c b/src/xen/xm_internal.c index 07a0c0f..8fa76b0 100644 --- a/src/xen/xm_internal.c +++ b/src/xen/xm_internal.c @@ -103,6 +103,7 @@ struct xenUnifiedDriver xenXMDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenXMDomainPinVcpu, /* domainPinVcpu */ NULL, /* domainGetVcpus */ xenXMListDefinedDomains, /* listDefinedDomains */ diff --git a/src/xen/xs_internal.c b/src/xen/xs_internal.c index c318f6c..94a4e3f 100644 --- a/src/xen/xs_internal.c +++ b/src/xen/xs_internal.c @@ -65,6 +65,7 @@ struct xenUnifiedDriver xenStoreDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainPinVcpu */ NULL, /* domainGetVcpus */ NULL, /* listDefinedDomains */ diff --git a/src/xenapi/xenapi_driver.c b/src/xenapi/xenapi_driver.c index 3fbdcc6..7d778cc 100644 --- a/src/xenapi/xenapi_driver.c +++ b/src/xenapi/xenapi_driver.c @@ -1816,6 +1816,7 @@ static virDriver xenapiDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenapiDomainSetVcpus, /* domainSetVcpus */ xenapiDomainSetVcpusFlags, /* domainSetVcpusFlags */ xenapiDomainGetVcpusFlags, /* domainGetVcpusFlags */ -- 1.7.5.rc3

* src/libvirt.c: new function --- src/libvirt.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 56 insertions(+), 0 deletions(-) diff --git a/src/libvirt.c b/src/libvirt.c index abacf85..2320ddb 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -2412,6 +2412,62 @@ error: } /** + * virDomainScreenshot: + * @domain: a domain object + * @stream: stream to use as output + * @flags: extra flags, currently unused + * + * Take a screenshot of current domain console as a stream. The image format + * is hypervisor specific. + * + * This call sets up a stream; subsequent use of stream API is necessary + * to transfer actual data, determine how much data is successfully + * transfered, and detect any errors. + * + * Returns a string representing the mime-type of the image format, or + * NULL upon error. The caller must free() the returned value. + */ +char * +virDomainScreenshot(virDomainPtr domain, + virStreamPtr stream, + unsigned int flags) +{ + VIR_DOMAIN_DEBUG(domain, "stream=%p flags=%u", stream, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return NULL; + } + if (!VIR_IS_STREAM(stream)) { + virLibConnError(VIR_ERR_INVALID_STREAM, __FUNCTION__); + return NULL; + } + if (domain->conn->flags & VIR_CONNECT_RO || + stream->conn->flags & VIR_CONNECT_RO) { + virLibConnError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + if (domain->conn->driver->domainScreenshot) { + char * ret; + ret = domain->conn->driver->domainScreenshot(domain, stream, flags); + + if (ret == NULL) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(domain->conn); + return NULL; +} + +/** * virDomainShutdown: * @domain: a domain object * -- 1.7.5.rc3

* src/remote/remote_protocol.x: Wire protocol definition * daemon/remote.c: New function * daemon/remote_generator.pl: Don't generate remote function body * src/remote/remote_driver.c: Client side * src/remote_protocol-structs: Add structures --- daemon/remote.c | 57 ++++++++++++++++++++++++++++++++++++++++++ daemon/remote_generator.pl | 2 + src/remote/remote_driver.c | 40 ++++++++++++++++++++++++++++- src/remote/remote_protocol.x | 12 ++++++++- src/remote_protocol-structs | 7 +++++ 5 files changed, 116 insertions(+), 2 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 2220655..81b400a 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1692,6 +1692,63 @@ no_memory: goto cleanup; } +static int +remoteDispatchDomainScreenshot (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *rerr, + remote_domain_screenshot_args *args, + remote_domain_screenshot_ret *ret) +{ + int rv = -1; + struct qemud_client_stream *stream = NULL; + virDomainPtr dom; + char *mime, **mime_p; + + ret->mime = NULL; + + dom = get_nonnull_domain (conn, args->dom); + if (dom == NULL) + goto err; + + stream = remoteCreateClientStream(conn, hdr); + if (!stream) + goto err; + + mime = virDomainScreenshot(dom, stream->st, args->flags); + if (!mime) + goto err; + + if (remoteAddClientStream(client, stream, 1) < 0) { + virStreamAbort(stream->st); + goto err; + } + + if (VIR_ALLOC(mime_p) < 0) { + remoteDispatchOOMError(rerr); + goto cleanup; + } + + *mime_p = strdup(mime); + if (*mime_p == NULL) { + remoteDispatchOOMError(rerr); + goto cleanup; + } + + ret->mime = mime_p; + rv = 0; + +err: + if (rv < 0) + remoteDispatchError(rerr); +cleanup: + virDomainFree(dom); + if (stream && rv != 0) + remoteFreeClientStream(client, stream); + return rv; +} + /*-------------------------------------------------------------*/ static int diff --git a/daemon/remote_generator.pl b/daemon/remote_generator.pl index 062ccc1..1837c2f 100755 --- a/daemon/remote_generator.pl +++ b/daemon/remote_generator.pl @@ -248,6 +248,7 @@ elsif ($opt_b) { "DomainMigratePrepareTunnel", "DomainOpenConsole", "DomainPinVcpu", + "DomainScreenshot", "DomainSetSchedulerParameters", "DomainSetMemoryParameters", "DomainSetBlkioParameters", @@ -737,6 +738,7 @@ elsif ($opt_k) { "DomainEventsRegisterAny", "DomainMigratePrepareTunnel", "DomainOpenConsole", + "DomainScreenshot", "DomainSetSchedulerParameters", "DomainSetMemoryParameters", "DomainSetBlkioParameters", diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 67e91c3..26d9b02 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -4798,6 +4798,44 @@ done: return rv; } +static char * +remoteDomainScreenshot (virDomainPtr domain, + virStreamPtr st, + unsigned int flags) +{ + struct private_data *priv = domain->conn->privateData; + struct private_stream_data *privst = NULL; + remote_domain_screenshot_args args; + remote_domain_screenshot_ret ret; + char *rv = NULL; + + remoteDriverLock(priv); + + if (!(privst = remoteStreamOpen(st, + REMOTE_PROC_DOMAIN_SCREENSHOT, + priv->counter))) + goto done; + + st->driver = &remoteStreamDrv; + st->privateData = privst; + + make_nonnull_domain(&args.dom, domain); + args.flags = flags; + + memset(&ret, 0, sizeof(ret)); + if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SCREENSHOT, + (xdrproc_t) xdr_remote_domain_screenshot_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_screenshot_ret, (char *) &ret) == -1) + goto done; + + rv = ret.mime ? *ret.mime : NULL; + VIR_FREE(ret.mime); + +done: + remoteDriverUnlock(priv); + return rv; +} + static int remoteStorageVolUpload(virStorageVolPtr vol, virStreamPtr st, @@ -6420,7 +6458,7 @@ static virDriver remote_driver = { remoteDomainSave, /* domainSave */ remoteDomainRestore, /* domainRestore */ remoteDomainCoreDump, /* domainCoreDump */ - NULL, /* domainScreenshot */ + remoteDomainScreenshot, /* domainScreenshot */ remoteDomainSetVcpus, /* domainSetVcpus */ remoteDomainSetVcpusFlags, /* domainSetVcpusFlags */ remoteDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index c706c36..776dfe8 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -714,6 +714,15 @@ struct remote_domain_core_dump_args { int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + unsigned int flags; +}; + +struct remote_domain_screenshot_ret { + remote_string mime; +}; + struct remote_domain_dump_xml_args { remote_nonnull_domain dom; int flags; @@ -2176,7 +2185,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_GET_BLKIO_PARAMETERS = 206, REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207, REMOTE_PROC_STORAGE_VOL_UPLOAD = 208, - REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209 + REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209, + REMOTE_PROC_DOMAIN_SCREENSHOT = 210 /* * Notice how the entries are grouped in sets of 10 ? diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index f904c4d..10c6d4a 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -403,6 +403,13 @@ struct remote_domain_core_dump_args { remote_nonnull_string to; int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + unsigned int flags; +}; +struct remote_domain_screenshot_ret { + remote_string mime; +}; struct remote_domain_dump_xml_args { remote_nonnull_domain dom; int flags; -- 1.7.5.rc3

* tools/virsh.c: Add screenshot command * tools/virsh.pod: Document new command --- tools/virsh.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 4 ++ 2 files changed, 94 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 2b16714..a03c712 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1873,6 +1873,95 @@ cmdDump(vshControl *ctl, const vshCmd *cmd) return ret; } +static const vshCmdInfo info_screenshot[] = { + {"help", N_("take a screenshot of a current domain console and store it " + "into a file")}, + {"desc", N_("screenshot of a current domain console")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_screenshot[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("where to store the screenshot")}, + {NULL, 0, 0, NULL} +}; + +static int cmdScreenshotSink(virStreamPtr st ATTRIBUTE_UNUSED, + const char *bytes, size_t nbytes, void *opaque) +{ + int *fd = opaque; + + return safewrite(*fd, bytes, nbytes); +} + +static int +cmdScreenshot(vshControl *ctl, const vshCmd *cmd) { + virDomainPtr dom; + const char *name = NULL; + const char *file = NULL; + int fd = -1; + virStreamPtr st = NULL; + unsigned int flags = 0; /* currently unused */ + int ret = false; + bool created = true; + char *mime = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (vshCommandOptString(cmd, "file", &file) < 0) { + vshError(ctl, "%s", _("file must not be empty")); + return false; + } + + if (!(dom = vshCommandOptDomain(ctl, cmd, &name))) + return false; + + if ((fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) { + created = false; + if (errno != EEXIST || + (fd = open(file, O_WRONLY|O_TRUNC, 0666)) < 0) { + vshError(ctl, _("cannot create file %s"), file); + goto cleanup; + } + } + + st = virStreamNew(ctl->conn, 0); + + mime = virDomainScreenshot(dom, st, flags); + if (mime == NULL) { + vshError(ctl, _("could not take a screenshot of %s"), name); + goto cleanup; + } + + if (virStreamRecvAll(st, cmdScreenshotSink, &fd) < 0) { + vshError(ctl, _("could not receive data from domain %s"), name); + goto cleanup; + } + + if (VIR_CLOSE(fd) < 0) { + vshError(ctl, _("cannot close file %s"), file); + goto cleanup; + } + + if (virStreamFinish(st) < 0) { + vshError(ctl, _("cannot close stream on domain %s"), name); + goto cleanup; + } + + vshPrint(ctl, _("Screenshot saved to %s it's type is %s"), file, mime); + ret = true; + +cleanup: + if (ret == false && created) + unlink(file); + virDomainFree(dom); + if (st) + virStreamFree(st); + VIR_FORCE_CLOSE(fd); + return ret; +} + /* * "resume" command */ @@ -10717,6 +10806,7 @@ static const vshCmdDef domManagementCmds[] = { {"resume", cmdResume, opts_resume, info_resume}, {"save", cmdSave, opts_save, info_save}, {"schedinfo", cmdSchedinfo, opts_schedinfo, info_schedinfo}, + {"screenshot", cmdScreenshot, opts_screenshot, info_screenshot}, {"setmaxmem", cmdSetmaxmem, opts_setmaxmem, info_setmaxmem}, {"setmem", cmdSetmem, opts_setmem, info_setmem}, {"setvcpus", cmdSetvcpus, opts_setvcpus, info_setvcpus}, diff --git a/tools/virsh.pod b/tools/virsh.pod index 2a708f6..56630a5 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -584,6 +584,10 @@ Therefore, -1 is a useful shorthand for 262144. B<Note>: The weight and cap parameters are defined only for the XEN_CREDIT scheduler and are now I<DEPRECATED>. +=item B<screenshot> I<domain-id> I<imagefilepath> + +Takes a screenshot of a current domain console and stores it into a file. + =item B<setmem> I<domain-id> B<kilobytes> optional I<--config> I<--live> I<--current> -- 1.7.5.rc3

This is needed if we want to transfer a temporary file. If the transfer is done with iohelper, we might run into a race condition, where we unlink() file before iohelper is executed. * src/fdstream.c, src/fdstream.h, src/util/iohelper.c: Add new option * src/lxc/lxc_driver.c, src/qemu/qemu_driver.c, src/storage/storage_driver.c, src/uml/uml_driver.c, src/xen/xen_driver.c: Expand existing function calls --- src/fdstream.c | 29 ++++++++++++++++++++++------- src/fdstream.h | 6 ++++-- src/lxc/lxc_driver.c | 3 ++- src/qemu/qemu_driver.c | 3 ++- src/storage/storage_driver.c | 4 ++-- src/uml/uml_driver.c | 3 ++- src/util/iohelper.c | 12 ++++++++++-- src/xen/xen_driver.c | 3 ++- 8 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/fdstream.c b/src/fdstream.c index d2325ae..2702ad7 100644 --- a/src/fdstream.c +++ b/src/fdstream.c @@ -493,7 +493,8 @@ virFDStreamOpenFileInternal(virStreamPtr st, unsigned long long offset, unsigned long long length, int flags, - int mode) + int mode, + bool delete) { int fd = -1; int fds[2] = { -1, -1 }; @@ -502,8 +503,8 @@ virFDStreamOpenFileInternal(virStreamPtr st, int errfd = -1; pid_t pid = 0; - VIR_DEBUG("st=%p path=%s flags=%d offset=%llu length=%llu mode=%d", - st, path, flags, offset, length, mode); + VIR_DEBUG("st=%p path=%s flags=%d offset=%llu length=%llu mode=%d delete=%d", + st, path, flags, offset, length, mode, delete); if (flags & O_CREAT) fd = open(path, flags, mode); @@ -554,6 +555,14 @@ virFDStreamOpenFileInternal(virStreamPtr st, virCommandAddArgFormat(cmd, "%d", mode); virCommandAddArgFormat(cmd, "%llu", offset); virCommandAddArgFormat(cmd, "%llu", length); + virCommandAddArgFormat(cmd, "%u", delete); + + /* when running iohelper we don't want to delete file now, + * because a race condition may occur in which we delete it + * before iohelper even opens it. We want iohelper to remove + * the file instead. + */ + delete = false; if (flags == O_RDONLY) { childfd = fds[1]; @@ -583,6 +592,9 @@ virFDStreamOpenFileInternal(virStreamPtr st, if (virFDStreamOpenInternal(st, fd, cmd, errfd, length) < 0) goto error; + if (delete) + unlink(path); + return 0; error: @@ -601,7 +613,8 @@ int virFDStreamOpenFile(virStreamPtr st, const char *path, unsigned long long offset, unsigned long long length, - int flags) + int flags, + bool delete) { if (flags & O_CREAT) { streamsReportError(VIR_ERR_INTERNAL_ERROR, @@ -611,7 +624,7 @@ int virFDStreamOpenFile(virStreamPtr st, } return virFDStreamOpenFileInternal(st, path, offset, length, - flags, 0); + flags, 0, delete); } int virFDStreamCreateFile(virStreamPtr st, @@ -619,9 +632,11 @@ int virFDStreamCreateFile(virStreamPtr st, unsigned long long offset, unsigned long long length, int flags, - mode_t mode) + mode_t mode, + bool delete) { return virFDStreamOpenFileInternal(st, path, offset, length, - flags | O_CREAT, mode); + flags | O_CREAT, + mode, delete); } diff --git a/src/fdstream.h b/src/fdstream.h index 6b395b6..a66902b 100644 --- a/src/fdstream.h +++ b/src/fdstream.h @@ -37,12 +37,14 @@ int virFDStreamOpenFile(virStreamPtr st, const char *path, unsigned long long offset, unsigned long long length, - int flags); + int flags, + bool delete); int virFDStreamCreateFile(virStreamPtr st, const char *path, unsigned long long offset, unsigned long long length, int flags, - mode_t mode); + mode_t mode, + bool delete); #endif /* __VIR_FDSTREAM_H_ */ diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index e88f834..c6bcc2d 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2781,7 +2781,8 @@ lxcDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, + 0, 0, O_RDWR, false) < 0) goto cleanup; ret = 0; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index a0cd7b1..5abecef 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7069,7 +7069,8 @@ qemuDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, + 0, 0, O_RDWR, false) < 0) goto cleanup; ret = 0; diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c index 5118ffb..5abfb3f 100644 --- a/src/storage/storage_driver.c +++ b/src/storage/storage_driver.c @@ -1592,7 +1592,7 @@ storageVolumeDownload(virStorageVolPtr obj, if (virFDStreamOpenFile(stream, vol->target.path, offset, length, - O_RDONLY) < 0) + O_RDONLY, false) < 0) goto out; ret = 0; @@ -1656,7 +1656,7 @@ storageVolumeUpload(virStorageVolPtr obj, if (virFDStreamOpenFile(stream, vol->target.path, offset, length, - O_WRONLY) < 0) + O_WRONLY, false) < 0) goto out; ret = 0; diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 00ab62d..4b81c43 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -2130,7 +2130,8 @@ umlDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, + 0, 0, O_RDWR, false) < 0) goto cleanup; ret = 0; diff --git a/src/util/iohelper.c b/src/util/iohelper.c index d5821b9..f519d5a 100644 --- a/src/util/iohelper.c +++ b/src/util/iohelper.c @@ -146,6 +146,7 @@ int main(int argc, char **argv) unsigned long long length; int flags; int mode; + unsigned int delete; if (setlocale(LC_ALL, "") == NULL || bindtextdomain(PACKAGE, LOCALEDIR) == NULL || @@ -161,8 +162,8 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - if (argc != 6) { - fprintf(stderr, _("%s: syntax FILENAME FLAGS MODE OFFSET LENGTH\n"), argv[0]); + if (argc != 7) { + fprintf(stderr, _("%s: syntax FILENAME FLAGS MODE OFFSET LENGTH DELETE\n"), argv[0]); exit(EXIT_FAILURE); } @@ -186,10 +187,17 @@ int main(int argc, char **argv) fprintf(stderr, _("%s: malformed file length %s"), argv[0], argv[5]); exit(EXIT_FAILURE); } + if (virStrToLong_ui(argv[6], NULL, 10, &delete) < 0) { + fprintf(stderr, _("%s: malformed delete flag %s"), argv[0],argv[6]); + exit(EXIT_FAILURE); + } if (runIO(path, flags, mode, offset, length) < 0) goto error; + if (delete) + unlink(path); + return 0; error: diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c index 5c5d789..eb9bfa9 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -2086,7 +2086,8 @@ xenUnifiedDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, + 0, 0, O_RDWR, false) < 0) goto cleanup; ret = 0; -- 1.7.5.rc3

* src/qemu/qemu_driver.c: new qemuDomainScreenshot() function * src/qemu/qemu_monitor.c, src/qemu/qemu_monitor.h, src/qemu/qemu_monitor_json.c, src/qemu/qemu_monitor_json.h, src/qemu/qemu_monitor_text.c, src/qemu/qemu_monitor_text.h: Monitor command --- src/qemu/qemu_driver.c | 80 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 20 ++++++++++ src/qemu/qemu_monitor.h | 3 ++ src/qemu/qemu_monitor_json.c | 23 ++++++++++++ src/qemu/qemu_monitor_json.h | 4 ++ src/qemu/qemu_monitor_text.c | 31 ++++++++++++++++ src/qemu/qemu_monitor_text.h | 2 + 7 files changed, 162 insertions(+), 1 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 5abecef..a564382 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2435,6 +2435,84 @@ cleanup: return ret; } +static char * +qemuDomainScreenshot(virDomainPtr dom, + virStreamPtr st, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = dom->conn->privateData; + virDomainObjPtr vm; + qemuDomainObjPrivatePtr priv; + char *tmp = NULL; + int tmp_fd = -1; + char *ret = NULL; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, dom->uuid); + qemuDriverUnlock(driver); + + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(dom->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain matching uuid '%s'"), uuidstr); + goto cleanup; + } + + priv = vm->privateData; + + if (qemuDomainObjBeginJob(vm) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } + + if (virAsprintf(&tmp, "%s/qemu.screendump.XXXXXX", driver->cacheDir) < 0) { + virReportOOMError(); + goto endjob; + } + + if ((tmp_fd = mkstemp(tmp)) == -1) { + virReportSystemError(errno, _("mkstemp(\"%s\") failed"), tmp); + goto endjob; + } + + qemuDomainObjEnterMonitor(vm); + if (qemuMonitorScreendump(priv->mon, tmp) < 0) { + qemuDomainObjExitMonitor(vm); + goto endjob; + } + qemuDomainObjExitMonitor(vm); + + if (VIR_CLOSE(tmp_fd) < 0) { + virReportSystemError(errno, _("unable to close %s"), tmp); + goto endjob; + } + + if (virFDStreamOpenFile(st, tmp, 0, 0, O_RDONLY, true) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to open stream")); + goto endjob; + } + + ret = strdup("image/x-portable-pixmap; charset=binary"); + +endjob: + VIR_FORCE_CLOSE(tmp_fd); + VIR_FREE(tmp); + + if (qemuDomainObjEndJob(vm) == 0) + vm = NULL; + +cleanup: + if (vm) + virDomainObjUnlock(vm); + return ret; +} + static void processWatchdogEvent(void *data, void *opaque) { int ret; @@ -7120,7 +7198,7 @@ static virDriver qemuDriver = { qemudDomainSave, /* domainSave */ qemuDomainRestore, /* domainRestore */ qemudDomainCoreDump, /* domainCoreDump */ - NULL, /* domainScreenshot */ + qemuDomainScreenshot, /* domainScreenshot */ qemudDomainSetVcpus, /* domainSetVcpus */ qemudDomainSetVcpusFlags, /* domainSetVcpusFlags */ qemudDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index f89038e..3c0a808 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2228,3 +2228,23 @@ int qemuMonitorArbitraryCommand(qemuMonitorPtr mon, ret = qemuMonitorTextArbitraryCommand(mon, cmd, reply); return ret; } + +int qemuMonitorScreendump(qemuMonitorPtr mon, + const char *file) +{ + int ret; + + VIR_DEBUG("mon=%p, file=%s", mon, file); + + if (!mon) { + qemuReportError(VIR_ERR_INVALID_ARG,"%s", + _("monitor must not be NULL")); + return -1; + } + + if (mon->json) + ret = qemuMonitorJSONScreendump(mon, file); + else + ret = qemuMonitorTextScreendump(mon, file); + return ret; +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index c90219b..5c96f12 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -423,6 +423,9 @@ int qemuMonitorArbitraryCommand(qemuMonitorPtr mon, char **reply, bool hmp); +int qemuMonitorScreendump(qemuMonitorPtr mon, + const char *file); + /** * When running two dd process and using <> redirection, we need a * shell that will not truncate files. These two strings serve that diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 20a78e1..df45e4b 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2513,3 +2513,26 @@ cleanup: return ret; } + +int qemuMonitorJSONScreendump(qemuMonitorPtr mon, + const char *file) +{ + int ret; + virJSONValuePtr cmd, reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("screendump", + "s:filename", file, + NULL); + + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 086f0e1..bcfdd24 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -204,4 +204,8 @@ int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon, char **reply_str, bool hmp); +int qemuMonitorJSONScreendump(qemuMonitorPtr mon, + const char *file); + + #endif /* QEMU_MONITOR_JSON_H */ diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index 53781c8..f71cf2d 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -2628,3 +2628,34 @@ int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd, return ret; } + +/* Returns -1 on error, -2 if not supported */ +int qemuMonitorTextScreendump(qemuMonitorPtr mon, const char *file) +{ + char *cmd = NULL; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "screendump %s", file) < 0){ + virReportOOMError(); + goto cleanup; + } + + if (qemuMonitorHMPCommand(mon, cmd, &reply) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("taking screenshot failed")); + goto cleanup; + } + + if (strstr(reply, "unknown command:")) { + ret = -2; + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(reply); + VIR_FREE(cmd); + return ret; +} diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index 0838a2b..773d33c 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -198,4 +198,6 @@ int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd, char **reply); +int qemuMonitorTextScreendump(qemuMonitorPtr mon, const char *file); + #endif /* QEMU_MONITOR_TEXT_H */ -- 1.7.5.rc3

* src/vbox/vbox_tmpl.c: New vboxDomainScreenshot() function --- src/vbox/vbox_tmpl.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 113 insertions(+), 0 deletions(-) diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 9a110f9..fd34a12 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -36,6 +36,9 @@ #include <sys/utsname.h> #include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> #include "internal.h" #include "datatypes.h" @@ -51,6 +54,9 @@ #include "nodeinfo.h" #include "logging.h" #include "vbox_driver.h" +#include "configmake.h" +#include "files.h" +#include "fdstream.h" /* This one changes from version to version. */ #if VBOX_API_VERSION == 2002 @@ -8527,6 +8533,109 @@ static char *vboxStorageVolGetPath(virStorageVolPtr vol) { return ret; } +#if VBOX_API_VERSION == 4000 +static char * +vboxDomainScreenshot(virDomainPtr dom, virStreamPtr st, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, char *, NULL); + IConsole *console = NULL; + vboxIID iid = VBOX_IID_INITIALIZER; + IMachine *machine = NULL; + nsresult rc; + char *tmp; + int tmp_fd = -1; + + vboxIIDFromUUID(&iid, dom->uuid); + rc = VBOX_OBJECT_GET_MACHINE(iid.value, &machine); + if (NS_FAILED(rc)) { + vboxError(VIR_ERR_NO_DOMAIN, "%s", + _("no domain with matching uuid")); + return NULL; + } + + if (virAsprintf(&tmp, "%s/cache/libvirt/vbox.screendump.XXXXXX", LOCALSTATEDIR) < 0) { + virReportOOMError(); + return NULL; + } + + if ((tmp_fd = mkstemp(tmp)) == -1) { + virReportSystemError(errno, _("mkstemp(\"%s\") failed"), tmp); + VIR_FREE(tmp); + return NULL; + } + + + rc = VBOX_SESSION_OPEN_EXISTING(iid.value, machine); + if (NS_SUCCEEDED(rc)) { + rc = data->vboxSession->vtbl->GetConsole(data->vboxSession, &console); + if (NS_SUCCEEDED(rc) && console) { + IDisplay *display = NULL; + + console->vtbl->GetDisplay(console, &display); + + if (display) { + PRUint32 width, height, bitsPerPixel; + PRUint32 screenDataSize; + PRUint8 *screenData; + + rc = display->vtbl->GetScreenResolution(display, 0, + &width, &height, + &bitsPerPixel); + + if (NS_FAILED(rc) || !width || !height) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to get screen resolution")); + goto endjob; + } + + rc = display->vtbl->TakeScreenShotPNGToArray(display, 0, + width, height, + &screenDataSize, + &screenData); + if (NS_FAILED(rc)) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("failed to take screenshot")); + goto endjob; + } + + if (safewrite(tmp_fd, (char *) screenData, + screenDataSize) < 0) { + virReportSystemError(errno, _("unable to write data " + "to '%s'"), tmp); + goto endjob; + } + + if (VIR_CLOSE(tmp_fd) < 0) { + virReportSystemError(errno, _("unable to close %s"), tmp); + goto endjob; + } + + if (virFDStreamOpenFile(st, tmp, 0, 0, O_RDONLY, true) < 0) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to open stream")); + goto endjob; + } + + ret = strdup("image/png; charset=binary"); + +endjob: + VIR_FREE(screenData); + VBOX_RELEASE(display); + } + VBOX_RELEASE(console); + } + VBOX_SESSION_CLOSE(); + } + + VIR_FORCE_CLOSE(tmp_fd); + VIR_FREE(tmp); + VBOX_RELEASE(machine); + vboxIIDUnalloc(&iid); + return ret; +} +#endif /* VBOX_API_VERSION == 4000 */ + /** * Function Tables */ @@ -8569,7 +8678,11 @@ virDriver NAME(Driver) = { vboxDomainSave, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ +#if VBOX_API_VERSION == 4000 + vboxDomainScreenshot, /* domainScreenshot */ +#else NULL, /* domainScreenshot */ +#endif vboxDomainSetVcpus, /* domainSetVcpus */ vboxDomainSetVcpusFlags, /* domainSetVcpusFlags */ vboxDomainGetVcpusFlags, /* domainGetVcpusFlags */ -- 1.7.5.rc3
participants (2)
-
Matthias Bolte
-
Michal Privoznik