/* $Id: GuestCtrlImplPrivate.h $ */
/** @file
 * Internal helpers/structures for guest control functionality.
 */

/*
 * Copyright (C) 2011-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.
 */

#ifndef MAIN_INCLUDED_GuestCtrlImplPrivate_h
#define MAIN_INCLUDED_GuestCtrlImplPrivate_h
#ifndef RT_WITHOUT_PRAGMA_ONCE
# pragma once
#endif

#include "ConsoleImpl.h"
#include "Global.h"

#include <iprt/asm.h>
#include <iprt/env.h>
#include <iprt/semaphore.h>
#include <iprt/cpp/utils.h>

#include <VBox/com/com.h>
#include <VBox/com/ErrorInfo.h>
#include <VBox/com/string.h>
#include <VBox/com/VirtualBox.h>
#include <VBox/err.h> /* VERR_GSTCTL_GUEST_ERROR */

#include <map>
#include <vector>

using namespace com;

#ifdef VBOX_WITH_GUEST_CONTROL
# include <VBox/GuestHost/GuestControl.h>
# include <VBox/HostServices/GuestControlSvc.h>
using namespace guestControl;
#endif

/** Vector holding a process' CPU affinity. */
typedef std::vector <LONG> ProcessAffinity;
/** Vector holding process startup arguments. */
typedef std::vector <Utf8Str> ProcessArguments;

class GuestProcessStreamBlock;
class GuestSession;


/**
 * Simple structure mantaining guest credentials.
 */
struct GuestCredentials
{
    Utf8Str                     mUser;
    Utf8Str                     mPassword;
    Utf8Str                     mDomain;
};


/**
 * Wrapper around the RTEnv API, unusable base class.
 *
 * @remarks Feel free to elevate this class to iprt/cpp/env.h as RTCEnv.
 */
class GuestEnvironmentBase
{
public:
    /**
     * Default constructor.
     *
     * The user must invoke one of the init methods before using the object.
     */
    GuestEnvironmentBase(void)
        : m_hEnv(NIL_RTENV)
        , m_cRefs(1)
        , m_fFlags(0)
    { }

    /**
     * Destructor.
     */
    virtual ~GuestEnvironmentBase(void)
    {
        Assert(m_cRefs <= 1);
        int rc = RTEnvDestroy(m_hEnv); AssertRC(rc);
        m_hEnv = NIL_RTENV;
    }

    /**
     * Retains a reference to this object.
     * @returns New reference count.
     * @remarks Sharing an object is currently only safe if no changes are made to
     *          it because RTENV does not yet implement any locking.  For the only
     *          purpose we need this, implementing IGuestProcess::environment by
     *          using IGuestSession::environmentBase, that's fine as the session
     *          base environment is immutable.
     */
    uint32_t retain(void)
    {
        uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
        Assert(cRefs > 1); Assert(cRefs < _1M);
        return cRefs;

    }
    /** Useful shortcut. */
    uint32_t retainConst(void) const { return unconst(this)->retain(); }

    /**
     * Releases a reference to this object, deleting the object when reaching zero.
     * @returns New reference count.
     */
    uint32_t release(void)
    {
        uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
        Assert(cRefs < _1M);
        if (cRefs == 0)
            delete this;
        return cRefs;
    }

    /** Useful shortcut. */
    uint32_t releaseConst(void) const { return unconst(this)->retain(); }

    /**
     * Checks if the environment has been successfully initialized or not.
     *
     * @returns @c true if initialized, @c false if not.
     */
    bool isInitialized(void) const
    {
        return m_hEnv != NIL_RTENV;
    }

    /**
     * Returns the variable count.
     * @return Number of variables.
     * @sa      RTEnvCountEx
     */
    uint32_t count(void) const
    {
        return RTEnvCountEx(m_hEnv);
    }

    /**
     * Deletes the environment change record entirely.
     *
     * The count() method will return zero after this call.
     *
     * @sa      RTEnvReset
     */
    void reset(void)
    {
        int rc = RTEnvReset(m_hEnv);
        AssertRC(rc);
    }

    /**
     * Exports the environment change block as an array of putenv style strings.
     *
     *
     * @returns VINF_SUCCESS or VERR_NO_MEMORY.
     * @param   pArray              The output array.
     */
    int queryPutEnvArray(std::vector<com::Utf8Str> *pArray) const
    {
        uint32_t cVars = RTEnvCountEx(m_hEnv);
        try
        {
            pArray->resize(cVars);
            for (uint32_t iVar = 0; iVar < cVars; iVar++)
            {
                const char *psz = RTEnvGetByIndexRawEx(m_hEnv, iVar);
                AssertReturn(psz, VERR_INTERNAL_ERROR_3); /* someone is racing us! */
                (*pArray)[iVar] = psz;
            }
            return VINF_SUCCESS;
        }
        catch (std::bad_alloc &)
        {
            return VERR_NO_MEMORY;
        }
    }

