qemu-ga patch queue

* new command: qemu-get-osinfo
 * build fix for OpenBSD
 * better error-reporting for failure on keyfile dump
 * remove redundant initialization of qa_state global
 * include libpcre in w32 package
 * w32 localization fixes for service installation/registration
 
 v2:
 * fix build issue with older GCCs introduced with guest_get_osinfo
 * relocated some declarations in guest_get_osinfo
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQEcBAABAgAGBQJZbeh9AAoJEDNTyc7xCLWE3hgH/2n5tn4kqkZ4fNLWiXRQgzmC
 Sp6lXMS1iqRwAQTWcI0+C74k6rP9ByytzxYv6UmmdpTUGmk+v26xMaJlV4LysDWx
 8ilOd75s4Fmu+pWOaJTXrA8bWKEeoq7RPhl5samtY1u27v2QbCWdqViwhjzbxzNJ
 QmomyOkUAKdEpC70XBtEbkSyKqB4aM6nPzlTli8m3/0CNXmtwBmq7g9VPV1alLxp
 U567tJu0IQKuu8WsqMTPUQTHQ8ATvl0NLaF5Mpux1/B/FBui1v/9swNXPnbD9YXg
 +ZWxjtCep9PWq3WVylU9O2tikttxMW2vXdgB2lYM1agjQZEX+AXOj8WzhF9d4bw=
 =UFcD
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/mdroth/tags/qga-pull-2017-07-17-v2-tag' into staging

qemu-ga patch queue

* new command: qemu-get-osinfo
* build fix for OpenBSD
* better error-reporting for failure on keyfile dump
* remove redundant initialization of qa_state global
* include libpcre in w32 package
* w32 localization fixes for service installation/registration

v2:
* fix build issue with older GCCs introduced with guest_get_osinfo
* relocated some declarations in guest_get_osinfo

# gpg: Signature made Tue 18 Jul 2017 11:52:45 BST
# gpg:                using RSA key 0x3353C9CEF108B584
# gpg: Good signature from "Michael Roth <flukshun@gmail.com>"
# gpg:                 aka "Michael Roth <mdroth@utexas.edu>"
# gpg:                 aka "Michael Roth <mdroth@linux.vnet.ibm.com>"
# Primary key fingerprint: CEAC C9E1 5534 EBAB B82D  3FA0 3353 C9CE F108 B584

* remotes/mdroth/tags/qga-pull-2017-07-17-v2-tag:
  test-qga: add test for guest-get-osinfo
  test-qga: pass environemnt to qemu-ga
  qemu-ga: add guest-get-osinfo command
  qga: report error on keyfile dump error
  qga-win32: remove a redundancy code
  qemu-ga: check if utmpx.h is available on the system
  qemu-ga: add missing libpcre to MSI build
  qga-win: fix installation on localized windows

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2017-07-18 14:14:32 +01:00
commit e9277a19a1
9 changed files with 542 additions and 9 deletions

19
configure vendored
View File

@ -4914,6 +4914,21 @@ if compile_prog "" "" ; then
have_static_assert=yes
fi
##########################################
# check for utmpx.h, it is missing e.g. on OpenBSD
have_utmpx=no
cat > $TMPC << EOF
#include <utmpx.h>
struct utmpx user_info;
int main(void) {
return 0;
}
EOF
if compile_prog "" "" ; then
have_utmpx=yes
fi
##########################################
# End of CC checks
# After here, no more $cc or $ld runs
@ -5959,6 +5974,10 @@ if test "$have_static_assert" = "yes" ; then
echo "CONFIG_STATIC_ASSERT=y" >> $config_host_mak
fi
if test "$have_utmpx" = "yes" ; then
echo "HAVE_UTMPX=y" >> $config_host_mak
fi
# Hold two types of flag:
# CONFIG_THREAD_SETNAME_BYTHREAD - we've got a way of setting the name on
# a thread we have a handle to

View File

