/* $Id: GuestImpl.cpp $ */
/** @file
 * VirtualBox COM class implementation: Guest features.
 */

/*
 * Copyright (C) 2006-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

#define LOG_GROUP LOG_GROUP_MAIN_GUEST
#include "LoggingNew.h"

#include "GuestImpl.h"
#ifdef VBOX_WITH_GUEST_CONTROL
# include "GuestSessionImpl.h"
#endif
#include "Global.h"
#include "ConsoleImpl.h"
#include "ProgressImpl.h"
#ifdef VBOX_WITH_DRAG_AND_DROP
# include "GuestDnDPrivate.h"
#endif
#include "VMMDev.h"

#include "AutoCaller.h"
#include "Performance.h"
#include "VBoxEvents.h"

#include <VBox/VMMDev.h>
#include <iprt/cpp/utils.h>
#include <iprt/ctype.h>
#include <iprt/stream.h>
#include <iprt/timer.h>
#include <VBox/vmm/pgm.h>
#include <VBox/version.h>

// defines
/////////////////////////////////////////////////////////////////////////////

// constructor / destructor
/////////////////////////////////////////////////////////////////////////////

DEFINE_EMPTY_CTOR_DTOR(Guest)

HRESULT Guest::FinalConstruct()
{
    return BaseFinalConstruct();
}

void Guest::FinalRelease()
{
    uninit();
    BaseFinalRelease();
}

// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////

/**
 * Initializes the guest object.
 */
HRESULT Guest::init(Console *aParent)
{
    LogFlowThisFunc(("aParent=%p\n", aParent));

    ComAssertRet(aParent, E_INVALIDARG);

    /* Enclose the state transition NotReady->InInit->Ready */
    AutoInitSpan autoInitSpan(this);
    AssertReturn(autoInitSpan.isOk(), E_FAIL);

    unconst(mParent) = aParent;

    ULONG aMemoryBalloonSize = 0;
    HRESULT hr = mParent->i_machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize);
    if (SUCCEEDED(hr))
        mMemoryBalloonSize = aMemoryBalloonSize;
    else
        mMemoryBalloonSize = 0; /* Default is no ballooning */

    BOOL fPageFusionEnabled = FALSE;
    hr = mParent->i_machine()->COMGETTER(PageFusionEnabled)(&fPageFusionEnabled);
    if (SUCCEEDED(hr))
        mfPageFusionEnabled = fPageFusionEnabled;
    else
        mfPageFusionEnabled = false; /* Default is no page fusion*/

    mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
    mCollectVMMStats = false;

    /* Clear statistics. */
    mNetStatRx = mNetStatTx = 0;
    mNetStatLastTs = RTTimeNanoTS();
    for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
        mCurrentGuestStat[i] = 0;
    mVmValidStats = pm::VMSTATMASK_NONE;
    RT_ZERO(mCurrentGuestCpuUserStat);
    RT_ZERO(mCurrentGuestCpuKernelStat);
    RT_ZERO(mCurrentGuestCpuIdleStat);

    mMagic = GUEST_MAGIC;
    mStatTimer = NIL_RTTIMERLR;

    hr = unconst(mEventSource).createObject();
    if (SUCCEEDED(hr))
        hr = mEventSource->init();

    mCpus = 1;

#ifdef VBOX_WITH_DRAG_AND_DROP
    if (SUCCEEDED(hr))
    {
        try
        {
            GuestDnD::createInstance(this /* pGuest */);
            hr = unconst(mDnDSource).createObject();
            if (SUCCEEDED(hr))
                hr = mDnDSource->init(this /* pGuest */);
            if (SUCCEEDED(hr))
            {
                hr = unconst(mDnDTarget).createObject();
                if (SUCCEEDED(hr))
                    hr = mDnDTarget->init(this /* pGuest */);
            }

            LogFlowFunc(("Drag and drop initializied with hr=%Rhrc\n", hr));
        }
        catch (std::bad_alloc &)
        {
            hr = E_OUTOFMEMORY;
        }
    }
#endif

    /* Confirm a successful initialization when it's the case: */
    if (SUCCEEDED(hr))
        autoInitSpan.setSucceeded();
    else
        autoInitSpan.setFailed();

    LogFlowFunc(("hr=%Rhrc\n", hr));
    return hr;
}

/**
 * Uninitializes the instance and sets the ready flag to FALSE.
 * Called either from FinalRelease() or by the parent when it gets destroyed.
 */
void Guest::uninit()
{
    LogFlowThisFunc(("\n"));

    /* Enclose the state transition Ready->InUninit->NotReady */
    AutoUninitSpan autoUninitSpan(this);
    if (autoUninitSpan.uninitDone())
        return;

    /* Destroy stat update timer */
    int vrc = RTTimerLRDestroy(mStatTimer);
    AssertMsgRC(vrc, ("Failed to create guest statistics update timer(%Rra)\n", vrc));
    mStatTimer = NIL_RTTIMERLR;
    mMagic     = 0;

#ifdef VBOX_WITH_GUEST_CONTROL
    LogFlowThisFunc(("Closing sessions (%RU64 total)\n",
                     mData.mGuestSessions.size()));
    GuestSessions::iterator itSessions = mData.mGuestSessions.begin();
    while (itSessions != mData.mGuestSessions.end())
    {
# ifdef DEBUG
/** @todo r=bird: hit a use-after-free situation here while debugging the
 * 0xcccccccc status code issue in copyto.  My bet is that this happens
 * because of an uninit race, where GuestSession::close(), or someone, does
 * not ensure that the parent object (Guest) is okay to use (in the AutoCaller
 * sense), only their own object. */
        ULONG cRefs = itSessions->second->AddRef();
        LogFlowThisFunc(("sessionID=%RU32, cRefs=%RU32\n", itSessions->first, cRefs > 1 ? cRefs - 1 : 0));
        itSessions->second->Release();
# endif
        itSessions->second->uninit();
        ++itSessions;
    }
    mData.mGuestSessions.clear();
#endif

#ifdef VBOX_WITH_DRAG_AND_DROP
    GuestDnD::destroyInstance();
    unconst(mDnDSource).setNull();
    unconst(mDnDTarget).setNull();
#endif

    unconst(mEventSource).setNull();
    unconst(mParent) = NULL;

    LogFlowFuncLeave();
}

