VirtualBox

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

Last change on this file since 34575 was 34122, checked in by vboxsync, 14 years ago

VBoxHeadless: LogRel disconnect on logout.

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