VirtualBox

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

Last change on this file since 30561 was 30200, checked in by vboxsync, 15 years ago

FE/headless: VNC coding style and minor fixes

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

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