/* static */
DECLCALLBACK(void) Guest::i_staticUpdateStats(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick)
{
    AssertReturnVoid(pvUser != NULL);
    Guest *guest = static_cast<Guest *>(pvUser);
    Assert(guest->mMagic == GUEST_MAGIC);
    if (guest->mMagic == GUEST_MAGIC)
        guest->i_updateStats(iTick);

    NOREF(hTimerLR);
}

/* static */
DECLCALLBACK(int)  Guest::i_staticEnumStatsCallback(const char *pszName, STAMTYPE enmType, void *pvSample,
                                                    STAMUNIT enmUnit, STAMVISIBILITY enmVisiblity,
                                                    const char *pszDesc, void *pvUser)
{
    RT_NOREF(enmVisiblity, pszDesc);
    AssertLogRelMsgReturn(enmType == STAMTYPE_COUNTER, ("Unexpected sample type %d ('%s')\n", enmType, pszName), VINF_SUCCESS);
    AssertLogRelMsgReturn(enmUnit == STAMUNIT_BYTES, ("Unexpected sample unit %d ('%s')\n", enmUnit, pszName), VINF_SUCCESS);

    /* Get the base name w/ slash. */
    const char *pszLastSlash = strrchr(pszName, '/');
    AssertLogRelMsgReturn(pszLastSlash, ("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);

    /* Receive or transmit? */
    bool fRx;
    if (!strcmp(pszLastSlash, "/BytesReceived"))
        fRx = true;
    else if (!strcmp(pszLastSlash, "/BytesTransmitted"))
        fRx = false;
    else
        AssertLogRelMsgFailedReturn(("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);

#if 0 /* not used for anything, so don't bother parsing it. */
    /* Find start of instance number. ASSUMES '/Public/Net/Name<Instance digits>/Bytes...' */
    do
        --pszLastSlash;
    while (pszLastSlash > pszName && RT_C_IS_DIGIT(*pszLastSlash));
    pszLastSlash++;

    uint8_t uInstance;
    int rc = RTStrToUInt8Ex(pszLastSlash, NULL, 10, &uInstance);
    AssertLogRelMsgReturn(RT_SUCCESS(rc) && rc != VWRN_NUMBER_TOO_BIG && rc != VWRN_NEGATIVE_UNSIGNED,
                          ("%Rrc '%s'\n", rc, pszName), VINF_SUCCESS)
#endif

    /* Add the bytes to our counters. */
    PSTAMCOUNTER pCnt   = (PSTAMCOUNTER)pvSample;
    Guest       *pGuest = (Guest *)pvUser;
    uint64_t     cb     = pCnt->c;
#if 0
    LogFlowFunc(("%s i=%u d=%s %llu bytes\n", pszName, uInstance, fRx ? "RX" : "TX", cb));
#else
    LogFlowFunc(("%s d=%s %llu bytes\n", pszName, fRx ? "RX" : "TX", cb));
#endif
    if (fRx)
        pGuest->mNetStatRx += cb;
    else
        pGuest->mNetStatTx += cb;

    return VINF_SUCCESS;
}

void Guest::i_updateStats(uint64_t iTick)
{
    RT_NOREF(iTick);

    uint64_t cbFreeTotal      = 0;
    uint64_t cbAllocTotal     = 0;
    uint64_t cbBalloonedTotal = 0;
    uint64_t cbSharedTotal    = 0;
    uint64_t cbSharedMem      = 0;
    ULONG    uNetStatRx       = 0;
    ULONG    uNetStatTx       = 0;
    ULONG    aGuestStats[GUESTSTATTYPE_MAX];
    RT_ZERO(aGuestStats);

    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    ULONG validStats = mVmValidStats;
    /* Check if we have anything to report */
    if (validStats)
    {
        mVmValidStats = pm::VMSTATMASK_NONE;
        memcpy(aGuestStats, mCurrentGuestStat, sizeof(aGuestStats));
    }
    alock.release();

    /*
     * Calling SessionMachine may take time as the object resides in VBoxSVC
     * process. This is why we took a snapshot of currently collected stats
     * and released the lock.
     */
    Console::SafeVMPtrQuiet ptrVM(mParent);
    if (ptrVM.isOk())
    {
        int rc;

        /*
         * There is no point in collecting VM shared memory if other memory
         * statistics are not available yet. Or is there?
         */
        if (validStats)
        {
            /* Query the missing per-VM memory statistics. */
            uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbZeroMemIgn;
            rc = PGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn);
            if (rc == VINF_SUCCESS)
                validStats |= pm::VMSTATMASK_GUEST_MEMSHARED;
        }

        if (mCollectVMMStats)
        {
            rc = PGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, &cbBalloonedTotal, &cbSharedTotal);
            AssertRC(rc);
            if (rc == VINF_SUCCESS)
                validStats |= pm::VMSTATMASK_VMM_ALLOC  | pm::VMSTATMASK_VMM_FREE
                           |  pm::VMSTATMASK_VMM_BALOON | pm::VMSTATMASK_VMM_SHARED;
        }

        uint64_t uRxPrev = mNetStatRx;
        uint64_t uTxPrev = mNetStatTx;
        mNetStatRx = mNetStatTx = 0;
        rc = STAMR3Enum(ptrVM.rawUVM(), "/Public/Net/*/Bytes*", i_staticEnumStatsCallback, this);
        AssertRC(rc);

        uint64_t uTsNow = RTTimeNanoTS();
        uint64_t cNsPassed = uTsNow - mNetStatLastTs;
        if (cNsPassed >= 1000)
        {
            mNetStatLastTs = uTsNow;

            uNetStatRx = (ULONG)((mNetStatRx - uRxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
            uNetStatTx = (ULONG)((mNetStatTx - uTxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
            validStats |= pm::VMSTATMASK_NET_RX | pm::VMSTATMASK_NET_TX;
            LogFlowThisFunc(("Net Rx=%llu Tx=%llu Ts=%llu Delta=%llu\n", mNetStatRx, mNetStatTx, uTsNow, cNsPassed));
        }
        else
        {
            /* Can happen on resume or if we're using a non-monotonic clock
               source for the timer and the time is adjusted. */
            mNetStatRx = uRxPrev;
            mNetStatTx = uTxPrev;
            LogThisFunc(("Net Ts=%llu cNsPassed=%llu - too small interval\n", uTsNow, cNsPassed));
        }
    }

    mParent->i_reportVmStatistics(validStats,
                                  aGuestStats[GUESTSTATTYPE_CPUUSER],
                                  aGuestStats[GUESTSTATTYPE_CPUKERNEL],
                                  aGuestStats[GUESTSTATTYPE_CPUIDLE],
                                  /* Convert the units for RAM usage stats: page (4K) -> 1KB units */
                                  mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K),
                                  mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K),
                                  mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K),
                                  (ULONG)(cbSharedMem / _1K), /* bytes -> KB */
                                  mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K),
                                  mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K),
                                  (ULONG)(cbAllocTotal / _1K), /* bytes -> KB */
                                  (ULONG)(cbFreeTotal / _1K),
                                  (ULONG)(cbBalloonedTotal / _1K),
                                  (ULONG)(cbSharedTotal / _1K),
                                  uNetStatRx,
                                  uNetStatTx);
}

// IGuest properties
/////////////////////////////////////////////////////////////////////////////

HRESULT Guest::getOSTypeId(com::Utf8Str &aOSTypeId)
{
    HRESULT hrc = S_OK;
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (!mData.mInterfaceVersion.isEmpty())
        aOSTypeId = mData.mOSTypeId;
    else
    {
        /* Redirect the call to IMachine if no additions are installed. */
        ComPtr<IMachine> ptrMachine(mParent->i_machine());
        alock.release();
        Bstr bstr;
        hrc = ptrMachine->COMGETTER(OSTypeId)(bstr.asOutParam());
        aOSTypeId = bstr;
    }
    return hrc;
}

HRESULT Guest::getAdditionsRunLevel(AdditionsRunLevelType_T *aAdditionsRunLevel)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    *aAdditionsRunLevel = mData.mAdditionsRunLevel;

    return S_OK;
}

