xref: /qemu/qga/commands-win32.c (revision 72ac97cd)
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 /*
156  * Return status of freeze/thaw
157  */
158 GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
159 {
160     if (!vss_initialized()) {
161         error_set(errp, QERR_UNSUPPORTED);
162         return 0;
163     }
164 
165     if (ga_is_frozen(ga_state)) {
166         return GUEST_FSFREEZE_STATUS_FROZEN;
167     }
168 
169     return GUEST_FSFREEZE_STATUS_THAWED;
170 }
171 
172 /*
173  * Freeze local file systems using Volume Shadow-copy Service.
174  * The frozen state is limited for up to 10 seconds by VSS.
175  */
176 int64_t qmp_guest_fsfreeze_freeze(Error **errp)
177 {
178     int i;
179     Error *local_err = NULL;
180 
181     if (!vss_initialized()) {
182         error_set(errp, QERR_UNSUPPORTED);
183         return 0;
184     }
185 
186     slog("guest-fsfreeze called");
187 
188     /* cannot risk guest agent blocking itself on a write in this state */
189     ga_set_frozen(ga_state);
190 
191     qga_vss_fsfreeze(&i, &local_err, true);
192     if (local_err) {
193         error_propagate(errp, local_err);
194         goto error;
195     }
196 
197     return i;
198 
199 error:
200     local_err = NULL;
201     qmp_guest_fsfreeze_thaw(&local_err);
202     if (local_err) {
203         g_debug("cleanup thaw: %s", error_get_pretty(local_err));
204         error_free(local_err);
205     }
206     return 0;
207 }
208 
209 /*
210  * Thaw local file systems using Volume Shadow-copy Service.
211  */
212 int64_t qmp_guest_fsfreeze_thaw(Error **errp)
213 {
214     int i;
215 
216     if (!vss_initialized()) {
217         error_set(errp, QERR_UNSUPPORTED);
218         return 0;
219     }
220 
221     qga_vss_fsfreeze(&i, errp, false);
222 
223     ga_unset_frozen(ga_state);
224     return i;
225 }
226 
227 static void guest_fsfreeze_cleanup(void)
228 {
229     Error *err = NULL;
230 
231     if (!vss_initialized()) {
232         return;
233     }
234 
235     if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
236         qmp_guest_fsfreeze_thaw(&err);
237         if (err) {
238             slog("failed to clean up frozen filesystems: %s",
239                  error_get_pretty(err));
240             error_free(err);
241         }
242     }
243 
244     vss_deinit(true);
245 }
246 
247 /*
248  * Walk list of mounted file systems in the guest, and discard unused
249  * areas.
250  */
251 void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
252 {
253     error_set(errp, QERR_UNSUPPORTED);
254 }
255 
256 typedef enum {
257     GUEST_SUSPEND_MODE_DISK,
258     GUEST_SUSPEND_MODE_RAM
259 } GuestSuspendMode;
260 
261 static void check_suspend_mode(GuestSuspendMode mode, Error **errp)
262 {
263     SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
264     Error *local_err = NULL;
265 
266     ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
267     if (!GetPwrCapabilities(&sys_pwr_caps)) {
268         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
269                   "failed to determine guest suspend capabilities");
270         goto out;
271     }
272 
273     switch (mode) {
274     case GUEST_SUSPEND_MODE_DISK:
275         if (!sys_pwr_caps.SystemS4) {
276             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
277                       "suspend-to-disk not supported by OS");
278         }
279         break;
280     case GUEST_SUSPEND_MODE_RAM:
281         if (!sys_pwr_caps.SystemS3) {
282             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
283                       "suspend-to-ram not supported by OS");
284         }
285         break;
286     default:
287         error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
288                   "GuestSuspendMode");
289     }
290 
291 out:
292     if (local_err) {
293         error_propagate(errp, local_err);
294     }
295 }
296 
297 static DWORD WINAPI do_suspend(LPVOID opaque)
298 {
299     GuestSuspendMode *mode = opaque;
300     DWORD ret = 0;
301 
302     if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
303         slog("failed to suspend guest, %lu", GetLastError());
304         ret = -1;
305     }
306     g_free(mode);
307     return ret;
308 }
309 
310 void qmp_guest_suspend_disk(Error **errp)
311 {
312     Error *local_err = NULL;
313     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
314 
315     *mode = GUEST_SUSPEND_MODE_DISK;
316     check_suspend_mode(*mode, &local_err);
317     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
318     execute_async(do_suspend, mode, &local_err);
319 
320     if (local_err) {
321         error_propagate(errp, local_err);
322         g_free(mode);
323     }
324 }
325 
326 void qmp_guest_suspend_ram(Error **errp)
327 {
328     Error *local_err = NULL;
329     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
330 
331     *mode = GUEST_SUSPEND_MODE_RAM;
332     check_suspend_mode(*mode, &local_err);
333     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
334     execute_async(do_suspend, mode, &local_err);
335 
336     if (local_err) {
337         error_propagate(errp, local_err);
338         g_free(mode);
339     }
340 }
341 
342 void qmp_guest_suspend_hybrid(Error **errp)
343 {
344     error_set(errp, QERR_UNSUPPORTED);
345 }
346 
347 GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
348 {
349     error_set(errp, QERR_UNSUPPORTED);
350     return NULL;
351 }
352 
353 int64_t qmp_guest_get_time(Error **errp)
354 {
355     SYSTEMTIME ts = {0};
356     int64_t time_ns;
357     FILETIME tf;
358 
359     GetSystemTime(&ts);
360     if (ts.wYear < 1601 || ts.wYear > 30827) {
361         error_setg(errp, "Failed to get time");
362         return -1;
363     }
364 
365     if (!SystemTimeToFileTime(&ts, &tf)) {
366         error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
367         return -1;
368     }
369 
370     time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
371                 - W32_FT_OFFSET) * 100;
372 
373     return time_ns;
374 }
375 
376 void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
377 {
378     Error *local_err = NULL;
379     SYSTEMTIME ts;
380     FILETIME tf;
381     LONGLONG time;
382 
383     if (has_time) {
384         /* Okay, user passed a time to set. Validate it. */
385         if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
386             error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
387             return;
388         }
389 
390         time = time_ns / 100 + W32_FT_OFFSET;
391 
392         tf.dwLowDateTime = (DWORD) time;
393         tf.dwHighDateTime = (DWORD) (time >> 32);
394 
395         if (!FileTimeToSystemTime(&tf, &ts)) {
396             error_setg(errp, "Failed to convert system time %d",
397                        (int)GetLastError());
398             return;
399         }
400     } else {
401         /* Otherwise read the time from RTC which contains the correct value.
402          * Hopefully. */
403         GetSystemTime(&ts);
404         if (ts.wYear < 1601 || ts.wYear > 30827) {
405             error_setg(errp, "Failed to get time");
406             return;
407         }
408     }
409 
410     acquire_privilege(SE_SYSTEMTIME_NAME, &local_err);
411     if (local_err) {
412         error_propagate(errp, local_err);
413         return;
414     }
415 
416     if (!SetSystemTime(&ts)) {
417         error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
418         return;
419     }
420 }
421 
422 GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
423 {
424     error_set(errp, QERR_UNSUPPORTED);
425     return NULL;
426 }
427 
428 int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
429 {
430     error_set(errp, QERR_UNSUPPORTED);
431     return -1;
432 }
433 
434 /* register init/cleanup routines for stateful command groups */
435 void ga_command_state_init(GAState *s, GACommandState *cs)
436 {
437     if (vss_init(true)) {
438         ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
439     }
440 }
441