xref: /openbmc/qemu/qga/commands-win32.c (revision a719a27c)
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 **err)
33 {
34     HANDLE token;
35     TOKEN_PRIVILEGES priv;
36     Error *local_err = NULL;
37 
38     if (error_is_set(err)) {
39         return;
40     }
41 
42     if (OpenProcessToken(GetCurrentProcess(),
43         TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
44     {
45         if (!LookupPrivilegeValue(NULL, name, &priv.Privileges[0].Luid)) {
46             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
47                       "no luid for requested privilege");
48             goto out;
49         }
50 
51         priv.PrivilegeCount = 1;
52         priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
53 
54         if (!AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0)) {
55             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
56                       "unable to acquire requested privilege");
57             goto out;
58         }
59 
60         CloseHandle(token);
61     } else {
62         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
63                   "failed to open privilege token");
64     }
65 
66 out:
67     if (local_err) {
68         error_propagate(err, local_err);
69     }
70 }
71 
72 static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque, Error **err)
73 {
74     Error *local_err = NULL;
75 
76     if (error_is_set(err)) {
77         return;
78     }
79     HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
80     if (!thread) {
81         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
82                   "failed to dispatch asynchronous command");
83         error_propagate(err, local_err);
84     }
85 }
86 
87 void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
88 {
89     UINT shutdown_flag = EWX_FORCE;
90 
91     slog("guest-shutdown called, mode: %s", mode);
92 
93     if (!has_mode || strcmp(mode, "powerdown") == 0) {
94         shutdown_flag |= EWX_POWEROFF;
95     } else if (strcmp(mode, "halt") == 0) {
96         shutdown_flag |= EWX_SHUTDOWN;
97     } else if (strcmp(mode, "reboot") == 0) {
98         shutdown_flag |= EWX_REBOOT;
99     } else {
100         error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
101                   "halt|powerdown|reboot");
102         return;
103     }
104 
105     /* Request a shutdown privilege, but try to shut down the system
106        anyway. */
107     acquire_privilege(SE_SHUTDOWN_NAME, err);
108     if (error_is_set(err)) {
109         return;
110     }
111 
112     if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
113         slog("guest-shutdown failed: %lu", GetLastError());
114         error_set(err, QERR_UNDEFINED_ERROR);
115     }
116 }
117 
118 int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err)
119 {
120     error_set(err, QERR_UNSUPPORTED);
121     return 0;
122 }
123 
124 void qmp_guest_file_close(int64_t handle, Error **err)
125 {
126     error_set(err, QERR_UNSUPPORTED);
127 }
128 
129 GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
130                                    int64_t count, Error **err)
131 {
132     error_set(err, QERR_UNSUPPORTED);
133     return 0;
134 }
135 
136 GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
137                                      bool has_count, int64_t count, Error **err)
138 {
139     error_set(err, QERR_UNSUPPORTED);
140     return 0;
141 }
142 
143 GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
144                                    int64_t whence, Error **err)
145 {
146     error_set(err, QERR_UNSUPPORTED);
147     return 0;
148 }
149 
150 void qmp_guest_file_flush(int64_t handle, Error **err)
151 {
152     error_set(err, QERR_UNSUPPORTED);
153 }
154 
155 /*
156  * Return status of freeze/thaw
157  */
158 GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
159 {
160     if (!vss_initialized()) {
161         error_set(err, 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 **err)
177 {
178     int i;
179     Error *local_err = NULL;
180 
181     if (!vss_initialized()) {
182         error_set(err, 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, err, true);
192     if (error_is_set(err)) {
193         goto error;
194     }
195 
196     return i;
197 
198 error:
199     qmp_guest_fsfreeze_thaw(&local_err);
200     if (local_err) {
201         g_debug("cleanup thaw: %s", error_get_pretty(local_err));
202         error_free(local_err);
203     }
204     return 0;
205 }
206 
207 /*
208  * Thaw local file systems using Volume Shadow-copy Service.
209  */
210 int64_t qmp_guest_fsfreeze_thaw(Error **err)
211 {
212     int i;
213 
214     if (!vss_initialized()) {
215         error_set(err, QERR_UNSUPPORTED);
216         return 0;
217     }
218 
219     qga_vss_fsfreeze(&i, err, false);
220 
221     ga_unset_frozen(ga_state);
222     return i;
223 }
224 
225 static void guest_fsfreeze_cleanup(void)
226 {
227     Error *err = NULL;
228 
229     if (!vss_initialized()) {
230         return;
231     }
232 
233     if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
234         qmp_guest_fsfreeze_thaw(&err);
235         if (err) {
236             slog("failed to clean up frozen filesystems: %s",
237                  error_get_pretty(err));
238             error_free(err);
239         }
240     }
241 
242     vss_deinit(true);
243 }
244 
245 /*
246  * Walk list of mounted file systems in the guest, and discard unused
247  * areas.
248  */
249 void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err)
250 {
251     error_set(err, QERR_UNSUPPORTED);
252 }
253 
254 typedef enum {
255     GUEST_SUSPEND_MODE_DISK,
256     GUEST_SUSPEND_MODE_RAM
257 } GuestSuspendMode;
258 
259 static void check_suspend_mode(GuestSuspendMode mode, Error **err)
260 {
261     SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
262     Error *local_err = NULL;
263 
264     if (error_is_set(err)) {
265         return;
266     }
267     ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
268     if (!GetPwrCapabilities(&sys_pwr_caps)) {
269         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
270                   "failed to determine guest suspend capabilities");
271         goto out;
272     }
273 
274     switch (mode) {
275     case GUEST_SUSPEND_MODE_DISK:
276         if (!sys_pwr_caps.SystemS4) {
277             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
278                       "suspend-to-disk not supported by OS");
279         }
280         break;
281     case GUEST_SUSPEND_MODE_RAM:
282         if (!sys_pwr_caps.SystemS3) {
283             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
284                       "suspend-to-ram not supported by OS");
285         }
286         break;
287     default:
288         error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
289                   "GuestSuspendMode");
290     }
291 
292 out:
293     if (local_err) {
294         error_propagate(err, local_err);
295     }
296 }
297 
298 static DWORD WINAPI do_suspend(LPVOID opaque)
299 {
300     GuestSuspendMode *mode = opaque;
301     DWORD ret = 0;
302 
303     if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
304         slog("failed to suspend guest, %lu", GetLastError());
305         ret = -1;
306     }
307     g_free(mode);
308     return ret;
309 }
310 
311 void qmp_guest_suspend_disk(Error **err)
312 {
313     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
314 
315     *mode = GUEST_SUSPEND_MODE_DISK;
316     check_suspend_mode(*mode, err);
317     acquire_privilege(SE_SHUTDOWN_NAME, err);
318     execute_async(do_suspend, mode, err);
319 
320     if (error_is_set(err)) {
321         g_free(mode);
322     }
323 }
324 
325 void qmp_guest_suspend_ram(Error **err)
326 {
327     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
328 
329     *mode = GUEST_SUSPEND_MODE_RAM;
330     check_suspend_mode(*mode, err);
331     acquire_privilege(SE_SHUTDOWN_NAME, err);
332     execute_async(do_suspend, mode, err);
333 
334     if (error_is_set(err)) {
335         g_free(mode);
336     }
337 }
338 
339 void qmp_guest_suspend_hybrid(Error **err)
340 {
341     error_set(err, QERR_UNSUPPORTED);
342 }
343 
344 GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **err)
345 {
346     error_set(err, QERR_UNSUPPORTED);
347     return NULL;
348 }
349 
350 int64_t qmp_guest_get_time(Error **errp)
351 {
352     SYSTEMTIME ts = {0};
353     int64_t time_ns;
354     FILETIME tf;
355 
356     GetSystemTime(&ts);
357     if (ts.wYear < 1601 || ts.wYear > 30827) {
358         error_setg(errp, "Failed to get time");
359         return -1;
360     }
361 
362     if (!SystemTimeToFileTime(&ts, &tf)) {
363         error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
364         return -1;
365     }
366 
367     time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
368                 - W32_FT_OFFSET) * 100;
369 
370     return time_ns;
371 }
372 
373 void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
374 {
375     SYSTEMTIME ts;
376     FILETIME tf;
377     LONGLONG time;
378 
379     if (has_time) {
380         /* Okay, user passed a time to set. Validate it. */
381         if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
382             error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
383             return;
384         }
385 
386         time = time_ns / 100 + W32_FT_OFFSET;
387 
388         tf.dwLowDateTime = (DWORD) time;
389         tf.dwHighDateTime = (DWORD) (time >> 32);
390 
391         if (!FileTimeToSystemTime(&tf, &ts)) {
392             error_setg(errp, "Failed to convert system time %d",
393                        (int)GetLastError());
394             return;
395         }
396     } else {
397         /* Otherwise read the time from RTC which contains the correct value.
398          * Hopefully. */
399         GetSystemTime(&ts);
400         if (ts.wYear < 1601 || ts.wYear > 30827) {
401             error_setg(errp, "Failed to get time");
402             return;
403         }
404     }
405 
406     acquire_privilege(SE_SYSTEMTIME_NAME, errp);
407     if (error_is_set(errp)) {
408         return;
409     }
410 
411     if (!SetSystemTime(&ts)) {
412         error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
413         return;
414     }
415 }
416 
417 GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
418 {
419     error_set(errp, QERR_UNSUPPORTED);
420     return NULL;
421 }
422 
423 int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
424 {
425     error_set(errp, QERR_UNSUPPORTED);
426     return -1;
427 }
428 
429 /* register init/cleanup routines for stateful command groups */
430 void ga_command_state_init(GAState *s, GACommandState *cs)
431 {
432     if (vss_init(true)) {
433         ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
434     }
435 }
436