VirtualBox

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

Last change on this file since 33995 was 33963, checked in by vboxsync, 14 years ago

Main: moved listeners to location usable by fronends, rework

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