    /**
     * Applies an array of putenv style strings.
     *
     * @returns IPRT status code.
     * @param   rArray          The array with the putenv style strings.
     * @param   pidxError       Where to return the index causing trouble on
     *                          failure.  Optional.
     * @sa      RTEnvPutEx
     */
    int applyPutEnvArray(const std::vector<com::Utf8Str> &rArray, size_t *pidxError = NULL)
    {
        size_t const cArray = rArray.size();
        for (size_t i = 0; i < cArray; i++)
        {
            int rc = RTEnvPutEx(m_hEnv, rArray[i].c_str());
            if (RT_FAILURE(rc))
            {
                if (pidxError)
                    *pidxError = i;
                return rc;
            }
        }
        return VINF_SUCCESS;
    }

    /**
     * Applies the changes from another environment to this.
     *
     * @returns IPRT status code.
     * @param   rChanges        Reference to an environment which variables will be
     *                          imported and, if it's a change record, schedule
     *                          variable unsets will be applied.
     * @sa      RTEnvApplyChanges
     */
    int applyChanges(const GuestEnvironmentBase &rChanges)
    {
        return RTEnvApplyChanges(m_hEnv, rChanges.m_hEnv);
    }

    /**
     * See RTEnvQueryUtf8Block for details.
     * @returns IPRT status code.
     * @param   ppszzBlock      Where to return the block pointer.
     * @param   pcbBlock        Where to optionally return the block size.
     * @sa      RTEnvQueryUtf8Block
     */
    int queryUtf8Block(char **ppszzBlock, size_t *pcbBlock)
    {
        return RTEnvQueryUtf8Block(m_hEnv, true /*fSorted*/, ppszzBlock, pcbBlock);
    }

    /**
     * Frees what queryUtf8Block returned, NULL ignored.
     * @sa      RTEnvFreeUtf8Block
     */
    static void freeUtf8Block(char *pszzBlock)
    {
        return RTEnvFreeUtf8Block(pszzBlock);
    }

    /**
     * Applies a block on the format returned by queryUtf8Block.
     *
     * @returns IPRT status code.
     * @param   pszzBlock           Pointer to the block.
     * @param   cbBlock             The size of the block.
     * @param   fNoEqualMeansUnset  Whether the lack of a '=' (equal) sign in a
     *                              string means it should be unset (@c true), or if
     *                              it means the variable should be defined with an
     *                              empty value (@c false, the default).
     * @todo move this to RTEnv!
     */
    int copyUtf8Block(const char *pszzBlock, size_t cbBlock, bool fNoEqualMeansUnset = false)
    {
        int rc = VINF_SUCCESS;
        while (cbBlock > 0 && *pszzBlock != '\0')
        {
            const char *pszEnd = (const char *)memchr(pszzBlock, '\0', cbBlock);
            if (!pszEnd)
                return VERR_BUFFER_UNDERFLOW;
            int rc2;
            if (fNoEqualMeansUnset || strchr(pszzBlock, '='))
                rc2 = RTEnvPutEx(m_hEnv, pszzBlock);
            else
                rc2 = RTEnvSetEx(m_hEnv, pszzBlock, "");
            if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
                rc = rc2;

            /* Advance. */
            cbBlock -= pszEnd - pszzBlock;
            if (cbBlock < 2)
                return VERR_BUFFER_UNDERFLOW;
            cbBlock--;
            pszzBlock = pszEnd + 1;
        }

        /* The remainder must be zero padded. */
        if (RT_SUCCESS(rc))
        {
            if (ASMMemIsZero(pszzBlock, cbBlock))
                return VINF_SUCCESS;
            return VERR_TOO_MUCH_DATA;
        }
        return rc;
    }

    /**
     * Get an environment variable.
     *
     * @returns IPRT status code.
     * @param   rName               The variable name.
     * @param   pValue              Where to return the value.
     * @sa      RTEnvGetEx
     */
    int getVariable(const com::Utf8Str &rName, com::Utf8Str *pValue) const
    {
        size_t cchNeeded;
        int rc = RTEnvGetEx(m_hEnv, rName.c_str(), NULL, 0, &cchNeeded);
        if (   RT_SUCCESS(rc)
            || rc == VERR_BUFFER_OVERFLOW)
        {
            try
            {
                pValue->reserve(cchNeeded + 1);
                rc = RTEnvGetEx(m_hEnv, rName.c_str(), pValue->mutableRaw(), pValue->capacity(), NULL);
                pValue->jolt();
            }
            catch (std::bad_alloc &)
            {
                rc = VERR_NO_STR_MEMORY;
            }
        }
        return rc;
    }

    /**
     * Checks if the given variable exists.
     *
     * @returns @c true if it exists, @c false if not or if it's an scheduled unset
     *          in a environment change record.
     * @param   rName               The variable name.
     * @sa      RTEnvExistEx
     */
    bool doesVariableExist(const com::Utf8Str &rName) const
    {
        return RTEnvExistEx(m_hEnv, rName.c_str());
    }

    /**
     * Set an environment variable.
     *
     * @returns IPRT status code.
     * @param   rName               The variable name.
     * @param   rValue              The value of the variable.
     * @sa      RTEnvSetEx
     */
    int setVariable(const com::Utf8Str &rName, const com::Utf8Str &rValue)
    {
        return RTEnvSetEx(m_hEnv, rName.c_str(), rValue.c_str());
    }

