Commit e9277a19 authored by Peter Maydell's avatar Peter Maydell
Browse files

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: default avatarPeter Maydell <peter.maydell@linaro.org>
parents 718d7f4f 339ca68b
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -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
+156 −1
Original line number Diff line number Diff line
@@ -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;
}
+191 −0
Original line number Diff line number Diff line
@@ -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;
}
+4 −0
Original line number Diff line number Diff line
@@ -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>
+7 −2
Original line number Diff line number Diff line
@@ -1074,7 +1074,12 @@ static void config_dump(GAConfig *config)
    g_free(tmp);

    tmp = g_key_file_to_data(keyfile, NULL, &error);
    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");
Loading