HRESULT Guest::getAdditionsVersion(com::Utf8Str &aAdditionsVersion)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    HRESULT hrc = S_OK;

    /*
     * Return the ReportGuestInfo2 version info if available.
     */
    if (   !mData.mAdditionsVersionNew.isEmpty()
         || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
        aAdditionsVersion = mData.mAdditionsVersionNew;
    else
    {
        /*
         * If we're running older guest additions (< 3.2.0) try get it from
         * the guest properties.  Detected switched around Version and
         * Revision in early 3.1.x releases (see r57115).
         */
        ComPtr<IMachine> ptrMachine = mParent->i_machine();
        alock.release(); /* No need to hold this during the IPC fun. */

        Bstr bstr;
        hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
        if (   SUCCEEDED(hrc)
            && !bstr.isEmpty())
        {
            Utf8Str str(bstr);
            if (str.count('.') == 0)
                hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
            str = bstr;
            if (str.count('.') != 2)
                hrc = E_FAIL;
        }

        if (SUCCEEDED(hrc))
            aAdditionsVersion = bstr;
        else
        {
            /* Returning 1.4 is better than nothing. */
            alock.acquire();
            aAdditionsVersion = mData.mInterfaceVersion;
            hrc = S_OK;
        }
    }
    return hrc;
}