    /**
     * Unset an environment variable.
     *
     * @returns IPRT status code.
     * @param   rName               The variable name.
     * @sa      RTEnvUnsetEx
     */
    int unsetVariable(const com::Utf8Str &rName)
    {
        return RTEnvUnsetEx(m_hEnv, rName.c_str());
    }

protected:
    /**
     * Copy constructor.
     * @throws HRESULT
     */
    GuestEnvironmentBase(const GuestEnvironmentBase &rThat, bool fChangeRecord, uint32_t fFlags = 0)
        : m_hEnv(NIL_RTENV)
        , m_cRefs(1)
        , m_fFlags(fFlags)
    {
        int rc = cloneCommon(rThat, fChangeRecord);
        if (RT_FAILURE(rc))
            throw (Global::vboxStatusCodeToCOM(rc));
    }

    /**
     * Common clone/copy method with type conversion abilities.
     *
     * @returns IPRT status code.
     * @param   rThat           The object to clone.
     * @param   fChangeRecord   Whether the this instance is a change record (true)
     *                          or normal (false) environment.
     */
    int cloneCommon(const GuestEnvironmentBase &rThat, bool fChangeRecord)
    {
        int   rc = VINF_SUCCESS;
        RTENV hNewEnv = NIL_RTENV;
        if (rThat.m_hEnv != NIL_RTENV)
        {
            /*
             * Clone it.
             */
            if (RTEnvIsChangeRecord(rThat.m_hEnv) == fChangeRecord)
                rc = RTEnvClone(&hNewEnv, rThat.m_hEnv);
            else
            {
                /* Need to type convert it. */
                if (fChangeRecord)
                    rc = RTEnvCreateChangeRecordEx(&hNewEnv, rThat.m_fFlags);
                else
                    rc = RTEnvCreateEx(&hNewEnv, rThat.m_fFlags);
                if (RT_SUCCESS(rc))
                {
                    rc = RTEnvApplyChanges(hNewEnv, rThat.m_hEnv);
                    if (RT_FAILURE(rc))
                        RTEnvDestroy(hNewEnv);
                }
            }
        }
        else
        {
            /*
             * Create an empty one so the object works smoothly.
             * (Relevant for GuestProcessStartupInfo and internal commands.)
             */
            if (fChangeRecord)
                rc = RTEnvCreateChangeRecordEx(&hNewEnv, rThat.m_fFlags);
            else
                rc = RTEnvCreateEx(&hNewEnv, rThat.m_fFlags);
        }
        if (RT_SUCCESS(rc))
        {
            RTEnvDestroy(m_hEnv);
            m_hEnv = hNewEnv;
            m_fFlags = rThat.m_fFlags;
        }
        return rc;
    }


    /** The environment change record. */
    RTENV               m_hEnv;
    /** Reference counter. */
    uint32_t volatile   m_cRefs;
    /** RTENV_CREATE_F_XXX. */
    uint32_t            m_fFlags;
};

class GuestEnvironmentChanges;


/**
 * Wrapper around the RTEnv API for a normal environment.
 */
class GuestEnvironment : public GuestEnvironmentBase
{
public:
    /**
     * Default constructor.
     *
     * The user must invoke one of the init methods before using the object.
     */
    GuestEnvironment(void)
        : GuestEnvironmentBase()
    { }

    /**
     * Copy operator.
     * @param   rThat       The object to copy.
     * @throws HRESULT
     */
    GuestEnvironment(const GuestEnvironment &rThat)
        : GuestEnvironmentBase(rThat, false /*fChangeRecord*/)
    { }

    /**
     * Copy operator.
     * @param   rThat       The object to copy.
     * @throws HRESULT
     */
    GuestEnvironment(const GuestEnvironmentBase &rThat)
        : GuestEnvironmentBase(rThat, false /*fChangeRecord*/)
    { }

    /**
     * Initialize this as a normal environment block.
     * @returns IPRT status code.
     * @param   fFlags      RTENV_CREATE_F_XXX
     */
    int initNormal(uint32_t fFlags)
    {
        AssertReturn(m_hEnv == NIL_RTENV, VERR_WRONG_ORDER);
        m_fFlags = fFlags;
        return RTEnvCreateEx(&m_hEnv, fFlags);
    }

    /**
     * Replaces this environemnt with that in @a rThat.
     *
     * @returns IPRT status code
     * @param   rThat       The environment to copy. If it's a different type
     *                      we'll convert the data to a normal environment block.
     */
    int copy(const GuestEnvironmentBase &rThat)
    {
        return cloneCommon(rThat, false /*fChangeRecord*/);
    }

    /**
     * @copydoc copy()
     */
    GuestEnvironment &operator=(const GuestEnvironmentBase &rThat)
    {
        int rc = copy(rThat);
        if (RT_FAILURE(rc))
            throw (Global::vboxStatusCodeToCOM(rc));
        return *this;
    }

