VirtualBox

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

Last change on this file was 103150, checked in by vboxsync, 6 months ago

Frontends: Some warning fixes about externally visible functions which should be static, bugref:3409

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

© 2023 Oracle
ContactPrivacy policyTerms of Use