VirtualBox

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

Last change on this file since 89038 was 89017, checked in by vboxsync, 4 years ago

FE/VBoxHeadless: Make that into release logging, so that it can be
observed in machine's VBox.log. bugref:8161.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 59.0 KB
Line 
1/* $Id: VBoxHeadless.cpp 89017 2021-05-12 15:03:52Z vboxsync $ */
2/** @file
3 * VBoxHeadless - The VirtualBox Headless frontend for running VMs on servers.
4 */
5
6/*
7 * Copyright (C) 2006-2020 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/path.h>
40#include <iprt/stream.h>
41#include <iprt/ldr.h>
42#include <iprt/getopt.h>
43#include <iprt/env.h>
44#include <VBox/err.h>
45#include <VBoxVideo.h>
46
47#ifdef VBOX_WITH_RECORDING
48# include <cstdlib>
49# include <cerrno>
50# include <iprt/process.h>
51#endif
52
53#ifdef RT_OS_DARWIN
54# include <iprt/asm.h>
55# include <dlfcn.h>
56# include <sys/mman.h>
57#endif
58
59//#define VBOX_WITH_SAVESTATE_ON_SIGNAL
60#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
61#include <signal.h>
62static void HandleSignal(int sig);
63#endif
64
65#include "PasswordInput.h"
66
67////////////////////////////////////////////////////////////////////////////////
68
69#define LogError(m,rc) \
70 do { \
71 Log(("VBoxHeadless: ERROR: " m " [rc=0x%08X]\n", rc)); \
72 RTPrintf("%s\n", m); \
73 } while (0)
74
75////////////////////////////////////////////////////////////////////////////////
76
77/* global weak references (for event handlers) */
78static IConsole *gConsole = NULL;
79static NativeEventQueue *gEventQ = NULL;
80
81/* flag whether frontend should terminate */
82static volatile bool g_fTerminateFE = false;
83
84////////////////////////////////////////////////////////////////////////////////
85
86/**
87 * Handler for VirtualBoxClient events.
88 */
89class VirtualBoxClientEventListener
90{
91public:
92 VirtualBoxClientEventListener()
93 {
94 }
95
96 virtual ~VirtualBoxClientEventListener()
97 {
98 }
99
100 HRESULT init()
101 {
102 return S_OK;
103 }
104
105 void uninit()
106 {
107 }
108
109 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
110 {
111 switch (aType)
112 {
113 case VBoxEventType_OnVBoxSVCAvailabilityChanged:
114 {
115 ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
116 Assert(pVSACEv);
117 BOOL fAvailable = FALSE;
118 pVSACEv->COMGETTER(Available)(&fAvailable);
119 if (!fAvailable)
120 {
121 LogRel(("VBoxHeadless: VBoxSVC became unavailable, exiting.\n"));
122 RTPrintf("VBoxSVC became unavailable, exiting.\n");
123 /* Terminate the VM as cleanly as possible given that VBoxSVC
124 * is no longer present. */
125 g_fTerminateFE = true;
126 gEventQ->interruptEventQueueProcessing();
127 }
128 break;
129 }
130 default:
131 AssertFailed();
132 }
133
134 return S_OK;
135 }
136
137private:
138};
139
140/**
141 * Handler for machine events.
142 */
143class ConsoleEventListener
144{
145public:
146 ConsoleEventListener() :
147 mLastVRDEPort(-1),
148 m_fIgnorePowerOffEvents(false),
149 m_fNoLoggedInUsers(true)
150 {
151 }
152
153 virtual ~ConsoleEventListener()
154 {
155 }
156
157 HRESULT init()
158 {
159 return S_OK;
160 }
161
162 void uninit()
163 {
164 }
165
166 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
167 {
168 switch (aType)
169 {
170 case VBoxEventType_OnMouseCapabilityChanged:
171 {
172
173 ComPtr<IMouseCapabilityChangedEvent> mccev = aEvent;
174 Assert(!mccev.isNull());
175
176 BOOL fSupportsAbsolute = false;
177 mccev->COMGETTER(SupportsAbsolute)(&fSupportsAbsolute);
178
179 /* Emit absolute mouse event to actually enable the host mouse cursor. */
180 if (fSupportsAbsolute && gConsole)
181 {
182 ComPtr<IMouse> mouse;
183 gConsole->COMGETTER(Mouse)(mouse.asOutParam());
184 if (mouse)
185 {
186 mouse->PutMouseEventAbsolute(-1, -1, 0, 0 /* Horizontal wheel */, 0);
187 }
188 }
189 break;
190 }
191 case VBoxEventType_OnStateChanged:
192 {
193 ComPtr<IStateChangedEvent> scev = aEvent;
194 Assert(scev);
195
196 MachineState_T machineState;
197 scev->COMGETTER(State)(&machineState);
198
199 /* Terminate any event wait operation if the machine has been
200 * PoweredDown/Saved/Aborted. */
201 if (machineState < MachineState_Running && !m_fIgnorePowerOffEvents)
202 {
203 g_fTerminateFE = true;
204 gEventQ->interruptEventQueueProcessing();
205 }
206
207 break;
208 }
209 case VBoxEventType_OnVRDEServerInfoChanged:
210 {
211 ComPtr<IVRDEServerInfoChangedEvent> rdicev = aEvent;
212 Assert(rdicev);
213
214 if (gConsole)
215 {
216 ComPtr<IVRDEServerInfo> info;
217 gConsole->COMGETTER(VRDEServerInfo)(info.asOutParam());
218 if (info)
219 {
220 LONG port;
221 info->COMGETTER(Port)(&port);
222 if (port != mLastVRDEPort)
223 {
224 if (port == -1)
225 RTPrintf("VRDE server is inactive.\n");
226 else if (port == 0)
227 RTPrintf("VRDE server failed to start.\n");
228 else
229 RTPrintf("VRDE server is listening on port %d.\n", port);
230
231 mLastVRDEPort = port;
232 }
233 }
234 }
235 break;
236 }
237 case VBoxEventType_OnCanShowWindow:
238 {
239 ComPtr<ICanShowWindowEvent> cswev = aEvent;
240 Assert(cswev);
241 cswev->AddVeto(NULL);
242 break;
243 }
244 case VBoxEventType_OnShowWindow:
245 {
246 ComPtr<IShowWindowEvent> swev = aEvent;
247 Assert(swev);
248 /* Ignore the event, WinId is either still zero or some other listener assigned it. */
249 NOREF(swev); /* swev->COMSETTER(WinId)(0); */
250 break;
251 }
252 case VBoxEventType_OnGuestPropertyChanged:
253 {
254 ComPtr<IGuestPropertyChangedEvent> pChangedEvent = aEvent;
255 Assert(pChangedEvent);
256
257 HRESULT hrc;
258
259 ComPtr <IMachine> pMachine;
260 if (gConsole)
261 {
262 hrc = gConsole->COMGETTER(Machine)(pMachine.asOutParam());
263 if (FAILED(hrc) || !pMachine)
264 hrc = VBOX_E_OBJECT_NOT_FOUND;
265 }
266 else
267 hrc = VBOX_E_INVALID_VM_STATE;
268
269 if (SUCCEEDED(hrc))
270 {
271 Bstr strKey;
272 hrc = pChangedEvent->COMGETTER(Name)(strKey.asOutParam());
273 AssertComRC(hrc);
274
275 Bstr strValue;
276 hrc = pChangedEvent->COMGETTER(Value)(strValue.asOutParam());
277 AssertComRC(hrc);
278
279 Utf8Str utf8Key = strKey;
280 Utf8Str utf8Value = strValue;
281 LogRelFlow(("Guest property \"%s\" has been changed to \"%s\"\n",
282 utf8Key.c_str(), utf8Value.c_str()));
283
284 if (utf8Key.equals("/VirtualBox/GuestInfo/OS/NoLoggedInUsers"))
285 {
286 LogRelFlow(("Guest indicates that there %s logged in users\n",
287 utf8Value.equals("true") ? "are no" : "are"));
288
289 /* Check if this is our machine and the "disconnect on logout feature" is enabled. */
290 BOOL fProcessDisconnectOnGuestLogout = FALSE;
291
292 /* Does the machine handle VRDP disconnects? */
293 Bstr strDiscon;
294 hrc = pMachine->GetExtraData(Bstr("VRDP/DisconnectOnGuestLogout").raw(),
295 strDiscon.asOutParam());
296 if (SUCCEEDED(hrc))
297 {
298 Utf8Str utf8Discon = strDiscon;
299 fProcessDisconnectOnGuestLogout = utf8Discon.equals("1")
300 ? TRUE : FALSE;
301 }
302
303 LogRelFlow(("VRDE: hrc=%Rhrc: Host %s disconnecting clients (current host state known: %s)\n",
304 hrc, fProcessDisconnectOnGuestLogout ? "will handle" : "does not handle",
305 m_fNoLoggedInUsers ? "No users logged in" : "Users logged in"));
306
307 if (fProcessDisconnectOnGuestLogout)
308 {
309 bool fDropConnection = false;
310 if (!m_fNoLoggedInUsers) /* Only if the property really changes. */
311 {
312 if ( utf8Value == "true"
313 /* Guest property got deleted due to reset,
314 * so it has no value anymore. */
315 || utf8Value.isEmpty())
316 {
317 m_fNoLoggedInUsers = true;
318 fDropConnection = true;
319 }
320 }
321 else if (utf8Value == "false")
322 m_fNoLoggedInUsers = false;
323 /* Guest property got deleted due to reset,
324 * take the shortcut without touching the m_fNoLoggedInUsers
325 * state. */
326 else if (utf8Value.isEmpty())
327 fDropConnection = true;
328
329 LogRelFlow(("VRDE: szNoLoggedInUsers=%s, m_fNoLoggedInUsers=%RTbool, fDropConnection=%RTbool\n",
330 utf8Value.c_str(), m_fNoLoggedInUsers, fDropConnection));
331
332 if (fDropConnection)
333 {
334 /* If there is a connection, drop it. */
335 ComPtr<IVRDEServerInfo> info;
336 hrc = gConsole->COMGETTER(VRDEServerInfo)(info.asOutParam());
337 if (SUCCEEDED(hrc) && info)
338 {
339 ULONG cClients = 0;
340 hrc = info->COMGETTER(NumberOfClients)(&cClients);
341
342 LogRelFlow(("VRDE: connected clients=%RU32\n", cClients));
343 if (SUCCEEDED(hrc) && cClients > 0)
344 {
345 ComPtr <IVRDEServer> vrdeServer;
346 hrc = pMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam());
347 if (SUCCEEDED(hrc) && vrdeServer)
348 {
349 LogRel(("VRDE: the guest user has logged out, disconnecting remote clients.\n"));
350 hrc = vrdeServer->COMSETTER(Enabled)(FALSE);
351 AssertComRC(hrc);
352 HRESULT hrc2 = vrdeServer->COMSETTER(Enabled)(TRUE);
353 if (SUCCEEDED(hrc))
354 hrc = hrc2;
355 }
356 }
357 }
358 }
359 }
360 }
361
362 if (FAILED(hrc))
363 LogRelFlow(("VRDE: returned error=%Rhrc\n", hrc));
364 }
365
366 break;
367 }
368
369 default:
370 AssertFailed();
371 }
372 return S_OK;
373 }
374
375 void ignorePowerOffEvents(bool fIgnore)
376 {
377 m_fIgnorePowerOffEvents = fIgnore;
378 }
379
380private:
381
382 long mLastVRDEPort;
383 bool m_fIgnorePowerOffEvents;
384 bool m_fNoLoggedInUsers;
385};
386
387typedef ListenerImpl<VirtualBoxClientEventListener> VirtualBoxClientEventListenerImpl;
388typedef ListenerImpl<ConsoleEventListener> ConsoleEventListenerImpl;
389
390VBOX_LISTENER_DECLARE(VirtualBoxClientEventListenerImpl)
391VBOX_LISTENER_DECLARE(ConsoleEventListenerImpl)
392
393#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
394static void
395HandleSignal(int sig)
396{
397 RT_NOREF(sig);
398 LogRel(("VBoxHeadless: received singal %d\n", sig));
399 g_fTerminateFE = true;
400}
401#endif /* VBOX_WITH_SAVESTATE_ON_SIGNAL */
402
403////////////////////////////////////////////////////////////////////////////////
404
405static void show_usage()
406{
407 RTPrintf("Usage:\n"
408 " -s, -startvm, --startvm <name|uuid> Start given VM (required argument)\n"
409 " -v, -vrde, --vrde on|off|config Enable or disable the VRDE server\n"
410 " or don't change the setting (default)\n"
411 " -e, -vrdeproperty, --vrdeproperty <name=[value]> Set a VRDE property:\n"
412 " \"TCP/Ports\" - comma-separated list of\n"
413 " ports the VRDE server can bind to; dash\n"
414 " between two port numbers specifies range\n"
415 " \"TCP/Address\" - interface IP the VRDE\n"
416 " server will bind to\n"
417 " --settingspw <pw> Specify the settings password\n"
418 " --settingspwfile <file> Specify a file containing the\n"
419 " settings password\n"
420 " -start-paused, --start-paused Start the VM in paused state\n"
421#ifdef VBOX_WITH_RECORDING
422 " -c, -record, --record Record the VM screen output to a file\n"
423 " -w, --videowidth Video frame width when recording\n"
424 " -h, --videoheight Video frame height when recording\n"
425 " -r, --videobitrate Recording bit rate when recording\n"
426 " -f, --filename File name when recording. The codec used\n"
427 " will be chosen based on file extension\n"
428#endif
429 "\n");
430}
431
432#ifdef VBOX_WITH_RECORDING
433/**
434 * Parse the environment for variables which can influence the VIDEOREC settings.
435 * purely for backwards compatibility.
436 * @param pulFrameWidth may be updated with a desired frame width
437 * @param pulFrameHeight may be updated with a desired frame height
438 * @param pulBitRate may be updated with a desired bit rate
439 * @param ppszFilename may be updated with a desired file name
440 */
441static void parse_environ(uint32_t *pulFrameWidth, uint32_t *pulFrameHeight,
442 uint32_t *pulBitRate, const char **ppszFilename)
443{
444 const char *pszEnvTemp;
445/** @todo r=bird: This isn't up to scratch. The life time of an RTEnvGet
446 * return value is only up to the next RTEnv*, *getenv, *putenv,
447 * setenv call in _any_ process in the system and the it has known and
448 * documented code page issues.
449 *
450 * Use RTEnvGetEx instead! */
451 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDWIDTH")) != 0)
452 {
453 errno = 0;
454 unsigned long ulFrameWidth = strtoul(pszEnvTemp, 0, 10);
455 if (errno != 0)
456 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDWIDTH environment variable", 0);
457 else
458 *pulFrameWidth = ulFrameWidth;
459 }
460 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDHEIGHT")) != 0)
461 {
462 errno = 0;
463 unsigned long ulFrameHeight = strtoul(pszEnvTemp, 0, 10);
464 if (errno != 0)
465 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDHEIGHT environment variable", 0);
466 else
467 *pulFrameHeight = ulFrameHeight;
468 }
469 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDBITRATE")) != 0)
470 {
471 errno = 0;
472 unsigned long ulBitRate = strtoul(pszEnvTemp, 0, 10);
473 if (errno != 0)
474 LogError("VBoxHeadless: ERROR: invalid VBOX_RECORDBITRATE environment variable", 0);
475 else
476 *pulBitRate = ulBitRate;
477 }
478 if ((pszEnvTemp = RTEnvGet("VBOX_RECORDFILE")) != 0)
479 *ppszFilename = pszEnvTemp;
480}
481#endif /* VBOX_WITH_RECORDING defined */
482
483#ifdef RT_OS_DARWIN
484
485# include <unistd.h>
486# include <stdio.h>
487# include <dlfcn.h>
488# include <iprt/formats/mach-o.h>
489
490/**
491 * Override this one to try hide the fact that we're setuid to root
492 * orginially.
493 */
494int issetugid_for_AppKit(void)
495{
496 Dl_info Info = {0};
497 char szMsg[512];
498 size_t cchMsg;
499 const void * uCaller = __builtin_return_address(0);
500 if (dladdr(uCaller, &Info))
501 cchMsg = snprintf(szMsg, sizeof(szMsg), "DEBUG: issetugid_for_AppKit was called by %p %s::%s+%p (via %p)\n",
502 uCaller, Info.dli_fname, Info.dli_sname, (void *)((uintptr_t)uCaller - (uintptr_t)Info.dli_saddr), __builtin_return_address(1));
503 else
504 cchMsg = snprintf(szMsg, sizeof(szMsg), "DEBUG: issetugid_for_AppKit was called by %p (via %p)\n", uCaller, __builtin_return_address(1));
505 write(2, szMsg, cchMsg);
506 return 0;
507}
508
509static bool patchExtSym(mach_header_64_t *pHdr, const char *pszSymbol, uintptr_t uNewValue)
510{
511 /*
512 * First do some basic header checks and the scan the load
513 * commands for the symbol table info.
514 */
515 AssertLogRelMsgReturn(pHdr->magic == (ARCH_BITS == 64 ? MH_MAGIC_64 : MH_MAGIC),
516 ("%p: magic=%#x\n", pHdr, pHdr->magic), false);
517 uint32_t const cCmds = pHdr->ncmds;
518 uint32_t const cbCmds = pHdr->sizeofcmds;
519 AssertLogRelMsgReturn(cCmds < 16384 && cbCmds < _2M, ("%p: ncmds=%u sizeofcmds=%u\n", pHdr, cCmds, cbCmds), false);
520
521 /*
522 * First command pass: Locate the symbol table and dynamic symbol table info
523 * commands, also calc the slide (load addr - link addr).
524 */
525 dysymtab_command_t const *pDySymTab = NULL;
526 symtab_command_t const *pSymTab = NULL;
527 segment_command_64_t const *pFirstSeg = NULL;
528 uintptr_t offSlide = 0;
529 uint32_t offCmd = 0;
530 for (uint32_t iCmd = 0; iCmd < cCmds; iCmd++)
531 {
532 AssertLogRelMsgReturn(offCmd + sizeof(load_command_t) <= cbCmds,
533 ("%p: iCmd=%u offCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCmds), false);
534 load_command_t const * const pCmd = (load_command_t const *)((uintptr_t)(pHdr + 1) + offCmd);
535 uint32_t const cbCurCmd = pCmd->cmdsize;
536 AssertLogRelMsgReturn(offCmd + cbCurCmd <= cbCmds && cbCurCmd <= cbCmds,
537 ("%p: iCmd=%u offCmd=%#x cbCurCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCurCmd, cbCmds), false);
538 offCmd += cbCurCmd;
539
540 if (pCmd->cmd == LC_SYMTAB)
541 {
542 AssertLogRelMsgReturn(!pSymTab, ("%p: pSymTab=%p pCmd=%p\n", pHdr, pSymTab, pCmd), false);
543 pSymTab = (symtab_command_t const *)pCmd;
544 AssertLogRelMsgReturn(cbCurCmd == sizeof(*pSymTab), ("%p: pSymTab=%p cbCurCmd=%#x\n", pHdr, pCmd, cbCurCmd), false);
545
546 }
547 else if (pCmd->cmd == LC_DYSYMTAB)
548 {
549 AssertLogRelMsgReturn(!pDySymTab, ("%p: pDySymTab=%p pCmd=%p\n", pHdr, pDySymTab, pCmd), false);
550 pDySymTab = (dysymtab_command_t const *)pCmd;
551 AssertLogRelMsgReturn(cbCurCmd == sizeof(*pDySymTab), ("%p: pDySymTab=%p cbCurCmd=%#x\n", pHdr, pCmd, cbCurCmd),
552 false);
553 }
554 else if (pCmd->cmd == LC_SEGMENT_64 && !pFirstSeg) /* ASSUMES the first seg is the one with the header and stuff. */
555 {
556 /* Note! the fileoff and vmaddr seems to be modified. */
557 pFirstSeg = (segment_command_64_t const *)pCmd;
558 AssertLogRelMsgReturn(cbCurCmd >= sizeof(*pFirstSeg), ("%p: iCmd=%u cbCurCmd=%#x\n", pHdr, iCmd, cbCurCmd), false);
559 AssertLogRelMsgReturn(/*pFirstSeg->fileoff == 0 && */ pFirstSeg->vmsize >= sizeof(*pHdr) + cbCmds,
560 ("%p: iCmd=%u fileoff=%llx vmsize=%#llx cbCmds=%#x name=%.16s\n",
561 pHdr, iCmd, pFirstSeg->fileoff, pFirstSeg->vmsize, cbCmds, pFirstSeg->segname), false);
562 offSlide = (uintptr_t)pHdr - pFirstSeg->vmaddr;
563 }
564 }
565 AssertLogRelMsgReturn(pSymTab, ("%p: no LC_SYMTAB\n", pHdr), false);
566 AssertLogRelMsgReturn(pDySymTab, ("%p: no LC_DYSYMTAB\n", pHdr), false);
567 AssertLogRelMsgReturn(pFirstSeg, ("%p: no LC_SEGMENT_64\n", pHdr), false);
568
569 /*
570 * Second command pass: Locate the memory locations of the symbol table, string
571 * table and the indirect symbol table by checking LC_SEGMENT_xx.
572 */
573 macho_nlist_64_t const *paSymbols = NULL;
574 uint32_t const offSymbols = pSymTab->symoff;
575 uint32_t const cSymbols = pSymTab->nsyms;
576 AssertLogRelMsgReturn(cSymbols > 0 && offSymbols >= sizeof(pHdr) + cbCmds,
577 ("%p: cSymbols=%#x offSymbols=%#x\n", pHdr, cSymbols, offSymbols), false);
578
579 const char *pchStrTab = NULL;
580 uint32_t const offStrTab = pSymTab->stroff;
581 uint32_t const cbStrTab = pSymTab->strsize;
582 AssertLogRelMsgReturn(cbStrTab > 0 && offStrTab >= sizeof(pHdr) + cbCmds,
583 ("%p: cbStrTab=%#x offStrTab=%#x\n", pHdr, cbStrTab, offStrTab), false);
584
585 uint32_t const *paidxIndirSymbols = NULL;
586 uint32_t const offIndirSymbols = pDySymTab->indirectsymboff;
587 uint32_t const cIndirSymbols = pDySymTab->nindirectsymb;
588 AssertLogRelMsgReturn(cIndirSymbols > 0 && offIndirSymbols >= sizeof(pHdr) + cbCmds,
589 ("%p: cIndirSymbols=%#x offIndirSymbols=%#x\n", pHdr, cIndirSymbols, offIndirSymbols), false);
590
591 offCmd = 0;
592 for (uint32_t iCmd = 0; iCmd < cCmds; iCmd++)
593 {
594 load_command_t const * const pCmd = (load_command_t const *)((uintptr_t)(pHdr + 1) + offCmd);
595 uint32_t const cbCurCmd = pCmd->cmdsize;
596 AssertLogRelMsgReturn(offCmd + cbCurCmd <= cbCmds && cbCurCmd <= cbCmds,
597 ("%p: iCmd=%u offCmd=%#x cbCurCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCurCmd, cbCmds), false);
598 offCmd += cbCurCmd;
599
600 if (pCmd->cmd == LC_SEGMENT_64)
601 {
602 segment_command_64_t const *pSeg = (segment_command_64_t const *)pCmd;
603 AssertLogRelMsgReturn(cbCurCmd >= sizeof(*pSeg), ("%p: iCmd=%u cbCurCmd=%#x\n", pHdr, iCmd, cbCurCmd), false);
604 uintptr_t const uPtrSeg = pSeg->vmaddr + offSlide;
605 uint64_t const cbSeg = pSeg->vmsize;
606 uint64_t const offFile = pSeg->fileoff;
607
608 uint64_t offSeg = offSymbols - offFile;
609 if (offSeg < cbSeg)
610 {
611 AssertLogRelMsgReturn(!paSymbols, ("%p: paSymbols=%p uPtrSeg=%p off=%#llx\n", pHdr, paSymbols, uPtrSeg, offSeg),
612 false);
613 AssertLogRelMsgReturn(offSeg + cSymbols * sizeof(paSymbols[0]) <= cbSeg,
614 ("%p: offSeg=%#llx cSymbols=%#x cbSeg=%llx\n", pHdr, offSeg, cSymbols, cbSeg), false);
615 paSymbols = (macho_nlist_64_t const *)(uPtrSeg + offSeg);
616 }
617
618 offSeg = offStrTab - offFile;
619 if (offSeg < cbSeg)
620 {
621 AssertLogRelMsgReturn(!pchStrTab, ("%p: paSymbols=%p uPtrSeg=%p\n", pHdr, pchStrTab, uPtrSeg), false);
622 AssertLogRelMsgReturn(offSeg + cbStrTab <= cbSeg,
623 ("%p: offSeg=%#llx cbStrTab=%#x cbSeg=%llx\n", pHdr, offSeg, cbStrTab, cbSeg), false);
624 pchStrTab = (const char *)(uPtrSeg + offSeg);
625 }
626
627 offSeg = offIndirSymbols - offFile;
628 if (offSeg < cbSeg)
629 {
630 AssertLogRelMsgReturn(!paidxIndirSymbols,
631 ("%p: paidxIndirSymbols=%p uPtrSeg=%p\n", pHdr, paidxIndirSymbols, uPtrSeg), false);
632 AssertLogRelMsgReturn(offSeg + cIndirSymbols * sizeof(paidxIndirSymbols[0]) <= cbSeg,
633 ("%p: offSeg=%#llx cIndirSymbols=%#x cbSeg=%llx\n", pHdr, offSeg, cIndirSymbols, cbSeg),
634 false);
635 paidxIndirSymbols = (uint32_t const *)(uPtrSeg + offSeg);
636 }
637 }
638 }
639
640 AssertLogRelMsgReturn(paSymbols, ("%p: offSymbols=%#x\n", pHdr, offSymbols), false);
641 AssertLogRelMsgReturn(pchStrTab, ("%p: offStrTab=%#x\n", pHdr, offStrTab), false);
642 AssertLogRelMsgReturn(paidxIndirSymbols, ("%p: offIndirSymbols=%#x\n", pHdr, offIndirSymbols), false);
643
644 /*
645 * Third command pass: Process sections of types S_NON_LAZY_SYMBOL_POINTERS
646 * and S_LAZY_SYMBOL_POINTERS
647 */
648 bool fFound = false;
649 offCmd = 0;
650 for (uint32_t iCmd = 0; iCmd < cCmds; iCmd++)
651 {
652 load_command_t const * const pCmd = (load_command_t const *)((uintptr_t)(pHdr + 1) + offCmd);
653 uint32_t const cbCurCmd = pCmd->cmdsize;
654 AssertLogRelMsgReturn(offCmd + cbCurCmd <= cbCmds && cbCurCmd <= cbCmds,
655 ("%p: iCmd=%u offCmd=%#x cbCurCmd=%#x cbCmds=%#x\n", pHdr, iCmd, offCmd, cbCurCmd, cbCmds), false);
656 offCmd += cbCurCmd;
657 if (pCmd->cmd == LC_SEGMENT_64)
658 {
659 segment_command_64_t const *pSeg = (segment_command_64_t const *)pCmd;
660 AssertLogRelMsgReturn(cbCurCmd >= sizeof(*pSeg), ("%p: iCmd=%u cbCurCmd=%#x\n", pHdr, iCmd, cbCurCmd), false);
661 uint64_t const uSegAddr = pSeg->vmaddr;
662 uint64_t const cbSeg = pSeg->vmsize;
663
664 uint32_t const cSections = pSeg->nsects;
665 section_64_t const * const paSections = (section_64_t const *)(pSeg + 1);
666 AssertLogRelMsgReturn(cSections < _256K && sizeof(*pSeg) + cSections * sizeof(paSections[0]) <= cbCurCmd,
667 ("%p: iCmd=%u cSections=%#x cbCurCmd=%#x\n", pHdr, iCmd, cSections, cbCurCmd), false);
668 for (uint32_t iSection = 0; iSection < cSections; iSection++)
669 {
670 if ( paSections[iSection].flags == S_NON_LAZY_SYMBOL_POINTERS
671 || paSections[iSection].flags == S_LAZY_SYMBOL_POINTERS)
672 {
673 uint32_t const idxIndirBase = paSections[iSection].reserved1;
674 uint32_t const cEntries = paSections[iSection].size / sizeof(uintptr_t);
675 AssertLogRelMsgReturn(idxIndirBase <= cIndirSymbols && idxIndirBase + cEntries <= cIndirSymbols,
676 ("%p: idxIndirBase=%#x cEntries=%#x cIndirSymbols=%#x\n",
677 pHdr, idxIndirBase, cEntries, cIndirSymbols), false);
678 uint64_t const uSecAddr = paSections[iSection].addr;
679 uint64_t const offInSeg = uSecAddr - uSegAddr;
680 AssertLogRelMsgReturn(offInSeg < cbSeg && offInSeg + cEntries * sizeof(uintptr_t) <= cbSeg,
681 ("%p: offInSeg=%#llx cEntries=%#x cbSeg=%#llx\n", pHdr, offInSeg, cEntries, cbSeg),
682 false);
683 uintptr_t *pauPtrs = (uintptr_t *)(uSecAddr + offSlide);
684 for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++)
685 {
686 uint32_t const idxSym = paidxIndirSymbols[idxIndirBase + iEntry];
687 if (idxSym < cSymbols)
688 {
689 macho_nlist_64_t const * const pSym = &paSymbols[idxSym];
690 const char * const pszName = pSym->n_un.n_strx < cbStrTab
691 ? &pchStrTab[pSym->n_un.n_strx] : "!invalid symtab offset!";
692 if (strcmp(pszName, pszSymbol) == 0)
693 {
694 pauPtrs[iEntry] = uNewValue;
695 fFound = true;
696 break;
697 }
698 }
699 else
700 AssertMsg(idxSym == INDIRECT_SYMBOL_LOCAL || idxSym == INDIRECT_SYMBOL_ABS, ("%#x\n", idxSym));
701 }
702 }
703 }
704 }
705 }
706 AssertLogRel(fFound);
707 return fFound;
708}
709
710/**
711 * Mac OS X: Really ugly hack to bypass a set-uid check in AppKit.
712 *
713 * This will modify the issetugid() function to always return zero. This must
714 * be done _before_ AppKit is initialized, otherwise it will refuse to play ball
715 * with us as it distrusts set-uid processes since Snow Leopard. We, however,
716 * have carefully dropped all root privileges at this point and there should be
717 * no reason for any security concern here.
718 */
719static void hideSetUidRootFromAppKit()
720{
721 void *pvAddr;
722 /* Find issetguid() and make it always return 0 by modifying the code: */
723# if 0
724 pvAddr = dlsym(RTLD_DEFAULT, "issetugid");
725 int rc = mprotect((void *)((uintptr_t)pvAddr & ~(uintptr_t)0xfff), 0x2000, PROT_WRITE | PROT_READ | PROT_EXEC);
726 if (!rc)
727 ASMAtomicWriteU32((volatile uint32_t *)pvAddr, 0xccc3c031); /* xor eax, eax; ret; int3 */
728 else
729# endif
730 {
731 /* Failing that, find AppKit and patch its import table: */
732 void *pvAppKit = dlopen("/System/Library/Frameworks/AppKit.framework/AppKit", RTLD_NOLOAD);
733 pvAddr = dlsym(pvAppKit, "NSApplicationMain");
734 Dl_info Info = {0};
735 if ( dladdr(pvAddr, &Info)
736 && Info.dli_fbase != NULL)
737 {
738 if (!patchExtSym((mach_header_64_t *)Info.dli_fbase, "_issetugid", (uintptr_t)&issetugid_for_AppKit))
739 write(2, RT_STR_TUPLE("WARNING: Failed to patch issetugid in AppKit! (patchExtSym)\n"));
740# ifdef DEBUG
741 else
742 write(2, RT_STR_TUPLE("INFO: Successfully patched _issetugid import for AppKit!\n"));
743# endif
744 }
745 else
746 write(2, RT_STR_TUPLE("WARNING: Failed to patch issetugid in AppKit! (dladdr)\n"));
747 }
748
749}
750
751#endif /* RT_OS_DARWIN */
752
753
754/*
755 * Simplified version of showProgress() borrowed from VBoxManage.
756 */
757HRESULT
758showProgress(const ComPtr<IProgress> &progress)
759{
760 BOOL fCompleted = FALSE;
761 ULONG ulLastPercent = 0;
762 ULONG ulCurrentPercent = 0;
763 HRESULT hrc;
764
765 com::Bstr bstrDescription;
766 hrc = progress->COMGETTER(Description(bstrDescription.asOutParam()));
767 if (FAILED(hrc))
768 {
769 RTStrmPrintf(g_pStdErr, "Failed to get progress description: %Rhrc\n", hrc);
770 return hrc;
771 }
772
773 RTStrmPrintf(g_pStdErr, "%ls: ", bstrDescription.raw());
774 RTStrmFlush(g_pStdErr);
775
776 hrc = progress->COMGETTER(Completed(&fCompleted));
777 while (SUCCEEDED(hrc))
778 {
779 progress->COMGETTER(Percent(&ulCurrentPercent));
780
781 /* did we cross a 10% mark? */
782 if (ulCurrentPercent / 10 > ulLastPercent / 10)
783 {
784 /* make sure to also print out missed steps */
785 for (ULONG curVal = (ulLastPercent / 10) * 10 + 10; curVal <= (ulCurrentPercent / 10) * 10; curVal += 10)
786 {
787 if (curVal < 100)
788 {
789 RTStrmPrintf(g_pStdErr, "%u%%...", curVal);
790 RTStrmFlush(g_pStdErr);
791 }
792 }
793 ulLastPercent = (ulCurrentPercent / 10) * 10;
794 }
795
796 if (fCompleted)
797 break;
798
799 gEventQ->processEventQueue(500);
800 hrc = progress->COMGETTER(Completed(&fCompleted));
801 }
802
803 /* complete the line. */
804 LONG iRc = E_FAIL;
805 hrc = progress->COMGETTER(ResultCode)(&iRc);
806 if (SUCCEEDED(hrc))
807 {
808 if (SUCCEEDED(iRc))
809 RTStrmPrintf(g_pStdErr, "100%%\n");
810#if 0
811 else if (g_fCanceled)
812 RTStrmPrintf(g_pStdErr, "CANCELED\n");
813#endif
814 else
815 {
816 RTStrmPrintf(g_pStdErr, "\n");
817 RTStrmPrintf(g_pStdErr, "Operation failed: %Rhrc\n", iRc);
818 }
819 hrc = iRc;
820 }
821 else
822 {
823 RTStrmPrintf(g_pStdErr, "\n");
824 RTStrmPrintf(g_pStdErr, "Failed to obtain operation result: %Rhrc\n", hrc);
825 }
826 RTStrmFlush(g_pStdErr);
827 return hrc;
828}
829
830
831/**
832 * Entry point.
833 */
834extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
835{
836 RT_NOREF(envp);
837 const char *vrdePort = NULL;
838 const char *vrdeAddress = NULL;
839 const char *vrdeEnabled = NULL;
840 unsigned cVRDEProperties = 0;
841 const char *aVRDEProperties[16];
842 unsigned fRawR0 = ~0U;
843 unsigned fRawR3 = ~0U;
844 unsigned fPATM = ~0U;
845 unsigned fCSAM = ~0U;
846 unsigned fPaused = 0;
847#ifdef VBOX_WITH_RECORDING
848 bool fRecordEnabled = false;
849 uint32_t ulRecordVideoWidth = 800;
850 uint32_t ulRecordVideoHeight = 600;
851 uint32_t ulRecordVideoRate = 300000;
852 char szRecordFilename[RTPATH_MAX];
853 const char *pszRecordFilenameTemplate = "VBox-%d.webm"; /* .webm container by default. */
854#endif /* VBOX_WITH_RECORDING */
855#ifdef RT_OS_WINDOWS
856 ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
857#endif
858
859 LogFlow(("VBoxHeadless STARTED.\n"));
860 RTPrintf(VBOX_PRODUCT " Headless Interface " VBOX_VERSION_STRING "\n"
861 "(C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
862 "All rights reserved.\n\n");
863
864#ifdef VBOX_WITH_RECORDING
865 /* Parse the environment */
866 parse_environ(&ulRecordVideoWidth, &ulRecordVideoHeight, &ulRecordVideoRate, &pszRecordFilenameTemplate);
867#endif
868
869 enum eHeadlessOptions
870 {
871 OPT_RAW_R0 = 0x100,
872 OPT_NO_RAW_R0,
873 OPT_RAW_R3,
874 OPT_NO_RAW_R3,
875 OPT_PATM,
876 OPT_NO_PATM,
877 OPT_CSAM,
878 OPT_NO_CSAM,
879 OPT_SETTINGSPW,
880 OPT_SETTINGSPW_FILE,
881 OPT_COMMENT,
882 OPT_PAUSED
883 };
884
885 static const RTGETOPTDEF s_aOptions[] =
886 {
887 { "-startvm", 's', RTGETOPT_REQ_STRING },
888 { "--startvm", 's', RTGETOPT_REQ_STRING },
889 { "-vrdpport", 'p', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
890 { "--vrdpport", 'p', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
891 { "-vrdpaddress", 'a', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
892 { "--vrdpaddress", 'a', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
893 { "-vrdp", 'v', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
894 { "--vrdp", 'v', RTGETOPT_REQ_STRING }, /* VRDE: deprecated. */
895 { "-vrde", 'v', RTGETOPT_REQ_STRING },
896 { "--vrde", 'v', RTGETOPT_REQ_STRING },
897 { "-vrdeproperty", 'e', RTGETOPT_REQ_STRING },
898 { "--vrdeproperty", 'e', RTGETOPT_REQ_STRING },
899 { "-rawr0", OPT_RAW_R0, 0 },
900 { "--rawr0", OPT_RAW_R0, 0 },
901 { "-norawr0", OPT_NO_RAW_R0, 0 },
902 { "--norawr0", OPT_NO_RAW_R0, 0 },
903 { "-rawr3", OPT_RAW_R3, 0 },
904 { "--rawr3", OPT_RAW_R3, 0 },
905 { "-norawr3", OPT_NO_RAW_R3, 0 },
906 { "--norawr3", OPT_NO_RAW_R3, 0 },
907 { "-patm", OPT_PATM, 0 },
908 { "--patm", OPT_PATM, 0 },
909 { "-nopatm", OPT_NO_PATM, 0 },
910 { "--nopatm", OPT_NO_PATM, 0 },
911 { "-csam", OPT_CSAM, 0 },
912 { "--csam", OPT_CSAM, 0 },
913 { "-nocsam", OPT_NO_CSAM, 0 },
914 { "--nocsam", OPT_NO_CSAM, 0 },
915 { "--settingspw", OPT_SETTINGSPW, RTGETOPT_REQ_STRING },
916 { "--settingspwfile", OPT_SETTINGSPW_FILE, RTGETOPT_REQ_STRING },
917#ifdef VBOX_WITH_RECORDING
918 { "-record", 'c', 0 },
919 { "--record", 'c', 0 },
920 { "--videowidth", 'w', RTGETOPT_REQ_UINT32 },
921 { "--videoheight", 'h', RTGETOPT_REQ_UINT32 }, /* great choice of short option! */
922 { "--videorate", 'r', RTGETOPT_REQ_UINT32 },
923 { "--filename", 'f', RTGETOPT_REQ_STRING },
924#endif /* VBOX_WITH_RECORDING defined */
925 { "-comment", OPT_COMMENT, RTGETOPT_REQ_STRING },
926 { "--comment", OPT_COMMENT, RTGETOPT_REQ_STRING },
927 { "-start-paused", OPT_PAUSED, 0 },
928 { "--start-paused", OPT_PAUSED, 0 }
929 };
930
931 const char *pcszNameOrUUID = NULL;
932
933#ifdef RT_OS_DARWIN
934 hideSetUidRootFromAppKit();
935#endif
936
937 // parse the command line
938 int ch;
939 const char *pcszSettingsPw = NULL;
940 const char *pcszSettingsPwFile = NULL;
941 RTGETOPTUNION ValueUnion;
942 RTGETOPTSTATE GetState;
943 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
944 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
945 {
946 switch(ch)
947 {
948 case 's':
949 pcszNameOrUUID = ValueUnion.psz;
950 break;
951 case 'p':
952 RTPrintf("Warning: '-p' or '-vrdpport' are deprecated. Use '-e \"TCP/Ports=%s\"'\n", ValueUnion.psz);
953 vrdePort = ValueUnion.psz;
954 break;
955 case 'a':
956 RTPrintf("Warning: '-a' or '-vrdpaddress' are deprecated. Use '-e \"TCP/Address=%s\"'\n", ValueUnion.psz);
957 vrdeAddress = ValueUnion.psz;
958 break;
959 case 'v':
960 vrdeEnabled = ValueUnion.psz;
961 break;
962 case 'e':
963 if (cVRDEProperties < RT_ELEMENTS(aVRDEProperties))
964 aVRDEProperties[cVRDEProperties++] = ValueUnion.psz;
965 else
966 RTPrintf("Warning: too many VRDE properties. Ignored: '%s'\n", ValueUnion.psz);
967 break;
968 case OPT_RAW_R0:
969 fRawR0 = true;
970 break;
971 case OPT_NO_RAW_R0:
972 fRawR0 = false;
973 break;
974 case OPT_RAW_R3:
975 fRawR3 = true;
976 break;
977 case OPT_NO_RAW_R3:
978 fRawR3 = false;
979 break;
980 case OPT_PATM:
981 fPATM = true;
982 break;
983 case OPT_NO_PATM:
984 fPATM = false;
985 break;
986 case OPT_CSAM:
987 fCSAM = true;
988 break;
989 case OPT_NO_CSAM:
990 fCSAM = false;
991 break;
992 case OPT_SETTINGSPW:
993 pcszSettingsPw = ValueUnion.psz;
994 break;
995 case OPT_SETTINGSPW_FILE:
996 pcszSettingsPwFile = ValueUnion.psz;
997 break;
998 case OPT_PAUSED:
999 fPaused = true;
1000 break;
1001#ifdef VBOX_WITH_RECORDING
1002 case 'c':
1003 fRecordEnabled = true;
1004 break;
1005 case 'w':
1006 ulRecordVideoWidth = ValueUnion.u32;
1007 break;
1008 case 'r':
1009 ulRecordVideoRate = ValueUnion.u32;
1010 break;
1011 case 'f':
1012 pszRecordFilenameTemplate = ValueUnion.psz;
1013 break;
1014#endif /* VBOX_WITH_RECORDING defined */
1015 case 'h':
1016#ifdef VBOX_WITH_RECORDING
1017 if ((GetState.pDef->fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
1018 {
1019 ulRecordVideoHeight = ValueUnion.u32;
1020 break;
1021 }
1022#endif
1023 show_usage();
1024 return 0;
1025 case OPT_COMMENT:
1026 /* nothing to do */
1027 break;
1028 case 'V':
1029 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
1030 return 0;
1031 default:
1032 ch = RTGetOptPrintError(ch, &ValueUnion);
1033 show_usage();
1034 return ch;
1035 }
1036 }
1037
1038#ifdef VBOX_WITH_RECORDING
1039 if (ulRecordVideoWidth < 512 || ulRecordVideoWidth > 2048 || ulRecordVideoWidth % 2)
1040 {
1041 LogError("VBoxHeadless: ERROR: please specify an even video frame width between 512 and 2048", 0);
1042 return 1;
1043 }
1044 if (ulRecordVideoHeight < 384 || ulRecordVideoHeight > 1536 || ulRecordVideoHeight % 2)
1045 {
1046 LogError("VBoxHeadless: ERROR: please specify an even video frame height between 384 and 1536", 0);
1047 return 1;
1048 }
1049 if (ulRecordVideoRate < 300000 || ulRecordVideoRate > 1000000)
1050 {
1051 LogError("VBoxHeadless: ERROR: please specify an even video bitrate between 300000 and 1000000", 0);
1052 return 1;
1053 }
1054 /* Make sure we only have %d or %u (or none) in the file name specified */
1055 char *pcPercent = (char*)strchr(pszRecordFilenameTemplate, '%');
1056 if (pcPercent != 0 && *(pcPercent + 1) != 'd' && *(pcPercent + 1) != 'u')
1057 {
1058 LogError("VBoxHeadless: ERROR: Only %%d and %%u are allowed in the recording file name.", -1);
1059 return 1;
1060 }
1061 /* And no more than one % in the name */
1062 if (pcPercent != 0 && strchr(pcPercent + 1, '%') != 0)
1063 {
1064 LogError("VBoxHeadless: ERROR: Only one format modifier is allowed in the recording file name.", -1);
1065 return 1;
1066 }
1067 RTStrPrintf(&szRecordFilename[0], RTPATH_MAX, pszRecordFilenameTemplate, RTProcSelf());
1068#endif /* defined VBOX_WITH_RECORDING */
1069
1070 if (!pcszNameOrUUID)
1071 {
1072 show_usage();
1073 return 1;
1074 }
1075
1076 HRESULT rc;
1077
1078 rc = com::Initialize();
1079#ifdef VBOX_WITH_XPCOM
1080 if (rc == NS_ERROR_FILE_ACCESS_DENIED)
1081 {
1082 char szHome[RTPATH_MAX] = "";
1083 com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
1084 RTPrintf("Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome);
1085 return 1;
1086 }
1087#endif
1088 if (FAILED(rc))
1089 {
1090 RTPrintf("VBoxHeadless: ERROR: failed to initialize COM!\n");
1091 return 1;
1092 }
1093
1094 ComPtr<IVirtualBoxClient> pVirtualBoxClient;
1095 ComPtr<IVirtualBox> virtualBox;
1096 ComPtr<ISession> session;
1097 ComPtr<IMachine> machine;
1098 bool fSessionOpened = false;
1099 ComPtr<IEventListener> vboxClientListener;
1100 ComPtr<IEventListener> vboxListener;
1101 ComObjPtr<ConsoleEventListenerImpl> consoleListener;
1102
1103 do
1104 {
1105 rc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
1106 if (FAILED(rc))
1107 {
1108 RTPrintf("VBoxHeadless: ERROR: failed to create the VirtualBoxClient object!\n");
1109 com::ErrorInfo info;
1110 if (!info.isFullAvailable() && !info.isBasicAvailable())
1111 {
1112 com::GluePrintRCMessage(rc);
1113 RTPrintf("Most likely, the VirtualBox COM server is not running or failed to start.\n");
1114 }
1115 else
1116 GluePrintErrorInfo(info);
1117 break;
1118 }
1119
1120 rc = pVirtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam());
1121 if (FAILED(rc))
1122 {
1123 RTPrintf("Failed to get VirtualBox object (rc=%Rhrc)!\n", rc);
1124 break;
1125 }
1126 rc = pVirtualBoxClient->COMGETTER(Session)(session.asOutParam());
1127 if (FAILED(rc))
1128 {
1129 RTPrintf("Failed to get session object (rc=%Rhrc)!\n", rc);
1130 break;
1131 }
1132
1133 if (pcszSettingsPw)
1134 {
1135 CHECK_ERROR(virtualBox, SetSettingsSecret(Bstr(pcszSettingsPw).raw()));
1136 if (FAILED(rc))
1137 break;
1138 }
1139 else if (pcszSettingsPwFile)
1140 {
1141 int rcExit = settingsPasswordFile(virtualBox, pcszSettingsPwFile);
1142 if (rcExit != RTEXITCODE_SUCCESS)
1143 break;
1144 }
1145
1146 ComPtr<IMachine> m;
1147
1148 rc = virtualBox->FindMachine(Bstr(pcszNameOrUUID).raw(), m.asOutParam());
1149 if (FAILED(rc))
1150 {
1151 LogError("Invalid machine name or UUID!\n", rc);
1152 break;
1153 }
1154 Bstr id;
1155 m->COMGETTER(Id)(id.asOutParam());
1156 AssertComRC(rc);
1157 if (FAILED(rc))
1158 break;
1159
1160 Log(("VBoxHeadless: Opening a session with machine (id={%s})...\n",
1161 Utf8Str(id).c_str()));
1162
1163 // set session name
1164 CHECK_ERROR_BREAK(session, COMSETTER(Name)(Bstr("headless").raw()));
1165 // open a session
1166 CHECK_ERROR_BREAK(m, LockMachine(session, LockType_VM));
1167 fSessionOpened = true;
1168
1169 /* get the console */
1170 ComPtr<IConsole> console;
1171 CHECK_ERROR_BREAK(session, COMGETTER(Console)(console.asOutParam()));
1172
1173 /* get the mutable machine */
1174 CHECK_ERROR_BREAK(console, COMGETTER(Machine)(machine.asOutParam()));
1175
1176 ComPtr<IDisplay> display;
1177 CHECK_ERROR_BREAK(console, COMGETTER(Display)(display.asOutParam()));
1178
1179#ifdef VBOX_WITH_RECORDING
1180 if (fRecordEnabled)
1181 {
1182 ComPtr<IRecordingSettings> recordingSettings;
1183 CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam()));
1184 CHECK_ERROR_BREAK(recordingSettings, COMSETTER(Enabled)(TRUE));
1185
1186 SafeIfaceArray <IRecordingScreenSettings> saRecordScreenScreens;
1187 CHECK_ERROR_BREAK(recordingSettings, COMGETTER(Screens)(ComSafeArrayAsOutParam(saRecordScreenScreens)));
1188
1189 /* Note: For now all screens have the same configuration. */
1190 for (size_t i = 0; i < saRecordScreenScreens.size(); ++i)
1191 {
1192 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(Enabled)(TRUE));
1193 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(Filename)(Bstr(szRecordFilename).raw()));
1194 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoWidth)(ulRecordVideoWidth));
1195 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoHeight)(ulRecordVideoHeight));
1196 CHECK_ERROR_BREAK(saRecordScreenScreens[i], COMSETTER(VideoRate)(ulRecordVideoRate));
1197 }
1198 }
1199#endif /* defined(VBOX_WITH_RECORDING) */
1200
1201 /* get the machine debugger (isn't necessarily available) */
1202 ComPtr <IMachineDebugger> machineDebugger;
1203 console->COMGETTER(Debugger)(machineDebugger.asOutParam());
1204 if (machineDebugger)
1205 {
1206 Log(("Machine debugger available!\n"));
1207 }
1208
1209 if (fRawR0 != ~0U)
1210 {
1211 if (!machineDebugger)
1212 {
1213 RTPrintf("Error: No debugger object; -%srawr0 cannot be executed!\n", fRawR0 ? "" : "no");
1214 break;
1215 }
1216 machineDebugger->COMSETTER(RecompileSupervisor)(!fRawR0);
1217 }
1218 if (fRawR3 != ~0U)
1219 {
1220 if (!machineDebugger)
1221 {
1222 RTPrintf("Error: No debugger object; -%srawr3 cannot be executed!\n", fRawR3 ? "" : "no");
1223 break;
1224 }
1225 machineDebugger->COMSETTER(RecompileUser)(!fRawR3);
1226 }
1227 if (fPATM != ~0U)
1228 {
1229 if (!machineDebugger)
1230 {
1231 RTPrintf("Error: No debugger object; -%spatm cannot be executed!\n", fPATM ? "" : "no");
1232 break;
1233 }
1234 machineDebugger->COMSETTER(PATMEnabled)(fPATM);
1235 }
1236 if (fCSAM != ~0U)
1237 {
1238 if (!machineDebugger)
1239 {
1240 RTPrintf("Error: No debugger object; -%scsam cannot be executed!\n", fCSAM ? "" : "no");
1241 break;
1242 }
1243 machineDebugger->COMSETTER(CSAMEnabled)(fCSAM);
1244 }
1245
1246 /* initialize global references */
1247 gConsole = console;
1248 gEventQ = com::NativeEventQueue::getMainEventQueue();
1249
1250 /* VirtualBoxClient events registration. */
1251 {
1252 ComPtr<IEventSource> pES;
1253 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1254 ComObjPtr<VirtualBoxClientEventListenerImpl> listener;
1255 listener.createObject();
1256 listener->init(new VirtualBoxClientEventListener());
1257 vboxClientListener = listener;
1258 com::SafeArray<VBoxEventType_T> eventTypes;
1259 eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
1260 CHECK_ERROR(pES, RegisterListener(vboxClientListener, ComSafeArrayAsInParam(eventTypes), true));
1261 }
1262
1263 /* Console events registration. */
1264 {
1265 ComPtr<IEventSource> es;
1266 CHECK_ERROR(console, COMGETTER(EventSource)(es.asOutParam()));
1267 consoleListener.createObject();
1268 consoleListener->init(new ConsoleEventListener());
1269 com::SafeArray<VBoxEventType_T> eventTypes;
1270 eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
1271 eventTypes.push_back(VBoxEventType_OnStateChanged);
1272 eventTypes.push_back(VBoxEventType_OnVRDEServerInfoChanged);
1273 eventTypes.push_back(VBoxEventType_OnCanShowWindow);
1274 eventTypes.push_back(VBoxEventType_OnShowWindow);
1275 eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
1276 CHECK_ERROR(es, RegisterListener(consoleListener, ComSafeArrayAsInParam(eventTypes), true));
1277 }
1278
1279 /* Default is to use the VM setting for the VRDE server. */
1280 enum VRDEOption
1281 {
1282 VRDEOption_Config,
1283 VRDEOption_Off,
1284 VRDEOption_On
1285 };
1286 VRDEOption enmVRDEOption = VRDEOption_Config;
1287 BOOL fVRDEEnabled;
1288 ComPtr <IVRDEServer> vrdeServer;
1289 CHECK_ERROR_BREAK(machine, COMGETTER(VRDEServer)(vrdeServer.asOutParam()));
1290 CHECK_ERROR_BREAK(vrdeServer, COMGETTER(Enabled)(&fVRDEEnabled));
1291
1292 if (vrdeEnabled != NULL)
1293 {
1294 /* -vrde on|off|config */
1295 if (!strcmp(vrdeEnabled, "off") || !strcmp(vrdeEnabled, "disable"))
1296 enmVRDEOption = VRDEOption_Off;
1297 else if (!strcmp(vrdeEnabled, "on") || !strcmp(vrdeEnabled, "enable"))
1298 enmVRDEOption = VRDEOption_On;
1299 else if (strcmp(vrdeEnabled, "config"))
1300 {
1301 RTPrintf("-vrde requires an argument (on|off|config)\n");
1302 break;
1303 }
1304 }
1305
1306 Log(("VBoxHeadless: enmVRDE %d, fVRDEEnabled %d\n", enmVRDEOption, fVRDEEnabled));
1307
1308 if (enmVRDEOption != VRDEOption_Off)
1309 {
1310 /* Set other specified options. */
1311
1312 /* set VRDE port if requested by the user */
1313 if (vrdePort != NULL)
1314 {
1315 Bstr bstr = vrdePort;
1316 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.raw()));
1317 }
1318 /* set VRDE address if requested by the user */
1319 if (vrdeAddress != NULL)
1320 {
1321 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(Bstr("TCP/Address").raw(), Bstr(vrdeAddress).raw()));
1322 }
1323
1324 /* Set VRDE properties. */
1325 if (cVRDEProperties > 0)
1326 {
1327 for (unsigned i = 0; i < cVRDEProperties; i++)
1328 {
1329 /* Parse 'name=value' */
1330 char *pszProperty = RTStrDup(aVRDEProperties[i]);
1331 if (pszProperty)
1332 {
1333 char *pDelimiter = strchr(pszProperty, '=');
1334 if (pDelimiter)
1335 {
1336 *pDelimiter = '\0';
1337
1338 Bstr bstrName = pszProperty;
1339 Bstr bstrValue = &pDelimiter[1];
1340 CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(bstrName.raw(), bstrValue.raw()));
1341 }
1342 else
1343 {
1344 RTPrintf("Error: Invalid VRDE property '%s'\n", aVRDEProperties[i]);
1345 RTStrFree(pszProperty);
1346 rc = E_INVALIDARG;
1347 break;
1348 }
1349 RTStrFree(pszProperty);
1350 }
1351 else
1352 {
1353 RTPrintf("Error: Failed to allocate memory for VRDE property '%s'\n", aVRDEProperties[i]);
1354 rc = E_OUTOFMEMORY;
1355 break;
1356 }
1357 }
1358 if (FAILED(rc))
1359 break;
1360 }
1361
1362 }
1363
1364 if (enmVRDEOption == VRDEOption_On)
1365 {
1366 /* enable VRDE server (only if currently disabled) */
1367 if (!fVRDEEnabled)
1368 {
1369 CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(TRUE));
1370 }
1371 }
1372 else if (enmVRDEOption == VRDEOption_Off)
1373 {
1374 /* disable VRDE server (only if currently enabled */
1375 if (fVRDEEnabled)
1376 {
1377 CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(FALSE));
1378 }
1379 }
1380
1381 /* Disable the host clipboard before powering up */
1382 console->COMSETTER(UseHostClipboard)(false);
1383
1384 Log(("VBoxHeadless: Powering up the machine...\n"));
1385
1386 ComPtr <IProgress> progress;
1387 if (!fPaused)
1388 CHECK_ERROR_BREAK(console, PowerUp(progress.asOutParam()));
1389 else
1390 CHECK_ERROR_BREAK(console, PowerUpPaused(progress.asOutParam()));
1391
1392 rc = showProgress(progress);
1393 if (FAILED(rc))
1394 {
1395 com::ProgressErrorInfo info(progress);
1396 if (info.isBasicAvailable())
1397 {
1398 RTPrintf("Error: failed to start machine. Error message: %ls\n", info.getText().raw());
1399 }
1400 else
1401 {
1402 RTPrintf("Error: failed to start machine. No error message available!\n");
1403 }
1404 break;
1405 }
1406
1407#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
1408 signal(SIGPIPE, SIG_IGN);
1409 signal(SIGTTOU, SIG_IGN);
1410
1411 struct sigaction sa;
1412 RT_ZERO(sa);
1413 sa.sa_handler = HandleSignal;
1414 sigaction(SIGHUP, &sa, NULL);
1415 sigaction(SIGINT, &sa, NULL);
1416 sigaction(SIGTERM, &sa, NULL);
1417 sigaction(SIGUSR1, &sa, NULL);
1418 sigaction(SIGUSR2, &sa, NULL);
1419#endif
1420
1421
1422 /*
1423 * Pump vbox events forever
1424 */
1425 LogRel(("VBoxHeadless: starting event loop\n"));
1426 for (;;)
1427 {
1428 int irc = gEventQ->processEventQueue(RT_INDEFINITE_WAIT);
1429
1430 /*
1431 * interruptEventQueueProcessing from another thread is
1432 * reported as VERR_INTERRUPTED, so check the flag first.
1433 */
1434 if (g_fTerminateFE)
1435 {
1436 LogRel(("VBoxHeadless: processEventQueue: %Rrc, termination requested\n", irc));
1437 break;
1438 }
1439
1440 if (RT_FAILURE(irc))
1441 {
1442 LogRel(("VBoxHeadless: processEventQueue: %Rrc\n", irc));
1443 RTMsgError("event loop: %Rrc", irc);
1444 break;
1445 }
1446 }
1447
1448 Log(("VBoxHeadless: event loop has terminated...\n"));
1449
1450#ifdef VBOX_WITH_RECORDING
1451 if (fRecordEnabled)
1452 {
1453 if (!machine.isNull())
1454 {
1455 ComPtr<IRecordingSettings> recordingSettings;
1456 CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam()));
1457 CHECK_ERROR_BREAK(recordingSettings, COMSETTER(Enabled)(FALSE));
1458 }
1459 }
1460#endif /* VBOX_WITH_RECORDING */
1461
1462 /* we don't have to disable VRDE here because we don't save the settings of the VM */
1463 }
1464 while (0);
1465
1466 /*
1467 * Get the machine state.
1468 */
1469 MachineState_T machineState = MachineState_Aborted;
1470 if (!machine.isNull())
1471 {
1472 rc = machine->COMGETTER(State)(&machineState);
1473 if (SUCCEEDED(rc))
1474 Log(("machine state = %RU32\n", machineState));
1475 else
1476 Log(("IMachine::getState: %Rhrc\n", rc));
1477 }
1478 else
1479 {
1480 Log(("machine == NULL\n"));
1481 }
1482
1483 /*
1484 * Turn off the VM if it's running
1485 */
1486 if ( gConsole
1487 && ( machineState == MachineState_Running
1488 || machineState == MachineState_Teleporting
1489 || machineState == MachineState_LiveSnapshotting
1490 /** @todo power off paused VMs too? */
1491 )
1492 )
1493 do
1494 {
1495 consoleListener->getWrapped()->ignorePowerOffEvents(true);
1496
1497 ComPtr<IProgress> pProgress;
1498#ifdef VBOX_WITH_SAVESTATE_ON_SIGNAL
1499 if (!machine.isNull())
1500 CHECK_ERROR_BREAK(machine, SaveState(pProgress.asOutParam()));
1501 else
1502#endif
1503 CHECK_ERROR_BREAK(gConsole, PowerDown(pProgress.asOutParam()));
1504
1505 rc = showProgress(pProgress);
1506 if (FAILED(rc))
1507 {
1508 RTPrintf("VBoxHeadless: ERROR: Failed to power down VM!");
1509 com::ErrorInfo info;
1510 if (!info.isFullAvailable() && !info.isBasicAvailable())
1511 com::GluePrintRCMessage(rc);
1512 else
1513 com::GluePrintErrorInfo(info);
1514 break;
1515 }
1516 } while (0);
1517
1518 /* VirtualBox callback unregistration. */
1519 if (vboxListener)
1520 {
1521 ComPtr<IEventSource> es;
1522 CHECK_ERROR(virtualBox, COMGETTER(EventSource)(es.asOutParam()));
1523 if (!es.isNull())
1524 CHECK_ERROR(es, UnregisterListener(vboxListener));
1525 vboxListener.setNull();
1526 }
1527
1528 /* Console callback unregistration. */
1529 if (consoleListener)
1530 {
1531 ComPtr<IEventSource> es;
1532 CHECK_ERROR(gConsole, COMGETTER(EventSource)(es.asOutParam()));
1533 if (!es.isNull())
1534 CHECK_ERROR(es, UnregisterListener(consoleListener));
1535 consoleListener.setNull();
1536 }
1537
1538 /* VirtualBoxClient callback unregistration. */
1539 if (vboxClientListener)
1540 {
1541 ComPtr<IEventSource> pES;
1542 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1543 if (!pES.isNull())
1544 CHECK_ERROR(pES, UnregisterListener(vboxClientListener));
1545 vboxClientListener.setNull();
1546 }
1547
1548 /* No more access to the 'console' object, which will be uninitialized by the next session->Close call. */
1549 gConsole = NULL;
1550
1551 if (fSessionOpened)
1552 {
1553 /*
1554 * Close the session. This will also uninitialize the console and
1555 * unregister the callback we've registered before.
1556 */
1557 Log(("VBoxHeadless: Closing the session...\n"));
1558 session->UnlockMachine();
1559 }
1560
1561 /* Must be before com::Shutdown */
1562 session.setNull();
1563 virtualBox.setNull();
1564 pVirtualBoxClient.setNull();
1565 machine.setNull();
1566
1567 com::Shutdown();
1568
1569 LogFlow(("VBoxHeadless FINISHED.\n"));
1570
1571 return FAILED(rc) ? 1 : 0;
1572}
1573
1574
1575#ifndef VBOX_WITH_HARDENING
1576/**
1577 * Main entry point.
1578 */
1579int main(int argc, char **argv, char **envp)
1580{
1581 // initialize VBox Runtime
1582 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB);
1583 if (RT_FAILURE(rc))
1584 {
1585 RTPrintf("VBoxHeadless: Runtime Error:\n"
1586 " %Rrc -- %Rrf\n", rc, rc);
1587 switch (rc)
1588 {
1589 case VERR_VM_DRIVER_NOT_INSTALLED:
1590 RTPrintf("Cannot access the kernel driver. Make sure the kernel module has been \n"
1591 "loaded successfully. Aborting ...\n");
1592 break;
1593 default:
1594 break;
1595 }
1596 return 1;
1597 }
1598
1599 return TrustedMain(argc, argv, envp);
1600}
1601#endif /* !VBOX_WITH_HARDENING */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette