[PATCH 10/12] hyperv: implement domainScreenshot

Matt Coleman mcoleman at datto.com
Fri Jan 22 20:18:41 UTC 2021


Signed-off-by: Matt Coleman <matt at datto.com>
---
 src/hyperv/hyperv_driver.c            | 207 ++++++++++++++++++++++++++
 src/hyperv/hyperv_wmi_classes.h       |  21 +++
 src/hyperv/hyperv_wmi_generator.input | 163 ++++++++++++++++++++
 3 files changed, 391 insertions(+)

diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c
index 22cfe6657d..0fe01f13b5 100644
--- a/src/hyperv/hyperv_driver.c
+++ b/src/hyperv/hyperv_driver.c
@@ -22,6 +22,8 @@
 
 #include <config.h>
 
+#include <fcntl.h>
+
 #include "internal.h"
 #include "datatypes.h"
 #include "virdomainobjlist.h"
@@ -38,6 +40,8 @@
 #include "virstring.h"
 #include "virkeycode.h"
 #include "domain_conf.h"
+#include "virfdstream.h"
+#include "virfile.h"
 
 #define VIR_FROM_THIS VIR_FROM_HYPERV
 
@@ -294,6 +298,73 @@ hypervCapsInit(hypervPrivate *priv)
     return NULL;
 }
 
