VirtualBox

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

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

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