VirtualBox

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

Last change on this file since 31678 was 31579, checked in by vboxsync, 14 years ago

EventQueue: Fix losing messages, use the right queue type on XPCOM (the fact that event handling in VBoxSVC worked was mainly luck), big code cleanup. VBoxHeadless and VirtualBoxImpl now use the only remaining event processing style. Eliminated redundant custom StateChange event in VBoxHeadless.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.7 KB
Line 
1/* $Id: VBoxHeadless.cpp 31579 2010-08-11 17:21:27Z vboxsync $ */
2/** @file
3 * VBoxHeadless - The VirtualBox Headless frontend for running VMs on servers.
4 */
5
6/*
7 * Copyright (C) 2006-2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include <VBox/com/com.h>
19#include <VBox/com/string.h>
20#include <VBox/com/array.h>
21#include <VBox/com/Guid.h>
22#include <VBox/com/ErrorInfo.h>
23#include <VBox/com/errorprint.h>
24#include <VBox/com/EventQueue.h>
25
26#include <VBox/com/VirtualBox.h>
27
28using namespace com;
29
30#define LOG_GROUP LOG_GROUP_GUI
31
32#include <VBox/log.h>
33#include <VBox/version.h>
34#ifdef VBOX_WITH_VRDP
35# include <VBox/vrdpapi.h>
36#endif
37#include <iprt/buildconfig.h>
38#include <iprt/ctype.h>
39#include <iprt/initterm.h>
40#include <iprt/stream.h>
41#include <iprt/ldr.h>
42#include <iprt/getopt.h>
43#include <iprt/env.h>
44#include <VBox/err.h>
45#include <VBox/VBoxVideo.h>
46
47#ifdef VBOX_FFMPEG
48#include <cstdlib>
49#include <cerrno>
50#include "VBoxHeadless.h"
51#include <iprt/env.h>
52#include <iprt/param.h>
53#include <iprt/process.h>
54#include <VBox/sup.h>
55#endif
56
57//#define VBOX_WITH_SAVESTATE_ON_SIGNAL
58#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
59#include <signal.h>
60#endif
61
62#ifdef VBOX_WITH_VRDP
63# include "Framebuffer.h"
64#endif
65#ifdef VBOX_WITH_VNC
66# include "FramebufferVNC.h"
67#endif
68
69
70////////////////////////////////////////////////////////////////////////////////
71
72#define LogError(m,rc) \
73 do { \
74 Log(("VBoxHeadless: ERROR: " m " [rc=0x%08X]\n", rc)); \
75 RTPrintf("%s\n", m); \
76 } while (0)
77
78////////////////////////////////////////////////////////////////////////////////
79
80/* global weak references (for event handlers) */
81static ISession *gSession = NULL;
82static IConsole *gConsole = NULL;
83static EventQueue *gEventQ = NULL;
84
85/* flag whether frontend should terminate */
86static volatile bool g_fTerminateFE = false;
87
88#ifdef VBOX_WITH_VNC
89static VNCFB *g_pFramebufferVNC;
90#endif
91
92
93////////////////////////////////////////////////////////////////////////////////
94
95/**
96 * Handler for global events.
97 */
98class VirtualBoxEventListener :
99 VBOX_SCRIPTABLE_IMPL(IEventListener)
100{
101public:
102 VirtualBoxEventListener()
103 {
104#ifndef VBOX_WITH_XPCOM
105 refcnt = 0;
106#endif
107 mfNoLoggedInUsers = true;
108 }
109
110 virtual ~VirtualBoxEventListener()
111 {
112 }
113
114#ifndef VBOX_WITH_XPCOM
115 STDMETHOD_(ULONG, AddRef)()
116 {
117 return ::InterlockedIncrement(&refcnt);
118 }
119 STDMETHOD_(ULONG, Release)()
120 {
121 long cnt = ::InterlockedDecrement(&refcnt);
122 if (cnt == 0)
123 delete this;
124 return cnt;
125 }
126#endif
127 VBOX_SCRIPTABLE_DISPATCH_IMPL(IEventListener)
128
129 NS_DECL_ISUPPORTS
130
131 STDMETHOD(HandleEvent)(IEvent * aEvent)
132 {
133 VBoxEventType_T aType = VBoxEventType_Invalid;
134
135 aEvent->COMGETTER(Type)(&aType);
136 switch (aType)
137 {
138 case VBoxEventType_OnGuestPropertyChanged:
139 {
140 ComPtr<IGuestPropertyChangedEvent> gpcev = aEvent;
141 Assert(gpcev);
142
143 Bstr aKey;
144 gpcev->COMGETTER(Name)(aKey.asOutParam());
145
146 if (aKey == Bstr("/VirtualBox/GuestInfo/OS/NoLoggedInUsers"))
147 {
148 /* Check if this is our machine and the "disconnect on logout feature" is enabled. */
149 BOOL fProcessDisconnectOnGuestLogout = FALSE;
150 ComPtr <IMachine> machine;
151 HRESULT hrc = S_OK;
152
153 if (gConsole)
154 {
155 hrc = gConsole->COMGETTER(Machine)(machine.asOutParam());
156 if (SUCCEEDED(hrc) && machine)
157 {
158 Bstr id, machineId;
159 hrc = machine->COMGETTER(Id)(id.asOutParam());
160 gpcev->COMGETTER(MachineId)(machineId.asOutParam());
161 if (id == machineId)
162 {
163 Bstr value1;
164 hrc = machine->GetExtraData(Bstr("VRDP/DisconnectOnGuestLogout"), value1.asOutParam());
165 if (SUCCEEDED(hrc) && value1 == "1")
166 {
167 fProcessDisconnectOnGuestLogout = TRUE;
168 }
169 }
170 }
171 }
172
173 if (fProcessDisconnectOnGuestLogout)
174 {
175 Bstr value;
176 gpcev->COMGETTER(Value)(value.asOutParam());
177 Utf8Str utf8Value = value;
178 if (utf8Value == "true")
179 {
180 if (!mfNoLoggedInUsers) /* Only if the property really changes. */
181 {
182 mfNoLoggedInUsers = true;
183
184 /* If there is a VRDP connection, drop it. */
185 ComPtr<IRemoteDisplayInfo> info;
186 hrc = gConsole->COMGETTER(RemoteDisplayInfo)(info.asOutParam());
187 if (SUCCEEDED(hrc) && info)
188 {
189 ULONG cClients = 0;
190 hrc = info->COMGETTER(NumberOfClients)(&cClients);
191 if (SUCCEEDED(hrc) && cClients > 0)
192 {
193 ComPtr <IVRDPServer> vrdpServer;
194 hrc = machine->COMGETTER(VRDPServer)(vrdpServer.asOutParam());
195 if (SUCCEEDED(hrc) && vrdpServer)
196 {
197 vrdpServer->COMSETTER(Enabled)(FALSE);
198 vrdpServer->COMSETTER(Enabled)(TRUE);
199 }
200 }
201 }
202 }
203 }
204 else
205 {
206 mfNoLoggedInUsers = false;
207 }
208 }
209 }
210 break;
211 }
212 default:
213 AssertFailed();
214 }
215
216 return S_OK;
217 }
218
219private:
220#ifndef VBOX_WITH_XPCOM
221 long refcnt;
222#endif
223
224 bool mfNoLoggedInUsers;
225};
226
227
228/**
229 * Handler for machine events.
230 */
231class ConsoleEventListener :
232 VBOX_SCRIPTABLE_IMPL(IEventListener)
233{
234public:
235 ConsoleEventListener()
236 {
237#ifndef VBOX_WITH_XPCOM
238 refcnt = 0;
239#endif
240 mLastVRDPPort = -1;
241 }
242
243 virtual ~ConsoleEventListener()
244 {
245 }
246
247#ifndef VBOX_WITH_XPCOM
248 STDMETHOD_(ULONG, AddRef)()
249 {
250 return ::InterlockedIncrement(&refcnt);
251 }
252 STDMETHOD_(ULONG, Release)()
253 {
254 long cnt = ::InterlockedDecrement(&refcnt);
255 if (cnt == 0)
256 delete this;
257 return cnt;
258 }
259#endif
260 VBOX_SCRIPTABLE_DISPATCH_IMPL(IEventListener)
261
262 NS_DECL_ISUPPORTS
263
264 STDMETHOD(HandleEvent)(IEvent * aEvent)
265 {
266 VBoxEventType_T aType = VBoxEventType_Invalid;
267
268 aEvent->COMGETTER(Type)(&aType);
269 switch (aType)
270 {
271 case VBoxEventType_OnMouseCapabilityChanged:
272 {
273
274 ComPtr<IMouseCapabilityChangedEvent> mccev = aEvent;
275 Assert(mccev);
276
277 BOOL fSupportsAbsolute = false;
278 mccev->COMGETTER(SupportsAbsolute)(&fSupportsAbsolute);
279
280 /* Emit absolute mouse event to actually enable the host mouse cursor. */
281 if (fSupportsAbsolute && gConsole)
282 {
283 ComPtr<IMouse> mouse;
284 gConsole->COMGETTER(Mouse)(mouse.asOutParam());
285 if (mouse)
286 {
287 mouse->PutMouseEventAbsolute(-1, -1, 0, 0 /* Horizontal wheel */, 0);
288 }
289 }
290#ifdef VBOX_WITH_VNC
291 if (g_pFramebufferVNC)
292 g_pFramebufferVNC->enableAbsMouse(fSupportsAbsolute);
293#endif
294 break;
295 }
296 case VBoxEventType_OnStateChanged:
297 {
298 ComPtr<IStateChangedEvent> scev = aEvent;
299 Assert(scev);
300
301 MachineState_T machineState;
302 scev->COMGETTER(State)(&machineState);
303
304 /* Terminate any event wait operation if the machine has been
305 * PoweredDown/Saved/Aborted. */
306 if (machineState < MachineState_Running)
307 {
308 g_fTerminateFE = true;
309 gEventQ->interruptEventQueueProcessing();
310 }
311
312 break;
313 }
314 case VBoxEventType_OnRemoteDisplayInfoChanged:
315 {
316 ComPtr<IRemoteDisplayInfoChangedEvent> rdicev = aEvent;
317 Assert(rdicev);
318
319#ifdef VBOX_WITH_VRDP
320 if (gConsole)
321 {
322 ComPtr<IRemoteDisplayInfo> info;
323 gConsole->COMGETTER(RemoteDisplayInfo)(info.asOutParam());
324 if (info)
325 {
326 LONG port;
327 info->COMGETTER(Port)(&port);
328 if (port != mLastVRDPPort)
329 {
330 if (port == -1)
331 RTPrintf("VRDP server is inactive.\n");
332 else if (port == 0)
333 RTPrintf("VRDP server failed to start.\n");
334 else
335 RTPrintf("Listening on port %d.\n", port);
336
337 mLastVRDPPort = port;
338 }
339 }
340 }
341#endif
342 break;
343 }
344 case VBoxEventType_OnCanShowWindow:
345 {
346 ComPtr<ICanShowWindowEvent> cswev = aEvent;
347 Assert(cswev);
348 cswev->AddVeto(NULL);
349 break;
350 }
351 case VBoxEventType_OnShowWindow:
352 {
353 ComPtr<IShowWindowEvent> swev = aEvent;
354 Assert(swev);
355 swev->COMSETTER(WinId)(0);
356 break;
357 }
358 default:
359 AssertFailed();
360 }
361 return S_OK;
362 }
363
364private:
365
366#ifndef VBOX_WITH_XPCOM
367 long refcnt;
368#endif
369 long mLastVRDPPort;
370};
371
372#ifdef VBOX_WITH_XPCOM
373NS_DECL_CLASSINFO(VirtualBoxEventListener)
374NS_IMPL_THREADSAFE_ISUPPORTS1_CI(VirtualBoxEventListener, IEventListener)
375NS_DECL_CLASSINFO(ConsoleEventListener)
376NS_IMPL_THREADSAFE_ISUPPORTS1_CI(ConsoleEventListener, IEventListener)
377#endif
378
379#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
380static void SaveState(int sig)
381{
382 ComPtr <IProgress> progress = NULL;
383
384/** @todo Deal with nested signals, multithreaded signal dispatching (esp. on windows),
385 * and multiple signals (both SIGINT and SIGTERM in some order).
386 * Consider processing the signal request asynchronously since there are lots of things
387 * which aren't safe (like RTPrintf and printf IIRC) in a signal context. */
388
389 RTPrintf("Signal received, saving state.\n");
390
391 HRESULT rc = gConsole->SaveState(progress.asOutParam());
392 if (FAILED(S_OK))
393 {
394 RTPrintf("Error saving state! rc = 0x%x\n", rc);
395 return;
396 }
397 Assert(progress);
398 LONG cPercent = 0;
399
400 RTPrintf("0%%");
401 RTStrmFlush(g_pStdOut);
402 for (;;)
403 {
404 BOOL fCompleted = false;
405 rc = progress->COMGETTER(Completed)(&fCompleted);
406 if (FAILED(rc) || fCompleted)
407 break;
408 ULONG cPercentNow;
409 rc = progress->COMGETTER(Percent)(&cPercentNow);
410 if (FAILED(rc))
411 break;
412 if ((cPercentNow / 10) != (cPercent / 10))
413 {
414 cPercent = cPercentNow;
415 RTPrintf("...%d%%", cPercentNow);
416 RTStrmFlush(g_pStdOut);
417 }
418
419 /* wait */
420 rc = progress->WaitForCompletion(100);
421 }
422
423 HRESULT lrc;
424 rc = progress->COMGETTER(ResultCode)(&lrc);
425 if (FAILED(rc))
426 lrc = ~0;
427 if (!lrc)
428 {
429 RTPrintf(" -- Saved the state successfully.\n");
430 RTThreadYield();
431 }
432 else
433 RTPrintf("-- Error saving state, lrc=%d (%#x)\n", lrc, lrc);
434
435}
436#endif /* VBOX_WITH_SAVESTATE_ON_SIGNAL */
437
438////////////////////////////////////////////////////////////////////////////////
439
440static void show_usage()
441{
442 RTPrintf("Usage:\n"
443 " -s, -startvm, --startvm <name|uuid> Start given VM (required argument)\n"
444#ifdef VBOX_WITH_VNC
445 " -n, --vnc Enable the built in VNC server\n"
446 " -m, --vncport <port> TCP port number to use for the VNC server\n"
447 " -o, --vncpass <pw> Set the VNC server password\n"
448#endif
449#ifdef VBOX_WITH_VRDP
450 " -v, -vrdp, --vrdp on|off|config Enable (default) or disable the VRDP\n"
451 " server or don't change the setting\n"
452 " -p, -vrdpport, --vrdpport <ports> Comma-separated list of ports the VRDP\n"
453 " server can bind to. Use a dash between\n"
454 " two port numbers to specify a range\n"
455 " -a, -vrdpaddress, --vrdpaddress <ip> Interface IP the VRDP will bind to \n"
456#endif
457#ifdef VBOX_FFMPEG
458 " -c, -capture, --capture Record the VM screen output to a file\n"
459 " -w, --width Frame width when recording\n"
460 " -h, --height Frame height when recording\n"
461 " -r, --bitrate Recording bit rate when recording\n"
462 " -f, --filename File name when recording. The codec\n"
463 " used will be chosen based on the\n"
464 " file extension\n"
465#endif
466 "\n");
467}
468
469#ifdef VBOX_FFMPEG
470/**
471 * Parse the environment for variables which can influence the FFMPEG settings.
472 * purely for backwards compatibility.
473 * @param pulFrameWidth may be updated with a desired frame width
474 * @param pulFrameHeight may be updated with a desired frame height
475 * @param pulBitRate may be updated with a desired bit rate
476 * @param ppszFileName may be updated with a desired file name
477 */
478static void parse_environ(unsigned long *pulFrameWidth, unsigned long *pulFrameHeight,
479 unsigned long *pulBitRate, const char **ppszFileName)
480{
481 const char *pszEnvTemp;
482
483 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREWIDTH")) != 0)
484 {
485 errno = 0;
486 unsigned long ulFrameWidth = strtoul(pszEnvTemp, 0, 10);
487 if (errno != 0)
488 LogError("VBoxHeadless: ERROR: invalid VBOX_CAPTUREWIDTH environment variable", 0);
489 else
490 *pulFrameWidth = ulFrameWidth;
491 }
492 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREHEIGHT")) != 0)
493 {
494 errno = 0;
495 unsigned long ulFrameHeight = strtoul(pszEnvTemp, 0, 10);
496 if (errno != 0)
497 LogError("VBoxHeadless: ERROR: invalid VBOX_CAPTUREHEIGHT environment variable", 0);
498 else
499 *pulFrameHeight = ulFrameHeight;
500 }
501 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREBITRATE")) != 0)
502 {
503 errno = 0;
504 unsigned long ulBitRate = strtoul(pszEnvTemp, 0, 10);
505 if (errno != 0)
506 LogError("VBoxHeadless: ERROR: invalid VBOX_CAPTUREBITRATE environment variable", 0);
507 else
508 *pulBitRate = ulBitRate;
509 }
510 if ((pszEnvTemp = RTEnvGet("VBOX_CAPTUREFILE")) != 0)
511 *ppszFileName = pszEnvTemp;
512}
513#endif /* VBOX_FFMPEG defined */
514
515/**
516 * Entry point.
517 */
518extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
519{
520#ifdef VBOX_WITH_VRDP
521 const char *vrdpPort = NULL;
522 const char *vrdpAddress = NULL;
523 const char *vrdpEnabled = NULL;
524#endif
525#ifdef VBOX_WITH_VNC
526 bool fVNCEnable = false;
527 unsigned uVNCPort = 0; /* default port */
528 char const *pszVNCPassword = NULL; /* no password */
529#endif
530 unsigned fRawR0 = ~0U;
531 unsigned fRawR3 = ~0U;
532 unsigned fPATM = ~0U;
533 unsigned fCSAM = ~0U;
534#ifdef VBOX_FFMPEG
535 unsigned fFFMPEG = 0;
536 unsigned long ulFrameWidth = 800;
537 unsigned long ulFrameHeight = 600;
538 unsigned long ulBitRate = 300000;
539 char pszMPEGFile[RTPATH_MAX];
540 const char *pszFileNameParam = "VBox-%d.vob";
541#endif /* VBOX_FFMPEG */
542
543 /* Make sure that DISPLAY is unset, so that X11 bits do not get initialised
544 * on X11-using OSes. */
545 /** @todo this should really be taken care of in Main. */
546 RTEnvUnset("DISPLAY");
547
548 LogFlow (("VBoxHeadless STARTED.\n"));
549 RTPrintf (VBOX_PRODUCT " Headless Interface " VBOX_VERSION_STRING "\n"
550 "(C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
551 "All rights reserved.\n\n");
552
553 Bstr id;
554 /* the below cannot be Bstr because on Linux Bstr doesn't work until XPCOM (nsMemory) is initialized */
555 const char *name = NULL;
556
557#ifdef VBOX_FFMPEG
558 /* Parse the environment */
559 parse_environ(&ulFrameWidth, &ulFrameHeight, &ulBitRate, &pszFileNameParam);
560#endif
561
562 enum eHeadlessOptions
563 {
564 OPT_RAW_R0 = 0x100,
565 OPT_NO_RAW_R0,
566 OPT_RAW_R3,
567 OPT_NO_RAW_R3,
568 OPT_PATM,
569 OPT_NO_PATM,
570 OPT_CSAM,
571 OPT_NO_CSAM,
572 OPT_COMMENT
573 };
574
575 static const RTGETOPTDEF s_aOptions[] =
576 {
577 { "-startvm", 's', RTGETOPT_REQ_STRING },
578 { "--startvm", 's', RTGETOPT_REQ_STRING },
579#ifdef VBOX_WITH_VRDP
580 { "-vrdpport", 'p', RTGETOPT_REQ_STRING },
581 { "--vrdpport", 'p', RTGETOPT_REQ_STRING },
582 { "-vrdpaddress", 'a', RTGETOPT_REQ_STRING },
583 { "--vrdpaddress", 'a', RTGETOPT_REQ_STRING },
584 { "-vrdp", 'v', RTGETOPT_REQ_STRING },
585 { "--vrdp", 'v', RTGETOPT_REQ_STRING },
586#endif /* VBOX_WITH_VRDP defined */
587#ifdef VBOX_WITH_VNC
588 { "--vncport", 'm', RTGETOPT_REQ_INT32 },
589 { "--vncpass", 'o', RTGETOPT_REQ_STRING },
590 { "--vnc", 'n', 0 },
591#endif /* VBOX_WITH_VNC */
592 { "-rawr0", OPT_RAW_R0, 0 },
593 { "--rawr0", OPT_RAW_R0, 0 },
594 { "-norawr0", OPT_NO_RAW_R0, 0 },
595 { "--norawr0", OPT_NO_RAW_R0, 0 },
596 { "-rawr3", OPT_RAW_R3, 0 },
597 { "--rawr3", OPT_RAW_R3, 0 },
598 { "-norawr3", OPT_NO_RAW_R3, 0 },
599 { "--norawr3", OPT_NO_RAW_R3, 0 },
600 { "-patm", OPT_PATM, 0 },
601 { "--patm", OPT_PATM, 0 },
602 { "-nopatm", OPT_NO_PATM, 0 },
603 { "--nopatm", OPT_NO_PATM, 0 },
604 { "-csam", OPT_CSAM, 0 },
605 { "--csam", OPT_CSAM, 0 },
606 { "-nocsam", OPT_NO_CSAM, 0 },
607 { "--nocsam", OPT_NO_CSAM, 0 },
608#ifdef VBOX_FFMPEG
609 { "-capture", 'c', 0 },
610 { "--capture", 'c', 0 },
611 { "--width", 'w', RTGETOPT_REQ_UINT32 },
612 { "--height", 'h', RTGETOPT_REQ_UINT32 }, /* great choice of short option! */
613 { "--bitrate", 'r', RTGETOPT_REQ_UINT32 },
614 { "--filename", 'f', RTGETOPT_REQ_STRING },
615#endif /* VBOX_FFMPEG defined */
616 { "-comment", OPT_COMMENT, RTGETOPT_REQ_STRING },
617 { "--comment", OPT_COMMENT, RTGETOPT_REQ_STRING }
618 };
619
620 // parse the command line
621 int ch;
622 RTGETOPTUNION ValueUnion;
623 RTGETOPTSTATE GetState;
624 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
625 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
626 {
627 switch(ch)
628 {
629 case 's':
630 id = asGuidStr(ValueUnion.psz);
631 /* If the argument was not a UUID, then it must be a name. */
632 if (id.isEmpty())
633 name = ValueUnion.psz;
634 break;
635#ifdef VBOX_WITH_VRDP
636 case 'p':
637 vrdpPort = ValueUnion.psz;
638 break;
639 case 'a':
640 vrdpAddress = ValueUnion.psz;
641 break;
642 case 'v':
643 vrdpEnabled = ValueUnion.psz;
644 break;
645#endif /* VBOX_WITH_VRDP defined */
646#ifdef VBOX_WITH_VNC
647 case 'n':
648 fVNCEnable = true;
649 break;
650 case 'm':
651 uVNCPort = ValueUnion.i32;
652 break;
653 case 'o':
654 pszVNCPassword = ValueUnion.psz;
655 break;
656#endif /* VBOX_WITH_VNC */
657 case OPT_RAW_R0:
658 fRawR0 = true;
659 break;
660 case OPT_NO_RAW_R0:
661 fRawR0 = false;
662 break;
663 case OPT_RAW_R3:
664 fRawR3 = true;
665 break;
666 case OPT_NO_RAW_R3:
667 fRawR3 = false;
668 break;
669 case OPT_PATM:
670 fPATM = true;
671 break;
672 case OPT_NO_PATM:
673 fPATM = false;
674 break;
675 case OPT_CSAM:
676 fCSAM = true;
677 break;
678 case OPT_NO_CSAM:
679 fCSAM = false;
680 break;
681#ifdef VBOX_FFMPEG
682 case 'c':
683 fFFMPEG = true;
684 break;
685 case 'w':
686 ulFrameWidth = ValueUnion.u32;
687 break;
688 case 'r':
689 ulBitRate = ValueUnion.u32;
690 break;
691 case 'f':
692 pszFileNameParam = ValueUnion.psz;
693 break;
694#endif /* VBOX_FFMPEG defined */
695 case 'h':
696#ifdef VBOX_FFMPEG
697 if ((GetState.pDef->fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
698 {
699 ulFrameHeight = ValueUnion.u32;
700 break;
701 }
702#endif
703 show_usage();
704 return 0;
705 case OPT_COMMENT:
706 /* nothing to do */
707 break;
708 case 'V':
709 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
710 return 0;
711 default:
712 ch = RTGetOptPrintError(ch, &ValueUnion);
713 show_usage();
714 return ch;
715 }
716 }
717
718#ifdef VBOX_FFMPEG
719 if (ulFrameWidth < 512 || ulFrameWidth > 2048 || ulFrameWidth % 2)
720 {
721 LogError("VBoxHeadless: ERROR: please specify an even frame width between 512 and 2048", 0);
722 return 1;
723 }
724 if (ulFrameHeight < 384 || ulFrameHeight > 1536 || ulFrameHeight % 2)
725 {
726 LogError("VBoxHeadless: ERROR: please specify an even frame height between 384 and 1536", 0);
727 return 1;
728 }
729 if (ulBitRate < 300000 || ulBitRate > 1000000)
730 {
731 LogError("VBoxHeadless: ERROR: please specify an even bitrate between 300000 and 1000000", 0);
732 return 1;
733 }
734 /* Make sure we only have %d or %u (or none) in the file name specified */
735 char *pcPercent = (char*)strchr(pszFileNameParam, '%');
736 if (pcPercent != 0 && *(pcPercent + 1) != 'd' && *(pcPercent + 1) != 'u')
737 {
738 LogError("VBoxHeadless: ERROR: Only %%d and %%u are allowed in the capture file name.", -1);
739 return 1;
740 }
741 /* And no more than one % in the name */
742 if (pcPercent != 0 && strchr(pcPercent + 1, '%') != 0)
743 {
744 LogError("VBoxHeadless: ERROR: Only one format modifier is allowed in the capture file name.", -1);
745 return 1;
746 }
747 RTStrPrintf(&pszMPEGFile[0], RTPATH_MAX, pszFileNameParam, RTProcSelf());
748#endif /* defined VBOX_FFMPEG */
749
750 if (id.isEmpty() && !name)
751 {
752 show_usage();
753 return 1;
754 }
755
756 HRESULT rc;
757
758 rc = com::Initialize();
759 if (FAILED(rc))
760 {
761 RTPrintf("VBoxHeadless: ERROR: failed to initialize COM!\n");
762 return 1;
763 }
764
765 ComPtr<IVirtualBox> virtualBox;
766 ComPtr<ISession> session;
767 bool fSessionOpened = false;
768 IEventListener *vboxListener = NULL, *consoleListener = NULL;
769
770 do
771 {
772 rc = virtualBox.createLocalObject(CLSID_VirtualBox);
773 if (FAILED(rc))
774 RTPrintf("VBoxHeadless: ERROR: failed to create the VirtualBox object!\n");
775 else
776 {
777 rc = session.createInprocObject(CLSID_Session);
778 if (FAILED(rc))
779 RTPrintf("VBoxHeadless: ERROR: failed to create a session object!\n");
780 }
781
782 if (FAILED(rc))
783 {
784 com::ErrorInfo info;
785 if (!info.isFullAvailable() && !info.isBasicAvailable())
786 {
787 com::GluePrintRCMessage(rc);
788 RTPrintf("Most likely, the VirtualBox COM server is not running or failed to start.\n");
789 }
790 else
791 GluePrintErrorInfo(info);
792 break;
793 }
794
795 ComPtr<IMachine> m;
796
797 /* find ID by name */
798 if (id.isEmpty())
799 {
800 rc = virtualBox->FindMachine(Bstr(name), m.asOutParam());
801 if (FAILED(rc))
802 {
803 LogError("Invalid machine name!\n", rc);
804 break;
805 }
806 m->COMGETTER(Id)(id.asOutParam());
807 AssertComRC(rc);
808 if (FAILED(rc))
809 break;
810 }
811 else
812 {
813 /* Use the GUID. */
814 rc = virtualBox->GetMachine(id, m.asOutParam());
815 if (FAILED(rc))
816 {
817 LogError("Invalid machine uid!\n", rc);
818 break;
819 }
820 }
821
822 Log(("VBoxHeadless: Opening a session with machine (id={%s})...\n",
823 Utf8Str(id).c_str()));
824
825 // open a session
826 CHECK_ERROR_BREAK(m, LockMachine(session, LockType_Write));
827 fSessionOpened = true;
828
829 /* get the console */
830 ComPtr<IConsole> console;
831 CHECK_ERROR_BREAK(session, COMGETTER(Console)(console.asOutParam()));
832
833 /* get the mutable machine */
834 ComPtr<IMachine> machine;
835 CHECK_ERROR_BREAK(console, COMGETTER(Machine)(machine.asOutParam()));
836
837 ComPtr<IDisplay> display;
838 CHECK_ERROR_BREAK(console, COMGETTER(Display)(display.asOutParam()));
839
840#ifdef VBOX_FFMPEG
841 IFramebuffer *pFramebuffer = 0;
842 RTLDRMOD hLdrFFmpegFB;
843 PFNREGISTERFFMPEGFB pfnRegisterFFmpegFB;
844
845 if (fFFMPEG)
846 {
847 int rrc = VINF_SUCCESS, rcc = S_OK;
848
849 Log2(("VBoxHeadless: loading VBoxFFmpegFB shared library\n"));
850 rrc = SUPR3HardenedLdrLoadAppPriv("VBoxFFmpegFB", &hLdrFFmpegFB);
851
852 if (RT_SUCCESS(rrc))
853 {
854 Log2(("VBoxHeadless: looking up symbol VBoxRegisterFFmpegFB\n"));
855 rrc = RTLdrGetSymbol(hLdrFFmpegFB, "VBoxRegisterFFmpegFB",
856 reinterpret_cast<void **>(&pfnRegisterFFmpegFB));
857 if (RT_FAILURE(rrc))
858 LogError("Failed to load the video capture extension, possibly due to a damaged file\n", rrc);
859 }
860 else
861 LogError("Failed to load the video capture extension\n", rrc);
862 if (RT_SUCCESS(rrc))
863 {
864 Log2(("VBoxHeadless: calling pfnRegisterFFmpegFB\n"));
865 rcc = pfnRegisterFFmpegFB(ulFrameWidth, ulFrameHeight, ulBitRate,
866 pszMPEGFile, &pFramebuffer);
867 if (rcc != S_OK)
868 LogError("Failed to initialise video capturing - make sure that the file format\n"
869 "you wish to use is supported on your system\n", rcc);
870 }
871 if (RT_SUCCESS(rrc) && (rcc == S_OK))
872 {
873 Log2(("VBoxHeadless: Registering framebuffer\n"));
874 pFramebuffer->AddRef();
875 display->SetFramebuffer(VBOX_VIDEO_PRIMARY_SCREEN, pFramebuffer);
876 }
877 if (!RT_SUCCESS(rrc) || (rcc != S_OK))
878 rc = E_FAIL;
879 }
880 if (rc != S_OK)
881 {
882 break;
883 }
884#endif /* defined(VBOX_FFMPEG) */
885#ifdef VBOX_WITH_VNC
886 if (fVNCEnable)
887 {
888 Bstr name;
889 machine->COMGETTER(Name)(name.asOutParam());
890 g_pFramebufferVNC = new VNCFB(console, uVNCPort, pszVNCPassword);
891 rc = g_pFramebufferVNC->init(name ? Utf8Str(name).raw() : "");
892 if (rc != S_OK)
893 {
894 LogError("Failed to load the vnc server extension, possibly due to a damaged file\n", rc);
895 delete g_pFramebufferVNC;
896 break;
897 }
898
899 Log2(("VBoxHeadless: Registering VNC framebuffer\n"));
900 g_pFramebufferVNC->AddRef();
901 display->SetFramebuffer(VBOX_VIDEO_PRIMARY_SCREEN, g_pFramebufferVNC);
902 }
903 if (rc != S_OK)
904 break;
905#endif
906 ULONG cMonitors = 1;
907 machine->COMGETTER(MonitorCount)(&cMonitors);
908
909#ifdef VBOX_WITH_VRDP
910 unsigned uScreenId;
911 for (uScreenId = 0; uScreenId < cMonitors; uScreenId++)
912 {
913# ifdef VBOX_FFMPEG
914 if (fFFMPEG && uScreenId == 0)
915 {
916 /* Already registered. */
917 continue;
918 }
919# endif
920# ifdef VBOX_WITH_VNC
921 if (fVNCEnable && uScreenId == 0)
922 {
923 /* Already registered. */
924 continue;
925 }
926# endif
927 VRDPFramebuffer *pVRDPFramebuffer = new VRDPFramebuffer();
928 if (!pVRDPFramebuffer)
929 {
930 RTPrintf("Error: could not create framebuffer object %d\n", uScreenId);
931 break;
932 }
933 pVRDPFramebuffer->AddRef();
934 display->SetFramebuffer(uScreenId, pVRDPFramebuffer);
935 }
936 if (uScreenId < cMonitors)
937 {
938 break;
939 }
940#endif
941
942 /* get the machine debugger (isn't necessarily available) */
943 ComPtr <IMachineDebugger> machineDebugger;
944 console->COMGETTER(Debugger)(machineDebugger.asOutParam());
945 if (machineDebugger)
946 {
947 Log(("Machine debugger available!\n"));
948 }
949
950 if (fRawR0 != ~0U)
951 {
952 if (!machineDebugger)
953 {
954 RTPrintf("Error: No debugger object; -%srawr0 cannot be executed!\n", fRawR0 ? "" : "no");
955 break;
956 }
957 machineDebugger->COMSETTER(RecompileSupervisor)(!fRawR0);
958 }
959 if (fRawR3 != ~0U)
960 {
961 if (!machineDebugger)
962 {
963 RTPrintf("Error: No debugger object; -%srawr3 cannot be executed!\n", fRawR0 ? "" : "no");
964 break;
965 }
966 machineDebugger->COMSETTER(RecompileUser)(!fRawR3);
967 }
968 if (fPATM != ~0U)
969 {
970 if (!machineDebugger)
971 {
972 RTPrintf("Error: No debugger object; -%spatm cannot be executed!\n", fRawR0 ? "" : "no");
973 break;
974 }
975 machineDebugger->COMSETTER(PATMEnabled)(fPATM);
976 }
977 if (fCSAM != ~0U)
978 {
979 if (!machineDebugger)
980 {
981 RTPrintf("Error: No debugger object; -%scsam cannot be executed!\n", fRawR0 ? "" : "no");
982 break;
983 }
984 machineDebugger->COMSETTER(CSAMEnabled)(fCSAM);
985 }
986
987 /* initialize global references */
988 gSession = session;
989 gConsole = console;
990 gEventQ = com::EventQueue::getMainEventQueue();
991
992 /* Console events registration. */
993 {
994 ComPtr<IEventSource> es;
995 CHECK_ERROR(console, COMGETTER(EventSource)(es.asOutParam()));
996 consoleListener = new ConsoleEventListener();
997 consoleListener->AddRef();
998 com::SafeArray <VBoxEventType_T> eventTypes;
999 eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
1000 eventTypes.push_back(VBoxEventType_OnStateChanged);
1001 eventTypes.push_back(VBoxEventType_OnRemoteDisplayInfoChanged);
1002 eventTypes.push_back(VBoxEventType_OnCanShowWindow);
1003 eventTypes.push_back(VBoxEventType_OnShowWindow);
1004 CHECK_ERROR(es, RegisterListener(consoleListener, ComSafeArrayAsInParam(eventTypes), true));
1005 }
1006
1007#ifdef VBOX_WITH_VRDP
1008 /* default is to enable the RDP server (backward compatibility) */
1009 BOOL fVRDPEnable = true;
1010 BOOL fVRDPEnabled;
1011 ComPtr <IVRDPServer> vrdpServer;
1012 CHECK_ERROR_BREAK(machine, COMGETTER(VRDPServer)(vrdpServer.asOutParam()));
1013 CHECK_ERROR_BREAK(vrdpServer, COMGETTER(Enabled)(&fVRDPEnabled));
1014
1015 if (vrdpEnabled != NULL)
1016 {
1017 /* -vrdp on|off|config */
1018 if (!strcmp(vrdpEnabled, "off") || !strcmp(vrdpEnabled, "disable"))
1019 fVRDPEnable = false;
1020 else if (!strcmp(vrdpEnabled, "config"))
1021 {
1022 if (!fVRDPEnabled)
1023 fVRDPEnable = false;
1024 }
1025 else if (strcmp(vrdpEnabled, "on") && strcmp(vrdpEnabled, "enable"))
1026 {
1027 RTPrintf("-vrdp requires an argument (on|off|config)\n");
1028 break;
1029 }
1030 }
1031
1032 if (fVRDPEnable)
1033 {
1034 Log(("VBoxHeadless: Enabling VRDP server...\n"));
1035
1036 /* set VRDP port if requested by the user */
1037 if (vrdpPort != NULL)
1038 {
1039 Bstr bstr = vrdpPort;
1040 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Ports)(bstr));
1041 }
1042 /* set VRDP address if requested by the user */
1043 if (vrdpAddress != NULL)
1044 {
1045 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(NetAddress)(Bstr(vrdpAddress)));
1046 }
1047 /* enable VRDP server (only if currently disabled) */
1048 if (!fVRDPEnabled)
1049 {
1050 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Enabled)(TRUE));
1051 }
1052 }
1053 else
1054 {
1055 /* disable VRDP server (only if currently enabled */
1056 if (fVRDPEnabled)
1057 {
1058 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Enabled)(FALSE));
1059 }
1060 }
1061#endif
1062 Log(("VBoxHeadless: Powering up the machine...\n"));
1063
1064 ComPtr <IProgress> progress;
1065 CHECK_ERROR_BREAK(console, PowerUp(progress.asOutParam()));
1066
1067 /*
1068 * Wait for the result because there can be errors.
1069 *
1070 * It's vital to process events while waiting (teleportation deadlocks),
1071 * so we'll poll for the completion instead of waiting on it.
1072 */
1073 for (;;)
1074 {
1075 BOOL fCompleted;
1076 rc = progress->COMGETTER(Completed)(&fCompleted);
1077 if (FAILED(rc) || fCompleted)
1078 break;
1079
1080 /* Process pending events, then wait for new ones. Note, this
1081 * processes NULL events signalling event loop termination. */
1082 gEventQ->processEventQueue(0);
1083 if (!g_fTerminateFE)
1084 gEventQ->processEventQueue(500);
1085 }
1086
1087 if (SUCCEEDED(progress->WaitForCompletion(-1)))
1088 {
1089 /* Figure out if the operation completed with a failed status
1090 * and print the error message. Terminate immediately, and let
1091 * the cleanup code take care of potentially pending events. */
1092 LONG progressRc;
1093 progress->COMGETTER(ResultCode)(&progressRc);
1094 rc = progressRc;
1095 if (FAILED(rc))
1096 {
1097 com::ProgressErrorInfo info(progress);
1098 if (info.isBasicAvailable())
1099 {
1100 RTPrintf("Error: failed to start machine. Error message: %lS\n", info.getText().raw());
1101 }
1102 else
1103 {
1104 RTPrintf("Error: failed to start machine. No error message available!\n");
1105 }
1106 break;
1107 }
1108 }
1109
1110 /* VirtualBox events registration. */
1111 {
1112 ComPtr<IEventSource> es;
1113 CHECK_ERROR(virtualBox, COMGETTER(EventSource)(es.asOutParam()));
1114 vboxListener = new VirtualBoxEventListener();
1115 vboxListener->AddRef();
1116 com::SafeArray <VBoxEventType_T> eventTypes;
1117 eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
1118 CHECK_ERROR(es, RegisterListener(vboxListener, ComSafeArrayAsInParam(eventTypes), true));
1119 }
1120
1121#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
1122 signal(SIGINT, SaveState);
1123 signal(SIGTERM, SaveState);
1124#endif
1125
1126 Log(("VBoxHeadless: Waiting for PowerDown...\n"));
1127
1128 while ( !g_fTerminateFE
1129 && RT_SUCCESS(gEventQ->processEventQueue(RT_INDEFINITE_WAIT)))
1130 /* nothing */ ;
1131
1132 Log(("VBoxHeadless: event loop has terminated...\n"));
1133
1134#ifdef VBOX_FFMPEG
1135 if (pFramebuffer)
1136 {
1137 pFramebuffer->Release();
1138 Log(("Released framebuffer\n"));
1139 pFramebuffer = NULL;
1140 }
1141#endif /* defined(VBOX_FFMPEG) */
1142
1143 /* we don't have to disable VRDP here because we don't save the settings of the VM */
1144 }
1145 while (0);
1146
1147 /* VirtualBox callback unregistration. */
1148 if (vboxListener)
1149 {
1150 ComPtr<IEventSource> es;
1151 CHECK_ERROR(virtualBox, COMGETTER(EventSource)(es.asOutParam()));
1152 CHECK_ERROR(es, UnregisterListener(vboxListener));
1153 vboxListener->Release();
1154 }
1155
1156 /* Console callback unregistration. */
1157 if (consoleListener)
1158 {
1159 ComPtr<IEventSource> es;
1160 CHECK_ERROR(gConsole, COMGETTER(EventSource)(es.asOutParam()));
1161 CHECK_ERROR(es, UnregisterListener(consoleListener));
1162 consoleListener->Release();
1163 }
1164
1165 /* No more access to the 'console' object, which will be uninitialized by the next session->Close call. */
1166 gConsole = NULL;
1167
1168 if (fSessionOpened)
1169 {
1170 /*
1171 * Close the session. This will also uninitialize the console and
1172 * unregister the callback we've registered before.
1173 */
1174 Log(("VBoxHeadless: Closing the session...\n"));
1175 session->UnlockMachine();
1176 }
1177
1178 /* Must be before com::Shutdown */
1179 session.setNull();
1180 virtualBox.setNull();
1181
1182 com::Shutdown();
1183
1184 LogFlow(("VBoxHeadless FINISHED.\n"));
1185
1186 return FAILED(rc) ? 1 : 0;
1187}
1188
1189
1190#ifndef VBOX_WITH_HARDENING
1191/**
1192 * Main entry point.
1193 */
1194int main(int argc, char **argv, char **envp)
1195{
1196 // initialize VBox Runtime
1197 int rc = RTR3InitAndSUPLib();
1198 if (RT_FAILURE(rc))
1199 {
1200 RTPrintf("VBoxHeadless: Runtime Error:\n"
1201 " %Rrc -- %Rrf\n", rc, rc);
1202 switch (rc)
1203 {
1204 case VERR_VM_DRIVER_NOT_INSTALLED:
1205 RTPrintf("Cannot access the kernel driver. Make sure the kernel module has been \n"
1206 "loaded successfully. Aborting ...\n");
1207 break;
1208 default:
1209 break;
1210 }
1211 return 1;
1212 }
1213
1214 return TrustedMain(argc, argv, envp);
1215}
1216#endif /* !VBOX_WITH_HARDENING */
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