VirtualBox

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

Last change on this file since 32988 was 32738, checked in by vboxsync, 14 years ago

OSE fix

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