VirtualBox

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

Last change on this file since 29436 was 28960, checked in by vboxsync, 14 years ago

Frontends: Use VBOX_E_DONT_CALL_AGAIN with IConsoleCallback.

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