HRESULT Guest::getAdditionsRevision(ULONG *aAdditionsRevision)
{
    HRESULT hrc = S_OK;
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    /*
     * Return the ReportGuestInfo2 version info if available.
     */
    if (   !mData.mAdditionsVersionNew.isEmpty()
        || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
        *aAdditionsRevision = mData.mAdditionsRevision;
    else
    {
        /*
         * If we're running older guest additions (< 3.2.0) try get it from
         * the guest properties. Detected switched around Version and
         * Revision in early 3.1.x releases (see r57115).
         */
        ComPtr<IMachine> ptrMachine = mParent->i_machine();
        alock.release(); /* No need to hold this during the IPC fun. */

        Bstr bstr;
        hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
        if (SUCCEEDED(hrc))
        {
            Utf8Str str(bstr);
            uint32_t uRevision;
            int vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
            if (vrc != VINF_SUCCESS && str.count('.') == 2)
            {
                hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
                if (SUCCEEDED(hrc))
                {
                    str = bstr;
                    vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
                }
            }
            if (vrc == VINF_SUCCESS)
                *aAdditionsRevision = uRevision;
            else
                hrc = VBOX_E_IPRT_ERROR;
        }
        if (FAILED(hrc))
        {
            /* Return 0 if we don't know. */
            *aAdditionsRevision = 0;
            hrc = S_OK;
        }
    }
    return hrc;
}

HRESULT Guest::getDnDSource(ComPtr<IGuestDnDSource> &aDnDSource)
{
#ifndef VBOX_WITH_DRAG_AND_DROP
    RT_NOREF(aDnDSource);
    ReturnComNotImplemented();
#else
    LogFlowThisFuncEnter();

    /* No need to lock - lifetime constant. */
    HRESULT hr = mDnDSource.queryInterfaceTo(aDnDSource.asOutParam());

    LogFlowFuncLeaveRC(hr);
    return hr;
#endif /* VBOX_WITH_DRAG_AND_DROP */
}

HRESULT Guest::getDnDTarget(ComPtr<IGuestDnDTarget> &aDnDTarget)
{
#ifndef VBOX_WITH_DRAG_AND_DROP
    RT_NOREF(aDnDTarget);
    ReturnComNotImplemented();
#else
    LogFlowThisFuncEnter();

    /* No need to lock - lifetime constant. */
    HRESULT hr = mDnDTarget.queryInterfaceTo(aDnDTarget.asOutParam());

    LogFlowFuncLeaveRC(hr);
    return hr;
#endif /* VBOX_WITH_DRAG_AND_DROP */
}

HRESULT Guest::getEventSource(ComPtr<IEventSource> &aEventSource)
{
    LogFlowThisFuncEnter();

    /* No need to lock - lifetime constant. */
    mEventSource.queryInterfaceTo(aEventSource.asOutParam());

    LogFlowFuncLeaveRC(S_OK);
    return S_OK;
}

HRESULT Guest::getFacilities(std::vector<ComPtr<IAdditionsFacility> > &aFacilities)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    aFacilities.resize(mData.mFacilityMap.size());
    size_t i = 0;
    for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it, ++i)
        it->second.queryInterfaceTo(aFacilities[i].asOutParam());

    return S_OK;
}

HRESULT Guest::getSessions(std::vector<ComPtr<IGuestSession> > &aSessions)
{
#ifdef VBOX_WITH_GUEST_CONTROL
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    aSessions.resize(mData.mGuestSessions.size());
    size_t i = 0;
    for (GuestSessions::iterator it = mData.mGuestSessions.begin(); it != mData.mGuestSessions.end(); ++it, ++i)
        it->second.queryInterfaceTo(aSessions[i].asOutParam());

    return S_OK;
#else
    ReturnComNotImplemented();
#endif
}

BOOL Guest::i_isPageFusionEnabled()
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    return mfPageFusionEnabled;
}

HRESULT Guest::getMemoryBalloonSize(ULONG *aMemoryBalloonSize)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    *aMemoryBalloonSize = mMemoryBalloonSize;

    return S_OK;
}

HRESULT Guest::setMemoryBalloonSize(ULONG aMemoryBalloonSize)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    /* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize)
     * does not call us back in any way! */
    HRESULT ret = mParent->i_machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize);
    if (ret == S_OK)
    {
        mMemoryBalloonSize = aMemoryBalloonSize;
        /* forward the information to the VMM device */
        VMMDev *pVMMDev = mParent->i_getVMMDev();
        /* MUST release all locks before calling VMM device as its critsect
         * has higher lock order than anything in Main. */
        alock.release();
        if (pVMMDev)
        {
            PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
            if (pVMMDevPort)
                pVMMDevPort->pfnSetMemoryBalloon(pVMMDevPort, aMemoryBalloonSize);
        }
    }

    return ret;
}

HRESULT Guest::getStatisticsUpdateInterval(ULONG *aStatisticsUpdateInterval)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    *aStatisticsUpdateInterval = mStatUpdateInterval;
    return S_OK;
}