    /** @copydoc copy() */
    GuestEnvironment &operator=(const GuestEnvironment &rThat)
    {   return operator=((const GuestEnvironmentBase &)rThat); }

    /** @copydoc copy() */
    GuestEnvironment &operator=(const GuestEnvironmentChanges &rThat)
    {   return operator=((const GuestEnvironmentBase &)rThat); }

};


/**
 * Wrapper around the RTEnv API for a environment change record.
 *
 * This class is used as a record of changes to be applied to a different
 * environment block (in VBoxService before launching a new process).
 */
class GuestEnvironmentChanges : public GuestEnvironmentBase
{
public:
    /**
     * Default constructor.
     *
     * The user must invoke one of the init methods before using the object.
     */
    GuestEnvironmentChanges(void)
        : GuestEnvironmentBase()
    { }

    /**
     * Copy operator.
     * @param   rThat       The object to copy.
     * @throws HRESULT
     */
    GuestEnvironmentChanges(const GuestEnvironmentChanges &rThat)
        : GuestEnvironmentBase(rThat, true /*fChangeRecord*/)
    { }

    /**
     * Copy operator.
     * @param   rThat       The object to copy.
     * @throws HRESULT
     */
    GuestEnvironmentChanges(const GuestEnvironmentBase &rThat)
        : GuestEnvironmentBase(rThat, true /*fChangeRecord*/)
    { }

    /**
     * Initialize this as a environment change record.
     * @returns IPRT status code.
     * @param   fFlags      RTENV_CREATE_F_XXX
     */
    int initChangeRecord(uint32_t fFlags)
    {
        AssertReturn(m_hEnv == NIL_RTENV, VERR_WRONG_ORDER);
        m_fFlags = fFlags;
        return RTEnvCreateChangeRecordEx(&m_hEnv, fFlags);
    }

    /**
     * Replaces this environemnt with that in @a rThat.
     *
     * @returns IPRT status code
     * @param   rThat       The environment to copy. If it's a different type
     *                      we'll convert the data to a set of changes.
     */
    int copy(const GuestEnvironmentBase &rThat)
    {
        return cloneCommon(rThat, true /*fChangeRecord*/);
    }

    /**
     * @copydoc copy()
     */
    GuestEnvironmentChanges &operator=(const GuestEnvironmentBase &rThat)
    {
        int rc = copy(rThat);
        if (RT_FAILURE(rc))
            throw (Global::vboxStatusCodeToCOM(rc));
        return *this;
    }

    /** @copydoc copy() */
    GuestEnvironmentChanges &operator=(const GuestEnvironmentChanges &rThat)
    {   return operator=((const GuestEnvironmentBase &)rThat); }

    /** @copydoc copy() */
    GuestEnvironmentChanges &operator=(const GuestEnvironment &rThat)
    {   return operator=((const GuestEnvironmentBase &)rThat); }
};

/**
 * Class for keeping guest error information.
 */
class GuestErrorInfo
{
public:

    /**
     * Enumeration for specifying the guest error type.
     */
    enum Type
    {
        /** Guest error is anonymous. Avoid this. */
        Type_Anonymous = 0,
        /** Guest error is from a guest session. */
        Type_Session,
        /** Guest error is from a guest process. */
        Type_Process,
        /** Guest error is from a guest file object. */
        Type_File,
        /** Guest error is from a guest directory object. */
        Type_Directory,
        /** Guest error is from a the built-in toolbox "vbox_cat" command. */
        Type_ToolCat,
        /** Guest error is from a the built-in toolbox "vbox_ls" command. */
        Type_ToolLs,
        /** Guest error is from a the built-in toolbox "vbox_rm" command. */
        Type_ToolRm,
        /** Guest error is from a the built-in toolbox "vbox_mkdir" command. */
        Type_ToolMkDir,
        /** Guest error is from a the built-in toolbox "vbox_mktemp" command. */
        Type_ToolMkTemp,
        /** Guest error is from a the built-in toolbox "vbox_stat" command. */
        Type_ToolStat,
        /** The usual 32-bit hack. */
        Type_32BIT_HACK = 0x7fffffff
    };

    /**
     * Initialization constructor.
     *
     * @param   eType           Error type to use.
     * @param   rc              IPRT-style rc to use.
     * @param   pcszWhat        Subject to use.
     */
    GuestErrorInfo(GuestErrorInfo::Type eType, int rc, const char *pcszWhat)
    {
        int rc2 = setV(eType, rc, pcszWhat);
        if (RT_FAILURE(rc2))
            throw rc2;
    }

    /**
     * Returns the (IPRT-style) rc of this error.
     *
     * @returns VBox status code.
     */
    int getRc(void) const { return mRc; }

    /**
     * Returns the type of this error.
     *
     * @returns Error type.
     */
    Type getType(void) const { return mType; }

    /**
     * Returns the subject of this error.
     *
     * @returns Subject as a string.
     */
    Utf8Str getWhat(void) const { return mWhat; }

