xref: /openbmc/qemu/qga/commands-win32.c (revision 8f1e884b)
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;
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         CloseHandle(token);
57     } else {
58         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
59                   "failed to open privilege token");
60     }
61 
62 out:
63     if (local_err) {
64         error_propagate(errp, local_err);
65     }
66 }
67 
68 static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque,
69                           Error **errp)
70 {
71     Error *local_err = NULL;
72 
73     HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
74     if (!thread) {
75         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
76                   "failed to dispatch asynchronous command");
77         error_propagate(errp, local_err);
78     }
79 }
80 
81 void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
82 {
83     Error *local_err = NULL;
84     UINT shutdown_flag = EWX_FORCE;
85 
86     slog("guest-shutdown called, mode: %s", mode);
87 
88     if (!has_mode || strcmp(mode, "powerdown") == 0) {
89         shutdown_flag |= EWX_POWEROFF;
90     } else if (strcmp(mode, "halt") == 0) {
91         shutdown_flag |= EWX_SHUTDOWN;
92     } else if (strcmp(mode, "reboot") == 0) {
93         shutdown_flag |= EWX_REBOOT;
94     } else {
95         error_set(errp, QERR_INVALID_PARAMETER_VALUE, "mode",
96                   "halt|powerdown|reboot");
97         return;
98     }
99 
100     /* Request a shutdown privilege, but try to shut down the system
101        anyway. */
102     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
103     if (local_err) {
104         error_propagate(errp, local_err);
105         return;
106     }
107 
108     if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
109         slog("guest-shutdown failed: %lu", GetLastError());
110         error_set(errp, QERR_UNDEFINED_ERROR);
111     }
112 }
113 
114 int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode,
115                             Error **errp)
116 {
117     error_set(errp, QERR_UNSUPPORTED);
118     return 0;
119 }
120 
121 void qmp_guest_file_close(int64_t handle, Error **errp)
122 {
123     error_set(errp, QERR_UNSUPPORTED);
124 }
125 
126 GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
127                                    int64_t count, Error **errp)
128 {
129     error_set(errp, QERR_UNSUPPORTED);
130     return 0;
131 }
132 
133 GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
134                                      bool has_count, int64_t count,
135                                      Error **errp)
136 {
137     error_set(errp, QERR_UNSUPPORTED);
138     return 0;
139 }
140 
141 GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
142                                    int64_t whence, Error **errp)
143 {
144     error_set(errp, QERR_UNSUPPORTED);
145     return 0;
146 }
147 
148 void qmp_guest_file_flush(int64_t handle, Error **errp)
149 {
150     error_set(errp, QERR_UNSUPPORTED);
151 }
152 
153 /*
154  * Return status of freeze/thaw
155  */
156 GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
157 {
158     if (!vss_initialized()) {
159         error_set(errp, QERR_UNSUPPORTED);
160         return 0;
161     }
162 
163     if (ga_is_frozen(ga_state)) {
164         return GUEST_FSFREEZE_STATUS_FROZEN;
165     }
166 
167     return GUEST_FSFREEZE_STATUS_THAWED;
168 }
169 
170 /*
171  * Freeze local file systems using Volume Shadow-copy Service.
172  * The frozen state is limited for up to 10 seconds by VSS.
173  */
174 int64_t qmp_guest_fsfreeze_freeze(Error **errp)
175 {
176     int i;
177     Error *local_err = NULL;
178 
179     if (!vss_initialized()) {
180         error_set(errp, QERR_UNSUPPORTED);
181         return 0;
182     }
183 
184     slog("guest-fsfreeze called");
185 
186     /* cannot risk guest agent blocking itself on a write in this state */
187     ga_set_frozen(ga_state);
188 
189     qga_vss_fsfreeze(&i, &local_err, true);
190     if (local_err) {
191         error_propagate(errp, local_err);
192         goto error;
193     }
194 
195     return i;
196 
197 error:
198     local_err = NULL;
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 **errp)
211 {
212     int i;
213 
214     if (!vss_initialized()) {
215         error_set(errp, QERR_UNSUPPORTED);
216         return 0;
217     }
218 
219     qga_vss_fsfreeze(&i, errp, 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 **errp)
250 {
251     error_set(errp, 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 **errp)
260 {
261     SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
262     Error *local_err = NULL;
263 
264     ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
265     if (!GetPwrCapabilities(&sys_pwr_caps)) {
266         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
267                   "failed to determine guest suspend capabilities");
268         goto out;
269     }
270 
271     switch (mode) {
272     case GUEST_SUSPEND_MODE_DISK:
273         if (!sys_pwr_caps.SystemS4) {
274             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
275                       "suspend-to-disk not supported by OS");
276         }
277         break;
278     case GUEST_SUSPEND_MODE_RAM:
279         if (!sys_pwr_caps.SystemS3) {
280             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
281                       "suspend-to-ram not supported by OS");
282         }
283         break;
284     default:
285         error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
286                   "GuestSuspendMode");
287     }
288 
289 out:
290     if (local_err) {
291         error_propagate(errp, local_err);
292     }
293 }
294 
295 static DWORD WINAPI do_suspend(LPVOID opaque)
296 {
297     GuestSuspendMode *mode = opaque;
298     DWORD ret = 0;
299 
300     if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
301         slog("failed to suspend guest, %lu", GetLastError());
302         ret = -1;
303     }
304     g_free(mode);
305     return ret;
306 }
307 
308 void qmp_guest_suspend_disk(Error **errp)
309 {
310     Error *local_err = NULL;
311     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
312 
313     *mode = GUEST_SUSPEND_MODE_DISK;
314     check_suspend_mode(*mode, &local_err);
315     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
316     execute_async(do_suspend, mode, &local_err);
317 
318     if (local_err) {
319         error_propagate(errp, local_err);
320         g_free(mode);
321     }
322 }
323 
324 void qmp_guest_suspend_ram(Error **errp)
325 {
326     Error *local_err = NULL;
327     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
328 
329     *mode = GUEST_SUSPEND_MODE_RAM;
330     check_suspend_mode(*mode, &local_err);
331     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
332     execute_async(do_suspend, mode, &local_err);
333 
334     if (local_err) {
335         error_propagate(errp, local_err);
336         g_free(mode);
337     }
338 }
339 
340 void qmp_guest_suspend_hybrid(Error **errp)
341 {
342     error_set(errp, QERR_UNSUPPORTED);
343 }
344 
345 GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
346 {
347     error_set(errp, QERR_UNSUPPORTED);
348     return NULL;
349 }
350 
351 int64_t qmp_guest_get_time(Error **errp)
352 {
353     SYSTEMTIME ts = {0};
354     int64_t time_ns;
355     FILETIME tf;
356 
357     GetSystemTime(&ts);
358     if (ts.wYear < 1601 || ts.wYear > 30827) {
359         error_setg(errp, "Failed to get time");
360         return -1;
361     }
362 
363     if (!SystemTimeToFileTime(&ts, &tf)) {
364         error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
365         return -1;
366     }
367 
368     time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
369                 - W32_FT_OFFSET) * 100;
370 
371     return time_ns;
372 }
373 
374 void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
375 {
376     Error *local_err = NULL;
377     SYSTEMTIME ts;
378     FILETIME tf;
379     LONGLONG time;
380 
381     if (has_time) {
382         /* Okay, user passed a time to set. Validate it. */
383         if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
384             error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
385             return;
386         }
387 
388         time = time_ns / 100 + W32_FT_OFFSET;
389 
390         tf.dwLowDateTime = (DWORD) time;
391         tf.dwHighDateTime = (DWORD) (time >> 32);
392 
393         if (!FileTimeToSystemTime(&tf, &ts)) {
394             error_setg(errp, "Failed to convert system time %d",
395                        (int)GetLastError());
396             return;
397         }
398     } else {
399         /* Otherwise read the time from RTC which contains the correct value.
400          * Hopefully. */
401         GetSystemTime(&ts);
402         if (ts.wYear < 1601 || ts.wYear > 30827) {
403             error_setg(errp, "Failed to get time");
404             return;
405         }
406     }
407 
408     acquire_privilege(SE_SYSTEMTIME_NAME, &local_err);
409     if (local_err) {
410         error_propagate(errp, local_err);
411         return;
412     }
413 
414     if (!SetSystemTime(&ts)) {
415         error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
416         return;
417     }
418 }
419 
420 GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
421 {
422     error_set(errp, QERR_UNSUPPORTED);
423     return NULL;
424 }
425 
426 int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
427 {
428     error_set(errp, QERR_UNSUPPORTED);
429     return -1;
430 }
431 
432 /* register init/cleanup routines for stateful command groups */
433 void ga_command_state_init(GAState *s, GACommandState *cs)
434 {
435     if (vss_init(true)) {
436         ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
437     }
438 }
439