VirtualBox

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

Last change on this file since 30620 was 30606, checked in by vboxsync, 15 years ago

Frontends: headless frontend use events now

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