HRESULT Guest::setStatisticsUpdateInterval(ULONG aStatisticsUpdateInterval)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    /* Update the timer, creating it the first time we're called with a non-zero value. */
    int vrc;
    HRESULT hrc = S_OK;
    if (aStatisticsUpdateInterval > 0)
    {
        if (mStatTimer == NIL_RTTIMERLR)
        {
            vrc = RTTimerLRCreate(&mStatTimer, aStatisticsUpdateInterval * RT_MS_1SEC, &Guest::i_staticUpdateStats, this);
            AssertRCStmt(vrc, hrc = setErrorVrc(vrc, "Failed to create guest statistics update timer (%Rrc)", vrc));
        }
        else if (aStatisticsUpdateInterval != mStatUpdateInterval)
        {
            vrc = RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval * RT_NS_1SEC_64);
            AssertRCStmt(vrc, hrc = setErrorVrc(vrc, "Failed to change guest statistics update timer interval from %u to %u failed (%Rrc)",
                                                mStatUpdateInterval, aStatisticsUpdateInterval, vrc));
            if (mStatUpdateInterval == 0)
            {
                vrc = RTTimerLRStart(mStatTimer, 0);
                AssertRCStmt(vrc, hrc = setErrorVrc(vrc, "Failed to start the guest statistics update timer (%Rrc)", vrc));
            }
        }
    }
    /* Setting interval to zero - stop the update timer if needed: */
    else if (mStatUpdateInterval > 0 && mStatTimer != NIL_RTTIMERLR)
    {
        vrc = RTTimerLRStop(mStatTimer);
        AssertRCStmt(vrc, hrc = setErrorVrc(vrc, "Failed to stop the guest statistics update timer (%Rrc)", vrc));
    }

    /* Update the interval now that the timer is in sync. */
    mStatUpdateInterval = aStatisticsUpdateInterval;

    /* Forward the information to the VMM device.
       MUST release all locks before calling VMM device as its critsect
       has higher lock order than anything in Main. */
    VMMDev *pVMMDev = mParent->i_getVMMDev();
    alock.release();
    if (pVMMDev)
    {
        PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
        if (pVMMDevPort)
            pVMMDevPort->pfnSetStatisticsInterval(pVMMDevPort, aStatisticsUpdateInterval);
    }

    return hrc;
}


HRESULT Guest::internalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle,
                                     ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon,
                                     ULONG *aMemShared, ULONG *aMemCache, ULONG *aPageTotal,
                                     ULONG *aMemAllocTotal, ULONG *aMemFreeTotal,
                                     ULONG *aMemBalloonTotal, ULONG *aMemSharedTotal)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    *aCpuUser    = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER];
    *aCpuKernel  = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL];
    *aCpuIdle    = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE];
    *aMemTotal   = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL]   * (_4K/_1K); /* page (4K) -> 1KB units */
    *aMemFree    = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE]    * (_4K/_1K); /* page (4K) -> 1KB units */
    *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */
    *aMemCache   = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE]   * (_4K/_1K); /* page (4K) -> 1KB units */
    *aPageTotal  = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL]  * (_4K/_1K); /* page (4K) -> 1KB units */

    /* Play safe or smth? */
    *aMemAllocTotal   = 0;
    *aMemFreeTotal    = 0;
    *aMemBalloonTotal = 0;
    *aMemSharedTotal  = 0;
    *aMemShared       = 0;

    /* MUST release all locks before calling any PGM statistics queries,
     * as they are executed by EMT and that might deadlock us by VMM device
     * activity which waits for the Guest object lock. */
    alock.release();
    Console::SafeVMPtr ptrVM(mParent);
    if (!ptrVM.isOk())
        return E_FAIL;

    uint64_t cbFreeTotal, cbAllocTotal, cbBalloonedTotal, cbSharedTotal;
    int rc = PGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, &cbBalloonedTotal, &cbSharedTotal);
    AssertRCReturn(rc, E_FAIL);

    *aMemAllocTotal   = (ULONG)(cbAllocTotal / _1K);  /* bytes -> KB */
    *aMemFreeTotal    = (ULONG)(cbFreeTotal / _1K);
    *aMemBalloonTotal = (ULONG)(cbBalloonedTotal / _1K);
    *aMemSharedTotal  = (ULONG)(cbSharedTotal / _1K);

    /* Query the missing per-VM memory statistics. */
    uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbSharedMem, cbZeroMemIgn;
    rc = PGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn);
    AssertRCReturn(rc, E_FAIL);
    *aMemShared = (ULONG)(cbSharedMem / _1K);

    return S_OK;
}

HRESULT Guest::i_setStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal)
{
    static ULONG indexToPerfMask[] =
    {
        pm::VMSTATMASK_GUEST_CPUUSER,
        pm::VMSTATMASK_GUEST_CPUKERNEL,
        pm::VMSTATMASK_GUEST_CPUIDLE,
        pm::VMSTATMASK_GUEST_MEMTOTAL,
        pm::VMSTATMASK_GUEST_MEMFREE,
        pm::VMSTATMASK_GUEST_MEMBALLOON,
        pm::VMSTATMASK_GUEST_MEMCACHE,
        pm::VMSTATMASK_GUEST_PAGETOTAL,
        pm::VMSTATMASK_NONE
    };
    AutoCaller autoCaller(this);
    if (FAILED(autoCaller.rc())) return autoCaller.rc();

    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    if (enmType >= GUESTSTATTYPE_MAX)
        return E_INVALIDARG;

    if (aCpuId < VMM_MAX_CPU_COUNT)
    {
        ULONG *paCpuStats;
        switch (enmType)
        {
            case GUESTSTATTYPE_CPUUSER:   paCpuStats = mCurrentGuestCpuUserStat;   break;
            case GUESTSTATTYPE_CPUKERNEL: paCpuStats = mCurrentGuestCpuKernelStat; break;
            case GUESTSTATTYPE_CPUIDLE:   paCpuStats = mCurrentGuestCpuIdleStat;   break;
            default:                      paCpuStats = NULL;                       break;
        }
        if (paCpuStats)
        {
            paCpuStats[aCpuId] = aVal;
            aVal = 0;
            for (uint32_t i = 0; i < mCpus && i < VMM_MAX_CPU_COUNT; i++)
                aVal += paCpuStats[i];
            aVal /= mCpus;
        }
    }

    mCurrentGuestStat[enmType] = aVal;
    mVmValidStats |= indexToPerfMask[enmType];
    return S_OK;
}

