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 32 static 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 61 out: 62 if (token) { 63 CloseHandle(token); 64 } 65 if (local_err) { 66 error_propagate(errp, local_err); 67 } 68 } 69 70 static 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 83 void 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 116 int64_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 123 void qmp_guest_file_close(int64_t handle, Error **errp) 124 { 125 error_set(errp, QERR_UNSUPPORTED); 126 } 127 128 GuestFileRead *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 135 GuestFileWrite *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 143 GuestFileSeek *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 150 void qmp_guest_file_flush(int64_t handle, Error **errp) 151 { 152 error_set(errp, QERR_UNSUPPORTED); 153 } 154 155 GuestFilesystemInfoList *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 */ 164 GuestFsfreezeStatus 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 */ 182 int64_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 205 error: 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 215 int64_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 */ 227 int64_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 242 static 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 */ 266 void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp) 267 { 268 error_set(errp, QERR_UNSUPPORTED); 269 } 270 271 typedef enum { 272 GUEST_SUSPEND_MODE_DISK, 273 GUEST_SUSPEND_MODE_RAM 274 } GuestSuspendMode; 275 276 static 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 306 out: 307 if (local_err) { 308 error_propagate(errp, local_err); 309 } 310 } 311 312 static 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 325 void 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 341 void 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 357 void qmp_guest_suspend_hybrid(Error **errp) 358 { 359 error_set(errp, QERR_UNSUPPORTED); 360 } 361 362 GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) 363 { 364 error_set(errp, QERR_UNSUPPORTED); 365 return NULL; 366 } 367 368 int64_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 391 void 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 437 GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) 438 { 439 error_set(errp, QERR_UNSUPPORTED); 440 return NULL; 441 } 442 443 int64_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 */ 450 GList *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 */ 480 void 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