[libvirt] [PATCH v4 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> <screnID>; For now, we just save the file in format as returned by hypervisor: PPM for Qemu, PNG for VirtualBox. diff to v3: - added screen ID argument 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 ++++++++++++++++++ include/libvirt/libvirt.h.in | 8 +++ src/driver.h | 8 ++- src/esx/esx_driver.c | 1 + src/fdstream.c | 29 +++++++-- src/fdstream.h | 6 +- src/libvirt.c | 60 +++++++++++++++++++ src/libvirt_public.syms | 1 + 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 | 92 ++++++++++++++++++++++++++++- 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 | 5 ++ src/qemu/qemu_monitor_text.c | 31 ++++++++++ src/qemu/qemu_monitor_text.h | 3 + src/remote/remote_driver.c | 41 +++++++++++++ src/remote/remote_protocol.x | 13 ++++- src/remote_protocol-structs | 8 +++ 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 | 134 ++++++++++++++++++++++++++++++++++++++++++ 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 | 97 ++++++++++++++++++++++++++++++ tools/virsh.pod | 6 ++ 38 files changed, 667 insertions(+), 19 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 | 8 ++++++++ src/libvirt_public.syms | 1 + 2 files changed, 9 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 0e1e27a..1d6b276 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -670,6 +670,14 @@ int virDomainCoreDump (virDomainPtr domain, int flags); /* + * Screenshot of current domain console + */ +char * virDomainScreenshot (virDomainPtr domain, + virStreamPtr stream, + unsigned int screen, + unsigned int flags); + +/* * Domain runtime information */ int virDomainGetInfo (virDomainPtr domain, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index ababf39..a5a93ba 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -439,6 +439,7 @@ LIBVIRT_0.9.0 { LIBVIRT_0.9.2 { global: virDomainInjectNMI; + virDomainScreenshot; } LIBVIRT_0.9.0; # .... define new API here using predicted next version number .... -- 1.7.5.rc3

On Thu, May 12, 2011 at 06:29:08PM +0200, Michal Privoznik wrote:
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 | 8 ++++++++ src/libvirt_public.syms | 1 + 2 files changed, 9 insertions(+), 0 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 0e1e27a..1d6b276 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -670,6 +670,14 @@ int virDomainCoreDump (virDomainPtr domain, int flags);
/* + * Screenshot of current domain console + */ +char * virDomainScreenshot (virDomainPtr domain, + virStreamPtr stream, + unsigned int screen, + unsigned int flags); + +/* * Domain runtime information */ int virDomainGetInfo (virDomainPtr domain, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index ababf39..a5a93ba 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -439,6 +439,7 @@ LIBVIRT_0.9.0 { LIBVIRT_0.9.2 { global: virDomainInjectNMI; + virDomainScreenshot; } LIBVIRT_0.9.0;
ACK 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: 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 | 8 +++++++- 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, 26 insertions(+), 1 deletions(-) diff --git a/src/driver.h b/src/driver.h index ea80701..b800f35 100644 --- a/src/driver.h +++ b/src/driver.h @@ -176,6 +176,11 @@ typedef int const char *to, int flags); typedef char * + (*virDrvDomainScreenshot) (virDomainPtr domain, + virStreamPtr stream, + unsigned int screen, + unsigned int flags); +typedef char * (*virDrvDomainGetXMLDesc) (virDomainPtr dom, int flags); typedef char * @@ -568,7 +573,8 @@ struct _virDriver { virDrvDomainGetInfo domainGetInfo; virDrvDomainSave domainSave; virDrvDomainRestore domainRestore; - virDrvDomainCoreDump domainCoreDump; + 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 2e0e9a1..ef7838a 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 d7ff0c6..7e2e7c0 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -2716,6 +2716,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 7a3e33d..1aadb02 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2742,6 +2742,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 60f2dc2..fbb8800 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 5e48b98..cc9ace3 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 732c187..7a3556f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7170,6 +7170,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 c50ff25..328ef62 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -6425,6 +6425,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 bf33d13..0f0d77b 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 9460eb2..baef86c 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 2c3efde..dc91240 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 a3a13c1..b920f79 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 3d8c2dd..0f19d27 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 ac1b685..f442cfd 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 6ef5359..71bb6be 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 64f65e7..8bd5ddf 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 69e14c3..642a5ee 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 5d8dc3d..4529ef4 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 7e863ac..136356d 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

On Thu, May 12, 2011 at 06:29:09PM +0200, Michal Privoznik wrote:
* 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 | 8 +++++++- 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, 26 insertions(+), 1 deletions(-)
diff --git a/src/driver.h b/src/driver.h index ea80701..b800f35 100644 --- a/src/driver.h +++ b/src/driver.h @@ -176,6 +176,11 @@ typedef int const char *to, int flags); typedef char * + (*virDrvDomainScreenshot) (virDomainPtr domain, + virStreamPtr stream, + unsigned int screen, + unsigned int flags); +typedef char * (*virDrvDomainGetXMLDesc) (virDomainPtr dom, int flags); typedef char * @@ -568,7 +573,8 @@ struct _virDriver { virDrvDomainGetInfo domainGetInfo; virDrvDomainSave domainSave; virDrvDomainRestore domainRestore; - virDrvDomainCoreDump domainCoreDump; + 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 2e0e9a1..ef7838a 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 d7ff0c6..7e2e7c0 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -2716,6 +2716,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 7a3e33d..1aadb02 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2742,6 +2742,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 60f2dc2..fbb8800 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 5e48b98..cc9ace3 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 732c187..7a3556f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7170,6 +7170,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 c50ff25..328ef62 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -6425,6 +6425,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 bf33d13..0f0d77b 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 9460eb2..baef86c 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 2c3efde..dc91240 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 a3a13c1..b920f79 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 3d8c2dd..0f19d27 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 ac1b685..f442cfd 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 6ef5359..71bb6be 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 64f65e7..8bd5ddf 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 69e14c3..642a5ee 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 5d8dc3d..4529ef4 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 7e863ac..136356d 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 */
ACK 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/libvirt.c: new function virDomainScreenshot --- src/libvirt.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 60 insertions(+), 0 deletions(-) diff --git a/src/libvirt.c b/src/libvirt.c index 7564db0..914618d 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -2444,6 +2444,66 @@ error: } /** + * virDomainScreenshot: + * @domain: a domain object + * @stream: stream to use as output + * @screen: monitor ID to take screenshot from + * @flags: extra flags, currently unused + * + * Take a screenshot of current domain console as a stream. The image format + * is hypervisor specific. Moreover, some hypervisors supports multiple + * displays per domain. These can be distinguished by @screen argument. + * + * 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 screen, + 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, + screen, 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

On Thu, May 12, 2011 at 06:29:10PM +0200, Michal Privoznik wrote:
* src/libvirt.c: new function virDomainScreenshot --- src/libvirt.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 60 insertions(+), 0 deletions(-)
diff --git a/src/libvirt.c b/src/libvirt.c index 7564db0..914618d 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -2444,6 +2444,66 @@ error: }
/** + * virDomainScreenshot: + * @domain: a domain object + * @stream: stream to use as output + * @screen: monitor ID to take screenshot from + * @flags: extra flags, currently unused + * + * Take a screenshot of current domain console as a stream. The image format + * is hypervisor specific. Moreover, some hypervisors supports multiple + * displays per domain. These can be distinguished by @screen argument.
Need to document the semantics of 'screen' wrt to multiple devices and multiple outputs.
+ * + * 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 screen, + 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, + screen, flags); + + if (ret == NULL) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(domain->conn); + return NULL; +}
ACK 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/remote/remote_protocol.x: Wire protocol definition * daemon/remote.c: Daemon part * src/remote/remote_driver.c: Client part * src/remote_protocol-structs: Add structures --- daemon/remote.c | 57 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 42 ++++++++++++++++++++++++++++++- src/remote/remote_protocol.x | 13 +++++++++- src/remote_protocol-structs | 8 ++++++ 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 1b424fe..6e13958 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->screen, 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/src/remote/remote_driver.c b/src/remote/remote_driver.c index 328ef62..7ba2770 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -4803,6 +4803,46 @@ done: return rv; } +static char * +remoteDomainScreenshot (virDomainPtr domain, + virStreamPtr st, + unsigned int screen, + 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; + args.screen = screen; + + 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, @@ -6425,7 +6465,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 99e5c95..e115f39 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -714,6 +714,16 @@ struct remote_domain_core_dump_args { int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + unsigned int screen; + unsigned int flags; +}; + +struct remote_domain_screenshot_ret { + remote_string mime; +}; + struct remote_domain_get_xml_desc_args { remote_nonnull_domain dom; int flags; @@ -2185,8 +2195,9 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207, /* autogen autogen */ REMOTE_PROC_STORAGE_VOL_UPLOAD = 208, /* skipgen skipgen */ REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209, /* skipgen skipgen */ - REMOTE_PROC_DOMAIN_INJECT_NMI = 210 /* autogen autogen */ + REMOTE_PROC_DOMAIN_INJECT_NMI = 210, /* autogen autogen */ + REMOTE_PROC_DOMAIN_SCREENSHOT = 211 /* skipgen skipgen */ /* * Notice how the entries are grouped in sets of 10 ? * Nice isn't it. Please keep it this way when adding more. diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 0507a91..bb4c8d5 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -403,6 +403,14 @@ struct remote_domain_core_dump_args { remote_nonnull_string to; int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + unsigned int screen; + unsigned int flags; +}; +struct remote_domain_screenshot_ret { + remote_string mime; +}; struct remote_domain_get_xml_desc_args { remote_nonnull_domain dom; int flags; -- 1.7.5.rc3

On Thu, May 12, 2011 at 06:29:11PM +0200, Michal Privoznik wrote:
* src/remote/remote_protocol.x: Wire protocol definition * daemon/remote.c: Daemon part * src/remote/remote_driver.c: Client part * src/remote_protocol-structs: Add structures --- daemon/remote.c | 57 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 42 ++++++++++++++++++++++++++++++- src/remote/remote_protocol.x | 13 +++++++++- src/remote_protocol-structs | 8 ++++++ 4 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/daemon/remote.c b/daemon/remote.c index 1b424fe..6e13958 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->screen, 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/src/remote/remote_driver.c b/src/remote/remote_driver.c index 328ef62..7ba2770 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -4803,6 +4803,46 @@ done: return rv; }
+static char * +remoteDomainScreenshot (virDomainPtr domain, + virStreamPtr st, + unsigned int screen, + 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; + args.screen = screen; + + 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, @@ -6425,7 +6465,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 99e5c95..e115f39 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -714,6 +714,16 @@ struct remote_domain_core_dump_args { int flags; };
+struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + unsigned int screen; + unsigned int flags; +}; + +struct remote_domain_screenshot_ret { + remote_string mime; +}; + struct remote_domain_get_xml_desc_args { remote_nonnull_domain dom; int flags; @@ -2185,8 +2195,9 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207, /* autogen autogen */ REMOTE_PROC_STORAGE_VOL_UPLOAD = 208, /* skipgen skipgen */ REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209, /* skipgen skipgen */ - REMOTE_PROC_DOMAIN_INJECT_NMI = 210 /* autogen autogen */ + REMOTE_PROC_DOMAIN_INJECT_NMI = 210, /* autogen autogen */
+ REMOTE_PROC_DOMAIN_SCREENSHOT = 211 /* skipgen skipgen */ /* * Notice how the entries are grouped in sets of 10 ? * Nice isn't it. Please keep it this way when adding more. diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 0507a91..bb4c8d5 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -403,6 +403,14 @@ struct remote_domain_core_dump_args { remote_nonnull_string to; int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + unsigned int screen; + unsigned int flags; +}; +struct remote_domain_screenshot_ret { + remote_string mime; +}; struct remote_domain_get_xml_desc_args { remote_nonnull_domain dom; int flags;
ACK 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 :|

* tools/virsh.c: Add screenshot command * tools/virsh.pod: Document new command --- tools/virsh.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 6 +++ 2 files changed, 103 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index fbeb7c8..9aa20ed 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1873,6 +1873,102 @@ 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")}, + {"screen", VSH_OT_INT, 0, N_("ID of a screen to take screenshot of")}, + {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 bool +cmdScreenshot(vshControl *ctl, const vshCmd *cmd) { + virDomainPtr dom; + const char *name = NULL; + const char *file = NULL; + int fd = -1; + virStreamPtr st = NULL; + unsigned int screen = 0; + 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 (vshCommandOptInt(cmd, "screen", (int*) &screen) < 0) { + vshError(ctl, "%s", _("invalid screen ID")); + 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, screen, 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 */ @@ -10751,6 +10847,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 f317c57..c4990f9 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -588,6 +588,12 @@ 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> optional I<screenID> + +Takes a screenshot of a current domain console and stores it into a file. +Optionally, if hypervisor supports more displays for a domain, I<screenID> +allows to specify from which should be the screenshot taken. + =item B<setmem> I<domain-id> B<kilobytes> optional I<--config> I<--live> I<--current> -- 1.7.5.rc3

On Thu, May 12, 2011 at 06:29:12PM +0200, Michal Privoznik wrote:
* tools/virsh.c: Add screenshot command * tools/virsh.pod: Document new command --- tools/virsh.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 6 +++ 2 files changed, 103 insertions(+), 0 deletions(-)
diff --git a/tools/virsh.c b/tools/virsh.c index fbeb7c8..9aa20ed 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1873,6 +1873,102 @@ 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")}, + {"screen", VSH_OT_INT, 0, N_("ID of a screen to take screenshot of")}, + {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 bool +cmdScreenshot(vshControl *ctl, const vshCmd *cmd) { + virDomainPtr dom; + const char *name = NULL; + const char *file = NULL; + int fd = -1; + virStreamPtr st = NULL; + unsigned int screen = 0; + 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 (vshCommandOptInt(cmd, "screen", (int*) &screen) < 0) { + vshError(ctl, "%s", _("invalid screen ID")); + 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, screen, 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 */ @@ -10751,6 +10847,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 f317c57..c4990f9 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -588,6 +588,12 @@ 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> optional I<screenID> + +Takes a screenshot of a current domain console and stores it into a file. +Optionally, if hypervisor supports more displays for a domain, I<screenID> +allows to specify from which should be the screenshot taken. +
I think it would be better if 'screenID' was a flag instead of a positional parameter, eg screenshot --screen 1 myguest imagefile.png That would then allow us to make the filename optional too. eg screenshot --screen 1 myguest could automatically create a filename, based on domain name, date + mimetype: myguest-s1-2011-04-20-10:16:10.png 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 :|

On 05/13/2011 03:16 AM, Daniel P. Berrange wrote:
On Thu, May 12, 2011 at 06:29:12PM +0200, Michal Privoznik wrote:
+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")}, + {"screen", VSH_OT_INT, 0, N_("ID of a screen to take screenshot of")}, + {NULL, 0, 0, NULL} +}; +
I think it would be better if 'screenID' was a flag instead of a positional parameter, eg
screenshot --screen 1 myguest imagefile.png
But virsh _already_ handles all named arguments in any order. That is, screenshot --screen 1 myguest imagefile.png screenshot myguest imagefile.png 1 are identical, at least with the above opts_screenshot (thanks to commit b9973f5).
That would then allow us to make the filename optional too. eg
screenshot --screen 1 myguest
Optional filename, however, _does_ make sense, which is not covered in the above opts_screenshot. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Fri, May 13, 2011 at 05:18:27PM -0600, Eric Blake wrote:
On 05/13/2011 03:16 AM, Daniel P. Berrange wrote:
On Thu, May 12, 2011 at 06:29:12PM +0200, Michal Privoznik wrote:
+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")}, + {"screen", VSH_OT_INT, 0, N_("ID of a screen to take screenshot of")}, + {NULL, 0, 0, NULL} +}; +
I think it would be better if 'screenID' was a flag instead of a positional parameter, eg
screenshot --screen 1 myguest imagefile.png
But virsh _already_ handles all named arguments in any order. That is,
screenshot --screen 1 myguest imagefile.png screenshot myguest imagefile.png 1
are identical, at least with the above opts_screenshot (thanks to commit b9973f5).
I don't see how that could work, if you want to make filename optional. Since if you have screenshot myguest 1 it can't determine whether '1' is a screen number or filename. Thus we have to use --screen for this 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 :|

On 16.05.2011 10:56, Daniel P. Berrange wrote:
On Fri, May 13, 2011 at 05:18:27PM -0600, Eric Blake wrote:
On 05/13/2011 03:16 AM, Daniel P. Berrange wrote:
On Thu, May 12, 2011 at 06:29:12PM +0200, Michal Privoznik wrote:
+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")}, + {"screen", VSH_OT_INT, 0, N_("ID of a screen to take screenshot of")}, + {NULL, 0, 0, NULL} +}; +
I think it would be better if 'screenID' was a flag instead of a positional parameter, eg
screenshot --screen 1 myguest imagefile.png
But virsh _already_ handles all named arguments in any order. That is,
screenshot --screen 1 myguest imagefile.png screenshot myguest imagefile.png 1
are identical, at least with the above opts_screenshot (thanks to commit b9973f5).
I don't see how that could work, if you want to make filename optional. Since if you have
screenshot myguest 1 This is now understand as: take screenshot of myguest, screenID=0 (by default) and store it into file named '1'.
Running 'screenshot --screen 1 f14' would produce an error: <file> required. I agree not to make arguments positional, and basically screenID is not. It is an added value that it can be specified as 3rd argument. If we make --file optional (and generate it), this will still work as expected: 'screenshot --screen 1 f14' would not produce any error but file instead. Moreover, screenshot f14 would take screenshot of screenID=0 and store it into file with generated file.
it can't determine whether '1' is a screen number or filename. Thus we have to use --screen for this
Daniel
Michal

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 1aadb02..0939a1d 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2691,7 +2691,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 7a3556f..482f177 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7120,7 +7120,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 903ecee..6542579 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 baef86c..e7cd77a 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 0f19d27..5bafb73 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

On Thu, May 12, 2011 at 06:29:13PM +0200, Michal Privoznik wrote:
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 1aadb02..0939a1d 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2691,7 +2691,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 7a3556f..482f177 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7120,7 +7120,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 903ecee..6542579 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 baef86c..e7cd77a 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 0f19d27..5bafb73 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;
ACK 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 :|

On 05/13/2011 03:18 AM, Daniel P. Berrange wrote:
On Thu, May 12, 2011 at 06:29:13PM +0200, Michal Privoznik wrote:
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
I still think it would be nice to have a mode where iohelper receives its fd by inheritance, rather than having to call open() again. But that's not a show-stopper for pushing this. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

* 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 | 90 +++++++++++++++++++++++++++++++++++++++++- 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 | 5 ++ src/qemu/qemu_monitor_text.c | 31 ++++++++++++++ src/qemu/qemu_monitor_text.h | 3 + 7 files changed, 174 insertions(+), 1 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 482f177..c0cd407 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2486,6 +2486,94 @@ cleanup: return ret; } +static char * +qemuDomainScreenshot(virDomainPtr dom, + virStreamPtr st, + unsigned int screen, + 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; + } + + /* Well, even if qemu allows multiple graphic cards, heads, whatever, + * screenshot command does not */ + if (screen) { + qemuReportError(VIR_ERR_INVALID_ARG, + "%s", _("currently is supported only taking " + "screenshots of screen ID 0")); + 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; @@ -7171,7 +7259,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 f031514..421a81e 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2242,3 +2242,23 @@ int qemuMonitorInjectNMI(qemuMonitorPtr mon) ret = qemuMonitorTextInjectNMI(mon); 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 b84e230..ee3e14d 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -425,6 +425,9 @@ int qemuMonitorArbitraryCommand(qemuMonitorPtr mon, int qemuMonitorInjectNMI(qemuMonitorPtr mon); +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 047a81f..32da893 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2540,3 +2540,26 @@ cleanup: virJSONValueFree(reply); 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 f2dc4d2..3158882 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -205,4 +205,9 @@ int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon, bool hmp); int qemuMonitorJSONInjectNMI(qemuMonitorPtr mon); + +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 1d90c1b..b83809c 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -2655,3 +2655,34 @@ fail: cmd); return -1; } + +/* 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 dbae72b..1ee1179 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -199,4 +199,7 @@ int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd, char **reply); int qemuMonitorTextInjectNMI(qemuMonitorPtr mon); + +int qemuMonitorTextScreendump(qemuMonitorPtr mon, const char *file); + #endif /* QEMU_MONITOR_TEXT_H */ -- 1.7.5.rc3

On Thu, May 12, 2011 at 06:29:14PM +0200, Michal Privoznik wrote:
* 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 | 90 +++++++++++++++++++++++++++++++++++++++++- 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 | 5 ++ src/qemu/qemu_monitor_text.c | 31 ++++++++++++++ src/qemu/qemu_monitor_text.h | 3 + 7 files changed, 174 insertions(+), 1 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 482f177..c0cd407 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2486,6 +2486,94 @@ cleanup: return ret; }
+static char * +qemuDomainScreenshot(virDomainPtr dom, + virStreamPtr st, + unsigned int screen, + 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; + } + + /* Well, even if qemu allows multiple graphic cards, heads, whatever, + * screenshot command does not */ + if (screen) { + qemuReportError(VIR_ERR_INVALID_ARG, + "%s", _("currently is supported only taking " + "screenshots of screen ID 0")); + 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");
I don't think we need the '; charset=binary' bit - just use the mimetype on its own. ACK if that change is made 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/vbox/vbox_tmpl.c: New vboxDomainScreenshot() function --- src/vbox/vbox_tmpl.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 133 insertions(+), 0 deletions(-) diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index dc91240..d598bcf 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,129 @@ static char *vboxStorageVolGetPath(virStorageVolPtr vol) { return ret; } +#if VBOX_API_VERSION == 4000 +static char * +vboxDomainScreenshot(virDomainPtr dom, + virStreamPtr st, + unsigned int screen, + 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; + unsigned int max_screen; + + 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; + } + + rc = machine->vtbl->GetMonitorCount(machine, &max_screen); + if (NS_FAILED(rc)) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to get monitor count")); + VBOX_RELEASE(machine); + return NULL; + } + + if (screen >= max_screen) { + vboxError(VIR_ERR_INVALID_ARG, _("screen ID higher than monitor " + "count (%d)"), max_screen); + VBOX_RELEASE(machine); + return NULL; + } + + if (virAsprintf(&tmp, "%s/cache/libvirt/vbox.screendump.XXXXXX", LOCALSTATEDIR) < 0) { + virReportOOMError(); + VBOX_RELEASE(machine); + return NULL; + } + + if ((tmp_fd = mkstemp(tmp)) == -1) { + virReportSystemError(errno, _("mkstemp(\"%s\") failed"), tmp); + VIR_FREE(tmp); + VBOX_RELEASE(machine); + 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, screen, + &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, screen, + 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 +8698,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

On Thu, May 12, 2011 at 06:29:15PM +0200, Michal Privoznik wrote:
* src/vbox/vbox_tmpl.c: New vboxDomainScreenshot() function --- src/vbox/vbox_tmpl.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 133 insertions(+), 0 deletions(-)
diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index dc91240..d598bcf 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,129 @@ static char *vboxStorageVolGetPath(virStorageVolPtr vol) { return ret; }
+#if VBOX_API_VERSION == 4000 +static char * +vboxDomainScreenshot(virDomainPtr dom, + virStreamPtr st, + unsigned int screen, + 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; + unsigned int max_screen; + + 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; + } + + rc = machine->vtbl->GetMonitorCount(machine, &max_screen); + if (NS_FAILED(rc)) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to get monitor count")); + VBOX_RELEASE(machine); + return NULL; + } + + if (screen >= max_screen) { + vboxError(VIR_ERR_INVALID_ARG, _("screen ID higher than monitor " + "count (%d)"), max_screen); + VBOX_RELEASE(machine); + return NULL; + } + + if (virAsprintf(&tmp, "%s/cache/libvirt/vbox.screendump.XXXXXX", LOCALSTATEDIR) < 0) { + virReportOOMError(); + VBOX_RELEASE(machine); + return NULL; + } + + if ((tmp_fd = mkstemp(tmp)) == -1) { + virReportSystemError(errno, _("mkstemp(\"%s\") failed"), tmp); + VIR_FREE(tmp); + VBOX_RELEASE(machine); + 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, screen, + &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, screen, + 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");
Likewise, drop the '; charset=binary' here ACK with that change 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 :|

On 05/13/2011 11:21 AM, Daniel P. Berrange wrote:
On Thu, May 12, 2011 at 06:29:15PM +0200, Michal Privoznik wrote:
* src/vbox/vbox_tmpl.c: New vboxDomainScreenshot() function --- src/vbox/vbox_tmpl.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 133 insertions(+), 0 deletions(-)
diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index dc91240..d598bcf 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,129 @@ static char *vboxStorageVolGetPath(virStorageVolPtr vol) { return ret; }
+#if VBOX_API_VERSION == 4000 +static char * +vboxDomainScreenshot(virDomainPtr dom, + virStreamPtr st, + unsigned int screen, + 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; + unsigned int max_screen; + + 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; + } + + rc = machine->vtbl->GetMonitorCount(machine, &max_screen); + if (NS_FAILED(rc)) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to get monitor count")); + VBOX_RELEASE(machine); + return NULL; + } + + if (screen >= max_screen) { + vboxError(VIR_ERR_INVALID_ARG, _("screen ID higher than monitor " + "count (%d)"), max_screen); + VBOX_RELEASE(machine); + return NULL; + } + + if (virAsprintf(&tmp, "%s/cache/libvirt/vbox.screendump.XXXXXX", LOCALSTATEDIR) < 0) { + virReportOOMError(); + VBOX_RELEASE(machine); + return NULL; + } + + if ((tmp_fd = mkstemp(tmp)) == -1) { + virReportSystemError(errno, _("mkstemp(\"%s\") failed"), tmp); + VIR_FREE(tmp); + VBOX_RELEASE(machine); + 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, screen, + &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, screen, + 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");
Likewise, drop the '; charset=binary' here
ACK with that change
Daniel Thanks, pushed the whole series.
Michal

On Thu, May 12, 2011 at 06:29:07PM +0200, Michal Privoznik wrote:
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> <screnID>;
Hmm, but we have two different ways of having multiple screens. - A single graphics card, with multiple virtual monitor outputs <video> <model type='vga' vram='9216' heads='4'/> </video> VirtualBox graphics card supports this - Multiple graphics cards, each with one monitor output <video> <model type='qxl' vram='9216' heads='1'/> </video> <video> <model type='qxl' vram='9216' heads='1'/> </video> <video> <model type='qxl' vram='9216' heads='1'/> </video> <video> <model type='qxl' vram='9216' heads='1'/> </video> QEMU supports this with QXL It is perfectly possible to combine both, to have multiple graphics cards each with multiple outputs. So we need to define what we mean by 'screen' number. We could have separate 'device' and 'head' parameters, or we have 'screen' and declare that heads are enumerated, before devices. So, eg with a config like <video> <model type='qxl' vram='9216' heads='2'/> <alias id='video0'/> </video> <video> <model type='qxl' vram='9216' heads='4'/> <alias id='video1'/> </video> Then, screen is calculated as Screen Device Head 0 video0 0 1 video0 1 2 video1 0 3 video1 1 4 video1 2 5 video1 3 Incidentally an RFE is needed against QEMU, since it can only do screen dump of the first device :-( 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 :|

On 05/12/2011 06:48 PM, Daniel P. Berrange wrote:
On Thu, May 12, 2011 at 06:29:07PM +0200, Michal Privoznik wrote: ... So, eg with a config like
<video> <model type='qxl' vram='9216' heads='2'/> <alias id='video0'/> </video> <video> <model type='qxl' vram='9216' heads='4'/> <alias id='video1'/> </video>
Then, screen is calculated as
Screen Device Head 0 video0 0 1 video0 1 2 video1 0 3 video1 1 4 video1 2 5 video1 3
Incidentally an RFE is needed against QEMU, since it can only do screen dump of the first device :-(
Daniel
Yes, that is what I had in my mind when creating this concept. Or would it be better to split screen ID into video # and head #? Michal

On Fri, May 13, 2011 at 10:34:24AM +0200, Michal Prívozník wrote:
On 05/12/2011 06:48 PM, Daniel P. Berrange wrote:
On Thu, May 12, 2011 at 06:29:07PM +0200, Michal Privoznik wrote: ... So, eg with a config like
<video> <model type='qxl' vram='9216' heads='2'/> <alias id='video0'/> </video> <video> <model type='qxl' vram='9216' heads='4'/> <alias id='video1'/> </video>
Then, screen is calculated as
Screen Device Head 0 video0 0 1 video0 1 2 video1 0 3 video1 1 4 video1 2 5 video1 3
Incidentally an RFE is needed against QEMU, since it can only do screen dump of the first device :-(
Daniel
Yes, that is what I had in my mind when creating this concept.
Ok, please document that in the API docs for the public API
Or would it be better to split screen ID into video # and head #?
No, I think that's probably overkill, unless anyone can think of something we can do with them separated, that we can't do with them combined... 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 :|

On 05/13/2011 02:56 AM, Daniel P. Berrange wrote:
On Fri, May 13, 2011 at 10:34:24AM +0200, Michal Prívozník wrote:
Then, screen is calculated as
Screen Device Head 0 video0 0 1 video0 1 2 video1 0 3 video1 1 4 video1 2 5 video1 3
Incidentally an RFE is needed against QEMU, since it can only do screen dump of the first device :-(
Daniel
Yes, that is what I had in my mind when creating this concept.
Ok, please document that in the API docs for the public API
Or would it be better to split screen ID into video # and head #?
No, I think that's probably overkill, unless anyone can think of something we can do with them separated, that we can't do with them combined...
Good thing we have the flags argument. When you have a card that supports multiple monitors, I could see it being worth capturing a screenshot of just one monitor, vs. a combined screenshot of both monitors as a single image. With the above layout, this could be done as: virDomainScreenshot(dom, st, 1, 0) - just screen 1 (head 1 of video0) virDomainScreenshot(dom, st, 1, VIR_DOMAIN_SCREENSHOT_DEVICE) - composite of all screens on video1 (that is, screens 2-5) of course, supposing that the hypervisors support combined imaging. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Fri, May 13, 2011 at 05:15:56PM -0600, Eric Blake wrote:
On 05/13/2011 02:56 AM, Daniel P. Berrange wrote:
On Fri, May 13, 2011 at 10:34:24AM +0200, Michal Prívozník wrote:
Then, screen is calculated as
Screen Device Head 0 video0 0 1 video0 1 2 video1 0 3 video1 1 4 video1 2 5 video1 3
Incidentally an RFE is needed against QEMU, since it can only do screen dump of the first device :-(
Daniel
Yes, that is what I had in my mind when creating this concept.
Ok, please document that in the API docs for the public API
Or would it be better to split screen ID into video # and head #?
No, I think that's probably overkill, unless anyone can think of something we can do with them separated, that we can't do with them combined...
Good thing we have the flags argument. When you have a card that supports multiple monitors, I could see it being worth capturing a screenshot of just one monitor, vs. a combined screenshot of both monitors as a single image. With the above layout, this could be done as:
virDomainScreenshot(dom, st, 1, 0) - just screen 1 (head 1 of video0) virDomainScreenshot(dom, st, 1, VIR_DOMAIN_SCREENSHOT_DEVICE) - composite of all screens on video1 (that is, screens 2-5)
of course, supposing that the hypervisors support combined imaging.
The only way I could see that being possible, is if there was actually a guest OS agent that captured the screenshot, because the hypervisor has no way of knowing what the layout of the screens are. ie if the device has 2 outputs and you want a single image of both outputs how do you know if the output 1 is left of output 2, or right of it, or above it, or below it, etc. 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 :|
participants (4)
-
Daniel P. Berrange
-
Eric Blake
-
Michal Privoznik
-
Michal Prívozník