* See the COPYING file in the top-level directory.
*/
-#include <glib.h>
+#ifndef _WIN32_WINNT
+# define _WIN32_WINNT 0x0600
+#endif
+
+#include "qemu/osdep.h"
#include <wtypes.h>
#include <powrprof.h>
-#include <stdio.h>
-#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iptypes.h>
#include <initguid.h>
#endif
#include <lm.h>
+#include <wtsapi32.h>
+#include <wininet.h>
#include "qga/guest-agent-core.h"
#include "qga/vss-win32.h"
-#include "qga-qmp-commands.h"
+#include "qga-qapi-commands.h"
+#include "qapi/error.h"
#include "qapi/qmp/qerror.h"
#include "qemu/queue.h"
#include "qemu/host-utils.h"
+#include "qemu/base64.h"
#ifndef SHTDN_REASON_FLAG_PLANNED
#define SHTDN_REASON_FLAG_PLANNED 0x80000000
.filehandles = QTAILQ_HEAD_INITIALIZER(guest_file_state.filehandles),
};
+#define FILE_GENERIC_APPEND (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA)
typedef struct OpenFlags {
const char *forms;
DWORD creation_disposition;
} OpenFlags;
static OpenFlags guest_file_open_modes[] = {
- {"r", GENERIC_READ, OPEN_EXISTING},
- {"rb", GENERIC_READ, OPEN_EXISTING},
- {"w", GENERIC_WRITE, CREATE_ALWAYS},
- {"wb", GENERIC_WRITE, CREATE_ALWAYS},
- {"a", GENERIC_WRITE, OPEN_ALWAYS },
- {"r+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
- {"rb+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
- {"r+b", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
- {"w+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
- {"wb+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
- {"w+b", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
- {"a+", GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS },
- {"ab+", GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS },
- {"a+b", GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS }
+ {"r", GENERIC_READ, OPEN_EXISTING},
+ {"rb", GENERIC_READ, OPEN_EXISTING},
+ {"w", GENERIC_WRITE, CREATE_ALWAYS},
+ {"wb", GENERIC_WRITE, CREATE_ALWAYS},
+ {"a", FILE_GENERIC_APPEND, OPEN_ALWAYS },
+ {"r+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
+ {"rb+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
+ {"r+b", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
+ {"w+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
+ {"wb+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
+ {"w+b", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
+ {"a+", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS },
+ {"ab+", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS },
+ {"a+b", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS }
};
static OpenFlags *find_open_flag(const char *mode_str)
if (token) {
CloseHandle(token);
}
- if (local_err) {
- error_propagate(errp, local_err);
- }
+ error_propagate(errp, local_err);
}
static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque,
return NULL;
}
fh = gfh->fh;
- buf = g_base64_decode(buf_b64, &buf_len);
+ buf = qbase64_decode(buf_b64, -1, &buf_len, errp);
+ if (!buf) {
+ return NULL;
+ }
if (!has_count) {
count = buf_len;
}
GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
- int64_t whence, Error **errp)
+ GuestFileWhence *whence_code,
+ Error **errp)
{
GuestFileHandle *gfh;
GuestFileSeek *seek_data;
LARGE_INTEGER new_pos, off_pos;
off_pos.QuadPart = offset;
BOOL res;
+ int whence;
+ Error *err = NULL;
+
gfh = guest_file_handle_find(handle, errp);
if (!gfh) {
return NULL;
}
+ /* We stupidly exposed 'whence':'int' in our qapi */
+ whence = ga_parse_whence(whence_code, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return NULL;
+ }
+
fh = gfh->fh;
res = SetFilePointerEx(fh, off_pos, &new_pos, whence);
if (!res) {
} else {
error_setg_win32(errp, GetLastError(),
"failed to get device name");
- goto out;
+ goto free_dev_info;
}
}
pci->bus = bus;
break;
}
+
+free_dev_info:
+ SetupDiDestroyDeviceInfoList(dev_info);
out:
g_free(buffer);
g_free(name);
/* cannot risk guest agent blocking itself on a write in this state */
ga_set_frozen(ga_state);
- qga_vss_fsfreeze(&i, &local_err, true);
+ qga_vss_fsfreeze(&i, true, &local_err);
if (local_err) {
error_propagate(errp, local_err);
goto error;
return 0;
}
- qga_vss_fsfreeze(&i, errp, false);
+ qga_vss_fsfreeze(&i, false, errp);
ga_unset_frozen(ga_state);
return i;
GuestFilesystemTrimResponse *
qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
{
- error_setg(errp, QERR_UNSUPPORTED);
- return NULL;
+ GuestFilesystemTrimResponse *resp;
+ HANDLE handle;
+ WCHAR guid[MAX_PATH] = L"";
+
+ handle = FindFirstVolumeW(guid, ARRAYSIZE(guid));
+ if (handle == INVALID_HANDLE_VALUE) {
+ error_setg_win32(errp, GetLastError(), "failed to find any volume");
+ return NULL;
+ }
+
+ resp = g_new0(GuestFilesystemTrimResponse, 1);
+
+ do {
+ GuestFilesystemTrimResult *res;
+ GuestFilesystemTrimResultList *list;
+ PWCHAR uc_path;
+ DWORD char_count = 0;
+ char *path, *out;
+ GError *gerr = NULL;
+ gchar * argv[4];
+
+ GetVolumePathNamesForVolumeNameW(guid, NULL, 0, &char_count);
+
+ if (GetLastError() != ERROR_MORE_DATA) {
+ continue;
+ }
+ if (GetDriveTypeW(guid) != DRIVE_FIXED) {
+ continue;
+ }
+
+ uc_path = g_malloc(sizeof(WCHAR) * char_count);
+ if (!GetVolumePathNamesForVolumeNameW(guid, uc_path, char_count,
+ &char_count) || !*uc_path) {
+ /* strange, but this condition could be faced even with size == 2 */
+ g_free(uc_path);
+ continue;
+ }
+
+ res = g_new0(GuestFilesystemTrimResult, 1);
+
+ path = g_utf16_to_utf8(uc_path, char_count, NULL, NULL, &gerr);
+
+ g_free(uc_path);
+
+ if (!path) {
+ res->has_error = true;
+ res->error = g_strdup(gerr->message);
+ g_error_free(gerr);
+ break;
+ }
+
+ res->path = path;
+
+ list = g_new0(GuestFilesystemTrimResultList, 1);
+ list->value = res;
+ list->next = resp->paths;
+
+ resp->paths = list;
+
+ memset(argv, 0, sizeof(argv));
+ argv[0] = (gchar *)"defrag.exe";
+ argv[1] = (gchar *)"/L";
+ argv[2] = path;
+
+ if (!g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
+ &out /* stdout */, NULL /* stdin */,
+ NULL, &gerr)) {
+ res->has_error = true;
+ res->error = g_strdup(gerr->message);
+ g_error_free(gerr);
+ } else {
+ /* defrag.exe is UGLY. Exit code is ALWAYS zero.
+ Error is reported in the output with something like
+ (x89000020) etc code in the stdout */
+
+ int i;
+ gchar **lines = g_strsplit(out, "\r\n", 0);
+ g_free(out);
+
+ for (i = 0; lines[i] != NULL; i++) {
+ if (g_strstr_len(lines[i], -1, "(0x") == NULL) {
+ continue;
+ }
+ res->has_error = true;
+ res->error = g_strdup(lines[i]);
+ break;
+ }
+ g_strfreev(lines);
+ }
+ } while (FindNextVolumeW(handle, guid, ARRAYSIZE(guid)));
+
+ FindVolumeClose(handle);
+ return resp;
}
typedef enum {
}
out:
- if (local_err) {
- error_propagate(errp, local_err);
- }
+ error_propagate(errp, local_err);
}
static DWORD WINAPI do_suspend(LPVOID opaque)
}
#endif
+#define INTERFACE_PATH_BUF_SZ 512
+
+static DWORD get_interface_index(const char *guid)
+{
+ ULONG index;
+ DWORD status;
+ wchar_t wbuf[INTERFACE_PATH_BUF_SZ];
+ snwprintf(wbuf, INTERFACE_PATH_BUF_SZ, L"\\device\\tcpip_%s", guid);
+ wbuf[INTERFACE_PATH_BUF_SZ - 1] = 0;
+ status = GetAdapterIndex (wbuf, &index);
+ if (status != NO_ERROR) {
+ return (DWORD)~0;
+ } else {
+ return index;
+ }
+}
+
+typedef NETIOAPI_API (WINAPI *GetIfEntry2Func)(PMIB_IF_ROW2 Row);
+
+static int guest_get_network_stats(const char *name,
+ GuestNetworkInterfaceStat *stats)
+{
+ OSVERSIONINFO os_ver;
+
+ os_ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&os_ver);
+ if (os_ver.dwMajorVersion >= 6) {
+ MIB_IF_ROW2 a_mid_ifrow;
+ GetIfEntry2Func getifentry2_ex;
+ DWORD if_index = 0;
+ HMODULE module = GetModuleHandle("iphlpapi");
+ PVOID func = GetProcAddress(module, "GetIfEntry2");
+
+ if (func == NULL) {
+ return -1;
+ }
+
+ getifentry2_ex = (GetIfEntry2Func)func;
+ if_index = get_interface_index(name);
+ if (if_index == (DWORD)~0) {
+ return -1;
+ }
+
+ memset(&a_mid_ifrow, 0, sizeof(a_mid_ifrow));
+ a_mid_ifrow.InterfaceIndex = if_index;
+ if (NO_ERROR == getifentry2_ex(&a_mid_ifrow)) {
+ stats->rx_bytes = a_mid_ifrow.InOctets;
+ stats->rx_packets = a_mid_ifrow.InUcastPkts;
+ stats->rx_errs = a_mid_ifrow.InErrors;
+ stats->rx_dropped = a_mid_ifrow.InDiscards;
+ stats->tx_bytes = a_mid_ifrow.OutOctets;
+ stats->tx_packets = a_mid_ifrow.OutUcastPkts;
+ stats->tx_errs = a_mid_ifrow.OutErrors;
+ stats->tx_dropped = a_mid_ifrow.OutDiscards;
+ return 0;
+ }
+ }
+ return -1;
+}
+
GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
{
IP_ADAPTER_ADDRESSES *adptr_addrs, *addr;
GuestNetworkInterfaceList *head = NULL, *cur_item = NULL;
GuestIpAddressList *head_addr, *cur_addr;
GuestNetworkInterfaceList *info;
+ GuestNetworkInterfaceStat *interface_stat = NULL;
GuestIpAddressList *address_item = NULL;
unsigned char *mac_addr;
char *addr_str;
info->value->has_ip_addresses = true;
info->value->ip_addresses = head_addr;
}
+ if (!info->value->has_statistics) {
+ interface_stat = g_malloc0(sizeof(*interface_stat));
+ if (guest_get_network_stats(addr->AdapterName,
+ interface_stat) == -1) {
+ info->value->has_statistics = false;
+ g_free(interface_stat);
+ } else {
+ info->value->statistics = interface_stat;
+ info->value->has_statistics = true;
+ }
+ }
}
WSACleanup();
out:
int64_t qmp_guest_get_time(Error **errp)
{
SYSTEMTIME ts = {0};
- int64_t time_ns;
FILETIME tf;
GetSystemTime(&ts);
return -1;
}
- time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
+ return ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
- W32_FT_OFFSET) * 100;
-
- return time_ns;
}
void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
* RTC yet:
*
* https://msdn.microsoft.com/en-us/library/aa908981.aspx
+ *
+ * Instead, a workaround is to use the Windows win32tm command to
+ * resync the time using the Windows Time service.
*/
- error_setg(errp, "Time argument is required on this platform");
+ LPVOID msg_buffer;
+ DWORD ret_flags;
+
+ HRESULT hr = system("w32tm /resync /nowait");
+
+ if (GetLastError() != 0) {
+ strerror_s((LPTSTR) & msg_buffer, 0, errno);
+ error_setg(errp, "system(...) failed: %s", (LPCTSTR)msg_buffer);
+ } else if (hr != 0) {
+ if (hr == HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_ACTIVE)) {
+ error_setg(errp, "Windows Time service not running on the "
+ "guest");
+ } else {
+ if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
+ (DWORD)hr, MAKELANGID(LANG_NEUTRAL,
+ SUBLANG_DEFAULT), (LPTSTR) & msg_buffer, 0,
+ NULL)) {
+ error_setg(errp, "w32tm failed with error (0x%lx), couldn'"
+ "t retrieve error message", hr);
+ } else {
+ error_setg(errp, "w32tm failed with error (0x%lx): %s", hr,
+ (LPCTSTR)msg_buffer);
+ LocalFree(msg_buffer);
+ }
+ }
+ } else if (!InternetGetConnectedState(&ret_flags, 0)) {
+ error_setg(errp, "No internet connection on guest, sync not "
+ "accurate");
+ }
return;
}
GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
{
- error_setg(errp, QERR_UNSUPPORTED);
+ PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pslpi, ptr;
+ DWORD length;
+ GuestLogicalProcessorList *head, **link;
+ Error *local_err = NULL;
+ int64_t current;
+
+ ptr = pslpi = NULL;
+ length = 0;
+ current = 0;
+ head = NULL;
+ link = &head;
+
+ if ((GetLogicalProcessorInformation(pslpi, &length) == FALSE) &&
+ (GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
+ (length > sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION))) {
+ ptr = pslpi = g_malloc0(length);
+ if (GetLogicalProcessorInformation(pslpi, &length) == FALSE) {
+ error_setg(&local_err, "Failed to get processor information: %d",
+ (int)GetLastError());
+ }
+ } else {
+ error_setg(&local_err,
+ "Failed to get processor information buffer length: %d",
+ (int)GetLastError());
+ }
+
+ while ((local_err == NULL) && (length > 0)) {
+ if (pslpi->Relationship == RelationProcessorCore) {
+ ULONG_PTR cpu_bits = pslpi->ProcessorMask;
+
+ while (cpu_bits > 0) {
+ if (!!(cpu_bits & 1)) {
+ GuestLogicalProcessor *vcpu;
+ GuestLogicalProcessorList *entry;
+
+ vcpu = g_malloc0(sizeof *vcpu);
+ vcpu->logical_id = current++;
+ vcpu->online = true;
+ vcpu->has_can_offline = true;
+
+ entry = g_malloc0(sizeof *entry);
+ entry->value = vcpu;
+
+ *link = entry;
+ link = &entry->next;
+ }
+ cpu_bits >>= 1;
+ }
+ }
+ length -= sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
+ pslpi++; /* next entry */
+ }
+
+ g_free(ptr);
+
+ if (local_err == NULL) {
+ if (head != NULL) {
+ return head;
+ }
+ /* there's no guest with zero VCPUs */
+ error_setg(&local_err, "Guest reported zero VCPUs");
+ }
+
+ qapi_free_GuestLogicalProcessorList(head);
+ error_propagate(errp, local_err);
return NULL;
}
HMODULE module = NULL;
gchar *retval = NULL;
wchar_t *msg = NULL;
- int flags, nchars;
+ int flags;
+ size_t nchars;
- flags = FORMAT_MESSAGE_ALLOCATE_BUFFER
- |FORMAT_MESSAGE_IGNORE_INSERTS
- |FORMAT_MESSAGE_FROM_SYSTEM;
+ flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_FROM_SYSTEM;
if (error >= NERR_BASE && error <= MAX_NERR) {
module = LoadLibraryExW(L"netmsg.dll", NULL, LOAD_LIBRARY_AS_DATAFILE);
if (msg != NULL) {
nchars = wcslen(msg);
- if (nchars > 2 && msg[nchars-1] == '\n' && msg[nchars-2] == '\r') {
- msg[nchars-2] = '\0';
+ if (nchars >= 2 &&
+ msg[nchars - 1] == L'\n' &&
+ msg[nchars - 2] == L'\r') {
+ msg[nchars - 2] = L'\0';
}
retval = g_utf16_to_utf8(msg, -1, NULL, NULL, NULL);
NET_API_STATUS nas;
char *rawpasswddata = NULL;
size_t rawpasswdlen;
- wchar_t *user, *wpass;
+ wchar_t *user = NULL, *wpass = NULL;
USER_INFO_1003 pi1003 = { 0, };
+ GError *gerr = NULL;
if (crypted) {
error_setg(errp, QERR_UNSUPPORTED);
return;
}
- rawpasswddata = (char *)g_base64_decode(password, &rawpasswdlen);
+ rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
+ if (!rawpasswddata) {
+ return;
+ }
rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
rawpasswddata[rawpasswdlen] = '\0';
- user = g_utf8_to_utf16(username, -1, NULL, NULL, NULL);
- wpass = g_utf8_to_utf16(rawpasswddata, -1, NULL, NULL, NULL);
+ user = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr);
+ if (!user) {
+ goto done;
+ }
+
+ wpass = g_utf8_to_utf16(rawpasswddata, -1, NULL, NULL, &gerr);
+ if (!wpass) {
+ goto done;
+ }
pi1003.usri1003_password = wpass;
nas = NetUserSetInfo(NULL, user,
g_free(msg);
}
+done:
+ if (gerr) {
+ error_setg(errp, QERR_QGA_COMMAND_FAILED, gerr->message);
+ g_error_free(gerr);
+ }
g_free(user);
g_free(wpass);
g_free(rawpasswddata);
{
const char *list_unsupported[] = {
"guest-suspend-hybrid",
- "guest-get-vcpus", "guest-set-vcpus",
+ "guest-set-vcpus",
"guest-get-memory-blocks", "guest-set-memory-blocks",
"guest-get-memory-block-size",
"guest-fsfreeze-freeze-list",
- "guest-fstrim", NULL};
+ NULL};
char **p = (char **)list_unsupported;
while (*p) {
ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
}
}
+
+/* MINGW is missing two fields: IncomingFrames & OutgoingFrames */
+typedef struct _GA_WTSINFOA {
+ WTS_CONNECTSTATE_CLASS State;
+ DWORD SessionId;
+ DWORD IncomingBytes;
+ DWORD OutgoingBytes;
+ DWORD IncomingFrames;
+ DWORD OutgoingFrames;
+ DWORD IncomingCompressedBytes;
+ DWORD OutgoingCompressedBy;
+ CHAR WinStationName[WINSTATIONNAME_LENGTH];
+ CHAR Domain[DOMAIN_LENGTH];
+ CHAR UserName[USERNAME_LENGTH + 1];
+ LARGE_INTEGER ConnectTime;
+ LARGE_INTEGER DisconnectTime;
+ LARGE_INTEGER LastInputTime;
+ LARGE_INTEGER LogonTime;
+ LARGE_INTEGER CurrentTime;
+
+} GA_WTSINFOA;
+
+GuestUserList *qmp_guest_get_users(Error **err)
+{
+#if (_WIN32_WINNT >= 0x0600)
+#define QGA_NANOSECONDS 10000000
+
+ GHashTable *cache = NULL;
+ GuestUserList *head = NULL, *cur_item = NULL;
+
+ DWORD buffer_size = 0, count = 0, i = 0;
+ GA_WTSINFOA *info = NULL;
+ WTS_SESSION_INFOA *entries = NULL;
+ GuestUserList *item = NULL;
+ GuestUser *user = NULL;
+ gpointer value = NULL;
+ INT64 login = 0;
+ double login_time = 0;
+
+ cache = g_hash_table_new(g_str_hash, g_str_equal);
+
+ if (WTSEnumerateSessionsA(NULL, 0, 1, &entries, &count)) {
+ for (i = 0; i < count; ++i) {
+ buffer_size = 0;
+ info = NULL;
+ if (WTSQuerySessionInformationA(
+ NULL,
+ entries[i].SessionId,
+ WTSSessionInfo,
+ (LPSTR *)&info,
+ &buffer_size
+ )) {
+
+ if (strlen(info->UserName) == 0) {
+ WTSFreeMemory(info);
+ continue;
+ }
+
+ login = info->LogonTime.QuadPart;
+ login -= W32_FT_OFFSET;
+ login_time = ((double)login) / QGA_NANOSECONDS;
+
+ if (g_hash_table_contains(cache, info->UserName)) {
+ value = g_hash_table_lookup(cache, info->UserName);
+ user = (GuestUser *)value;
+ if (user->login_time > login_time) {
+ user->login_time = login_time;
+ }
+ } else {
+ item = g_new0(GuestUserList, 1);
+ item->value = g_new0(GuestUser, 1);
+
+ item->value->user = g_strdup(info->UserName);
+ item->value->domain = g_strdup(info->Domain);
+ item->value->has_domain = true;
+
+ item->value->login_time = login_time;
+
+ g_hash_table_add(cache, item->value->user);
+
+ if (!cur_item) {
+ head = cur_item = item;
+ } else {
+ cur_item->next = item;
+ cur_item = item;
+ }
+ }
+ }
+ WTSFreeMemory(info);
+ }
+ WTSFreeMemory(entries);
+ }
+ g_hash_table_destroy(cache);
+ return head;
+#else
+ error_setg(err, QERR_UNSUPPORTED);
+ return NULL;
+#endif
+}
+
+typedef struct _ga_matrix_lookup_t {
+ int major;
+ int minor;
+ char const *version;
+ char const *version_id;
+} ga_matrix_lookup_t;
+
+static ga_matrix_lookup_t const WIN_VERSION_MATRIX[2][8] = {
+ {
+ /* Desktop editions */
+ { 5, 0, "Microsoft Windows 2000", "2000"},
+ { 5, 1, "Microsoft Windows XP", "xp"},
+ { 6, 0, "Microsoft Windows Vista", "vista"},
+ { 6, 1, "Microsoft Windows 7" "7"},
+ { 6, 2, "Microsoft Windows 8", "8"},
+ { 6, 3, "Microsoft Windows 8.1", "8.1"},
+ {10, 0, "Microsoft Windows 10", "10"},
+ { 0, 0, 0}
+ },{
+ /* Server editions */
+ { 5, 2, "Microsoft Windows Server 2003", "2003"},
+ { 6, 0, "Microsoft Windows Server 2008", "2008"},
+ { 6, 1, "Microsoft Windows Server 2008 R2", "2008r2"},
+ { 6, 2, "Microsoft Windows Server 2012", "2012"},
+ { 6, 3, "Microsoft Windows Server 2012 R2", "2012r2"},
+ {10, 0, "Microsoft Windows Server 2016", "2016"},
+ { 0, 0, 0},
+ { 0, 0, 0}
+ }
+};
+
+static void ga_get_win_version(RTL_OSVERSIONINFOEXW *info, Error **errp)
+{
+ typedef NTSTATUS(WINAPI * rtl_get_version_t)(
+ RTL_OSVERSIONINFOEXW *os_version_info_ex);
+
+ info->dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
+
+ HMODULE module = GetModuleHandle("ntdll");
+ PVOID fun = GetProcAddress(module, "RtlGetVersion");
+ if (fun == NULL) {
+ error_setg(errp, QERR_QGA_COMMAND_FAILED,
+ "Failed to get address of RtlGetVersion");
+ return;
+ }
+
+ rtl_get_version_t rtl_get_version = (rtl_get_version_t)fun;
+ rtl_get_version(info);
+ return;
+}
+
+static char *ga_get_win_name(OSVERSIONINFOEXW const *os_version, bool id)
+{
+ DWORD major = os_version->dwMajorVersion;
+ DWORD minor = os_version->dwMinorVersion;
+ int tbl_idx = (os_version->wProductType != VER_NT_WORKSTATION);
+ ga_matrix_lookup_t const *table = WIN_VERSION_MATRIX[tbl_idx];
+ while (table->version != NULL) {
+ if (major == table->major && minor == table->minor) {
+ if (id) {
+ return g_strdup(table->version_id);
+ } else {
+ return g_strdup(table->version);
+ }
+ }
+ ++table;
+ }
+ slog("failed to lookup Windows version: major=%lu, minor=%lu",
+ major, minor);
+ return g_strdup("N/A");
+}
+
+static char *ga_get_win_product_name(Error **errp)
+{
+ HKEY key = NULL;
+ DWORD size = 128;
+ char *result = g_malloc0(size);
+ LONG err = ERROR_SUCCESS;
+
+ err = RegOpenKeyA(HKEY_LOCAL_MACHINE,
+ "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+ &key);
+ if (err != ERROR_SUCCESS) {
+ error_setg_win32(errp, err, "failed to open registry key");
+ goto fail;
+ }
+
+ err = RegQueryValueExA(key, "ProductName", NULL, NULL,
+ (LPBYTE)result, &size);
+ if (err == ERROR_MORE_DATA) {
+ slog("ProductName longer than expected (%lu bytes), retrying",
+ size);
+ g_free(result);
+ result = NULL;
+ if (size > 0) {
+ result = g_malloc0(size);
+ err = RegQueryValueExA(key, "ProductName", NULL, NULL,
+ (LPBYTE)result, &size);
+ }
+ }
+ if (err != ERROR_SUCCESS) {
+ error_setg_win32(errp, err, "failed to retrive ProductName");
+ goto fail;
+ }
+
+ return result;
+
+fail:
+ g_free(result);
+ return NULL;
+}
+
+static char *ga_get_current_arch(void)
+{
+ SYSTEM_INFO info;
+ GetNativeSystemInfo(&info);
+ char *result = NULL;
+ switch (info.wProcessorArchitecture) {
+ case PROCESSOR_ARCHITECTURE_AMD64:
+ result = g_strdup("x86_64");
+ break;
+ case PROCESSOR_ARCHITECTURE_ARM:
+ result = g_strdup("arm");
+ break;
+ case PROCESSOR_ARCHITECTURE_IA64:
+ result = g_strdup("ia64");
+ break;
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ result = g_strdup("x86");
+ break;
+ case PROCESSOR_ARCHITECTURE_UNKNOWN:
+ default:
+ slog("unknown processor architecture 0x%0x",
+ info.wProcessorArchitecture);
+ result = g_strdup("unknown");
+ break;
+ }
+ return result;
+}
+
+GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
+{
+ Error *local_err = NULL;
+ OSVERSIONINFOEXW os_version = {0};
+ bool server;
+ char *product_name;
+ GuestOSInfo *info;
+
+ ga_get_win_version(&os_version, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ server = os_version.wProductType != VER_NT_WORKSTATION;
+ product_name = ga_get_win_product_name(&local_err);
+ if (product_name == NULL) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ info = g_new0(GuestOSInfo, 1);
+
+ info->has_kernel_version = true;
+ info->kernel_version = g_strdup_printf("%lu.%lu",
+ os_version.dwMajorVersion,
+ os_version.dwMinorVersion);
+ info->has_kernel_release = true;
+ info->kernel_release = g_strdup_printf("%lu",
+ os_version.dwBuildNumber);
+ info->has_machine = true;
+ info->machine = ga_get_current_arch();
+
+ info->has_id = true;
+ info->id = g_strdup("mswindows");
+ info->has_name = true;
+ info->name = g_strdup("Microsoft Windows");
+ info->has_pretty_name = true;
+ info->pretty_name = product_name;
+ info->has_version = true;
+ info->version = ga_get_win_name(&os_version, false);
+ info->has_version_id = true;
+ info->version_id = ga_get_win_name(&os_version, true);
+ info->has_variant = true;
+ info->variant = g_strdup(server ? "server" : "client");
+ info->has_variant_id = true;
+ info->variant_id = g_strdup(server ? "server" : "client");
+
+ return info;
+}