    /**
     * Sets the error information using a variable arguments list (va_list).
     *
     * @returns VBox status code.
     * @param   eType           Error type to use.
     * @param   rc              IPRT-style rc to use.
     * @param   pcszWhat        Subject to use.
     */
    int setV(GuestErrorInfo::Type eType, int rc, const char *pcszWhat)
    {
        mType = eType;
        mRc   = rc;
        mWhat = pcszWhat;

        return VINF_SUCCESS;
    }

protected:

    /** Error type. */
    Type    mType;
    /** IPRT-style error code. */
    int     mRc;
    /** Subject string related to this error. */
    Utf8Str mWhat;
};

/**
 * Structure for keeping all the relevant guest directory
 * information around.
 */
struct GuestDirectoryOpenInfo
{
    GuestDirectoryOpenInfo(void)
        : mFlags(0) { }

    /** The directory path. */
    Utf8Str                 mPath;
    /** Then open filter. */
    Utf8Str                 mFilter;
    /** Opening flags. */
    uint32_t                mFlags;
};


/**
 * Structure for keeping all the relevant guest file
 * information around.
 */
struct GuestFileOpenInfo
{
    GuestFileOpenInfo(void)
        : mAccessMode((FileAccessMode_T)0)
        , mOpenAction((FileOpenAction_T)0)
        , mSharingMode((FileSharingMode_T)0)
        , mCreationMode(0)
        , mfOpenEx(0) { }

    /** The filename. */
    Utf8Str                 mFilename;
    /** The file access mode. */
    FileAccessMode_T        mAccessMode;
    /** The file open action.  */
    FileOpenAction_T        mOpenAction;
    /** The file sharing mode. */
    FileSharingMode_T       mSharingMode;
    /** Octal creation mode. */
    uint32_t                mCreationMode;
    /** Extended open flags (currently none defined). */
    uint32_t                mfOpenEx;
};


/**
 * Structure representing information of a
 * file system object.
 */
struct GuestFsObjData
{
    GuestFsObjData(void)
        : mType(FsObjType_Unknown)
        , mObjectSize(0)
        , mAllocatedSize(0)
        , mAccessTime(0)
        , mBirthTime(0)
        , mChangeTime(0)
        , mModificationTime(0)
        , mUID(0)
        , mGID(0)
        , mNodeID(0)
        , mNodeIDDevice(0)
        , mNumHardLinks(0)
        , mDeviceNumber(0)
        , mGenerationID(0)
        , mUserFlags(0) { }

    /** @name Helper functions to extract the data from a certin VBoxService tool's guest stream block.
     * @{ */
    int FromLs(const GuestProcessStreamBlock &strmBlk, bool fLong);
    int FromStat(const GuestProcessStreamBlock &strmBlk);
    int FromMkTemp(const GuestProcessStreamBlock &strmBlk);
    /** @}  */

    /** @name Static helper functions to work with time from stream block keys.
     * @{ */
    static PRTTIMESPEC TimeSpecFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey, PRTTIMESPEC pTimeSpec);
    static int64_t UnixEpochNsFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey);
    /** @}  */

    /** @name helper functions to work with IPRT stuff.
     * @{ */
    RTFMODE GetFileMode(void) const;
    /** @}  */

    Utf8Str              mName;
    FsObjType_T          mType;
    Utf8Str              mFileAttrs;
    int64_t              mObjectSize;
    int64_t              mAllocatedSize;
    int64_t              mAccessTime;
    int64_t              mBirthTime;
    int64_t              mChangeTime;
    int64_t              mModificationTime;
    Utf8Str              mUserName;
    int32_t              mUID;
    int32_t              mGID;
    Utf8Str              mGroupName;
    Utf8Str              mACL;
    int64_t              mNodeID;
    uint32_t             mNodeIDDevice;
    uint32_t             mNumHardLinks;
    uint32_t             mDeviceNumber;
    uint32_t             mGenerationID;
    uint32_t             mUserFlags;
};


/**
 * Structure for keeping all the relevant guest session
 * startup parameters around.
 */
class GuestSessionStartupInfo
{
public:

    GuestSessionStartupInfo(void)
        : mID(UINT32_MAX)
        , mIsInternal(false /* Non-internal session */)
        , mOpenTimeoutMS(30 * 1000 /* 30s opening timeout */)
        , mOpenFlags(0 /* No opening flags set */) { }

    /** The session's friendly name. Optional. */
    Utf8Str                     mName;
    /** The session's unique ID. Used to encode a context ID.
     *  UINT32_MAX if not initialized. */
    uint32_t                    mID;
    /** Flag indicating if this is an internal session
     *  or not. Internal session are not accessible by
     *  public API clients. */
    bool                        mIsInternal;
    /** Timeout (in ms) used for opening the session. */
    uint32_t                    mOpenTimeoutMS;
    /** Session opening flags. */
    uint32_t                    mOpenFlags;
};


/**
 * Structure for keeping all the relevant guest process
 * startup parameters around.
 */
class GuestProcessStartupInfo
{
public:

    GuestProcessStartupInfo(void)
        : mFlags(ProcessCreateFlag_None)
        , mTimeoutMS(UINT32_MAX /* No timeout by default */)
        , mPriority(ProcessPriority_Default)
        , mAffinity(0) { }

