summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xconfigure2
-rw-r--r--include/glib-compat.h6
-rw-r--r--qga/commands-posix.c60
-rw-r--r--qga/commands-win32.c105
-rw-r--r--qga/commands.c49
-rw-r--r--qga/main.c23
-rw-r--r--qga/qapi-schema.json88
-rw-r--r--qga/vss-win32.h1
-rw-r--r--qga/vss-win32/install.cpp28
-rw-r--r--qga/vss-win32/install.h20
-rw-r--r--qga/vss-win32/provider.cpp1
-rw-r--r--qga/vss-win32/requester.cpp2
-rw-r--r--qga/vss-win32/vss-common.h11
-rw-r--r--qga/vss-win32/vss-handles.h14
14 files changed, 393 insertions, 17 deletions
diff --git a/configure b/configure
index c35acf1192..80d17f38c4 100755
--- a/configure
+++ b/configure
@@ -743,7 +743,7 @@ if test "$mingw32" = "yes" ; then
sysconfdir="\${prefix}"
local_statedir=
confsuffix=""
- libs_qga="-lws2_32 -lwinmm -lpowrprof -liphlpapi -lnetapi32 $libs_qga"
+ libs_qga="-lws2_32 -lwinmm -lpowrprof -lwtsapi32 -liphlpapi -lnetapi32 $libs_qga"
fi
werror=""
diff --git a/include/glib-compat.h b/include/glib-compat.h
index 863c8cf73d..fcffcd3f07 100644
--- a/include/glib-compat.h
+++ b/include/glib-compat.h
@@ -217,6 +217,12 @@ static inline void g_hash_table_add(GHashTable *hash_table, gpointer key)
{
g_hash_table_replace(hash_table, key, key);
}
+
+static inline gboolean g_hash_table_contains(GHashTable *hash_table,
+ gpointer key)
+{
+ return g_hash_table_lookup_extended(hash_table, key, NULL, NULL);
+}
#endif
#ifndef g_assert_true
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 915df9ed90..ba06be4c86 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -15,6 +15,7 @@
#include <sys/ioctl.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"
@@ -2517,3 +2518,62 @@ void ga_command_state_init(GAState *s, GACommandState *cs)
ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
#endif
}
+
+#define QGA_MICRO_SECOND_TO_SECOND 1000000
+
+static double ga_get_login_time(struct utmpx *user_info)
+{
+ double seconds = (double)user_info->ut_tv.tv_sec;
+ double useconds = (double)user_info->ut_tv.tv_usec;
+ useconds /= QGA_MICRO_SECOND_TO_SECOND;
+ return seconds + useconds;
+}
+
+GuestUserList *qmp_guest_get_users(Error **err)
+{
+ GHashTable *cache = NULL;
+ GuestUserList *head = NULL, *cur_item = NULL;
+ struct utmpx *user_info = NULL;
+ gpointer value = NULL;
+ GuestUser *user = NULL;
+ GuestUserList *item = NULL;
+ double login_time = 0;
+
+ cache = g_hash_table_new(g_str_hash, g_str_equal);
+ setutxent();
+
+ for (;;) {
+ user_info = getutxent();
+ if (user_info == NULL) {
+ break;
+ } else if (user_info->ut_type != USER_PROCESS) {
+ continue;
+ } else if (g_hash_table_contains(cache, user_info->ut_user)) {
+ value = g_hash_table_lookup(cache, user_info->ut_user);
+ user = (GuestUser *)value;
+ login_time = ga_get_login_time(user_info);
+ /* We're ensuring the earliest login time to be sent */
+ if (login_time < user->login_time) {
+ user->login_time = login_time;
+ }
+ continue;
+ }
+
+ item = g_new0(GuestUserList, 1);
+ item->value = g_new0(GuestUser, 1);
+ item->value->user = g_strdup(user_info->ut_user);
+ item->value->login_time = ga_get_login_time(user_info);
+
+ g_hash_table_insert(cache, item->value->user, item->value);
+
+ if (!cur_item) {
+ head = cur_item = item;
+ } else {
+ cur_item->next = item;
+ cur_item = item;
+ }
+ }
+ endutxent();
+ g_hash_table_destroy(cache);
+ return head;
+}
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 04026eedbf..439d229225 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -11,6 +11,9 @@
* See the COPYING file in the top-level directory.
*/
+#ifndef _WIN32_WINNT
+# define _WIN32_WINNT 0x0600
+#endif
#include "qemu/osdep.h"
#include <wtypes.h>
#include <powrprof.h>
@@ -25,6 +28,7 @@
#include <initguid.h>
#endif
#include <lm.h>
+#include <wtsapi32.h>
#include "qga/guest-agent-core.h"
#include "qga/vss-win32.h"
@@ -1344,7 +1348,7 @@ GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
vcpu = g_malloc0(sizeof *vcpu);
vcpu->logical_id = current++;
vcpu->online = true;
- vcpu->has_can_offline = false;
+ vcpu->has_can_offline = true;
entry = g_malloc0(sizeof *entry);
entry->value = vcpu;
@@ -1536,3 +1540,102 @@ void ga_command_state_init(GAState *s, GACommandState *cs)
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
+}
diff --git a/qga/commands.c b/qga/commands.c
index 4d92946820..3333ed50b2 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -499,3 +499,52 @@ int ga_parse_whence(GuestFileWhence *whence, Error **errp)
error_setg(errp, "invalid whence code %"PRId64, whence->u.value);
return -1;
}
+
+GuestHostName *qmp_guest_get_host_name(Error **err)
+{
+ GuestHostName *result = NULL;
+ gchar const *hostname = g_get_host_name();
+ if (hostname != NULL) {
+ result = g_new0(GuestHostName, 1);
+ result->host_name = g_strdup(hostname);
+ }
+ return result;
+}
+
+GuestTimezone *qmp_guest_get_timezone(Error **errp)
+{
+#if GLIB_CHECK_VERSION(2, 28, 0)
+ GuestTimezone *info = NULL;
+ GTimeZone *tz = NULL;
+ gint64 now = 0;
+ gint32 intv = 0;
+ gchar const *name = NULL;
+
+ info = g_new0(GuestTimezone, 1);
+ tz = g_time_zone_new_local();
+ if (tz == NULL) {
+ error_setg(errp, QERR_QGA_COMMAND_FAILED,
+ "Couldn't retrieve local timezone");
+ goto error;
+ }
+
+ now = g_get_real_time() / G_USEC_PER_SEC;
+ intv = g_time_zone_find_interval(tz, G_TIME_TYPE_UNIVERSAL, now);
+ info->offset = g_time_zone_get_offset(tz, intv);
+ name = g_time_zone_get_abbreviation(tz, intv);
+ if (name != NULL) {
+ info->has_zone = true;
+ info->zone = g_strdup(name);
+ }
+ g_time_zone_unref(tz);
+
+ return info;
+
+error:
+ g_free(info);
+ return NULL;
+#else
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+#endif
+}
diff --git a/qga/main.c b/qga/main.c
index 07c295376f..ad6f68f187 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -131,9 +131,32 @@ static void quit_handler(int sig)
* unless all log/pid files are on unfreezable filesystems. there's
* also a very likely chance killing the agent before unfreezing
* the filesystems is a mistake (or will be viewed as one later).
+ * On Windows the freeze interval is limited to 10 seconds, so
+ * we should quit, but first we should wait for the timeout, thaw
+ * the filesystem and quit.
*/
if (ga_is_frozen(ga_state)) {
+#ifdef _WIN32
+ int i = 0;
+ Error *err = NULL;
+ HANDLE hEventTimeout;
+
+ g_debug("Thawing filesystems before exiting");
+
+ hEventTimeout = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_TIMEOUT);
+ if (hEventTimeout) {
+ WaitForSingleObject(hEventTimeout, 0);
+ CloseHandle(hEventTimeout);
+ }
+ qga_vss_fsfreeze(&i, false, &err);
+ if (err) {
+ g_debug("Error unfreezing filesystems prior to exiting: %s",
+ error_get_pretty(err));
+ error_free(err);
+ }
+#else
return;
+#endif
}
g_debug("received signal num %d, quitting", sig);
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index a02dbf2d18..03743ab905 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -426,7 +426,13 @@
##
# @guest-fsfreeze-freeze:
#
-# Sync and freeze all freezable, local guest filesystems
+# Sync and freeze all freezable, local guest filesystems. If this
+# command succeeded, you may call @guest-fsfreeze-thaw later to
+# unfreeze.
+#
+# Note: On Windows, the command is implemented with the help of a
+# Volume Shadow-copy Service DLL helper. The frozen state is limited
+# for up to 10 seconds by VSS.
#
# Returns: Number of file systems currently frozen. On error, all filesystems
# will be thawed.
@@ -439,10 +445,12 @@
##
# @guest-fsfreeze-freeze-list:
#
-# Sync and freeze specified guest filesystems
+# Sync and freeze specified guest filesystems.
+# See also @guest-fsfreeze-freeze.
#
# @mountpoints: an array of mountpoints of filesystems to be frozen.
# If omitted, every mounted filesystem is frozen.
+# Invalid mount points are ignored.
#
# Returns: Number of file systems currently frozen. On error, all filesystems
# will be thawed.
@@ -1042,3 +1050,79 @@
'data': { 'path': 'str', '*arg': ['str'], '*env': ['str'],
'*input-data': 'str', '*capture-output': 'bool' },
'returns': 'GuestExec' }
+
+
+##
+# @GuestHostName:
+# @host-name: Fully qualified domain name of the guest OS
+#
+# Since: 2.10
+##
+{ 'struct': 'GuestHostName',
+ 'data': { 'host-name': 'str' } }
+
+##
+# @guest-get-host-name:
+#
+# Return a name for the machine.
+#
+# The returned name is not necessarily a fully-qualified domain name, or even
+# present in DNS or some other name service at all. It need not even be unique
+# on your local network or site, but usually it is.
+#
+# Returns: the host name of the machine on success
+#
+# Since: 2.10
+##
+{ 'command': 'guest-get-host-name',
+ 'returns': 'GuestHostName' }
+
+
+##
+# @GuestUser:
+# @user: Username
+# @domain: Logon domain (windows only)
+# @login-time: Time of login of this user on the computer. If multiple
+# instances of the user are logged in, the earliest login time is
+# reported. The value is in fractional seconds since epoch time.
+#
+# Since: 2.10
+##
+{ 'struct': 'GuestUser',
+ 'data': { 'user': 'str', 'login-time': 'number', '*domain': 'str' } }
+
+##
+# @guest-get-users:
+# Retrieves a list of currently active users on the VM.
+#
+# Returns: A unique list of users.
+#
+# Since: 2.10
+##
+{ 'command': 'guest-get-users',
+ 'returns': ['GuestUser'] }
+
+##
+# @GuestTimezone:
+#
+# @zone: Timezone name. These values may differ depending on guest/OS and
+# should only be used for informational purposes.
+# @offset: Offset to UTC in seconds, negative numbers for time zones west of
+# GMT, positive numbers for east
+#
+# Since: 2.10
+##
+{ 'struct': 'GuestTimezone',
+ 'data': { '*zone': 'str', 'offset': 'int' } }
+
+##
+# @guest-get-timezone:
+#
+# Retrieves the timezone information from the guest.
+#
+# Returns: A GuestTimezone dictionary.
+#
+# Since: 2.10
+##
+{ 'command': 'guest-get-timezone',
+ 'returns': 'GuestTimezone' }
diff --git a/qga/vss-win32.h b/qga/vss-win32.h
index 51d303a8f6..4f8e39aa5c 100644
--- a/qga/vss-win32.h
+++ b/qga/vss-win32.h
@@ -13,6 +13,7 @@
#ifndef VSS_WIN32_H
#define VSS_WIN32_H
+#include "qga/vss-win32/vss-handles.h"
bool vss_init(bool init_requester);
void vss_deinit(bool deinit_requester);
diff --git a/qga/vss-win32/install.cpp b/qga/vss-win32/install.cpp
index f4160a3a86..f41fcdfdda 100644
--- a/qga/vss-win32/install.cpp
+++ b/qga/vss-win32/install.cpp
@@ -14,7 +14,7 @@
#include "vss-common.h"
#include <inc/win2003/vscoordint.h>
-#include <comadmin.h>
+#include "install.h"
#include <wbemidl.h>
#include <comdef.h>
#include <comutil.h>
@@ -276,7 +276,7 @@ STDAPI COMRegister(void)
chk(pCatalog->CreateServiceForApplication(
_bstr_t(QGA_PROVIDER_LNAME), _bstr_t(QGA_PROVIDER_LNAME),
- _bstr_t(L"SERVICE_AUTO_START"), _bstr_t(L"SERVICE_ERROR_NORMAL"),
+ _bstr_t(L"SERVICE_DEMAND_START"), _bstr_t(L"SERVICE_ERROR_NORMAL"),
_bstr_t(L""), _bstr_t(L".\\localsystem"), _bstr_t(L""), FALSE));
chk(pCatalog->InstallComponent(_bstr_t(QGA_PROVIDER_LNAME),
_bstr_t(dllPath), _bstr_t(tlbPath),
@@ -461,3 +461,27 @@ namespace _com_util
return bstr;
}
}
+
+/* Stop QGA VSS provider service from COM+ Application Admin Catalog */
+
+STDAPI StopService(void)
+{
+ HRESULT hr;
+ COMInitializer initializer;
+ COMPointer<IUnknown> pUnknown;
+ COMPointer<ICOMAdminCatalog2> pCatalog;
+
+ int count = 0;
+
+ chk(QGAProviderFind(QGAProviderCount, (void *)&count));
+ if (count) {
+ chk(CoCreateInstance(CLSID_COMAdminCatalog, NULL, CLSCTX_INPROC_SERVER,
+ IID_IUnknown, (void **)pUnknown.replace()));
+ chk(pUnknown->QueryInterface(IID_ICOMAdminCatalog2,
+ (void **)pCatalog.replace()));
+ chk(pCatalog->ShutdownApplication(_bstr_t(QGA_PROVIDER_LNAME)));
+ }
+
+out:
+ return hr;
+}
diff --git a/qga/vss-win32/install.h b/qga/vss-win32/install.h
new file mode 100644
index 0000000000..35364afdea
--- /dev/null
+++ b/qga/vss-win32/install.h
@@ -0,0 +1,20 @@
+/*
+ * QEMU Guest Agent VSS requester declarations
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef INSTALL_H
+#define INSTALL_H
+
+#include <comadmin.h>
+
+STDAPI StopService(void);
+
+#endif
diff --git a/qga/vss-win32/provider.cpp b/qga/vss-win32/provider.cpp
index ef9466909a..72d8b0e19d 100644
--- a/qga/vss-win32/provider.cpp
+++ b/qga/vss-win32/provider.cpp
@@ -377,7 +377,6 @@ STDMETHODIMP CQGAVssProvider::CommitSnapshots(VSS_ID SnapshotSetId)
if (WaitForSingleObject(hEventThaw, VSS_TIMEOUT_MSEC) != WAIT_OBJECT_0) {
/* Send event to qemu-ga to notify the provider is timed out */
SetEvent(hEventTimeout);
- hr = E_ABORT;
}
CloseHandle(hEventThaw);
diff --git a/qga/vss-win32/requester.cpp b/qga/vss-win32/requester.cpp
index 0cd2f0ee7f..301762d8b1 100644
--- a/qga/vss-win32/requester.cpp
+++ b/qga/vss-win32/requester.cpp
@@ -13,6 +13,7 @@
#include "qemu/osdep.h"
#include "vss-common.h"
#include "requester.h"
+#include "install.h"
#include <inc/win2003/vswriter.h>
#include <inc/win2003/vsbackup.h>
@@ -501,4 +502,5 @@ void requester_thaw(int *num_vols, ErrorSet *errset)
requester_cleanup();
CoUninitialize();
+ StopService();
}
diff --git a/qga/vss-win32/vss-common.h b/qga/vss-win32/vss-common.h
index c81a8564b2..61c170b52e 100644
--- a/qga/vss-win32/vss-common.h
+++ b/qga/vss-win32/vss-common.h
@@ -51,21 +51,12 @@
* http://www.microsoft.com/en-us/download/details.aspx?id=23490
*/
#include <inc/win2003/vss.h>
+#include "vss-handles.h"
/* Macros to convert char definitions to wchar */
#define _L(a) L##a
#define L(a) _L(a)
-/* Constants for QGA VSS Provider */
-
-#define QGA_PROVIDER_NAME "QEMU Guest Agent VSS Provider"
-#define QGA_PROVIDER_LNAME L(QGA_PROVIDER_NAME)
-#define QGA_PROVIDER_VERSION L(QEMU_VERSION)
-
-#define EVENT_NAME_FROZEN "Global\\QGAVSSEvent-frozen"
-#define EVENT_NAME_THAW "Global\\QGAVSSEvent-thaw"
-#define EVENT_NAME_TIMEOUT "Global\\QGAVSSEvent-timeout"
-
const GUID g_gProviderId = { 0x3629d4ed, 0xee09, 0x4e0e,
{0x9a, 0x5c, 0x6d, 0x8b, 0xa2, 0x87, 0x2a, 0xef} };
const GUID g_gProviderVersion = { 0x11ef8b15, 0xcac6, 0x40d6,
diff --git a/qga/vss-win32/vss-handles.h b/qga/vss-win32/vss-handles.h
new file mode 100644
index 0000000000..ff399dd73a
--- /dev/null
+++ b/qga/vss-win32/vss-handles.h
@@ -0,0 +1,14 @@
+#ifndef VSS_HANDLES
+#define VSS_HANDLES
+
+/* Constants for QGA VSS Provider */
+
+#define QGA_PROVIDER_NAME "QEMU Guest Agent VSS Provider"
+#define QGA_PROVIDER_LNAME L(QGA_PROVIDER_NAME)
+#define QGA_PROVIDER_VERSION L(QEMU_VERSION)
+
+#define EVENT_NAME_FROZEN "Global\\QGAVSSEvent-frozen"
+#define EVENT_NAME_THAW "Global\\QGAVSSEvent-thaw"
+#define EVENT_NAME_TIMEOUT "Global\\QGAVSSEvent-timeout"
+
+#endif