VirtualBox

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

Last change on this file since 32183 was 31974, checked in by vboxsync, 14 years ago

VBoxHeadless: OSE build fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.5 KB
Line 
1/* $Id: VBoxHeadless.cpp 31974 2010-08-26 08:21:59Z 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"), 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 machineName;
889 machine->COMGETTER(Name)(machineName.asOutParam());
890 g_pFramebufferVNC = new VNCFB(console, uVNCPort, pszVNCPassword);
891 rc = g_pFramebufferVNC->init(machineName ? Utf8Str(machineName).c_str() : "");
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 unsigned uScreenId;
910#ifdef VBOX_WITH_VRDP
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 // fill in remaining slots with null framebuffers
942 for (uScreenId = 0; uScreenId < cMonitors; uScreenId++)
943 {
944 ComPtr<IFramebuffer> fb;
945 LONG xOrigin, yOrigin;
946 HRESULT hrc2 = display->GetFramebuffer(uScreenId,
947 fb.asOutParam(),
948 &xOrigin, &yOrigin);
949 if (hrc2 == S_OK && fb.isNull())
950 {
951 NullFB *pNullFB = new NullFB();
952 pNullFB->AddRef();
953 pNullFB->init();
954 display->SetFramebuffer(uScreenId, pNullFB);
955 }
956 }
957
958 /* get the machine debugger (isn't necessarily available) */
959 ComPtr <IMachineDebugger> machineDebugger;
960 console->COMGETTER(Debugger)(machineDebugger.asOutParam());
961 if (machineDebugger)
962 {
963 Log(("Machine debugger available!\n"));
964 }
965
966 if (fRawR0 != ~0U)
967 {
968 if (!machineDebugger)
969 {
970 RTPrintf("Error: No debugger object; -%srawr0 cannot be executed!\n", fRawR0 ? "" : "no");
971 break;
972 }
973 machineDebugger->COMSETTER(RecompileSupervisor)(!fRawR0);
974 }
975 if (fRawR3 != ~0U)
976 {
977 if (!machineDebugger)
978 {
979 RTPrintf("Error: No debugger object; -%srawr3 cannot be executed!\n", fRawR0 ? "" : "no");
980 break;
981 }
982 machineDebugger->COMSETTER(RecompileUser)(!fRawR3);
983 }
984 if (fPATM != ~0U)
985 {
986 if (!machineDebugger)
987 {
988 RTPrintf("Error: No debugger object; -%spatm cannot be executed!\n", fRawR0 ? "" : "no");
989 break;
990 }
991 machineDebugger->COMSETTER(PATMEnabled)(fPATM);
992 }
993 if (fCSAM != ~0U)
994 {
995 if (!machineDebugger)
996 {
997 RTPrintf("Error: No debugger object; -%scsam cannot be executed!\n", fRawR0 ? "" : "no");
998 break;
999 }
1000 machineDebugger->COMSETTER(CSAMEnabled)(fCSAM);
1001 }
1002
1003 /* initialize global references */
1004 gSession = session;
1005 gConsole = console;
1006 gEventQ = com::EventQueue::getMainEventQueue();
1007
1008 /* Console events registration. */
1009 {
1010 ComPtr<IEventSource> es;
1011 CHECK_ERROR(console, COMGETTER(EventSource)(es.asOutParam()));
1012 consoleListener = new ConsoleEventListener();
1013 consoleListener->AddRef();
1014 com::SafeArray <VBoxEventType_T> eventTypes;
1015 eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
1016 eventTypes.push_back(VBoxEventType_OnStateChanged);
1017 eventTypes.push_back(VBoxEventType_OnRemoteDisplayInfoChanged);
1018 eventTypes.push_back(VBoxEventType_OnCanShowWindow);
1019 eventTypes.push_back(VBoxEventType_OnShowWindow);
1020 CHECK_ERROR(es, RegisterListener(consoleListener, ComSafeArrayAsInParam(eventTypes), true));
1021 }
1022
1023#ifdef VBOX_WITH_VRDP
1024 /* default is to enable the RDP server (backward compatibility) */
1025 BOOL fVRDPEnable = true;
1026 BOOL fVRDPEnabled;
1027 ComPtr <IVRDPServer> vrdpServer;
1028 CHECK_ERROR_BREAK(machine, COMGETTER(VRDPServer)(vrdpServer.asOutParam()));
1029 CHECK_ERROR_BREAK(vrdpServer, COMGETTER(Enabled)(&fVRDPEnabled));
1030
1031 if (vrdpEnabled != NULL)
1032 {
1033 /* -vrdp on|off|config */
1034 if (!strcmp(vrdpEnabled, "off") || !strcmp(vrdpEnabled, "disable"))
1035 fVRDPEnable = false;
1036 else if (!strcmp(vrdpEnabled, "config"))
1037 {
1038 if (!fVRDPEnabled)
1039 fVRDPEnable = false;
1040 }
1041 else if (strcmp(vrdpEnabled, "on") && strcmp(vrdpEnabled, "enable"))
1042 {
1043 RTPrintf("-vrdp requires an argument (on|off|config)\n");
1044 break;
1045 }
1046 }
1047
1048 if (fVRDPEnable)
1049 {
1050 Log(("VBoxHeadless: Enabling VRDP server...\n"));
1051
1052 /* set VRDP port if requested by the user */
1053 if (vrdpPort != NULL)
1054 {
1055 Bstr bstr = vrdpPort;
1056 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Ports)(bstr));
1057 }
1058 /* set VRDP address if requested by the user */
1059 if (vrdpAddress != NULL)
1060 {
1061 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(NetAddress)(Bstr(vrdpAddress)));
1062 }
1063 /* enable VRDP server (only if currently disabled) */
1064 if (!fVRDPEnabled)
1065 {
1066 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Enabled)(TRUE));
1067 }
1068 }
1069 else
1070 {
1071 /* disable VRDP server (only if currently enabled */
1072 if (fVRDPEnabled)
1073 {
1074 CHECK_ERROR_BREAK(vrdpServer, COMSETTER(Enabled)(FALSE));
1075 }
1076 }
1077#endif
1078 Log(("VBoxHeadless: Powering up the machine...\n"));
1079
1080 ComPtr <IProgress> progress;
1081 CHECK_ERROR_BREAK(console, PowerUp(progress.asOutParam()));
1082
1083 /*
1084 * Wait for the result because there can be errors.
1085 *
1086 * It's vital to process events while waiting (teleportation deadlocks),
1087 * so we'll poll for the completion instead of waiting on it.
1088 */
1089 for (;;)
1090 {
1091 BOOL fCompleted;
1092 rc = progress->COMGETTER(Completed)(&fCompleted);
1093 if (FAILED(rc) || fCompleted)
1094 break;
1095
1096 /* Process pending events, then wait for new ones. Note, this
1097 * processes NULL events signalling event loop termination. */
1098 gEventQ->processEventQueue(0);
1099 if (!g_fTerminateFE)
1100 gEventQ->processEventQueue(500);
1101 }
1102
1103 if (SUCCEEDED(progress->WaitForCompletion(-1)))
1104 {
1105 /* Figure out if the operation completed with a failed status
1106 * and print the error message. Terminate immediately, and let
1107 * the cleanup code take care of potentially pending events. */
1108 LONG progressRc;
1109 progress->COMGETTER(ResultCode)(&progressRc);
1110 rc = progressRc;
1111 if (FAILED(rc))
1112 {
1113 com::ProgressErrorInfo info(progress);
1114 if (info.isBasicAvailable())
1115 {
1116 RTPrintf("Error: failed to start machine. Error message: %lS\n", info.getText().raw());
1117 }
1118 else
1119 {
1120 RTPrintf("Error: failed to start machine. No error message available!\n");
1121 }
1122 break;
1123 }
1124 }
1125
1126 /* VirtualBox events registration. */
1127 {
1128 ComPtr<IEventSource> es;
1129 CHECK_ERROR(virtualBox, COMGETTER(EventSource)(es.asOutParam()));
1130 vboxListener = new VirtualBoxEventListener();
1131 vboxListener->AddRef();
1132 com::SafeArray <VBoxEventType_T> eventTypes;
1133 eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
1134 CHECK_ERROR(es, RegisterListener(vboxListener, ComSafeArrayAsInParam(eventTypes), true));
1135 }
1136
1137#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
1138 signal(SIGINT, SaveState);
1139 signal(SIGTERM, SaveState);
1140#endif
1141
1142 Log(("VBoxHeadless: Waiting for PowerDown...\n"));
1143
1144 while ( !g_fTerminateFE
1145 && RT_SUCCESS(gEventQ->processEventQueue(RT_INDEFINITE_WAIT)))
1146 /* nothing */ ;
1147
1148 Log(("VBoxHeadless: event loop has terminated...\n"));
1149
1150#ifdef VBOX_FFMPEG
1151 if (pFramebuffer)
1152 {
1153 pFramebuffer->Release();
1154 Log(("Released framebuffer\n"));
1155 pFramebuffer = NULL;
1156 }
1157#endif /* defined(VBOX_FFMPEG) */
1158
1159 /* we don't have to disable VRDP here because we don't save the settings of the VM */
1160 }
1161 while (0);
1162
1163 /* VirtualBox callback unregistration. */
1164 if (vboxListener)
1165 {
1166 ComPtr<IEventSource> es;
1167 CHECK_ERROR(virtualBox, COMGETTER(EventSource)(es.asOutParam()));
1168 CHECK_ERROR(es, UnregisterListener(vboxListener));
1169 vboxListener->Release();
1170 }
1171
1172 /* Console callback unregistration. */
1173 if (consoleListener)
1174 {
1175 ComPtr<IEventSource> es;
1176 CHECK_ERROR(gConsole, COMGETTER(EventSource)(es.asOutParam()));
1177 CHECK_ERROR(es, UnregisterListener(consoleListener));
1178 consoleListener->Release();
1179 }
1180
1181 /* No more access to the 'console' object, which will be uninitialized by the next session->Close call. */
1182 gConsole = NULL;
1183
1184 if (fSessionOpened)
1185 {
1186 /*
1187 * Close the session. This will also uninitialize the console and
1188 * unregister the callback we've registered before.
1189 */
1190 Log(("VBoxHeadless: Closing the session...\n"));
1191 session->UnlockMachine();
1192 }
1193
1194 /* Must be before com::Shutdown */
1195 session.setNull();
1196 virtualBox.setNull();
1197
1198 com::Shutdown();
1199
1200 LogFlow(("VBoxHeadless FINISHED.\n"));
1201
1202 return FAILED(rc) ? 1 : 0;
1203}
1204
1205
1206#ifndef VBOX_WITH_HARDENING
1207/**
1208 * Main entry point.
1209 */
1210int main(int argc, char **argv, char **envp)
1211{
1212 // initialize VBox Runtime
1213 int rc = RTR3InitAndSUPLib();
1214 if (RT_FAILURE(rc))
1215 {
1216 RTPrintf("VBoxHeadless: Runtime Error:\n"
1217 " %Rrc -- %Rrf\n", rc, rc);
1218 switch (rc)
1219 {
1220 case VERR_VM_DRIVER_NOT_INSTALLED:
1221 RTPrintf("Cannot access the kernel driver. Make sure the kernel module has been \n"
1222 "loaded successfully. Aborting ...\n");
1223 break;
1224 default:
1225 break;
1226 }
1227 return 1;
1228 }
1229
1230 return TrustedMain(argc, argv, envp);
1231}
1232#endif /* !VBOX_WITH_HARDENING */
1233
1234#ifdef VBOX_WITH_XPCOM
1235NS_DECL_CLASSINFO(NullFB)
1236NS_IMPL_THREADSAFE_ISUPPORTS1_CI(NullFB, IFramebuffer)
1237#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