VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModAPIMonitor.cpp@ 43421

Last change on this file since 43421 was 41444, checked in by vboxsync, 12 years ago

VBoxBalloonCtrl/APIMon: Power off VM after saving state, logging.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.0 KB
Line 
1
2/* $Id: VBoxModAPIMonitor.cpp 41444 2012-05-25 08:23:06Z vboxsync $ */
3/** @file
4 * VBoxModAPIMonitor - API monitor module for detecting host isolation.
5 */
6
7/*
8 * Copyright (C) 2012 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19
20/*******************************************************************************
21* Header Files *
22*******************************************************************************/
23#ifndef VBOX_ONLY_DOCS
24# include <iprt/message.h>
25# include <VBox/com/errorprint.h>
26#endif /* !VBOX_ONLY_DOCS */
27
28#include "VBoxWatchdogInternal.h"
29
30using namespace com;
31
32#define VBOX_MOD_APIMON_NAME "apimon"
33
34/**
35 * The module's RTGetOpt-IDs for the command line.
36 */
37enum GETOPTDEF_APIMON
38{
39 GETOPTDEF_APIMON_GROUPS = 3000,
40 GETOPTDEF_APIMON_ISLN_RESPONSE,
41 GETOPTDEF_APIMON_ISLN_TIMEOUT,
42 GETOPTDEF_APIMON_RESP_TIMEOUT
43};
44
45/**
46 * The module's command line arguments.
47 */
48static const RTGETOPTDEF g_aAPIMonitorOpts[] = {
49 { "--apimon-groups", GETOPTDEF_APIMON_GROUPS, RTGETOPT_REQ_STRING },
50 { "--apimon-isln-response", GETOPTDEF_APIMON_ISLN_RESPONSE, RTGETOPT_REQ_STRING },
51 { "--apimon-isln-timeout", GETOPTDEF_APIMON_ISLN_TIMEOUT, RTGETOPT_REQ_UINT32 },
52 { "--apimon-resp-timeout", GETOPTDEF_APIMON_RESP_TIMEOUT, RTGETOPT_REQ_UINT32 }
53};
54
55enum APIMON_RESPONSE
56{
57 /** Unknown / unhandled response. */
58 APIMON_RESPONSE_NONE = 0,
59 /** Pauses the VM execution. */
60 APIMON_RESPONSE_PAUSE = 10,
61 /** Does a hard power off. */
62 APIMON_RESPONSE_POWEROFF = 200,
63 /** Tries to save the current machine state. */
64 APIMON_RESPONSE_SAVE = 250,
65 /** Tries to shut down all running VMs in
66 * a gentle manner. */
67 APIMON_RESPONSE_SHUTDOWN = 300
68};
69
70/** The VM group(s) the API monitor handles. If none, all VMs get handled. */
71static mapGroups g_vecAPIMonGroups; /** @todo Move this into module payload! */
72static APIMON_RESPONSE g_enmAPIMonIslnResp = APIMON_RESPONSE_NONE;
73static unsigned long g_ulAPIMonIslnTimeoutMS = 0;
74static Bstr g_strAPIMonIslnLastBeat;
75static unsigned long g_ulAPIMonResponseTimeoutMS = 0;
76static uint64_t g_uAPIMonIslnLastBeatMS = 0;
77
78static int apimonResponseToEnum(const char *pszResponse, APIMON_RESPONSE *pResp)
79{
80 AssertPtrReturn(pszResponse, VERR_INVALID_POINTER);
81 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
82
83 int rc = VINF_SUCCESS;
84 if (!RTStrICmp(pszResponse, "none"))
85 {
86 *pResp = APIMON_RESPONSE_NONE;
87 }
88 else if (!RTStrICmp(pszResponse, "pause"))
89 {
90 *pResp = APIMON_RESPONSE_PAUSE;
91 }
92 else if ( !RTStrICmp(pszResponse, "poweroff")
93 || !RTStrICmp(pszResponse, "powerdown"))
94 {
95 *pResp = APIMON_RESPONSE_POWEROFF;
96 }
97 else if (!RTStrICmp(pszResponse, "save"))
98 {
99 *pResp = APIMON_RESPONSE_SAVE;
100 }
101 else if ( !RTStrICmp(pszResponse, "shutdown")
102 || !RTStrICmp(pszResponse, "shutoff"))
103 {
104 *pResp = APIMON_RESPONSE_SHUTDOWN;
105 }
106 else
107 {
108 *pResp = APIMON_RESPONSE_NONE;
109 rc = VERR_INVALID_PARAMETER;
110 }
111
112 return rc;
113}
114
115static const char* apimonResponseToStr(APIMON_RESPONSE enmResp)
116{
117 if (APIMON_RESPONSE_NONE == enmResp)
118 return "none";
119 else if (APIMON_RESPONSE_PAUSE == enmResp)
120 return "pausing";
121 else if (APIMON_RESPONSE_POWEROFF == enmResp)
122 return "powering off";
123 else if (APIMON_RESPONSE_SAVE == enmResp)
124 return "saving state";
125 else if (APIMON_RESPONSE_SHUTDOWN == enmResp)
126 return "shutting down";
127
128 return "unknown";
129}
130
131/* Copied from VBoxManageInfo.cpp. */
132static const char *apimonMachineStateToName(MachineState_T machineState, bool fShort)
133{
134 switch (machineState)
135 {
136 case MachineState_PoweredOff:
137 return fShort ? "poweroff" : "powered off";
138 case MachineState_Saved:
139 return "saved";
140 case MachineState_Aborted:
141 return "aborted";
142 case MachineState_Teleported:
143 return "teleported";
144 case MachineState_Running:
145 return "running";
146 case MachineState_Paused:
147 return "paused";
148 case MachineState_Stuck:
149 return fShort ? "gurumeditation" : "guru meditation";
150 case MachineState_LiveSnapshotting:
151 return fShort ? "livesnapshotting" : "live snapshotting";
152 case MachineState_Teleporting:
153 return "teleporting";
154 case MachineState_Starting:
155 return "starting";
156 case MachineState_Stopping:
157 return "stopping";
158 case MachineState_Saving:
159 return "saving";
160 case MachineState_Restoring:
161 return "restoring";
162 case MachineState_TeleportingPausedVM:
163 return fShort ? "teleportingpausedvm" : "teleporting paused vm";
164 case MachineState_TeleportingIn:
165 return fShort ? "teleportingin" : "teleporting (incoming)";
166 case MachineState_RestoringSnapshot:
167 return fShort ? "restoringsnapshot" : "restoring snapshot";
168 case MachineState_DeletingSnapshot:
169 return fShort ? "deletingsnapshot" : "deleting snapshot";
170 case MachineState_DeletingSnapshotOnline:
171 return fShort ? "deletingsnapshotlive" : "deleting snapshot live";
172 case MachineState_DeletingSnapshotPaused:
173 return fShort ? "deletingsnapshotlivepaused" : "deleting snapshot live paused";
174 case MachineState_SettingUp:
175 return fShort ? "settingup" : "setting up";
176 default:
177 break;
178 }
179 return "unknown";
180}
181
182static int apimonMachineControl(const Bstr &strUuid, PVBOXWATCHDOG_MACHINE pMachine,
183 APIMON_RESPONSE enmResp, unsigned long ulTimeout)
184{
185 /** @todo Add other commands (with enmResp) here. */
186 AssertPtrReturn(pMachine, VERR_INVALID_POINTER);
187
188 serviceLogVerbose(("apimon: Triggering \"%s\" (%RU32ms timeout) for machine \"%ls\"\n",
189 apimonResponseToStr(enmResp), ulTimeout, strUuid.raw()));
190
191 if ( enmResp == APIMON_RESPONSE_NONE
192 || g_fDryrun)
193 return VINF_SUCCESS; /* Nothing to do. */
194
195 HRESULT rc;
196 ComPtr <IMachine> machine;
197 CHECK_ERROR_RET(g_pVirtualBox, FindMachine(strUuid.raw(),
198 machine.asOutParam()), VERR_NOT_FOUND);
199 do
200 {
201 /* Query the machine's state to avoid unnecessary IPC. */
202 MachineState_T machineState;
203 CHECK_ERROR_BREAK(machine, COMGETTER(State)(&machineState));
204
205 if ( machineState == MachineState_Running
206 || machineState == MachineState_Paused)
207 {
208 /* Open a session for the VM. */
209 CHECK_ERROR_BREAK(machine, LockMachine(g_pSession, LockType_Shared));
210
211 do
212 {
213 /* Get the associated console. */
214 ComPtr<IConsole> console;
215 CHECK_ERROR_BREAK(g_pSession, COMGETTER(Console)(console.asOutParam()));
216
217 ComPtr<IProgress> progress;
218
219 switch (enmResp)
220 {
221 case APIMON_RESPONSE_PAUSE:
222 if (machineState != MachineState_Paused)
223 {
224 serviceLogVerbose(("apimon: Pausing machine \"%ls\" ...\n",
225 strUuid.raw()));
226 CHECK_ERROR_BREAK(console, Pause());
227 }
228 break;
229
230 case APIMON_RESPONSE_POWEROFF:
231 serviceLogVerbose(("apimon: Powering off machine \"%ls\" ...\n",
232 strUuid.raw()));
233 CHECK_ERROR_BREAK(console, PowerDown(progress.asOutParam()));
234 progress->WaitForCompletion(ulTimeout);
235 CHECK_PROGRESS_ERROR(progress, ("Failed to power off machine \"%ls\"",
236 strUuid.raw()));
237 break;
238
239 case APIMON_RESPONSE_SAVE:
240 {
241 serviceLogVerbose(("apimon: Saving state of machine \"%ls\" ...\n",
242 strUuid.raw()));
243
244 /* First pause so we don't trigger a live save which needs more time/resources. */
245 bool fPaused = false;
246 rc = console->Pause();
247 if (FAILED(rc))
248 {
249 bool fError = true;
250 if (rc == VBOX_E_INVALID_VM_STATE)
251 {
252 /* Check if we are already paused. */
253 CHECK_ERROR_BREAK(console, COMGETTER(State)(&machineState));
254 /* The error code was lost by the previous instruction. */
255 rc = VBOX_E_INVALID_VM_STATE;
256 if (machineState != MachineState_Paused)
257 {
258 serviceLog("apimon: Machine \"%ls\" in invalid state %d -- %s\n",
259 strUuid.raw(), machineState, apimonMachineStateToName(machineState, false));
260 }
261 else
262 {
263 fError = false;
264 fPaused = true;
265 }
266 }
267 if (fError)
268 break;
269 }
270
271 CHECK_ERROR(console, SaveState(progress.asOutParam()));
272 if (SUCCEEDED(rc))
273 {
274 progress->WaitForCompletion(ulTimeout);
275 CHECK_PROGRESS_ERROR(progress, ("Failed to save machine state of machine \"%ls\"",
276 strUuid.raw()));
277 }
278
279 if (SUCCEEDED(rc))
280 {
281 serviceLogVerbose(("apimon: State of machine \"%ls\" saved, powering off ...\n", strUuid.raw()));
282 CHECK_ERROR_BREAK(console, PowerButton());
283 }
284 else
285 serviceLogVerbose(("apimon: Saving state of machine \"%ls\" failed\n", strUuid.raw()));
286
287 break;
288 }
289
290 case APIMON_RESPONSE_SHUTDOWN:
291 serviceLogVerbose(("apimon: Shutting down machine \"%ls\" ...\n", strUuid.raw()));
292 CHECK_ERROR_BREAK(console, PowerButton());
293 break;
294
295 default:
296 AssertMsgFailed(("Response %d not implemented", enmResp));
297 break;
298 }
299 } while (0);
300
301 /* Unlock the machine again. */
302 g_pSession->UnlockMachine();
303 }
304 else
305 serviceLogVerbose(("apimon: Warning: Could not trigger \"%s\" (%d) for machine \"%ls\"; in state \"%s\" (%d) currently\n",
306 apimonResponseToStr(enmResp), enmResp, strUuid.raw(),
307 apimonMachineStateToName(machineState, false), machineState));
308 } while (0);
309
310
311
312 return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_COM_IPRT_ERROR;
313}
314
315static bool apimonHandleVM(const PVBOXWATCHDOG_MACHINE pMachine)
316{
317 bool fHandleVM = false;
318
319 try
320 {
321 mapGroupsIterConst itVMGroup = pMachine->groups.begin();
322 while ( itVMGroup != pMachine->groups.end()
323 && !fHandleVM)
324 {
325 mapGroupsIterConst itInGroup = g_vecAPIMonGroups.find(itVMGroup->first);
326 if (itInGroup != g_vecAPIMonGroups.end())
327 fHandleVM = true;
328
329 itVMGroup++;
330 }
331 }
332 catch (...)
333 {
334 AssertFailed();
335 }
336
337 return fHandleVM;
338}
339
340static int apimonTrigger(APIMON_RESPONSE enmResp)
341{
342 int rc = VINF_SUCCESS;
343
344 bool fAllGroups = g_vecAPIMonGroups.empty();
345 mapVMIter it = g_mapVM.begin();
346
347 if (it == g_mapVM.end())
348 {
349 serviceLog("apimon: No machines in list, skipping ...\n");
350 return rc;
351 }
352
353 while (it != g_mapVM.end())
354 {
355 bool fHandleVM = fAllGroups;
356 try
357 {
358 if (!fHandleVM)
359 fHandleVM = apimonHandleVM(&it->second);
360
361 if (fHandleVM)
362 {
363 int rc2 = apimonMachineControl(it->first /* Uuid */,
364 &it->second /* Machine */, enmResp, g_ulAPIMonResponseTimeoutMS);
365 if (RT_FAILURE(rc2))
366 serviceLog("apimon: Controlling machine \"%ls\" (response \"%s\") failed with rc=%Rrc",
367 it->first.raw(), apimonResponseToStr(enmResp), rc);
368
369 if (RT_SUCCESS(rc))
370 rc = rc2; /* Store original error. */
371 /* Keep going. */
372 }
373 }
374 catch (...)
375 {
376 AssertFailed();
377 }
378
379 it++;
380 }
381
382 return rc;
383}
384
385/* Callbacks. */
386static DECLCALLBACK(int) VBoxModAPIMonitorPreInit(void)
387{
388 return VINF_SUCCESS;
389}
390
391static DECLCALLBACK(int) VBoxModAPIMonitorOption(int argc, char **argv)
392{
393 if (!argc) /* Take a shortcut. */
394 return -1;
395
396 AssertPtrReturn(argv, VERR_INVALID_PARAMETER);
397
398 RTGETOPTSTATE GetState;
399 int rc = RTGetOptInit(&GetState, argc, argv,
400 g_aAPIMonitorOpts, RT_ELEMENTS(g_aAPIMonitorOpts),
401 0 /* First */, 0 /*fFlags*/);
402 if (RT_FAILURE(rc))
403 return rc;
404
405 rc = 0; /* Set default parsing result to valid. */
406
407 int c;
408 RTGETOPTUNION ValueUnion;
409 while ((c = RTGetOpt(&GetState, &ValueUnion)))
410 {
411 switch (c)
412 {
413 case GETOPTDEF_APIMON_GROUPS:
414 {
415 rc = groupAdd(g_vecAPIMonGroups, ValueUnion.psz, 0 /* Flags */);
416 if (RT_FAILURE(rc))
417 rc = -1; /* Option unknown. */
418 break;
419 }
420
421 case GETOPTDEF_APIMON_ISLN_RESPONSE:
422 rc = apimonResponseToEnum(ValueUnion.psz, &g_enmAPIMonIslnResp);
423 if (RT_FAILURE(rc))
424 rc = -1; /* Option unknown. */
425 break;
426
427 case GETOPTDEF_APIMON_ISLN_TIMEOUT:
428 g_ulAPIMonIslnTimeoutMS = ValueUnion.u32;
429 if (g_ulAPIMonIslnTimeoutMS < 1000) /* Don't allow timeouts < 1s. */
430 g_ulAPIMonIslnTimeoutMS = 1000;
431 break;
432
433 case GETOPTDEF_APIMON_RESP_TIMEOUT:
434 g_ulAPIMonResponseTimeoutMS = ValueUnion.u32;
435 if (g_ulAPIMonResponseTimeoutMS < 5000) /* Don't allow timeouts < 5s. */
436 g_ulAPIMonResponseTimeoutMS = 5000;
437 break;
438
439 default:
440 rc = -1; /* We don't handle this option, skip. */
441 break;
442 }
443 }
444
445 return rc;
446}
447
448static DECLCALLBACK(int) VBoxModAPIMonitorInit(void)
449{
450 HRESULT rc = S_OK;
451
452 do
453 {
454 Bstr strValue;
455
456 /* VM groups to watch for. */
457 if (g_vecAPIMonGroups.empty()) /* Not set by command line? */
458 {
459 CHECK_ERROR_BREAK(g_pVirtualBox, GetExtraData(Bstr("VBoxInternal2/Watchdog/APIMonitor/Groups").raw(),
460 strValue.asOutParam()));
461 if (!strValue.isEmpty())
462 {
463 int rc2 = groupAdd(g_vecAPIMonGroups, Utf8Str(strValue).c_str(), 0 /* Flags */);
464 if (RT_FAILURE(rc2))
465 serviceLog("apimon: Warning: API monitor groups string invalid (%ls)\n", strValue.raw());
466 }
467 }
468
469 if (!g_ulAPIMonIslnTimeoutMS)
470 cfgGetValueULong(g_pVirtualBox, NULL /* Machine */,
471 "VBoxInternal2/Watchdog/APIMonitor/IsolationTimeoutMS", NULL /* Per-machine */,
472 &g_ulAPIMonIslnTimeoutMS, 30 * 1000 /* Default is 30 seconds timeout. */);
473 g_ulAPIMonIslnTimeoutMS = RT_MIN(1000, g_ulAPIMonIslnTimeoutMS);
474
475 if (g_enmAPIMonIslnResp == APIMON_RESPONSE_NONE) /* Not set by command line? */
476 {
477 Utf8Str strResp;
478 int rc2 = cfgGetValueStr(g_pVirtualBox, NULL /* Machine */,
479 "VBoxInternal2/Watchdog/APIMonitor/IsolationResponse", NULL /* Per-machine */,
480 strResp, "" /* Default value. */);
481 if (RT_SUCCESS(rc2))
482 {
483 rc2 = apimonResponseToEnum(strResp.c_str(), &g_enmAPIMonIslnResp);
484 if (RT_FAILURE(rc2))
485 serviceLog("apimon: Warning: API monitor response string invalid (%ls), defaulting to no action\n",
486 strValue.raw());
487 }
488 }
489
490 if (!g_ulAPIMonResponseTimeoutMS)
491 cfgGetValueULong(g_pVirtualBox, NULL /* Machine */,
492 "VBoxInternal2/Watchdog/APIMonitor/ResponseTimeoutMS", NULL /* Per-machine */,
493 &g_ulAPIMonResponseTimeoutMS, 30 * 1000 /* Default is 30 seconds timeout. */);
494 g_ulAPIMonResponseTimeoutMS = RT_MIN(5000, g_ulAPIMonResponseTimeoutMS);
495
496#ifdef DEBUG
497 /* Groups. */
498 serviceLogVerbose(("apimon: Handling %u groups:", g_vecAPIMonGroups.size()));
499 mapGroupsIterConst itGroups = g_vecAPIMonGroups.begin();
500 while (itGroups != g_vecAPIMonGroups.end())
501 {
502 serviceLogVerbose((" %s", itGroups->first.c_str()));
503 itGroups++;
504 }
505 serviceLogVerbose(("\n"));
506#endif
507
508 } while (0);
509
510 if (SUCCEEDED(rc))
511 {
512 g_uAPIMonIslnLastBeatMS = 0;
513 }
514
515 return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_COM_IPRT_ERROR; /* @todo Find a better rc! */
516}
517
518static DECLCALLBACK(int) VBoxModAPIMonitorMain(void)
519{
520 static uint64_t uLastRun = 0;
521 uint64_t uNow = RTTimeProgramMilliTS();
522 uint64_t uDelta = uNow - uLastRun;
523 if (uDelta < 1000) /* Only check every second (or later). */
524 return VINF_SUCCESS;
525 uLastRun = uNow;
526
527 int vrc = VINF_SUCCESS;
528 HRESULT rc;
529
530#ifdef DEBUG
531 serviceLogVerbose(("apimon: Checking for API heartbeat (%RU64ms) ...\n",
532 g_ulAPIMonIslnTimeoutMS));
533#endif
534
535 do
536 {
537 Bstr strHeartbeat;
538 CHECK_ERROR_BREAK(g_pVirtualBox, GetExtraData(Bstr("Watchdog/APIMonitor/Heartbeat").raw(),
539 strHeartbeat.asOutParam()));
540 if ( SUCCEEDED(rc)
541 && !strHeartbeat.isEmpty()
542 && g_strAPIMonIslnLastBeat.compare(strHeartbeat, Bstr::CaseSensitive))
543 {
544 serviceLogVerbose(("apimon: API heartbeat received, resetting timeout\n"));
545
546 g_uAPIMonIslnLastBeatMS = 0;
547 g_strAPIMonIslnLastBeat = strHeartbeat;
548 }
549 else
550 {
551 g_uAPIMonIslnLastBeatMS += uDelta;
552 if (g_uAPIMonIslnLastBeatMS > g_ulAPIMonIslnTimeoutMS)
553 {
554 serviceLogVerbose(("apimon: No API heartbeat within time received (%RU64ms)\n",
555 g_ulAPIMonIslnTimeoutMS));
556
557 vrc = apimonTrigger(g_enmAPIMonIslnResp);
558 g_uAPIMonIslnLastBeatMS = 0;
559 }
560 }
561 } while (0);
562
563 if (FAILED(rc))
564 vrc = VERR_COM_IPRT_ERROR;
565
566 return vrc;
567}
568
569static DECLCALLBACK(int) VBoxModAPIMonitorStop(void)
570{
571 return VINF_SUCCESS;
572}
573
574static DECLCALLBACK(void) VBoxModAPIMonitorTerm(void)
575{
576}
577
578static DECLCALLBACK(int) VBoxModAPIMonitorOnMachineRegistered(const Bstr &strUuid)
579{
580 return VINF_SUCCESS;
581}
582
583static DECLCALLBACK(int) VBoxModAPIMonitorOnMachineUnregistered(const Bstr &strUuid)
584{
585 return VINF_SUCCESS;
586}
587
588static DECLCALLBACK(int) VBoxModAPIMonitorOnMachineStateChanged(const Bstr &strUuid,
589 MachineState_T enmState)
590{
591 return VINF_SUCCESS;
592}
593
594static DECLCALLBACK(int) VBoxModAPIMonitorOnServiceStateChanged(bool fAvailable)
595{
596 if (!fAvailable)
597 {
598 serviceLog(("apimon: VBoxSVC became unavailable, triggering action\n"));
599 return apimonTrigger(g_enmAPIMonIslnResp);
600 }
601 return VINF_SUCCESS;
602}
603
604/**
605 * The 'apimonitor' module description.
606 */
607VBOXMODULE g_ModAPIMonitor =
608{
609 /* pszName. */
610 VBOX_MOD_APIMON_NAME,
611 /* pszDescription. */
612 "API monitor for host isolation detection",
613 /* pszDepends. */
614 NULL,
615 /* uPriority. */
616 0 /* Not used */,
617 /* pszUsage. */
618 " [--apimon-groups=<string[,stringN]>]\n"
619 " [--apimon-isln-response=<cmd>] [--apimon-isln-timeout=<ms>]\n"
620 " [--apimon-resp-timeout=<ms>]",
621 /* pszOptions. */
622 "--apimon-groups Sets the VM groups for monitoring (all),\n"
623 " comma-separated list.\n"
624 "--apimon-isln-response Sets the isolation response to one of:\n"
625 " none, pause, poweroff, save, shutdown\n"
626 " (none).\n"
627 "--apimon-isln-timeout Sets the isolation timeout in ms (30s).\n"
628 "--apimon-resp-timeout Sets the response timeout in ms (30s).\n",
629 /* methods. */
630 VBoxModAPIMonitorPreInit,
631 VBoxModAPIMonitorOption,
632 VBoxModAPIMonitorInit,
633 VBoxModAPIMonitorMain,
634 VBoxModAPIMonitorStop,
635 VBoxModAPIMonitorTerm,
636 /* callbacks. */
637 VBoxModAPIMonitorOnMachineRegistered,
638 VBoxModAPIMonitorOnMachineUnregistered,
639 VBoxModAPIMonitorOnMachineStateChanged,
640 VBoxModAPIMonitorOnServiceStateChanged
641};
642
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use