VirtualBox

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

Last change on this file was 107100, checked in by vboxsync, 2 months ago

FE/Qt,VBoxManage,VBoxHeadless: Refuse to run in WoW64 to prevent mixing up amd64 and arm64 builds in the out directory. jiraref:VBP-1466

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