VirtualBox

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

Last change on this file since 86603 was 85488, checked in by vboxsync, 4 years ago

FE/VBoxHeadless: ticketref:19706 Apply alternative hideSetUidRootFromAppKit() from VirtualBox/src/main.cpp (r135961)

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