VirtualBox

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

Last change on this file since 102493 was 99775, checked in by vboxsync, 19 months ago

*: Mark functions as static if not used outside of a given compilation unit. Enables the compiler to optimize inlining, reduces the symbol tables, exposes unused functions and in some rare cases exposes mismtaches between function declarations and definitions, but most importantly reduces the number of parfait reports for the extern-function-no-forward-declaration category. This should not result in any functional changes, bugref:3409

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