xref: /openbmc/qemu/qga/commands-win32.c (revision 77a8257e)
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 <stdio.h>
18 #include <string.h>
19 #include "qga/guest-agent-core.h"
20 #include "qga/vss-win32.h"
21 #include "qga-qmp-commands.h"
22 #include "qapi/qmp/qerror.h"
23 #include "qemu/queue.h"
24 
25 #ifndef SHTDN_REASON_FLAG_PLANNED
26 #define SHTDN_REASON_FLAG_PLANNED 0x80000000
27 #endif
28 
29 /* multiple of 100 nanoseconds elapsed between windows baseline
30  *    (1/1/1601) and Unix Epoch (1/1/1970), accounting for leap years */
31 #define W32_FT_OFFSET (10000000ULL * 60 * 60 * 24 * \
32                        (365 * (1970 - 1601) +       \
33                         (1970 - 1601) / 4 - 3))
34 
35 #define INVALID_SET_FILE_POINTER ((DWORD)-1)
36 
37 typedef struct GuestFileHandle {
38     int64_t id;
39     HANDLE fh;
40     QTAILQ_ENTRY(GuestFileHandle) next;
41 } GuestFileHandle;
42 
43 static struct {
44     QTAILQ_HEAD(, GuestFileHandle) filehandles;
45 } guest_file_state;
46 
47 
48 typedef struct OpenFlags {
49     const char *forms;
50     DWORD desired_access;
51     DWORD creation_disposition;
52 } OpenFlags;
53 static OpenFlags guest_file_open_modes[] = {
54     {"r",   GENERIC_READ,               OPEN_EXISTING},
55     {"rb",  GENERIC_READ,               OPEN_EXISTING},
56     {"w",   GENERIC_WRITE,              CREATE_ALWAYS},
57     {"wb",  GENERIC_WRITE,              CREATE_ALWAYS},
58     {"a",   GENERIC_WRITE,              OPEN_ALWAYS  },
59     {"r+",  GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
60     {"rb+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
61     {"r+b", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
62     {"w+",  GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
63     {"wb+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
64     {"w+b", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
65     {"a+",  GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS  },
66     {"ab+", GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS  },
67     {"a+b", GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS  }
68 };
69 
70 static OpenFlags *find_open_flag(const char *mode_str)
71 {
72     int mode;
73     Error **errp = NULL;
74 
75     for (mode = 0; mode < ARRAY_SIZE(guest_file_open_modes); ++mode) {
76         OpenFlags *flags = guest_file_open_modes + mode;
77 
78         if (strcmp(flags->forms, mode_str) == 0) {
79             return flags;
80         }
81     }
82 
83     error_setg(errp, "invalid file open mode '%s'", mode_str);
84     return NULL;
85 }
86 
87 static int64_t guest_file_handle_add(HANDLE fh, Error **errp)
88 {
89     GuestFileHandle *gfh;
90     int64_t handle;
91 
92     handle = ga_get_fd_handle(ga_state, errp);
93     if (handle < 0) {
94         return -1;
95     }
96     gfh = g_malloc0(sizeof(GuestFileHandle));
97     gfh->id = handle;
98     gfh->fh = fh;
99     QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
100 
101     return handle;
102 }
103 
104 static GuestFileHandle *guest_file_handle_find(int64_t id, Error **errp)
105 {
106     GuestFileHandle *gfh;
107     QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) {
108         if (gfh->id == id) {
109             return gfh;
110         }
111     }
112     error_setg(errp, "handle '%" PRId64 "' has not been found", id);
113     return NULL;
114 }
115 
116 int64_t qmp_guest_file_open(const char *path, bool has_mode,
117                             const char *mode, Error **errp)
118 {
119     int64_t fd;
120     HANDLE fh;
121     HANDLE templ_file = NULL;
122     DWORD share_mode = FILE_SHARE_READ;
123     DWORD flags_and_attr = FILE_ATTRIBUTE_NORMAL;
124     LPSECURITY_ATTRIBUTES sa_attr = NULL;
125     OpenFlags *guest_flags;
126 
127     if (!has_mode) {
128         mode = "r";
129     }
130     slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
131     guest_flags = find_open_flag(mode);
132     if (guest_flags == NULL) {
133         error_setg(errp, "invalid file open mode");
134         return -1;
135     }
136 
137     fh = CreateFile(path, guest_flags->desired_access, share_mode, sa_attr,
138                     guest_flags->creation_disposition, flags_and_attr,
139                     templ_file);
140     if (fh == INVALID_HANDLE_VALUE) {
141         error_setg_win32(errp, GetLastError(), "failed to open file '%s'",
142                          path);
143         return -1;
144     }
145 
146     fd = guest_file_handle_add(fh, errp);
147     if (fd < 0) {
148         CloseHandle(&fh);
149         error_setg(errp, "failed to add handle to qmp handle table");
150         return -1;
151     }
152 
153     slog("guest-file-open, handle: % " PRId64, fd);
154     return fd;
155 }
156 
157 void qmp_guest_file_close(int64_t handle, Error **errp)
158 {
159     bool ret;
160     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
161     slog("guest-file-close called, handle: %" PRId64, handle);
162     if (gfh == NULL) {
163         return;
164     }
165     ret = CloseHandle(gfh->fh);
166     if (!ret) {
167         error_setg_win32(errp, GetLastError(), "failed close handle");
168         return;
169     }
170 
171     QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
172     g_free(gfh);
173 }
174 
175 static void acquire_privilege(const char *name, Error **errp)
176 {
177     HANDLE token = NULL;
178     TOKEN_PRIVILEGES priv;
179     Error *local_err = NULL;
180 
181     if (OpenProcessToken(GetCurrentProcess(),
182         TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
183     {
184         if (!LookupPrivilegeValue(NULL, name, &priv.Privileges[0].Luid)) {
185             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
186                       "no luid for requested privilege");
187             goto out;
188         }
189 
190         priv.PrivilegeCount = 1;
191         priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
192 
193         if (!AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0)) {
194             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
195                       "unable to acquire requested privilege");
196             goto out;
197         }
198 
199     } else {
200         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
201                   "failed to open privilege token");
202     }
203 
204 out:
205     if (token) {
206         CloseHandle(token);
207     }
208     if (local_err) {
209         error_propagate(errp, local_err);
210     }
211 }
212 
213 static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque,
214                           Error **errp)
215 {
216     Error *local_err = NULL;
217 
218     HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
219     if (!thread) {
220         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
221                   "failed to dispatch asynchronous command");
222         error_propagate(errp, local_err);
223     }
224 }
225 
226 void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
227 {
228     Error *local_err = NULL;
229     UINT shutdown_flag = EWX_FORCE;
230 
231     slog("guest-shutdown called, mode: %s", mode);
232 
233     if (!has_mode || strcmp(mode, "powerdown") == 0) {
234         shutdown_flag |= EWX_POWEROFF;
235     } else if (strcmp(mode, "halt") == 0) {
236         shutdown_flag |= EWX_SHUTDOWN;
237     } else if (strcmp(mode, "reboot") == 0) {
238         shutdown_flag |= EWX_REBOOT;
239     } else {
240         error_set(errp, QERR_INVALID_PARAMETER_VALUE, "mode",
241                   "halt|powerdown|reboot");
242         return;
243     }
244 
245     /* Request a shutdown privilege, but try to shut down the system
246        anyway. */
247     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
248     if (local_err) {
249         error_propagate(errp, local_err);
250         return;
251     }
252 
253     if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
254         slog("guest-shutdown failed: %lu", GetLastError());
255         error_set(errp, QERR_UNDEFINED_ERROR);
256     }
257 }
258 
259 GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
260                                    int64_t count, Error **errp)
261 {
262     GuestFileRead *read_data = NULL;
263     guchar *buf;
264     HANDLE fh;
265     bool is_ok;
266     DWORD read_count;
267     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
268 
269     if (!gfh) {
270         return NULL;
271     }
272     if (!has_count) {
273         count = QGA_READ_COUNT_DEFAULT;
274     } else if (count < 0) {
275         error_setg(errp, "value '%" PRId64
276                    "' is invalid for argument count", count);
277         return NULL;
278     }
279 
280     fh = gfh->fh;
281     buf = g_malloc0(count+1);
282     is_ok = ReadFile(fh, buf, count, &read_count, NULL);
283     if (!is_ok) {
284         error_setg_win32(errp, GetLastError(), "failed to read file");
285         slog("guest-file-read failed, handle %" PRId64, handle);
286     } else {
287         buf[read_count] = 0;
288         read_data = g_malloc0(sizeof(GuestFileRead));
289         read_data->count = (size_t)read_count;
290         read_data->eof = read_count == 0;
291 
292         if (read_count != 0) {
293             read_data->buf_b64 = g_base64_encode(buf, read_count);
294         }
295     }
296     g_free(buf);
297 
298     return read_data;
299 }
300 
301 GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
302                                      bool has_count, int64_t count,
303                                      Error **errp)
304 {
305     GuestFileWrite *write_data = NULL;
306     guchar *buf;
307     gsize buf_len;
308     bool is_ok;
309     DWORD write_count;
310     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
311     HANDLE fh;
312 
313     if (!gfh) {
314         return NULL;
315     }
316     fh = gfh->fh;
317     buf = g_base64_decode(buf_b64, &buf_len);
318 
319     if (!has_count) {
320         count = buf_len;
321     } else if (count < 0 || count > buf_len) {
322         error_setg(errp, "value '%" PRId64
323                    "' is invalid for argument count", count);
324         goto done;
325     }
326 
327     is_ok = WriteFile(fh, buf, count, &write_count, NULL);
328     if (!is_ok) {
329         error_setg_win32(errp, GetLastError(), "failed to write to file");
330         slog("guest-file-write-failed, handle: %" PRId64, handle);
331     } else {
332         write_data = g_malloc0(sizeof(GuestFileWrite));
333         write_data->count = (size_t) write_count;
334     }
335 
336 done:
337     g_free(buf);
338     return write_data;
339 }
340 
341 GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
342                                    int64_t whence, Error **errp)
343 {
344     GuestFileHandle *gfh;
345     GuestFileSeek *seek_data;
346     HANDLE fh;
347     LARGE_INTEGER new_pos, off_pos;
348     off_pos.QuadPart = offset;
349     BOOL res;
350     gfh = guest_file_handle_find(handle, errp);
351     if (!gfh) {
352         return NULL;
353     }
354 
355     fh = gfh->fh;
356     res = SetFilePointerEx(fh, off_pos, &new_pos, whence);
357     if (!res) {
358         error_setg_win32(errp, GetLastError(), "failed to seek file");
359         return NULL;
360     }
361     seek_data = g_new0(GuestFileSeek, 1);
362     seek_data->position = new_pos.QuadPart;
363     return seek_data;
364 }
365 
366 void qmp_guest_file_flush(int64_t handle, Error **errp)
367 {
368     HANDLE fh;
369     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
370     if (!gfh) {
371         return;
372     }
373 
374     fh = gfh->fh;
375     if (!FlushFileBuffers(fh)) {
376         error_setg_win32(errp, GetLastError(), "failed to flush file");
377     }
378 }
379 
380 static void guest_file_init(void)
381 {
382     QTAILQ_INIT(&guest_file_state.filehandles);
383 }
384 
385 GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
386 {
387     error_set(errp, QERR_UNSUPPORTED);
388     return NULL;
389 }
390 
391 /*
392  * Return status of freeze/thaw
393  */
394 GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
395 {
396     if (!vss_initialized()) {
397         error_set(errp, QERR_UNSUPPORTED);
398         return 0;
399     }
400 
401     if (ga_is_frozen(ga_state)) {
402         return GUEST_FSFREEZE_STATUS_FROZEN;
403     }
404 
405     return GUEST_FSFREEZE_STATUS_THAWED;
406 }
407 
408 /*
409  * Freeze local file systems using Volume Shadow-copy Service.
410  * The frozen state is limited for up to 10 seconds by VSS.
411  */
412 int64_t qmp_guest_fsfreeze_freeze(Error **errp)
413 {
414     int i;
415     Error *local_err = NULL;
416 
417     if (!vss_initialized()) {
418         error_set(errp, QERR_UNSUPPORTED);
419         return 0;
420     }
421 
422     slog("guest-fsfreeze called");
423 
424     /* cannot risk guest agent blocking itself on a write in this state */
425     ga_set_frozen(ga_state);
426 
427     qga_vss_fsfreeze(&i, &local_err, true);
428     if (local_err) {
429         error_propagate(errp, local_err);
430         goto error;
431     }
432 
433     return i;
434 
435 error:
436     local_err = NULL;
437     qmp_guest_fsfreeze_thaw(&local_err);
438     if (local_err) {
439         g_debug("cleanup thaw: %s", error_get_pretty(local_err));
440         error_free(local_err);
441     }
442     return 0;
443 }
444 
445 int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
446                                        strList *mountpoints,
447                                        Error **errp)
448 {
449     error_set(errp, QERR_UNSUPPORTED);
450 
451     return 0;
452 }
453 
454 /*
455  * Thaw local file systems using Volume Shadow-copy Service.
456  */
457 int64_t qmp_guest_fsfreeze_thaw(Error **errp)
458 {
459     int i;
460 
461     if (!vss_initialized()) {
462         error_set(errp, QERR_UNSUPPORTED);
463         return 0;
464     }
465 
466     qga_vss_fsfreeze(&i, errp, false);
467 
468     ga_unset_frozen(ga_state);
469     return i;
470 }
471 
472 static void guest_fsfreeze_cleanup(void)
473 {
474     Error *err = NULL;
475 
476     if (!vss_initialized()) {
477         return;
478     }
479 
480     if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
481         qmp_guest_fsfreeze_thaw(&err);
482         if (err) {
483             slog("failed to clean up frozen filesystems: %s",
484                  error_get_pretty(err));
485             error_free(err);
486         }
487     }
488 
489     vss_deinit(true);
490 }
491 
492 /*
493  * Walk list of mounted file systems in the guest, and discard unused
494  * areas.
495  */
496 void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
497 {
498     error_set(errp, QERR_UNSUPPORTED);
499 }
500 
501 typedef enum {
502     GUEST_SUSPEND_MODE_DISK,
503     GUEST_SUSPEND_MODE_RAM
504 } GuestSuspendMode;
505 
506 static void check_suspend_mode(GuestSuspendMode mode, Error **errp)
507 {
508     SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
509     Error *local_err = NULL;
510 
511     ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
512     if (!GetPwrCapabilities(&sys_pwr_caps)) {
513         error_set(&local_err, QERR_QGA_COMMAND_FAILED,
514                   "failed to determine guest suspend capabilities");
515         goto out;
516     }
517 
518     switch (mode) {
519     case GUEST_SUSPEND_MODE_DISK:
520         if (!sys_pwr_caps.SystemS4) {
521             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
522                       "suspend-to-disk not supported by OS");
523         }
524         break;
525     case GUEST_SUSPEND_MODE_RAM:
526         if (!sys_pwr_caps.SystemS3) {
527             error_set(&local_err, QERR_QGA_COMMAND_FAILED,
528                       "suspend-to-ram not supported by OS");
529         }
530         break;
531     default:
532         error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
533                   "GuestSuspendMode");
534     }
535 
536 out:
537     if (local_err) {
538         error_propagate(errp, local_err);
539     }
540 }
541 
542 static DWORD WINAPI do_suspend(LPVOID opaque)
543 {
544     GuestSuspendMode *mode = opaque;
545     DWORD ret = 0;
546 
547     if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
548         slog("failed to suspend guest, %lu", GetLastError());
549         ret = -1;
550     }
551     g_free(mode);
552     return ret;
553 }
554 
555 void qmp_guest_suspend_disk(Error **errp)
556 {
557     Error *local_err = NULL;
558     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
559 
560     *mode = GUEST_SUSPEND_MODE_DISK;
561     check_suspend_mode(*mode, &local_err);
562     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
563     execute_async(do_suspend, mode, &local_err);
564 
565     if (local_err) {
566         error_propagate(errp, local_err);
567         g_free(mode);
568     }
569 }
570 
571 void qmp_guest_suspend_ram(Error **errp)
572 {
573     Error *local_err = NULL;
574     GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
575 
576     *mode = GUEST_SUSPEND_MODE_RAM;
577     check_suspend_mode(*mode, &local_err);
578     acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
579     execute_async(do_suspend, mode, &local_err);
580 
581     if (local_err) {
582         error_propagate(errp, local_err);
583         g_free(mode);
584     }
585 }
586 
587 void qmp_guest_suspend_hybrid(Error **errp)
588 {
589     error_set(errp, QERR_UNSUPPORTED);
590 }
591 
592 GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
593 {
594     error_set(errp, QERR_UNSUPPORTED);
595     return NULL;
596 }
597 
598 int64_t qmp_guest_get_time(Error **errp)
599 {
600     SYSTEMTIME ts = {0};
601     int64_t time_ns;
602     FILETIME tf;
603 
604     GetSystemTime(&ts);
605     if (ts.wYear < 1601 || ts.wYear > 30827) {
606         error_setg(errp, "Failed to get time");
607         return -1;
608     }
609 
610     if (!SystemTimeToFileTime(&ts, &tf)) {
611         error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
612         return -1;
613     }
614 
615     time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
616                 - W32_FT_OFFSET) * 100;
617 
618     return time_ns;
619 }
620 
621 void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
622 {
623     Error *local_err = NULL;
624     SYSTEMTIME ts;
625     FILETIME tf;
626     LONGLONG time;
627 
628     if (!has_time) {
629         /* Unfortunately, Windows libraries don't provide an easy way to access
630          * RTC yet:
631          *
632          * https://msdn.microsoft.com/en-us/library/aa908981.aspx
633          */
634         error_setg(errp, "Time argument is required on this platform");
635         return;
636     }
637 
638     /* Validate time passed by user. */
639     if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
640         error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
641         return;
642     }
643 
644     time = time_ns / 100 + W32_FT_OFFSET;
645 
646     tf.dwLowDateTime = (DWORD) time;
647     tf.dwHighDateTime = (DWORD) (time >> 32);
648 
649     if (!FileTimeToSystemTime(&tf, &ts)) {
650         error_setg(errp, "Failed to convert system time %d",
651                    (int)GetLastError());
652         return;
653     }
654 
655     acquire_privilege(SE_SYSTEMTIME_NAME, &local_err);
656     if (local_err) {
657         error_propagate(errp, local_err);
658         return;
659     }
660 
661     if (!SetSystemTime(&ts)) {
662         error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
663         return;
664     }
665 }
666 
667 GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
668 {
669     error_set(errp, QERR_UNSUPPORTED);
670     return NULL;
671 }
672 
673 int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
674 {
675     error_set(errp, QERR_UNSUPPORTED);
676     return -1;
677 }
678 
679 void qmp_guest_set_user_password(const char *username,
680                                  const char *password,
681                                  bool crypted,
682                                  Error **errp)
683 {
684     error_set(errp, QERR_UNSUPPORTED);
685 }
686 
687 GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
688 {
689     error_set(errp, QERR_UNSUPPORTED);
690     return NULL;
691 }
692 
693 GuestMemoryBlockResponseList *
694 qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
695 {
696     error_set(errp, QERR_UNSUPPORTED);
697     return NULL;
698 }
699 
700 GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
701 {
702     error_set(errp, QERR_UNSUPPORTED);
703     return NULL;
704 }
705 
706 /* add unsupported commands to the blacklist */
707 GList *ga_command_blacklist_init(GList *blacklist)
708 {
709     const char *list_unsupported[] = {
710         "guest-suspend-hybrid", "guest-network-get-interfaces",
711         "guest-get-vcpus", "guest-set-vcpus",
712         "guest-set-user-password",
713         "guest-get-memory-blocks", "guest-set-memory-blocks",
714         "guest-get-memory-block-size",
715         "guest-fsfreeze-freeze-list", "guest-get-fsinfo",
716         "guest-fstrim", NULL};
717     char **p = (char **)list_unsupported;
718 
719     while (*p) {
720         blacklist = g_list_append(blacklist, *p++);
721     }
722 
723     if (!vss_init(true)) {
724         const char *list[] = {
725             "guest-get-fsinfo", "guest-fsfreeze-status",
726             "guest-fsfreeze-freeze", "guest-fsfreeze-thaw", NULL};
727         p = (char **)list;
728 
729         while (*p) {
730             blacklist = g_list_append(blacklist, *p++);
731         }
732     }
733 
734     return blacklist;
735 }
736 
737 /* register init/cleanup routines for stateful command groups */
738 void ga_command_state_init(GAState *s, GACommandState *cs)
739 {
740     if (!vss_initialized()) {
741         ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
742     }
743     ga_command_state_add(cs, guest_file_init, NULL);
744 }
745