    /** The process' friendly name. */
    Utf8Str                     mName;
    /** The executable. */
    Utf8Str                     mExecutable;
    /** Arguments vector (starting with argument \#0). */
    ProcessArguments            mArguments;
    /** The process environment change record.  */
    GuestEnvironmentChanges     mEnvironmentChanges;
    /** Process creation flags. */
    uint32_t                    mFlags;
    /** Timeout (in ms) the process is allowed to run.
     *  Specify UINT32_MAX if no timeout (unlimited run time) is given. */
    ULONG                       mTimeoutMS;
    /** Process priority. */
    ProcessPriority_T           mPriority;
    /** Process affinity. At the moment we
     *  only support 64 VCPUs. API and
     *  guest can do more already!  */
    uint64_t                    mAffinity;
};


/**
 * Class representing the "value" side of a "key=value" pair.
 */
class GuestProcessStreamValue
{
public:

    GuestProcessStreamValue(void) { }
    GuestProcessStreamValue(const char *pszValue)
        : mValue(pszValue) {}

    GuestProcessStreamValue(const GuestProcessStreamValue& aThat)
           : mValue(aThat.mValue) { }

    /** Copy assignment operator. */
    GuestProcessStreamValue &operator=(GuestProcessStreamValue const &a_rThat) RT_NOEXCEPT
    {
        mValue = a_rThat.mValue;

        return *this;
    }

    Utf8Str mValue;
};

/** Map containing "key=value" pairs of a guest process stream. */
typedef std::pair< Utf8Str, GuestProcessStreamValue > GuestCtrlStreamPair;
typedef std::map < Utf8Str, GuestProcessStreamValue > GuestCtrlStreamPairMap;
typedef std::map < Utf8Str, GuestProcessStreamValue >::iterator GuestCtrlStreamPairMapIter;
typedef std::map < Utf8Str, GuestProcessStreamValue >::const_iterator GuestCtrlStreamPairMapIterConst;

/**
 * Class representing a block of stream pairs (key=value). Each block in a raw guest
 * output stream is separated by "\0\0", each pair is separated by "\0". The overall
 * end of a guest stream is marked by "\0\0\0\0".
 */
class GuestProcessStreamBlock
{
public:

    GuestProcessStreamBlock(void);

    virtual ~GuestProcessStreamBlock(void);

public:

    void Clear(void);

#ifdef DEBUG
    void DumpToLog(void) const;
#endif

    const char *GetString(const char *pszKey) const;
    size_t      GetCount(void) const;
    int         GetRc(void) const;
    int         GetInt64Ex(const char *pszKey, int64_t *piVal) const;
    int64_t     GetInt64(const char *pszKey) const;
    int         GetUInt32Ex(const char *pszKey, uint32_t *puVal) const;
    uint32_t    GetUInt32(const char *pszKey, uint32_t uDefault = 0) const;
    int32_t     GetInt32(const char *pszKey, int32_t iDefault = 0) const;

    bool        IsEmpty(void) { return mPairs.empty(); }

    int         SetValue(const char *pszKey, const char *pszValue);

protected:

    GuestCtrlStreamPairMap mPairs;
};

/** Vector containing multiple allocated stream pair objects. */
typedef std::vector< GuestProcessStreamBlock > GuestCtrlStreamObjects;
typedef std::vector< GuestProcessStreamBlock >::iterator GuestCtrlStreamObjectsIter;
typedef std::vector< GuestProcessStreamBlock >::const_iterator GuestCtrlStreamObjectsIterConst;

/**
 * Class for parsing machine-readable guest process output by VBoxService'
 * toolbox commands ("vbox_ls", "vbox_stat" etc), aka "guest stream".
 */
class GuestProcessStream
{

public:

    GuestProcessStream();

    virtual ~GuestProcessStream();

public:

    int AddData(const BYTE *pbData, size_t cbData);

    void Destroy();

#ifdef DEBUG
    void Dump(const char *pszFile);
#endif

    size_t GetOffset() { return m_offBuffer; }

    size_t GetSize() { return m_cbUsed; }

    int ParseBlock(GuestProcessStreamBlock &streamBlock);

protected:

    /** Maximum allowed size the stream buffer can grow to.
     *  Defaults to 32 MB. */
    size_t m_cbMax;
    /** Currently allocated size of internal stream buffer. */
    size_t m_cbAllocated;
    /** Currently used size at m_offBuffer. */
    size_t m_cbUsed;
    /** Current byte offset within the internal stream buffer. */
    size_t m_offBuffer;
    /** Internal stream buffer. */
    BYTE  *m_pbBuffer;
};

class Guest;
class Progress;

class GuestWaitEventPayload
{

public:

    GuestWaitEventPayload(void)
        : uType(0),
          cbData(0),
          pvData(NULL) { }

