VirtualBox

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

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

doc/manual,Main,Frontends: API changes in preparation for full VM encryption, guarded by VBOX_WITH_FULL_VM_ENCRYPTION (disabled by default) and returning a not supported error for now, bugref:9955

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 51.6 KB
Line 
1/* $Id: VBoxHeadless.cpp 94660 2022-04-21 08:38:34Z vboxsync $ */
2/** @file
3 * VBoxHeadless - The VirtualBox Headless frontend for running VMs on servers.
4 */
5
6/*
7 * Copyright (C) 2006-2022 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 <iprt/errcore.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 VirtualBox settings password\n"
422 " --settingspwfile <file> Specify a file containing the\n"
423 " VirtualBox settings password\n"
424 " --password <file>|- Specify the VM password. Either file containing\n"
425 " the VM password or \"-\" to read it from console\n"
426 " --password-id <id> Specify the password id for the VM password\n"
427 " -start-paused, --start-paused Start the VM in paused state\n"
428#ifdef VBOX_WITH_RECORDING
429 " -c, -record, --record Record the VM screen output to a file\n"
430 " -w, --videowidth Video frame width when recording\n"
431 " -h, --videoheight Video frame height when recording\n"
432 " -r, --videobitrate Recording bit rate when recording\n"
433 " -f, --filename File name when recording. The codec used\n"
434 " will be chosen based on file extension\n"
435#endif
436 "\n");
437}
438
439#ifdef VBOX_WITH_RECORDING
440/**
441 * Parse the environment for variables which can influence the VIDEOREC settings.
442 * purely for backwards compatibility.
443 * @param pulFrameWidth may be updated with a desired frame width
444 * @param pulFrameHeight may be updated with a desired frame height
445 * @param pulBitRate may be updated with a desired bit rate
446 * @param ppszFilename may be updated with a desired file name
447 */
448static void parse_environ(uint32_t *pulFrameWidth, uint32_t *pulFrameHeight,
449 uint32_t *pulBitRate, const char **ppszFilename)
450{
451 const char *pszEnvTemp;
452/** @todo r=bird: This isn't up to scratch. The life time of an RTEnvGet
453 * return value is only up to the next RTEnv*, *getenv, *putenv,
454 * setenv call in _any_ process in the system and the it has known and
455 * documented code page issues.
456 *
457 * Use RTEnvGetEx instead! */
458 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDWIDTH")) != 0)
459 {
460 errno = 0;
461 unsigned long ulFrameWidth = strtoul(pszEnvTemp, 0, 10);
462 if (errno != 0)
463 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDWIDTH environment variable", 0);
464 else
465 *pulFrameWidth = ulFrameWidth;
466 }
467 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDHEIGHT")) != 0)
468 {
469 errno = 0;
470 unsigned long ulFrameHeight = strtoul(pszEnvTemp, 0, 10);
471 if (errno != 0)
472 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDHEIGHT environment variable", 0);
473 else
474 *pulFrameHeight = ulFrameHeight;
475 }
476 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDBITRATE")) != 0)
477 {
478 errno = 0;
479 unsigned long ulBitRate = strtoul(pszEnvTemp, 0, 10);
480 if (errno != 0)
481 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDBITRATE environment variable", 0);
482 else
483 *pulBitRate = ulBitRate;
484 }
485 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDFILE")) != 0)
486 *ppszFilename = pszEnvTemp;
487}
488#endif /* VBOX_WITH_RECORDING defined */
489
490
491#ifdef RT_OS_WINDOWS
492
493#define MAIN_WND_CLASS L"VirtualBox Headless Interface"
494
495HINSTANCE g_hInstance = NULL;
496HWND g_hWindow = NULL;
497RTSEMEVENT g_hCanQuit;
498
499static DECLCALLBACK(int) windowsMessageMonitor(RTTHREAD ThreadSelf, void *pvUser);
500static int createWindow();
501static LRESULT CALLBACK WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
502static void destroyWindow();
503
504
505static DECLCALLBACK(int)
506windowsMessageMonitor(RTTHREAD ThreadSelf, void *pvUser)
507{
508 RT_NOREF(ThreadSelf, pvUser);
509 int rc;
510
511 rc = createWindow();
512 if (RT_FAILURE(rc))
513 return rc;
514
515 RTSemEventCreate(&g_hCanQuit);
516
517 MSG msg;
518 BOOL b;
519 while ((b = ::GetMessage(&msg, 0, 0, 0)) > 0)
520 {
521 ::TranslateMessage(&msg);
522 ::DispatchMessage(&msg);
523 }
524
525 if (b < 0)
526 LogRel(("VBoxHeadless: GetMessage failed\n"));
527
528 destroyWindow();
529 return VINF_SUCCESS;
530}
531
532
533static int
534createWindow()
535{
536 /* program instance handle */
537 g_hInstance = (HINSTANCE)::GetModuleHandle(NULL);
538 if (g_hInstance == NULL)
539 {
540 LogRel(("VBoxHeadless: failed to obtain module handle\n"));
541 return VERR_GENERAL_FAILURE;
542 }
543
544 /* window class */
545 WNDCLASS wc;
546 RT_ZERO(wc);
547
548 wc.style = CS_NOCLOSE;
549 wc.lpfnWndProc = WinMainWndProc;
550 wc.hInstance = g_hInstance;
551 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
552 wc.lpszClassName = MAIN_WND_CLASS;
553
554 ATOM atomWindowClass = ::RegisterClass(&wc);
555 if (atomWindowClass == 0)
556 {
557 LogRel(("VBoxHeadless: failed to register window class\n"));
558 return VERR_GENERAL_FAILURE;
559 }
560
561 /* secret window, secret garden */
562 g_hWindow = ::CreateWindowEx(0, MAIN_WND_CLASS, MAIN_WND_CLASS, 0,
563 0, 0, 1, 1, NULL, NULL, g_hInstance, NULL);
564 if (g_hWindow == NULL)
565 {
566 LogRel(("VBoxHeadless: failed to create window\n"));
567 return VERR_GENERAL_FAILURE;
568 }
569
570 return VINF_SUCCESS;
571}
572
573
574static void
575destroyWindow()
576{
577 if (g_hWindow == NULL)
578 return;
579
580 ::DestroyWindow(g_hWindow);
581 g_hWindow = NULL;
582
583 if (g_hInstance == NULL)
584 return;
585
586 ::UnregisterClass(MAIN_WND_CLASS, g_hInstance);
587 g_hInstance = NULL;
588}
589
590
591static LRESULT CALLBACK
592WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
593{
594 int rc;
595
596 LRESULT lResult = 0;
597 switch (msg)
598 {
599 case WM_QUERYENDSESSION:
600 LogRel(("VBoxHeadless: WM_QUERYENDSESSION:%s%s%s%s (0x%08lx)\n",
601 lParam == 0 ? " shutdown" : "",
602 lParam & ENDSESSION_CRITICAL ? " critical" : "",
603 lParam & ENDSESSION_LOGOFF ? " logoff" : "",
604 lParam & ENDSESSION_CLOSEAPP ? " close" : "",
605 (unsigned long)lParam));
606
607 /* do not block windows session termination */
608 lResult = TRUE;
609 break;
610
611 case WM_ENDSESSION:
612 lResult = 0;
613 LogRel(("WM_ENDSESSION:%s%s%s%s%s (%s/0x%08lx)\n",
614 lParam == 0 ? " shutdown" : "",
615 lParam & ENDSESSION_CRITICAL ? " critical" : "",
616 lParam & ENDSESSION_LOGOFF ? " logoff" : "",
617 lParam & ENDSESSION_CLOSEAPP ? " close" : "",
618 wParam == FALSE ? " cancelled" : "",
619 wParam ? "TRUE" : "FALSE",
620 (unsigned long)lParam));
621 if (wParam == FALSE)
622 break;
623
624 /* tell the user what we are doing */
625 ::ShutdownBlockReasonCreate(hwnd,
626 com::BstrFmt("%s saving state",
627 g_strVMName.c_str()).raw());
628
629 /* tell the VM to save state/power off */
630 g_fTerminateFE = true;
631 gEventQ->interruptEventQueueProcessing();
632
633 if (g_hCanQuit != NIL_RTSEMEVENT)
634 {
635 LogRel(("VBoxHeadless: WM_ENDSESSION: waiting for VM termination...\n"));
636
637 rc = RTSemEventWait(g_hCanQuit, RT_INDEFINITE_WAIT);
638 if (RT_SUCCESS(rc))
639 LogRel(("VBoxHeadless: WM_ENDSESSION: done\n"));
640 else
641 LogRel(("VBoxHeadless: WM_ENDSESSION: failed to wait for VM termination: %Rrc\n", rc));
642 }
643 else
644 {
645 LogRel(("VBoxHeadless: WM_ENDSESSION: cannot wait for VM termination\n"));
646 }
647 break;
648
649 default:
650 lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
651 break;
652 }
653 return lResult;
654}
655
656
657static const char * const ctrl_event_names[] = {
658 "CTRL_C_EVENT",
659 "CTRL_BREAK_EVENT",
660 "CTRL_CLOSE_EVENT",
661 /* reserved, not used */
662 "<console control event 3>",
663 "<console control event 4>",
664 /* not sent to processes that load gdi32.dll or user32.dll */
665 "CTRL_LOGOFF_EVENT",
666 "CTRL_SHUTDOWN_EVENT",
667};
668
669
670BOOL WINAPI
671ConsoleCtrlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
672{
673 const char *signame;
674 char namebuf[48];
675 int rc;
676
677 if (dwCtrlType < RT_ELEMENTS(ctrl_event_names))
678 signame = ctrl_event_names[dwCtrlType];
679 else
680 {
681 /* should not happen, but be prepared */
682 RTStrPrintf(namebuf, sizeof(namebuf),
683 "<console control event %lu>", (unsigned long)dwCtrlType);
684 signame = namebuf;
685 }
686 LogRel(("VBoxHeadless: got %s\n", signame));
687 RTMsgInfo("Got %s\n", signame);
688 RTMsgInfo("");
689
690 /* tell the VM to save state/power off */
691 g_fTerminateFE = true;
692 gEventQ->interruptEventQueueProcessing();
693
694 /*
695 * We don't need to wait for Ctrl-C / Ctrl-Break, but we must wait
696 * for Close, or we will be killed before the VM is saved.
697 */
698 if (g_hCanQuit != NIL_RTSEMEVENT)
699 {
700 LogRel(("VBoxHeadless: waiting for VM termination...\n"));
701
702 rc = RTSemEventWait(g_hCanQuit, RT_INDEFINITE_WAIT);
703 if (RT_FAILURE(rc))
704 LogRel(("VBoxHeadless: Failed to wait for VM termination: %Rrc\n", rc));
705 }
706
707 /* tell the system we handled it */
708 LogRel(("VBoxHeadless: ConsoleCtrlHandler: return\n"));
709 return TRUE;
710}
711#endif /* RT_OS_WINDOWS */
712
713
714/*
715 * Simplified version of showProgress() borrowed from VBoxManage.
716 * Note that machine power up/down operations are not cancelable, so
717 * we don't bother checking for signals.
718 */
719HRESULT
720showProgress(const ComPtr<IProgress> &progress)
721{
722 BOOL fCompleted = FALSE;
723 ULONG ulLastPercent = 0;
724 ULONG ulCurrentPercent = 0;
725 HRESULT hrc;
726
727 com::Bstr bstrDescription;
728 hrc = progress->COMGETTER(Description(bstrDescription.asOutParam()));
729 if (FAILED(hrc))
730 {
731 RTStrmPrintf(g_pStdErr, "Failed to get progress description: %Rhrc\n", hrc);
732 return hrc;
733 }
734
735 RTStrmPrintf(g_pStdErr, "%ls: ", bstrDescription.raw());
736 RTStrmFlush(g_pStdErr);
737
738 hrc = progress->COMGETTER(Completed(&fCompleted));
739 while (SUCCEEDED(hrc))
740 {
741 progress->COMGETTER(Percent(&ulCurrentPercent));
742
743 /* did we cross a 10% mark? */
744 if (ulCurrentPercent / 10 > ulLastPercent / 10)
745 {
746 /* make sure to also print out missed steps */
747 for (ULONG curVal = (ulLastPercent / 10) * 10 + 10; curVal <= (ulCurrentPercent / 10) * 10; curVal += 10)
748 {
749 if (curVal < 100)
750 {
751 RTStrmPrintf(g_pStdErr, "%u%%...", curVal);
752 RTStrmFlush(g_pStdErr);
753 }
754 }
755 ulLastPercent = (ulCurrentPercent / 10) * 10;
756 }
757
758 if (fCompleted)
759 break;
760
761 gEventQ->processEventQueue(500);
762 hrc = progress->COMGETTER(Completed(&fCompleted));
763 }
764
765 /* complete the line. */
766 LONG iRc = E_FAIL;
767 hrc = progress->COMGETTER(ResultCode)(&iRc);
768 if (SUCCEEDED(hrc))
769 {
770 if (SUCCEEDED(iRc))
771 RTStrmPrintf(g_pStdErr, "100%%\n");
772#if 0
773 else if (g_fCanceled)
774 RTStrmPrintf(g_pStdErr, "CANCELED\n");
775#endif
776 else
777 {
778 RTStrmPrintf(g_pStdErr, "\n");
779 RTStrmPrintf(g_pStdErr, "Operation failed: %Rhrc\n", iRc);
780 }
781 hrc = iRc;
782 }
783 else
784 {
785 RTStrmPrintf(g_pStdErr, "\n");
786 RTStrmPrintf(g_pStdErr, "Failed to obtain operation result: %Rhrc\n", hrc);
787 }
788 RTStrmFlush(g_pStdErr);
789 return hrc;
790}
791
792
793/**
794 * Entry point.
795 */
796extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
797{
798 RT_NOREF(envp);
799 const char *vrdePort = NULL;
800 const char *vrdeAddress = NULL;
801 const char *vrdeEnabled = NULL;
802 unsigned cVRDEProperties = 0;
803 const char *aVRDEProperties[16];
804 unsigned fPaused = 0;
805#ifdef VBOX_WITH_RECORDING
806 bool fRecordEnabled = false;
807 uint32_t ulRecordVideoWidth = 800;
808 uint32_t ulRecordVideoHeight = 600;
809 uint32_t ulRecordVideoRate = 300000;
810 char szRecordFilename[RTPATH_MAX];
811 const char *pszRecordFilenameTemplate = "VBox-%d.webm"; /* .webm container by default. */
812#endif /* VBOX_WITH_RECORDING */
813#ifdef RT_OS_WINDOWS
814 ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
815#endif
816
817 LogFlow(("VBoxHeadless STARTED.\n"));
818 RTPrintf(VBOX_PRODUCT " Headless Interface " VBOX_VERSION_STRING "\n"
819 "(C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
820 "All rights reserved.\n\n");
821
822#ifdef VBOX_WITH_RECORDING
823 /* Parse the environment */
824 parse_environ(&ulRecordVideoWidth, &ulRecordVideoHeight, &ulRecordVideoRate, &pszRecordFilenameTemplate);
825#endif
826
827 enum eHeadlessOptions
828 {
829 OPT_SETTINGSPW = 0x100,
830 OPT_SETTINGSPW_FILE,
831 OPT_COMMENT,
832 OPT_PAUSED,
833 OPT_VMPW,
834 OPT_VMPWID
835 };
836
837 static const RTGETOPTDEF s_aOptions[] =
838 {
839 { "-startvm", 's', RTGETOPT_REQ_STRING },
840 { "--startvm", 's', RTGETOPT_REQ_STRING },
841 { "-vrdpport", 'p', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
842 { "--vrdpport", 'p', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
843 { "-vrdpaddress", 'a', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
844 { "--vrdpaddress", 'a', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
845 { "-vrdp", 'v', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
846 { "--vrdp", 'v', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
847 { "-vrde", 'v', RTGETOPT_REQ_STRING },
848 { "--vrde", 'v', RTGETOPT_REQ_STRING },
849 { "-vrdeproperty", 'e', RTGETOPT_REQ_STRING },
850 { "--vrdeproperty", 'e', RTGETOPT_REQ_STRING },
851 { "--settingspw", OPT_SETTINGSPW, RTGETOPT_REQ_STRING },
852 { "--settingspwfile", OPT_SETTINGSPW_FILE, RTGETOPT_REQ_STRING },
853 { "--password", OPT_VMPW, RTGETOPT_REQ_STRING },
854 { "--password-id", OPT_VMPWID, RTGETOPT_REQ_STRING },
855#ifdef VBOX_WITH_RECORDING
856 { "-record", 'c', 0 },
857 { "--record", 'c', 0 },
858 { "--videowidth", 'w', RTGETOPT_REQ_UINT32 },
859 { "--videoheight", 'h', RTGETOPT_REQ_UINT32 }, /* great choice of short option! */
860 { "--videorate", 'r', RTGETOPT_REQ_UINT32 },
861 { "--filename", 'f', RTGETOPT_REQ_STRING },
862#endif /* VBOX_WITH_RECORDING defined */
863 { "-comment", OPT_COMMENT, RTGETOPT_REQ_STRING },
864 { "--comment", OPT_COMMENT, RTGETOPT_REQ_STRING },
865 { "-start-paused", OPT_PAUSED, 0 },
866 { "--start-paused", OPT_PAUSED, 0 }
867 };
868
869 const char *pcszNameOrUUID = NULL;
870
871 // parse the command line
872 int ch;
873 const char *pcszSettingsPw = NULL;
874 const char *pcszSettingsPwFile = NULL;
875 const char *pcszVmPassword = NULL;
876 const char *pcszVmPasswordId = NULL;
877 RTGETOPTUNION ValueUnion;
878 RTGETOPTSTATE GetState;
879 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
880 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
881 {
882 switch(ch)
883 {
884 case 's':
885 pcszNameOrUUID = ValueUnion.psz;
886 break;
887 case 'p':
888 RTPrintf("Warning: '-p' or '-vrdpport' are deprecated. Use '-e \"TCP/Ports=%s\"'\n", ValueUnion.psz);
889 vrdePort = ValueUnion.psz;
890 break;
891 case 'a':
892 RTPrintf("Warning: '-a' or '-vrdpaddress' are deprecated. Use '-e \"TCP/Address=%s\"'\n", ValueUnion.psz);
893 vrdeAddress = ValueUnion.psz;
894 break;
895 case 'v':
896 vrdeEnabled = ValueUnion.psz;
897 break;
898 case 'e':
899 if (cVRDEProperties < RT_ELEMENTS(aVRDEProperties))
900 aVRDEProperties[cVRDEProperties++] = ValueUnion.psz;
901 else
902 RTPrintf("Warning: too many VRDE properties. Ignored: '%s'\n", ValueUnion.psz);
903 break;
904 case OPT_SETTINGSPW:
905 pcszSettingsPw = ValueUnion.psz;
906 break;
907 case OPT_SETTINGSPW_FILE:
908 pcszSettingsPwFile = ValueUnion.psz;
909 break;
910 case OPT_VMPW:
911 pcszVmPassword = ValueUnion.psz;
912 break;
913 case OPT_VMPWID:
914 pcszVmPasswordId = ValueUnion.psz;
915 break;
916 case OPT_PAUSED:
917 fPaused = true;
918 break;
919#ifdef VBOX_WITH_RECORDING
920 case 'c':
921 fRecordEnabled = true;
922 break;
923 case 'w':
924 ulRecordVideoWidth = ValueUnion.u32;
925 break;
926 case 'r':
927 ulRecordVideoRate = ValueUnion.u32;
928 break;
929 case 'f':
930 pszRecordFilenameTemplate = ValueUnion.psz;
931 break;
932#endif /* VBOX_WITH_RECORDING defined */
933 case 'h':
934#ifdef VBOX_WITH_RECORDING
935 if ((GetState.pDef->fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
936 {
937 ulRecordVideoHeight = ValueUnion.u32;
938 break;
939 }
940#endif
941 show_usage();
942 return 0;
943 case OPT_COMMENT:
944 /* nothing to do */
945 break;
946 case 'V':
947 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
948 return 0;
949 default:
950 ch = RTGetOptPrintError(ch, &ValueUnion);
951 show_usage();
952 return ch;
953 }
954 }
955
956#ifdef VBOX_WITH_RECORDING
957 if (ulRecordVideoWidth < 512 || ulRecordVideoWidth > 2048 || ulRecordVideoWidth % 2)
958 {
959 LogError("VBoxHeadless: ERROR: please specify an even video frame width between 512 and 2048", 0);
960 return 1;
961 }
962 if (ulRecordVideoHeight < 384 || ulRecordVideoHeight > 1536 || ulRecordVideoHeight % 2)
963 {
964 LogError("VBoxHeadless: ERROR: please specify an even video frame height between 384 and 1536", 0);
965 return 1;
966 }
967 if (ulRecordVideoRate < 300000 || ulRecordVideoRate > 1000000)
968 {
969 LogError("VBoxHeadless: ERROR: please specify an even video bitrate between 300000 and 1000000", 0);
970 return 1;
971 }
972 /* Make sure we only have %d or %u (or none) in the file name specified */
973 char *pcPercent = (char*)strchr(pszRecordFilenameTemplate, '%');
974 if (pcPercent != 0 && *(pcPercent + 1) != 'd' && *(pcPercent + 1) != 'u')
975 {
976 LogError("VBoxHeadless: ERROR: Only %%d and %%u are allowed in the recording file name.", -1);
977 return 1;
978 }
979 /* And no more than one % in the name */
980 if (pcPercent != 0 && strchr(pcPercent + 1, '%') != 0)
981 {
982 LogError("VBoxHeadless: ERROR: Only one format modifier is allowed in the recording file name.", -1);
983 return 1;
984 }
985 RTStrPrintf(&szRecordFilename[0], RTPATH_MAX, pszRecordFilenameTemplate, RTProcSelf());
986#endif /* defined VBOX_WITH_RECORDING */
987
988 if (!pcszNameOrUUID)
989 {
990 show_usage();
991 return 1;
992 }
993
994 HRESULT rc;
995 int irc;
996
997 rc = com::Initialize();
998#ifdef VBOX_WITH_XPCOM
999 if (rc == NS_ERROR_FILE_ACCESS_DENIED)
1000 {
1001 char szHome[RTPATH_MAX] = "";
1002 com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
1003 RTPrintf("Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome);
1004 return 1;
1005 }
1006#endif
1007 if (FAILED(rc))
1008 {
1009 RTPrintf("VBoxHeadless: ERROR: failed to initialize COM!\n");
1010 return 1;
1011 }
1012
1013 ComPtr<IVirtualBoxClient> pVirtualBoxClient;
1014 ComPtr<IVirtualBox> virtualBox;
1015 ComPtr<ISession> session;
1016 ComPtr<IMachine> machine;
1017 bool fSessionOpened = false;
1018 ComPtr<IEventListener> vboxClientListener;
1019 ComPtr<IEventListener> vboxListener;
1020 ComObjPtr<ConsoleEventListenerImpl> consoleListener;
1021
1022 do
1023 {
1024 rc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
1025 if (FAILED(rc))
1026 {
1027 RTPrintf("VBoxHeadless: ERROR: failed to create the VirtualBoxClient object!\n");
1028 com::ErrorInfo info;
1029 if (!info.isFullAvailable() && !info.isBasicAvailable())
1030 {
1031 com::GluePrintRCMessage(rc);
1032 RTPrintf("Most likely, the VirtualBox COM server is not running or failed to start.\n");
1033 }
1034 else
1035 GluePrintErrorInfo(info);
1036 break;
1037 }
1038
1039 rc = pVirtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam());
1040 if (FAILED(rc))
1041 {
1042 RTPrintf("Failed to get VirtualBox object (rc=%Rhrc)!\n", rc);
1043 break;
1044 }
1045 rc = pVirtualBoxClient->COMGETTER(Session)(session.asOutParam());
1046 if (FAILED(rc))
1047 {
1048 RTPrintf("Failed to get session object (rc=%Rhrc)!\n", rc);
1049 break;
1050 }
1051
1052 if (pcszSettingsPw)
1053 {
1054 CHECK_ERROR(virtualBox, SetSettingsSecret(Bstr(pcszSettingsPw).raw()));
1055 if (FAILED(rc))
1056 break;
1057 }
1058 else if (pcszSettingsPwFile)
1059 {
1060 int rcExit = settingsPasswordFile(virtualBox, pcszSettingsPwFile);
1061 if (rcExit != RTEXITCODE_SUCCESS)
1062 break;
1063 }
1064
1065 ComPtr<IMachine> m;
1066
1067 rc = virtualBox->FindMachine(Bstr(pcszNameOrUUID).raw(), m.asOutParam());
1068 if (FAILED(rc))
1069 {
1070 LogError("Invalid machine name or UUID!\n", rc);
1071 break;
1072 }
1073
1074 /* add VM password if required */
1075 if (pcszVmPassword && pcszVmPasswordId)
1076 {
1077 com::Utf8Str strPassword;
1078 if (!RTStrCmp(pcszVmPassword, "-"))
1079 {
1080 /* Get password from console. */
1081 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:");
1082 if (rcExit == RTEXITCODE_FAILURE)
1083 break;
1084 }
1085 else
1086 {
1087 RTEXITCODE rcExit = readPasswordFile(pcszVmPassword, &strPassword);
1088 if (rcExit != RTEXITCODE_SUCCESS)
1089 break;
1090 }
1091 CHECK_ERROR_BREAK(m, AddEncryptionPassword(Bstr(pcszVmPasswordId).raw(),
1092 Bstr(strPassword).raw()));
1093 }
1094 Bstr bstrVMId;
1095 rc = m->COMGETTER(Id)(bstrVMId.asOutParam());
1096 AssertComRC(rc);
1097 if (FAILED(rc))
1098 break;
1099 g_strVMUUID = bstrVMId;
1100
1101 Bstr bstrVMName;
1102 rc = m->COMGETTER(Name)(bstrVMName.asOutParam());
1103 AssertComRC(rc);
1104 if (FAILED(rc))
1105 break;
1106 g_strVMName = bstrVMName;
1107
1108 Log(("VBoxHeadless: Opening a session with machine (id={%s})...\n",
1109 g_strVMUUID.c_str()));
1110
1111 // set session name
1112 CHECK_ERROR_BREAK(session, COMSETTER(Name)(Bstr("headless").raw()));
1113 // open a session
1114 CHECK_ERROR_BREAK(m, LockMachine(session, LockType_VM));
1115 fSessionOpened = true;
1116
1117 /* get the console */
1118 ComPtr<IConsole> console;
1119 CHECK_ERROR_BREAK(session, COMGETTER(Console)(console.asOutParam()));
1120
1121 /* get the mutable machine */
1122 CHECK_ERROR_BREAK(console, COMGETTER(Machine)(machine.asOutParam()));
1123
1124 ComPtr<IDisplay> display;
1125 CHECK_ERROR_BREAK(console, COMGETTER(Display)(display.asOutParam()));
1126
1127#ifdef VBOX_WITH_RECORDING
1128 if (fRecordEnabled)
1129 {
1130 ComPtr<IRecordingSettings> recordingSettings;
1131 CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam()));
1132 CHECK_ERROR_BREAK(recordingSettings, COMSETTER(Enabled)(TRUE));
1133
1134 SafeIfaceArray <IRecordingScreenSettings> saRecordScreenScreens;
1135 CHECK_ERROR_BREAK(recordingSettings, COMGETTER(Screens)(ComSafeArrayAsOutParam(saRecordScreenScreens)));
1136
1137 /* Note: For now all screens have the same configuration. */
1138 for (size_t i = 0; i < saRecordScreenScreens.size(); ++i)
1139 {
1140 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(Enabled)(TRUE));
1141 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(Filename)(Bstr(szRecordFilename).raw()));
1142 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoWidth)(ulRecordVideoWidth));
1143 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoHeight)(ulRecordVideoHeight));
1144 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoRate)(ulRecordVideoRate));
1145 }
1146 }
1147#endif /* defined(VBOX_WITH_RECORDING) */
1148
1149#if 0
1150 /* get the machine debugger (isn't necessarily available) */
1151 ComPtr <IMachineDebugger> machineDebugger;
1152 console->COMGETTER(Debugger)(machineDebugger.asOutParam());
1153 if (machineDebugger)
1154 Log(("Machine debugger available!\n"));
1155#endif
1156
1157 /* initialize global references */
1158 gConsole = console;
1159 gEventQ = com::NativeEventQueue::getMainEventQueue();
1160
1161 /* VirtualBoxClient events registration. */
1162 {
1163 ComPtr<IEventSource> pES;
1164 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1165 ComObjPtr<VirtualBoxClientEventListenerImpl> listener;
1166 listener.createObject();
1167 listener->init(new VirtualBoxClientEventListener());
1168 vboxClientListener = listener;
1169 com::SafeArray<VBoxEventType_T> eventTypes;
1170 eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
1171 CHECK_ERROR(pES, RegisterListener(vboxClientListener, ComSafeArrayAsInParam(eventTypes), true));
1172 }
1173
1174 /* Console events registration. */
1175 {
1176 ComPtr<IEventSource> es;
1177 CHECK_ERROR(console, COMGETTER(EventSource)(es.asOutParam()));
1178 consoleListener.createObject();
1179 consoleListener->init(new ConsoleEventListener());
1180 com::SafeArray<VBoxEventType_T> eventTypes;
1181 eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
1182 eventTypes.push_back(VBoxEventType_OnStateChanged);
1183 eventTypes.push_back(VBoxEventType_OnVRDEServerInfoChanged);
1184 eventTypes.push_back(VBoxEventType_OnCanShowWindow);
1185 eventTypes.push_back(VBoxEventType_OnShowWindow);
1186 eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
1187 CHECK_ERROR(es, RegisterListener(consoleListener, ComSafeArrayAsInParam(eventTypes), true));
1188 }
1189
1190 /* Default is to use the VM setting for the VRDE server. */
1191 enum VRDEOption
1192 {
1193 VRDEOption_Config,
1194 VRDEOption_Off,
1195 VRDEOption_On
1196 };
1197 VRDEOption enmVRDEOption = VRDEOption_Config;
1198 BOOL fVRDEEnabled;
1199 ComPtr <IVRDEServer> vrdeServer;
1200 CHECK_ERROR_BREAK(machine, COMGETTER(VRDEServer)(vrdeServer.asOutParam()));
1201 CHECK_ERROR_BREAK(vrdeServer, COMGETTER(Enabled)(&fVRDEEnabled));
1202
1203 if (vrdeEnabled != NULL)
1204 {
1205 /* -vrde on|off|config */
1206 if (!strcmp(vrdeEnabled, "off") || !strcmp(vrdeEnabled, "disable"))
1207 enmVRDEOption = VRDEOption_Off;
1208 else if (!strcmp(vrdeEnabled, "on") || !strcmp(vrdeEnabled, "enable"))
1209 enmVRDEOption = VRDEOption_On;
1210 else if (strcmp(vrdeEnabled, "config"))
1211 {
1212 RTPrintf("-vrde requires an argument (on|off|config)\n");
1213 break;
1214 }
1215 }
1216
1217 Log(("VBoxHeadless: enmVRDE %d, fVRDEEnabled %d\n", enmVRDEOption, fVRDEEnabled));
1218
1219 if (enmVRDEOption != VRDEOption_Off)
1220 {
1221 /* Set other specified options. */
1222
1223 /* set VRDE port if requested by the user */
1224 if (vrdePort != NULL)
1225 {
1226 Bstr bstr = vrdePort;
1227 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.raw()));
1228 }
1229 /* set VRDE address if requested by the user */
1230 if (vrdeAddress != NULL)
1231 {
1232 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(Bstr("TCP/Address").raw(), Bstr(vrdeAddress).raw()));
1233 }
1234
1235 /* Set VRDE properties. */
1236 if (cVRDEProperties > 0)
1237 {
1238 for (unsigned i = 0; i < cVRDEProperties; i++)
1239 {
1240 /* Parse 'name=value' */
1241 char *pszProperty = RTStrDup(aVRDEProperties[i]);
1242 if (pszProperty)
1243 {
1244 char *pDelimiter = strchr(pszProperty, '=');
1245 if (pDelimiter)
1246 {
1247 *pDelimiter = '\0';
1248
1249 Bstr bstrName = pszProperty;
1250 Bstr bstrValue = &pDelimiter[1];
1251 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(bstrName.raw(), bstrValue.raw()));
1252 }
1253 else
1254 {
1255 RTPrintf("Error: Invalid VRDE property '%s'\n", aVRDEProperties[i]);
1256 RTStrFree(pszProperty);
1257 rc = E_INVALIDARG;
1258 break;
1259 }
1260 RTStrFree(pszProperty);
1261 }
1262 else
1263 {
1264 RTPrintf("Error: Failed to allocate memory for VRDE property '%s'\n", aVRDEProperties[i]);
1265 rc = E_OUTOFMEMORY;
1266 break;
1267 }
1268 }
1269 if (FAILED(rc))
1270 break;
1271 }
1272
1273 }
1274
1275 if (enmVRDEOption == VRDEOption_On)
1276 {
1277 /* enable VRDE server (only if currently disabled) */
1278 if (!fVRDEEnabled)
1279 {
1280 CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(TRUE));
1281 }
1282 }
1283 else if (enmVRDEOption == VRDEOption_Off)
1284 {
1285 /* disable VRDE server (only if currently enabled */
1286 if (fVRDEEnabled)
1287 {
1288 CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(FALSE));
1289 }
1290 }
1291
1292 /* Disable the host clipboard before powering up */
1293 console->COMSETTER(UseHostClipboard)(false);
1294
1295 Log(("VBoxHeadless: Powering up the machine...\n"));
1296
1297
1298 /**
1299 * @todo We should probably install handlers earlier so that
1300 * we can undo any temporary settings we do above in case of
1301 * an early signal and use RAII to ensure proper cleanup.
1302 */
1303#if !defined(RT_OS_WINDOWS)
1304 signal(SIGPIPE, SIG_IGN);
1305 signal(SIGTTOU, SIG_IGN);
1306
1307 struct sigaction sa;
1308 RT_ZERO(sa);
1309 sa.sa_handler = HandleSignal;
1310 sigaction(SIGHUP, &sa, NULL);
1311 sigaction(SIGINT, &sa, NULL);
1312 sigaction(SIGTERM, &sa, NULL);
1313 sigaction(SIGUSR1, &sa, NULL);
1314 /* Don't touch SIGUSR2 as IPRT could be using it for RTThreadPoke(). */
1315
1316#else /* RT_OS_WINDOWS */
1317 /*
1318 * Register windows console signal handler to react to Ctrl-C,
1319 * Ctrl-Break, Close, non-interactive session termination.
1320 */
1321 ::SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
1322#endif
1323
1324
1325 ComPtr <IProgress> progress;
1326 if (!fPaused)
1327 CHECK_ERROR_BREAK(console, PowerUp(progress.asOutParam()));
1328 else
1329 CHECK_ERROR_BREAK(console, PowerUpPaused(progress.asOutParam()));
1330
1331 rc = showProgress(progress);
1332 if (FAILED(rc))
1333 {
1334 com::ProgressErrorInfo info(progress);
1335 if (info.isBasicAvailable())
1336 {
1337 RTPrintf("Error: failed to start machine. Error message: %ls\n", info.getText().raw());
1338 }
1339 else
1340 {
1341 RTPrintf("Error: failed to start machine. No error message available!\n");
1342 }
1343 break;
1344 }
1345
1346#ifdef RT_OS_WINDOWS
1347 /*
1348 * Spawn windows message pump to monitor session events.
1349 */
1350 RTTHREAD hThrMsg;
1351 irc = RTThreadCreate(&hThrMsg,
1352 windowsMessageMonitor, NULL,
1353 0, /* :cbStack */
1354 RTTHREADTYPE_MSG_PUMP, 0,
1355 "MSG");
1356 if (RT_FAILURE(irc)) /* not fatal */
1357 LogRel(("VBoxHeadless: failed to start windows message monitor: %Rrc\n", irc));
1358#endif /* RT_OS_WINDOWS */
1359
1360
1361 /*
1362 * Pump vbox events forever
1363 */
1364 LogRel(("VBoxHeadless: starting event loop\n"));
1365 for (;;)
1366 {
1367 irc = gEventQ->processEventQueue(RT_INDEFINITE_WAIT);
1368
1369 /*
1370 * interruptEventQueueProcessing from another thread is
1371 * reported as VERR_INTERRUPTED, so check the flag first.
1372 */
1373 if (g_fTerminateFE)
1374 {
1375 LogRel(("VBoxHeadless: processEventQueue: %Rrc, termination requested\n", irc));
1376 break;
1377 }
1378
1379 if (RT_FAILURE(irc))
1380 {
1381 LogRel(("VBoxHeadless: processEventQueue: %Rrc\n", irc));
1382 RTMsgError("event loop: %Rrc", irc);
1383 break;
1384 }
1385 }
1386
1387 Log(("VBoxHeadless: event loop has terminated...\n"));
1388
1389#ifdef VBOX_WITH_RECORDING
1390 if (fRecordEnabled)
1391 {
1392 if (!machine.isNull())
1393 {
1394 ComPtr<IRecordingSettings> recordingSettings;
1395 CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam()));
1396 CHECK_ERROR_BREAK(recordingSettings, COMSETTER(Enabled)(FALSE));
1397 }
1398 }
1399#endif /* VBOX_WITH_RECORDING */
1400
1401 /* we don't have to disable VRDE here because we don't save the settings of the VM */
1402 }
1403 while (0);
1404
1405 /*
1406 * Get the machine state.
1407 */
1408 MachineState_T machineState = MachineState_Aborted;
1409 if (!machine.isNull())
1410 {
1411 rc = machine->COMGETTER(State)(&machineState);
1412 if (SUCCEEDED(rc))
1413 Log(("machine state = %RU32\n", machineState));
1414 else
1415 Log(("IMachine::getState: %Rhrc\n", rc));
1416 }
1417 else
1418 {
1419 Log(("machine == NULL\n"));
1420 }
1421
1422 /*
1423 * Turn off the VM if it's running
1424 */
1425 if ( gConsole
1426 && ( machineState == MachineState_Running
1427 || machineState == MachineState_Teleporting
1428 || machineState == MachineState_LiveSnapshotting
1429 /** @todo power off paused VMs too? */
1430 )
1431 )
1432 do
1433 {
1434 consoleListener->getWrapped()->ignorePowerOffEvents(true);
1435
1436 ComPtr<IProgress> pProgress;
1437 if (!machine.isNull())
1438 CHECK_ERROR_BREAK(machine, SaveState(pProgress.asOutParam()));
1439 else
1440 CHECK_ERROR_BREAK(gConsole, PowerDown(pProgress.asOutParam()));
1441
1442 rc = showProgress(pProgress);
1443 if (FAILED(rc))
1444 {
1445 com::ErrorInfo info;
1446 if (!info.isFullAvailable() && !info.isBasicAvailable())
1447 com::GluePrintRCMessage(rc);
1448 else
1449 com::GluePrintErrorInfo(info);
1450 break;
1451 }
1452 } while (0);
1453
1454 /* VirtualBox callback unregistration. */
1455 if (vboxListener)
1456 {
1457 ComPtr<IEventSource> es;
1458 CHECK_ERROR(virtualBox, COMGETTER(EventSource)(es.asOutParam()));
1459 if (!es.isNull())
1460 CHECK_ERROR(es, UnregisterListener(vboxListener));
1461 vboxListener.setNull();
1462 }
1463
1464 /* Console callback unregistration. */
1465 if (consoleListener)
1466 {
1467 ComPtr<IEventSource> es;
1468 CHECK_ERROR(gConsole, COMGETTER(EventSource)(es.asOutParam()));
1469 if (!es.isNull())
1470 CHECK_ERROR(es, UnregisterListener(consoleListener));
1471 consoleListener.setNull();
1472 }
1473
1474 /* VirtualBoxClient callback unregistration. */
1475 if (vboxClientListener)
1476 {
1477 ComPtr<IEventSource> pES;
1478 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1479 if (!pES.isNull())
1480 CHECK_ERROR(pES, UnregisterListener(vboxClientListener));
1481 vboxClientListener.setNull();
1482 }
1483
1484 /* No more access to the 'console' object, which will be uninitialized by the next session->Close call. */
1485 gConsole = NULL;
1486
1487 if (fSessionOpened)
1488 {
1489 /*
1490 * Close the session. This will also uninitialize the console and
1491 * unregister the callback we've registered before.
1492 */
1493 Log(("VBoxHeadless: Closing the session...\n"));
1494 session->UnlockMachine();
1495 }
1496
1497 /* Must be before com::Shutdown */
1498 session.setNull();
1499 virtualBox.setNull();
1500 pVirtualBoxClient.setNull();
1501 machine.setNull();
1502
1503 com::Shutdown();
1504
1505#ifdef RT_OS_WINDOWS
1506 /* tell the session monitor it can ack WM_ENDSESSION */
1507 if (g_hCanQuit != NIL_RTSEMEVENT)
1508 {
1509 RTSemEventSignal(g_hCanQuit);
1510 }
1511
1512 /* tell the session monitor to quit */
1513 if (g_hWindow != NULL)
1514 {
1515 ::PostMessage(g_hWindow, WM_QUIT, 0, 0);
1516 }
1517#endif
1518
1519 LogRel(("VBoxHeadless: exiting\n"));
1520 return FAILED(rc) ? 1 : 0;
1521}
1522
1523
1524#ifndef VBOX_WITH_HARDENING
1525/**
1526 * Main entry point.
1527 */
1528int main(int argc, char **argv, char **envp)
1529{
1530 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB);
1531 if (RT_SUCCESS(rc))
1532 return TrustedMain(argc, argv, envp);
1533 RTPrintf("VBoxHeadless: Runtime initialization failed: %Rrc - %Rrf\n", rc, rc);
1534 return RTEXITCODE_FAILURE;
1535}
1536#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