2c958923bc
As the description to the guest-set-time states, the command is there to ease time synchronization after resume. If guest was suspended for longer period of time, its system time can go off so badly, that even NTP refuses to set it. That's why the command was invented: to give users chance to set the time (not necessarily 100% correct). However, there's is no real need for us to require users to pass an arbitrary time. Especially if we can read the correct value from RTC (boiling down to reading host's time). Hence this commit enables logic: guest-set-time() == guest-set-time($now_from_rtc) Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
436 lines
11 KiB
C
436 lines
11 KiB
C
/*
|
|
* QEMU Guest Agent win32-specific command implementations
|
|
*
|
|
* Copyright IBM Corp. 2012
|
|
*
|
|
* 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 "qapi/qmp/qerror.h"
|
|
|
|
#ifndef SHTDN_REASON_FLAG_PLANNED
|
|
#define SHTDN_REASON_FLAG_PLANNED 0x80000000
|
|
#endif
|
|
|
|
/* 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);
|
|
|
|
if (!has_mode || strcmp(mode, "powerdown") == 0) {
|
|
shutdown_flag |= EWX_POWEROFF;
|
|
} else if (strcmp(mode, "halt") == 0) {
|
|
shutdown_flag |= EWX_SHUTDOWN;
|
|
} else if (strcmp(mode, "reboot") == 0) {
|
|
shutdown_flag |= EWX_REBOOT;
|
|
} else {
|
|
error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
|
|
"halt|powerdown|reboot");
|
|
return;
|
|
}
|
|
|
|
/* Request a shutdown privilege, but try to shut down the system
|
|
anyway. */
|
|
acquire_privilege(SE_SHUTDOWN_NAME, err);
|
|
if (error_is_set(err)) {
|
|
return;
|
|
}
|
|
|
|
if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
|
|
slog("guest-shutdown failed: %lu", GetLastError());
|
|
error_set(err, QERR_UNDEFINED_ERROR);
|
|
}
|
|
}
|
|
|
|
int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err)
|
|
{
|
|
error_set(err, QERR_UNSUPPORTED);
|
|
return 0;
|
|
}
|
|
|
|
void qmp_guest_file_close(int64_t handle, Error **err)
|
|
{
|
|
error_set(err, QERR_UNSUPPORTED);
|
|
}
|
|
|
|
GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
|
|
int64_t count, Error **err)
|
|
{
|
|
error_set(err, QERR_UNSUPPORTED);
|
|
return 0;
|
|
}
|
|
|
|
GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
|
|
bool has_count, int64_t count, Error **err)
|
|
{
|
|
error_set(err, QERR_UNSUPPORTED);
|
|
return 0;
|
|
}
|
|
|
|
GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
|
|
int64_t whence, Error **err)
|
|
{
|
|
error_set(err, QERR_UNSUPPORTED);
|
|
return 0;
|
|
}
|
|
|
|
void qmp_guest_file_flush(int64_t handle, Error **err)
|
|
{
|
|
error_set(err, QERR_UNSUPPORTED);
|
|
}
|
|
|
|
/*
|
|
* Return status of freeze/thaw
|
|
*/
|
|
GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
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 (local_err) {
|
|
g_debug("cleanup thaw: %s", error_get_pretty(local_err));
|
|
error_free(local_err);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
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, %lu", GetLastError());
|
|
ret = -1;
|
|
}
|
|
g_free(mode);
|
|
return ret;
|
|
}
|
|
|
|
void qmp_guest_suspend_disk(Error **err)
|
|
{
|
|
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)
|
|
{
|
|
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(bool has_time, int64_t time_ns, Error **errp)
|
|
{
|
|
SYSTEMTIME ts;
|
|
FILETIME tf;
|
|
LONGLONG time;
|
|
|
|
if (has_time) {
|
|
/* Okay, user passed a time to set. Validate it. */
|
|
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;
|
|
}
|
|
} else {
|
|
/* Otherwise read the time from RTC which contains the correct value.
|
|
* Hopefully. */
|
|
GetSystemTime(&ts);
|
|
if (ts.wYear < 1601 || ts.wYear > 30827) {
|
|
error_setg(errp, "Failed to get time");
|
|
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);
|
|
}
|
|
}
|