/**
 * Returns the status of a specified Guest Additions facility.
 *
 * @return  COM status code
 * @param   aFacility       Facility to get the status from.
 * @param   aTimestamp      Timestamp of last facility status update in ms (optional).
 * @param   aStatus         Current status of the specified facility.
 */
HRESULT Guest::getFacilityStatus(AdditionsFacilityType_T aFacility, LONG64 *aTimestamp, AdditionsFacilityStatus_T *aStatus)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    /* Not checking for aTimestamp is intentional; it's optional. */
    FacilityMapIterConst it = mData.mFacilityMap.find(aFacility);
    if (it != mData.mFacilityMap.end())
    {
        AdditionsFacility *pFacility = it->second;
        ComAssert(pFacility);
        *aStatus = pFacility->i_getStatus();
        if (aTimestamp)
            *aTimestamp = pFacility->i_getLastUpdated();
    }
    else
    {
        /*
         * Do not fail here -- could be that the facility never has been brought up (yet) but
         * the host wants to have its status anyway. So just tell we don't know at this point.
         */
        *aStatus = AdditionsFacilityStatus_Unknown;
        if (aTimestamp)
            *aTimestamp = RTTimeMilliTS();
    }
    return S_OK;
}

HRESULT Guest::getAdditionsStatus(AdditionsRunLevelType_T aLevel, BOOL *aActive)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    HRESULT rc = S_OK;
    switch (aLevel)
    {
        case AdditionsRunLevelType_System:
            *aActive = (mData.mAdditionsRunLevel > AdditionsRunLevelType_None);
            break;

        case AdditionsRunLevelType_Userland:
            *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Userland);
            break;

        case AdditionsRunLevelType_Desktop:
            *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Desktop);
            break;

        default:
            rc = setError(VBOX_E_NOT_SUPPORTED,
                          tr("Invalid status level defined: %u"), aLevel);
            break;
    }

    return rc;
}
HRESULT Guest::setCredentials(const com::Utf8Str &aUserName, const com::Utf8Str &aPassword,
                              const com::Utf8Str &aDomain, BOOL aAllowInteractiveLogon)
{
    /* Check for magic domain names which are used to pass encryption keys to the disk. */
    if (Utf8Str(aDomain) == "@@disk")
        return mParent->i_setDiskEncryptionKeys(aPassword);
    if (Utf8Str(aDomain) == "@@mem")
    {
        /** @todo */
        return E_NOTIMPL;
    }

    /* forward the information to the VMM device */
    VMMDev *pVMMDev = mParent->i_getVMMDev();
    if (pVMMDev)
    {
        PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
        if (pVMMDevPort)
        {
            uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
            if (!aAllowInteractiveLogon)
                u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON;

            pVMMDevPort->pfnSetCredentials(pVMMDevPort,
                                           aUserName.c_str(),
                                           aPassword.c_str(),
                                           aDomain.c_str(),
                                           u32Flags);
            return S_OK;
        }
    }

    return setError(VBOX_E_VM_ERROR, tr("VMM device is not available (is the VM running?)"));
}

// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////

/**
 * Sets the general Guest Additions information like
 * API (interface) version and OS type.  Gets called by
 * vmmdevUpdateGuestInfo.
 *
 * @param aInterfaceVersion
 * @param aOsType
 */