    /**
     * Initialization constructor. Will throw() VBox status code (rc).
     *
     * @param   uTypePayload    Payload type to set.
     * @param   pvPayload       Pointer to payload data to set (deep copy).
     * @param   cbPayload       Size (in bytes) of payload data to set.
     */
    GuestWaitEventPayload(uint32_t uTypePayload,
                          const void *pvPayload, uint32_t cbPayload)
        : uType(0),
          cbData(0),
          pvData(NULL)
    {
        int rc = copyFrom(uTypePayload, pvPayload, cbPayload);
        if (RT_FAILURE(rc))
            throw rc;
    }

    virtual ~GuestWaitEventPayload(void)
    {
        Clear();
    }

    GuestWaitEventPayload& operator=(const GuestWaitEventPayload &that)
    {
        CopyFromDeep(that);
        return *this;
    }

public:

    void Clear(void)
    {
        if (pvData)
        {
            Assert(cbData);
            RTMemFree(pvData);
            cbData = 0;
            pvData = NULL;
        }
        uType = 0;
    }

    int CopyFromDeep(const GuestWaitEventPayload &payload)
    {
        return copyFrom(payload.uType, payload.pvData, payload.cbData);
    }

    const void* Raw(void) const { return pvData; }

    size_t Size(void) const { return cbData; }

    uint32_t Type(void) const { return uType; }

    void* MutableRaw(void) { return pvData; }

    Utf8Str ToString(void)
    {
        const char  *pszStr = (const char *)pvData;
              size_t cbStr  = cbData;

        if (RT_FAILURE(RTStrValidateEncodingEx(pszStr, cbStr,
                                               RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED | RTSTR_VALIDATE_ENCODING_EXACT_LENGTH)))
        {
            AssertFailed();
            return "";
        }

        return Utf8Str(pszStr, cbStr);
    }

protected:

    int copyFrom(uint32_t uTypePayload, const void *pvPayload, uint32_t cbPayload)
    {
        if (cbPayload > _64K) /* Paranoia. */
            return VERR_TOO_MUCH_DATA;

        Clear();

        int rc = VINF_SUCCESS;

        if (cbPayload)
        {
            pvData = RTMemAlloc(cbPayload);
            if (pvData)
            {
                uType = uTypePayload;

                memcpy(pvData, pvPayload, cbPayload);
                cbData = cbPayload;
            }
            else
                rc = VERR_NO_MEMORY;
        }
        else
        {
            uType = uTypePayload;

            pvData = NULL;
            cbData = 0;
        }

        return rc;
    }

protected:

    /** Type of payload. */
    uint32_t uType;
    /** Size (in bytes) of payload. */
    uint32_t cbData;
    /** Pointer to actual payload data. */
    void    *pvData;
};

class GuestWaitEventBase
{

protected:

    GuestWaitEventBase(void);
    virtual ~GuestWaitEventBase(void);

public:

    uint32_t                        ContextID(void) { return mCID; };
    int                             GuestResult(void) { return mGuestRc; }
    int                             Result(void) { return mRc; }
    GuestWaitEventPayload &         Payload(void) { return mPayload; }
    int                             SignalInternal(int rc, int guestRc, const GuestWaitEventPayload *pPayload);
    int                             Wait(RTMSINTERVAL uTimeoutMS);

protected:

    int             Init(uint32_t uCID);

protected:

    /* Shutdown indicator. */
    bool                       mfAborted;
    /* Associated context ID (CID). */
    uint32_t                   mCID;
    /** The event semaphore for triggering
     *  the actual event. */
    RTSEMEVENT                 mEventSem;
    /** The event's overall result. If
     *  set to VERR_GSTCTL_GUEST_ERROR,
     *  mGuestRc will contain the actual
     *  error code from the guest side. */
    int                        mRc;
    /** The event'S overall result from the
     *  guest side. If used, mRc must be
     *  set to VERR_GSTCTL_GUEST_ERROR. */
    int                        mGuestRc;
    /** The event's payload data. Optional. */
    GuestWaitEventPayload      mPayload;
};

/** List of public guest event types. */
typedef std::list < VBoxEventType_T > GuestEventTypes;

class GuestWaitEvent : public GuestWaitEventBase
{

public:

    GuestWaitEvent(void);
    virtual ~GuestWaitEvent(void);

public:

    int                              Init(uint32_t uCID);
    int                              Init(uint32_t uCID, const GuestEventTypes &lstEvents);
    int                              Cancel(void);
    const ComPtr<IEvent>             Event(void) { return mEvent; }
    bool                             HasGuestError(void) const { return mRc == VERR_GSTCTL_GUEST_ERROR; }
    int                              GetGuestError(void) const { return mGuestRc; }
    int                              SignalExternal(IEvent *pEvent);
    const GuestEventTypes           &Types(void) { return mEventTypes; }
    size_t                           TypeCount(void) { return mEventTypes.size(); }

protected:

    /** List of public event types this event should
     *  be signalled on. Optional. */
    GuestEventTypes            mEventTypes;
    /** Pointer to the actual public event, if any. */
    ComPtr<IEvent>             mEvent;
};
/** Map of pointers to guest events. The primary key
 *  contains the context ID. */
typedef std::map < uint32_t, GuestWaitEvent* > GuestWaitEvents;
/** Map of wait events per public guest event. Nice for
 *  faster lookups when signalling a whole event group. */
typedef std::map < VBoxEventType_T, GuestWaitEvents > GuestEventGroup;

class GuestBase
{

public:

    GuestBase(void);
    virtual ~GuestBase(void);

public:

    /** Signals a wait event using a public guest event; also used for
     *  for external event listeners. */
    int signalWaitEvent(VBoxEventType_T aType, IEvent *aEvent);
    /** Signals a wait event using a guest rc. */
    int signalWaitEventInternal(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, int guestRc, const GuestWaitEventPayload *pPayload);
    /** Signals a wait event without letting public guest events know,
     *  extended director's cut version. */
    int signalWaitEventInternalEx(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, int rc, int guestRc, const GuestWaitEventPayload *pPayload);

public:

    int baseInit(void);
    void baseUninit(void);
    int cancelWaitEvents(void);
    int dispatchGeneric(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
    int generateContextID(uint32_t uSessionID, uint32_t uObjectID, uint32_t *puContextID);
    int registerWaitEvent(uint32_t uSessionID, uint32_t uObjectID, GuestWaitEvent **ppEvent);
    int registerWaitEventEx(uint32_t uSessionID, uint32_t uObjectID, const GuestEventTypes &lstEvents, GuestWaitEvent **ppEvent);
    int unregisterWaitEvent(GuestWaitEvent *pEvent);
    int waitForEvent(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, VBoxEventType_T *pType, IEvent **ppEvent);

#ifndef VBOX_GUESTCTRL_TEST_CASE
    HRESULT setErrorExternal(VirtualBoxBase *pInterface, const Utf8Str &strAction, const GuestErrorInfo &guestErrorInfo);
#endif

public:

    static FsObjType_T fileModeToFsObjType(RTFMODE fMode);
    static Utf8Str getErrorAsString(const Utf8Str &strAction, const GuestErrorInfo& guestErrorInfo);
    static Utf8Str getErrorAsString(const GuestErrorInfo &guestErrorInfo);

protected:

    /** Pointer to the console object. Needed
     *  for HGCM (VMMDev) communication. */
    Console                 *mConsole;
    /**  The next context ID counter component for this object. */
    uint32_t                 mNextContextID;
    /** Local listener for handling the waiting events
     *  internally. */
    ComPtr<IEventListener>   mLocalListener;
    /** Critical section for wait events access. */
    RTCRITSECT               mWaitEventCritSect;
    /** Map of registered wait events per event group. */
    GuestEventGroup          mWaitEventGroups;
    /** Map of registered wait events. */
    GuestWaitEvents          mWaitEvents;
};

/**
 * Virtual class (interface) for guest objects (processes, files, ...) --
 * contains all per-object callback management.
 */
class GuestObject : public GuestBase
{
    friend class GuestSession;

public:

    GuestObject(void);
    virtual ~GuestObject(void);

public:

    ULONG getObjectID(void) { return mObjectID; }

protected:

    /**
     * Called by IGuestSession when the session status has been changed.
     *
     * @returns VBox status code.
     * @param   enmSessionStatus    New session status.
     */
    virtual int i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus) = 0;

    /**
     * Called by IGuestSession right before this object gets
     * unregistered (removed) from the public object list.
     */
    virtual int i_onUnregister(void) = 0;

    /** Callback dispatcher -- must be implemented by the actual object. */
    virtual int i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) = 0;

protected:

    int bindToSession(Console *pConsole, GuestSession *pSession, uint32_t uObjectID);
    int registerWaitEvent(const GuestEventTypes &lstEvents, GuestWaitEvent **ppEvent);
    int sendMessage(uint32_t uFunction, uint32_t cParms, PVBOXHGCMSVCPARM paParms);

protected:

    /** @name Common parameters for all derived objects.  They have their own
     * mData structure to keep their specific data around.
     * @{ */
    /** Pointer to parent session. Per definition
     *  this objects *always* lives shorter than the
     *  parent.
     * @todo r=bird: When wanting to use mSession in the
     * IGuestProcess::getEnvironment() implementation I wanted to access
     * GuestSession::mData::mpBaseEnvironment.  Seeing the comment in
     * GuestProcess::terminate() saying:
     *      "Now only API clients still can hold references to it."
     * and recalling seeing similar things in VirtualBox.xidl or some such place,
     * I'm wondering how this "per definition" behavior is enforced.  Is there any
     * GuestProcess:uninit() call or similar magic that invalidates objects that
     * GuestSession loses track of in place like GuestProcess::terminate() that I've
     * failed to spot?
     *
     * Please enlighten me.
     */
    GuestSession            *mSession;
    /** The object ID -- must be unique for each guest
     *  object and is encoded into the context ID. Must
     *  be set manually when initializing the object.
     *
     *  For guest processes this is the internal PID,
     *  for guest files this is the internal file ID. */
    uint32_t                 mObjectID;
    /** @} */
};
#endif /* !MAIN_INCLUDED_GuestCtrlImplPrivate_h */