@ -13,9 +13,9 @@
#include "qemu/osdep.h"
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <dirent.h>
#include <utmpx.h>
#include "qga/guest-agent-core.h"
#include "qga-qmp-commands.h"
#include "qapi/qmp/qerror.h"
@ -25,6 +25,10 @@
#include "qemu/base64.h"
#include "qemu/cutils.h"
#ifdef HAVE_UTMPX
#include <utmpx.h>
#endif
#ifndef CONFIG_HAS_ENVIRON
#ifdef __APPLE__
#include <crt_externs.h>
@ -2519,6 +2523,8 @@ void ga_command_state_init(GAState *s, GACommandState *cs)
#endif
}
#ifdef HAVE_UTMPX
#define QGA_MICRO_SECOND_TO_SECOND 1000000
static double ga_get_login_time(struct utmpx *user_info)
@ -2577,3 +2583,152 @@ GuestUserList *qmp_guest_get_users(Error **err)
g_hash_table_destroy(cache);
return head;
}
#else
GuestUserList *qmp_guest_get_users(Error **errp)
{
error_setg(errp, QERR_UNSUPPORTED);
return NULL;
}
#endif
/* Replace escaped special characters with theire real values. The replacement
* is done in place -- returned value is in the original string.
*/
static void ga_osrelease_replace_special(gchar *value)
{
gchar *p, *p2, quote;
/* Trim the string at first space or semicolon if it is not enclosed in
* single or double quotes. */
if ((value[0] != '"') || (value[0] == '\'')) {
p = strchr(value, ' ');
if (p != NULL) {
*p = 0;
}
p = strchr(value, ';');
if (p != NULL) {
*p = 0;
}
return;
}
quote = value[0];
p2 = value;
p = value + 1;
while (*p != 0) {
if (*p == '\\') {
p++;
switch (*p) {
case '$':
case '\'':
case '"':
case '\\':
case '`':
break;
default:
/* Keep literal backslash followed by whatever is there */
p--;
break;
}
} else if (*p == quote) {
*p2 = 0;
break;
}
*(p2++) = *(p++);
}
}
static GKeyFile *ga_parse_osrelease(const char *fname)
{
gchar *content = NULL;
gchar *content2 = NULL;
GError *err = NULL;
GKeyFile *keys = g_key_file_new();
const char *group = "[os-release]\n";
if (!g_file_get_contents(fname, &content, NULL, &err)) {
slog("failed to read '%s', error: %s", fname, err->message);
goto fail;
}
if (!g_utf8_validate(content, -1, NULL)) {
slog("file is not utf-8 encoded: %s", fname);
goto fail;
}
content2 = g_strdup_printf("%s%s", group, content);
if (!g_key_file_load_from_data(keys, content2, -1, G_KEY_FILE_NONE,
&err)) {
slog("failed to parse file '%s', error: %s", fname, err->message);
goto fail;
}
g_free(content);
g_free(content2);
return keys;
fail:
g_error_free(err);
g_free(content);
g_free(content2);
g_key_file_free(keys);
return NULL;
}
GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
{
GuestOSInfo *info = NULL;
struct utsname kinfo;
GKeyFile *osrelease = NULL;
const char *qga_os_release = g_getenv("QGA_OS_RELEASE");
info = g_new0(GuestOSInfo, 1);
if (uname(&kinfo) != 0) {
error_setg_errno(errp, errno, "uname failed");
} else {
info->has_kernel_version = true;
info->kernel_version = g_strdup(kinfo.version);
info->has_kernel_release = true;
info->kernel_release = g_strdup(kinfo.release);
info->has_machine = true;
info->machine = g_strdup(kinfo.machine);
}
if (qga_os_release != NULL) {
osrelease = ga_parse_osrelease(qga_os_release);
} else {
osrelease = ga_parse_osrelease("/etc/os-release");
if (osrelease == NULL) {
osrelease = ga_parse_osrelease("/usr/lib/os-release");
}
}
if (osrelease != NULL) {
char *value;
#define GET_FIELD(field, osfield) do { \
value = g_key_file_get_value(osrelease, "os-release", osfield, NULL); \
if (value != NULL) { \
ga_osrelease_replace_special(value); \
info->has_ ## field = true; \
info->field = value; \
} \
} while (0)
GET_FIELD(id, "ID");
GET_FIELD(name, "NAME");
GET_FIELD(pretty_name, "PRETTY_NAME");
GET_FIELD(version, "VERSION");
GET_FIELD(version_id, "VERSION_ID");
GET_FIELD(variant, "VARIANT");
GET_FIELD(variant_id, "VARIANT_ID");
#undef GET_FIELD
g_key_file_free(osrelease);
}
return info;
}

