[libvirt] [PATCH 0/7] Add support for taking screenshots

This patch series implements screenshots taking feature, which is accessible via new virDomainScreenshot API and 'screenshot' command in virsh. By now, 'flags' argument is not used, but can be later when specifying say image output format, etc. Talking of - output is hypervisor specific. QEMU output images in PPM, VirtualBox in PNG. Michal Privoznik (7): 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 qemu: Implement the driver methods vbox: Implement the driver methods daemon/remote.c | 26 +++++++++ daemon/remote_dispatch_args.h | 1 + daemon/remote_dispatch_prototypes.h | 8 +++ daemon/remote_dispatch_table.h | 5 ++ include/libvirt/libvirt.h.in | 7 ++ src/driver.h | 5 ++ src/esx/esx_driver.c | 1 + src/libvirt.c | 77 +++++++++++++++++++++++++ src/libvirt_public.syms | 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 | 106 +++++++++++++++++++++++++++++++++++ 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 | 26 +++++++++ src/remote/remote_protocol.c | 13 ++++ src/remote/remote_protocol.h | 10 +++ src/remote/remote_protocol.x | 9 +++- src/remote_protocol-structs | 5 ++ src/test/test_driver.c | 1 + src/uml/uml_driver.c | 1 + src/vbox/vbox_tmpl.c | 97 ++++++++++++++++++++++++++++++++ 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 + tools/virsh.c | 49 ++++++++++++++++ tools/virsh.pod | 4 + 39 files changed, 547 insertions(+), 1 deletions(-) -- 1.7.4

