VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestImpl.cpp@ 73768

Last change on this file since 73768 was 73003, checked in by vboxsync, 6 years ago

Main: Use setErrorBoth when we've got a VBox status code handy. (The COM status codes aren't too specfic and this may help us decode error messages and provide an alternative to strstr for API clients. setErrorBoth isn't new, btw.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.8 KB
Line 
1/* $Id: GuestImpl.cpp 73003 2018-07-09 11:09:32Z vboxsync $ */
2/** @file
3 * VirtualBox COM class implementation: Guest features.
4 */
5
6/*
7 * Copyright (C) 2006-2017 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#define LOG_GROUP LOG_GROUP_MAIN_GUEST
19#include "LoggingNew.h"
20
21#include "GuestImpl.h"
22#ifdef VBOX_WITH_GUEST_CONTROL
23# include "GuestSessionImpl.h"
24#endif
25#include "Global.h"
26#include "ConsoleImpl.h"
27#include "ProgressImpl.h"
28#ifdef VBOX_WITH_DRAG_AND_DROP
29# include "GuestDnDPrivate.h"
30#endif
31#include "VMMDev.h"
32
33#include "AutoCaller.h"
34#include "Performance.h"
35#include "VBoxEvents.h"
36
37#include <VBox/VMMDev.h>
38#include <iprt/cpp/utils.h>
39#include <iprt/ctype.h>
40#include <iprt/stream.h>
41#include <iprt/timer.h>
42#include <VBox/vmm/pgm.h>
43#include <VBox/version.h>
44
45// defines
46/////////////////////////////////////////////////////////////////////////////
47
48// constructor / destructor
49/////////////////////////////////////////////////////////////////////////////
50
51DEFINE_EMPTY_CTOR_DTOR(Guest)
52
53HRESULT Guest::FinalConstruct()
54{
55 return BaseFinalConstruct();
56}
57
58void Guest::FinalRelease()
59{
60 uninit();
61 BaseFinalRelease();
62}
63
64// public methods only for internal purposes
65/////////////////////////////////////////////////////////////////////////////
66
67/**
68 * Initializes the guest object.
69 */
70HRESULT Guest::init(Console *aParent)
71{
72 LogFlowThisFunc(("aParent=%p\n", aParent));
73
74 ComAssertRet(aParent, E_INVALIDARG);
75
76 /* Enclose the state transition NotReady->InInit->Ready */
77 AutoInitSpan autoInitSpan(this);
78 AssertReturn(autoInitSpan.isOk(), E_FAIL);
79
80 unconst(mParent) = aParent;
81
82 /* Confirm a successful initialization when it's the case */
83 autoInitSpan.setSucceeded();
84
85 ULONG aMemoryBalloonSize;
86 HRESULT hr = mParent->i_machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize);
87 if (hr == S_OK) /** @todo r=andy SUCCEEDED? */
88 mMemoryBalloonSize = aMemoryBalloonSize;
89 else
90 mMemoryBalloonSize = 0; /* Default is no ballooning */
91
92 BOOL fPageFusionEnabled;
93 hr = mParent->i_machine()->COMGETTER(PageFusionEnabled)(&fPageFusionEnabled);
94 if (hr == S_OK) /** @todo r=andy SUCCEEDED? */
95 mfPageFusionEnabled = fPageFusionEnabled;
96 else
97 mfPageFusionEnabled = false; /* Default is no page fusion*/
98
99 mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
100 mCollectVMMStats = false;
101
102 /* Clear statistics. */
103 mNetStatRx = mNetStatTx = 0;
104 mNetStatLastTs = RTTimeNanoTS();
105 for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
106 mCurrentGuestStat[i] = 0;
107 mVmValidStats = pm::VMSTATMASK_NONE;
108 RT_ZERO(mCurrentGuestCpuUserStat);
109 RT_ZERO(mCurrentGuestCpuKernelStat);
110 RT_ZERO(mCurrentGuestCpuIdleStat);
111
112 mMagic = GUEST_MAGIC;
113 int vrc = RTTimerLRCreate(&mStatTimer, 1000 /* ms */,
114 &Guest::i_staticUpdateStats, this);
115 AssertMsgRC(vrc, ("Failed to create guest statistics update timer (%Rrc)\n", vrc));
116
117 hr = unconst(mEventSource).createObject();
118 if (SUCCEEDED(hr))
119 hr = mEventSource->init();
120
121 mCpus = 1;
122
123#ifdef VBOX_WITH_DRAG_AND_DROP
124 try
125 {
126 GuestDnD::createInstance(this /* pGuest */);
127 hr = unconst(mDnDSource).createObject();
128 if (SUCCEEDED(hr))
129 hr = mDnDSource->init(this /* pGuest */);
130 if (SUCCEEDED(hr))
131 {
132 hr = unconst(mDnDTarget).createObject();
133 if (SUCCEEDED(hr))
134 hr = mDnDTarget->init(this /* pGuest */);
135 }
136
137 LogFlowFunc(("Drag and drop initializied with hr=%Rhrc\n", hr));
138 }
139 catch (std::bad_alloc &)
140 {
141 hr = E_OUTOFMEMORY;
142 }
143#endif
144
145 LogFlowFunc(("hr=%Rhrc\n", hr));
146 return hr;
147}
148
149/**
150 * Uninitializes the instance and sets the ready flag to FALSE.
151 * Called either from FinalRelease() or by the parent when it gets destroyed.
152 */
153void Guest::uninit()
154{
155 LogFlowThisFunc(("\n"));
156
157 /* Enclose the state transition Ready->InUninit->NotReady */
158 AutoUninitSpan autoUninitSpan(this);
159 if (autoUninitSpan.uninitDone())
160 return;
161
162 /* Destroy stat update timer */
163 int vrc = RTTimerLRDestroy(mStatTimer);
164 AssertMsgRC(vrc, ("Failed to create guest statistics update timer(%Rra)\n", vrc));
165 mStatTimer = NULL;
166 mMagic = 0;
167
168#ifdef VBOX_WITH_GUEST_CONTROL
169 LogFlowThisFunc(("Closing sessions (%RU64 total)\n",
170 mData.mGuestSessions.size()));
171 GuestSessions::iterator itSessions = mData.mGuestSessions.begin();
172 while (itSessions != mData.mGuestSessions.end())
173 {
174# ifdef DEBUG
175 ULONG cRefs = itSessions->second->AddRef();
176 LogFlowThisFunc(("sessionID=%RU32, cRefs=%RU32\n", itSessions->first, cRefs > 1 ? cRefs - 1 : 0));
177 itSessions->second->Release();
178# endif
179 itSessions->second->uninit();
180 ++itSessions;
181 }
182 mData.mGuestSessions.clear();
183#endif
184
185#ifdef VBOX_WITH_DRAG_AND_DROP
186 GuestDnD::destroyInstance();
187 unconst(mDnDSource).setNull();
188 unconst(mDnDTarget).setNull();
189#endif
190
191 unconst(mEventSource).setNull();
192 unconst(mParent) = NULL;
193
194 LogFlowFuncLeave();
195}
196
197/* static */
198DECLCALLBACK(void) Guest::i_staticUpdateStats(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick)
199{
200 AssertReturnVoid(pvUser != NULL);
201 Guest *guest = static_cast<Guest *>(pvUser);
202 Assert(guest->mMagic == GUEST_MAGIC);
203 if (guest->mMagic == GUEST_MAGIC)
204 guest->i_updateStats(iTick);
205
206 NOREF(hTimerLR);
207}
208
209/* static */
210DECLCALLBACK(int) Guest::i_staticEnumStatsCallback(const char *pszName, STAMTYPE enmType, void *pvSample,
211 STAMUNIT enmUnit, STAMVISIBILITY enmVisiblity,
212 const char *pszDesc, void *pvUser)
213{
214 RT_NOREF(enmVisiblity, pszDesc);
215 AssertLogRelMsgReturn(enmType == STAMTYPE_COUNTER, ("Unexpected sample type %d ('%s')\n", enmType, pszName), VINF_SUCCESS);
216 AssertLogRelMsgReturn(enmUnit == STAMUNIT_BYTES, ("Unexpected sample unit %d ('%s')\n", enmUnit, pszName), VINF_SUCCESS);
217
218 /* Get the base name w/ slash. */
219 const char *pszLastSlash = strrchr(pszName, '/');
220 AssertLogRelMsgReturn(pszLastSlash, ("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);
221
222 /* Receive or transmit? */
223 bool fRx;
224 if (!strcmp(pszLastSlash, "/BytesReceived"))
225 fRx = true;
226 else if (!strcmp(pszLastSlash, "/BytesTransmitted"))
227 fRx = false;
228 else
229 AssertLogRelMsgFailedReturn(("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);
230
231#if 0 /* not used for anything, so don't bother parsing it. */
232 /* Find start of instance number. ASSUMES '/Public/Net/Name<Instance digits>/Bytes...' */
233 do
234 --pszLastSlash;
235 while (pszLastSlash > pszName && RT_C_IS_DIGIT(*pszLastSlash));
236 pszLastSlash++;
237
238 uint8_t uInstance;
239 int rc = RTStrToUInt8Ex(pszLastSlash, NULL, 10, &uInstance);
240 AssertLogRelMsgReturn(RT_SUCCESS(rc) && rc != VWRN_NUMBER_TOO_BIG && rc != VWRN_NEGATIVE_UNSIGNED,
241 ("%Rrc '%s'\n", rc, pszName), VINF_SUCCESS)
242#endif
243
244 /* Add the bytes to our counters. */
245 PSTAMCOUNTER pCnt = (PSTAMCOUNTER)pvSample;
246 Guest *pGuest = (Guest *)pvUser;
247 uint64_t cb = pCnt->c;
248#if 0
249 LogFlowFunc(("%s i=%u d=%s %llu bytes\n", pszName, uInstance, fRx ? "RX" : "TX", cb));
250#else
251 LogFlowFunc(("%s d=%s %llu bytes\n", pszName, fRx ? "RX" : "TX", cb));
252#endif
253 if (fRx)
254 pGuest->mNetStatRx += cb;
255 else
256 pGuest->mNetStatTx += cb;
257
258 return VINF_SUCCESS;
259}
260
261void Guest::i_updateStats(uint64_t iTick)
262{
263 RT_NOREF(iTick);
264
265 uint64_t cbFreeTotal = 0;
266 uint64_t cbAllocTotal = 0;
267 uint64_t cbBalloonedTotal = 0;
268 uint64_t cbSharedTotal = 0;
269 uint64_t cbSharedMem = 0;
270 ULONG uNetStatRx = 0;
271 ULONG uNetStatTx = 0;
272 ULONG aGuestStats[GUESTSTATTYPE_MAX];
273 RT_ZERO(aGuestStats);
274
275 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
276
277 ULONG validStats = mVmValidStats;
278 /* Check if we have anything to report */
279 if (validStats)
280 {
281 mVmValidStats = pm::VMSTATMASK_NONE;
282 memcpy(aGuestStats, mCurrentGuestStat, sizeof(aGuestStats));
283 }
284 alock.release();
285
286 /*
287 * Calling SessionMachine may take time as the object resides in VBoxSVC
288 * process. This is why we took a snapshot of currently collected stats
289 * and released the lock.
290 */
291 Console::SafeVMPtrQuiet ptrVM(mParent);
292 if (ptrVM.isOk())
293 {
294 int rc;
295
296 /*
297 * There is no point in collecting VM shared memory if other memory
298 * statistics are not available yet. Or is there?
299 */
300 if (validStats)
301 {
302 /* Query the missing per-VM memory statistics. */
303 uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbZeroMemIgn;
304 rc = PGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn);
305 if (rc == VINF_SUCCESS)
306 validStats |= pm::VMSTATMASK_GUEST_MEMSHARED;
307 }
308
309 if (mCollectVMMStats)
310 {
311 rc = PGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, &cbBalloonedTotal, &cbSharedTotal);
312 AssertRC(rc);
313 if (rc == VINF_SUCCESS)
314 validStats |= pm::VMSTATMASK_VMM_ALLOC | pm::VMSTATMASK_VMM_FREE
315 | pm::VMSTATMASK_VMM_BALOON | pm::VMSTATMASK_VMM_SHARED;
316 }
317
318 uint64_t uRxPrev = mNetStatRx;
319 uint64_t uTxPrev = mNetStatTx;
320 mNetStatRx = mNetStatTx = 0;
321 rc = STAMR3Enum(ptrVM.rawUVM(), "/Public/Net/*/Bytes*", i_staticEnumStatsCallback, this);
322 AssertRC(rc);
323
324 uint64_t uTsNow = RTTimeNanoTS();
325 uint64_t cNsPassed = uTsNow - mNetStatLastTs;
326 if (cNsPassed >= 1000)
327 {
328 mNetStatLastTs = uTsNow;
329
330 uNetStatRx = (ULONG)((mNetStatRx - uRxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
331 uNetStatTx = (ULONG)((mNetStatTx - uTxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
332 validStats |= pm::VMSTATMASK_NET_RX | pm::VMSTATMASK_NET_TX;
333 LogFlowThisFunc(("Net Rx=%llu Tx=%llu Ts=%llu Delta=%llu\n", mNetStatRx, mNetStatTx, uTsNow, cNsPassed));
334 }
335 else
336 {
337 /* Can happen on resume or if we're using a non-monotonic clock
338 source for the timer and the time is adjusted. */
339 mNetStatRx = uRxPrev;
340 mNetStatTx = uTxPrev;
341 LogThisFunc(("Net Ts=%llu cNsPassed=%llu - too small interval\n", uTsNow, cNsPassed));
342 }
343 }
344
345 mParent->i_reportVmStatistics(validStats,
346 aGuestStats[GUESTSTATTYPE_CPUUSER],
347 aGuestStats[GUESTSTATTYPE_CPUKERNEL],
348 aGuestStats[GUESTSTATTYPE_CPUIDLE],
349 /* Convert the units for RAM usage stats: page (4K) -> 1KB units */
350 mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K),
351 mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K),
352 mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K),
353 (ULONG)(cbSharedMem / _1K), /* bytes -> KB */
354 mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K),
355 mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K),
356 (ULONG)(cbAllocTotal / _1K), /* bytes -> KB */
357 (ULONG)(cbFreeTotal / _1K),
358 (ULONG)(cbBalloonedTotal / _1K),
359 (ULONG)(cbSharedTotal / _1K),
360 uNetStatRx,
361 uNetStatTx);
362}
363
364// IGuest properties
365/////////////////////////////////////////////////////////////////////////////
366
367HRESULT Guest::getOSTypeId(com::Utf8Str &aOSTypeId)
368{
369 HRESULT hrc = S_OK;
370 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
371 if (!mData.mInterfaceVersion.isEmpty())
372 aOSTypeId = mData.mOSTypeId;
373 else
374 {
375 /* Redirect the call to IMachine if no additions are installed. */
376 ComPtr<IMachine> ptrMachine(mParent->i_machine());
377 alock.release();
378 BSTR bstr;
379 hrc = ptrMachine->COMGETTER(OSTypeId)(&bstr);
380 aOSTypeId = bstr;
381 }
382 return hrc;
383}
384
385HRESULT Guest::getAdditionsRunLevel(AdditionsRunLevelType_T *aAdditionsRunLevel)
386{
387 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
388
389 *aAdditionsRunLevel = mData.mAdditionsRunLevel;
390
391 return S_OK;
392}
393
394HRESULT Guest::getAdditionsVersion(com::Utf8Str &aAdditionsVersion)
395{
396 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
397 HRESULT hrc = S_OK;
398
399 /*
400 * Return the ReportGuestInfo2 version info if available.
401 */
402 if ( !mData.mAdditionsVersionNew.isEmpty()
403 || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
404 aAdditionsVersion = mData.mAdditionsVersionNew;
405 else
406 {
407 /*
408 * If we're running older guest additions (< 3.2.0) try get it from
409 * the guest properties. Detected switched around Version and
410 * Revision in early 3.1.x releases (see r57115).
411 */
412 ComPtr<IMachine> ptrMachine = mParent->i_machine();
413 alock.release(); /* No need to hold this during the IPC fun. */
414
415 Bstr bstr;
416 hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
417 if ( SUCCEEDED(hrc)
418 && !bstr.isEmpty())
419 {
420 Utf8Str str(bstr);
421 if (str.count('.') == 0)
422 hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
423 str = bstr;
424 if (str.count('.') != 2)
425 hrc = E_FAIL;
426 }
427
428 if (SUCCEEDED(hrc))
429 aAdditionsVersion = bstr;
430 else
431 {
432 /* Returning 1.4 is better than nothing. */
433 alock.acquire();
434 aAdditionsVersion = mData.mInterfaceVersion;
435 hrc = S_OK;
436 }
437 }
438 return hrc;
439}
440
441HRESULT Guest::getAdditionsRevision(ULONG *aAdditionsRevision)
442{
443 HRESULT hrc = S_OK;
444 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
445
446 /*
447 * Return the ReportGuestInfo2 version info if available.
448 */
449 if ( !mData.mAdditionsVersionNew.isEmpty()
450 || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
451 *aAdditionsRevision = mData.mAdditionsRevision;
452 else
453 {
454 /*
455 * If we're running older guest additions (< 3.2.0) try get it from
456 * the guest properties. Detected switched around Version and
457 * Revision in early 3.1.x releases (see r57115).
458 */
459 ComPtr<IMachine> ptrMachine = mParent->i_machine();
460 alock.release(); /* No need to hold this during the IPC fun. */
461
462 Bstr bstr;
463 hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
464 if (SUCCEEDED(hrc))
465 {
466 Utf8Str str(bstr);
467 uint32_t uRevision;
468 int vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
469 if (vrc != VINF_SUCCESS && str.count('.') == 2)
470 {
471 hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
472 if (SUCCEEDED(hrc))
473 {
474 str = bstr;
475 vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
476 }
477 }
478 if (vrc == VINF_SUCCESS)
479 *aAdditionsRevision = uRevision;
480 else
481 hrc = VBOX_E_IPRT_ERROR;
482 }
483 if (FAILED(hrc))
484 {
485 /* Return 0 if we don't know. */
486 *aAdditionsRevision = 0;
487 hrc = S_OK;
488 }
489 }
490 return hrc;
491}
492
493HRESULT Guest::getDnDSource(ComPtr<IGuestDnDSource> &aDnDSource)
494{
495#ifndef VBOX_WITH_DRAG_AND_DROP
496 RT_NOREF(aDnDSource);
497 ReturnComNotImplemented();
498#else
499 LogFlowThisFuncEnter();
500
501 /* No need to lock - lifetime constant. */
502 HRESULT hr = mDnDSource.queryInterfaceTo(aDnDSource.asOutParam());
503
504 LogFlowFuncLeaveRC(hr);
505 return hr;
506#endif /* VBOX_WITH_DRAG_AND_DROP */
507}
508
509HRESULT Guest::getDnDTarget(ComPtr<IGuestDnDTarget> &aDnDTarget)
510{
511#ifndef VBOX_WITH_DRAG_AND_DROP
512 RT_NOREF(aDnDTarget);
513 ReturnComNotImplemented();
514#else
515 LogFlowThisFuncEnter();
516
517 /* No need to lock - lifetime constant. */
518 HRESULT hr = mDnDTarget.queryInterfaceTo(aDnDTarget.asOutParam());
519
520 LogFlowFuncLeaveRC(hr);
521 return hr;
522#endif /* VBOX_WITH_DRAG_AND_DROP */
523}
524
525HRESULT Guest::getEventSource(ComPtr<IEventSource> &aEventSource)
526{
527 LogFlowThisFuncEnter();
528
529 /* No need to lock - lifetime constant. */
530 mEventSource.queryInterfaceTo(aEventSource.asOutParam());
531
532 LogFlowFuncLeaveRC(S_OK);
533 return S_OK;
534}
535
536HRESULT Guest::getFacilities(std::vector<ComPtr<IAdditionsFacility> > &aFacilities)
537{
538 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
539
540 aFacilities.resize(mData.mFacilityMap.size());
541 size_t i = 0;
542 for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it, ++i)
543 it->second.queryInterfaceTo(aFacilities[i].asOutParam());
544
545 return S_OK;
546}
547
548HRESULT Guest::getSessions(std::vector<ComPtr<IGuestSession> > &aSessions)
549{
550#ifdef VBOX_WITH_GUEST_CONTROL
551 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
552
553 aSessions.resize(mData.mGuestSessions.size());
554 size_t i = 0;
555 for (GuestSessions::iterator it = mData.mGuestSessions.begin(); it != mData.mGuestSessions.end(); ++it, ++i)
556 it->second.queryInterfaceTo(aSessions[i].asOutParam());
557
558 return S_OK;
559#else
560 ReturnComNotImplemented();
561#endif
562}
563
564BOOL Guest::i_isPageFusionEnabled()
565{
566 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
567
568 return mfPageFusionEnabled;
569}
570
571HRESULT Guest::getMemoryBalloonSize(ULONG *aMemoryBalloonSize)
572{
573 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
574
575 *aMemoryBalloonSize = mMemoryBalloonSize;
576
577 return S_OK;
578}
579
580HRESULT Guest::setMemoryBalloonSize(ULONG aMemoryBalloonSize)
581{
582 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
583
584 /* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize)
585 * does not call us back in any way! */
586 HRESULT ret = mParent->i_machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize);
587 if (ret == S_OK)
588 {
589 mMemoryBalloonSize = aMemoryBalloonSize;
590 /* forward the information to the VMM device */
591 VMMDev *pVMMDev = mParent->i_getVMMDev();
592 /* MUST release all locks before calling VMM device as its critsect
593 * has higher lock order than anything in Main. */
594 alock.release();
595 if (pVMMDev)
596 {
597 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
598 if (pVMMDevPort)
599 pVMMDevPort->pfnSetMemoryBalloon(pVMMDevPort, aMemoryBalloonSize);
600 }
601 }
602
603 return ret;
604}
605
606HRESULT Guest::getStatisticsUpdateInterval(ULONG *aStatisticsUpdateInterval)
607{
608 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
609
610 *aStatisticsUpdateInterval = mStatUpdateInterval;
611 return S_OK;
612}
613
614HRESULT Guest::setStatisticsUpdateInterval(ULONG aStatisticsUpdateInterval)
615{
616 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
617
618 if (mStatUpdateInterval)
619 if (aStatisticsUpdateInterval == 0)
620 RTTimerLRStop(mStatTimer);
621 else
622 RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval);
623 else
624 if (aStatisticsUpdateInterval != 0)
625 {
626 RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval);
627 RTTimerLRStart(mStatTimer, 0);
628 }
629 mStatUpdateInterval = aStatisticsUpdateInterval;
630 /* forward the information to the VMM device */
631 VMMDev *pVMMDev = mParent->i_getVMMDev();
632 /* MUST release all locks before calling VMM device as its critsect
633 * has higher lock order than anything in Main. */
634 alock.release();
635 if (pVMMDev)
636 {
637 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
638 if (pVMMDevPort)
639 pVMMDevPort->pfnSetStatisticsInterval(pVMMDevPort, aStatisticsUpdateInterval);
640 }
641
642 return S_OK;
643}
644
645
646HRESULT Guest::internalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle,
647 ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon,
648 ULONG *aMemShared, ULONG *aMemCache, ULONG *aPageTotal,
649 ULONG *aMemAllocTotal, ULONG *aMemFreeTotal,
650 ULONG *aMemBalloonTotal, ULONG *aMemSharedTotal)
651{
652 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
653
654 *aCpuUser = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER];
655 *aCpuKernel = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL];
656 *aCpuIdle = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE];
657 *aMemTotal = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
658 *aMemFree = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K); /* page (4K) -> 1KB units */
659 *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */
660 *aMemCache = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K); /* page (4K) -> 1KB units */
661 *aPageTotal = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
662
663 /* Play safe or smth? */
664 *aMemAllocTotal = 0;
665 *aMemFreeTotal = 0;
666 *aMemBalloonTotal = 0;
667 *aMemSharedTotal = 0;
668 *aMemShared = 0;
669
670 /* MUST release all locks before calling any PGM statistics queries,
671 * as they are executed by EMT and that might deadlock us by VMM device
672 * activity which waits for the Guest object lock. */
673 alock.release();
674 Console::SafeVMPtr ptrVM(mParent);
675 if (!ptrVM.isOk())
676 return E_FAIL;
677
678 uint64_t cbFreeTotal, cbAllocTotal, cbBalloonedTotal, cbSharedTotal;
679 int rc = PGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, &cbBalloonedTotal, &cbSharedTotal);
680 AssertRCReturn(rc, E_FAIL);
681
682 *aMemAllocTotal = (ULONG)(cbAllocTotal / _1K); /* bytes -> KB */
683 *aMemFreeTotal = (ULONG)(cbFreeTotal / _1K);
684 *aMemBalloonTotal = (ULONG)(cbBalloonedTotal / _1K);
685 *aMemSharedTotal = (ULONG)(cbSharedTotal / _1K);
686
687 /* Query the missing per-VM memory statistics. */
688 uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbSharedMem, cbZeroMemIgn;
689 rc = PGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn);
690 AssertRCReturn(rc, E_FAIL);
691 *aMemShared = (ULONG)(cbSharedMem / _1K);
692
693 return S_OK;
694}
695
696HRESULT Guest::i_setStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal)
697{
698 static ULONG indexToPerfMask[] =
699 {
700 pm::VMSTATMASK_GUEST_CPUUSER,
701 pm::VMSTATMASK_GUEST_CPUKERNEL,
702 pm::VMSTATMASK_GUEST_CPUIDLE,
703 pm::VMSTATMASK_GUEST_MEMTOTAL,
704 pm::VMSTATMASK_GUEST_MEMFREE,
705 pm::VMSTATMASK_GUEST_MEMBALLOON,
706 pm::VMSTATMASK_GUEST_MEMCACHE,
707 pm::VMSTATMASK_GUEST_PAGETOTAL,
708 pm::VMSTATMASK_NONE
709 };
710 AutoCaller autoCaller(this);
711 if (FAILED(autoCaller.rc())) return autoCaller.rc();
712
713 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
714
715 if (enmType >= GUESTSTATTYPE_MAX)
716 return E_INVALIDARG;
717
718 if (aCpuId < VMM_MAX_CPU_COUNT)
719 {
720 ULONG *paCpuStats;
721 switch (enmType)
722 {
723 case GUESTSTATTYPE_CPUUSER: paCpuStats = mCurrentGuestCpuUserStat; break;
724 case GUESTSTATTYPE_CPUKERNEL: paCpuStats = mCurrentGuestCpuKernelStat; break;
725 case GUESTSTATTYPE_CPUIDLE: paCpuStats = mCurrentGuestCpuIdleStat; break;
726 default: paCpuStats = NULL; break;
727 }
728 if (paCpuStats)
729 {
730 paCpuStats[aCpuId] = aVal;
731 aVal = 0;
732 for (uint32_t i = 0; i < mCpus && i < VMM_MAX_CPU_COUNT; i++)
733 aVal += paCpuStats[i];
734 aVal /= mCpus;
735 }
736 }
737
738 mCurrentGuestStat[enmType] = aVal;
739 mVmValidStats |= indexToPerfMask[enmType];
740 return S_OK;
741}
742
743/**
744 * Returns the status of a specified Guest Additions facility.
745 *
746 * @return COM status code
747 * @param aFacility Facility to get the status from.
748 * @param aTimestamp Timestamp of last facility status update in ms (optional).
749 * @param aStatus Current status of the specified facility.
750 */
751HRESULT Guest::getFacilityStatus(AdditionsFacilityType_T aFacility, LONG64 *aTimestamp, AdditionsFacilityStatus_T *aStatus)
752{
753 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
754
755 /* Not checking for aTimestamp is intentional; it's optional. */
756 FacilityMapIterConst it = mData.mFacilityMap.find(aFacility);
757 if (it != mData.mFacilityMap.end())
758 {
759 AdditionsFacility *pFacility = it->second;
760 ComAssert(pFacility);
761 *aStatus = pFacility->i_getStatus();
762 if (aTimestamp)
763 *aTimestamp = pFacility->i_getLastUpdated();
764 }
765 else
766 {
767 /*
768 * Do not fail here -- could be that the facility never has been brought up (yet) but
769 * the host wants to have its status anyway. So just tell we don't know at this point.
770 */
771 *aStatus = AdditionsFacilityStatus_Unknown;
772 if (aTimestamp)
773 *aTimestamp = RTTimeMilliTS();
774 }
775 return S_OK;
776}
777
778HRESULT Guest::getAdditionsStatus(AdditionsRunLevelType_T aLevel, BOOL *aActive)
779{
780 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
781
782 HRESULT rc = S_OK;
783 switch (aLevel)
784 {
785 case AdditionsRunLevelType_System:
786 *aActive = (mData.mAdditionsRunLevel > AdditionsRunLevelType_None);
787 break;
788
789 case AdditionsRunLevelType_Userland:
790 *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Userland);
791 break;
792
793 case AdditionsRunLevelType_Desktop:
794 *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Desktop);
795 break;
796
797 default:
798 rc = setError(VBOX_E_NOT_SUPPORTED,
799 tr("Invalid status level defined: %u"), aLevel);
800 break;
801 }
802
803 return rc;
804}
805HRESULT Guest::setCredentials(const com::Utf8Str &aUserName, const com::Utf8Str &aPassword,
806 const com::Utf8Str &aDomain, BOOL aAllowInteractiveLogon)
807{
808 /* Check for magic domain names which are used to pass encryption keys to the disk. */
809 if (Utf8Str(aDomain) == "@@disk")
810 return mParent->i_setDiskEncryptionKeys(aPassword);
811 if (Utf8Str(aDomain) == "@@mem")
812 {
813 /** @todo */
814 return E_NOTIMPL;
815 }
816
817 /* forward the information to the VMM device */
818 VMMDev *pVMMDev = mParent->i_getVMMDev();
819 if (pVMMDev)
820 {
821 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
822 if (pVMMDevPort)
823 {
824 uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
825 if (!aAllowInteractiveLogon)
826 u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON;
827
828 pVMMDevPort->pfnSetCredentials(pVMMDevPort,
829 aUserName.c_str(),
830 aPassword.c_str(),
831 aDomain.c_str(),
832 u32Flags);
833 return S_OK;
834 }
835 }
836
837 return setError(VBOX_E_VM_ERROR, tr("VMM device is not available (is the VM running?)"));
838}
839
840// public methods only for internal purposes
841/////////////////////////////////////////////////////////////////////////////
842
843/**
844 * Sets the general Guest Additions information like
845 * API (interface) version and OS type. Gets called by
846 * vmmdevUpdateGuestInfo.
847 *
848 * @param aInterfaceVersion
849 * @param aOsType
850 */
851void Guest::i_setAdditionsInfo(const com::Utf8Str &aInterfaceVersion, VBOXOSTYPE aOsType)
852{
853 RTTIMESPEC TimeSpecTS;
854 RTTimeNow(&TimeSpecTS);
855
856 AutoCaller autoCaller(this);
857 AssertComRCReturnVoid(autoCaller.rc());
858
859 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
860
861
862 /*
863 * Note: The Guest Additions API (interface) version is deprecated
864 * and will not be used anymore! We might need it to at least report
865 * something as version number if *really* ancient Guest Additions are
866 * installed (without the guest version + revision properties having set).
867 */
868 mData.mInterfaceVersion = aInterfaceVersion;
869
870 /*
871 * Older Additions rely on the Additions API version whether they
872 * are assumed to be active or not. Since newer Additions do report
873 * the Additions version *before* calling this function (by calling
874 * VMMDevReportGuestInfo2, VMMDevReportGuestStatus, VMMDevReportGuestInfo,
875 * in that order) we can tell apart old and new Additions here. Old
876 * Additions never would set VMMDevReportGuestInfo2 (which set mData.mAdditionsVersion)
877 * so they just rely on the aInterfaceVersion string (which gets set by
878 * VMMDevReportGuestInfo).
879 *
880 * So only mark the Additions as being active (run level = system) when we
881 * don't have the Additions version set.
882 */
883 if (mData.mAdditionsVersionNew.isEmpty())
884 {
885 if (aInterfaceVersion.isEmpty())
886 mData.mAdditionsRunLevel = AdditionsRunLevelType_None;
887 else
888 {
889 mData.mAdditionsRunLevel = AdditionsRunLevelType_System;
890
891 /*
892 * To keep it compatible with the old Guest Additions behavior we need to set the
893 * "graphics" (feature) facility to active as soon as we got the Guest Additions
894 * interface version.
895 */
896 i_facilityUpdate(VBoxGuestFacilityType_Graphics, VBoxGuestFacilityStatus_Active, 0 /*fFlags*/, &TimeSpecTS);
897 }
898 }
899
900 /*
901 * Older Additions didn't have this finer grained capability bit,
902 * so enable it by default. Newer Additions will not enable this here
903 * and use the setSupportedFeatures function instead.
904 */
905 /** @todo r=bird: I don't get the above comment nor the code below...
906 * One talks about capability bits, the one always does something to a facility.
907 * Then there is the comment below it all, which is placed like it addresses the
908 * mOSTypeId, but talks about something which doesn't remotely like mOSTypeId...
909 *
910 * Andy, could you please try clarify and make the comments shorter and more
911 * coherent! Also, explain why this is important and what depends on it.
912 *
913 * PS. There is the VMMDEV_GUEST_SUPPORTS_GRAPHICS capability* report... It
914 * should come in pretty quickly after this update, normally.
915 */
916 i_facilityUpdate(VBoxGuestFacilityType_Graphics,
917 i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver)
918 ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
919 0 /*fFlags*/, &TimeSpecTS); /** @todo the timestamp isn't gonna be right here on saved state restore. */
920
921 /*
922 * Note! There is a race going on between setting mAdditionsRunLevel and
923 * mSupportsGraphics here and disabling/enabling it later according to
924 * its real status when using new(er) Guest Additions.
925 */
926 mData.mOSType = aOsType;
927 mData.mOSTypeId = Global::OSTypeId(aOsType);
928}
929
930/**
931 * Sets the Guest Additions version information details.
932 *
933 * Gets called by vmmdevUpdateGuestInfo2 and vmmdevUpdateGuestInfo (to clear the
934 * state).
935 *
936 * @param a_uFullVersion VBoxGuestInfo2::additionsMajor,
937 * VBoxGuestInfo2::additionsMinor and
938 * VBoxGuestInfo2::additionsBuild combined into
939 * one value by VBOX_FULL_VERSION_MAKE.
940 *
941 * When this is 0, it's vmmdevUpdateGuestInfo
942 * calling to reset the state.
943 *
944 * @param a_pszName Build type tag and/or publisher tag, empty
945 * string if neiter of those are present.
946 * @param a_uRevision See VBoxGuestInfo2::additionsRevision.
947 * @param a_fFeatures See VBoxGuestInfo2::additionsFeatures.
948 */
949void Guest::i_setAdditionsInfo2(uint32_t a_uFullVersion, const char *a_pszName, uint32_t a_uRevision, uint32_t a_fFeatures)
950{
951 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
952
953 if (a_uFullVersion)
954 {
955 mData.mAdditionsVersionNew = Utf8StrFmt(*a_pszName ? "%u.%u.%u_%s" : "%u.%u.%u",
956 VBOX_FULL_VERSION_GET_MAJOR(a_uFullVersion),
957 VBOX_FULL_VERSION_GET_MINOR(a_uFullVersion),
958 VBOX_FULL_VERSION_GET_BUILD(a_uFullVersion),
959 a_pszName);
960 mData.mAdditionsVersionFull = a_uFullVersion;
961 mData.mAdditionsRevision = a_uRevision;
962 mData.mAdditionsFeatures = a_fFeatures;
963 }
964 else
965 {
966 Assert(!a_fFeatures && !a_uRevision && !*a_pszName);
967 mData.mAdditionsVersionNew.setNull();
968 mData.mAdditionsVersionFull = 0;
969 mData.mAdditionsRevision = 0;
970 mData.mAdditionsFeatures = 0;
971 }
972}
973
974bool Guest::i_facilityIsActive(VBoxGuestFacilityType enmFacility)
975{
976 Assert(enmFacility < INT32_MAX);
977 FacilityMapIterConst it = mData.mFacilityMap.find((AdditionsFacilityType_T)enmFacility);
978 if (it != mData.mFacilityMap.end())
979 {
980 AdditionsFacility *pFac = it->second;
981 return (pFac->i_getStatus() == AdditionsFacilityStatus_Active);
982 }
983 return false;
984}
985
986void Guest::i_facilityUpdate(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
987 uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
988{
989 AssertReturnVoid( a_enmFacility < VBoxGuestFacilityType_All
990 && a_enmFacility > VBoxGuestFacilityType_Unknown);
991
992 FacilityMapIter it = mData.mFacilityMap.find((AdditionsFacilityType_T)a_enmFacility);
993 if (it != mData.mFacilityMap.end())
994 {
995 AdditionsFacility *pFac = it->second;
996 pFac->i_update((AdditionsFacilityStatus_T)a_enmStatus, a_fFlags, a_pTimeSpecTS);
997 }
998 else
999 {
1000 if (mData.mFacilityMap.size() > 64)
1001 {
1002 /* The easy way out for now. We could automatically destroy
1003 inactive facilities like VMMDev does if we like... */
1004 AssertFailedReturnVoid();
1005 }
1006
1007 ComObjPtr<AdditionsFacility> ptrFac;
1008 ptrFac.createObject();
1009 AssertReturnVoid(!ptrFac.isNull());
1010
1011 HRESULT hrc = ptrFac->init(this, (AdditionsFacilityType_T)a_enmFacility, (AdditionsFacilityStatus_T)a_enmStatus,
1012 a_fFlags, a_pTimeSpecTS);
1013 if (SUCCEEDED(hrc))
1014 mData.mFacilityMap.insert(std::make_pair((AdditionsFacilityType_T)a_enmFacility, ptrFac));
1015 }
1016}
1017
1018/**
1019 * Issued by the guest when a guest user changed its state.
1020 *
1021 * @return IPRT status code.
1022 * @param aUser Guest user name.
1023 * @param aDomain Domain of guest user account. Optional.
1024 * @param enmState New state to indicate.
1025 * @param pbDetails Pointer to state details. Optional.
1026 * @param cbDetails Size (in bytes) of state details. Pass 0 if not used.
1027 */
1028void Guest::i_onUserStateChange(Bstr aUser, Bstr aDomain, VBoxGuestUserState enmState,
1029 const uint8_t *pbDetails, uint32_t cbDetails)
1030{
1031 RT_NOREF(pbDetails, cbDetails);
1032 LogFlowThisFunc(("\n"));
1033
1034 AutoCaller autoCaller(this);
1035 AssertComRCReturnVoid(autoCaller.rc());
1036
1037 Bstr strDetails; /** @todo Implement state details here. */
1038
1039 fireGuestUserStateChangedEvent(mEventSource, aUser.raw(), aDomain.raw(),
1040 (GuestUserState_T)enmState, strDetails.raw());
1041 LogFlowFuncLeave();
1042}
1043
1044/**
1045 * Sets the status of a certain Guest Additions facility.
1046 *
1047 * Gets called by vmmdevUpdateGuestStatus, which just passes the report along.
1048 *
1049 * @param a_enmFacility The facility.
1050 * @param a_enmStatus The status.
1051 * @param a_fFlags Flags assoicated with the update. Currently
1052 * reserved and should be ignored.
1053 * @param a_pTimeSpecTS Pointer to the timestamp of this report.
1054 * @sa PDMIVMMDEVCONNECTOR::pfnUpdateGuestStatus, vmmdevUpdateGuestStatus
1055 * @thread The emulation thread.
1056 */
1057void Guest::i_setAdditionsStatus(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
1058 uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
1059{
1060 Assert( a_enmFacility > VBoxGuestFacilityType_Unknown
1061 && a_enmFacility <= VBoxGuestFacilityType_All); /* Paranoia, VMMDev checks for this. */
1062
1063 AutoCaller autoCaller(this);
1064 AssertComRCReturnVoid(autoCaller.rc());
1065
1066 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1067
1068 /*
1069 * Set a specific facility status.
1070 */
1071 if (a_enmFacility == VBoxGuestFacilityType_All)
1072 for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it)
1073 i_facilityUpdate((VBoxGuestFacilityType)it->first, a_enmStatus, a_fFlags, a_pTimeSpecTS);
1074 else /* Update one facility only. */
1075 i_facilityUpdate(a_enmFacility, a_enmStatus, a_fFlags, a_pTimeSpecTS);
1076
1077 /*
1078 * Recalc the runlevel.
1079 */
1080 if (i_facilityIsActive(VBoxGuestFacilityType_VBoxTrayClient))
1081 mData.mAdditionsRunLevel = AdditionsRunLevelType_Desktop;
1082 else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxService))
1083 mData.mAdditionsRunLevel = AdditionsRunLevelType_Userland;
1084 else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver))
1085 mData.mAdditionsRunLevel = AdditionsRunLevelType_System;
1086 else
1087 mData.mAdditionsRunLevel = AdditionsRunLevelType_None;
1088}
1089
1090/**
1091 * Sets the supported features (and whether they are active or not).
1092 *
1093 * @param aCaps Guest capability bit mask (VMMDEV_GUEST_SUPPORTS_XXX).
1094 */
1095void Guest::i_setSupportedFeatures(uint32_t aCaps)
1096{
1097 AutoCaller autoCaller(this);
1098 AssertComRCReturnVoid(autoCaller.rc());
1099
1100 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1101
1102 /** @todo A nit: The timestamp is wrong on saved state restore. Would be better
1103 * to move the graphics and seamless capability -> facility translation to
1104 * VMMDev so this could be saved. */
1105 RTTIMESPEC TimeSpecTS;
1106 RTTimeNow(&TimeSpecTS);
1107
1108 i_facilityUpdate(VBoxGuestFacilityType_Seamless,
1109 aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
1110 0 /*fFlags*/, &TimeSpecTS);
1111 /** @todo Add VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING */
1112}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use