RHBZ: 800338 Adds a new capability to qemu, QEMU_CAPS_SCREENDUMP_ASYNC, available if the qmp command "screendump-async" exists. If that cap exists qemuDomainScreenshot uses it. The implementation consists of a hash from filename to struct holding the stream and temporary fd. The fd is closed and the stream is written to (in reverse order) by the completion callback, qemuProcessScreenshotComplete. Note: in qemuDomainScreenshot I don't check for an existing entry in the screenshots hash table because we the key is a temporary filename, produced by mkstemp, and it's only unlinked at qemuProcessScreenshotComplete. For testing you need to apply the following patches (they are still pending review on qemu-devel): http://patchwork.ozlabs.org/patch/144706/ http://patchwork.ozlabs.org/patch/144705/ http://patchwork.ozlabs.org/patch/144704/ Signed-off-by: Alon Levy <alevy@xxxxxxxxxx> --- src/qemu/qemu_capabilities.c | 1 + src/qemu/qemu_capabilities.h | 1 + src/qemu/qemu_domain.c | 6 ++++++ src/qemu/qemu_domain.h | 12 ++++++++++++ src/qemu/qemu_driver.c | 42 +++++++++++++++++++++++++++++++++++------- src/qemu/qemu_monitor.c | 26 ++++++++++++++++++++++++++ src/qemu/qemu_monitor.h | 8 ++++++++ src/qemu/qemu_monitor_json.c | 39 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 3 +++ src/qemu/qemu_process.c | 29 +++++++++++++++++++++++++++++ 10 files changed, 160 insertions(+), 7 deletions(-) diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 64a4546..57771ff 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -154,6 +154,7 @@ VIR_ENUM_IMPL(qemuCaps, QEMU_CAPS_LAST, "drive-iotune", /* 85 */ "system_wakeup", "scsi-disk.channel", + "screendump-async", ); struct qemu_feature_flags { diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index db584ce..24d620d 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -122,6 +122,7 @@ enum qemuCapsFlags { QEMU_CAPS_DRIVE_IOTUNE = 85, /* -drive bps= and friends */ QEMU_CAPS_WAKEUP = 86, /* system_wakeup monitor command */ QEMU_CAPS_SCSI_DISK_CHANNEL = 87, /* Is scsi-disk.channel available? */ + QEMU_CAPS_SCREENDUMP_ASYNC = 88, /* screendump-async qmp command */ QEMU_CAPS_LAST, /* this must always be the last item */ }; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 2fed91e..acf56c4 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -181,6 +181,10 @@ qemuDomainObjFreeJob(qemuDomainObjPrivatePtr priv) ignore_value(virCondDestroy(&priv->job.asyncCond)); } +static void +freeScreenshot(void *payload, const void *name ATTRIBUTE_UNUSED) { + VIR_FREE(payload); +} static void *qemuDomainObjPrivateAlloc(void) { @@ -196,6 +200,8 @@ static void *qemuDomainObjPrivateAlloc(void) goto error; priv->migMaxBandwidth = QEMU_DOMAIN_DEFAULT_MIG_BANDWIDTH_MAX; + priv->screenshots = virHashCreate(QEMU_DOMAIN_SCREENSHOTS_CONCURRENT_MAX, + freeScreenshot); return priv; diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 1333d8c..15721ec 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -40,6 +40,8 @@ # define QEMU_DOMAIN_DEFAULT_MIG_BANDWIDTH_MAX 32 +# define QEMU_DOMAIN_SCREENSHOTS_CONCURRENT_MAX 16 + # define JOB_MASK(job) (1 << (job - 1)) # define DEFAULT_JOB_MASK \ (JOB_MASK(QEMU_JOB_QUERY) | \ @@ -91,6 +93,14 @@ struct qemuDomainJobObj { virDomainJobInfo info; /* Async job progress data */ }; +struct _qemuScreenshotAsync { + virStreamPtr stream; /* stream to write results to */ + const char *filename; /* temporary file to read results from */ + int fd; /* handle to open temporary file */ +}; +typedef struct _qemuScreenshotAsync qemuScreenshotAsync; +typedef qemuScreenshotAsync *qemuScreenshotAsyncPtr; + typedef struct _qemuDomainPCIAddressSet qemuDomainPCIAddressSet; typedef qemuDomainPCIAddressSet *qemuDomainPCIAddressSetPtr; @@ -130,6 +140,8 @@ struct _qemuDomainObjPrivate { char *origname; virConsolesPtr cons; + + virHashTablePtr screenshots; }; struct qemuDomainWatchdogEvent diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 733df0a..e397bc3 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -3135,6 +3135,7 @@ qemuDomainScreenshot(virDomainPtr dom, int tmp_fd = -1; char *ret = NULL; bool unlink_tmp = false; + qemuScreenshotAsync *screenshot; virCheckFlags(0, NULL); @@ -3184,9 +3185,34 @@ qemuDomainScreenshot(virDomainPtr dom, virSecurityManagerSetSavedStateLabel(qemu_driver->securityManager, vm->def, tmp); qemuDomainObjEnterMonitor(driver, vm); - if (qemuMonitorScreendump(priv->mon, tmp) < 0) { - qemuDomainObjExitMonitor(driver, vm); - goto endjob; + if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_SCREENDUMP_ASYNC)) { + if (virHashSize(priv->screenshots) >= + QEMU_DOMAIN_SCREENSHOTS_CONCURRENT_MAX) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("too many ongoing screenshots")); + goto endjob; + } + if (VIR_ALLOC(screenshot) < 0) { + qemuReportError(VIR_ERR_NO_MEMORY, "%s", _("out of memory")); + goto endjob; + } + screenshot->fd = tmp_fd; + screenshot->filename = tmp; + screenshot->stream = st; + virHashAddEntry(priv->screenshots, tmp, screenshot); + if (qemuMonitorScreendumpAsync(priv->mon, tmp) < 0) { + qemuDomainObjExitMonitor(driver, vm); + goto endjob; + } + /* string and fd are freed by qmp event callback */ + tmp = NULL; + tmp_fd = -1; + unlink_tmp = false; + } else { + if (qemuMonitorScreendump(priv->mon, tmp) < 0) { + qemuDomainObjExitMonitor(driver, vm); + goto endjob; + } } qemuDomainObjExitMonitor(driver, vm); @@ -3195,10 +3221,12 @@ qemuDomainScreenshot(virDomainPtr dom, goto endjob; } - if (virFDStreamOpenFile(st, tmp, 0, 0, O_RDONLY) < 0) { - qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("unable to open stream")); - goto endjob; + if (!qemuCapsGet(priv->qemuCaps, QEMU_CAPS_SCREENDUMP_ASYNC)) { + if (virFDStreamOpenFile(st, tmp, 0, 0, O_RDONLY) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to open stream")); + goto endjob; + } } ret = strdup("image/x-portable-pixmap"); diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 1da73f6..0df63e7 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -1054,6 +1054,18 @@ int qemuMonitorEmitBlockJob(qemuMonitorPtr mon, } +int qemuMonitorEmitScreenDumpComplete(qemuMonitorPtr mon, + const char *filename) +{ + int ret = -1; + VIR_DEBUG("mon=%p", mon); + + QEMU_MONITOR_CALLBACK(mon, ret, domainScreenshotComplete, mon->vm, + filename); + return ret; +} + + int qemuMonitorSetCapabilities(qemuMonitorPtr mon, virBitmapPtr qemuCaps) @@ -2710,6 +2722,20 @@ int qemuMonitorScreendump(qemuMonitorPtr mon, return ret; } +int qemuMonitorScreendumpAsync(qemuMonitorPtr mon, + const char *file) +{ + VIR_DEBUG("mon=%p, file=%s", mon, file); + + if (!mon) { + qemuReportError(VIR_ERR_INVALID_ARG,"%s", + _("monitor must not be NULL")); + return -1; + } + + return qemuMonitorJSONScreendumpAsync(mon, file); +} + int qemuMonitorBlockJob(qemuMonitorPtr mon, const char *device, const char *base, diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index b1c956c..abe977c 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -124,6 +124,9 @@ struct _qemuMonitorCallbacks { const char *diskAlias, int type, int status); + int (*domainScreenshotComplete)(qemuMonitorPtr mon, + virDomainObjPtr vm, + const char *filename); }; @@ -195,6 +198,8 @@ int qemuMonitorEmitBlockJob(qemuMonitorPtr mon, const char *diskAlias, int type, int status); +int qemuMonitorEmitScreenDumpComplete(qemuMonitorPtr mon, + const char *filename); @@ -505,6 +510,9 @@ int qemuMonitorInjectNMI(qemuMonitorPtr mon); int qemuMonitorScreendump(qemuMonitorPtr mon, const char *file); +int qemuMonitorScreendumpAsync(qemuMonitorPtr mon, + const char *file); + int qemuMonitorSendKey(qemuMonitorPtr mon, unsigned int holdtime, unsigned int *keycodes, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index dc67b4b..79ec6ba 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -59,6 +59,7 @@ static void qemuMonitorJSONHandleVNCConnect(qemuMonitorPtr mon, virJSONValuePtr static void qemuMonitorJSONHandleVNCInitialize(qemuMonitorPtr mon, virJSONValuePtr data); static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValuePtr data); static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr data); +static void qemuMonitorJSONHandleScreenDumpComplete(qemuMonitorPtr mon, virJSONValuePtr data); static struct { const char *type; @@ -75,6 +76,7 @@ static struct { { "VNC_INITIALIZED", qemuMonitorJSONHandleVNCInitialize, }, { "VNC_DISCONNECTED", qemuMonitorJSONHandleVNCDisconnect, }, { "BLOCK_JOB_COMPLETED", qemuMonitorJSONHandleBlockJob, }, + { "SCREEN_DUMP_COMPLETE", qemuMonitorJSONHandleScreenDumpComplete, }, }; @@ -725,6 +727,16 @@ out: qemuMonitorEmitBlockJob(mon, device, type, status); } +static void qemuMonitorJSONHandleScreenDumpComplete(qemuMonitorPtr mon, + virJSONValuePtr data) +{ + const char *filename; + + if ((filename = virJSONValueObjectGetString(data, "filename")) == NULL) { + VIR_WARN("missing filename in screen dump complete event"); + } + qemuMonitorEmitScreenDumpComplete(mon, filename); +} int qemuMonitorJSONHumanCommandWithFd(qemuMonitorPtr mon, @@ -836,6 +848,9 @@ qemuMonitorJSONCheckCommands(qemuMonitorPtr mon, if (STREQ(name, "system_wakeup")) qemuCapsSet(qemuCaps, QEMU_CAPS_WAKEUP); + + if (STREQ(name, "screendump-async")) + qemuCapsSet(qemuCaps, QEMU_CAPS_SCREENDUMP_ASYNC); } ret = 0; @@ -3135,6 +3150,30 @@ int qemuMonitorJSONScreendump(qemuMonitorPtr mon, return ret; } +int qemuMonitorJSONScreendumpAsync(qemuMonitorPtr mon, + const char *file) +{ + int ret; + virJSONValuePtr cmd, reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("screendump-async", + "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; +} + + static int qemuMonitorJSONGetBlockJobInfoOne(virJSONValuePtr entry, const char *device, virDomainBlockJobInfoPtr info) diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 0932a2c..74acce1 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -244,6 +244,9 @@ int qemuMonitorJSONSendKey(qemuMonitorPtr mon, int qemuMonitorJSONScreendump(qemuMonitorPtr mon, const char *file); +int qemuMonitorJSONScreendumpAsync(qemuMonitorPtr mon, + const char *file); + int qemuMonitorJSONBlockJob(qemuMonitorPtr mon, const char *device, const char *base, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 7b99814..7985a37 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -47,6 +47,7 @@ #include "datatypes.h" #include "logging.h" +#include "fdstream.h" #include "virterror_internal.h" #include "memory.h" #include "hooks.h" @@ -918,6 +919,33 @@ qemuProcessHandleBlockJob(qemuMonitorPtr mon ATTRIBUTE_UNUSED, } static int +qemuProcessScreenshotComplete(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + virDomainObjPtr vm, + const char *filename) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + qemuScreenshotAsyncPtr screenshot; + int ret = 0; + + if ((screenshot = virHashLookup(priv->screenshots, filename)) == NULL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("got screendump completion event for wrong filename")); + ret = -1; + goto end; + } + if (virFDStreamOpenFile(screenshot->stream, filename, 0, 0, O_RDONLY) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("unable to open stream")); + ret = -1; + } +end: + VIR_FORCE_CLOSE(screenshot->fd); + VIR_FREE(screenshot->filename); + virHashRemoveEntry(priv->screenshots, filename); + return ret; +} + +static int qemuProcessHandleGraphics(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, int phase, @@ -1034,6 +1062,7 @@ static qemuMonitorCallbacks monitorCallbacks = { .domainIOError = qemuProcessHandleIOError, .domainGraphics = qemuProcessHandleGraphics, .domainBlockJob = qemuProcessHandleBlockJob, + .domainScreenshotComplete = qemuProcessScreenshotComplete, }; static int -- 1.7.9.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list