VirtualBox

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

Last change on this file since 99739 was 99214, checked in by vboxsync, 20 months ago

VBoxHeadless: Simplified code around SetVRDEProperty.

  • 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 99214 2023-03-29 20:28:22Z 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 */
878HRESULT
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