*
* Authors:
* Michael Roth <mdroth@linux.vnet.ibm.com>
+ * Gal Hammer <ghammer@redhat.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.
*/
#include <glib.h>
+#include <wtypes.h>
+#include <powrprof.h>
#include "qga/guest-agent-core.h"
+#include "qga/vss-win32.h"
#include "qga-qmp-commands.h"
-#include "qerror.h"
+#include "qapi/qmp/qerror.h"
#ifndef SHTDN_REASON_FLAG_PLANNED
#define SHTDN_REASON_FLAG_PLANNED 0x80000000
#endif
-void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
+/* multiple of 100 nanoseconds elapsed between windows baseline
+ * (1/1/1601) and Unix Epoch (1/1/1970), accounting for leap years */
+#define W32_FT_OFFSET (10000000ULL * 60 * 60 * 24 * \
+ (365 * (1970 - 1601) + \
+ (1970 - 1601) / 4 - 3))
+
+static void acquire_privilege(const char *name, Error **err)
{
HANDLE token;
TOKEN_PRIVILEGES priv;
+ Error *local_err = NULL;
+
+ if (error_is_set(err)) {
+ return;
+ }
+
+ if (OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
+ {
+ if (!LookupPrivilegeValue(NULL, name, &priv.Privileges[0].Luid)) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "no luid for requested privilege");
+ goto out;
+ }
+
+ priv.PrivilegeCount = 1;
+ priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ if (!AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0)) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "unable to acquire requested privilege");
+ goto out;
+ }
+
+ CloseHandle(token);
+ } else {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to open privilege token");
+ }
+
+out:
+ if (local_err) {
+ error_propagate(err, local_err);
+ }
+}
+
+static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque, Error **err)
+{
+ Error *local_err = NULL;
+
+ if (error_is_set(err)) {
+ return;
+ }
+ HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
+ if (!thread) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to dispatch asynchronous command");
+ error_propagate(err, local_err);
+ }
+}
+
+void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
+{
UINT shutdown_flag = EWX_FORCE;
slog("guest-shutdown called, mode: %s", mode);
/* Request a shutdown privilege, but try to shut down the system
anyway. */
- if (OpenProcessToken(GetCurrentProcess(),
- TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
- {
- LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
- &priv.Privileges[0].Luid);
-
- priv.PrivilegeCount = 1;
- priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
-
- AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0);
+ acquire_privilege(SE_SHUTDOWN_NAME, err);
+ if (error_is_set(err)) {
+ return;
}
if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
*/
GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
{
- error_set(err, QERR_UNSUPPORTED);
- return 0;
+ if (!vss_initialized()) {
+ error_set(err, QERR_UNSUPPORTED);
+ return 0;
+ }
+
+ if (ga_is_frozen(ga_state)) {
+ return GUEST_FSFREEZE_STATUS_FROZEN;
+ }
+
+ return GUEST_FSFREEZE_STATUS_THAWED;
}
/*
- * Walk list of mounted file systems in the guest, and freeze the ones which
- * are real local file systems.
+ * Freeze local file systems using Volume Shadow-copy Service.
+ * The frozen state is limited for up to 10 seconds by VSS.
*/
int64_t qmp_guest_fsfreeze_freeze(Error **err)
{
- error_set(err, QERR_UNSUPPORTED);
+ int i;
+ Error *local_err = NULL;
+
+ if (!vss_initialized()) {
+ error_set(err, QERR_UNSUPPORTED);
+ return 0;
+ }
+
+ slog("guest-fsfreeze called");
+
+ /* cannot risk guest agent blocking itself on a write in this state */
+ ga_set_frozen(ga_state);
+
+ qga_vss_fsfreeze(&i, err, true);
+ if (error_is_set(err)) {
+ goto error;
+ }
+
+ return i;
+
+error:
+ qmp_guest_fsfreeze_thaw(&local_err);
+ if (error_is_set(&local_err)) {
+ g_debug("cleanup thaw: %s", error_get_pretty(local_err));
+ error_free(local_err);
+ }
return 0;
}
/*
- * Walk list of frozen file systems in the guest, and thaw them.
+ * Thaw local file systems using Volume Shadow-copy Service.
*/
int64_t qmp_guest_fsfreeze_thaw(Error **err)
+{
+ int i;
+
+ if (!vss_initialized()) {
+ error_set(err, QERR_UNSUPPORTED);
+ return 0;
+ }
+
+ qga_vss_fsfreeze(&i, err, false);
+
+ ga_unset_frozen(ga_state);
+ return i;
+}
+
+static void guest_fsfreeze_cleanup(void)
+{
+ Error *err = NULL;
+
+ if (!vss_initialized()) {
+ return;
+ }
+
+ if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
+ qmp_guest_fsfreeze_thaw(&err);
+ if (err) {
+ slog("failed to clean up frozen filesystems: %s",
+ error_get_pretty(err));
+ error_free(err);
+ }
+ }
+
+ vss_deinit(true);
+}
+
+/*
+ * Walk list of mounted file systems in the guest, and discard unused
+ * areas.
+ */
+void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err)
{
error_set(err, QERR_UNSUPPORTED);
- return 0;
+}
+
+typedef enum {
+ GUEST_SUSPEND_MODE_DISK,
+ GUEST_SUSPEND_MODE_RAM
+} GuestSuspendMode;
+
+static void check_suspend_mode(GuestSuspendMode mode, Error **err)
+{
+ SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
+ Error *local_err = NULL;
+
+ if (error_is_set(err)) {
+ return;
+ }
+ ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
+ if (!GetPwrCapabilities(&sys_pwr_caps)) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to determine guest suspend capabilities");
+ goto out;
+ }
+
+ switch (mode) {
+ case GUEST_SUSPEND_MODE_DISK:
+ if (!sys_pwr_caps.SystemS4) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "suspend-to-disk not supported by OS");
+ }
+ break;
+ case GUEST_SUSPEND_MODE_RAM:
+ if (!sys_pwr_caps.SystemS3) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "suspend-to-ram not supported by OS");
+ }
+ break;
+ default:
+ error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
+ "GuestSuspendMode");
+ }
+
+out:
+ if (local_err) {
+ error_propagate(err, local_err);
+ }
+}
+
+static DWORD WINAPI do_suspend(LPVOID opaque)
+{
+ GuestSuspendMode *mode = opaque;
+ DWORD ret = 0;
+
+ if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
+ slog("failed to suspend guest, %s", GetLastError());
+ ret = -1;
+ }
+ g_free(mode);
+ return ret;
}
void qmp_guest_suspend_disk(Error **err)
{
- error_set(err, QERR_UNSUPPORTED);
+ GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
+
+ *mode = GUEST_SUSPEND_MODE_DISK;
+ check_suspend_mode(*mode, err);
+ acquire_privilege(SE_SHUTDOWN_NAME, err);
+ execute_async(do_suspend, mode, err);
+
+ if (error_is_set(err)) {
+ g_free(mode);
+ }
}
void qmp_guest_suspend_ram(Error **err)
{
- error_set(err, QERR_UNSUPPORTED);
+ GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
+
+ *mode = GUEST_SUSPEND_MODE_RAM;
+ check_suspend_mode(*mode, err);
+ acquire_privilege(SE_SHUTDOWN_NAME, err);
+ execute_async(do_suspend, mode, err);
+
+ if (error_is_set(err)) {
+ g_free(mode);
+ }
}
void qmp_guest_suspend_hybrid(Error **err)
error_set(err, QERR_UNSUPPORTED);
}
+GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **err)
+{
+ error_set(err, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+int64_t qmp_guest_get_time(Error **errp)
+{
+ SYSTEMTIME ts = {0};
+ int64_t time_ns;
+ FILETIME tf;
+
+ GetSystemTime(&ts);
+ if (ts.wYear < 1601 || ts.wYear > 30827) {
+ error_setg(errp, "Failed to get time");
+ return -1;
+ }
+
+ if (!SystemTimeToFileTime(&ts, &tf)) {
+ error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
+ return -1;
+ }
+
+ time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
+ - W32_FT_OFFSET) * 100;
+
+ return time_ns;
+}
+
+void qmp_guest_set_time(int64_t time_ns, Error **errp)
+{
+ SYSTEMTIME ts;
+ FILETIME tf;
+ LONGLONG time;
+
+ if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
+ error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
+ return;
+ }
+
+ time = time_ns / 100 + W32_FT_OFFSET;
+
+ tf.dwLowDateTime = (DWORD) time;
+ tf.dwHighDateTime = (DWORD) (time >> 32);
+
+ if (!FileTimeToSystemTime(&tf, &ts)) {
+ error_setg(errp, "Failed to convert system time %d", (int)GetLastError());
+ return;
+ }
+
+ acquire_privilege(SE_SYSTEMTIME_NAME, errp);
+ if (error_is_set(errp)) {
+ return;
+ }
+
+ if (!SetSystemTime(&ts)) {
+ error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
+ return;
+ }
+}
+
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+ error_set(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
+{
+ error_set(errp, QERR_UNSUPPORTED);
+ return -1;
+}
+
/* register init/cleanup routines for stateful command groups */
void ga_command_state_init(GAState *s, GACommandState *cs)
{
+ if (vss_init(true)) {
+ ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
+ }
}