xref: /openbmc/qemu/qga/commands-win32.c (revision a1857ad1)
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