VirtualBox

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

Last change on this file since 92613 was 92613, checked in by vboxsync, 3 years ago

SUP,IPRT,++: Adding SUPSECMAIN_FLAGS_DRIVERLESS_IEM_ALLOWED and SUPR3INIT_F_DRIVERLESS_NEM_FALLBACK to SUPLib and RTR3INIT_FLAGS_TRY_SUPLIB to RTR3Init*, the latter probably reflects the actual state there a lot better. Currently only the TRY_SUPLIB option works, the other two aren't really implemented in SUPLib yet. bugref:10138

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 52.7 KB
Line 
1/* $Id: VBoxHeadless.cpp 92613 2021-11-26 21:53:47Z vboxsync $ */
2/** @file
3 * VBoxHeadless - The VirtualBox Headless frontend for running VMs on servers.
4 */
5
6/*
7 * Copyright (C) 2006-2020 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/NativeEventQueue.h>
25
26#include <VBox/com/VirtualBox.h>
27#include <VBox/com/listeners.h>
28
29using namespace com;
30
31#define LOG_GROUP LOG_GROUP_GUI
32
33#include <VBox/log.h>
34#include <VBox/version.h>
35#include <iprt/buildconfig.h>
36#include <iprt/ctype.h>
37#include <iprt/initterm.h>
38#include <iprt/message.h>
39#include <iprt/semaphore.h>
40#include <iprt/path.h>
41#include <iprt/stream.h>
42#include <iprt/ldr.h>
43#include <iprt/getopt.h>
44#include <iprt/env.h>
45#include <VBox/err.h>
46#include <VBoxVideo.h>
47
48#ifdef VBOX_WITH_RECORDING
49# include <cstdlib>
50# include <cerrno>
51# include <iprt/process.h>
52#endif
53
54#ifdef RT_OS_DARWIN
55# include <iprt/asm.h>
56# include <dlfcn.h>
57# include <sys/mman.h>
58#endif
59
60#if !defined(RT_OS_WINDOWS)
61#include <signal.h>
62static void HandleSignal(int sig);
63#endif
64
65#include "PasswordInput.h"
66
67////////////////////////////////////////////////////////////////////////////////
68
69#define LogError(m,rc) \
70 do { \
71 Log(("VBoxHeadless: ERROR: " m " [rc=0x%08X]\n", rc)); \
72 RTPrintf("%s\n", m); \
73 } while (0)
74
75////////////////////////////////////////////////////////////////////////////////
76
77/* global weak references (for event handlers) */
78static IConsole *gConsole = NULL;
79static NativeEventQueue *gEventQ = NULL;
80
81/* keep this handy for messages */
82static com::Utf8Str g_strVMName;
83static com::Utf8Str g_strVMUUID;
84
85/* flag whether frontend should terminate */
86static volatile bool g_fTerminateFE = false;
87
88////////////////////////////////////////////////////////////////////////////////
89
90/**
91 * Handler for VirtualBoxClient events.
92 */
93class VirtualBoxClientEventListener
94{
95public:
96 VirtualBoxClientEventListener()
97 {
98 }
99
100 virtual ~VirtualBoxClientEventListener()
101 {
102 }
103
104 HRESULT init()
105 {
106 return S_OK;
107 }
108
109 void uninit()
110 {
111 }
112
113 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
114 {
115 switch (aType)
116 {
117 case VBoxEventType_OnVBoxSVCAvailabilityChanged:
118 {
119 ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
120 Assert(pVSACEv);
121 BOOL fAvailable = FALSE;
122 pVSACEv->COMGETTER(Available)(&fAvailable);
123 if (!fAvailable)
124 {
125 LogRel(("VBoxHeadless: VBoxSVC became unavailable, exiting.\n"));
126 RTPrintf("VBoxSVC became unavailable, exiting.\n");
127 /* Terminate the VM as cleanly as possible given that VBoxSVC
128 * is no longer present. */
129 g_fTerminateFE = true;
130 gEventQ->interruptEventQueueProcessing();
131 }
132 break;
133 }
134 default:
135 AssertFailed();
136 }
137
138 return S_OK;
139 }
140
141private:
142};
143
144/**
145 * Handler for machine events.
146 */
147class ConsoleEventListener
148{
149public:
150 ConsoleEventListener() :
151 mLastVRDEPort(-1),
152 m_fIgnorePowerOffEvents(false),
153 m_fNoLoggedInUsers(true)
154 {
155 }
156
157 virtual ~ConsoleEventListener()
158 {
159 }
160
161 HRESULT init()
162 {
163 return S_OK;
164 }
165
166 void uninit()
167 {
168 }
169
170 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
171 {
172 switch (aType)
173 {
174 case VBoxEventType_OnMouseCapabilityChanged:
175 {
176
177 ComPtr<IMouseCapabilityChangedEvent> mccev = aEvent;
178 Assert(!mccev.isNull());
179
180 BOOL fSupportsAbsolute = false;
181 mccev->COMGETTER(SupportsAbsolute)(&fSupportsAbsolute);
182
183 /* Emit absolute mouse event to actually enable the host mouse cursor. */
184 if (fSupportsAbsolute && gConsole)
185 {
186 ComPtr<IMouse> mouse;
187 gConsole->COMGETTER(Mouse)(mouse.asOutParam());
188 if (mouse)
189 {
190 mouse->PutMouseEventAbsolute(-1, -1, 0, 0 /* Horizontal wheel */, 0);
191 }
192 }
193 break;
194 }
195 case VBoxEventType_OnStateChanged:
196 {
197 ComPtr<IStateChangedEvent> scev = aEvent;
198 Assert(scev);
199
200 MachineState_T machineState;
201 scev->COMGETTER(State)(&machineState);
202
203 /* Terminate any event wait operation if the machine has been
204 * PoweredDown/Saved/Aborted. */
205 if (machineState < MachineState_Running && !m_fIgnorePowerOffEvents)
206 {
207 g_fTerminateFE = true;
208 gEventQ->interruptEventQueueProcessing();
209 }
210
211 break;
212 }
213 case VBoxEventType_OnVRDEServerInfoChanged:
214 {
215 ComPtr<IVRDEServerInfoChangedEvent> rdicev = aEvent;
216 Assert(rdicev);
217
218 if (gConsole)
219 {
220 ComPtr<IVRDEServerInfo> info;
221 gConsole->COMGETTER(VRDEServerInfo)(info.asOutParam());
222 if (info)
223 {
224 LONG port;
225 info->COMGETTER(Port)(&port);
226 if (port != mLastVRDEPort)
227 {
228 if (port == -1)
229 RTPrintf("VRDE server is inactive.\n");
230 else if (port == 0)
231 RTPrintf("VRDE server failed to start.\n");
232 else
233 RTPrintf("VRDE server is listening on port %d.\n", port);
234
235 mLastVRDEPort = port;
236 }
237 }
238 }
239 break;
240 }
241 case VBoxEventType_OnCanShowWindow:
242 {
243 ComPtr<ICanShowWindowEvent> cswev = aEvent;
244 Assert(cswev);
245 cswev->AddVeto(NULL);
246 break;
247 }
248 case VBoxEventType_OnShowWindow:
249 {
250 ComPtr<IShowWindowEvent> swev = aEvent;
251 Assert(swev);
252 /* Ignore the event, WinId is either still zero or some other listener assigned it. */
253 NOREF(swev); /* swev->COMSETTER(WinId)(0); */
254 break;
255 }
256 case VBoxEventType_OnGuestPropertyChanged:
257 {
258 ComPtr<IGuestPropertyChangedEvent> pChangedEvent = aEvent;
259 Assert(pChangedEvent);
260
261 HRESULT hrc;
262
263 ComPtr <IMachine> pMachine;
264 if (gConsole)
265 {
266 hrc = gConsole->COMGETTER(Machine)(pMachine.asOutParam());
267 if (FAILED(hrc) || !pMachine)
268 hrc = VBOX_E_OBJECT_NOT_FOUND;
269 }
270 else
271 hrc = VBOX_E_INVALID_VM_STATE;
272
273 if (SUCCEEDED(hrc))
274 {
275 Bstr strKey;
276 hrc = pChangedEvent->COMGETTER(Name)(strKey.asOutParam());
277 AssertComRC(hrc);
278
279 Bstr strValue;
280 hrc = pChangedEvent->COMGETTER(Value)(strValue.asOutParam());
281 AssertComRC(hrc);
282
283 Utf8Str utf8Key = strKey;
284 Utf8Str utf8Value = strValue;
285 LogRelFlow(("Guest property \"%s\" has been changed to \"%s\"\n",
286 utf8Key.c_str(), utf8Value.c_str()));
287
288 if (utf8Key.equals("/VirtualBox/GuestInfo/OS/NoLoggedInUsers"))
289 {
290 LogRelFlow(("Guest indicates that there %s logged in users\n",
291 utf8Value.equals("true") ? "are no" : "are"));
292
293 /* Check if this is our machine and the "disconnect on logout feature" is enabled. */
294 BOOL fProcessDisconnectOnGuestLogout = FALSE;
295
296 /* Does the machine handle VRDP disconnects? */
297 Bstr strDiscon;
298 hrc = pMachine->GetExtraData(Bstr("VRDP/DisconnectOnGuestLogout").raw(),
299 strDiscon.asOutParam());
300 if (SUCCEEDED(hrc))
301 {
302 Utf8Str utf8Discon = strDiscon;
303 fProcessDisconnectOnGuestLogout = utf8Discon.equals("1")
304 ? TRUE : FALSE;
305 }
306
307 LogRelFlow(("VRDE: hrc=%Rhrc: Host %s disconnecting clients (current host state known: %s)\n",
308 hrc, fProcessDisconnectOnGuestLogout ? "will handle" : "does not handle",
309 m_fNoLoggedInUsers ? "No users logged in" : "Users logged in"));
310
311 if (fProcessDisconnectOnGuestLogout)
312 {
313 bool fDropConnection = false;
314 if (!m_fNoLoggedInUsers) /* Only if the property really changes. */
315 {
316 if ( utf8Value == "true"
317 /* Guest property got deleted due to reset,
318 * so it has no value anymore. */
319 || utf8Value.isEmpty())
320 {
321 m_fNoLoggedInUsers = true;
322 fDropConnection = true;
323 }
324 }
325 else if (utf8Value == "false")
326 m_fNoLoggedInUsers = false;
327 /* Guest property got deleted due to reset,
328 * take the shortcut without touching the m_fNoLoggedInUsers
329 * state. */
330 else if (utf8Value.isEmpty())
331 fDropConnection = true;
332
333 LogRelFlow(("VRDE: szNoLoggedInUsers=%s, m_fNoLoggedInUsers=%RTbool, fDropConnection=%RTbool\n",
334 utf8Value.c_str(), m_fNoLoggedInUsers, fDropConnection));
335
336 if (fDropConnection)
337 {
338 /* If there is a connection, drop it. */
339 ComPtr<IVRDEServerInfo> info;
340 hrc = gConsole->COMGETTER(VRDEServerInfo)(info.asOutParam());
341 if (SUCCEEDED(hrc) && info)
342 {
343 ULONG cClients = 0;
344 hrc = info->COMGETTER(NumberOfClients)(&cClients);
345
346 LogRelFlow(("VRDE: connected clients=%RU32\n", cClients));
347 if (SUCCEEDED(hrc) && cClients > 0)
348 {
349 ComPtr <IVRDEServer> vrdeServer;
350 hrc = pMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam());
351 if (SUCCEEDED(hrc) && vrdeServer)
352 {
353 LogRel(("VRDE: the guest user has logged out, disconnecting remote clients.\n"));
354 hrc = vrdeServer->COMSETTER(Enabled)(FALSE);
355 AssertComRC(hrc);
356 HRESULT hrc2 = vrdeServer->COMSETTER(Enabled)(TRUE);
357 if (SUCCEEDED(hrc))
358 hrc = hrc2;
359 }
360 }
361 }
362 }
363 }
364 }
365
366 if (FAILED(hrc))
367 LogRelFlow(("VRDE: returned error=%Rhrc\n", hrc));
368 }
369
370 break;
371 }
372
373 default:
374 AssertFailed();
375 }
376 return S_OK;
377 }
378
379 void ignorePowerOffEvents(bool fIgnore)
380 {
381 m_fIgnorePowerOffEvents = fIgnore;
382 }
383
384private:
385
386 long mLastVRDEPort;
387 bool m_fIgnorePowerOffEvents;
388 bool m_fNoLoggedInUsers;
389};
390
391typedef ListenerImpl<VirtualBoxClientEventListener> VirtualBoxClientEventListenerImpl;
392typedef ListenerImpl<ConsoleEventListener> ConsoleEventListenerImpl;
393
394VBOX_LISTENER_DECLARE(VirtualBoxClientEventListenerImpl)
395VBOX_LISTENER_DECLARE(ConsoleEventListenerImpl)
396
397#if !defined(RT_OS_WINDOWS)
398static void
399HandleSignal(int sig)
400{
401 RT_NOREF(sig);
402 LogRel(("VBoxHeadless: received singal %d\n", sig));
403 g_fTerminateFE = true;
404}
405#endif /* !RT_OS_WINDOWS */
406
407////////////////////////////////////////////////////////////////////////////////
408
409static void show_usage()
410{
411 RTPrintf("Usage:\n"
412 " -s, -startvm, --startvm <name|uuid> Start given VM (required argument)\n"
413 " -v, -vrde, --vrde on|off|config Enable or disable the VRDE server\n"
414 " or don't change the setting (default)\n"
415 " -e, -vrdeproperty, --vrdeproperty <name=[value]> Set a VRDE property:\n"
416 " \"TCP/Ports\" - comma-separated list of\n"
417 " ports the VRDE server can bind to; dash\n"
418 " between two port numbers specifies range\n"
419 " \"TCP/Address\" - interface IP the VRDE\n"
420 " server will bind to\n"
421 " --settingspw <pw> Specify the settings password\n"
422 " --settingspwfile <file> Specify a file containing the\n"
423 " settings password\n"
424 " -start-paused, --start-paused Start the VM in paused state\n"
425#ifdef VBOX_WITH_RECORDING
426 " -c, -record, --record Record the VM screen output to a file\n"
427 " -w, --videowidth Video frame width when recording\n"
428 " -h, --videoheight Video frame height when recording\n"
429 " -r, --videobitrate Recording bit rate when recording\n"
430 " -f, --filename File name when recording. The codec used\n"
431 " will be chosen based on file extension\n"
432#endif
433 "\n");
434}
435
436#ifdef VBOX_WITH_RECORDING
437/**
438 * Parse the environment for variables which can influence the VIDEOREC settings.
439 * purely for backwards compatibility.
440 * @param pulFrameWidth may be updated with a desired frame width
441 * @param pulFrameHeight may be updated with a desired frame height
442 * @param pulBitRate may be updated with a desired bit rate
443 * @param ppszFilename may be updated with a desired file name
444 */
445static void parse_environ(uint32_t *pulFrameWidth, uint32_t *pulFrameHeight,
446 uint32_t *pulBitRate, const char **ppszFilename)
447{
448 const char *pszEnvTemp;
449/** @todo r=bird: This isn't up to scratch. The life time of an RTEnvGet
450 * return value is only up to the next RTEnv*, *getenv, *putenv,
451 * setenv call in _any_ process in the system and the it has known and
452 * documented code page issues.
453 *
454 * Use RTEnvGetEx instead! */
455 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDWIDTH")) != 0)
456 {
457 errno = 0;
458 unsigned long ulFrameWidth = strtoul(pszEnvTemp, 0, 10);
459 if (errno != 0)
460 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDWIDTH environment variable", 0);
461 else
462 *pulFrameWidth = ulFrameWidth;
463 }
464 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDHEIGHT")) != 0)
465 {
466 errno = 0;
467 unsigned long ulFrameHeight = strtoul(pszEnvTemp, 0, 10);
468 if (errno != 0)
469 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDHEIGHT environment variable", 0);
470 else
471 *pulFrameHeight = ulFrameHeight;
472 }
473 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDBITRATE")) != 0)
474 {
475 errno = 0;
476 unsigned long ulBitRate = strtoul(pszEnvTemp, 0, 10);
477 if (errno != 0)
478 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDBITRATE environment variable", 0);
479 else
480 *pulBitRate = ulBitRate;
481 }
482 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDFILE")) != 0)
483 *ppszFilename = pszEnvTemp;
484}
485#endif /* VBOX_WITH_RECORDING defined */
486
487
488#ifdef RT_OS_WINDOWS
489
490#define MAIN_WND_CLASS L"VirtualBox Headless Interface"
491
492HINSTANCE g_hInstance = NULL;
493HWND g_hWindow = NULL;
494RTSEMEVENT g_hCanQuit;
495
496static DECLCALLBACK(int) windowsMessageMonitor(RTTHREAD ThreadSelf, void *pvUser);
497static int createWindow();
498static LRESULT CALLBACK WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
499static void destroyWindow();
500
501
502static DECLCALLBACK(int)
503windowsMessageMonitor(RTTHREAD ThreadSelf, void *pvUser)
504{
505 RT_NOREF(ThreadSelf, pvUser);
506 int rc;
507
508 rc = createWindow();
509 if (RT_FAILURE(rc))
510 return rc;
511
512 RTSemEventCreate(&g_hCanQuit);
513
514 MSG msg;
515 BOOL b;
516 while ((b = ::GetMessage(&msg, 0, 0, 0)) > 0)
517 {
518 ::TranslateMessage(&msg);
519 ::DispatchMessage(&msg);
520 }
521
522 if (b < 0)
523 LogRel(("VBoxHeadless: GetMessage failed\n"));
524
525 destroyWindow();
526 return VINF_SUCCESS;
527}
528
529
530static int
531createWindow()
532{
533 /* program instance handle */
534 g_hInstance = (HINSTANCE)::GetModuleHandle(NULL);
535 if (g_hInstance == NULL)
536 {
537 LogRel(("VBoxHeadless: failed to obtain module handle\n"));
538 return VERR_GENERAL_FAILURE;
539 }
540
541 /* window class */
542 WNDCLASS wc;
543 RT_ZERO(wc);
544
545 wc.style = CS_NOCLOSE;
546 wc.lpfnWndProc = WinMainWndProc;
547 wc.hInstance = g_hInstance;
548 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
549 wc.lpszClassName = MAIN_WND_CLASS;
550
551 ATOM atomWindowClass = ::RegisterClass(&wc);
552 if (atomWindowClass == 0)
553 {
554 LogRel(("VBoxHeadless: failed to register window class\n"));
555 return VERR_GENERAL_FAILURE;
556 }
557
558 /* secret window, secret garden */
559 g_hWindow = ::CreateWindowEx(0, MAIN_WND_CLASS, MAIN_WND_CLASS, 0,
560 0, 0, 1, 1, NULL, NULL, g_hInstance, NULL);
561 if (g_hWindow == NULL)
562 {
563 LogRel(("VBoxHeadless: failed to create window\n"));
564 return VERR_GENERAL_FAILURE;
565 }
566
567 return VINF_SUCCESS;
568}
569
570
571static void
572destroyWindow()
573{
574 if (g_hWindow == NULL)
575 return;
576
577 ::DestroyWindow(g_hWindow);
578 g_hWindow = NULL;
579
580 if (g_hInstance == NULL)
581 return;
582
583 ::UnregisterClass(MAIN_WND_CLASS, g_hInstance);
584 g_hInstance = NULL;
585}
586
587
588static LRESULT CALLBACK
589WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
590{
591 int rc;
592
593 LRESULT lResult = 0;
594 switch (msg)
595 {
596 case WM_QUERYENDSESSION:
597 LogRel(("VBoxHeadless: WM_QUERYENDSESSION:%s%s%s%s (0x%08lx)\n",
598 lParam == 0 ? " shutdown" : "",
599 lParam & ENDSESSION_CRITICAL ? " critical" : "",
600 lParam & ENDSESSION_LOGOFF ? " logoff" : "",
601 lParam & ENDSESSION_CLOSEAPP ? " close" : "",
602 (unsigned long)lParam));
603
604 /* do not block windows session termination */
605 lResult = TRUE;
606 break;
607
608 case WM_ENDSESSION:
609 lResult = 0;
610 LogRel(("WM_ENDSESSION:%s%s%s%s%s (%s/0x%08lx)\n",
611 lParam == 0 ? " shutdown" : "",
612 lParam & ENDSESSION_CRITICAL ? " critical" : "",
613 lParam & ENDSESSION_LOGOFF ? " logoff" : "",
614 lParam & ENDSESSION_CLOSEAPP ? " close" : "",
615 wParam == FALSE ? " cancelled" : "",
616 wParam ? "TRUE" : "FALSE",
617 (unsigned long)lParam));
618 if (wParam == FALSE)
619 break;
620
621 /* tell the user what we are doing */
622 ::ShutdownBlockReasonCreate(hwnd,
623 com::BstrFmt("%s saving state",
624 g_strVMName.c_str()).raw());
625
626 /* tell the VM to save state/power off */
627 g_fTerminateFE = true;
628 gEventQ->interruptEventQueueProcessing();
629
630 if (g_hCanQuit != NIL_RTSEMEVENT)
631 {
632 LogRel(("VBoxHeadless: WM_ENDSESSION: waiting for VM termination...\n"));
633
634 rc = RTSemEventWait(g_hCanQuit, RT_INDEFINITE_WAIT);
635 if (RT_SUCCESS(rc))
636 LogRel(("VBoxHeadless: WM_ENDSESSION: done\n"));
637 else
638 LogRel(("VBoxHeadless: WM_ENDSESSION: failed to wait for VM termination: %Rrc\n", rc));
639 }
640 else
641 {
642 LogRel(("VBoxHeadless: WM_ENDSESSION: cannot wait for VM termination\n"));
643 }
644 break;
645
646 default:
647 lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
648 break;
649 }
650 return lResult;
651}
652
653
654static const char * const ctrl_event_names[] = {
655 "CTRL_C_EVENT",
656 "CTRL_BREAK_EVENT",
657 "CTRL_CLOSE_EVENT",
658 /* reserved, not used */
659 "<console control event 3>",
660 "<console control event 4>",
661 /* not sent to processes that load gdi32.dll or user32.dll */
662 "CTRL_LOGOFF_EVENT",
663 "CTRL_SHUTDOWN_EVENT",
664};
665
666
667BOOL WINAPI
668ConsoleCtrlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
669{
670 const char *signame;
671 char namebuf[48];
672 int rc;
673
674 if (dwCtrlType < RT_ELEMENTS(ctrl_event_names))
675 signame = ctrl_event_names[dwCtrlType];
676 else
677 {
678 /* should not happen, but be prepared */
679 RTStrPrintf(namebuf, sizeof(namebuf),
680 "<console control event %lu>", (unsigned long)dwCtrlType);
681 signame = namebuf;
682 }
683 LogRel(("VBoxHeadless: got %s\n", signame));
684 RTMsgInfo("Got %s\n", signame);
685 RTMsgInfo("");
686
687 /* tell the VM to save state/power off */
688 g_fTerminateFE = true;
689 gEventQ->interruptEventQueueProcessing();
690
691 /*
692 * We don't need to wait for Ctrl-C / Ctrl-Break, but we must wait
693 * for Close, or we will be killed before the VM is saved.
694 */
695 if (g_hCanQuit != NIL_RTSEMEVENT)
696 {
697 LogRel(("VBoxHeadless: waiting for VM termination...\n"));
698
699 rc = RTSemEventWait(g_hCanQuit, RT_INDEFINITE_WAIT);
700 if (RT_FAILURE(rc))
701 LogRel(("VBoxHeadless: Failed to wait for VM termination: %Rrc\n", rc));
702 }
703
704 /* tell the system we handled it */
705 LogRel(("VBoxHeadless: ConsoleCtrlHandler: return\n"));
706 return TRUE;
707}
708#endif /* RT_OS_WINDOWS */
709
710
711/*
712 * Simplified version of showProgress() borrowed from VBoxManage.
713 * Note that machine power up/down operations are not cancelable, so
714 * we don't bother checking for signals.
715 */
716HRESULT
717showProgress(const ComPtr<IProgress> &progress)
718{
719 BOOL fCompleted = FALSE;
720 ULONG ulLastPercent = 0;
721 ULONG ulCurrentPercent = 0;
722 HRESULT hrc;
723
724 com::Bstr bstrDescription;
725 hrc = progress->COMGETTER(Description(bstrDescription.asOutParam()));
726 if (FAILED(hrc))
727 {
728 RTStrmPrintf(g_pStdErr, "Failed to get progress description: %Rhrc\n", hrc);
729 return hrc;
730 }
731
732 RTStrmPrintf(g_pStdErr, "%ls: ", bstrDescription.raw());
733 RTStrmFlush(g_pStdErr);
734
735 hrc = progress->COMGETTER(Completed(&fCompleted));
736 while (SUCCEEDED(hrc))
737 {
738 progress->COMGETTER(Percent(&ulCurrentPercent));
739
740 /* did we cross a 10% mark? */
741 if (ulCurrentPercent / 10 > ulLastPercent / 10)
742 {
743 /* make sure to also print out missed steps */
744 for (ULONG curVal = (ulLastPercent / 10) * 10 + 10; curVal <= (ulCurrentPercent / 10) * 10; curVal += 10)
745 {
746 if (curVal < 100)
747 {
748 RTStrmPrintf(g_pStdErr, "%u%%...", curVal);
749 RTStrmFlush(g_pStdErr);
750 }
751 }
752 ulLastPercent = (ulCurrentPercent / 10) * 10;
753 }
754
755 if (fCompleted)
756 break;
757
758 gEventQ->processEventQueue(500);
759 hrc = progress->COMGETTER(Completed(&fCompleted));
760 }
761
762 /* complete the line. */
763 LONG iRc = E_FAIL;
764 hrc = progress->COMGETTER(ResultCode)(&iRc);
765 if (SUCCEEDED(hrc))
766 {
767 if (SUCCEEDED(iRc))
768 RTStrmPrintf(g_pStdErr, "100%%\n");
769#if 0
770 else if (g_fCanceled)
771 RTStrmPrintf(g_pStdErr, "CANCELED\n");
772#endif
773 else
774 {
775 RTStrmPrintf(g_pStdErr, "\n");
776 RTStrmPrintf(g_pStdErr, "Operation failed: %Rhrc\n", iRc);
777 }
778 hrc = iRc;
779 }
780 else
781 {
782 RTStrmPrintf(g_pStdErr, "\n");
783 RTStrmPrintf(g_pStdErr, "Failed to obtain operation result: %Rhrc\n", hrc);
784 }
785 RTStrmFlush(g_pStdErr);
786 return hrc;
787}
788
789
790/**
791 * Entry point.
792 */
793extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
794{
795 RT_NOREF(envp);
796 const char *vrdePort = NULL;
797 const char *vrdeAddress = NULL;
798 const char *vrdeEnabled = NULL;
799 unsigned cVRDEProperties = 0;
800 const char *aVRDEProperties[16];
801 unsigned fRawR0 = ~0U;
802 unsigned fRawR3 = ~0U;
803 unsigned fPATM = ~0U;
804 unsigned fCSAM = ~0U;
805 unsigned fPaused = 0;
806#ifdef VBOX_WITH_RECORDING
807 bool fRecordEnabled = false;
808 uint32_t ulRecordVideoWidth = 800;
809 uint32_t ulRecordVideoHeight = 600;
810 uint32_t ulRecordVideoRate = 300000;
811 char szRecordFilename[RTPATH_MAX];
812 const char *pszRecordFilenameTemplate = "VBox-%d.webm"; /* .webm container by default. */
813#endif /* VBOX_WITH_RECORDING */
814#ifdef RT_OS_WINDOWS
815 ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
816#endif
817
818 LogFlow(("VBoxHeadless STARTED.\n"));
819 RTPrintf(VBOX_PRODUCT " Headless Interface " VBOX_VERSION_STRING "\n"
820 "(C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
821 "All rights reserved.\n\n");
822
823#ifdef VBOX_WITH_RECORDING
824 /* Parse the environment */
825 parse_environ(&ulRecordVideoWidth, &ulRecordVideoHeight, &ulRecordVideoRate, &pszRecordFilenameTemplate);
826#endif
827
828 enum eHeadlessOptions
829 {
830 OPT_RAW_R0 = 0x100,
831 OPT_NO_RAW_R0,
832 OPT_RAW_R3,
833 OPT_NO_RAW_R3,
834 OPT_PATM,
835 OPT_NO_PATM,
836 OPT_CSAM,
837 OPT_NO_CSAM,
838 OPT_SETTINGSPW,
839 OPT_SETTINGSPW_FILE,
840 OPT_COMMENT,
841 OPT_PAUSED
842 };
843
844 static const RTGETOPTDEF s_aOptions[] =
845 {
846 { "-startvm", 's', RTGETOPT_REQ_STRING },
847 { "--startvm", 's', RTGETOPT_REQ_STRING },
848 { "-vrdpport", 'p', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
849 { "--vrdpport", 'p', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
850 { "-vrdpaddress", 'a', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
851 { "--vrdpaddress", 'a', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
852 { "-vrdp", 'v', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
853 { "--vrdp", 'v', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
854 { "-vrde", 'v', RTGETOPT_REQ_STRING },
855 { "--vrde", 'v', RTGETOPT_REQ_STRING },
856 { "-vrdeproperty", 'e', RTGETOPT_REQ_STRING },
857 { "--vrdeproperty", 'e', RTGETOPT_REQ_STRING },
858 { "-rawr0", OPT_RAW_R0, 0 },
859 { "--rawr0", OPT_RAW_R0, 0 },
860 { "-norawr0", OPT_NO_RAW_R0, 0 },
861 { "--norawr0", OPT_NO_RAW_R0, 0 },
862 { "-rawr3", OPT_RAW_R3, 0 },
863 { "--rawr3", OPT_RAW_R3, 0 },
864 { "-norawr3", OPT_NO_RAW_R3, 0 },
865 { "--norawr3", OPT_NO_RAW_R3, 0 },
866 { "-patm", OPT_PATM, 0 },
867 { "--patm", OPT_PATM, 0 },
868 { "-nopatm", OPT_NO_PATM, 0 },
869 { "--nopatm", OPT_NO_PATM, 0 },
870 { "-csam", OPT_CSAM, 0 },
871 { "--csam", OPT_CSAM, 0 },
872 { "-nocsam", OPT_NO_CSAM, 0 },
873 { "--nocsam", OPT_NO_CSAM, 0 },
874 { "--settingspw", OPT_SETTINGSPW, RTGETOPT_REQ_STRING },
875 { "--settingspwfile", OPT_SETTINGSPW_FILE, RTGETOPT_REQ_STRING },
876#ifdef VBOX_WITH_RECORDING
877 { "-record", 'c', 0 },
878 { "--record", 'c', 0 },
879 { "--videowidth", 'w', RTGETOPT_REQ_UINT32 },
880 { "--videoheight", 'h', RTGETOPT_REQ_UINT32 }, /* great choice of short option! */
881 { "--videorate", 'r', RTGETOPT_REQ_UINT32 },
882 { "--filename", 'f', RTGETOPT_REQ_STRING },
883#endif /* VBOX_WITH_RECORDING defined */
884 { "-comment", OPT_COMMENT, RTGETOPT_REQ_STRING },
885 { "--comment", OPT_COMMENT, RTGETOPT_REQ_STRING },
886 { "-start-paused", OPT_PAUSED, 0 },
887 { "--start-paused", OPT_PAUSED, 0 }
888 };
889
890 const char *pcszNameOrUUID = NULL;
891
892 // parse the command line
893 int ch;
894 const char *pcszSettingsPw = NULL;
895 const char *pcszSettingsPwFile = NULL;
896 RTGETOPTUNION ValueUnion;
897 RTGETOPTSTATE GetState;
898 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
899 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
900 {
901 switch(ch)
902 {
903 case 's':
904 pcszNameOrUUID = ValueUnion.psz;
905 break;
906 case 'p':
907 RTPrintf("Warning: '-p' or '-vrdpport' are deprecated. Use '-e \"TCP/Ports=%s\"'\n", ValueUnion.psz);
908 vrdePort = ValueUnion.psz;
909 break;
910 case 'a':
911 RTPrintf("Warning: '-a' or '-vrdpaddress' are deprecated. Use '-e \"TCP/Address=%s\"'\n", ValueUnion.psz);
912 vrdeAddress = ValueUnion.psz;
913 break;
914 case 'v':
915 vrdeEnabled = ValueUnion.psz;
916 break;
917 case 'e':
918 if (cVRDEProperties < RT_ELEMENTS(aVRDEProperties))
919 aVRDEProperties[cVRDEProperties++] = ValueUnion.psz;
920 else
921 RTPrintf("Warning: too many VRDE properties. Ignored: '%s'\n", ValueUnion.psz);
922 break;
923 case OPT_RAW_R0:
924 fRawR0 = true;
925 break;
926 case OPT_NO_RAW_R0:
927 fRawR0 = false;
928 break;
929 case OPT_RAW_R3:
930 fRawR3 = true;
931 break;
932 case OPT_NO_RAW_R3:
933 fRawR3 = false;
934 break;
935 case OPT_PATM:
936 fPATM = true;
937 break;
938 case OPT_NO_PATM:
939 fPATM = false;
940 break;
941 case OPT_CSAM:
942 fCSAM = true;
943 break;
944 case OPT_NO_CSAM:
945 fCSAM = false;
946 break;
947 case OPT_SETTINGSPW:
948 pcszSettingsPw = ValueUnion.psz;
949 break;
950 case OPT_SETTINGSPW_FILE:
951 pcszSettingsPwFile = ValueUnion.psz;
952 break;
953 case OPT_PAUSED:
954 fPaused = true;
955 break;
956#ifdef VBOX_WITH_RECORDING
957 case 'c':
958 fRecordEnabled = true;
959 break;
960 case 'w':
961 ulRecordVideoWidth = ValueUnion.u32;
962 break;
963 case 'r':
964 ulRecordVideoRate = ValueUnion.u32;
965 break;
966 case 'f':
967 pszRecordFilenameTemplate = ValueUnion.psz;
968 break;
969#endif /* VBOX_WITH_RECORDING defined */
970 case 'h':
971#ifdef VBOX_WITH_RECORDING
972 if ((GetState.pDef->fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
973 {
974 ulRecordVideoHeight = ValueUnion.u32;
975 break;
976 }
977#endif
978 show_usage();
979 return 0;
980 case OPT_COMMENT:
981 /* nothing to do */
982 break;
983 case 'V':
984 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
985 return 0;
986 default:
987 ch = RTGetOptPrintError(ch, &ValueUnion);
988 show_usage();
989 return ch;
990 }
991 }
992
993#ifdef VBOX_WITH_RECORDING
994 if (ulRecordVideoWidth < 512 || ulRecordVideoWidth > 2048 || ulRecordVideoWidth % 2)
995 {
996 LogError("VBoxHeadless: ERROR: please specify an even video frame width between 512 and 2048", 0);
997 return 1;
998 }
999 if (ulRecordVideoHeight < 384 || ulRecordVideoHeight > 1536 || ulRecordVideoHeight % 2)
1000 {
1001 LogError("VBoxHeadless: ERROR: please specify an even video frame height between 384 and 1536", 0);
1002 return 1;
1003 }
1004 if (ulRecordVideoRate < 300000 || ulRecordVideoRate > 1000000)
1005 {
1006 LogError("VBoxHeadless: ERROR: please specify an even video bitrate between 300000 and 1000000", 0);
1007 return 1;
1008 }
1009 /* Make sure we only have %d or %u (or none) in the file name specified */
1010 char *pcPercent = (char*)strchr(pszRecordFilenameTemplate, '%');
1011 if (pcPercent != 0 && *(pcPercent + 1) != 'd' && *(pcPercent + 1) != 'u')
1012 {
1013 LogError("VBoxHeadless: ERROR: Only %%d and %%u are allowed in the recording file name.", -1);
1014 return 1;
1015 }
1016 /* And no more than one % in the name */
1017 if (pcPercent != 0 && strchr(pcPercent + 1, '%') != 0)
1018 {
1019 LogError("VBoxHeadless: ERROR: Only one format modifier is allowed in the recording file name.", -1);
1020 return 1;
1021 }
1022 RTStrPrintf(&szRecordFilename[0], RTPATH_MAX, pszRecordFilenameTemplate, RTProcSelf());
1023#endif /* defined VBOX_WITH_RECORDING */
1024
1025 if (!pcszNameOrUUID)
1026 {
1027 show_usage();
1028 return 1;
1029 }
1030
1031 HRESULT rc;
1032 int irc;
1033
1034 rc = com::Initialize();
1035#ifdef VBOX_WITH_XPCOM
1036 if (rc == NS_ERROR_FILE_ACCESS_DENIED)
1037 {
1038 char szHome[RTPATH_MAX] = "";
1039 com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
1040 RTPrintf("Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome);
1041 return 1;
1042 }
1043#endif
1044 if (FAILED(rc))
1045 {
1046 RTPrintf("VBoxHeadless: ERROR: failed to initialize COM!\n");
1047 return 1;
1048 }
1049
1050 ComPtr<IVirtualBoxClient> pVirtualBoxClient;
1051 ComPtr<IVirtualBox> virtualBox;
1052 ComPtr<ISession> session;
1053 ComPtr<IMachine> machine;
1054 bool fSessionOpened = false;
1055 ComPtr<IEventListener> vboxClientListener;
1056 ComPtr<IEventListener> vboxListener;
1057 ComObjPtr<ConsoleEventListenerImpl> consoleListener;
1058
1059 do
1060 {
1061 rc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
1062 if (FAILED(rc))
1063 {
1064 RTPrintf("VBoxHeadless: ERROR: failed to create the VirtualBoxClient object!\n");
1065 com::ErrorInfo info;
1066 if (!info.isFullAvailable() && !info.isBasicAvailable())
1067 {
1068 com::GluePrintRCMessage(rc);
1069 RTPrintf("Most likely, the VirtualBox COM server is not running or failed to start.\n");
1070 }
1071 else
1072 GluePrintErrorInfo(info);
1073 break;
1074 }
1075
1076 rc = pVirtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam());
1077 if (FAILED(rc))
1078 {
1079 RTPrintf("Failed to get VirtualBox object (rc=%Rhrc)!\n", rc);
1080 break;
1081 }
1082 rc = pVirtualBoxClient->COMGETTER(Session)(session.asOutParam());
1083 if (FAILED(rc))
1084 {
1085 RTPrintf("Failed to get session object (rc=%Rhrc)!\n", rc);
1086 break;
1087 }
1088
1089 if (pcszSettingsPw)
1090 {
1091 CHECK_ERROR(virtualBox, SetSettingsSecret(Bstr(pcszSettingsPw).raw()));
1092 if (FAILED(rc))
1093 break;
1094 }
1095 else if (pcszSettingsPwFile)
1096 {
1097 int rcExit = settingsPasswordFile(virtualBox, pcszSettingsPwFile);
1098 if (rcExit != RTEXITCODE_SUCCESS)
1099 break;
1100 }
1101
1102 ComPtr<IMachine> m;
1103
1104 rc = virtualBox->FindMachine(Bstr(pcszNameOrUUID).raw(), m.asOutParam());
1105 if (FAILED(rc))
1106 {
1107 LogError("Invalid machine name or UUID!\n", rc);
1108 break;
1109 }
1110
1111 Bstr bstrVMId;
1112 rc = m->COMGETTER(Id)(bstrVMId.asOutParam());
1113 AssertComRC(rc);
1114 if (FAILED(rc))
1115 break;
1116 g_strVMUUID = bstrVMId;
1117
1118 Bstr bstrVMName;
1119 rc = m->COMGETTER(Name)(bstrVMName.asOutParam());
1120 AssertComRC(rc);
1121 if (FAILED(rc))
1122 break;
1123 g_strVMName = bstrVMName;
1124
1125 Log(("VBoxHeadless: Opening a session with machine (id={%s})...\n",
1126 g_strVMUUID.c_str()));
1127
1128 // set session name
1129 CHECK_ERROR_BREAK(session, COMSETTER(Name)(Bstr("headless").raw()));
1130 // open a session
1131 CHECK_ERROR_BREAK(m, LockMachine(session, LockType_VM));
1132 fSessionOpened = true;
1133
1134 /* get the console */
1135 ComPtr<IConsole> console;
1136 CHECK_ERROR_BREAK(session, COMGETTER(Console)(console.asOutParam()));
1137
1138 /* get the mutable machine */
1139 CHECK_ERROR_BREAK(console, COMGETTER(Machine)(machine.asOutParam()));
1140
1141 ComPtr<IDisplay> display;
1142 CHECK_ERROR_BREAK(console, COMGETTER(Display)(display.asOutParam()));
1143
1144#ifdef VBOX_WITH_RECORDING
1145 if (fRecordEnabled)
1146 {
1147 ComPtr<IRecordingSettings> recordingSettings;
1148 CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam()));
1149 CHECK_ERROR_BREAK(recordingSettings, COMSETTER(Enabled)(TRUE));
1150
1151 SafeIfaceArray <IRecordingScreenSettings> saRecordScreenScreens;
1152 CHECK_ERROR_BREAK(recordingSettings, COMGETTER(Screens)(ComSafeArrayAsOutParam(saRecordScreenScreens)));
1153
1154 /* Note: For now all screens have the same configuration. */
1155 for (size_t i = 0; i < saRecordScreenScreens.size(); ++i)
1156 {
1157 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(Enabled)(TRUE));
1158 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(Filename)(Bstr(szRecordFilename).raw()));
1159 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoWidth)(ulRecordVideoWidth));
1160 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoHeight)(ulRecordVideoHeight));
1161 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoRate)(ulRecordVideoRate));
1162 }
1163 }
1164#endif /* defined(VBOX_WITH_RECORDING) */
1165
1166 /* get the machine debugger (isn't necessarily available) */
1167 ComPtr <IMachineDebugger> machineDebugger;
1168 console->COMGETTER(Debugger)(machineDebugger.asOutParam());
1169 if (machineDebugger)
1170 {
1171 Log(("Machine debugger available!\n"));
1172 }
1173
1174 if (fRawR0 != ~0U)
1175 {
1176 if (!machineDebugger)
1177 {
1178 RTPrintf("Error: No debugger object; -%srawr0 cannot be executed!\n", fRawR0 ? "" : "no");
1179 break;
1180 }
1181 machineDebugger->COMSETTER(RecompileSupervisor)(!fRawR0);
1182 }
1183 if (fRawR3 != ~0U)
1184 {
1185 if (!machineDebugger)
1186 {
1187 RTPrintf("Error: No debugger object; -%srawr3 cannot be executed!\n", fRawR3 ? "" : "no");
1188 break;
1189 }
1190 machineDebugger->COMSETTER(RecompileUser)(!fRawR3);
1191 }
1192 if (fPATM != ~0U)
1193 {
1194 if (!machineDebugger)
1195 {
1196 RTPrintf("Error: No debugger object; -%spatm cannot be executed!\n", fPATM ? "" : "no");
1197 break;
1198 }
1199 machineDebugger->COMSETTER(PATMEnabled)(fPATM);
1200 }
1201 if (fCSAM != ~0U)
1202 {
1203 if (!machineDebugger)
1204 {
1205 RTPrintf("Error: No debugger object; -%scsam cannot be executed!\n", fCSAM ? "" : "no");
1206 break;
1207 }
1208 machineDebugger->COMSETTER(CSAMEnabled)(fCSAM);
1209 }
1210
1211 /* initialize global references */
1212 gConsole = console;
1213 gEventQ = com::NativeEventQueue::getMainEventQueue();
1214
1215 /* VirtualBoxClient events registration. */
1216 {
1217 ComPtr<IEventSource> pES;
1218 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1219 ComObjPtr<VirtualBoxClientEventListenerImpl> listener;
1220 listener.createObject();
1221 listener->init(new VirtualBoxClientEventListener());
1222 vboxClientListener = listener;
1223 com::SafeArray<VBoxEventType_T> eventTypes;
1224 eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
1225 CHECK_ERROR(pES, RegisterListener(vboxClientListener, ComSafeArrayAsInParam(eventTypes), true));
1226 }
1227
1228 /* Console events registration. */
1229 {
1230 ComPtr<IEventSource> es;
1231 CHECK_ERROR(console, COMGETTER(EventSource)(es.asOutParam()));
1232 consoleListener.createObject();
1233 consoleListener->init(new ConsoleEventListener());
1234 com::SafeArray<VBoxEventType_T> eventTypes;
1235 eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
1236 eventTypes.push_back(VBoxEventType_OnStateChanged);
1237 eventTypes.push_back(VBoxEventType_OnVRDEServerInfoChanged);
1238 eventTypes.push_back(VBoxEventType_OnCanShowWindow);
1239 eventTypes.push_back(VBoxEventType_OnShowWindow);
1240 eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
1241 CHECK_ERROR(es, RegisterListener(consoleListener, ComSafeArrayAsInParam(eventTypes), true));
1242 }
1243
1244 /* Default is to use the VM setting for the VRDE server. */
1245 enum VRDEOption
1246 {
1247 VRDEOption_Config,
1248 VRDEOption_Off,
1249 VRDEOption_On
1250 };
1251 VRDEOption enmVRDEOption = VRDEOption_Config;
1252 BOOL fVRDEEnabled;
1253 ComPtr <IVRDEServer> vrdeServer;
1254 CHECK_ERROR_BREAK(machine, COMGETTER(VRDEServer)(vrdeServer.asOutParam()));
1255 CHECK_ERROR_BREAK(vrdeServer, COMGETTER(Enabled)(&fVRDEEnabled));
1256
1257 if (vrdeEnabled != NULL)
1258 {
1259 /* -vrde on|off|config */
1260 if (!strcmp(vrdeEnabled, "off") || !strcmp(vrdeEnabled, "disable"))
1261 enmVRDEOption = VRDEOption_Off;
1262 else if (!strcmp(vrdeEnabled, "on") || !strcmp(vrdeEnabled, "enable"))
1263 enmVRDEOption = VRDEOption_On;
1264 else if (strcmp(vrdeEnabled, "config"))
1265 {
1266 RTPrintf("-vrde requires an argument (on|off|config)\n");
1267 break;
1268 }
1269 }
1270
1271 Log(("VBoxHeadless: enmVRDE %d, fVRDEEnabled %d\n", enmVRDEOption, fVRDEEnabled));
1272
1273 if (enmVRDEOption != VRDEOption_Off)
1274 {
1275 /* Set other specified options. */
1276
1277 /* set VRDE port if requested by the user */
1278 if (vrdePort != NULL)
1279 {
1280 Bstr bstr = vrdePort;
1281 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.raw()));
1282 }
1283 /* set VRDE address if requested by the user */
1284 if (vrdeAddress != NULL)
1285 {
1286 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(Bstr("TCP/Address").raw(), Bstr(vrdeAddress).raw()));
1287 }
1288
1289 /* Set VRDE properties. */
1290 if (cVRDEProperties > 0)
1291 {
1292 for (unsigned i = 0; i < cVRDEProperties; i++)
1293 {
1294 /* Parse 'name=value' */
1295 char *pszProperty = RTStrDup(aVRDEProperties[i]);
1296 if (pszProperty)
1297 {
1298 char *pDelimiter = strchr(pszProperty, '=');
1299 if (pDelimiter)
1300 {
1301 *pDelimiter = '\0';
1302
1303 Bstr bstrName = pszProperty;
1304 Bstr bstrValue = &pDelimiter[1];
1305 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(bstrName.raw(), bstrValue.raw()));
1306 }
1307 else
1308 {
1309 RTPrintf("Error: Invalid VRDE property '%s'\n", aVRDEProperties[i]);
1310 RTStrFree(pszProperty);
1311 rc = E_INVALIDARG;
1312 break;
1313 }
1314 RTStrFree(pszProperty);
1315 }
1316 else
1317 {
1318 RTPrintf("Error: Failed to allocate memory for VRDE property '%s'\n", aVRDEProperties[i]);
1319 rc = E_OUTOFMEMORY;
1320 break;
1321 }
1322 }
1323 if (FAILED(rc))
1324 break;
1325 }
1326
1327 }
1328
1329 if (enmVRDEOption == VRDEOption_On)
1330 {
1331 /* enable VRDE server (only if currently disabled) */
1332 if (!fVRDEEnabled)
1333 {
1334 CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(TRUE));
1335 }
1336 }
1337 else if (enmVRDEOption == VRDEOption_Off)
1338 {
1339 /* disable VRDE server (only if currently enabled */
1340 if (fVRDEEnabled)
1341 {
1342 CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(FALSE));
1343 }
1344 }
1345
1346 /* Disable the host clipboard before powering up */
1347 console->COMSETTER(UseHostClipboard)(false);
1348
1349 Log(("VBoxHeadless: Powering up the machine...\n"));
1350
1351
1352 /**
1353 * @todo We should probably install handlers earlier so that
1354 * we can undo any temporary settings we do above in case of
1355 * an early signal and use RAII to ensure proper cleanup.
1356 */
1357#if !defined(RT_OS_WINDOWS)
1358 signal(SIGPIPE, SIG_IGN);
1359 signal(SIGTTOU, SIG_IGN);
1360
1361 struct sigaction sa;
1362 RT_ZERO(sa);
1363 sa.sa_handler = HandleSignal;
1364 sigaction(SIGHUP, &sa, NULL);
1365 sigaction(SIGINT, &sa, NULL);
1366 sigaction(SIGTERM, &sa, NULL);
1367 sigaction(SIGUSR1, &sa, NULL);
1368 /* Don't touch SIGUSR2 as IPRT could be using it for RTThreadPoke(). */
1369
1370#else /* RT_OS_WINDOWS */
1371 /*
1372 * Register windows console signal handler to react to Ctrl-C,
1373 * Ctrl-Break, Close, non-interactive session termination.
1374 */
1375 ::SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
1376#endif
1377
1378
1379 ComPtr <IProgress> progress;
1380 if (!fPaused)
1381 CHECK_ERROR_BREAK(console, PowerUp(progress.asOutParam()));
1382 else
1383 CHECK_ERROR_BREAK(console, PowerUpPaused(progress.asOutParam()));
1384
1385 rc = showProgress(progress);
1386 if (FAILED(rc))
1387 {
1388 com::ProgressErrorInfo info(progress);
1389 if (info.isBasicAvailable())
1390 {
1391 RTPrintf("Error: failed to start machine. Error message: %ls\n", info.getText().raw());
1392 }
1393 else
1394 {
1395 RTPrintf("Error: failed to start machine. No error message available!\n");
1396 }
1397 break;
1398 }
1399
1400#ifdef RT_OS_WINDOWS
1401 /*
1402 * Spawn windows message pump to monitor session events.
1403 */
1404 RTTHREAD hThrMsg;
1405 irc = RTThreadCreate(&hThrMsg,
1406 windowsMessageMonitor, NULL,
1407 0, /* :cbStack */
1408 RTTHREADTYPE_MSG_PUMP, 0,
1409 "MSG");
1410 if (RT_FAILURE(irc)) /* not fatal */
1411 LogRel(("VBoxHeadless: failed to start windows message monitor: %Rrc\n", irc));
1412#endif /* RT_OS_WINDOWS */
1413
1414
1415 /*
1416 * Pump vbox events forever
1417 */
1418 LogRel(("VBoxHeadless: starting event loop\n"));
1419 for (;;)
1420 {
1421 irc = gEventQ->processEventQueue(RT_INDEFINITE_WAIT);
1422
1423 /*
1424 * interruptEventQueueProcessing from another thread is
1425 * reported as VERR_INTERRUPTED, so check the flag first.
1426 */
1427 if (g_fTerminateFE)
1428 {
1429 LogRel(("VBoxHeadless: processEventQueue: %Rrc, termination requested\n", irc));
1430 break;
1431 }
1432
1433 if (RT_FAILURE(irc))
1434 {
1435 LogRel(("VBoxHeadless: processEventQueue: %Rrc\n", irc));
1436 RTMsgError("event loop: %Rrc", irc);
1437 break;
1438 }
1439 }
1440
1441 Log(("VBoxHeadless: event loop has terminated...\n"));
1442
1443#ifdef VBOX_WITH_RECORDING
1444 if (fRecordEnabled)
1445 {
1446 if (!machine.isNull())
1447 {
1448 ComPtr<IRecordingSettings> recordingSettings;
1449 CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam()));
1450 CHECK_ERROR_BREAK(recordingSettings, COMSETTER(Enabled)(FALSE));
1451 }
1452 }
1453#endif /* VBOX_WITH_RECORDING */
1454
1455 /* we don't have to disable VRDE here because we don't save the settings of the VM */
1456 }
1457 while (0);
1458
1459 /*
1460 * Get the machine state.
1461 */
1462 MachineState_T machineState = MachineState_Aborted;
1463 if (!machine.isNull())
1464 {
1465 rc = machine->COMGETTER(State)(&machineState);
1466 if (SUCCEEDED(rc))
1467 Log(("machine state = %RU32\n", machineState));
1468 else
1469 Log(("IMachine::getState: %Rhrc\n", rc));
1470 }
1471 else
1472 {
1473 Log(("machine == NULL\n"));
1474 }
1475
1476 /*
1477 * Turn off the VM if it's running
1478 */
1479 if ( gConsole
1480 && ( machineState == MachineState_Running
1481 || machineState == MachineState_Teleporting
1482 || machineState == MachineState_LiveSnapshotting
1483 /** @todo power off paused VMs too? */
1484 )
1485 )
1486 do
1487 {
1488 consoleListener->getWrapped()->ignorePowerOffEvents(true);
1489
1490 ComPtr<IProgress> pProgress;
1491 if (!machine.isNull())
1492 CHECK_ERROR_BREAK(machine, SaveState(pProgress.asOutParam()));
1493 else
1494 CHECK_ERROR_BREAK(gConsole, PowerDown(pProgress.asOutParam()));
1495
1496 rc = showProgress(pProgress);
1497 if (FAILED(rc))
1498 {
1499 com::ErrorInfo info;
1500 if (!info.isFullAvailable() && !info.isBasicAvailable())
1501 com::GluePrintRCMessage(rc);
1502 else
1503 com::GluePrintErrorInfo(info);
1504 break;
1505 }
1506 } while (0);
1507
1508 /* VirtualBox callback unregistration. */
1509 if (vboxListener)
1510 {
1511 ComPtr<IEventSource> es;
1512 CHECK_ERROR(virtualBox, COMGETTER(EventSource)(es.asOutParam()));
1513 if (!es.isNull())
1514 CHECK_ERROR(es, UnregisterListener(vboxListener));
1515 vboxListener.setNull();
1516 }
1517
1518 /* Console callback unregistration. */
1519 if (consoleListener)
1520 {
1521 ComPtr<IEventSource> es;
1522 CHECK_ERROR(gConsole, COMGETTER(EventSource)(es.asOutParam()));
1523 if (!es.isNull())
1524 CHECK_ERROR(es, UnregisterListener(consoleListener));
1525 consoleListener.setNull();
1526 }
1527
1528 /* VirtualBoxClient callback unregistration. */
1529 if (vboxClientListener)
1530 {
1531 ComPtr<IEventSource> pES;
1532 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1533 if (!pES.isNull())
1534 CHECK_ERROR(pES, UnregisterListener(vboxClientListener));
1535 vboxClientListener.setNull();
1536 }
1537
1538 /* No more access to the 'console' object, which will be uninitialized by the next session->Close call. */
1539 gConsole = NULL;
1540
1541 if (fSessionOpened)
1542 {
1543 /*
1544 * Close the session. This will also uninitialize the console and
1545 * unregister the callback we've registered before.
1546 */
1547 Log(("VBoxHeadless: Closing the session...\n"));
1548 session->UnlockMachine();
1549 }
1550
1551 /* Must be before com::Shutdown */
1552 session.setNull();
1553 virtualBox.setNull();
1554 pVirtualBoxClient.setNull();
1555 machine.setNull();
1556
1557 com::Shutdown();
1558
1559#ifdef RT_OS_WINDOWS
1560 /* tell the session monitor it can ack WM_ENDSESSION */
1561 if (g_hCanQuit != NIL_RTSEMEVENT)
1562 {
1563 RTSemEventSignal(g_hCanQuit);
1564 }
1565
1566 /* tell the session monitor to quit */
1567 if (g_hWindow != NULL)
1568 {
1569 ::PostMessage(g_hWindow, WM_QUIT, 0, 0);
1570 }
1571#endif
1572
1573 LogRel(("VBoxHeadless: exiting\n"));
1574 return FAILED(rc) ? 1 : 0;
1575}
1576
1577
1578#ifndef VBOX_WITH_HARDENING
1579/**
1580 * Main entry point.
1581 */
1582int main(int argc, char **argv, char **envp)
1583{
1584 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB);
1585 if (RT_SUCCESS(rc))
1586 return TrustedMain(argc, argv, envp);
1587 RTPrintf("VBoxHeadless: Runtime initialization failed: %Rrc - %Rrf\n", rc, rc);
1588 return RTEXITCODE_FAILURE;
1589}
1590#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