--- include/libvirt/libvirt.h.in | 7 +++++++ src/libvirt_public.syms | 1 + 2 files changed, 8 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index bd36015..42711e6 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 + */ +int virDomainScreenshot (virDomainPtr domain, + const char *to, + int flags); + +/* * Domain runtime information */ int virDomainGetInfo (virDomainPtr domain, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index b4aed41..a6ce1bb 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -428,6 +428,7 @@ LIBVIRT_0.9.0 { global: virDomainGetBlkioParameters; virDomainMigrateSetMaxSpeed; + virDomainScreenshot; virDomainSetBlkioParameters; virDomainSetMemoryFlags; virEventRegisterDefaultImpl; -- 1.7.4

On 04/01/2011 09:29 AM, Michal Privoznik wrote:
--- include/libvirt/libvirt.h.in | 7 +++++++ src/libvirt_public.syms | 1 + 2 files changed, 8 insertions(+), 0 deletions(-)
Hmm, we've already declared 0.9.0 feature freeze;
+++ b/src/libvirt_public.syms @@ -428,6 +428,7 @@ LIBVIRT_0.9.0 { global: virDomainGetBlkioParameters; virDomainMigrateSetMaxSpeed; + virDomainScreenshot; virDomainSetBlkioParameters; virDomainSetMemoryFlags; virEventRegisterDefaultImpl;
So this needs to be reworked to start a new section for LIBVIRT_0.9.1, and defer this series until after the release. While it's probably a low-risk API addition, we're setting a bad precedent if we let this in this late after the release candidate and this close to the final release. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Fri, Apr 01, 2011 at 05:29:15PM +0200, Michal Privoznik wrote:
--- include/libvirt/libvirt.h.in | 7 +++++++ src/libvirt_public.syms | 1 + 2 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index bd36015..42711e6 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 + */ +int virDomainScreenshot (virDomainPtr domain, + const char *to, + int flags);
I'm loathe to introduce any new APIs which accept a filename argument, because they're essentially useless for any app talking to libvirtd remotely, and often troublesome for non-privileged apps talking to privileged libvirtd. I think this API should instead use the virStreamPtr facility, making it follow the design of the virStorageVolDownload() API I recently added. A minor point is that 'flags' should be 'unsigned int' The question of file format is another interesting issue. We might want to actually return a string representing the mime-type of the output file. eg, char * virDomainScreenshot (virDomainPtr domain, virStreamPtr stream, unsigned int flags); char *mimetype = virDomainScreenshot(dom, st, 0); So then the client app can decide what file extension to use when saving the data, or when parsing it in memory. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

--- 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 e5f91ca..af463d0 100644 --- a/src/driver.h +++ b/src/driver.h @@ -175,6 +175,10 @@ typedef int (*virDrvDomainCoreDump) (virDomainPtr domain, const char *to, int flags); +typedef int + (*virDrvDomainScreenshot) (virDomainPtr domain, + const char *to, + int flags); typedef char * (*virDrvDomainDumpXML) (virDomainPtr dom, int flags); @@ -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 deda372..2d52314 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4602,6 +4602,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 3859405..7fa55bb 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -2282,6 +2282,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 e905302..ed40cb2 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2833,6 +2833,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 fb30c37..b451276 100644 --- a/src/openvz/openvz_driver.c +++ b/src/openvz/openvz_driver.c @@ -1581,6 +1581,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 51f9ff6..761ac34 100644 --- a/src/phyp/phyp_driver.c +++ b/src/phyp/phyp_driver.c @@ -3982,6 +3982,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 5aa715e..c9cc74a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -6850,6 +6850,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 bf94e70..d7772d0 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -11223,6 +11223,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 17f5ad9..5a33054 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 e2bd5f2..517b219 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -2176,6 +2176,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 8bd27dd..390fc3b 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -8564,6 +8564,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 b5e416b..926eca0 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 9f47722..a883c15 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -2068,6 +2068,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 47355ce..f624ed3 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 7d4ba4c..46b7870 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 8859373..070ffc6 100644 --- a/src/xen/xend_internal.c +++ b/src/xen/xend_internal.c @@ -3847,6 +3847,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 7f73588..ae55e0f 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 d9aad1f..c17d5f9 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 27206a0..d510697 100644 --- a/src/xenapi/xenapi_driver.c +++ b/src/xenapi/xenapi_driver.c @@ -1812,6 +1812,7 @@ static virDriver xenapiDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenapiDomainSetVcpus, /* domainSetVcpus */ xenapiDomainSetVcpusFlags, /* domainSetVcpusFlags */ xenapiDomainGetVcpusFlags, /* domainGetVcpusFlags */ -- 1.7.4

--- src/libvirt.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 77 insertions(+), 0 deletions(-) diff --git a/src/libvirt.c b/src/libvirt.c index 8be18d4..9f8f2e6 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -2445,6 +2445,83 @@ error: } /** + * virDomainScreenshot: + * @domain: a domain object + * @to: path for the image + * @flags: extra flags, currently unused + * + * Take a screenshot of current domain console and store it + * as image under given path. The image format is hypervisor + * specific. + * + * Returns 0 in case of success and -1 in case of failure. + */ +int +virDomainScreenshot(virDomainPtr domain, const char *to, int flags) +{ + char filepath[4096]; + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "to=%s, flags=%d", to, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + if (domain->conn->flags & VIR_CONNECT_RO) { + virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + conn = domain->conn; + if (to == NULL) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + /* + * We must absolutize the file path as the save is done out of process + * TODO: check for URI when libxml2 is linked in. + */ + if(to[0] != '/') { + unsigned int len, t; + + t = strlen(to); + if (getcwd(filepath, sizeof(filepath) - (t + 3)) == NULL) { + virLibDomainError(VIR_ERR_SYSTEM_ERROR, + _("cannot get current directory")); + goto error; + } + len = strlen(filepath); + /* that should be covered by getcwd() semantic, but be 100% sure */ + if (len > sizeof(filepath) - (t + 3)) { + virLibDomainError(VIR_ERR_INTERNAL_ERROR, + _("path too long")); + goto error; + } + filepath[len] = '/'; + strcpy(&filepath[len + 1], to); + to = &filepath[0]; + } + + if (conn->driver->domainScreenshot) { + int ret; + ret = conn->driver->domainScreenshot (domain, to, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(domain->conn); + return -1; +} + +/** * virDomainShutdown: * @domain: a domain object * -- 1.7.4

--- daemon/remote.c | 26 ++++++++++++++++++++++++++ daemon/remote_dispatch_args.h | 1 + daemon/remote_dispatch_prototypes.h | 8 ++++++++ daemon/remote_dispatch_table.h | 5 +++++ src/remote/remote_driver.c | 27 ++++++++++++++++++++++++++- src/remote/remote_protocol.c | 13 +++++++++++++ src/remote/remote_protocol.h | 10 ++++++++++ src/remote/remote_protocol.x | 9 ++++++++- src/remote_protocol-structs | 5 +++++ 9 files changed, 102 insertions(+), 2 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 1700c2d..8797c38 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -2310,6 +2310,32 @@ remoteDispatchDomainCoreDump (struct qemud_server *server ATTRIBUTE_UNUSED, } static int +remoteDispatchDomainScreenshot (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_screenshot_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + virDomainPtr dom; + + dom = get_nonnull_domain (conn, args->dom); + if (dom == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + if (virDomainScreenshot (dom, args->to, args->flags) == -1) { + remoteDispatchConnError(rerr, conn); + virDomainFree(dom); + return -1; + } + virDomainFree(dom); + return 0; +} + +static int remoteDispatchDomainSetAutostart (struct qemud_server *server ATTRIBUTE_UNUSED, struct qemud_client *client ATTRIBUTE_UNUSED, virConnectPtr conn, diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h index f9537d7..2be8003 100644 --- a/daemon/remote_dispatch_args.h +++ b/daemon/remote_dispatch_args.h @@ -178,3 +178,4 @@ remote_domain_migrate_set_max_speed_args val_remote_domain_migrate_set_max_speed_args; remote_storage_vol_upload_args val_remote_storage_vol_upload_args; remote_storage_vol_download_args val_remote_storage_vol_download_args; + remote_domain_screenshot_args val_remote_domain_screenshot_args; diff --git a/daemon/remote_dispatch_prototypes.h b/daemon/remote_dispatch_prototypes.h index 18bf41d..d4844b4 100644 --- a/daemon/remote_dispatch_prototypes.h +++ b/daemon/remote_dispatch_prototypes.h @@ -546,6 +546,14 @@ static int remoteDispatchDomainSave( remote_error *err, remote_domain_save_args *args, void *ret); +static int remoteDispatchDomainScreenshot( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_screenshot_args *args, + void *ret); static int remoteDispatchDomainSetAutostart( struct qemud_server *server, struct qemud_client *client, diff --git a/daemon/remote_dispatch_table.h b/daemon/remote_dispatch_table.h index b39f7c2..349006a 100644 --- a/daemon/remote_dispatch_table.h +++ b/daemon/remote_dispatch_table.h @@ -1052,3 +1052,8 @@ .args_filter = (xdrproc_t) xdr_remote_storage_vol_download_args, .ret_filter = (xdrproc_t) xdr_void, }, +{ /* DomainScreenshot => 210 */ + .fn = (dispatch_fn) remoteDispatchDomainScreenshot, + .args_filter = (xdrproc_t) xdr_remote_domain_screenshot_args, + .ret_filter = (xdrproc_t) xdr_void, +}, diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index d7772d0..3b6d141 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -2929,6 +2929,31 @@ done: } static int +remoteDomainScreenshot (virDomainPtr domain, const char *to, int flags) +{ + int rv = -1; + remote_domain_screenshot_args args; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain (&args.dom, domain); + args.to = (char *) to; + args.flags = flags; + + if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SCREENSHOT, + (xdrproc_t) xdr_remote_domain_screenshot_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) + goto done; + + rv = 0; + +done: + remoteDriverUnlock(priv); + return rv; +} + +static int remoteDomainSetVcpus (virDomainPtr domain, unsigned int nvcpus) { int rv = -1; @@ -11223,7 +11248,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.c b/src/remote/remote_protocol.c index 5604371..00b51a0 100644 --- a/src/remote/remote_protocol.c +++ b/src/remote/remote_protocol.c @@ -1230,6 +1230,19 @@ xdr_remote_domain_core_dump_args (XDR *xdrs, remote_domain_core_dump_args *objp) } bool_t +xdr_remote_domain_screenshot_args (XDR *xdrs, remote_domain_screenshot_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->dom)) + return FALSE; + if (!xdr_remote_nonnull_string (xdrs, &objp->to)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t xdr_remote_domain_dump_xml_args (XDR *xdrs, remote_domain_dump_xml_args *objp) { diff --git a/src/remote/remote_protocol.h b/src/remote/remote_protocol.h index d9bf151..826853c 100644 --- a/src/remote/remote_protocol.h +++ b/src/remote/remote_protocol.h @@ -666,6 +666,13 @@ struct remote_domain_core_dump_args { }; typedef struct remote_domain_core_dump_args remote_domain_core_dump_args; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + remote_nonnull_string to; + int flags; +}; +typedef struct remote_domain_screenshot_args remote_domain_screenshot_args; + struct remote_domain_dump_xml_args { remote_nonnull_domain dom; int flags; @@ -2413,6 +2420,7 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207, REMOTE_PROC_STORAGE_VOL_UPLOAD = 208, REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209, + REMOTE_PROC_DOMAIN_SCREENSHOT = 210, }; typedef enum remote_procedure remote_procedure; @@ -2541,6 +2549,7 @@ extern bool_t xdr_remote_domain_get_info_ret (XDR *, remote_domain_get_info_ret extern bool_t xdr_remote_domain_save_args (XDR *, remote_domain_save_args*); extern bool_t xdr_remote_domain_restore_args (XDR *, remote_domain_restore_args*); extern bool_t xdr_remote_domain_core_dump_args (XDR *, remote_domain_core_dump_args*); +extern bool_t xdr_remote_domain_screenshot_args (XDR *, remote_domain_screenshot_args*); extern bool_t xdr_remote_domain_dump_xml_args (XDR *, remote_domain_dump_xml_args*); extern bool_t xdr_remote_domain_dump_xml_ret (XDR *, remote_domain_dump_xml_ret*); extern bool_t xdr_remote_domain_migrate_prepare_args (XDR *, remote_domain_migrate_prepare_args*); @@ -2898,6 +2907,7 @@ extern bool_t xdr_remote_domain_get_info_ret (); extern bool_t xdr_remote_domain_save_args (); extern bool_t xdr_remote_domain_restore_args (); extern bool_t xdr_remote_domain_core_dump_args (); +extern bool_t xdr_remote_domain_screenshot_args (); extern bool_t xdr_remote_domain_dump_xml_args (); extern bool_t xdr_remote_domain_dump_xml_ret (); extern bool_t xdr_remote_domain_migrate_prepare_args (); diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 675eccd..090e959 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -714,6 +714,12 @@ struct remote_domain_core_dump_args { int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + remote_nonnull_string to; + int flags; +}; + struct remote_domain_dump_xml_args { remote_nonnull_domain dom; int flags; @@ -2176,7 +2182,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 944553c..9adc695 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -403,6 +403,11 @@ struct remote_domain_core_dump_args { remote_nonnull_string to; int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + remote_nonnull_string to; + int flags; +}; struct remote_domain_dump_xml_args { remote_nonnull_domain dom; int flags; -- 1.7.4

--- tools/virsh.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 4 ++++ 2 files changed, 53 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 19e3449..6f6883d 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1881,6 +1881,54 @@ cmdDump(vshControl *ctl, const vshCmd *cmd) } /* + * "screenshot" command + */ +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 +cmdScreenshot(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + const char *name = NULL; + const char *to = NULL; + int ret = FALSE; + int flags = 0; /* currently unused */ + + if (!vshConnectionUsability(ctl, ctl->conn)) + return FALSE; + + if (vshCommandOptString(cmd, "file", &to) <= 0) + return FALSE; + + if (!(dom = vshCommandOptDomain(ctl, cmd, &name))) + return FALSE; + + if (virDomainScreenshot(dom, to, flags) == 0) { + vshPrint(ctl, _("Screenshot saved to %s"), to); + } else { + vshError(ctl, _("Failed to take a screenshot of %s"), name); + goto error; + } + + ret = TRUE; + +error: + virDomainFree(dom); + return ret; +} + +/* * "resume" command */ static const vshCmdInfo info_resume[] = { @@ -10691,6 +10739,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 f4bd294..e61f501 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -578,6 +578,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> Change the memory allocation for a guest domain. -- 1.7.4

--- src/qemu/qemu_driver.c | 107 +++++++++++++++++++++++++++++++++++++++++- 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, 189 insertions(+), 1 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index c9cc74a..e25c7a6 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2383,6 +2383,111 @@ cleanup: return ret; } +static int +qemuDomainScreenshot(virDomainPtr dom, const char *path, + int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = dom->conn->privateData; + virDomainObjPtr vm; + qemuDomainObjPrivatePtr priv; + char *tmp = NULL; + int path_fd = -1, tmp_fd = -1, ret = -1; + char buf[4096]; + ssize_t nread, nwrite; + + 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 ((path_fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR)) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to create '%s'"), path); + goto endjob; + } + + if (virAsprintf(&tmp, "%s/qemu.screendump.XXXXXX", driver->cacheDir) < 0) { + virReportOOMError(); + goto endjob; + } + + /* Create a temporary filename */ + 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); + + /* Copy to destination */ + while ((nread=saferead(tmp_fd, buf, sizeof(buf))) > 0) { + nwrite = safewrite(path_fd, buf, nread); + if (nread != nwrite) { + virReportSystemError(errno, _("failed to write data to '%s'"), + path); + goto endjob; + } + } + if (nread < 0) { + virReportSystemError(errno, _("failed to read data from '%s'"), + tmp); + } + + if (VIR_CLOSE(tmp_fd) < 0) { + virReportSystemError(errno, _("error closing '%s'"), tmp); + goto endjob; + } + tmp_fd = -1; + + if (VIR_CLOSE(path_fd) < 0) { + virReportSystemError(errno, _("error closing '%s'"), path); + goto endjob; + } + path_fd = -1; + + ret = 0; + +endjob: + VIR_FORCE_CLOSE(tmp_fd); + if (tmp != NULL) { + unlink(tmp); + VIR_FREE(tmp); + } + + VIR_FORCE_CLOSE(path_fd); + + if (qemuDomainObjEndJob(vm) == 0) + vm = NULL; + +cleanup: + if (vm) + virDomainObjUnlock(vm); + return ret; +} + static void processWatchdogEvent(void *data, void *opaque) { int ret; @@ -6850,7 +6955,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 2d28f8d..6834d29 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 168c60f..52c82d0 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.4

--- src/vbox/vbox_tmpl.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 96 insertions(+), 0 deletions(-) diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 390fc3b..5c9c271 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,7 @@ #include "nodeinfo.h" #include "logging.h" #include "vbox_driver.h" +#include "files.h" /* This one changes from version to version. */ #if VBOX_API_VERSION == 2002 @@ -8522,6 +8526,94 @@ static char *vboxStorageVolGetPath(virStorageVolPtr vol) { return ret; } +#if VBOX_API_VERSION == 4000 +static int +vboxDomainScreenshot(virDomainPtr dom, const char *path, + int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, int, -1); + IConsole *console = NULL; + vboxIID iid = VBOX_IID_INITIALIZER; + IMachine *machine = NULL; + nsresult rc; + int 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 -1; + } + + 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 ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, + S_IRUSR|S_IWUSR)) < 0) { + vboxError(VIR_ERR_OPERATION_FAILED, _("failed to " + "create '%s'"), path); + goto endjob; + } + + if (safewrite(fd, (char *) screenData, screenDataSize) < 0) { + virReportSystemError(errno, _("unable to write data " + "to '%s'"), path); + goto endjob; + } + + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, _("error closing '%s'"), + path); + goto endjob; + } + + ret = 0; + +endjob: + VBOX_RELEASE(display); + } + VBOX_RELEASE(console); + } + VBOX_SESSION_CLOSE(); + } + + VBOX_RELEASE(machine); + vboxIIDUnalloc(&iid); + return ret; +} +#endif /* VBOX_API_VERSION == 4000 */ + /** * Function Tables */ @@ -8564,7 +8656,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.4
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Michal Privoznik