diff options
-rwxr-xr-x | configure | 2 | ||||
-rw-r--r-- | qga/commands-win32.c | 132 |
2 files changed, 121 insertions, 13 deletions
@@ -525,7 +525,7 @@ EOF bindir="\${prefix}" sysconfdir="\${prefix}" confsuffix="" - libs_qga="-lws2_32 -lwinmm $lib_qga" + libs_qga="-lws2_32 -lwinmm -lpowrprof $lib_qga" fi werror="" diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 7ef185f587..062e519054 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -5,12 +5,15 @@ * * 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-qmp-commands.h" #include "qerror.h" @@ -19,10 +22,63 @@ #define SHTDN_REASON_FLAG_PLANNED 0x80000000 #endif -void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) +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); @@ -41,16 +97,9 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) /* 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)) { @@ -124,9 +173,68 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err) return 0; } +typedef enum { + GUEST_SUSPEND_MODE_DISK +} 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; + } + + if (mode == GUEST_SUSPEND_MODE_DISK) { + if (sys_pwr_caps.SystemS4) { + return; + } + } else { + error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode", + "GuestSuspendMode"); + goto out; + } + + error_set(&local_err, QERR_QGA_COMMAND_FAILED, + "suspend mode not supported by OS"); +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) |