void Guest::i_setAdditionsInfo(const com::Utf8Str &aInterfaceVersion, VBOXOSTYPE aOsType)
{
    RTTIMESPEC TimeSpecTS;
    RTTimeNow(&TimeSpecTS);

    AutoCaller autoCaller(this);
    AssertComRCReturnVoid(autoCaller.rc());

    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    /*
     * Note: The Guest Additions API (interface) version is deprecated
     * and will not be used anymore!  We might need it to at least report
     * something as version number if *really* ancient Guest Additions are
     * installed (without the guest version + revision properties having set).
     */
    mData.mInterfaceVersion = aInterfaceVersion;

    /*
     * Older Additions rely on the Additions API version whether they
     * are assumed to be active or not.  Since newer Additions do report
     * the Additions version *before* calling this function (by calling
     * VMMDevReportGuestInfo2, VMMDevReportGuestStatus, VMMDevReportGuestInfo,
     * in that order) we can tell apart old and new Additions here. Old
     * Additions never would set VMMDevReportGuestInfo2 (which set mData.mAdditionsVersion)
     * so they just rely on the aInterfaceVersion string (which gets set by
     * VMMDevReportGuestInfo).
     *
     * So only mark the Additions as being active (run level = system) when we
     * don't have the Additions version set.
     */
    if (mData.mAdditionsVersionNew.isEmpty())
    {
        if (aInterfaceVersion.isEmpty())
            mData.mAdditionsRunLevel = AdditionsRunLevelType_None;
        else
        {
            mData.mAdditionsRunLevel = AdditionsRunLevelType_System;

            /*
             * To keep it compatible with the old Guest Additions behavior we need to set the
             * "graphics" (feature) facility to active as soon as we got the Guest Additions
             * interface version.
             */
            i_facilityUpdate(VBoxGuestFacilityType_Graphics, VBoxGuestFacilityStatus_Active, 0 /*fFlags*/, &TimeSpecTS);
        }
    }

    /*
     * Older Additions didn't have this finer grained capability bit,
     * so enable it by default. Newer Additions will not enable this here
     * and use the setSupportedFeatures function instead.
     */
    /** @todo r=bird: I don't get the above comment nor the code below...
     * One talks about capability bits, the one always does something to a facility.
     * Then there is the comment below it all, which is placed like it addresses the
     * mOSTypeId, but talks about something which doesn't remotely like mOSTypeId...
     *
     * Andy, could you please try clarify and make the comments shorter and more
     * coherent! Also, explain why this is important and what depends on it.
     *
     * PS. There is the VMMDEV_GUEST_SUPPORTS_GRAPHICS capability* report... It
     * should come in pretty quickly after this update, normally.
     */
    i_facilityUpdate(VBoxGuestFacilityType_Graphics,
                     i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver)
                     ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
                     0 /*fFlags*/, &TimeSpecTS); /** @todo the timestamp isn't gonna be right here on saved state restore. */

    /*
     * Note! There is a race going on between setting mAdditionsRunLevel and
     * mSupportsGraphics here and disabling/enabling it later according to
     * its real status when using new(er) Guest Additions.
     */
    mData.mOSType = aOsType;
    mData.mOSTypeId = Global::OSTypeId(aOsType);

    /*
     * Always fire an event here.
     */
    AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel;
    alock.release();
    fireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_None, AdditionsFacilityStatus_Active,
                                         enmRunLevel, RTTimeSpecGetMilli(&TimeSpecTS));
}

/**
 * Sets the Guest Additions version information details.
 *
 * Gets called by vmmdevUpdateGuestInfo2 and vmmdevUpdateGuestInfo (to clear the
 * state).
 *
 * @param   a_uFullVersion          VBoxGuestInfo2::additionsMajor,
 *                                  VBoxGuestInfo2::additionsMinor and
 *                                  VBoxGuestInfo2::additionsBuild combined into
 *                                  one value by VBOX_FULL_VERSION_MAKE.
 *
 *                                  When this is 0, it's vmmdevUpdateGuestInfo
 *                                  calling to reset the state.
 *
 * @param   a_pszName               Build type tag and/or publisher tag, empty
 *                                  string if neiter of those are present.
 * @param   a_uRevision             See VBoxGuestInfo2::additionsRevision.
 * @param   a_fFeatures             See VBoxGuestInfo2::additionsFeatures.
 */
void Guest::i_setAdditionsInfo2(uint32_t a_uFullVersion, const char *a_pszName, uint32_t a_uRevision, uint32_t a_fFeatures)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    if (a_uFullVersion)
    {
        mData.mAdditionsVersionNew  = Utf8StrFmt(*a_pszName ? "%u.%u.%u_%s" : "%u.%u.%u",
                                                 VBOX_FULL_VERSION_GET_MAJOR(a_uFullVersion),
                                                 VBOX_FULL_VERSION_GET_MINOR(a_uFullVersion),
                                                 VBOX_FULL_VERSION_GET_BUILD(a_uFullVersion),
                                                 a_pszName);
        mData.mAdditionsVersionFull = a_uFullVersion;
        mData.mAdditionsRevision    = a_uRevision;
        mData.mAdditionsFeatures    = a_fFeatures;
    }
    else
    {
        Assert(!a_fFeatures && !a_uRevision && !*a_pszName);
        mData.mAdditionsVersionNew.setNull();
        mData.mAdditionsVersionFull = 0;
        mData.mAdditionsRevision    = 0;
        mData.mAdditionsFeatures    = 0;
    }
}

bool Guest::i_facilityIsActive(VBoxGuestFacilityType enmFacility)
{
    Assert(enmFacility < INT32_MAX);
    FacilityMapIterConst it = mData.mFacilityMap.find((AdditionsFacilityType_T)enmFacility);
    if (it != mData.mFacilityMap.end())
    {
        AdditionsFacility *pFac = it->second;
        return (pFac->i_getStatus() == AdditionsFacilityStatus_Active);
    }
    return false;
}

