VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp@ 5868

Last change on this file since 5868 was 5842, checked in by vboxsync, 17 years ago

build fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.5 KB
Line 
1/** @file
2 *
3 * VBox frontends: VBoxHeadless (headless frontend):
4 * Headless server executable
5 */
6
7/*
8 * Copyright (C) 2006-2007 innotek GmbH
9 *
10 * innotek GmbH confidential
11 * All rights reserved
12 */
13
14#include <VBox/com/com.h>
15#include <VBox/com/string.h>
16#include <VBox/com/Guid.h>
17#include <VBox/com/ErrorInfo.h>
18#include <VBox/com/EventQueue.h>
19
20#include <VBox/com/VirtualBox.h>
21
22using namespace com;
23
24#define LOG_GROUP LOG_GROUP_GUI
25
26#include <VBox/log.h>
27#include <VBox/version.h>
28#ifdef VBOX_WITH_VRDP
29# include <VBox/vrdpapi.h>
30#endif
31#include <iprt/runtime.h>
32#include <iprt/stream.h>
33#include <iprt/ldr.h>
34
35#ifdef VBOX_FFMPEG
36#include <cstdlib>
37#include <cerrno>
38#include "VBoxHeadless.h"
39#include <iprt/env.h>
40#include <iprt/param.h>
41#include <iprt/process.h>
42#endif
43
44#ifdef VBOX_WITH_VRDP
45# include "Framebuffer.h"
46#endif
47
48////////////////////////////////////////////////////////////////////////////////
49
50#define LogError(m,rc) \
51 if (1) { \
52 Log (("VBoxHeadless: ERROR: " m " [rc=0x%08X]\n", rc)); \
53 RTPrintf (m " (rc = 0x%08X)\n", rc); \
54 }
55
56////////////////////////////////////////////////////////////////////////////////
57
58/* global weak references (for event handlers) */
59static ComPtr <ISession, ComWeakRef> gSession;
60static ComPtr <IConsole, ComWeakRef> gConsole;
61static EventQueue *gEventQ = NULL;
62
63////////////////////////////////////////////////////////////////////////////////
64
65/**
66 * State change event.
67 */
68class StateChangeEvent : public Event
69{
70public:
71 StateChangeEvent (MachineState_T state) : mState (state) {}
72protected:
73 void *handler()
74 {
75 LogFlow (("VBoxHeadless: StateChangeEvent: %d\n", mState));
76 /* post the termination event if the machine has been PoweredDown/Saved/Aborted */
77 if (mState < MachineState_Running)
78 gEventQ->postEvent (NULL);
79 return 0;
80 }
81private:
82 MachineState_T mState;
83};
84
85/**
86 * Callback handler for machine events.
87 */
88class ConsoleCallback : public IConsoleCallback
89{
90public:
91
92 ConsoleCallback ()
93 {
94#ifndef VBOX_WITH_XPCOM
95 refcnt = 0;
96#endif
97 }
98
99 virtual ~ConsoleCallback() {}
100
101 NS_DECL_ISUPPORTS
102
103#ifndef VBOX_WITH_XPCOM
104 STDMETHOD_(ULONG, AddRef)()
105 {
106 return ::InterlockedIncrement(&refcnt);
107 }
108 STDMETHOD_(ULONG, Release)()
109 {
110 long cnt = ::InterlockedDecrement(&refcnt);
111 if (cnt == 0)
112 delete this;
113 return cnt;
114 }
115 STDMETHOD(QueryInterface) (REFIID riid , void **ppObj)
116 {
117 if (riid == IID_IUnknown)
118 {
119 *ppObj = this;
120 AddRef();
121 return S_OK;
122 }
123 if (riid == IID_IConsoleCallback)
124 {
125 *ppObj = this;
126 AddRef();
127 return S_OK;
128 }
129 *ppObj = NULL;
130 return E_NOINTERFACE;
131 }
132#endif
133
134 STDMETHOD(OnMousePointerShapeChange) (BOOL visible, BOOL alpha, ULONG xHot, ULONG yHot,
135 ULONG width, ULONG height, BYTE *shape)
136 {
137 return S_OK;
138 }
139
140 STDMETHOD(OnMouseCapabilityChange) (BOOL supportsAbsolute, BOOL needsHostCursor)
141 {
142 /* Emit absolute mouse event to actually enable the host mouse cursor. */
143 if (supportsAbsolute && gConsole)
144 {
145 ComPtr<IMouse> mouse;
146 gConsole->COMGETTER(Mouse)(mouse.asOutParam());
147 if (mouse)
148 {
149 mouse->PutMouseEventAbsolute(-1, -1, 0, 0);
150 }
151 }
152 return S_OK;
153 }
154
155 STDMETHOD(OnKeyboardLedsChange)(BOOL fNumLock, BOOL fCapsLock, BOOL fScrollLock)
156 {
157 return S_OK;
158 }
159
160 STDMETHOD(OnStateChange) (MachineState_T machineState)
161 {
162 gEventQ->postEvent (new StateChangeEvent (machineState));
163 return S_OK;
164 }
165
166 STDMETHOD(OnExtraDataChange) (BSTR key)
167 {
168 return S_OK;
169 }
170
171 STDMETHOD(OnAdditionsStateChange)()
172 {
173 return S_OK;
174 }
175
176 STDMETHOD(OnDVDDriveChange)()
177 {
178 return S_OK;
179 }
180
181 STDMETHOD(OnFloppyDriveChange)()
182 {
183 return S_OK;
184 }
185
186 STDMETHOD(OnNetworkAdapterChange) (INetworkAdapter *aNetworkAdapter)
187 {
188 return S_OK;
189 }
190
191 STDMETHOD(OnSerialPortChange) (ISerialPort *aSerialPort)
192 {
193 return S_OK;
194 }
195
196 STDMETHOD(OnParallelPortChange) (IParallelPort *aParallelPort)
197 {
198 return S_OK;
199 }
200
201 STDMETHOD(OnVRDPServerChange)()
202 {
203 return S_OK;
204 }
205
206 STDMETHOD(OnUSBControllerChange)()
207 {
208 return S_OK;
209 }
210
211 STDMETHOD(OnUSBDeviceStateChange) (IUSBDevice *aDevice, BOOL aAttached,
212 IVirtualBoxErrorInfo *aError)
213 {
214 return S_OK;
215 }
216
217 STDMETHOD(OnSharedFolderChange) (Scope_T aScope)
218 {
219 return S_OK;
220 }
221
222 STDMETHOD(OnRuntimeError)(BOOL fatal, INPTR BSTR id, INPTR BSTR message)
223 {
224 return S_OK;
225 }
226
227 STDMETHOD(OnCanShowWindow)(BOOL *canShow)
228 {
229 if (!canShow)
230 return E_POINTER;
231 /* Headless windows should not be shown */
232 *canShow = FALSE;
233 return S_OK;
234 }
235
236 STDMETHOD(OnShowWindow) (ULONG64 *winId)
237 {
238 /* OnCanShowWindow() always returns FALSE, so this call should never
239 * happen. */
240 AssertFailed();
241 if (!winId)
242 return E_POINTER;
243 *winId = 0;
244 return E_NOTIMPL;
245 }
246
247private:
248
249#ifndef VBOX_WITH_XPCOM
250 long refcnt;
251#endif
252};
253
254#ifdef VBOX_WITH_XPCOM
255NS_DECL_CLASSINFO (ConsoleCallback)
256NS_IMPL_THREADSAFE_ISUPPORTS1_CI (ConsoleCallback, IConsoleCallback)
257#endif
258
259////////////////////////////////////////////////////////////////////////////////
260
261static void show_usage()
262{
263 RTPrintf("Usage:\n"
264 " -startvm <name|uuid> Start given VM (required argument)\n"
265#ifdef VBOX_WITH_VRDP
266 " -vrdpport <port> Port number the VRDP server will bind to\n"
267 " -vrdpaddress <ip> Interface IP the VRDP will bind to \n"
268#endif
269 "\n");
270}
271
272/**
273 * Entry point.
274 */
275int main (int argc, char **argv)
276{
277#ifdef VBOX_WITH_VRDP
278 ULONG vrdpPort = ~0U;
279 const char *vrdpAddress = NULL;
280#endif
281 unsigned fRawR0 = ~0U;
282 unsigned fRawR3 = ~0U;
283 unsigned fPATM = ~0U;
284 unsigned fCSAM = ~0U;
285#ifdef VBOX_FFMPEG
286 unsigned fFFMPEG = 0;
287 unsigned ulFrameWidth = 800;
288 unsigned ulFrameHeight = 600;
289 unsigned ulBitRate = 300000;
290 char pszMPEGFile[RTPATH_MAX];
291#endif /* VBOX_FFMPEG */
292
293 // initialize VBox Runtime
294 RTR3Init(true, ~(size_t)0);
295
296 LogFlow (("VBoxHeadless STARTED.\n"));
297 RTPrintf ("VirtualBox Headless Interface %s\n"
298 "(C) 2005-2007 innotek GmbH\n"
299 "All rights reserved\n\n",
300 VBOX_VERSION_STRING);
301
302 Guid id;
303 /* the below cannot be Bstr because on Linux Bstr doesn't work until XPCOM (nsMemory) is initialized */
304 const char *name = NULL;
305
306 // parse the command line
307 for (int curArg = 1; curArg < argc; curArg++)
308 {
309 if (strcmp(argv[curArg], "-startvm") == 0)
310 {
311 if (++curArg >= argc)
312 {
313 LogError("VBoxHeadless: ERROR: missing VM identifier!", 0);
314 return -1;
315 }
316 id = argv[curArg];
317 /* If the argument was not a UUID, then it must be a name. */
318 if (!id)
319 {
320 name = argv[curArg];
321 }
322 }
323#ifdef VBOX_WITH_VRDP
324 else if (strcmp(argv[curArg], "-vrdpport") == 0)
325 {
326 if (++curArg >= argc)
327 {
328 LogError("VBoxHeadless: ERROR: missing VRDP port value!", 0);
329 return -1;
330 }
331 vrdpPort = atoi(argv[curArg]);
332 }
333 else if (strcmp(argv[curArg], "-vrdpaddress") == 0)
334 {
335 if (++curArg >= argc)
336 {
337 LogError("VBoxHeadless: ERROR: missing VRDP address value!", 0);
338 return -1;
339 }
340 vrdpAddress = argv[curArg];
341 }
342#endif
343 else if (strcmp(argv[curArg], "-rawr0") == 0)
344 {
345 fRawR0 = true;
346 }
347 else if (strcmp(argv[curArg], "-norawr0") == 0)
348 {
349 fRawR0 = false;
350 }
351 else if (strcmp(argv[curArg], "-rawr3") == 0)
352 {
353 fRawR3 = true;
354 }
355 else if (strcmp(argv[curArg], "-norawr3") == 0)
356 {
357 fRawR3 = false;
358 }
359 else if (strcmp(argv[curArg], "-patm") == 0)
360 {
361 fPATM = true;
362 }
363 else if (strcmp(argv[curArg], "-nopatm") == 0)
364 {
365 fPATM = false;
366 }
367 else if (strcmp(argv[curArg], "-csam") == 0)
368 {
369 fCSAM = true;
370 }
371 else if (strcmp(argv[curArg], "-nocsam") == 0)
372 {
373 fCSAM = false;
374 }
375#ifdef VBOX_FFMPEG
376 else if (strcmp(argv[curArg], "-capture") == 0)
377 {
378 fFFMPEG = true;
379 }
380#endif
381 else if (strcmp(argv[curArg], "-comment") == 0)
382 {
383 /* We could just ignore this, but check the syntax for consistency... */
384 if (++curArg >= argc)
385 {
386 LogError("VBoxHeadless: ERROR: missing comment!", 0);
387 return -1;
388 }
389 }
390 else
391 {
392 LogError("VBoxHeadless: ERROR: unknown option '%s'", argv[curArg]);
393 show_usage();
394 return -1;
395 }
396 }
397
398#ifdef VBOX_FFMPEG
399 const char *pszEnvTemp;
400 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREWIDTH")) != 0)
401 {
402 errno = 0;
403 ulFrameWidth = strtoul(pszEnvTemp, 0, 10);
404 if (errno != 0)
405 {
406 LogError("VBoxHeadless: ERROR: invalid VBOX_CAPTUREWIDTH environment variable", 0);
407 return -1;
408 }
409 if (ulFrameWidth < 512 || ulFrameWidth > 2048 || ulFrameWidth % 2)
410 {
411 LogError("VBoxHeadless: ERROR: please specify an even VBOX_CAPTUREWIDTH variable between 512 and 2048", 0);
412 return -1;
413 }
414 }
415
416 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREHEIGHT")) != 0)
417 {
418 errno = 0;
419 ulFrameHeight = strtoul(pszEnvTemp, 0, 10);
420 if (errno != 0)
421 {
422 LogError("VBoxHeadless: ERROR: invalid VBOX_CAPTUREHEIGHT environment variable", 0);
423 return -1;
424 }
425 if (ulFrameHeight < 384 || ulFrameHeight > 1536 || ulFrameHeight % 2)
426 {
427 LogError("VBoxHeadless: ERROR: please specify an even VBOX_CAPTUREHEIGHT variable between 384 and 1536", 0);
428 return -1;
429 }
430 }
431
432 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREBITRATE")) != 0)
433 {
434 errno = 0;
435 ulBitRate = strtoul(pszEnvTemp, 0, 10);
436 if (errno != 0)
437 {
438 LogError("VBoxHeadless: ERROR: invalid VBOX_CAPTUREBITRATE environment variable", 0);
439 return -1;
440 }
441 if (ulBitRate < 300000 || ulBitRate > 1000000)
442 {
443 LogError("VBoxHeadless: ERROR: please specify an even VBOX_CAPTUREHEIGHT variable between 300000 and 1000000", 0);
444 return -1;
445 }
446 }
447
448 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREFILE")) == 0)
449 /* Standard base name */
450 pszEnvTemp = "VBox-%d.vob";
451
452 /* Make sure we only have %d or %u (or none) */
453 char *pcPercent = (char*)strchr(pszEnvTemp, '%');
454 if (pcPercent != 0 && *(pcPercent + 1) != 'd' && *(pcPercent + 1) != 'u')
455 {
456 LogError("VBoxHeadless: ERROR: Only %%d and %%u are allowed in the VBOX_CAPTUREFILE parameter.", -1);
457 return -1;
458 }
459
460 /* And no more than one % in the name */
461 if (pcPercent != 0 && strchr(pcPercent + 1, '%') != 0)
462 {
463 LogError("VBoxHeadless: ERROR: Only one format modifier is allowed in the VBOX_CAPTUREFILE parameter.", -1);
464 return -1;
465 }
466 RTStrPrintf(&pszMPEGFile[0], RTPATH_MAX, pszEnvTemp, RTProcSelf());
467#endif /* defined VBOX_FFMPEG */
468
469 if (!id && !name)
470 {
471 LogError("VBoxHeadless: ERROR: invalid invocation!", -1);
472 show_usage();
473 return -1;
474 }
475
476 HRESULT rc;
477
478 rc = com::Initialize();
479 if (FAILED (rc))
480 return rc;
481
482 do
483 {
484 ComPtr <IVirtualBox> virtualBox;
485 ComPtr <ISession> session;
486
487 /* create VirtualBox object */
488 rc = virtualBox.createLocalObject (CLSID_VirtualBox);
489 if (FAILED (rc))
490 {
491 com::ErrorInfo info;
492 if (info.isFullAvailable())
493 {
494 RTPrintf("Failed to create VirtualBox object! Error info: '%lS' (component %lS).\n",
495 info.getText().raw(), info.getComponent().raw());
496 }
497 else
498 RTPrintf("Failed to create VirtualBox object! No error information available (rc = 0x%x).\n", rc);
499 break;
500 }
501
502 /* create Session object */
503 rc = session.createInprocObject (CLSID_Session);
504 if (FAILED (rc))
505 {
506 LogError ("Cannot create Session object!", rc);
507 break;
508 }
509
510 /* find ID by name */
511 if (!id)
512 {
513 ComPtr <IMachine> m;
514 rc = virtualBox->FindMachine (Bstr (name), m.asOutParam());
515 if (FAILED (rc))
516 {
517 LogError ("Invalid machine name!\n", rc);
518 break;
519 }
520 m->COMGETTER(Id) (id.asOutParam());
521 AssertComRC (rc);
522 if (FAILED (rc))
523 break;
524 }
525
526 Log (("VBoxHeadless: Opening a session with machine (id={%s})...\n",
527 id.toString().raw()));
528
529 // open a session
530 CHECK_ERROR_BREAK(virtualBox, OpenSession (session, id));
531
532 /* get the console */
533 ComPtr <IConsole> console;
534 CHECK_ERROR_BREAK(session, COMGETTER (Console) (console.asOutParam()));
535
536 /* get the machine */
537 ComPtr <IMachine> machine;
538 CHECK_ERROR_BREAK(console, COMGETTER(Machine) (machine.asOutParam()));
539
540 ComPtr <IDisplay> display;
541 CHECK_ERROR_BREAK(console, COMGETTER(Display) (display.asOutParam()));
542
543#ifdef VBOX_FFMPEG
544 IFramebuffer *pFramebuffer = 0;
545 RTLDRMOD hLdrFFmpegFB;
546 PFNREGISTERFFMPEGFB pfnRegisterFFmpegFB;
547
548 if (fFFMPEG)
549 {
550 Log2(("VBoxHeadless: loading VBoxFFmpegFB shared library\n"));
551 rc = RTLdrLoad("VBoxFFmpegFB", &hLdrFFmpegFB);
552
553 if (rc == VINF_SUCCESS)
554 {
555 Log2(("VBoxHeadless: looking up symbol VBoxRegisterFFmpegFB\n"));
556 rc = RTLdrGetSymbol(hLdrFFmpegFB, "VBoxRegisterFFmpegFB",
557 reinterpret_cast<void **>(&pfnRegisterFFmpegFB));
558
559 if (rc == VINF_SUCCESS)
560 {
561 Log2(("VBoxHeadless: calling pfnRegisterFFmpegFB\n"));
562 rc = pfnRegisterFFmpegFB(ulFrameWidth, ulFrameHeight, ulBitRate,
563 pszMPEGFile, &pFramebuffer);
564
565 if (rc == S_OK)
566 {
567 Log2(("VBoxHeadless: Registering framebuffer\n"));
568 pFramebuffer->AddRef();
569 display->RegisterExternalFramebuffer(pFramebuffer);
570 }
571 }
572 }
573 }
574 if (rc != S_OK)
575 {
576 LogError ("Failed to load VBoxFFmpegFB shared library\n", 0);
577 return -1;
578 }
579#endif /* defined(VBOX_FFMPEG) */
580
581 ULONG cMonitors = 1;
582 machine->COMGETTER(MonitorCount)(&cMonitors);
583
584#ifdef VBOX_WITH_VRDP
585 unsigned uScreenId;
586 for (uScreenId = 0; uScreenId < cMonitors; uScreenId++)
587 {
588#ifdef VBOX_FFMPEG
589 if (fFFMPEG && uScreenId == 0)
590 {
591 /* Already registered. */
592 continue;
593 }
594#endif /* defined(VBOX_FFMPEG) */
595 VRDPFramebuffer *pFramebuffer = new VRDPFramebuffer ();
596 if (!pFramebuffer)
597 {
598 RTPrintf("Error: could not create framebuffer object %d\n", uScreenId);
599 return -1;
600 }
601 pFramebuffer->AddRef();
602 display->SetFramebuffer(uScreenId, pFramebuffer);
603 }
604#endif
605
606 /* get the machine debugger (isn't necessarily available) */
607 ComPtr <IMachineDebugger> machineDebugger;
608 console->COMGETTER(Debugger)(machineDebugger.asOutParam());
609 if (machineDebugger)
610 {
611 Log(("Machine debugger available!\n"));
612 }
613
614 if (fRawR0 != ~0U)
615 {
616 if (!machineDebugger)
617 {
618 RTPrintf("Error: No debugger object; -%srawr0 cannot be executed!\n", fRawR0 ? "" : "no");
619 break;
620 }
621 machineDebugger->COMSETTER(RecompileSupervisor)(!fRawR0);
622 }
623 if (fRawR3 != ~0U)
624 {
625 if (!machineDebugger)
626 {
627 RTPrintf("Error: No debugger object; -%srawr3 cannot be executed!\n", fRawR0 ? "" : "no");
628 break;
629 }
630 machineDebugger->COMSETTER(RecompileUser)(!fRawR3);
631 }
632 if (fPATM != ~0U)
633 {
634 if (!machineDebugger)
635 {
636 RTPrintf("Error: No debugger object; -%spatm cannot be executed!\n", fRawR0 ? "" : "no");
637 break;
638 }
639 machineDebugger->COMSETTER(PATMEnabled)(fPATM);
640 }
641 if (fCSAM != ~0U)
642 {
643 if (!machineDebugger)
644 {
645 RTPrintf("Error: No debugger object; -%scsam cannot be executed!\n", fRawR0 ? "" : "no");
646 break;
647 }
648 machineDebugger->COMSETTER(CSAMEnabled)(fCSAM);
649 }
650
651 /* create an event queue */
652 EventQueue eventQ;
653
654 /* initialize global references */
655 gSession = session;
656 gConsole = console;
657 gEventQ = &eventQ;
658
659 /* register a callback for machine events */
660 {
661 ConsoleCallback *callback = new ConsoleCallback ();
662 callback->AddRef();
663 CHECK_ERROR(console, RegisterCallback (callback));
664 callback->Release();
665 if (FAILED (rc))
666 break;
667 }
668
669#ifdef VBOX_WITH_VRDP
670 Log (("VBoxHeadless: Enabling VRDP server...\n"));
671
672 ComPtr <IVRDPServer> vrdpServer;
673 CHECK_ERROR_BREAK(machine, COMGETTER (VRDPServer) (vrdpServer.asOutParam()));
674
675 /* set VRDP port if requested by the user */
676 if (vrdpPort != ~0U)
677 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Port)(vrdpPort));
678 else
679 CHECK_ERROR_BREAK(vrdpServer, COMGETTER(Port)(&vrdpPort));
680 /* set VRDP address if requested by the user */
681 if (vrdpAddress != NULL)
682 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(NetAddress)(Bstr(vrdpAddress)));
683
684 /* enable VRDP server (only if currently disabled) */
685 BOOL fVRDPEnabled;
686 CHECK_ERROR_BREAK(vrdpServer, COMGETTER(Enabled) (&fVRDPEnabled));
687 if (!fVRDPEnabled)
688 {
689 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Enabled) (TRUE));
690 }
691#endif
692 Log (("VBoxHeadless: Powering up the machine...\n"));
693#ifdef VBOX_WITH_VRDP
694 RTPrintf("Listening on port %d\n", !vrdpPort ? VRDP_DEFAULT_PORT : vrdpPort);
695#endif
696
697 ComPtr <IProgress> progress;
698 CHECK_ERROR_BREAK(console, PowerUp (progress.asOutParam()));
699
700 /* wait for result because there can be errors */
701 if (SUCCEEDED(progress->WaitForCompletion (-1)))
702 {
703 progress->COMGETTER(ResultCode)(&rc);
704 if (FAILED(rc))
705 {
706 com::ProgressErrorInfo info(progress);
707 if (info.isBasicAvailable())
708 {
709 RTPrintf("Error: failed to start machine. Error message: %lS\n", info.getText().raw());
710 }
711 else
712 {
713 RTPrintf("Error: failed to start machine. No error message available!\n");
714 }
715 }
716 }
717 Log (("VBoxHeadless: Waiting for PowerDown...\n"));
718
719 Event *e;
720
721 while (eventQ.waitForEvent (&e) && e)
722 eventQ.handleEvent (e);
723
724 Log (("VBoxHeadless: event loop has terminated...\n"));
725
726#ifdef VBOX_FFMPEG
727 if (pFramebuffer)
728 {
729 pFramebuffer->Release ();
730 Log(("Released framebuffer\n"));
731 pFramebuffer = NULL;
732 }
733#endif /* defined(VBOX_FFMPEG) */
734
735 /* we don't have to disable VRDP here because we don't save the settings of the VM */
736
737 /*
738 * Close the session. This will also uninitialize the console and
739 * unregister the callback we've registered before.
740 */
741 Log (("VBoxHeadless: Closing the session...\n"));
742 session->Close();
743 }
744 while (0);
745
746 com::Shutdown();
747
748 LogFlow (("VBoxHeadless FINISHED.\n"));
749
750 return rc;
751}
752
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette