VirtualBox

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

Last change on this file since 33223 was 33004, checked in by vboxsync, 14 years ago

Renamed VBox-VRDP interface to VRDE.

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

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