bool Guest::i_facilityUpdate(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
                             uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
{
    AssertReturn(   a_enmFacility < VBoxGuestFacilityType_All
                 && a_enmFacility > VBoxGuestFacilityType_Unknown, false);

    bool fChanged;
    FacilityMapIter it = mData.mFacilityMap.find((AdditionsFacilityType_T)a_enmFacility);
    if (it != mData.mFacilityMap.end())
    {
        AdditionsFacility *pFac = it->second;
        fChanged = pFac->i_update((AdditionsFacilityStatus_T)a_enmStatus, a_fFlags, a_pTimeSpecTS);
    }
    else
    {
        if (mData.mFacilityMap.size() > 64)
        {
            /* The easy way out for now. We could automatically destroy
               inactive facilities like VMMDev does if we like... */
            AssertFailedReturn(false);
        }

        ComObjPtr<AdditionsFacility> ptrFac;
        HRESULT hrc = ptrFac.createObject();
        AssertComRCReturn(hrc, false);
        Assert(ptrFac);

        hrc = ptrFac->init(this, (AdditionsFacilityType_T)a_enmFacility, (AdditionsFacilityStatus_T)a_enmStatus,
                           a_fFlags, a_pTimeSpecTS);
        AssertComRCReturn(hrc, false);
        try
        {
            mData.mFacilityMap.insert(std::make_pair((AdditionsFacilityType_T)a_enmFacility, ptrFac));
            fChanged = true;
        }
        catch (std::bad_alloc &)
        {
            fChanged = false;
        }
    }
    return fChanged;
}

/**
 * Issued by the guest when a guest user changed its state.
 *
 * @return  IPRT status code.
 * @param   aUser               Guest user name.
 * @param   aDomain             Domain of guest user account. Optional.
 * @param   enmState            New state to indicate.
 * @param   pbDetails           Pointer to state details. Optional.
 * @param   cbDetails           Size (in bytes) of state details. Pass 0 if not used.
 */
void Guest::i_onUserStateChange(Bstr aUser, Bstr aDomain, VBoxGuestUserState enmState,
                                const uint8_t *pbDetails, uint32_t cbDetails)
{
    RT_NOREF(pbDetails, cbDetails);
    LogFlowThisFunc(("\n"));

    AutoCaller autoCaller(this);
    AssertComRCReturnVoid(autoCaller.rc());

    Bstr strDetails; /** @todo Implement state details here. */

    fireGuestUserStateChangedEvent(mEventSource, aUser.raw(), aDomain.raw(),
                                   (GuestUserState_T)enmState, strDetails.raw());
    LogFlowFuncLeave();
}

/**
 * Sets the status of a certain Guest Additions facility.
 *
 * Gets called by vmmdevUpdateGuestStatus, which just passes the report along.
 *
 * @param   a_enmFacility       The facility.
 * @param   a_enmStatus         The status.
 * @param   a_fFlags            Flags assoicated with the update. Currently
 *                              reserved and should be ignored.
 * @param   a_pTimeSpecTS       Pointer to the timestamp of this report.
 * @sa      PDMIVMMDEVCONNECTOR::pfnUpdateGuestStatus, vmmdevUpdateGuestStatus
 * @thread  The emulation thread.
 */
void Guest::i_setAdditionsStatus(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
                                 uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
{
    Assert(   a_enmFacility > VBoxGuestFacilityType_Unknown
           && a_enmFacility <= VBoxGuestFacilityType_All); /* Paranoia, VMMDev checks for this. */

    AutoCaller autoCaller(this);
    AssertComRCReturnVoid(autoCaller.rc());

    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    /*
     * Set a specific facility status.
     */
    bool fFireEvent = false;
    if (a_enmFacility == VBoxGuestFacilityType_All)
        for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it)
            fFireEvent |= i_facilityUpdate((VBoxGuestFacilityType)it->first, a_enmStatus, a_fFlags, a_pTimeSpecTS);
    else /* Update one facility only. */
        fFireEvent = i_facilityUpdate(a_enmFacility, a_enmStatus, a_fFlags, a_pTimeSpecTS);

    /*
     * Recalc the runlevel.
     */
    AdditionsRunLevelType_T const enmOldRunLevel = mData.mAdditionsRunLevel;
    if (i_facilityIsActive(VBoxGuestFacilityType_VBoxTrayClient))
        mData.mAdditionsRunLevel = AdditionsRunLevelType_Desktop;
    else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxService))
        mData.mAdditionsRunLevel = AdditionsRunLevelType_Userland;
    else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver))
        mData.mAdditionsRunLevel = AdditionsRunLevelType_System;
    else
        mData.mAdditionsRunLevel = AdditionsRunLevelType_None;

    /*
     * Fire event if something actually changed.
     */
    AdditionsRunLevelType_T const enmNewRunLevel = mData.mAdditionsRunLevel;
    if (fFireEvent || enmNewRunLevel != enmOldRunLevel)
    {
        alock.release();
        fireGuestAdditionsStatusChangedEvent(mEventSource, (AdditionsFacilityType_T)a_enmFacility,
                                             (AdditionsFacilityStatus_T)a_enmStatus, enmNewRunLevel,
                                             RTTimeSpecGetMilli(a_pTimeSpecTS));
    }
}

/**
 * Sets the supported features (and whether they are active or not).
 *
 * @param   aCaps   Guest capability bit mask (VMMDEV_GUEST_SUPPORTS_XXX).
 */
void Guest::i_setSupportedFeatures(uint32_t aCaps)
{
    AutoCaller autoCaller(this);
    AssertComRCReturnVoid(autoCaller.rc());

    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    /** @todo A nit: The timestamp is wrong on saved state restore. Would be better
     *  to move the graphics and seamless capability -> facility translation to
     *  VMMDev so this could be saved.  */
    RTTIMESPEC TimeSpecTS;
    RTTimeNow(&TimeSpecTS);

    bool fFireEvent = i_facilityUpdate(VBoxGuestFacilityType_Seamless,
                                       aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS
                                       ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
                                       0 /*fFlags*/, &TimeSpecTS);
    /** @todo Add VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING */

    /*
     * Fire event if the state actually changed.
     */
    if (fFireEvent)
    {
        AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel;
        alock.release();
        fireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_Seamless,
                                             aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS
                                             ? AdditionsFacilityStatus_Active : AdditionsFacilityStatus_Inactive, enmRunLevel,
                                             RTTimeSpecGetMilli(&TimeSpecTS));
    }
}
