]>
Commit | Line | Data |
---|---|---|
d8ca685a MR |
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> | |
aa59637e | 8 | * Gal Hammer <ghammer@redhat.com> |
d8ca685a MR |
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> | |
aa59637e GH |
15 | #include <wtypes.h> |
16 | #include <powrprof.h> | |
d8ca685a | 17 | #include "qga/guest-agent-core.h" |
64c00317 | 18 | #include "qga/vss-win32.h" |
d8ca685a | 19 | #include "qga-qmp-commands.h" |
7b1b5d19 | 20 | #include "qapi/qmp/qerror.h" |
d8ca685a | 21 | |
546b60d0 MR |
22 | #ifndef SHTDN_REASON_FLAG_PLANNED |
23 | #define SHTDN_REASON_FLAG_PLANNED 0x80000000 | |
24 | #endif | |
25 | ||
3f2a6087 LL |
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 | ||
77dbc81b | 32 | static void acquire_privilege(const char *name, Error **errp) |
d8ca685a | 33 | { |
374044f0 | 34 | HANDLE token = NULL; |
546b60d0 | 35 | TOKEN_PRIVILEGES priv; |
aa59637e GH |
36 | Error *local_err = NULL; |
37 | ||
aa59637e GH |
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 | ||
aa59637e GH |
56 | } else { |
57 | error_set(&local_err, QERR_QGA_COMMAND_FAILED, | |
58 | "failed to open privilege token"); | |
59 | } | |
60 | ||
61 | out: | |
374044f0 GA |
62 | if (token) { |
63 | CloseHandle(token); | |
64 | } | |
aa59637e | 65 | if (local_err) { |
77dbc81b | 66 | error_propagate(errp, local_err); |
aa59637e GH |
67 | } |
68 | } | |
69 | ||
77dbc81b MA |
70 | static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque, |
71 | Error **errp) | |
aa59637e GH |
72 | { |
73 | Error *local_err = NULL; | |
74 | ||
aa59637e GH |
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"); | |
77dbc81b | 79 | error_propagate(errp, local_err); |
aa59637e GH |
80 | } |
81 | } | |
82 | ||
77dbc81b | 83 | void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp) |
aa59637e | 84 | { |
0f230bf7 | 85 | Error *local_err = NULL; |
546b60d0 MR |
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 { | |
77dbc81b | 97 | error_set(errp, QERR_INVALID_PARAMETER_VALUE, "mode", |
546b60d0 MR |
98 | "halt|powerdown|reboot"); |
99 | return; | |
100 | } | |
101 | ||
102 | /* Request a shutdown privilege, but try to shut down the system | |
103 | anyway. */ | |
0f230bf7 MA |
104 | acquire_privilege(SE_SHUTDOWN_NAME, &local_err); |
105 | if (local_err) { | |
106 | error_propagate(errp, local_err); | |
aa59637e | 107 | return; |
546b60d0 MR |
108 | } |
109 | ||
110 | if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) { | |
16f4e8fa | 111 | slog("guest-shutdown failed: %lu", GetLastError()); |
77dbc81b | 112 | error_set(errp, QERR_UNDEFINED_ERROR); |
546b60d0 | 113 | } |
d8ca685a MR |
114 | } |
115 | ||
77dbc81b MA |
116 | int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, |
117 | Error **errp) | |
d8ca685a | 118 | { |
77dbc81b | 119 | error_set(errp, QERR_UNSUPPORTED); |
d8ca685a MR |
120 | return 0; |
121 | } | |
122 | ||
77dbc81b | 123 | void qmp_guest_file_close(int64_t handle, Error **errp) |
d8ca685a | 124 | { |
77dbc81b | 125 | error_set(errp, QERR_UNSUPPORTED); |
d8ca685a MR |
126 | } |
127 | ||
128 | GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, | |
77dbc81b | 129 | int64_t count, Error **errp) |
d8ca685a | 130 | { |
77dbc81b | 131 | error_set(errp, QERR_UNSUPPORTED); |
d8ca685a MR |
132 | return 0; |
133 | } | |
134 | ||
135 | GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, | |
77dbc81b MA |
136 | bool has_count, int64_t count, |
137 | Error **errp) | |
d8ca685a | 138 | { |
77dbc81b | 139 | error_set(errp, QERR_UNSUPPORTED); |
d8ca685a MR |
140 | return 0; |
141 | } | |
142 | ||
143 | GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, | |
77dbc81b | 144 | int64_t whence, Error **errp) |
d8ca685a | 145 | { |
77dbc81b | 146 | error_set(errp, QERR_UNSUPPORTED); |
d8ca685a MR |
147 | return 0; |
148 | } | |
149 | ||
77dbc81b | 150 | void qmp_guest_file_flush(int64_t handle, Error **errp) |
d8ca685a | 151 | { |
77dbc81b | 152 | error_set(errp, QERR_UNSUPPORTED); |
d8ca685a MR |
153 | } |
154 | ||
46d4c572 TS |
155 | GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) |
156 | { | |
157 | error_set(errp, QERR_UNSUPPORTED); | |
158 | return NULL; | |
159 | } | |
160 | ||
d8ca685a MR |
161 | /* |
162 | * Return status of freeze/thaw | |
163 | */ | |
77dbc81b | 164 | GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp) |
d8ca685a | 165 | { |
64c00317 | 166 | if (!vss_initialized()) { |
77dbc81b | 167 | error_set(errp, QERR_UNSUPPORTED); |
64c00317 TS |
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; | |
d8ca685a MR |
176 | } |
177 | ||
178 | /* | |
64c00317 TS |
179 | * Freeze local file systems using Volume Shadow-copy Service. |
180 | * The frozen state is limited for up to 10 seconds by VSS. | |
d8ca685a | 181 | */ |
77dbc81b | 182 | int64_t qmp_guest_fsfreeze_freeze(Error **errp) |
d8ca685a | 183 | { |
64c00317 TS |
184 | int i; |
185 | Error *local_err = NULL; | |
186 | ||
187 | if (!vss_initialized()) { | |
77dbc81b | 188 | error_set(errp, QERR_UNSUPPORTED); |
64c00317 TS |
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 | ||
0f230bf7 MA |
197 | qga_vss_fsfreeze(&i, &local_err, true); |
198 | if (local_err) { | |
199 | error_propagate(errp, local_err); | |
64c00317 TS |
200 | goto error; |
201 | } | |
202 | ||
203 | return i; | |
204 | ||
205 | error: | |
0f230bf7 | 206 | local_err = NULL; |
64c00317 | 207 | qmp_guest_fsfreeze_thaw(&local_err); |
84d18f06 | 208 | if (local_err) { |
64c00317 TS |
209 | g_debug("cleanup thaw: %s", error_get_pretty(local_err)); |
210 | error_free(local_err); | |
211 | } | |
d8ca685a MR |
212 | return 0; |
213 | } | |
214 | ||
e99bce20 TS |
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 | ||
d8ca685a | 224 | /* |
64c00317 | 225 | * Thaw local file systems using Volume Shadow-copy Service. |
d8ca685a | 226 | */ |
77dbc81b | 227 | int64_t qmp_guest_fsfreeze_thaw(Error **errp) |
d8ca685a | 228 | { |
64c00317 TS |
229 | int i; |
230 | ||
231 | if (!vss_initialized()) { | |
77dbc81b | 232 | error_set(errp, QERR_UNSUPPORTED); |
64c00317 TS |
233 | return 0; |
234 | } | |
235 | ||
77dbc81b | 236 | qga_vss_fsfreeze(&i, errp, false); |
64c00317 TS |
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); | |
d8ca685a MR |
260 | } |
261 | ||
eab5fd59 PB |
262 | /* |
263 | * Walk list of mounted file systems in the guest, and discard unused | |
264 | * areas. | |
265 | */ | |
77dbc81b | 266 | void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp) |
eab5fd59 | 267 | { |
77dbc81b | 268 | error_set(errp, QERR_UNSUPPORTED); |
eab5fd59 PB |
269 | } |
270 | ||
aa59637e | 271 | typedef enum { |
f54603b6 MR |
272 | GUEST_SUSPEND_MODE_DISK, |
273 | GUEST_SUSPEND_MODE_RAM | |
aa59637e GH |
274 | } GuestSuspendMode; |
275 | ||
77dbc81b | 276 | static void check_suspend_mode(GuestSuspendMode mode, Error **errp) |
aa59637e GH |
277 | { |
278 | SYSTEM_POWER_CAPABILITIES sys_pwr_caps; | |
279 | Error *local_err = NULL; | |
280 | ||
aa59637e GH |
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 | ||
f54603b6 MR |
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"); | |
aa59637e | 293 | } |
f54603b6 MR |
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: | |
aa59637e GH |
302 | error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode", |
303 | "GuestSuspendMode"); | |
aa59637e GH |
304 | } |
305 | ||
aa59637e GH |
306 | out: |
307 | if (local_err) { | |
77dbc81b | 308 | error_propagate(errp, local_err); |
aa59637e GH |
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)) { | |
16f4e8fa | 318 | slog("failed to suspend guest, %lu", GetLastError()); |
aa59637e GH |
319 | ret = -1; |
320 | } | |
321 | g_free(mode); | |
322 | return ret; | |
323 | } | |
324 | ||
77dbc81b | 325 | void qmp_guest_suspend_disk(Error **errp) |
11d0f125 | 326 | { |
0f230bf7 | 327 | Error *local_err = NULL; |
aa59637e GH |
328 | GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode)); |
329 | ||
330 | *mode = GUEST_SUSPEND_MODE_DISK; | |
0f230bf7 MA |
331 | check_suspend_mode(*mode, &local_err); |
332 | acquire_privilege(SE_SHUTDOWN_NAME, &local_err); | |
333 | execute_async(do_suspend, mode, &local_err); | |
aa59637e | 334 | |
0f230bf7 MA |
335 | if (local_err) { |
336 | error_propagate(errp, local_err); | |
aa59637e GH |
337 | g_free(mode); |
338 | } | |
11d0f125 LC |
339 | } |
340 | ||
77dbc81b | 341 | void qmp_guest_suspend_ram(Error **errp) |
fbf42210 | 342 | { |
0f230bf7 | 343 | Error *local_err = NULL; |
f54603b6 MR |
344 | GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode)); |
345 | ||
346 | *mode = GUEST_SUSPEND_MODE_RAM; | |
0f230bf7 MA |
347 | check_suspend_mode(*mode, &local_err); |
348 | acquire_privilege(SE_SHUTDOWN_NAME, &local_err); | |
349 | execute_async(do_suspend, mode, &local_err); | |
f54603b6 | 350 | |
0f230bf7 MA |
351 | if (local_err) { |
352 | error_propagate(errp, local_err); | |
f54603b6 MR |
353 | g_free(mode); |
354 | } | |
fbf42210 LC |
355 | } |
356 | ||
77dbc81b | 357 | void qmp_guest_suspend_hybrid(Error **errp) |
95f4f404 | 358 | { |
77dbc81b | 359 | error_set(errp, QERR_UNSUPPORTED); |
95f4f404 LC |
360 | } |
361 | ||
77dbc81b | 362 | GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) |
3424fc9f | 363 | { |
77dbc81b | 364 | error_set(errp, QERR_UNSUPPORTED); |
3424fc9f MP |
365 | return NULL; |
366 | } | |
367 | ||
6912e6a9 LL |
368 | int64_t qmp_guest_get_time(Error **errp) |
369 | { | |
3f2a6087 LL |
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; | |
6912e6a9 LL |
389 | } |
390 | ||
2c958923 | 391 | void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp) |
a1bca57f | 392 | { |
0f230bf7 | 393 | Error *local_err = NULL; |
b8f954fe LL |
394 | SYSTEMTIME ts; |
395 | FILETIME tf; | |
396 | LONGLONG time; | |
397 | ||
2c958923 MP |
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 | } | |
b8f954fe | 404 | |
2c958923 | 405 | time = time_ns / 100 + W32_FT_OFFSET; |
b8f954fe | 406 | |
2c958923 MP |
407 | tf.dwLowDateTime = (DWORD) time; |
408 | tf.dwHighDateTime = (DWORD) (time >> 32); | |
b8f954fe | 409 | |
2c958923 MP |
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 | } | |
b8f954fe LL |
423 | } |
424 | ||
0f230bf7 MA |
425 | acquire_privilege(SE_SYSTEMTIME_NAME, &local_err); |
426 | if (local_err) { | |
427 | error_propagate(errp, local_err); | |
b8f954fe LL |
428 | return; |
429 | } | |
430 | ||
431 | if (!SetSystemTime(&ts)) { | |
432 | error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError()); | |
433 | return; | |
434 | } | |
a1bca57f LL |
435 | } |
436 | ||
70e133a7 LE |
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 | ||
1281c08a TS |
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 | ||
d8ca685a MR |
479 | /* register init/cleanup routines for stateful command groups */ |
480 | void ga_command_state_init(GAState *s, GACommandState *cs) | |
481 | { | |
1281c08a | 482 | if (!vss_initialized()) { |
64c00317 TS |
483 | ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup); |
484 | } | |
d8ca685a | 485 | } |