View File

@ -1642,3 +1642,194 @@ GuestUserList *qmp_guest_get_users(Error **err)
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;
}

View File

@ -125,6 +125,9 @@
<Component Id="libwinpthread" Guid="{6C117C78-0F47-4B07-8F34-6BEE11643829}">
<File Id="libwinpthread_1.dll" Name="libwinpthread-1.dll" Source="$(var.Mingw_bin)/libwinpthread-1.dll" KeyPath="yes" DiskId="1"/>
</Component>
<Component Id="libpcre" Guid="{7A86B45E-A009-489A-A849-CE3BACF03CD0}">
<File Id="libpcre_1.dll" Name="libpcre-1.dll" Source="$(var.Mingw_bin)/libpcre-1.dll" KeyPath="yes" DiskId="1"/>
</Component>
<Component Id="registry_entries" Guid="{D075D109-51CA-11E3-9F8B-000C29858960}">
<RegistryKey Root="HKLM"
Key="Software\$(env.QEMU_GA_MANUFACTURER)\$(env.QEMU_GA_DISTRO)\Tools\QemuGA">
@ -173,6 +176,7 @@
<ComponentRef Id="libssp" />
<ComponentRef Id="libwinpthread" />
<ComponentRef Id="registry_entries" />
<ComponentRef Id="libpcre" />
</Feature>
<InstallExecuteSequence>

View File