+
+static int
+hypervGetVideoResolution(hypervPrivate *priv,
+                         char *vm_uuid,
+                         int *xRes,
+                         int *yRes,
+                         bool fallback)
+{
+    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
+    g_autoptr(Msvm_S3DisplayController) s3Display = NULL;
+    g_autoptr(Msvm_SyntheticDisplayController) synthetic = NULL;
+    g_autoptr(Msvm_VideoHead) heads = NULL;
+    const char *wmiClass = NULL;
+    char *deviceId = NULL;
+    g_autofree char *enabledStateString = NULL;
+
+    if (fallback) {
+        wmiClass = "Msvm_S3DisplayController";
+
+        virBufferEscapeSQL(&query,
+                           MSVM_S3DISPLAYCONTROLLER_WQL_SELECT "WHERE SystemName = '%s'",
+                           vm_uuid);
+
+        if (hypervGetWmiClass(Msvm_S3DisplayController, &s3Display) < 0 || !s3Display)
+            return -1;
+
+        deviceId = s3Display->data->DeviceID;
+    } else {
+        wmiClass = "Msvm_SyntheticDisplayController";
+
+        virBufferEscapeSQL(&query,
+                           MSVM_SYNTHETICDISPLAYCONTROLLER_WQL_SELECT "WHERE SystemName = '%s'",
+                           vm_uuid);
+
+        if (hypervGetWmiClass(Msvm_SyntheticDisplayController, &synthetic) < 0 || !synthetic)
+            return -1;
+
+        deviceId = synthetic->data->DeviceID;
+    }
+
+    virBufferFreeAndReset(&query);
+
+    virBufferAsprintf(&query,
+                      "ASSOCIATORS OF {%s."
+                      "CreationClassName='%s',"
+                      "DeviceID='%s',"
+                      "SystemCreationClassName='Msvm_ComputerSystem',"
+                      "SystemName='%s'"
+                      "} WHERE AssocClass = Msvm_VideoHeadOnController "
+                      "ResultClass = Msvm_VideoHead",
+                      wmiClass, wmiClass, deviceId, vm_uuid);
+
+    if (hypervGetWmiClass(Msvm_VideoHead, &heads) < 0)
+        return -1;
+
+    enabledStateString = g_strdup_printf("%d", CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED);
+    if (heads && STREQ(heads->data->EnabledState, enabledStateString)) {
+        *xRes = heads->data->CurrentHorizontalResolution;
+        *yRes = heads->data->CurrentVerticalResolution;
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
 /*
  * Virtual device functions
  */
@@ -2322,6 +2393,141 @@ hypervDomainGetState(virDomainPtr domain, int *state, int *reason,
 }
 
 
+static char *
+hypervDomainScreenshot(virDomainPtr domain,
+                       virStreamPtr stream,
+                       unsigned int screen G_GNUC_UNUSED,
+                       unsigned int flags)
+{
+    char uuid_string[VIR_UUID_STRING_BUFLEN];
+    hypervPrivate *priv = domain->conn->privateData;
+    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
+    g_autoptr(hypervInvokeParamsList) params = NULL;
+    g_auto(WsXmlDocH) ret_doc = NULL;
+    int xRes = 640;
+    int yRes = 480;
+    g_autofree char *width = NULL;
+    g_autofree char *height = NULL;
+    g_autofree char *imageDataText = NULL;
+    g_autofree unsigned char *imageDataBuffer = NULL;
+    size_t imageDataBufferSize;
+    const char *temporaryDirectory = NULL;
+    g_autofree char *temporaryFile = NULL;
+    g_autofree uint8_t *ppmBuffer = NULL;
+    char *result = NULL;
+    const char *xpath = "/s:Envelope/s:Body/p:GetVirtualSystemThumbnailImage_OUTPUT/p:ImageData";
+    int pixelCount;
+    int pixelByteCount;
+    size_t i = 0;
+    int fd = -1;
+    bool unlinkTemporaryFile = false;
+
+    virCheckFlags(0, NULL);
+
+    virUUIDFormat(domain->uuid, uuid_string);
+
+    /* Hyper-V Generation 1 VMs use two video heads:
+     *  - S3DisplayController is used for early boot screens.
+     *  - SyntheticDisplayController takes over when the guest OS initializes its video driver.
+     *
+     * This attempts to get the resolution from the SyntheticDisplayController first.
+     * If that fails, it falls back to S3DisplayController. */
+    if (hypervGetVideoResolution(priv, uuid_string, &xRes, &yRes, false) < 0) {
+        if (hypervGetVideoResolution(priv, uuid_string, &xRes, &yRes, true) < 0)
+            goto cleanup;
+    }
+
+    /* prepare params */
+    params = hypervCreateInvokeParamsList("GetVirtualSystemThumbnailImage",
+                                          MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR,
+                                          Msvm_VirtualSystemManagementService_WmiInfo);
+    if (!params)
+        goto cleanup;
+
+    width = g_strdup_printf("%d", xRes);
+    hypervAddSimpleParam(params, "WidthPixels", width);
+
+    height = g_strdup_printf("%d", yRes);
+    hypervAddSimpleParam(params, "HeightPixels", height);
+
+    virBufferAsprintf(&query,
+                      "ASSOCIATORS OF "
+                      "{Msvm_ComputerSystem.CreationClassName='Msvm_ComputerSystem',Name='%s'} "
+                      "WHERE ResultClass = Msvm_VirtualSystemSettingData",
+                      uuid_string);
+    hypervAddEprParam(params, "TargetSystem", &query, Msvm_VirtualSystemSettingData_WmiInfo);
+
+    /* capture and parse the screenshot */
+    if (hypervInvokeMethod(priv, &params, &ret_doc) < 0)
+        goto cleanup;
+
+    if (!ws_xml_get_soap_envelope(ret_doc)) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not retrieve screenshot"));
+        goto cleanup;
+    }
+
+    imageDataText = ws_xml_get_xpath_value(ret_doc, (char *)xpath);
+
+    if (!imageDataText) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to retrieve image data"));
+        goto cleanup;
+    }
+
+    imageDataBuffer = g_base64_decode(imageDataText, &imageDataBufferSize);
+
+    pixelCount = imageDataBufferSize / 2;
+    pixelByteCount = pixelCount * 3;
+
+    ppmBuffer = g_new0(uint8_t, pixelByteCount);
+
+    /* convert rgb565 to rgb888 */
+    for (i = 0; i < pixelCount; i++) {
+        const uint16_t pixel = imageDataBuffer[i * 2] + (imageDataBuffer[i * 2 + 1] << 8);
+        const uint16_t redMask = 0xF800;
+        const uint16_t greenMask = 0x7E0;
+        const uint16_t blueMask = 0x1F;
+        const uint8_t redFive = (pixel & redMask) >> 11;
+        const uint8_t greenSix = (pixel & greenMask) >> 5;
+        const uint8_t blueFive = pixel & blueMask;
+        ppmBuffer[i * 3] = (redFive * 527 + 23) >> 6;
+        ppmBuffer[i * 3 + 1] = (greenSix * 259 + 33) >> 6;
+        ppmBuffer[i * 3 + 2] = (blueFive * 527 + 23) >> 6;
+    }
+
+    temporaryDirectory = getenv("TMPDIR");
+    if (!temporaryDirectory)
+        temporaryDirectory = "/tmp";
+    temporaryFile = g_strdup_printf("%s/libvirt.hyperv.screendump.XXXXXX", temporaryDirectory);
+    if ((fd = g_mkstemp_full(temporaryFile, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR)) == -1) {
+        virReportSystemError(errno, _("g_mkstemp(\"%s\") failed"), temporaryFile);
+        goto cleanup;
+    }
+    unlinkTemporaryFile = true;
+
+    /* write image data */
+    dprintf(fd, "P6\n%d %d\n255\n", xRes, yRes);
+    if (safewrite(fd, ppmBuffer, pixelByteCount) != pixelByteCount) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to write pixel data"));
+        goto cleanup;
+    }
+
+    if (VIR_CLOSE(fd) < 0)
+        virReportSystemError(errno, "%s", _("failed to close screenshot file"));
+
+    if (virFDStreamOpenFile(stream, temporaryFile, 0, 0, O_RDONLY) < 0)
+        goto cleanup;
+
+    result = g_strdup("image/x-portable-pixmap");
+
+ cleanup:
+    VIR_FORCE_CLOSE(fd);
+    if (unlinkTemporaryFile)
+        unlink(temporaryFile);
+
+    return result;
+}
+
+
 static int
 hypervDomainSetVcpusFlags(virDomainPtr domain,
                           unsigned int nvcpus,
@@ -3483,6 +3689,7 @@ static virHypervisorDriver hypervHypervisorDriver = {
     .domainSetMemoryFlags = hypervDomainSetMemoryFlags, /* 3.6.0 */
     .domainGetInfo = hypervDomainGetInfo, /* 0.9.5 */
     .domainGetState = hypervDomainGetState, /* 0.9.5 */
+    .domainScreenshot = hypervDomainScreenshot, /* 7.1.0 */
     .domainSetVcpus = hypervDomainSetVcpus, /* 6.10.0 */
     .domainSetVcpusFlags = hypervDomainSetVcpusFlags, /* 6.10.0 */
     .domainGetVcpusFlags = hypervDomainGetVcpusFlags, /* 6.10.0 */
diff --git a/src/hyperv/hyperv_wmi_classes.h b/src/hyperv/hyperv_wmi_classes.h
index 86a7124799..faf98077eb 100644
--- a/src/hyperv/hyperv_wmi_classes.h
+++ b/src/hyperv/hyperv_wmi_classes.h
@@ -133,6 +133,27 @@ enum _Msvm_EthernetPortAllocationSettingData_EnabledState {
 
 
 
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * CIM_EnabledLogicalElement
+ */
+
+/* https://docs.microsoft.com/en-us/windows/win32/hyperv_v2/cim-enabledlogicalelement#Unknown */
+enum _CIM_EnabledLogicalElement_EnabledState {
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_UNKNOWN = 0,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_OTHER = 1,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED = 2,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_DISABLED = 3,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_SHUTTING_DOWN = 4,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_NOT_APPLICABLE = 5,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED_BUT_OFFLINE = 6,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_IN_TEST = 7,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_DEFERRED = 8,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_QUIESCE = 9,
+    CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_STARTING = 10,
+};
+
+
+
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * WMI
  */
diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_generator.input
index f9d486bd4c..0b342cbfa6 100644
--- a/src/hyperv/hyperv_wmi_generator.input
+++ b/src/hyperv/hyperv_wmi_generator.input
@@ -968,3 +968,166 @@ class Msvm_VirtualEthernetSwitchManagementService
     string   StartMode
     boolean  Started
 end
+
+
+class Msvm_SyntheticDisplayController
+    string   InstanceID
+    string   Caption
+    string   Description
+    string   ElementName
+    datetime InstallDate
+    string   Name
+    uint16   OperationalStatus[]
+    string   StatusDescriptions[]
+    string   Status
+    uint16   HealthState
+    uint16   CommunicationStatus
+    uint16   DetailedStatus
+    uint16   OperatingStatus
+    uint16   PrimaryStatus
+    string   EnabledState
+    string   OtherEnabledState
+    uint16   RequestedState
+    uint16   EnabledDefault
+    datetime TimeOfLastStateChange
+    uint16   AvailableRequestedStates[]
+    uint16   TransitioningToState
+    string   SystemCreationClassName
+    string   SystemName
+    string   CreationClassName
+    string   DeviceID
+    boolean  PowerManagementSupported
+    uint16   PowerManagementCapabilities[]
+    uint16   Availability
+    uint16   StatusInfo
+    uint32   LastErrorCode
+    string   ErrorDescription
+    boolean  ErrorCleared
+    uint64   PowerOnHours
+    uint64   TotalPowerOnHours
+    string   OtherIdentifyingInfo[]
+    string   IdentifyingDescriptions[]
+    uint16   AdditionalAvailability[]
+    uint64   MaxQuiesceTime
+    datetime TimeOfLastReset
+    uint16   ProtocolSupported
+    uint32   MaxNumberControlled
+    string   ProtocolDescription
+    string   VideoProcessor
+    uint16   VideoMemoryType
+    string   OtherVideoMemoryType
+    uint32   NumberOfVideoPages
+    uint32   MaxMemorySupported
+    uint16   AcceleratorCapabilities[]
+    string   CapabilityDescriptions[]
+    string   OtherVideoArchitecture
+    uint16   VideoArchitecture
+end
+
+
+class Msvm_S3DisplayController
+    string   InstanceID
+    string   Caption
+    string   Description
+    string   ElementName
+    datetime InstallDate
+    string   Name
+    uint16   OperationalStatus[]
+    string   StatusDescriptions[]
+    string   Status
+    uint16   HealthState
+    uint16   CommunicationStatus
+    uint16   DetailedStatus
+    uint16   OperatingStatus
+    uint16   PrimaryStatus
+    string   EnabledState
+    string   OtherEnabledState
+    uint16   RequestedState
+    uint16   EnabledDefault
+    datetime TimeOfLastStateChange
+    uint16   AvailableRequestedStates[]
+    uint16   TransitioningToState
+    string   SystemCreationClassName
+    string   SystemName
+    string   CreationClassName
+    string   DeviceID
+    boolean  PowerManagementSupported
+    uint16   PowerManagementCapabilities[]
+    uint16   Availability
+    uint16   StatusInfo
+    uint32   LastErrorCode
+    string   ErrorDescription
+    boolean  ErrorCleared
+    string   OtherIdentifyingInfo[]
+    uint64   PowerOnHours
+    uint64   TotalPowerOnHours
+    string   IdentifyingDescriptions[]
+    uint16   AdditionalAvailability[]
+    uint64   MaxQuiesceTime
+    datetime TimeOfLastReset
+    uint16   ProtocolSupported
+    uint32   MaxNumberControlled
+    string   ProtocolDescription
+    string   VideoProcessor
+    uint16   VideoMemoryType
+    string   OtherVideoMemoryType
+    uint32   NumberOfVideoPages
+    uint32   MaxMemorySupported
+    uint16   AcceleratorCapabilities[]
+    string   CapabilityDescriptions[]
+    string   OtherVideoArchitecture
+    uint16   VideoArchitecture
+end
+
+
+class Msvm_VideoHead
+    string   InstanceID
+    string   Caption
+    string   Description
+    string   ElementName
+    datetime InstallDate
+    string   Name
+    uint16   OperationalStatus[]
+    string   StatusDescriptions[]
+    string   Status
+    uint16   HealthState
+    uint16   CommunicationStatus
+    uint16   DetailedStatus
+    uint16   OperatingStatus
+    uint16   PrimaryStatus
+    string   EnabledState
+    string   OtherEnabledState
+    uint16   RequestedState
+    uint16   EnabledDefault
+    datetime TimeOfLastStateChange
+    uint16   AvailableRequestedStates[]
+    uint16   TransitioningToState
+    string   SystemCreationClassName
+    string   SystemName
+    string   CreationClassName
+    string   DeviceID
+    boolean  PowerManagementSupported
+    uint16   PowerManagementCapabilities[]
+    uint16   Availability
+    uint16   StatusInfo
+    uint32   LastErrorCode
+    string   ErrorDescription
+    boolean  ErrorCleared
+    string   OtherIdentifyingInfo[]
+    uint64   PowerOnHours
+    uint64   TotalPowerOnHours
+    string   IdentifyingDescriptions[]
+    uint16   AdditionalAvailability[]
+    uint64   MaxQuiesceTime
+    uint32   CurrentBitsPerPixel
+    uint32   CurrentHorizontalResolution
+    uint32   CurrentVerticalResolution
+    uint32   MaxRefreshRate
+    uint32   MinRefreshRate
+    uint32   CurrentRefreshRate
+    uint16   CurrentScanMode
+    string   OtherCurrentScanMode
+    uint32   CurrentNumberOfRows
+    uint32   CurrentNumberOfColumns
+    uint64   CurrentNumberOfColors
+end
-- 
2.30.0





More information about the libvir-list mailing list