[libvirt] [PATCH v2 0/8] Add support for taking screenshots

Diff to v1: - filename argument replaced with stream - returning file mime-type This patch series implements screenshots taking feature, which is accessible via new virDomainScreenshot API and 'screenshot' command in virsh. By now, 'flags' argument is not used, but can be later when specifying say image output format, etc. Talking of - output is hypervisor specific. QEMU output images in PPM, VirtualBox in PNG. Michal Privoznik (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 | 61 +++++++++++++++++++ daemon/remote_dispatch_args.h | 1 + daemon/remote_dispatch_prototypes.h | 8 +++ daemon/remote_dispatch_ret.h | 1 + daemon/remote_dispatch_table.h | 5 ++ include/libvirt/libvirt.h.in | 7 ++ src/driver.h | 5 ++ src/esx/esx_driver.c | 1 + src/fdstream.c | 25 ++++++-- src/fdstream.h | 6 +- src/libvirt.c | 56 +++++++++++++++++ src/libvirt_public.syms | 5 ++ src/libxl/libxl_driver.c | 1 + src/lxc/lxc_driver.c | 4 +- src/openvz/openvz_driver.c | 1 + src/phyp/phyp_driver.c | 1 + src/qemu/qemu_driver.c | 82 +++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 20 ++++++ src/qemu/qemu_monitor.h | 3 + src/qemu/qemu_monitor_json.c | 23 +++++++ src/qemu/qemu_monitor_json.h | 4 + src/qemu/qemu_monitor_text.c | 31 ++++++++++ src/qemu/qemu_monitor_text.h | 2 + src/remote/remote_driver.c | 38 ++++++++++++ src/remote/remote_protocol.c | 20 ++++++ src/remote/remote_protocol.h | 16 +++++ src/remote/remote_protocol.x | 12 ++++- src/storage/storage_driver.c | 4 +- src/test/test_driver.c | 1 + src/uml/uml_driver.c | 4 +- src/util/iohelper.c | 12 +++- src/vbox/vbox_tmpl.c | 114 +++++++++++++++++++++++++++++++++++ src/vmware/vmware_driver.c | 1 + src/xen/xen_driver.c | 4 +- src/xen/xen_driver.h | 1 + src/xen/xen_hypervisor.c | 1 + src/xen/xen_inotify.c | 1 + src/xen/xend_internal.c | 1 + src/xen/xm_internal.c | 1 + src/xen/xs_internal.c | 1 + src/xenapi/xenapi_driver.c | 1 + tools/virsh.c | 90 +++++++++++++++++++++++++++ tools/virsh.pod | 4 + 43 files changed, 664 insertions(+), 16 deletions(-) -- 1.7.4

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

* src/driver.h: Stub code for new API * src/esx/esx_driver.c, src/libxl/libxl_driver.c, src/lxc/lxc_driver.c, src/openvz/openvz_driver.c, src/phyp/phyp_driver.c, src/qemu/qemu_driver.c, rc/remote/remote_driver.c, rc/test/test_driver.c, src/uml/uml_driver.c, src/vbox/vbox_tmpl.c, src/vmware/vmware_driver.c, src/xen/xen_driver.c, src/xen/xen_driver.h, src/xen/xen_hypervisor.c, src/xen/xen_inotify.c, src/xen/xend_internal.c, src/xen/xm_internal.c, src/xen/xs_internal.c, src/xenapi/xenapi_driver.c: Add dummy entries in driver table for new APIs --- src/driver.h | 5 +++++ src/esx/esx_driver.c | 1 + src/libxl/libxl_driver.c | 1 + src/lxc/lxc_driver.c | 1 + src/openvz/openvz_driver.c | 1 + src/phyp/phyp_driver.c | 1 + src/qemu/qemu_driver.c | 1 + src/remote/remote_driver.c | 1 + src/test/test_driver.c | 1 + src/uml/uml_driver.c | 1 + src/vbox/vbox_tmpl.c | 1 + src/vmware/vmware_driver.c | 1 + src/xen/xen_driver.c | 1 + src/xen/xen_driver.h | 1 + src/xen/xen_hypervisor.c | 1 + src/xen/xen_inotify.c | 1 + src/xen/xend_internal.c | 1 + src/xen/xm_internal.c | 1 + src/xen/xs_internal.c | 1 + src/xenapi/xenapi_driver.c | 1 + 20 files changed, 24 insertions(+), 0 deletions(-) diff --git a/src/driver.h b/src/driver.h index e5f91ca..999abab 100644 --- a/src/driver.h +++ b/src/driver.h @@ -176,6 +176,10 @@ typedef int const char *to, int flags); typedef char * + (*virDrvDomainScreenshot) (virDomainPtr domain, + virStreamPtr stream, + unsigned int flags); +typedef char * (*virDrvDomainDumpXML) (virDomainPtr dom, int flags); typedef char * @@ -566,6 +570,7 @@ struct _virDriver { virDrvDomainSave domainSave; virDrvDomainRestore domainRestore; virDrvDomainCoreDump domainCoreDump; + virDrvDomainScreenshot domainScreenshot; virDrvDomainSetVcpus domainSetVcpus; virDrvDomainSetVcpusFlags domainSetVcpusFlags; virDrvDomainGetVcpusFlags domainGetVcpusFlags; diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index deda372..2d52314 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4602,6 +4602,7 @@ static virDriver esxDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ esxDomainSetVcpus, /* domainSetVcpus */ esxDomainSetVcpusFlags, /* domainSetVcpusFlags */ esxDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index 75f99c1..0c6635c 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -2407,6 +2407,7 @@ static virDriver libxlDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ libxlDomainSetVcpus, /* domainSetVcpus */ libxlDomainSetVcpusFlags, /* domainSetVcpusFlags */ libxlDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index e905302..ed40cb2 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2833,6 +2833,7 @@ static virDriver lxcDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainSetVcpus */ NULL, /* domainSetVcpusFlags */ NULL, /* domainGetVcpusFlags */ diff --git a/src/openvz/openvz_driver.c b/src/openvz/openvz_driver.c index 4af28e9..c9a40e7 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 ddbc103..aaac1da 100644 --- a/src/phyp/phyp_driver.c +++ b/src/phyp/phyp_driver.c @@ -3999,6 +3999,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 48fe266..a8e4db9 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -6853,6 +6853,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 9310ddf..f5b7c55 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -11227,6 +11227,7 @@ static virDriver remote_driver = { remoteDomainSave, /* domainSave */ remoteDomainRestore, /* domainRestore */ remoteDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ remoteDomainSetVcpus, /* domainSetVcpus */ remoteDomainSetVcpusFlags, /* domainSetVcpusFlags */ remoteDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/test/test_driver.c b/src/test/test_driver.c index 17f5ad9..5a33054 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -5374,6 +5374,7 @@ static virDriver testDriver = { testDomainSave, /* domainSave */ testDomainRestore, /* domainRestore */ testDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ testSetVcpus, /* domainSetVcpus */ testDomainSetVcpusFlags, /* domainSetVcpusFlags */ testDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 33849a0..00ab62d 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -2180,6 +2180,7 @@ static virDriver umlDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainSetVcpus */ NULL, /* domainSetVcpusFlags */ NULL, /* domainGetVcpusFlags */ diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 0fbfba5..a87c6a5 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -8564,6 +8564,7 @@ virDriver NAME(Driver) = { vboxDomainSave, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ vboxDomainSetVcpus, /* domainSetVcpus */ vboxDomainSetVcpusFlags, /* domainSetVcpusFlags */ vboxDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/vmware/vmware_driver.c b/src/vmware/vmware_driver.c index b5e416b..926eca0 100644 --- a/src/vmware/vmware_driver.c +++ b/src/vmware/vmware_driver.c @@ -934,6 +934,7 @@ static virDriver vmwareDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainSetVcpus */ NULL, /* domainSetVcpusFlags */ NULL, /* domainGetVcpusFlags */ diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c index 9f47722..a883c15 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -2068,6 +2068,7 @@ static virDriver xenUnifiedDriver = { xenUnifiedDomainSave, /* domainSave */ xenUnifiedDomainRestore, /* domainRestore */ xenUnifiedDomainCoreDump, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenUnifiedDomainSetVcpus, /* domainSetVcpus */ xenUnifiedDomainSetVcpusFlags, /* domainSetVcpusFlags */ xenUnifiedDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/xen/xen_driver.h b/src/xen/xen_driver.h index 58b8561..8fb5832 100644 --- a/src/xen/xen_driver.h +++ b/src/xen/xen_driver.h @@ -93,6 +93,7 @@ struct xenUnifiedDriver { virDrvDomainSave domainSave; virDrvDomainRestore domainRestore; virDrvDomainCoreDump domainCoreDump; + virDrvDomainScreenshot domainScreenshot; virDrvDomainPinVcpu domainPinVcpu; virDrvDomainGetVcpus domainGetVcpus; virDrvListDefinedDomains listDefinedDomains; diff --git a/src/xen/xen_hypervisor.c b/src/xen/xen_hypervisor.c index 8a9dae5..7fccaba 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 5a997e6..d072fbb 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 04122ba..1ad074f 100644 --- a/src/xen/xend_internal.c +++ b/src/xen/xend_internal.c @@ -3866,6 +3866,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 9225808..889ac6c 100644 --- a/src/xen/xm_internal.c +++ b/src/xen/xm_internal.c @@ -103,6 +103,7 @@ struct xenUnifiedDriver xenXMDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ xenXMDomainPinVcpu, /* domainPinVcpu */ NULL, /* domainGetVcpus */ xenXMListDefinedDomains, /* listDefinedDomains */ diff --git a/src/xen/xs_internal.c b/src/xen/xs_internal.c index d9aad1f..c17d5f9 100644 --- a/src/xen/xs_internal.c +++ b/src/xen/xs_internal.c @@ -65,6 +65,7 @@ struct xenUnifiedDriver xenStoreDriver = { NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ + NULL, /* domainScreenshot */ NULL, /* domainPinVcpu */ NULL, /* domainGetVcpus */ NULL, /* listDefinedDomains */ diff --git a/src/xenapi/xenapi_driver.c b/src/xenapi/xenapi_driver.c index 60b23c7..dd4d13d 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.4

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

* daemon/remote.c, src/remote/remote_driver.c: Implementation of screenshot API * src/remote/remote_protocol.x: Wire protocol definition * daemon/remote_dispatch_args.h, daemon/remote_dispatch_prototypes.h, daemon/remote_dispatch_ret.h, daemon/remote_dispatch_table.h, src/remote/remote_protocol.c, src/remote/remote_protocol.h: Re-generate --- daemon/remote.c | 61 +++++++++++++++++++++++++++++++++++ daemon/remote_dispatch_args.h | 1 + daemon/remote_dispatch_prototypes.h | 8 ++++ daemon/remote_dispatch_ret.h | 1 + daemon/remote_dispatch_table.h | 5 +++ src/remote/remote_driver.c | 39 +++++++++++++++++++++- src/remote/remote_protocol.c | 20 +++++++++++ src/remote/remote_protocol.h | 16 +++++++++ src/remote/remote_protocol.x | 12 ++++++- 9 files changed, 161 insertions(+), 2 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index dd85ef1..c289e52 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -2318,6 +2318,67 @@ remoteDispatchDomainCoreDump (struct qemud_server *server ATTRIBUTE_UNUSED, } 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) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + stream = remoteCreateClientStream(conn, hdr); + if (!stream) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + mime = virDomainScreenshot(dom, stream->st, args->flags); + if (mime == NULL) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + if (remoteAddClientStream(client, stream, 1) < 0) { + remoteDispatchConnError(rerr, conn); + virStreamAbort(stream->st); + goto cleanup; + } + + 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; + +cleanup: + virDomainFree(dom); + if (stream && rv != 0) + remoteFreeClientStream(client, stream); + return rv; +} + +static int remoteDispatchDomainSetAutostart (struct qemud_server *server ATTRIBUTE_UNUSED, struct qemud_client *client ATTRIBUTE_UNUSED, virConnectPtr conn, diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h index f9537d7..2be8003 100644 --- a/daemon/remote_dispatch_args.h +++ b/daemon/remote_dispatch_args.h @@ -178,3 +178,4 @@ remote_domain_migrate_set_max_speed_args val_remote_domain_migrate_set_max_speed_args; remote_storage_vol_upload_args val_remote_storage_vol_upload_args; remote_storage_vol_download_args val_remote_storage_vol_download_args; + remote_domain_screenshot_args val_remote_domain_screenshot_args; diff --git a/daemon/remote_dispatch_prototypes.h b/daemon/remote_dispatch_prototypes.h index 18bf41d..582a8dd 100644 --- a/daemon/remote_dispatch_prototypes.h +++ b/daemon/remote_dispatch_prototypes.h @@ -546,6 +546,14 @@ static int remoteDispatchDomainSave( remote_error *err, remote_domain_save_args *args, void *ret); +static int remoteDispatchDomainScreenshot( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_screenshot_args *args, + remote_domain_screenshot_ret *ret); static int remoteDispatchDomainSetAutostart( struct qemud_server *server, struct qemud_client *client, diff --git a/daemon/remote_dispatch_ret.h b/daemon/remote_dispatch_ret.h index 114e832..1ecb8a9 100644 --- a/daemon/remote_dispatch_ret.h +++ b/daemon/remote_dispatch_ret.h @@ -140,3 +140,4 @@ remote_domain_is_updated_ret val_remote_domain_is_updated_ret; remote_get_sysinfo_ret val_remote_get_sysinfo_ret; remote_domain_get_blkio_parameters_ret val_remote_domain_get_blkio_parameters_ret; + remote_domain_screenshot_ret val_remote_domain_screenshot_ret; diff --git a/daemon/remote_dispatch_table.h b/daemon/remote_dispatch_table.h index b39f7c2..b058db3 100644 --- a/daemon/remote_dispatch_table.h +++ b/daemon/remote_dispatch_table.h @@ -1052,3 +1052,8 @@ .args_filter = (xdrproc_t) xdr_remote_storage_vol_download_args, .ret_filter = (xdrproc_t) xdr_void, }, +{ /* DomainScreenshot => 210 */ + .fn = (dispatch_fn) remoteDispatchDomainScreenshot, + .args_filter = (xdrproc_t) xdr_remote_domain_screenshot_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_screenshot_ret, +}, diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index f5b7c55..91a7651 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -11057,6 +11057,43 @@ remoteDomainEventQueueFlush(int timer ATTRIBUTE_UNUSED, void *opaque) remoteDriverUnlock(priv); } +static char * +remoteDomainScreenshot (virDomainPtr domain, + virStreamPtr st, + unsigned int flags) +{ + struct private_data *priv = domain->conn->privateData; + struct private_stream_data *privst = NULL; + remote_domain_screenshot_args args; + remote_domain_screenshot_ret ret; + char *rv = NULL; + + remoteDriverLock(priv); + + if (!(privst = remoteStreamOpen(st, + REMOTE_PROC_DOMAIN_SCREENSHOT, + priv->counter))) + goto done; + + st->driver = &remoteStreamDrv; + st->privateData = privst; + + make_nonnull_domain(&args.dom, domain); + args.flags = flags; + + memset(&ret, 0, sizeof(ret)); + if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SCREENSHOT, + (xdrproc_t) xdr_remote_domain_screenshot_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_screenshot_ret, (char *) &ret) == -1) + goto done; + + rv = ret.mime ? *ret.mime : NULL; + VIR_FREE(ret.mime); + +done: + remoteDriverUnlock(priv); + return rv; +} /* get_nonnull_domain and get_nonnull_network turn an on-wire * (name, uuid) pair into virDomainPtr or virNetworkPtr object. @@ -11227,7 +11264,7 @@ static virDriver remote_driver = { remoteDomainSave, /* domainSave */ remoteDomainRestore, /* domainRestore */ remoteDomainCoreDump, /* domainCoreDump */ - NULL, /* domainScreenshot */ + remoteDomainScreenshot, /* domainScreenshot */ remoteDomainSetVcpus, /* domainSetVcpus */ remoteDomainSetVcpusFlags, /* domainSetVcpusFlags */ remoteDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/remote/remote_protocol.c b/src/remote/remote_protocol.c index 5604371..a4deb67 100644 --- a/src/remote/remote_protocol.c +++ b/src/remote/remote_protocol.c @@ -1230,6 +1230,26 @@ xdr_remote_domain_core_dump_args (XDR *xdrs, remote_domain_core_dump_args *objp) } bool_t +xdr_remote_domain_screenshot_args (XDR *xdrs, remote_domain_screenshot_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->dom)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_screenshot_ret (XDR *xdrs, remote_domain_screenshot_ret *objp) +{ + + if (!xdr_remote_string (xdrs, &objp->mime)) + return FALSE; + return TRUE; +} + +bool_t xdr_remote_domain_dump_xml_args (XDR *xdrs, remote_domain_dump_xml_args *objp) { diff --git a/src/remote/remote_protocol.h b/src/remote/remote_protocol.h index d9bf151..d1b02d6 100644 --- a/src/remote/remote_protocol.h +++ b/src/remote/remote_protocol.h @@ -666,6 +666,17 @@ struct remote_domain_core_dump_args { }; typedef struct remote_domain_core_dump_args remote_domain_core_dump_args; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + u_int flags; +}; +typedef struct remote_domain_screenshot_args remote_domain_screenshot_args; + +struct remote_domain_screenshot_ret { + remote_string mime; +}; +typedef struct remote_domain_screenshot_ret remote_domain_screenshot_ret; + struct remote_domain_dump_xml_args { remote_nonnull_domain dom; int flags; @@ -2413,6 +2424,7 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207, REMOTE_PROC_STORAGE_VOL_UPLOAD = 208, REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209, + REMOTE_PROC_DOMAIN_SCREENSHOT = 210, }; typedef enum remote_procedure remote_procedure; @@ -2541,6 +2553,8 @@ extern bool_t xdr_remote_domain_get_info_ret (XDR *, remote_domain_get_info_ret extern bool_t xdr_remote_domain_save_args (XDR *, remote_domain_save_args*); extern bool_t xdr_remote_domain_restore_args (XDR *, remote_domain_restore_args*); extern bool_t xdr_remote_domain_core_dump_args (XDR *, remote_domain_core_dump_args*); +extern bool_t xdr_remote_domain_screenshot_args (XDR *, remote_domain_screenshot_args*); +extern bool_t xdr_remote_domain_screenshot_ret (XDR *, remote_domain_screenshot_ret*); extern bool_t xdr_remote_domain_dump_xml_args (XDR *, remote_domain_dump_xml_args*); extern bool_t xdr_remote_domain_dump_xml_ret (XDR *, remote_domain_dump_xml_ret*); extern bool_t xdr_remote_domain_migrate_prepare_args (XDR *, remote_domain_migrate_prepare_args*); @@ -2898,6 +2912,8 @@ extern bool_t xdr_remote_domain_get_info_ret (); extern bool_t xdr_remote_domain_save_args (); extern bool_t xdr_remote_domain_restore_args (); extern bool_t xdr_remote_domain_core_dump_args (); +extern bool_t xdr_remote_domain_screenshot_args (); +extern bool_t xdr_remote_domain_screenshot_ret (); extern bool_t xdr_remote_domain_dump_xml_args (); extern bool_t xdr_remote_domain_dump_xml_ret (); extern bool_t xdr_remote_domain_migrate_prepare_args (); diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 675eccd..6e5d09a 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -714,6 +714,15 @@ struct remote_domain_core_dump_args { int flags; }; +struct remote_domain_screenshot_args { + remote_nonnull_domain dom; + unsigned int flags; +}; + +struct remote_domain_screenshot_ret { + remote_string mime; +}; + struct remote_domain_dump_xml_args { remote_nonnull_domain dom; int flags; @@ -2176,7 +2185,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_GET_BLKIO_PARAMETERS = 206, REMOTE_PROC_DOMAIN_MIGRATE_SET_MAX_SPEED = 207, REMOTE_PROC_STORAGE_VOL_UPLOAD = 208, - REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209 + REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 209, + REMOTE_PROC_DOMAIN_SCREENSHOT = 210 /* * Notice how the entries are grouped in sets of 10 ? -- 1.7.4

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

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 | 25 ++++++++++++++++++++----- 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, 44 insertions(+), 15 deletions(-) diff --git a/src/fdstream.c b/src/fdstream.c index 3475bfd..5198a36 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 }; @@ -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 ed40cb2..5bf3212 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2782,7 +2782,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 a8e4db9..9f0bad4 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -6803,7 +6803,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 1ea5d12..3a9a5f4 100644 --- a/src/storage/storage_driver.c +++ b/src/storage/storage_driver.c @@ -1588,7 +1588,7 @@ storageVolumeDownload(virStorageVolPtr obj, if (virFDStreamOpenFile(stream, vol->target.path, offset, length, - O_RDONLY) < 0) + O_RDONLY, false) < 0) goto out; ret = 0; @@ -1652,7 +1652,7 @@ storageVolumeUpload(virStorageVolPtr obj, if (virFDStreamOpenFile(stream, vol->target.path, offset, length, - O_WRONLY) < 0) + O_WRONLY, false) < 0) goto out; ret = 0; diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 00ab62d..4b81c43 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -2130,7 +2130,8 @@ umlDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, + 0, 0, O_RDWR, false) < 0) goto cleanup; ret = 0; diff --git a/src/util/iohelper.c b/src/util/iohelper.c index d5821b9..f519d5a 100644 --- a/src/util/iohelper.c +++ b/src/util/iohelper.c @@ -146,6 +146,7 @@ int main(int argc, char **argv) unsigned long long length; int flags; int mode; + unsigned int delete; if (setlocale(LC_ALL, "") == NULL || bindtextdomain(PACKAGE, LOCALEDIR) == NULL || @@ -161,8 +162,8 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - if (argc != 6) { - fprintf(stderr, _("%s: syntax FILENAME FLAGS MODE OFFSET LENGTH\n"), argv[0]); + if (argc != 7) { + fprintf(stderr, _("%s: syntax FILENAME FLAGS MODE OFFSET LENGTH DELETE\n"), argv[0]); exit(EXIT_FAILURE); } @@ -186,10 +187,17 @@ int main(int argc, char **argv) fprintf(stderr, _("%s: malformed file length %s"), argv[0], argv[5]); exit(EXIT_FAILURE); } + if (virStrToLong_ui(argv[6], NULL, 10, &delete) < 0) { + fprintf(stderr, _("%s: malformed delete flag %s"), argv[0],argv[6]); + exit(EXIT_FAILURE); + } if (runIO(path, flags, mode, offset, length) < 0) goto error; + if (delete) + unlink(path); + return 0; error: diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c index a883c15..358c307 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -2019,7 +2019,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.4

On 04/05/2011 10:12 AM, 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'm not convinced about this commit. We should instead be fixing iohelper to receive its file by fd inheritance, so that it doesn't have to open() in the first place. Then you don't need iohelper to worry about unlink(). -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Tue, Apr 05, 2011 at 10:55:40AM -0600, Eric Blake wrote:
On 04/05/2011 10:12 AM, 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'm not convinced about this commit. We should instead be fixing iohelper to receive its file by fd inheritance, so that it doesn't have to open() in the first place. Then you don't need iohelper to worry about unlink().
My intention was that iohelper could also do uid/gid changes in the future to cope with opening files on root squashing NFS. For that we'd want it to be doing the open, not libvirtd Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 04/06/2011 02:59 AM, Daniel P. Berrange wrote:
On Tue, Apr 05, 2011 at 10:55:40AM -0600, Eric Blake wrote:
On 04/05/2011 10:12 AM, 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'm not convinced about this commit. We should instead be fixing iohelper to receive its file by fd inheritance, so that it doesn't have to open() in the first place. Then you don't need iohelper to worry about unlink().
My intention was that iohelper could also do uid/gid changes in the future to cope with opening files on root squashing NFS. For that we'd want it to be doing the open, not libvirtd
Even if the parent process does virFileOpenAs? I think we've already got all the uid/gid changes covered in the parent process, without having to offload that into the child and coordinate a second open(); and even if we don't, fchmod/fchown are better than a second open()/chmod()/chown(). -- 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 stubs --- src/qemu/qemu_driver.c | 80 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 20 ++++++++++ src/qemu/qemu_monitor.h | 3 ++ src/qemu/qemu_monitor_json.c | 23 ++++++++++++ src/qemu/qemu_monitor_json.h | 4 ++ src/qemu/qemu_monitor_text.c | 31 ++++++++++++++++ src/qemu/qemu_monitor_text.h | 2 + 7 files changed, 162 insertions(+), 1 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 9f0bad4..1ea2174 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2384,6 +2384,84 @@ cleanup: return ret; } +static char * +qemuDomainScreenshot(virDomainPtr dom, + virStreamPtr st, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = dom->conn->privateData; + virDomainObjPtr vm; + qemuDomainObjPrivatePtr priv; + char *tmp = NULL; + int tmp_fd = -1; + char *ret = NULL; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, dom->uuid); + qemuDriverUnlock(driver); + + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(dom->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain matching uuid '%s'"), uuidstr); + goto cleanup; + } + + priv = vm->privateData; + + if (qemuDomainObjBeginJob(vm) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } + + if (virAsprintf(&tmp, "%s/qemu.screendump.XXXXXX", driver->cacheDir) < 0) { + virReportOOMError(); + goto endjob; + } + + if ((tmp_fd = mkstemp(tmp)) == -1) { + virReportSystemError(errno, _("mkstemp(\"%s\") failed"), tmp); + goto endjob; + } + + qemuDomainObjEnterMonitor(vm); + if (qemuMonitorScreendump(priv->mon, tmp) < 0) { + qemuDomainObjExitMonitor(vm); + goto endjob; + } + qemuDomainObjExitMonitor(vm); + + if (VIR_CLOSE(tmp_fd) < 0) { + virReportSystemError(errno, _("unable to close %s"), tmp); + goto endjob; + } + + if (virFDStreamOpenFile(st, tmp, 0, 0, O_RDONLY, true) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to open stream")); + goto endjob; + } + + ret = strdup("image/x-portable-pixmap; charset=binary"); + +endjob: + VIR_FORCE_CLOSE(tmp_fd); + VIR_FREE(tmp); + + if (qemuDomainObjEndJob(vm) == 0) + vm = NULL; + +cleanup: + if (vm) + virDomainObjUnlock(vm); + return ret; +} + static void processWatchdogEvent(void *data, void *opaque) { int ret; @@ -6854,7 +6932,7 @@ static virDriver qemuDriver = { qemudDomainSave, /* domainSave */ qemuDomainRestore, /* domainRestore */ qemudDomainCoreDump, /* domainCoreDump */ - NULL, /* domainScreenshot */ + qemuDomainScreenshot, /* domainScreenshot */ qemudDomainSetVcpus, /* domainSetVcpus */ qemudDomainSetVcpusFlags, /* domainSetVcpusFlags */ qemudDomainGetVcpusFlags, /* domainGetVcpusFlags */ diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 2d28f8d..6834d29 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2228,3 +2228,23 @@ int qemuMonitorArbitraryCommand(qemuMonitorPtr mon, ret = qemuMonitorTextArbitraryCommand(mon, cmd, reply); return ret; } + +int qemuMonitorScreendump(qemuMonitorPtr mon, + const char *file) +{ + int ret; + + VIR_DEBUG("mon=%p, file=%s", mon, file); + + if (!mon) { + qemuReportError(VIR_ERR_INVALID_ARG,"%s", + _("monitor must not be NULL")); + return -1; + } + + if (mon->json) + ret = qemuMonitorJSONScreendump(mon, file); + else + ret = qemuMonitorTextScreendump(mon, file); + return ret; +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index c90219b..5c96f12 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -423,6 +423,9 @@ int qemuMonitorArbitraryCommand(qemuMonitorPtr mon, char **reply, bool hmp); +int qemuMonitorScreendump(qemuMonitorPtr mon, + const char *file); + /** * When running two dd process and using <> redirection, we need a * shell that will not truncate files. These two strings serve that diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 20a78e1..df45e4b 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2513,3 +2513,26 @@ cleanup: return ret; } + +int qemuMonitorJSONScreendump(qemuMonitorPtr mon, + const char *file) +{ + int ret; + virJSONValuePtr cmd, reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("screendump", + "s:filename", file, + NULL); + + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 086f0e1..bcfdd24 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -204,4 +204,8 @@ int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon, char **reply_str, bool hmp); +int qemuMonitorJSONScreendump(qemuMonitorPtr mon, + const char *file); + + #endif /* QEMU_MONITOR_JSON_H */ diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index 53781c8..f71cf2d 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -2628,3 +2628,34 @@ int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd, return ret; } + +/* Returns -1 on error, -2 if not supported */ +int qemuMonitorTextScreendump(qemuMonitorPtr mon, const char *file) +{ + char *cmd = NULL; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "screendump %s", file) < 0){ + virReportOOMError(); + goto cleanup; + } + + if (qemuMonitorHMPCommand(mon, cmd, &reply) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("taking screenshot failed")); + goto cleanup; + } + + if (strstr(reply, "unknown command:")) { + ret = -2; + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(reply); + VIR_FREE(cmd); + return ret; +} diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index 0838a2b..773d33c 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -198,4 +198,6 @@ int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd, char **reply); +int qemuMonitorTextScreendump(qemuMonitorPtr mon, const char *file); + #endif /* QEMU_MONITOR_TEXT_H */ -- 1.7.4

* src/vbox/vbox_tmpl.c: New vboxDomainScreenshot() function --- src/vbox/vbox_tmpl.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 113 insertions(+), 0 deletions(-) diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index a87c6a5..53a3a88 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 @@ -8522,6 +8528,109 @@ static char *vboxStorageVolGetPath(virStorageVolPtr vol) { return ret; } +#if VBOX_API_VERSION == 4000 +static char * +vboxDomainScreenshot(virDomainPtr dom, virStreamPtr st, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, char *, NULL); + IConsole *console = NULL; + vboxIID iid = VBOX_IID_INITIALIZER; + IMachine *machine = NULL; + nsresult rc; + char *tmp; + int tmp_fd = -1; + + vboxIIDFromUUID(&iid, dom->uuid); + rc = VBOX_OBJECT_GET_MACHINE(iid.value, &machine); + if (NS_FAILED(rc)) { + vboxError(VIR_ERR_NO_DOMAIN, "%s", + _("no domain with matching uuid")); + return NULL; + } + + if (virAsprintf(&tmp, "%s/cache/libvirt/vbox.screendump.XXXXXX", LOCALSTATEDIR) < 0) { + virReportOOMError(); + return NULL; + } + + if ((tmp_fd = mkstemp(tmp)) == -1) { + virReportSystemError(errno, _("mkstemp(\"%s\") failed"), tmp); + VIR_FREE(tmp); + return NULL; + } + + + rc = VBOX_SESSION_OPEN_EXISTING(iid.value, machine); + if (NS_SUCCEEDED(rc)) { + rc = data->vboxSession->vtbl->GetConsole(data->vboxSession, &console); + if (NS_SUCCEEDED(rc) && console) { + IDisplay *display = NULL; + + console->vtbl->GetDisplay(console, &display); + + if (display) { + PRUint32 width, height, bitsPerPixel; + PRUint32 screenDataSize; + PRUint8 *screenData; + + rc = display->vtbl->GetScreenResolution(display, 0, + &width, &height, + &bitsPerPixel); + + if (NS_FAILED(rc) || !width || !height) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to get screen resolution")); + goto endjob; + } + + rc = display->vtbl->TakeScreenShotPNGToArray(display, 0, + width, height, + &screenDataSize, + &screenData); + if (NS_FAILED(rc)) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("failed to take screenshot")); + goto endjob; + } + + if (safewrite(tmp_fd, (char *) screenData, + screenDataSize) < 0) { + virReportSystemError(errno, _("unable to write data " + "to '%s'"), tmp); + goto endjob; + } + + if (VIR_CLOSE(tmp_fd) < 0) { + virReportSystemError(errno, _("unable to close %s"), tmp); + goto endjob; + } + + if (virFDStreamOpenFile(st, tmp, 0, 0, O_RDONLY, true) < 0) { + vboxError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to open stream")); + goto endjob; + } + + ret = strdup("image/png; charset=binary"); + +endjob: + VIR_FREE(screenData); + VBOX_RELEASE(display); + } + VBOX_RELEASE(console); + } + VBOX_SESSION_CLOSE(); + } + + VIR_FORCE_CLOSE(tmp_fd); + VIR_FREE(tmp); + VBOX_RELEASE(machine); + vboxIIDUnalloc(&iid); + return ret; +} +#endif /* VBOX_API_VERSION == 4000 */ + /** * Function Tables */ @@ -8564,7 +8673,11 @@ virDriver NAME(Driver) = { vboxDomainSave, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ +#if VBOX_API_VERSION == 4000 + vboxDomainScreenshot, /* domainScreenshot */ +#else NULL, /* domainScreenshot */ +#endif vboxDomainSetVcpus, /* domainSetVcpus */ vboxDomainSetVcpusFlags, /* domainSetVcpusFlags */ vboxDomainGetVcpusFlags, /* domainGetVcpusFlags */ -- 1.7.4
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Michal Privoznik