@ -1074,7 +1074,12 @@ static void config_dump(GAConfig *config)
g_free(tmp);
tmp = g_key_file_to_data(keyfile, NULL, &error);
printf("%s", tmp);
if (error) {
g_critical("Failed to dump keyfile: %s", error->message);
g_clear_error(&error);
} else {
printf("%s", tmp);
}
g_free(tmp);
g_key_file_free(keyfile);
@ -1314,7 +1319,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation)
ga_command_state_init(s, s->command_state);
ga_command_state_init_all(s->command_state);
json_message_parser_init(&s->parser, process_event);
ga_state = s;
#ifndef _WIN32
if (!register_signal_handlers()) {
g_critical("failed to register signal handlers");

View File

@ -1126,3 +1126,68 @@
##
{ 'command': 'guest-get-timezone',
'returns': 'GuestTimezone' }
##
# @GuestOSInfo:
#
# @kernel-release:
# * POSIX: release field returned by uname(2)
# * Windows: version number of the OS
# @kernel-version:
# * POSIX: version field returned by uname(2)
# * Windows: build number of the OS
# @machine:
# * POSIX: machine field returned by uname(2)
# * Windows: one of x86, x86_64, arm, ia64
# @id:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "mswindows"
# @name:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "Microsoft Windows"
# @pretty-name:
# * POSIX: as defined by os-release(5)
# * Windows: product name, e.g. "Microsoft Windows 10 Enterprise"
# @version:
# * POSIX: as defined by os-release(5)
# * Windows: long version string, e.g. "Microsoft Windows Server 2008"
# @version-id:
# * POSIX: as defined by os-release(5)
# * Windows: short version identifier, e.g. "7" or "20012r2"
# @variant:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "server" or "client"
# @variant-id:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "server" or "client"
#
# Notes:
#
# On POSIX systems the fields @id, @name, @pretty-name, @version, @version-id,
# @variant and @variant-id follow the definition specified in os-release(5).
# Refer to the manual page for exact description of the fields. Their values
# are taken from the os-release file. If the file is not present in the system,
# or the values are not present in the file, the fields are not included.
#
# On Windows the values are filled from information gathered from the system.
#
# Since: 2.10
##
{ 'struct': 'GuestOSInfo',
'data': {
'*kernel-release': 'str', '*kernel-version': 'str',
'*machine': 'str', '*id': 'str', '*name': 'str',
'*pretty-name': 'str', '*version': 'str', '*version-id': 'str',
'*variant': 'str', '*variant-id': 'str' } }
##
# @guest-get-osinfo:
#
# Retrieve guest operating system information
#
# Returns: @GuestOSInfo
#
# Since: 2.10
##
{ 'command': 'guest-get-osinfo',
'returns': 'GuestOSInfo' }

View File

@ -18,6 +18,9 @@
#include <wbemidl.h>
#include <comdef.h>
#include <comutil.h>
#include <sddl.h>
#define BUFFER_SIZE 1024
extern HINSTANCE g_hinstDll;
@ -135,6 +138,27 @@ out:
return hr;
}
/* Acquire group or user name by SID */
static HRESULT getNameByStringSID(
const wchar_t *sid, LPWSTR buffer, LPDWORD bufferLen)
{
HRESULT hr = S_OK;
PSID psid = NULL;
SID_NAME_USE groupType;
DWORD domainNameLen = BUFFER_SIZE;
wchar_t domainName[BUFFER_SIZE];
chk(ConvertStringSidToSidW(sid, &psid));
LookupAccountSidW(NULL, psid, buffer, bufferLen,
domainName, &domainNameLen, &groupType);
hr = HRESULT_FROM_WIN32(GetLastError());
LocalFree(psid);
out:
return hr;
}
/* Find and iterate QGA VSS provider in COM+ Application Catalog */
static HRESULT QGAProviderFind(
HRESULT (*found)(ICatalogCollection *, int, void *), void *arg)
@ -216,6 +240,10 @@ STDAPI COMRegister(void)
CHAR dllPath[MAX_PATH], tlbPath[MAX_PATH];
bool unregisterOnFailure = false;
int count = 0;
DWORD bufferLen = BUFFER_SIZE;
wchar_t buffer[BUFFER_SIZE];
const wchar_t *administratorsGroupSID = L"S-1-5-32-544";
const wchar_t *systemUserSID = L"S-1-5-18";
if (!g_hinstDll) {
errmsg(E_FAIL, "Failed to initialize DLL");
@ -284,11 +312,12 @@ STDAPI COMRegister(void)
/* Setup roles of the applicaion */
chk(getNameByStringSID(administratorsGroupSID, buffer, &bufferLen));
chk(pApps->GetCollection(_bstr_t(L"Roles"), key,
(IDispatch **)pRoles.replace()));
chk(pRoles->Populate());
chk(pRoles->Add((IDispatch **)pObj.replace()));
chk(put_Value(pObj, L"Name", L"Administrators"));
chk(put_Value(pObj, L"Name", buffer));
chk(put_Value(pObj, L"Description", L"Administrators group"));
chk(pRoles->SaveChanges(&n));
chk(pObj->get_Key(&key));
@ -303,8 +332,10 @@ STDAPI COMRegister(void)
chk(GetAdminName(&name));
chk(put_Value(pObj, L"User", _bstr_t(".\\") + name));
bufferLen = BUFFER_SIZE;
chk(getNameByStringSID(systemUserSID, buffer, &bufferLen));
chk(pUsersInRole->Add((IDispatch **)pObj.replace()));
chk(put_Value(pObj, L"User", L"SYSTEM"));
chk(put_Value(pObj, L"User", buffer));
chk(pUsersInRole->SaveChanges(&n));
out:

View File

@ -0,0 +1,7 @@
ID=qemu-ga-test
NAME=QEMU-GA
PRETTY_NAME="QEMU Guest Agent test"
VERSION="Test 1"
VERSION_ID=1
VARIANT="Unit test \"\'\$\`\\ and \\\\ etc."
VARIANT_ID=unit-test

View File

@ -46,7 +46,7 @@ static void qga_watch(GPid pid, gint status, gpointer user_data)
}
static void
fixture_setup(TestFixture *fixture, gconstpointer data)
fixture_setup(TestFixture *fixture, gconstpointer data, gchar **envp)
{
const gchar *extra_arg = data;
GError *error = NULL;
@ -67,7 +67,7 @@ fixture_setup(TestFixture *fixture, gconstpointer data)
g_shell_parse_argv(cmd, NULL, &argv, &error);
g_assert_no_error(error);
g_spawn_async(fixture->test_dir, argv, NULL,
g_spawn_async(fixture->test_dir, argv, envp,
G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
NULL, NULL, &fixture->pid, &error);
g_assert_no_error(error);
@ -707,7 +707,7 @@ static void test_qga_blacklist(gconstpointer data)
QDict *ret, *error;
const gchar *class, *desc;
fixture_setup(&fix, "-b guest-ping,guest-get-time");
fixture_setup(&fix, "-b guest-ping,guest-get-time", NULL);
/* check blacklist */
ret = qmp_fd(fix.fd, "{'execute': 'guest-ping'}");
@ -936,6 +936,60 @@ static void test_qga_guest_exec_invalid(gconstpointer fix)
QDECREF(ret);
}
static void test_qga_guest_get_osinfo(gconstpointer data)
{
TestFixture fixture;
const gchar *str;
gchar *cwd, *env[2];
QDict *ret, *val;
cwd = g_get_current_dir();
env[0] = g_strdup_printf(
"QGA_OS_RELEASE=%s%ctests%cdata%ctest-qga-os-release",
cwd, G_DIR_SEPARATOR, G_DIR_SEPARATOR, G_DIR_SEPARATOR);
env[1] = NULL;
g_free(cwd);
fixture_setup(&fixture, NULL, env);
ret = qmp_fd(fixture.fd, "{'execute': 'guest-get-osinfo'}");
g_assert_nonnull(ret);
qmp_assert_no_error(ret);
val = qdict_get_qdict(ret, "return");
str = qdict_get_try_str(val, "id");
g_assert_nonnull(str);
g_assert_cmpstr(str, ==, "qemu-ga-test");
str = qdict_get_try_str(val, "name");
g_assert_nonnull(str);
g_assert_cmpstr(str, ==, "QEMU-GA");
str = qdict_get_try_str(val, "pretty-name");
g_assert_nonnull(str);
g_assert_cmpstr(str, ==, "QEMU Guest Agent test");
str = qdict_get_try_str(val, "version");
g_assert_nonnull(str);
g_assert_cmpstr(str, ==, "Test 1");
str = qdict_get_try_str(val, "version-id");
g_assert_nonnull(str);
g_assert_cmpstr(str, ==, "1");
str = qdict_get_try_str(val, "variant");
g_assert_nonnull(str);
g_assert_cmpstr(str, ==, "Unit test \"'$`\\ and \\\\ etc.");
str = qdict_get_try_str(val, "variant-id");
g_assert_nonnull(str);
g_assert_cmpstr(str, ==, "unit-test");
QDECREF(ret);
g_free(env[0]);
fixture_tear_down(&fixture, NULL);
}
int main(int argc, char **argv)
{
TestFixture fix;
@ -943,7 +997,7 @@ int main(int argc, char **argv)
setlocale (LC_ALL, "");
g_test_init(&argc, &argv, NULL);
fixture_setup(&fix, NULL);
fixture_setup(&fix, NULL, NULL);
g_test_add_data_func("/qga/sync-delimited", &fix, test_qga_sync_delimited);
g_test_add_data_func("/qga/sync", &fix, test_qga_sync);
@ -972,6 +1026,8 @@ int main(int argc, char **argv)
g_test_add_data_func("/qga/guest-exec", &fix, test_qga_guest_exec);
g_test_add_data_func("/qga/guest-exec-invalid", &fix,
test_qga_guest_exec_invalid);
g_test_add_data_func("/qga/guest-get-osinfo", &fix,
test_qga_guest_get_osinfo);
if (g_getenv("QGA_TEST_SIDE_EFFECTING")) {
g_test_add_data_func("/qga/fsfreeze-and-thaw", &fix,