VirtualBox

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

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

VBoxHeadless: scm fixes. bugref:9898

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