qemu/qga/commands-win32.c
<<
>>
Prefs
   1/*
   2 * QEMU Guest Agent win32-specific command implementations
   3 *
   4 * Copyright IBM Corp. 2012
   5 *
   6 * Authors:
   7 *  Michael Roth      <mdroth@linux.vnet.ibm.com>
   8 *  Gal Hammer        <ghammer@redhat.com>
   9 *
  10 * This work is licensed under the terms of the GNU GPL, version 2 or later.
  11 * See the COPYING file in the top-level directory.
  12 */
  13
  14#include <glib.h>
  15#include <wtypes.h>
  16#include <powrprof.h>
  17#include "qga/guest-agent-core.h"
  18#include "qga/vss-win32.h"
  19#include "qga-qmp-commands.h"
  20#include "qapi/qmp/qerror.h"
  21
  22#ifndef SHTDN_REASON_FLAG_PLANNED
  23#define SHTDN_REASON_FLAG_PLANNED 0x80000000
  24#endif
  25
  26/* multiple of 100 nanoseconds elapsed between windows baseline
  27 *    (1/1/1601) and Unix Epoch (1/1/1970), accounting for leap years */
  28#define W32_FT_OFFSET (10000000ULL * 60 * 60 * 24 * \
  29                       (365 * (1970 - 1601) +       \
  30                        (1970 - 1601) / 4 - 3))
  31
  32static void acquire_privilege(const char *name, Error **errp)
  33{
  34    HANDLE token = NULL;
  35    TOKEN_PRIVILEGES priv;
  36    Error *local_err = NULL;
  37
  38    if (OpenProcessToken(GetCurrentProcess(),
  39        TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
  40    {
  41        if (!LookupPrivilegeValue(NULL, name, &priv.Privileges[0].Luid)) {
  42            error_set(&local_err, QERR_QGA_COMMAND_FAILED,
  43                      "no luid for requested privilege");
  44            goto out;
  45        }
  46
  47        priv.PrivilegeCount = 1;
  48        priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  49
  50        if (!AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0)) {
  51            error_set(&local_err, QERR_QGA_COMMAND_FAILED,
  52                      "unable to acquire requested privilege");
  53            goto out;
  54        }
  55
  56    } else {
  57        error_set(&local_err, QERR_QGA_COMMAND_FAILED,
  58                  "failed to open privilege token");
  59    }
  60
  61out:
  62    if (token) {
  63        CloseHandle(token);
  64    }
  65    if (local_err) {
  66        error_propagate(errp, local_err);
  67    }
  68}
  69
  70static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque,
  71                          Error **errp)
  72{
  73    Error *local_err = NULL;
  74
  75    HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
  76    if (!thread) {
  77        error_set(&local_err, QERR_QGA_COMMAND_FAILED,
  78                  "failed to dispatch asynchronous command");
  79        error_propagate(errp, local_err);
  80    }
  81}
  82
  83void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
  84{
  85    Error *local_err = NULL;
  86    UINT shutdown_flag = EWX_FORCE;
  87
  88    slog("guest-shutdown called, mode: %s", mode);
  89
  90    if (!has_mode || strcmp(mode, "powerdown") == 0) {
  91        shutdown_flag |= EWX_POWEROFF;
  92    } else if (strcmp(mode, "halt") == 0) {
  93        shutdown_flag |= EWX_SHUTDOWN;
  94    } else if (strcmp(mode, "reboot") == 0) {
  95        shutdown_flag |= EWX_REBOOT;
  96    } else {
  97        error_set(errp, QERR_INVALID_PARAMETER_VALUE, "mode",
  98                  "halt|powerdown|reboot");
  99        return;
 100    }
 101
 102    /* Request a shutdown privilege, but try to shut down the system
 103       anyway. */
 104    acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
 105    if (local_err) {
 106        error_propagate(errp, local_err);
 107        return;
 108    }
 109
 110    if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
 111        slog("guest-shutdown failed: %lu", GetLastError());
 112        error_set(errp, QERR_UNDEFINED_ERROR);
 113    }
 114}
 115
 116int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode,
 117                            Error **errp)
 118{
 119    error_set(errp, QERR_UNSUPPORTED);
 120    return 0;
 121}
 122
 123void qmp_guest_file_close(int64_t handle, Error **errp)
 124{
 125    error_set(errp, QERR_UNSUPPORTED);
 126}
 127
 128GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
 129                                   int64_t count, Error **errp)
 130{
 131    error_set(errp, QERR_UNSUPPORTED);
 132    return 0;
 133}
 134
 135GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
 136                                     bool has_count, int64_t count,
 137                                     Error **errp)
 138{
 139    error_set(errp, QERR_UNSUPPORTED);
 140    return 0;
 141}
 142
 143GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
 144                                   int64_t whence, Error **errp)
 145{
 146    error_set(errp, QERR_UNSUPPORTED);
 147    return 0;
 148}
 149
 150void qmp_guest_file_flush(int64_t handle, Error **errp)
 151{
 152    error_set(errp, QERR_UNSUPPORTED);
 153}
 154
 155GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
 156{
 157    error_set(errp, QERR_UNSUPPORTED);
 158    return NULL;
 159}
 160
 161/*
 162 * Return status of freeze/thaw
 163 */
 164GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
 165{
 166    if (!vss_initialized()) {
 167        error_set(errp, QERR_UNSUPPORTED);
 168        return 0;
 169    }
 170
 171    if (ga_is_frozen(ga_state)) {
 172        return GUEST_FSFREEZE_STATUS_FROZEN;
 173    }
 174
 175    return GUEST_FSFREEZE_STATUS_THAWED;
 176}
 177
 178/*
 179 * Freeze local file systems using Volume Shadow-copy Service.
 180 * The frozen state is limited for up to 10 seconds by VSS.
 181 */
 182int64_t qmp_guest_fsfreeze_freeze(Error **errp)
 183{
 184    int i;
 185    Error *local_err = NULL;
 186
 187    if (!vss_initialized()) {
 188        error_set(errp, QERR_UNSUPPORTED);
 189        return 0;
 190    }
 191
 192    slog("guest-fsfreeze called");
 193
 194    /* cannot risk guest agent blocking itself on a write in this state */
 195    ga_set_frozen(ga_state);
 196
 197    qga_vss_fsfreeze(&i, &local_err, true);
 198    if (local_err) {
 199        error_propagate(errp, local_err);
 200        goto error;
 201    }
 202
 203    return i;
 204
 205error:
 206    local_err = NULL;
 207    qmp_guest_fsfreeze_thaw(&local_err);
 208    if (local_err) {
 209        g_debug("cleanup thaw: %s", error_get_pretty(local_err));
 210        error_free(local_err);
 211    }
 212    return 0;
 213}
 214
 215int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
 216                                       strList *mountpoints,
 217                                       Error **errp)
 218{
 219    error_set(errp, QERR_UNSUPPORTED);
 220
 221    return 0;
 222}
 223
 224/*
 225 * Thaw local file systems using Volume Shadow-copy Service.
 226 */
 227int64_t qmp_guest_fsfreeze_thaw(Error **errp)
 228{
 229    int i;
 230
 231    if (!vss_initialized()) {
 232        error_set(errp, QERR_UNSUPPORTED);
 233        return 0;
 234    }
 235
 236    qga_vss_fsfreeze(&i, errp, false);
 237
 238    ga_unset_frozen(ga_state);
 239    return i;
 240}
 241
 242static void guest_fsfreeze_cleanup(void)
 243{
 244    Error *err = NULL;
 245
 246    if (!vss_initialized()) {
 247        return;
 248    }
 249
 250    if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
 251        qmp_guest_fsfreeze_thaw(&err);
 252        if (err) {
 253            slog("failed to clean up frozen filesystems: %s",
 254                 error_get_pretty(err));
 255            error_free(err);
 256        }
 257    }
 258
 259    vss_deinit(true);
 260}
 261
 262/*
 263 * Walk list of mounted file systems in the guest, and discard unused
 264 * areas.
 265 */
 266void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
 267{
 268    error_set(errp, QERR_UNSUPPORTED);
 269}
 270
 271typedef enum {
 272    GUEST_SUSPEND_MODE_DISK,
 273    GUEST_SUSPEND_MODE_RAM
 274} GuestSuspendMode;
 275
 276static void check_suspend_mode(GuestSuspendMode mode, Error **errp)
 277{
 278    SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
 279    Error *local_err = NULL;
 280
 281    ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
 282    if (!GetPwrCapabilities(&sys_pwr_caps)) {
 283        error_set(&local_err, QERR_QGA_COMMAND_FAILED,
 284                  "failed to determine guest suspend capabilities");
 285        goto out;
 286    }
 287
 288    switch (mode) {
 289    case GUEST_SUSPEND_MODE_DISK:
 290        if (!sys_pwr_caps.SystemS4) {
 291            error_set(&local_err, QERR_QGA_COMMAND_FAILED,
 292                      "suspend-to-disk not supported by OS");
 293        }
 294        break;
 295    case GUEST_SUSPEND_MODE_RAM:
 296        if (!sys_pwr_caps.SystemS3) {
 297            error_set(&local_err, QERR_QGA_COMMAND_FAILED,
 298                      "suspend-to-ram not supported by OS");
 299        }
 300        break;
 301    default:
 302        error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
 303                  "GuestSuspendMode");
 304    }
 305
 306out:
 307    if (local_err) {
 308        error_propagate(errp, local_err);
 309    }
 310}
 311
 312static DWORD WINAPI do_suspend(LPVOID opaque)
 313{
 314    GuestSuspendMode *mode = opaque;
 315    DWORD ret = 0;
 316
 317    if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
 318        slog("failed to suspend guest, %lu", GetLastError());
 319        ret = -1;
 320    }
 321    g_free(mode);
 322    return ret;
 323}
 324
 325void qmp_guest_suspend_disk(Error **errp)
 326{
 327    Error *local_err = NULL;
 328    GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
 329
 330    *mode = GUEST_SUSPEND_MODE_DISK;
 331    check_suspend_mode(*mode, &local_err);
 332    acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
 333    execute_async(do_suspend, mode, &local_err);
 334
 335    if (local_err) {
 336        error_propagate(errp, local_err);
 337        g_free(mode);
 338    }
 339}
 340
 341void qmp_guest_suspend_ram(Error **errp)
 342{
 343    Error *local_err = NULL;
 344    GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
 345
 346    *mode = GUEST_SUSPEND_MODE_RAM;
 347    check_suspend_mode(*mode, &local_err);
 348    acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
 349    execute_async(do_suspend, mode, &local_err);
 350
 351    if (local_err) {
 352        error_propagate(errp, local_err);
 353        g_free(mode);
 354    }
 355}
 356
 357void qmp_guest_suspend_hybrid(Error **errp)
 358{
 359    error_set(errp, QERR_UNSUPPORTED);
 360}
 361
 362GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
 363{
 364    error_set(errp, QERR_UNSUPPORTED);
 365    return NULL;
 366}
 367
 368int64_t qmp_guest_get_time(Error **errp)
 369{
 370    SYSTEMTIME ts = {0};
 371    int64_t time_ns;
 372    FILETIME tf;
 373
 374    GetSystemTime(&ts);
 375    if (ts.wYear < 1601 || ts.wYear > 30827) {
 376        error_setg(errp, "Failed to get time");
 377        return -1;
 378    }
 379
 380    if (!SystemTimeToFileTime(&ts, &tf)) {
 381        error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
 382        return -1;
 383    }
 384
 385    time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
 386                - W32_FT_OFFSET) * 100;
 387
 388    return time_ns;
 389}
 390
 391void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
 392{
 393    Error *local_err = NULL;
 394    SYSTEMTIME ts;
 395    FILETIME tf;
 396    LONGLONG time;
 397
 398    if (has_time) {
 399        /* Okay, user passed a time to set. Validate it. */
 400        if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
 401            error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
 402            return;
 403        }
 404
 405        time = time_ns / 100 + W32_FT_OFFSET;
 406
 407        tf.dwLowDateTime = (DWORD) time;
 408        tf.dwHighDateTime = (DWORD) (time >> 32);
 409
 410        if (!FileTimeToSystemTime(&tf, &ts)) {
 411            error_setg(errp, "Failed to convert system time %d",
 412                       (int)GetLastError());
 413            return;
 414        }
 415    } else {
 416        /* Otherwise read the time from RTC which contains the correct value.
 417         * Hopefully. */
 418        GetSystemTime(&ts);
 419        if (ts.wYear < 1601 || ts.wYear > 30827) {
 420            error_setg(errp, "Failed to get time");
 421            return;
 422        }
 423    }
 424
 425    acquire_privilege(SE_SYSTEMTIME_NAME, &local_err);
 426    if (local_err) {
 427        error_propagate(errp, local_err);
 428        return;
 429    }
 430
 431    if (!SetSystemTime(&ts)) {
 432        error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
 433        return;
 434    }
 435}
 436
 437GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
 438{
 439    error_set(errp, QERR_UNSUPPORTED);
 440    return NULL;
 441}
 442
 443int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
 444{
 445    error_set(errp, QERR_UNSUPPORTED);
 446    return -1;
 447}
 448
 449/* add unsupported commands to the blacklist */
 450GList *ga_command_blacklist_init(GList *blacklist)
 451{
 452    const char *list_unsupported[] = {
 453        "guest-file-open", "guest-file-close", "guest-file-read",
 454        "guest-file-write", "guest-file-seek", "guest-file-flush",
 455        "guest-suspend-hybrid", "guest-network-get-interfaces",
 456        "guest-get-vcpus", "guest-set-vcpus",
 457        "guest-fsfreeze-freeze-list", "guest-get-fsinfo",
 458        "guest-fstrim", NULL};
 459    char **p = (char **)list_unsupported;
 460
 461    while (*p) {
 462        blacklist = g_list_append(blacklist, *p++);
 463    }
 464
 465    if (!vss_init(true)) {
 466        const char *list[] = {
 467            "guest-get-fsinfo", "guest-fsfreeze-status",
 468            "guest-fsfreeze-freeze", "guest-fsfreeze-thaw", NULL};
 469        p = (char **)list;
 470
 471        while (*p) {
 472            blacklist = g_list_append(blacklist, *p++);
 473        }
 474    }
 475
 476    return blacklist;
 477}
 478
 479/* register init/cleanup routines for stateful command groups */
 480void ga_command_state_init(GAState *s, GACommandState *cs)
 481{
 482    if (!vss_initialized()) {
 483        ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
 484    }
 485}
 486