diff options
Diffstat (limited to 'src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.cpp')
-rw-r--r-- | src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.cpp | 2612 |
1 files changed, 2612 insertions, 0 deletions
diff --git a/src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.cpp b/src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.cpp new file mode 100644 index 00000000..c61f9919 --- /dev/null +++ b/src/VBox/HostServices/GuestControl/VBoxGuestControlSvc.cpp @@ -0,0 +1,2612 @@ +/* $Id: VBoxGuestControlSvc.cpp $ */ +/** @file + * Guest Control Service: Controlling the guest. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_svc_guest_control Guest Control HGCM Service + * + * This service acts as a proxy for handling and buffering host message requests + * and clients on the guest. It tries to be as transparent as possible to let + * the guest (client) and host side do their protocol handling as desired. + * + * The following terms are used: + * - Host: A host process (e.g. VBoxManage or another tool utilizing the Main API) + * which wants to control something on the guest. + * - Client: A client (e.g. VBoxService) running inside the guest OS waiting for + * new host messages to perform. There can be multiple clients connected + * to this service. A client is represented by its unique HGCM client ID. + * - Context ID: An (almost) unique ID automatically generated on the host (Main API) + * to not only distinguish clients but individual requests. Because + * the host does not know anything about connected clients it needs + * an indicator which it can refer to later. This context ID gets + * internally bound by the service to a client which actually processes + * the message in order to have a relationship between client<->context ID(s). + * + * The host can trigger messages which get buffered by the service (with full HGCM + * parameter info). As soon as a client connects (or is ready to do some new work) + * it gets a buffered host message to process it. This message then will be immediately + * removed from the message list. If there are ready clients but no new messages to be + * processed, these clients will be set into a deferred state (that is being blocked + * to return until a new host message is available). + * + * If a client needs to inform the host that something happened, it can send a + * message to a low level HGCM callback registered in Main. This callback contains + * the actual data as well as the context ID to let the host do the next necessary + * steps for this context. This context ID makes it possible to wait for an event + * inside the host's Main API function (like starting a process on the guest and + * wait for getting its PID returned by the client) as well as cancelling blocking + * host calls in order the client terminated/crashed (HGCM detects disconnected + * clients and reports it to this service's callback). + * + * Starting at VBox 4.2 the context ID itself consists of a session ID, an object + * ID (for example a process or file ID) and a count. This is necessary to not break + * compatibility between older hosts and to manage guest session on the host. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_CONTROL +#include <VBox/HostServices/GuestControlSvc.h> +#include <VBox/GuestHost/GuestControl.h> /** @todo r=bird: Why two headers??? */ + +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/AssertGuest.h> +#include <VBox/VMMDev.h> +#include <VBox/vmm/ssm.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <iprt/assert.h> +#include <iprt/cpp/autores.h> +#include <iprt/cpp/utils.h> +#include <iprt/mem.h> +#include <iprt/list.h> +#include <iprt/req.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include <map> +#include <new> /* for std::nothrow*/ + + +using namespace guestControl; + + +/** + * Structure for maintaining a request. + */ +typedef struct ClientRequest +{ + /** The call handle */ + VBOXHGCMCALLHANDLE mHandle; + /** Number of parameters */ + uint32_t mNumParms; + /** The call parameters */ + VBOXHGCMSVCPARM *mParms; + /** The default constructor. */ + ClientRequest(void) + : mHandle(0), mNumParms(0), mParms(NULL) + {} +} ClientRequest; + +/** + * Structure for holding a buffered host message which has + * not been processed yet. + */ +typedef struct HostMsg +{ + /** Entry on the ClientState::m_HostMsgList list. */ + RTLISTNODE m_ListEntry; + union + { + /** The top two twomost bits are exploited for message destination. + * See VBOX_GUESTCTRL_DST_XXX. */ + uint64_t m_idContextAndDst; + /** The context ID this message belongs to (extracted from the first parameter). */ + uint32_t m_idContext; + }; + /** Dynamic structure for holding the HGCM parms */ + uint32_t mType; + /** Number of HGCM parameters. */ + uint32_t mParmCount; + /** Array of HGCM parameters. */ + PVBOXHGCMSVCPARM mpParms; + /** Set if we detected the message skipping hack from r121400. */ + bool m_f60BetaHackInPlay; + + HostMsg() + : m_idContextAndDst(0) + , mType(UINT32_MAX) + , mParmCount(0) + , mpParms(NULL) + , m_f60BetaHackInPlay(false) + { + RTListInit(&m_ListEntry); + } + + /** + * Releases the host message, properly deleting it if no further references. + */ + void Delete(void) + { + LogFlowThisFunc(("[Msg %RU32 (%s)] destroying\n", mType, GstCtrlHostMsgtoStr((eHostMsg)mType))); + if (mpParms) + { + for (uint32_t i = 0; i < mParmCount; i++) + if (mpParms[i].type == VBOX_HGCM_SVC_PARM_PTR) + { + RTMemFree(mpParms[i].u.pointer.addr); + mpParms[i].u.pointer.addr = NULL; + } + RTMemFree(mpParms); + mpParms = NULL; + } + mParmCount = 0; + delete this; + } + + + /** + * Initializes the message. + * + * The specified parameters are copied and any buffers referenced by it + * duplicated as well. + * + * @returns VBox status code. + * @param idMsg The host message number, eHostMsg. + * @param cParms Number of parameters in the HGCM request. + * @param paParms Array of parameters. + */ + int Init(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) + { + LogFlowThisFunc(("[Msg %RU32 (%s)] Allocating cParms=%RU32, paParms=%p\n", + idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), cParms, paParms)); + Assert(mpParms == NULL); + Assert(mParmCount == 0); + Assert(RTListIsEmpty(&m_ListEntry)); + + /* + * Fend of bad stuff. + */ + AssertReturn(cParms > 0, VERR_WRONG_PARAMETER_COUNT); /* At least one parameter (context ID) must be present. */ + AssertReturn(cParms < VMMDEV_MAX_HGCM_PARMS, VERR_WRONG_PARAMETER_COUNT); + AssertPtrReturn(paParms, VERR_INVALID_POINTER); + + /* + * The first parameter is the context ID and the message destination mask. + */ + if (paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT) + { + m_idContextAndDst = paParms[0].u.uint64; + AssertReturn(m_idContextAndDst & VBOX_GUESTCTRL_DST_BOTH, VERR_INTERNAL_ERROR_3); + } + else if (paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) + { + AssertMsgFailed(("idMsg=%u %s - caller must set dst!\n", idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg))); + m_idContextAndDst = paParms[0].u.uint32 | VBOX_GUESTCTRL_DST_BOTH; + } + else + AssertFailedReturn(VERR_WRONG_PARAMETER_TYPE); + + /* + * Just make a copy of the parameters and any buffers. + */ + mType = idMsg; + mParmCount = cParms; + mpParms = (VBOXHGCMSVCPARM *)RTMemAllocZ(sizeof(VBOXHGCMSVCPARM) * mParmCount); + AssertReturn(mpParms, VERR_NO_MEMORY); + + for (uint32_t i = 0; i < cParms; i++) + { + mpParms[i].type = paParms[i].type; + switch (paParms[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: + mpParms[i].u.uint32 = paParms[i].u.uint32; + break; + + case VBOX_HGCM_SVC_PARM_64BIT: + mpParms[i].u.uint64 = paParms[i].u.uint64; + break; + + case VBOX_HGCM_SVC_PARM_PTR: + mpParms[i].u.pointer.size = paParms[i].u.pointer.size; + if (mpParms[i].u.pointer.size > 0) + { + mpParms[i].u.pointer.addr = RTMemDup(paParms[i].u.pointer.addr, mpParms[i].u.pointer.size); + AssertReturn(mpParms[i].u.pointer.addr, VERR_NO_MEMORY); + } + /* else: structure is zeroed by allocator. */ + break; + + default: + AssertMsgFailedReturn(("idMsg=%u (%s) parameter #%u: type=%u\n", + idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), i, paParms[i].type), + VERR_WRONG_PARAMETER_TYPE); + } + } + + /* + * Morph the first parameter back to 32-bit. + */ + mpParms[0].type = VBOX_HGCM_SVC_PARM_32BIT; + mpParms[0].u.uint32 = (uint32_t)paParms[0].u.uint64; + + return VINF_SUCCESS; + } + + + /** + * Sets the GUEST_MSG_PEEK_WAIT GUEST_MSG_PEEK_NOWAIT return parameters. + * + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters (at least two). + * @remarks ASSUMES the parameters has been cleared by clientMsgPeek. + */ + inline void setPeekReturn(PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) + { + Assert(cDstParms >= 2); + if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) + paDstParms[0].u.uint32 = mType; + else + paDstParms[0].u.uint64 = mType; + paDstParms[1].u.uint32 = mParmCount; + + uint32_t i = RT_MIN(cDstParms, mParmCount + 2); + while (i-- > 2) + switch (mpParms[i - 2].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break; + case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break; + case VBOX_HGCM_SVC_PARM_PTR: paDstParms[i].u.uint32 = mpParms[i - 2].u.pointer.size; break; + } + } + + + /** @name Support for old-style (GUEST_MSG_WAIT) operation. + * @{ + */ + + /** + * Worker for Assign() that opies data from the buffered HGCM request to the + * current HGCM request. + * + * @returns VBox status code. + * @param paDstParms Array of parameters of HGCM request to fill the data into. + * @param cDstParms Number of parameters the HGCM request can handle. + */ + int CopyTo(VBOXHGCMSVCPARM paDstParms[], uint32_t cDstParms) const + { + LogFlowThisFunc(("[Msg %RU32] mParmCount=%RU32, m_idContext=%RU32 (Session %RU32)\n", + mType, mParmCount, m_idContext, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(m_idContext))); + + int rc = VINF_SUCCESS; + if (cDstParms != mParmCount) + { + LogFlowFunc(("Parameter count does not match (got %RU32, expected %RU32)\n", + cDstParms, mParmCount)); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < mParmCount; i++) + { + if (paDstParms[i].type != mpParms[i].type) + { + LogFunc(("Parameter %RU32 type mismatch (got %RU32, expected %RU32)\n", i, paDstParms[i].type, mpParms[i].type)); + rc = VERR_INVALID_PARAMETER; + } + else + { + switch (mpParms[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: +#ifdef DEBUG_andy + LogFlowFunc(("\tmpParms[%RU32] = %RU32 (uint32_t)\n", + i, mpParms[i].u.uint32)); +#endif + paDstParms[i].u.uint32 = mpParms[i].u.uint32; + break; + + case VBOX_HGCM_SVC_PARM_64BIT: +#ifdef DEBUG_andy + LogFlowFunc(("\tmpParms[%RU32] = %RU64 (uint64_t)\n", + i, mpParms[i].u.uint64)); +#endif + paDstParms[i].u.uint64 = mpParms[i].u.uint64; + break; + + case VBOX_HGCM_SVC_PARM_PTR: + { +#ifdef DEBUG_andy + LogFlowFunc(("\tmpParms[%RU32] = %p (ptr), size = %RU32\n", + i, mpParms[i].u.pointer.addr, mpParms[i].u.pointer.size)); +#endif + if (!mpParms[i].u.pointer.size) + continue; /* Only copy buffer if there actually is something to copy. */ + + if (!paDstParms[i].u.pointer.addr) + rc = VERR_INVALID_PARAMETER; + else if (paDstParms[i].u.pointer.size < mpParms[i].u.pointer.size) + rc = VERR_BUFFER_OVERFLOW; + else + memcpy(paDstParms[i].u.pointer.addr, + mpParms[i].u.pointer.addr, + mpParms[i].u.pointer.size); + break; + } + + default: + LogFunc(("Parameter %RU32 of type %RU32 is not supported yet\n", i, mpParms[i].type)); + rc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_FAILURE(rc)) + { + LogFunc(("Parameter %RU32 invalid (%Rrc), refusing\n", i, rc)); + break; + } + } + } + + LogFlowFunc(("Returned with rc=%Rrc\n", rc)); + return rc; + } + + int Assign(const ClientRequest *pReq) + { + AssertPtrReturn(pReq, VERR_INVALID_POINTER); + + int rc; + + LogFlowThisFunc(("[Msg %RU32] mParmCount=%RU32, mpParms=%p\n", mType, mParmCount, mpParms)); + + /* Does the current host message need more parameter space which + * the client does not provide yet? */ + if (mParmCount > pReq->mNumParms) + { + LogFlowThisFunc(("[Msg %RU32] Requires %RU32 parms, only got %RU32 from client\n", + mType, mParmCount, pReq->mNumParms)); + /* + * So this call apparently failed because the guest wanted to peek + * how much parameters it has to supply in order to successfully retrieve + * this message. Let's tell him so! + */ + rc = VERR_TOO_MUCH_DATA; + } + else + { + rc = CopyTo(pReq->mParms, pReq->mNumParms); + + /* + * Has there been enough parameter space but the wrong parameter types + * were submitted -- maybe the client was just asking for the next upcoming + * host message? + * + * Note: To keep this compatible to older clients we return VERR_TOO_MUCH_DATA + * in every case. + */ + if (RT_FAILURE(rc)) + rc = VERR_TOO_MUCH_DATA; + } + + return rc; + } + + int Peek(const ClientRequest *pReq) + { + AssertPtrReturn(pReq, VERR_INVALID_POINTER); + + LogFlowThisFunc(("[Msg %RU32] mParmCount=%RU32, mpParms=%p\n", mType, mParmCount, mpParms)); + + if (pReq->mNumParms >= 2) + { + HGCMSvcSetU32(&pReq->mParms[0], mType); /* Message ID */ + HGCMSvcSetU32(&pReq->mParms[1], mParmCount); /* Required parameters for message */ + } + else + LogFlowThisFunc(("Warning: Client has not (yet) submitted enough parameters (%RU32, must be at least 2) to at least peak for the next message\n", + pReq->mNumParms)); + + /* + * Always return VERR_TOO_MUCH_DATA data here to + * keep it compatible with older clients and to + * have correct accounting (mHostRc + mHostMsgTries). + */ + return VERR_TOO_MUCH_DATA; + } + + /** @} */ +} HostMsg; + +/** + * Per-client structure used for book keeping/state tracking a + * certain host message. + */ +typedef struct ClientContext +{ + /* Pointer to list node of this message. */ + HostMsg *mpHostMsg; + /** The standard constructor. */ + ClientContext(void) : mpHostMsg(NULL) {} + /** Internal constrcutor. */ + ClientContext(HostMsg *pHostMsg) : mpHostMsg(pHostMsg) {} +} ClientContext; +typedef std::map< uint32_t, ClientContext > ClientContextMap; + +/** + * Structure for holding a connected guest client state. + */ +typedef struct ClientState +{ + PVBOXHGCMSVCHELPERS m_pSvcHelpers; + /** Host message list to process (HostMsg). */ + RTLISTANCHOR m_HostMsgList; + /** The HGCM client ID. */ + uint32_t m_idClient; + /** The session ID for this client, UINT32_MAX if not set or master. */ + uint32_t m_idSession; + /** Set if master. */ + bool m_fIsMaster; + /** Set if restored (needed for shutting legacy mode assert on non-masters). */ + bool m_fRestored; + + /** Set if we've got a pending wait cancel. */ + bool m_fPendingCancel; + /** Pending client call (GUEST_MSG_PEEK_WAIT or GUEST_MSG_WAIT), zero if none pending. + * + * This means the client waits for a new host message to reply and won't return + * from the waiting call until a new host message is available. */ + guestControl::eGuestMsg m_enmPendingMsg; + /** Pending peek/wait request details. */ + ClientRequest m_PendingReq; + + + ClientState(void) + : m_pSvcHelpers(NULL) + , m_idClient(0) + , m_idSession(UINT32_MAX) + , m_fIsMaster(false) + , m_fRestored(false) + , m_fPendingCancel(false) + , m_enmPendingMsg((guestControl::eGuestMsg)0) + , mHostMsgRc(VINF_SUCCESS) + , mHostMsgTries(0) + , mPeekCount(0) + { + RTListInit(&m_HostMsgList); + } + + ClientState(PVBOXHGCMSVCHELPERS pSvcHelpers, uint32_t idClient) + : m_pSvcHelpers(pSvcHelpers) + , m_idClient(idClient) + , m_idSession(UINT32_MAX) + , m_fIsMaster(false) + , m_fRestored(false) + , m_fPendingCancel(false) + , m_enmPendingMsg((guestControl::eGuestMsg)0) + , mHostMsgRc(VINF_SUCCESS) + , mHostMsgTries(0) + , mPeekCount(0) + { + RTListInit(&m_HostMsgList); + } + + /** + * Used by for Service::hostProcessMessage(). + */ + void EnqueueMessage(HostMsg *pHostMsg) + { + AssertPtr(pHostMsg); + RTListAppend(&m_HostMsgList, &pHostMsg->m_ListEntry); + } + + /** + * Used by for Service::hostProcessMessage(). + * + * @returns VBox status code. + * @retval VINF_NO_CHANGE if the client has not been woken up. + * + * @note This wakes up both GUEST_MSG_WAIT and GUEST_MSG_PEEK_WAIT sleepers. + */ + int Wakeup(void) + { + int rc = VINF_NO_CHANGE; + + LogFlowFunc(("[Client %RU32] enmPendingMsg=%RU32, idSession=%RU32, fIsMaster=%RTbool, fRestored=%RTbool\n", + m_idClient, m_enmPendingMsg, m_idSession, m_fIsMaster, m_fRestored)); + + if (m_enmPendingMsg != 0) + { + rc = VINF_SUCCESS; + + HostMsg *pFirstMsg = RTListGetFirstCpp(&m_HostMsgList, HostMsg, m_ListEntry); + if (pFirstMsg) + { + LogFlowThisFunc(("[Client %RU32] Current host message is %RU32 (CID=%#RX32, cParms=%RU32)\n", + m_idClient, pFirstMsg->mType, pFirstMsg->m_idContext, pFirstMsg->mParmCount)); + + if (m_enmPendingMsg == GUEST_MSG_PEEK_WAIT) + { + pFirstMsg->setPeekReturn(m_PendingReq.mParms, m_PendingReq.mNumParms); + rc = m_pSvcHelpers->pfnCallComplete(m_PendingReq.mHandle, VINF_SUCCESS); + + m_PendingReq.mHandle = NULL; + m_PendingReq.mParms = NULL; + m_PendingReq.mNumParms = 0; + m_enmPendingMsg = (guestControl::eGuestMsg)0; + } + else if (m_enmPendingMsg == GUEST_MSG_WAIT) + rc = OldRun(&m_PendingReq, pFirstMsg); + else + AssertMsgFailed(("m_enmIsPending=%d\n", m_enmPendingMsg)); + } + else + AssertMsgFailed(("Waking up client ID=%RU32 with no host message in queue is a bad idea\n", m_idClient)); + } + + LogFlowFuncLeaveRC(rc); + return rc; + } + + /** + * Used by Service::call() to handle GUEST_MSG_CANCEL. + * + * @note This cancels both GUEST_MSG_WAIT and GUEST_MSG_PEEK_WAIT sleepers. + */ + int CancelWaiting() + { + LogFlowFunc(("[Client %RU32] Cancelling waiting thread, isPending=%d, pendingNumParms=%RU32, m_idSession=%x\n", + m_idClient, m_enmPendingMsg, m_PendingReq.mNumParms, m_idSession)); + + /* + * The PEEK call is simple: At least two parameters, all set to zero before sleeping. + */ + int rcComplete; + if (m_enmPendingMsg == GUEST_MSG_PEEK_WAIT) + { + HGCMSvcSetU32(&m_PendingReq.mParms[0], HOST_MSG_CANCEL_PENDING_WAITS); + rcComplete = VINF_TRY_AGAIN; + } + /* + * The GUEST_MSG_WAIT call is complicated, though we're generally here + * to wake up someone who is peeking and have two parameters. If there + * aren't two parameters, fail the call. + */ + else if (m_enmPendingMsg != 0) + { + Assert(m_enmPendingMsg == GUEST_MSG_WAIT); + if (m_PendingReq.mNumParms > 0) + HGCMSvcSetU32(&m_PendingReq.mParms[0], HOST_MSG_CANCEL_PENDING_WAITS); + if (m_PendingReq.mNumParms > 1) + HGCMSvcSetU32(&m_PendingReq.mParms[1], 0); + rcComplete = m_PendingReq.mNumParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN; + } + /* + * If nobody is waiting, flag the next wait call as cancelled. + */ + else + { + m_fPendingCancel = true; + return VINF_SUCCESS; + } + + m_pSvcHelpers->pfnCallComplete(m_PendingReq.mHandle, rcComplete); + + m_PendingReq.mHandle = NULL; + m_PendingReq.mParms = NULL; + m_PendingReq.mNumParms = 0; + m_enmPendingMsg = (guestControl::eGuestMsg)0; + m_fPendingCancel = false; + return VINF_SUCCESS; + } + + + /** @name The GUEST_MSG_WAIT state and helpers. + * + * @note Don't try understand this, it is certificable! + * + * @{ + */ + + /** Last (most recent) rc after handling the host message. */ + int mHostMsgRc; + /** How many GUEST_MSG_WAIT calls the client has issued to retrieve one message. + * + * This is used as a heuristic to remove a message that the client appears not + * to be able to successfully retrieve. */ + uint32_t mHostMsgTries; + /** Number of times we've peeked at a pending message. + * + * This is necessary for being compatible with older Guest Additions. In case + * there are messages which only have two (2) parameters and therefore would fit + * into the GUEST_MSG_WAIT reply immediately, we now can make sure that the + * client first gets back the GUEST_MSG_WAIT results first. + */ + uint32_t mPeekCount; + + /** + * Ditches the first host message and crazy GUEST_MSG_WAIT state. + * + * @note Only used by GUEST_MSG_WAIT scenarios. + */ + void OldDitchFirstHostMsg() + { + HostMsg *pFirstMsg = RTListGetFirstCpp(&m_HostMsgList, HostMsg, m_ListEntry); + Assert(pFirstMsg); + RTListNodeRemove(&pFirstMsg->m_ListEntry); + pFirstMsg->Delete(); + + /* Reset state else. */ + mHostMsgRc = VINF_SUCCESS; + mHostMsgTries = 0; + mPeekCount = 0; + } + + /** + * Used by Wakeup() and OldRunCurrent(). + * + * @note Only used by GUEST_MSG_WAIT scenarios. + */ + int OldRun(ClientRequest const *pReq, HostMsg *pHostMsg) + { + AssertPtrReturn(pReq, VERR_INVALID_POINTER); + AssertPtrReturn(pHostMsg, VERR_INVALID_POINTER); + Assert(RTListNodeIsFirst(&m_HostMsgList, &pHostMsg->m_ListEntry)); + + LogFlowFunc(("[Client %RU32] pReq=%p, mHostMsgRc=%Rrc, mHostMsgTries=%RU32, mPeekCount=%RU32\n", + m_idClient, pReq, mHostMsgRc, mHostMsgTries, mPeekCount)); + + int rc = mHostMsgRc = OldSendReply(pReq, pHostMsg); + + LogFlowThisFunc(("[Client %RU32] Processing host message %RU32 ended with rc=%Rrc\n", + m_idClient, pHostMsg->mType, mHostMsgRc)); + + bool fRemove = false; + if (RT_FAILURE(rc)) + { + mHostMsgTries++; + + /* + * If the client understood the message but supplied too little buffer space + * don't send this message again and drop it after 6 unsuccessful attempts. + * + * Note: Due to legacy reasons this the retry counter has to be even because on + * every peek there will be the actual message retrieval from the client side. + * To not get the actual message if the client actually only wants to peek for + * the next message, there needs to be two rounds per try, e.g. 3 rounds = 6 tries. + */ + /** @todo Fix the mess stated above. GUEST_MSG_WAIT should be become GUEST_MSG_PEEK, *only* + * (and every time) returning the next upcoming host message (if any, blocking). Then + * it's up to the client what to do next, either peeking again or getting the actual + * host message via an own GUEST_ type message. + */ + if ( rc == VERR_TOO_MUCH_DATA + || rc == VERR_CANCELLED) + { + if (mHostMsgTries == 6) + fRemove = true; + } + /* Client did not understand the message or something else weird happened. Try again one + * more time and drop it if it didn't get handled then. */ + else if (mHostMsgTries > 1) + fRemove = true; + } + else + fRemove = true; /* Everything went fine, remove it. */ + + LogFlowThisFunc(("[Client %RU32] Tried host message %RU32 for %RU32 times, (last result=%Rrc, fRemove=%RTbool)\n", + m_idClient, pHostMsg->mType, mHostMsgTries, rc, fRemove)); + + if (fRemove) + { + Assert(RTListNodeIsFirst(&m_HostMsgList, &pHostMsg->m_ListEntry)); + OldDitchFirstHostMsg(); + } + + LogFlowFunc(("[Client %RU32] Returned with rc=%Rrc\n", m_idClient, rc)); + return rc; + } + + /** + * @note Only used by GUEST_MSG_WAIT scenarios. + */ + int OldRunCurrent(const ClientRequest *pReq) + { + AssertPtrReturn(pReq, VERR_INVALID_POINTER); + + /* + * If the host message list is empty, the request must wait for one to be posted. + */ + HostMsg *pFirstMsg = RTListGetFirstCpp(&m_HostMsgList, HostMsg, m_ListEntry); + if (!pFirstMsg) + { + if (!m_fPendingCancel) + { + /* Go to sleep. */ + ASSERT_GUEST_RETURN(m_enmPendingMsg == 0, VERR_WRONG_ORDER); + m_PendingReq = *pReq; + m_enmPendingMsg = GUEST_MSG_WAIT; + LogFlowFunc(("[Client %RU32] Is now in pending mode\n", m_idClient)); + return VINF_HGCM_ASYNC_EXECUTE; + } + + /* Wait was cancelled. */ + m_fPendingCancel = false; + if (pReq->mNumParms > 0) + HGCMSvcSetU32(&pReq->mParms[0], HOST_MSG_CANCEL_PENDING_WAITS); + if (pReq->mNumParms > 1) + HGCMSvcSetU32(&pReq->mParms[1], 0); + return pReq->mNumParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN; + } + + /* + * Return first host message. + */ + return OldRun(pReq, pFirstMsg); + } + + /** + * Internal worker for OldRun(). + * @note Only used for GUEST_MSG_WAIT. + */ + int OldSendReply(ClientRequest const *pReq, + HostMsg *pHostMsg) + { + AssertPtrReturn(pReq, VERR_INVALID_POINTER); + AssertPtrReturn(pHostMsg, VERR_INVALID_POINTER); + + /* In case of VERR_CANCELLED. */ + uint32_t const cSavedPeeks = mPeekCount; + + int rc; + /* If the client is in pending mode, always send back + * the peek result first. */ + if (m_enmPendingMsg) + { + Assert(m_enmPendingMsg == GUEST_MSG_WAIT); + rc = pHostMsg->Peek(pReq); + mPeekCount++; + } + else + { + /* If this is the very first peek, make sure to *always* give back the peeking answer + * instead of the actual message, even if this message would fit into the current + * connection buffer. */ + if (!mPeekCount) + { + rc = pHostMsg->Peek(pReq); + mPeekCount++; + } + else + { + /* Try assigning the host message to the client and store the + * result code for later use. */ + rc = pHostMsg->Assign(pReq); + if (RT_FAILURE(rc)) /* If something failed, let the client peek (again). */ + { + rc = pHostMsg->Peek(pReq); + mPeekCount++; + } + else + mPeekCount = 0; + } + } + + /* Reset pending status. */ + m_enmPendingMsg = (guestControl::eGuestMsg)0; + + /* In any case the client did something, so complete + * the pending call with the result we just got. */ + AssertPtr(m_pSvcHelpers); + int rc2 = m_pSvcHelpers->pfnCallComplete(pReq->mHandle, rc); + + /* Rollback in case the guest cancelled the call. */ + if (rc2 == VERR_CANCELLED && RT_SUCCESS(rc)) + { + mPeekCount = cSavedPeeks; + rc = VERR_CANCELLED; + } + + LogFlowThisFunc(("[Client %RU32] Message %RU32 ended with %Rrc (mPeekCount=%RU32, pReq=%p)\n", + m_idClient, pHostMsg->mType, rc, mPeekCount, pReq)); + return rc; + } + + /** @} */ +} ClientState; +typedef std::map< uint32_t, ClientState *> ClientStateMap; + +/** + * Prepared session (GUEST_SESSION_PREPARE). + */ +typedef struct GstCtrlPreparedSession +{ + /** List entry. */ + RTLISTNODE ListEntry; + /** The session ID. */ + uint32_t idSession; + /** The key size. */ + uint32_t cbKey; + /** The key bytes. */ + RT_FLEXIBLE_ARRAY_EXTENSION + uint8_t abKey[RT_FLEXIBLE_ARRAY]; +} GstCtrlPreparedSession; + + +/** + * Class containing the shared information service functionality. + */ +class GstCtrlService : public RTCNonCopyable +{ + +private: + + /** Type definition for use in callback functions. */ + typedef GstCtrlService SELF; + /** HGCM helper functions. */ + PVBOXHGCMSVCHELPERS mpHelpers; + /** Callback function supplied by the host for notification of updates to properties. */ + PFNHGCMSVCEXT mpfnHostCallback; + /** User data pointer to be supplied to the host callback function. */ + void *mpvHostData; + /** Map containing all connected clients, key is HGCM client ID. */ + ClientStateMap m_ClientStateMap; + /** Session ID -> client state. */ + ClientStateMap m_SessionIdMap; + /** The current master client, NULL if none. */ + ClientState *m_pMasterClient; + /** The master HGCM client ID, UINT32_MAX if none. */ + uint32_t m_idMasterClient; + /** Set if we're in legacy mode (pre 6.0). */ + bool m_fLegacyMode; + /** Number of prepared sessions. */ + uint32_t m_cPreparedSessions; + /** List of prepared session (GstCtrlPreparedSession). */ + RTLISTANCHOR m_PreparedSessions; + /** Guest feature flags, VBOX_GUESTCTRL_GF_0_XXX. */ + uint64_t m_fGuestFeatures0; + /** Guest feature flags, VBOX_GUESTCTRL_GF_1_XXX. */ + uint64_t m_fGuestFeatures1; + +public: + explicit GstCtrlService(PVBOXHGCMSVCHELPERS pHelpers) + : mpHelpers(pHelpers) + , mpfnHostCallback(NULL) + , mpvHostData(NULL) + , m_pMasterClient(NULL) + , m_idMasterClient(UINT32_MAX) + , m_fLegacyMode(true) + , m_cPreparedSessions(0) + , m_fGuestFeatures0(0) + , m_fGuestFeatures1(0) + { + RTListInit(&m_PreparedSessions); + } + + static DECLCALLBACK(int) svcUnload(void *pvService); + static DECLCALLBACK(int) svcConnect(void *pvService, uint32_t idClient, void *pvClient, + uint32_t fRequestor, bool fRestoring); + static DECLCALLBACK(int) svcDisconnect(void *pvService, uint32_t idClient, void *pvClient); + static DECLCALLBACK(void) svcCall(void *pvService, VBOXHGCMCALLHANDLE hCall, uint32_t idClient, void *pvClient, + uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival); + static DECLCALLBACK(int) svcHostCall(void *pvService, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + static DECLCALLBACK(int) svcSaveState(void *pvService, uint32_t idClient, void *pvClient, + PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM); + static DECLCALLBACK(int) svcLoadState(void *pvService, uint32_t idClient, void *pvClient, + PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion); + static DECLCALLBACK(int) svcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension); + +private: + int clientMakeMeMaster(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms); + int clientReportFeatures(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientQueryFeatures(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientMsgPeek(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fWait); + int clientMsgGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientMsgCancel(ClientState *pClient, uint32_t cParms); + int clientMsgSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientSessionPrepare(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientSessionCancelPrepared(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientSessionAccept(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientSessionCloseOther(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientToMain(ClientState *pClient, uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + + int clientMsgOldGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientMsgOldFilterSet(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int clientMsgOldSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms); + + int hostCallback(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int hostProcessMessage(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(GstCtrlService); +}; + + +/** Host feature mask for GUEST_MSG_REPORT_FEATURES/GUEST_MSG_QUERY_FEATURES. */ +static uint64_t const g_fGstCtrlHostFeatures0 = VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET + | VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0; + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnUnload, + * Simply deletes the GstCtrlService object} + */ +/*static*/ DECLCALLBACK(int) +GstCtrlService::svcUnload(void *pvService) +{ + AssertLogRelReturn(RT_VALID_PTR(pvService), VERR_INVALID_PARAMETER); + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + delete pThis; + + return VINF_SUCCESS; +} + + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnConnect, + * Initializes the state for a new client.} + */ +/*static*/ DECLCALLBACK(int) +GstCtrlService::svcConnect(void *pvService, uint32_t idClient, void *pvClient, uint32_t fRequestor, bool fRestoring) +{ + LogFlowFunc(("[Client %RU32] Connected\n", idClient)); + + RT_NOREF(fRestoring, pvClient); + AssertLogRelReturn(RT_VALID_PTR(pvService), VERR_INVALID_PARAMETER); + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + AssertMsg(pThis->m_ClientStateMap.find(idClient) == pThis->m_ClientStateMap.end(), + ("Client with ID=%RU32 already connected when it should not\n", idClient)); + + /* + * Create client state. + */ + ClientState *pClient = NULL; + try + { + pClient = new (pvClient) ClientState(pThis->mpHelpers, idClient); + pThis->m_ClientStateMap[idClient] = pClient; + } + catch (std::bad_alloc &) + { + if (pClient) + pClient->~ClientState(); + return VERR_NO_MEMORY; + } + + /* + * For legacy compatibility reasons we have to pick a master client at some + * point, so if the /dev/vboxguest requirements checks out we pick the first + * one through the door. + */ +/** @todo make picking the master more dynamic/flexible? */ + if ( pThis->m_fLegacyMode + && pThis->m_idMasterClient == UINT32_MAX) + { + if ( fRequestor == VMMDEV_REQUESTOR_LEGACY + || !(fRequestor & VMMDEV_REQUESTOR_USER_DEVICE)) + { + LogFunc(("Picking %u as master for now.\n", idClient)); + pThis->m_pMasterClient = pClient; + pThis->m_idMasterClient = idClient; + pClient->m_fIsMaster = true; + } + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnConnect, + * Handles a client which disconnected.} + * + * This functiond does some internal cleanup as well as sends notifications to + * the host so that the host can do the same (if required). + */ +/*static*/ DECLCALLBACK(int) +GstCtrlService::svcDisconnect(void *pvService, uint32_t idClient, void *pvClient) +{ + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + ClientState *pClient = reinterpret_cast<ClientState *>(pvClient); + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + LogFlowFunc(("[Client %RU32] Disconnected (%zu clients total)\n", idClient, pThis->m_ClientStateMap.size())); + + /* + * Cancel all pending host messages, replying with GUEST_DISCONNECTED if final recipient. + */ + HostMsg *pCurMsg, *pNextMsg; + RTListForEachSafeCpp(&pClient->m_HostMsgList, pCurMsg, pNextMsg, HostMsg, m_ListEntry) + { + RTListNodeRemove(&pCurMsg->m_ListEntry); + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, pCurMsg->m_idContext); + int rc2 = pThis->hostCallback(GUEST_MSG_DISCONNECTED, 1, &Parm); + LogFlowFunc(("Cancelled host message %u (%s) with idContext=%#x -> %Rrc\n", + pCurMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pCurMsg->mType), pCurMsg->m_idContext, rc2)); + RT_NOREF(rc2); + + pCurMsg->Delete(); + } + + /* + * If it's the master disconnecting, we need to reset related globals. + */ + if (idClient == pThis->m_idMasterClient) + { + pThis->m_pMasterClient = NULL; + pThis->m_idMasterClient = UINT32_MAX; + + GstCtrlPreparedSession *pCur, *pNext; + RTListForEachSafe(&pThis->m_PreparedSessions, pCur, pNext, GstCtrlPreparedSession, ListEntry) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + pThis->m_cPreparedSessions = 0; + + /* Make sure that the host gets notified about still associated guest sessions going down. + * + * Some guest OSes (like OL8) do reboot / shut down quite abruptly so that + * VBoxService does not have the chance to do so instead. + * + * Note: We do this only when the master disconnects as a last meassure, as this otherwise + * would overwrite formerly sent session statuses on the host. + */ + ClientStateMap::const_iterator itClientState = pThis->m_SessionIdMap.begin(); + while (itClientState != pThis->m_SessionIdMap.end()) + { + VBOXHGCMSVCPARM aParms[4]; + HGCMSvcSetU32(&aParms[0], VBOX_GUESTCTRL_CONTEXTID_MAKE(pCur->idSession, 0 /* uObject */, 0 /* uCount */)); + HGCMSvcSetU32(&aParms[1], GUEST_SESSION_NOTIFYTYPE_DWN); /* type */ + HGCMSvcSetU32(&aParms[2], VINF_SUCCESS); /* result */ + + int rc2 = pThis->hostCallback(GUEST_MSG_SESSION_NOTIFY, 3, aParms); + LogFlowFunc(("Notified host about session ID=%RU32 going down -> %Rrc\n", pClient->m_idSession, rc2)); + RT_NOREF(rc2); + + ++itClientState; + /* Note: Don't erase the client state -- this will be done when the actual client is disconnecting. */ + } + } + else + Assert(pClient != pThis->m_pMasterClient); + + /* + * Delete the client state. + */ + pThis->m_ClientStateMap.erase(idClient); + if (pClient->m_idSession != UINT32_MAX) + pThis->m_SessionIdMap.erase(pClient->m_idSession); + pClient->~ClientState(); + + if (pThis->m_ClientStateMap.empty()) + pThis->m_fLegacyMode = true; + + return VINF_SUCCESS; +} + + +/** + * A client asks for the next message to process. + * + * This either fills in a pending host message into the client's parameter space + * or defers the guest call until we have something from the host. + * + * @returns VBox status code. + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::clientMsgOldGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + ASSERT_GUEST(pClient->m_idSession != UINT32_MAX || pClient->m_fIsMaster || pClient->m_fRestored); + + /* Use the current (inbound) connection. */ + ClientRequest thisCon; + thisCon.mHandle = hCall; + thisCon.mNumParms = cParms; + thisCon.mParms = paParms; + + return pClient->OldRunCurrent(&thisCon); +} + + +/** + * Implements GUEST_MAKE_ME_MASTER. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here). + * @retval VERR_ACCESS_DENIED if not using main VBoxGuest device not + * @retval VERR_RESOURCE_BUSY if there is already a master. + * @retval VERR_VERSION_MISMATCH if VBoxGuest didn't supply requestor info. + * @retval VERR_WRONG_PARAMETER_COUNT + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + */ +int GstCtrlService::clientMakeMeMaster(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_RETURN(cParms == 0, VERR_WRONG_PARAMETER_COUNT); + + uint32_t fRequestor = mpHelpers->pfnGetRequestor(hCall); + /* The next assertion triggers upgrading GAs on some linux guests. Problem is that VBoxService is + restarted after installation but the kernel module hasn't been reloaded, so things are out + of wack. Just reboot. */ + ASSERT_GUEST_LOGREL_MSG_RETURN(fRequestor != VMMDEV_REQUESTOR_LEGACY, + ("Guest is using outdated VBoxGuest w/o requestor support.\n" + "Please update guest additions (or restart guest if you just did)!\n"), + VERR_VERSION_MISMATCH); + ASSERT_GUEST_LOGREL_MSG_RETURN(!(fRequestor & VMMDEV_REQUESTOR_USER_DEVICE), ("fRequestor=%#x\n", fRequestor), + VERR_ACCESS_DENIED); + + /* + * Do the work. + */ + ASSERT_GUEST_MSG_RETURN(m_idMasterClient == pClient->m_idClient || m_idMasterClient == UINT32_MAX, + ("Already have master session %RU32, refusing %RU32.\n", m_idMasterClient, pClient->m_idClient), + VERR_RESOURCE_BUSY); + int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + m_pMasterClient = pClient; + m_idMasterClient = pClient->m_idClient; + m_fLegacyMode = false; + pClient->m_fIsMaster = true; + Log(("[Client %RU32] is master.\n", pClient->m_idClient)); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + + +/** + * Implements GUEST_MSG_REPORT_FEATURES. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here). + * @retval VERR_ACCESS_DENIED if not master + * @retval VERR_INVALID_PARAMETER if bit 63 in the 2nd parameter isn't set. + * @retval VERR_WRONG_PARAMETER_COUNT + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::clientReportFeatures(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + uint64_t const fFeatures0 = paParms[0].u.uint64; + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + uint64_t const fFeatures1 = paParms[1].u.uint64; + ASSERT_GUEST_RETURN(fFeatures1 & VBOX_GUESTCTRL_GF_1_MUST_BE_ONE, VERR_INVALID_PARAMETER); + + ASSERT_GUEST_RETURN(pClient->m_fIsMaster, VERR_ACCESS_DENIED); + + /* + * Do the work. + */ + VBOXHGCMSVCPARM aCopyForMain[2] = { paParms[0], paParms[1] }; + + paParms[0].u.uint64 = g_fGstCtrlHostFeatures0; + paParms[1].u.uint64 = 0; + + int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + m_fGuestFeatures0 = fFeatures0; + m_fGuestFeatures1 = fFeatures1; + Log(("[Client %RU32] reported features: %#RX64 %#RX64\n", pClient->m_idClient, fFeatures0, fFeatures1)); + + /* + * Forward the info to main. + */ + hostCallback(GUEST_MSG_REPORT_FEATURES, RT_ELEMENTS(aCopyForMain), aCopyForMain); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + + +/** + * Implements GUEST_MSG_QUERY_FEATURES. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here). + * @retval VERR_WRONG_PARAMETER_COUNT + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::clientQueryFeatures(ClientState *pClient, + VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + RT_NOREF(pClient); + + /* + * Validate the request. + */ + ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST(paParms[1].u.uint64 & RT_BIT_64(63)); + + /* + * Do the work. + */ + paParms[0].u.uint64 = g_fGstCtrlHostFeatures0; + paParms[1].u.uint64 = 0; + int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + Log(("[Client %RU32] query features: %#RX64 0\n", pClient->m_idClient, g_fGstCtrlHostFeatures0)); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + + return VINF_HGCM_ASYNC_EXECUTE; +} + + +/** + * Implements GUEST_MSG_PEEK_WAIT and GUEST_MSG_PEEK_NOWAIT. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if a message was pending and is being returned. + * @retval VERR_TRY_AGAIN if no message pending and not blocking. + * @retval VERR_RESOURCE_BUSY if another read already made a waiting call. + * @retval VINF_HGCM_ASYNC_EXECUTE if message wait is pending. + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + * @param fWait Set if we should wait for a message, clear if to return + * immediately. + */ +int GstCtrlService::clientMsgPeek(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fWait) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_MSG_RETURN(cParms >= 2, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT); + + uint64_t idRestoreCheck = 0; + uint32_t i = 0; + if (paParms[i].type == VBOX_HGCM_SVC_PARM_64BIT) + { + idRestoreCheck = paParms[0].u.uint64; + paParms[0].u.uint64 = 0; + i++; + } + for (; i < cParms; i++) + { + ASSERT_GUEST_MSG_RETURN(paParms[i].type == VBOX_HGCM_SVC_PARM_32BIT, ("#%u type=%u\n", i, paParms[i].type), + VERR_WRONG_PARAMETER_TYPE); + paParms[i].u.uint32 = 0; + } + + /* + * Check restore session ID. + */ + if (idRestoreCheck != 0) + { + uint64_t idRestore = mpHelpers->pfnGetVMMDevSessionId(mpHelpers); + if (idRestoreCheck != idRestore) + { + paParms[0].u.uint64 = idRestore; + LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_XXXX -> VERR_VM_RESTORED (%#RX64 -> %#RX64)\n", + pClient->m_idClient, idRestoreCheck, idRestore)); + return VERR_VM_RESTORED; + } + Assert(!mpHelpers->pfnIsCallRestored(hCall)); + } + + /* + * Return information about the first message if one is pending in the list. + */ + HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry); + if (pFirstMsg) + { + pFirstMsg->setPeekReturn(paParms, cParms); + LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_XXXX -> VINF_SUCCESS (idMsg=%u (%s), cParms=%u)\n", + pClient->m_idClient, pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType), pFirstMsg->mParmCount)); + return VINF_SUCCESS; + } + + /* + * If we cannot wait, fail the call. + */ + if (!fWait) + { + LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_NOWAIT -> VERR_TRY_AGAIN\n", pClient->m_idClient)); + return VERR_TRY_AGAIN; + } + + /* + * Wait for the host to queue a message for this client. + */ + ASSERT_GUEST_MSG_RETURN(pClient->m_enmPendingMsg == 0, ("Already pending! (idClient=%RU32)\n", pClient->m_idClient), + VERR_RESOURCE_BUSY); + pClient->m_PendingReq.mHandle = hCall; + pClient->m_PendingReq.mNumParms = cParms; + pClient->m_PendingReq.mParms = paParms; + pClient->m_enmPendingMsg = GUEST_MSG_PEEK_WAIT; + LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->m_idClient)); + return VINF_HGCM_ASYNC_EXECUTE; +} + +/** + * Implements GUEST_MSG_GET. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if message retrieved and removed from the pending queue. + * @retval VERR_TRY_AGAIN if no message pending. + * @retval VERR_BUFFER_OVERFLOW if a parmeter buffer is too small. The buffer + * size was updated to reflect the required size, though this isn't yet + * forwarded to the guest. (The guest is better of using peek with + * parameter count + 2 parameters to get the sizes.) + * @retval VERR_MISMATCH if the incoming message ID does not match the pending. + * @retval VINF_HGCM_ASYNC_EXECUTE if message was completed already. + * + * @param pClient The client state. + * @param hCall The client's call handle. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::clientMsgGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the request. + * + * The weird first parameter logic is due to GUEST_MSG_WAIT compatibility + * (don't want to rewrite all the message structures). + */ + uint32_t const idMsgExpected = cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT ? paParms[0].u.uint32 + : cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT ? paParms[0].u.uint64 + : UINT32_MAX; + + /* + * Return information about the first message if one is pending in the list. + */ + HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry); + if (pFirstMsg) + { + + ASSERT_GUEST_MSG_RETURN(pFirstMsg->mType == idMsgExpected || idMsgExpected == UINT32_MAX, + ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", + pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType), pFirstMsg->mParmCount, + idMsgExpected, GstCtrlHostMsgtoStr((eHostMsg)idMsgExpected), cParms), + VERR_MISMATCH); + ASSERT_GUEST_MSG_RETURN(pFirstMsg->mParmCount == cParms, + ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", + pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType), pFirstMsg->mParmCount, + idMsgExpected, GstCtrlHostMsgtoStr((eHostMsg)idMsgExpected), cParms), + VERR_WRONG_PARAMETER_COUNT); + + /* Check the parameter types. */ + for (uint32_t i = 0; i < cParms; i++) + ASSERT_GUEST_MSG_RETURN(pFirstMsg->mpParms[i].type == paParms[i].type, + ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->mpParms[i].type, + paParms[i].type, pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType)), + VERR_WRONG_PARAMETER_TYPE); + + /* + * Copy out the parameters. + * + * No assertions on buffer overflows, and keep going till the end so we can + * communicate all the required buffer sizes. + */ + int rc = VINF_SUCCESS; + for (uint32_t i = 0; i < cParms; i++) + switch (pFirstMsg->mpParms[i].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: + paParms[i].u.uint32 = pFirstMsg->mpParms[i].u.uint32; + break; + + case VBOX_HGCM_SVC_PARM_64BIT: + paParms[i].u.uint64 = pFirstMsg->mpParms[i].u.uint64; + break; + + case VBOX_HGCM_SVC_PARM_PTR: + { + uint32_t const cbSrc = pFirstMsg->mpParms[i].u.pointer.size; + uint32_t const cbDst = paParms[i].u.pointer.size; + paParms[i].u.pointer.size = cbSrc; /** @todo Check if this is safe in other layers... + * Update: Safe, yes, but VMMDevHGCM doesn't pass it along. */ + if (cbSrc <= cbDst) + memcpy(paParms[i].u.pointer.addr, pFirstMsg->mpParms[i].u.pointer.addr, cbSrc); + else + rc = VERR_BUFFER_OVERFLOW; + break; + } + + default: + AssertMsgFailed(("#%u: %u\n", i, pFirstMsg->mpParms[i].type)); + rc = VERR_INTERNAL_ERROR; + break; + } + if (RT_SUCCESS(rc)) + { + /* + * Complete the message and remove the pending message unless the + * guest raced us and cancelled this call in the meantime. + */ + AssertPtr(mpHelpers); + rc = mpHelpers->pfnCallComplete(hCall, rc); + if (rc != VERR_CANCELLED) + { + RTListNodeRemove(&pFirstMsg->m_ListEntry); + pFirstMsg->Delete(); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + } + return rc; + } + + paParms[0].u.uint32 = 0; + paParms[1].u.uint32 = 0; + LogFlowFunc(("[Client %RU32] GUEST_MSG_GET -> VERR_TRY_AGAIN\n", pClient->m_idClient)); + return VERR_TRY_AGAIN; +} + +/** + * Implements GUEST_MSG_CANCEL. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if cancelled any calls. + * @retval VWRN_NOT_FOUND if no callers. + * @retval VINF_HGCM_ASYNC_EXECUTE if message wait is pending. + * + * @param pClient The client state. + * @param cParms Number of parameters. + */ +int GstCtrlService::clientMsgCancel(ClientState *pClient, uint32_t cParms) +{ + /* + * Validate the request. + */ + ASSERT_GUEST_MSG_RETURN(cParms == 0, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT); + + /* + * Execute. + */ + if (pClient->m_enmPendingMsg != 0) + { + pClient->CancelWaiting(); + return VINF_SUCCESS; + } + return VWRN_NOT_FOUND; +} + + +/** + * Implements GUEST_MSG_SKIP. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success as we complete the message. + * @retval VERR_NOT_FOUND if no message pending. + * + * @param pClient The client state. + * @param hCall The call handle for completing it. + * @param cParms Number of parameters. + * @param paParms The parameters. + */ +int GstCtrlService::clientMsgSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate the call. + */ + ASSERT_GUEST_RETURN(cParms <= 2, VERR_WRONG_PARAMETER_COUNT); + + int32_t rcSkip = VERR_NOT_SUPPORTED; + if (cParms >= 1) + { + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + rcSkip = (int32_t)paParms[0].u.uint32; + } + + uint32_t idMsg = UINT32_MAX; + if (cParms >= 2) + { + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + idMsg = paParms[1].u.uint32; + } + + /* + * Do the job. + */ + HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry); + if (pFirstMsg) + { + if ( pFirstMsg->mType == idMsg + || idMsg == UINT32_MAX) + { + int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + /* + * Remove the message from the queue. + */ + Assert(RTListNodeIsFirst(&pClient->m_HostMsgList, &pFirstMsg->m_ListEntry) ); + RTListNodeRemove(&pFirstMsg->m_ListEntry); + + /* + * Compose a reply to the host service. + */ + VBOXHGCMSVCPARM aReplyParams[5]; + HGCMSvcSetU32(&aReplyParams[0], pFirstMsg->m_idContext); + switch (pFirstMsg->mType) + { + case HOST_MSG_EXEC_CMD: + HGCMSvcSetU32(&aReplyParams[1], 0); /* pid */ + HGCMSvcSetU32(&aReplyParams[2], PROC_STS_ERROR); /* status */ + HGCMSvcSetU32(&aReplyParams[3], rcSkip); /* flags / whatever */ + HGCMSvcSetPv(&aReplyParams[4], NULL, 0); /* data buffer */ + hostCallback(GUEST_MSG_EXEC_STATUS, 5, aReplyParams); + break; + + case HOST_MSG_SESSION_CREATE: + HGCMSvcSetU32(&aReplyParams[1], GUEST_SESSION_NOTIFYTYPE_ERROR); /* type */ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* result */ + hostCallback(GUEST_MSG_SESSION_NOTIFY, 3, aReplyParams); + break; + + case HOST_MSG_EXEC_SET_INPUT: + HGCMSvcSetU32(&aReplyParams[1], pFirstMsg->mParmCount >= 2 ? pFirstMsg->mpParms[1].u.uint32 : 0); + HGCMSvcSetU32(&aReplyParams[2], INPUT_STS_ERROR); /* status */ + HGCMSvcSetU32(&aReplyParams[3], rcSkip); /* flags / whatever */ + HGCMSvcSetU32(&aReplyParams[4], 0); /* bytes consumed */ + hostCallback(GUEST_MSG_EXEC_INPUT_STATUS, 5, aReplyParams); + break; + + case HOST_MSG_FILE_OPEN: + HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_OPEN); /* type*/ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* rc */ + HGCMSvcSetU32(&aReplyParams[3], VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pFirstMsg->m_idContext)); /* handle */ + hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams); + break; + case HOST_MSG_FILE_CLOSE: + HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_ERROR); /* type*/ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* rc */ + hostCallback(GUEST_MSG_FILE_NOTIFY, 3, aReplyParams); + break; + case HOST_MSG_FILE_READ: + case HOST_MSG_FILE_READ_AT: + HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_READ); /* type */ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* rc */ + HGCMSvcSetPv(&aReplyParams[3], NULL, 0); /* data buffer */ + hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams); + break; + case HOST_MSG_FILE_WRITE: + case HOST_MSG_FILE_WRITE_AT: + HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_WRITE); /* type */ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* rc */ + HGCMSvcSetU32(&aReplyParams[3], 0); /* bytes written */ + hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams); + break; + case HOST_MSG_FILE_SEEK: + HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_SEEK); /* type */ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* rc */ + HGCMSvcSetU64(&aReplyParams[3], 0); /* actual */ + hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams); + break; + case HOST_MSG_FILE_TELL: + HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_TELL); /* type */ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* rc */ + HGCMSvcSetU64(&aReplyParams[3], 0); /* actual */ + hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams); + break; + case HOST_MSG_FILE_SET_SIZE: + HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_SET_SIZE); /* type */ + HGCMSvcSetU32(&aReplyParams[2], rcSkip); /* rc */ + HGCMSvcSetU64(&aReplyParams[3], 0); /* actual */ + hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams); + break; + + case HOST_MSG_EXEC_GET_OUTPUT: /** @todo This can't be right/work. */ + case HOST_MSG_EXEC_TERMINATE: /** @todo This can't be right/work. */ + case HOST_MSG_EXEC_WAIT_FOR: /** @todo This can't be right/work. */ + case HOST_MSG_PATH_USER_DOCUMENTS: + case HOST_MSG_PATH_USER_HOME: + case HOST_MSG_PATH_RENAME: + case HOST_MSG_DIR_REMOVE: + default: + HGCMSvcSetU32(&aReplyParams[1], pFirstMsg->mType); + HGCMSvcSetU32(&aReplyParams[2], (uint32_t)rcSkip); + HGCMSvcSetPv(&aReplyParams[3], NULL, 0); + hostCallback(GUEST_MSG_REPLY, 4, aReplyParams); + break; + } + + /* + * Free the message. + */ + pFirstMsg->Delete(); + } + else + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + } + LogFunc(("Warning: GUEST_MSG_SKIP mismatch! Found %u, caller expected %u!\n", pFirstMsg->mType, idMsg)); + return VERR_MISMATCH; + } + return VERR_NOT_FOUND; +} + + +/** + * Implements GUEST_SESSION_PREPARE. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success as we complete the message. + * @retval VERR_OUT_OF_RESOURCES if too many pending sessions hanging around. + * @retval VERR_OUT_OF_RANGE if the session ID outside the allowed range. + * @retval VERR_BUFFER_OVERFLOW if key too large. + * @retval VERR_BUFFER_UNDERFLOW if key too small. + * @retval VERR_ACCESS_DENIED if not master or in legacy mode. + * @retval VERR_DUPLICATE if the session ID has been prepared already. + * + * @param pClient The client state. + * @param hCall The call handle for completing it. + * @param cParms Number of parameters. + * @param paParms The parameters. + */ +int GstCtrlService::clientSessionPrepare(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate parameters. + */ + ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const idSession = paParms[0].u.uint32; + ASSERT_GUEST_RETURN(idSession >= 1, VERR_OUT_OF_RANGE); + ASSERT_GUEST_RETURN(idSession <= 0xfff0, VERR_OUT_OF_RANGE); + + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); + uint32_t const cbKey = paParms[1].u.pointer.size; + void const *pvKey = paParms[1].u.pointer.addr; + ASSERT_GUEST_RETURN(cbKey >= 64, VERR_BUFFER_UNDERFLOW); + ASSERT_GUEST_RETURN(cbKey <= _16K, VERR_BUFFER_OVERFLOW); + + ASSERT_GUEST_RETURN(pClient->m_fIsMaster, VERR_ACCESS_DENIED); + ASSERT_GUEST_RETURN(!m_fLegacyMode, VERR_ACCESS_DENIED); + Assert(m_idMasterClient == pClient->m_idClient); + Assert(m_pMasterClient == pClient); + + /* Now that we know it's the master, we can check for session ID duplicates. */ + GstCtrlPreparedSession *pCur; + RTListForEach(&m_PreparedSessions, pCur, GstCtrlPreparedSession, ListEntry) + { + ASSERT_GUEST_RETURN(pCur->idSession != idSession, VERR_DUPLICATE); + } + + /* + * Make a copy of the session ID and key. + */ + ASSERT_GUEST_RETURN(m_cPreparedSessions < 128, VERR_OUT_OF_RESOURCES); + + GstCtrlPreparedSession *pPrepped = (GstCtrlPreparedSession *)RTMemAlloc(RT_UOFFSETOF_DYN(GstCtrlPreparedSession, abKey[cbKey])); + AssertReturn(pPrepped, VERR_NO_MEMORY); + pPrepped->idSession = idSession; + pPrepped->cbKey = cbKey; + memcpy(pPrepped->abKey, pvKey, cbKey); + + RTListAppend(&m_PreparedSessions, &pPrepped->ListEntry); + m_cPreparedSessions++; + + /* + * Try complete the message. + */ + int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + LogFlow(("Prepared %u with a %#x byte key (%u pending).\n", idSession, cbKey, m_cPreparedSessions)); + else + { + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + RTListNodeRemove(&pPrepped->ListEntry); + RTMemFree(pPrepped); + m_cPreparedSessions--; + } + return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ +} + + +/** + * Implements GUEST_SESSION_CANCEL_PREPARED. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success as we complete the message. + * @retval VWRN_NOT_FOUND if no session with the specified ID. + * @retval VERR_ACCESS_DENIED if not master or in legacy mode. + * + * @param pClient The client state. + * @param cParms Number of parameters. + * @param paParms The parameters. + */ +int GstCtrlService::clientSessionCancelPrepared(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate parameters. + */ + ASSERT_GUEST_RETURN(cParms == 1, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const idSession = paParms[0].u.uint32; + + ASSERT_GUEST_RETURN(pClient->m_fIsMaster, VERR_ACCESS_DENIED); + ASSERT_GUEST_RETURN(!m_fLegacyMode, VERR_ACCESS_DENIED); + Assert(m_idMasterClient == pClient->m_idClient); + Assert(m_pMasterClient == pClient); + + /* + * Do the work. + */ + int rc = VWRN_NOT_FOUND; + if (idSession == UINT32_MAX) + { + GstCtrlPreparedSession *pCur, *pNext; + RTListForEachSafe(&m_PreparedSessions, pCur, pNext, GstCtrlPreparedSession, ListEntry) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + rc = VINF_SUCCESS; + } + m_cPreparedSessions = 0; + } + else + { + GstCtrlPreparedSession *pCur, *pNext; + RTListForEachSafe(&m_PreparedSessions, pCur, pNext, GstCtrlPreparedSession, ListEntry) + { + if (pCur->idSession == idSession) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + m_cPreparedSessions -= 1; + rc = VINF_SUCCESS; + break; + } + } + } + return VINF_SUCCESS; +} + + +/** + * Implements GUEST_SESSION_ACCEPT. + * + * @returns VBox status code. + * @retval VINF_HGCM_ASYNC_EXECUTE on success as we complete the message. + * @retval VERR_NOT_FOUND if the specified session ID wasn't found. + * @retval VERR_MISMATCH if the key didn't match. + * @retval VERR_ACCESS_DENIED if we're in legacy mode or is master. + * @retval VERR_RESOURCE_BUSY if the client is already associated with a + * session. + * + * @param pClient The client state. + * @param hCall The call handle for completing it. + * @param cParms Number of parameters. + * @param paParms The parameters. + */ +int GstCtrlService::clientSessionAccept(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate parameters. + */ + ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const idSession = paParms[0].u.uint32; + ASSERT_GUEST_RETURN(idSession >= 1, VERR_OUT_OF_RANGE); + ASSERT_GUEST_RETURN(idSession <= 0xfff0, VERR_OUT_OF_RANGE); + + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); + uint32_t const cbKey = paParms[1].u.pointer.size; + void const *pvKey = paParms[1].u.pointer.addr; + ASSERT_GUEST_RETURN(cbKey >= 64, VERR_BUFFER_UNDERFLOW); + ASSERT_GUEST_RETURN(cbKey <= _16K, VERR_BUFFER_OVERFLOW); + + ASSERT_GUEST_RETURN(!pClient->m_fIsMaster, VERR_ACCESS_DENIED); + ASSERT_GUEST_RETURN(!m_fLegacyMode, VERR_ACCESS_DENIED); + Assert(m_idMasterClient != pClient->m_idClient); + Assert(m_pMasterClient != pClient); + ASSERT_GUEST_RETURN(pClient->m_idSession == UINT32_MAX, VERR_RESOURCE_BUSY); + + /* + * Look for the specified session and match the key to it. + */ + GstCtrlPreparedSession *pCur; + RTListForEach(&m_PreparedSessions, pCur, GstCtrlPreparedSession, ListEntry) + { + if (pCur->idSession == idSession) + { + if ( pCur->cbKey == cbKey + && memcmp(pCur->abKey, pvKey, cbKey) == 0) + { + /* + * We've got a match. + * Try insert it into the sessio ID map and complete the request. + */ + try + { + m_SessionIdMap[idSession] = pClient; + } + catch (std::bad_alloc &) + { + LogFunc(("Out of memory!\n")); + return VERR_NO_MEMORY; + } + + int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + pClient->m_idSession = idSession; + + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + m_cPreparedSessions -= 1; + Log(("[Client %RU32] accepted session id %u.\n", pClient->m_idClient, idSession)); + } + else + { + LogFunc(("pfnCallComplete -> %Rrc\n", rc)); + m_SessionIdMap.erase(idSession); + } + return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */ + } + LogFunc(("Key mismatch for %u!\n", pClient->m_idClient)); + return VERR_MISMATCH; + } + } + + LogFunc(("No client prepared for %u!\n", pClient->m_idClient)); + return VERR_NOT_FOUND; +} + + +/** + * Client asks another client (guest) session to close. + * + * @returns VBox status code. + * @param pClient The client state. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::clientSessionCloseOther(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate input. + */ + ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const idContext = paParms[0].u.uint32; + + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t const fFlags = paParms[1].u.uint32; + + ASSERT_GUEST_RETURN(pClient->m_fIsMaster || (m_fLegacyMode && pClient->m_idSession == UINT32_MAX), VERR_ACCESS_DENIED); + + /* + * Forward the message to the destiation. + * Since we modify the first parameter, we must make a copy of the parameters. + */ + VBOXHGCMSVCPARM aParms[2]; + HGCMSvcSetU64(&aParms[0], idContext | VBOX_GUESTCTRL_DST_SESSION); + HGCMSvcSetU32(&aParms[1], fFlags); + int rc = hostProcessMessage(HOST_MSG_SESSION_CLOSE, RT_ELEMENTS(aParms), aParms); + + LogFlowFunc(("Closing guest context ID=%RU32 (from client ID=%RU32) returned with rc=%Rrc\n", idContext, pClient->m_idClient, rc)); + return rc; +} + + +/** + * For compatiblity with old additions only - filtering / set session ID. + * + * @return VBox status code. + * @param pClient The client state. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::clientMsgOldFilterSet(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Validate input and access. + */ + ASSERT_GUEST_RETURN(cParms == 4, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t uValue = paParms[0].u.uint32; + ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t fMaskAdd = paParms[1].u.uint32; + ASSERT_GUEST_RETURN(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); + uint32_t fMaskRemove = paParms[2].u.uint32; + ASSERT_GUEST_RETURN(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* flags, unused */ + + /* + * We have a bunch of expectations here: + * - Never called in non-legacy mode. + * - Only called once per session. + * - Never called by the master session. + * - Clients that doesn't wish for any messages passes all zeros. + * - All other calls has a unique session ID. + */ + ASSERT_GUEST_LOGREL_RETURN(m_fLegacyMode, VERR_WRONG_ORDER); + ASSERT_GUEST_LOGREL_MSG_RETURN(pClient->m_idSession == UINT32_MAX, ("m_idSession=%#x\n", pClient->m_idSession), + VERR_WRONG_ORDER); + ASSERT_GUEST_LOGREL_RETURN(!pClient->m_fIsMaster, VERR_WRONG_ORDER); + + if (uValue == 0) + { + ASSERT_GUEST_LOGREL(fMaskAdd == 0); + ASSERT_GUEST_LOGREL(fMaskRemove == 0); + /* Nothing to do, already muted (UINT32_MAX). */ + } + else + { + ASSERT_GUEST_LOGREL(fMaskAdd == UINT32_C(0xf8000000)); + ASSERT_GUEST_LOGREL(fMaskRemove == 0); + + uint32_t idSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uValue); + ASSERT_GUEST_LOGREL_MSG_RETURN(idSession > 0, ("idSession=%u (%#x)\n", idSession, uValue), VERR_OUT_OF_RANGE); + + ClientStateMap::iterator ItConflict = m_SessionIdMap.find(idSession); + ASSERT_GUEST_LOGREL_MSG_RETURN(ItConflict == m_SessionIdMap.end(), + ("idSession=%u uValue=%#x idClient=%u; conflicting with client %u\n", + idSession, uValue, pClient->m_idClient, ItConflict->second->m_idClient), + VERR_DUPLICATE); + + /* Commit it. */ + try + { + m_SessionIdMap[idSession] = pClient; + } + catch (std::bad_alloc &) + { + LogFunc(("Out of memory\n")); + return VERR_NO_MEMORY; + } + pClient->m_idSession = idSession; + } + return VINF_SUCCESS; +} + + +/** + * For compatibility with old additions only - skip the current message w/o + * calling main code. + * + * Please note that we don't care if the caller cancelled the request, because + * old additions code didn't give damn about VERR_INTERRUPT. + * + * @return VBox status code. + * @param pClient The client state. + * @param hCall The call handle for completing it. + * @param cParms Number of parameters. + */ +int GstCtrlService::clientMsgOldSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms) +{ + /* + * Validate input and access. + */ + ASSERT_GUEST_RETURN(cParms == 1, VERR_WRONG_PARAMETER_COUNT); + + /* + * Execute the request. + * + * Note! As it turns out the old and new skip should be mostly the same. The + * pre-6.0 GAs (up to BETA3) has a hack which tries to issue a + * VERR_NOT_SUPPORTED reply to unknown host requests, however the 5.2.x + * and earlier GAs doesn't. We need old skip behavior only for the 6.0 + * beta GAs, nothing else. + * So, we have to track whether they issued a MSG_REPLY or not. Wonderful. + */ + HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry); + if (pFirstMsg) + { + uint32_t const idMsg = pFirstMsg->mType; + bool const f60BetaHackInPlay = pFirstMsg->m_f60BetaHackInPlay; + int rc; + if (!f60BetaHackInPlay) + rc = clientMsgSkip(pClient, hCall, 0, NULL); + else + { + RTListNodeRemove(&pFirstMsg->m_ListEntry); + pFirstMsg->Delete(); + rc = VINF_SUCCESS; + } + + /* Reset legacy message wait/get state: */ + if (RT_SUCCESS(rc)) + { + pClient->mHostMsgRc = VINF_SUCCESS; + pClient->mHostMsgTries = 0; + pClient->mPeekCount = 0; + } + + LogFlowFunc(("[Client %RU32] Legacy message skipping: Skipped %u (%s)%s!\n", + pClient->m_idClient, idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), f60BetaHackInPlay ? " hack style" : "")); + NOREF(idMsg); + return rc; + } + LogFlowFunc(("[Client %RU32] Legacy message skipping: No messages pending!\n", pClient->m_idClient)); + return VINF_SUCCESS; +} + + +/** + * Forwards client call to the Main API. + * + * This is typically notifications and replys. + * + * @returns VBox status code. + * @param pClient The client state. + * @param idMsg Message ID that occured. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::clientToMain(ClientState *pClient, uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * Do input validation. This class of messages all have a 32-bit context ID as + * the first parameter, so make sure it is there and appropriate for the caller. + */ + ASSERT_GUEST_RETURN(cParms >= 1, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_COUNT); + uint32_t const idContext = paParms[0].u.uint32; + uint32_t const idSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(idContext); + + ASSERT_GUEST_MSG_RETURN( pClient->m_idSession == idSession + || pClient->m_fIsMaster + || ( m_fLegacyMode /* (see bugref:9313#c16) */ + && pClient->m_idSession == UINT32_MAX + && ( idMsg == GUEST_MSG_EXEC_STATUS + || idMsg == GUEST_MSG_SESSION_NOTIFY)), + ("idSession=%u (CID=%#x) m_idSession=%u idClient=%u idMsg=%u (%s)\n", idSession, idContext, + pClient->m_idSession, pClient->m_idClient, idMsg, GstCtrlGuestMsgToStr((eGuestMsg)idMsg)), + VERR_ACCESS_DENIED); + + /* + * It seems okay, so make the call. + */ + return hostCallback(idMsg, cParms, paParms); +} + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnCall} + * + * @note All functions which do not involve an unreasonable delay will be + * handled synchronously. If needed, we will add a request handler + * thread in future for those which do. + * @thread HGCM + */ +/*static*/ DECLCALLBACK(void) +GstCtrlService::svcCall(void *pvService, VBOXHGCMCALLHANDLE hCall, uint32_t idClient, void *pvClient, + uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival) +{ + LogFlowFunc(("[Client %RU32] u32Function=%RU32 (%s), cParms=%RU32, paParms=0x%p\n", + idClient, u32Function, GstCtrlGuestMsgToStr((eGuestMsg)u32Function), cParms, paParms)); + RT_NOREF(tsArrival, idClient); + + /* + * Convert opaque pointers to typed ones. + */ + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturnVoid(pThis); + ClientState *pClient = reinterpret_cast<ClientState *>(pvClient); + AssertReturnVoidStmt(pClient, pThis->mpHelpers->pfnCallComplete(hCall, VERR_INVALID_CLIENT_ID)); + Assert(pClient->m_idClient == idClient); + + /* + * Do the dispatching. + */ + int rc; + switch (u32Function) + { + case GUEST_MSG_MAKE_ME_MASTER: + LogFlowFunc(("[Client %RU32] GUEST_MAKE_ME_MASTER\n", idClient)); + rc = pThis->clientMakeMeMaster(pClient, hCall, cParms); + break; + case GUEST_MSG_REPORT_FEATURES: + LogFlowFunc(("[Client %RU32] GUEST_MSG_REPORT_FEATURES\n", idClient)); + rc = pThis->clientReportFeatures(pClient, hCall, cParms, paParms); + break; + case GUEST_MSG_QUERY_FEATURES: + LogFlowFunc(("[Client %RU32] GUEST_MSG_QUERY_FEATURES\n", idClient)); + rc = pThis->clientQueryFeatures(pClient, hCall, cParms, paParms); + break; + case GUEST_MSG_PEEK_NOWAIT: + LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_NOWAIT\n", idClient)); + rc = pThis->clientMsgPeek(pClient, hCall, cParms, paParms, false /*fWait*/); + break; + case GUEST_MSG_PEEK_WAIT: + LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_WAIT\n", idClient)); + rc = pThis->clientMsgPeek(pClient, hCall, cParms, paParms, true /*fWait*/); + break; + case GUEST_MSG_GET: + LogFlowFunc(("[Client %RU32] GUEST_MSG_GET\n", idClient)); + rc = pThis->clientMsgGet(pClient, hCall, cParms, paParms); + break; + case GUEST_MSG_CANCEL: + LogFlowFunc(("[Client %RU32] GUEST_MSG_CANCEL\n", idClient)); + rc = pThis->clientMsgCancel(pClient, cParms); + break; + case GUEST_MSG_SKIP: + LogFlowFunc(("[Client %RU32] GUEST_MSG_SKIP\n", idClient)); + rc = pThis->clientMsgSkip(pClient, hCall, cParms, paParms); + break; + case GUEST_MSG_SESSION_PREPARE: + LogFlowFunc(("[Client %RU32] GUEST_SESSION_PREPARE\n", idClient)); + rc = pThis->clientSessionPrepare(pClient, hCall, cParms, paParms); + break; + case GUEST_MSG_SESSION_CANCEL_PREPARED: + LogFlowFunc(("[Client %RU32] GUEST_SESSION_CANCEL_PREPARED\n", idClient)); + rc = pThis->clientSessionCancelPrepared(pClient, cParms, paParms); + break; + case GUEST_MSG_SESSION_ACCEPT: + LogFlowFunc(("[Client %RU32] GUEST_SESSION_ACCEPT\n", idClient)); + rc = pThis->clientSessionAccept(pClient, hCall, cParms, paParms); + break; + case GUEST_MSG_SESSION_CLOSE: + LogFlowFunc(("[Client %RU32] GUEST_SESSION_CLOSE\n", idClient)); + rc = pThis->clientSessionCloseOther(pClient, cParms, paParms); + break; + + /* + * Stuff the goes to various main objects: + */ + case GUEST_MSG_REPLY: + if (cParms >= 3 && paParms[2].u.uint32 == (uint32_t)VERR_NOT_SUPPORTED) + { + HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry); + if (pFirstMsg && pFirstMsg->m_idContext == paParms[0].u.uint32) + pFirstMsg->m_f60BetaHackInPlay = true; + } + RT_FALL_THROUGH(); + case GUEST_MSG_PROGRESS_UPDATE: + case GUEST_MSG_SESSION_NOTIFY: + case GUEST_MSG_EXEC_OUTPUT: + case GUEST_MSG_EXEC_STATUS: + case GUEST_MSG_EXEC_INPUT_STATUS: + case GUEST_MSG_EXEC_IO_NOTIFY: + case GUEST_MSG_DIR_NOTIFY: + case GUEST_MSG_FILE_NOTIFY: + LogFlowFunc(("[Client %RU32] %s\n", idClient, GstCtrlGuestMsgToStr((eGuestMsg)u32Function))); + rc = pThis->clientToMain(pClient, u32Function /* Msg */, cParms, paParms); + Assert(rc != VINF_HGCM_ASYNC_EXECUTE); + break; + + /* + * The remaining messages are here for compatibility with older Guest Additions: + */ + case GUEST_MSG_WAIT: + LogFlowFunc(("[Client %RU32] GUEST_MSG_WAIT\n", idClient)); + pThis->clientMsgOldGet(pClient, hCall, cParms, paParms); + rc = VINF_HGCM_ASYNC_EXECUTE; + break; + + case GUEST_MSG_SKIP_OLD: + LogFlowFunc(("[Client %RU32] GUEST_MSG_SKIP_OLD\n", idClient)); + rc = pThis->clientMsgOldSkip(pClient, hCall, cParms); + break; + + case GUEST_MSG_FILTER_SET: + LogFlowFunc(("[Client %RU32] GUEST_MSG_FILTER_SET\n", idClient)); + rc = pThis->clientMsgOldFilterSet(pClient, cParms, paParms); + break; + + case GUEST_MSG_FILTER_UNSET: + LogFlowFunc(("[Client %RU32] GUEST_MSG_FILTER_UNSET\n", idClient)); + rc = VERR_NOT_IMPLEMENTED; + break; + + /* + * Anything else shall return invalid function. + * Note! We used to return VINF_SUCCESS for these. See bugref:9313 + * and Guest::i_notifyCtrlDispatcher(). + */ + default: + ASSERT_GUEST_MSG_FAILED(("u32Function=%RU32 (%#x)\n", u32Function, u32Function)); + rc = VERR_INVALID_FUNCTION; + break; + } + + if (rc != VINF_HGCM_ASYNC_EXECUTE) + { + /* Tell the client that the call is complete (unblocks waiting). */ + LogFlowFunc(("[Client %RU32] Calling pfnCallComplete w/ rc=%Rrc\n", idClient, rc)); + AssertPtr(pThis->mpHelpers); + pThis->mpHelpers->pfnCallComplete(hCall, rc); + } +} + + +/** + * Notifies the host (using low-level HGCM callbacks) about an event + * which was sent from the client. + * + * @returns VBox status code. + * @param u32Function Message ID that occured. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::hostCallback(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFunc(("u32Function=%RU32 (%s), cParms=%ld, paParms=%p\n", + u32Function, GstCtrlGuestMsgToStr((eGuestMsg)u32Function), cParms, paParms)); + + int rc; + if (mpfnHostCallback) + { + VBOXGUESTCTRLHOSTCALLBACK data = { cParms, paParms }; + rc = mpfnHostCallback(mpvHostData, u32Function, &data, sizeof(data)); + } + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Processes a message received from the host side and re-routes it to + * a connect client on the guest. + * + * @returns VBox status code. + * @param idMsg Message ID to process. + * @param cParms Number of parameters. + * @param paParms Array of parameters. + */ +int GstCtrlService::hostProcessMessage(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + /* + * If no client is connected at all we don't buffer any host messages + * and immediately return an error to the host. This avoids the host + * waiting for a response from the guest side in case VBoxService on + * the guest is not running/system is messed up somehow. + */ + if (m_ClientStateMap.empty()) + { + LogFlow(("GstCtrlService::hostProcessMessage: VERR_NOT_FOUND!\n")); + return VERR_NOT_FOUND; + } + + /* + * Create a host message for each destination. + * Note! There is currently only one scenario in which we send a host + * message to two recipients. + */ + HostMsg *pHostMsg = new (std::nothrow) HostMsg(); + AssertReturn(pHostMsg, VERR_NO_MEMORY); + int rc = pHostMsg->Init(idMsg, cParms, paParms); + if (RT_SUCCESS(rc)) + { + uint64_t const fDestinations = pHostMsg->m_idContextAndDst & VBOX_GUESTCTRL_DST_BOTH; + HostMsg *pHostMsg2 = NULL; + if (fDestinations != VBOX_GUESTCTRL_DST_BOTH) + { /* likely */ } + else + { + pHostMsg2 = new (std::nothrow) HostMsg(); + if (pHostMsg2) + rc = pHostMsg2->Init(idMsg, cParms, paParms); + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Handling host message m_idContextAndDst=%#RX64, idMsg=%RU32, cParms=%RU32, paParms=%p, cClients=%zu\n", + pHostMsg->m_idContextAndDst, idMsg, cParms, paParms, m_ClientStateMap.size())); + + /* + * Find the message destination and post it to the client. If the + * session ID doesn't match any particular client it goes to the master. + */ + AssertMsg(!m_ClientStateMap.empty(), ("Client state map is empty when it should not be!\n")); + + /* Dispatch to the session. */ + if (fDestinations & VBOX_GUESTCTRL_DST_SESSION) + { + uint32_t const idSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pHostMsg->m_idContext); + ClientStateMap::iterator It = m_SessionIdMap.find(idSession); + if (It != m_SessionIdMap.end()) + { + ClientState *pClient = It->second; + Assert(pClient->m_idSession == idSession); + RTListAppend(&pClient->m_HostMsgList, &pHostMsg->m_ListEntry); + pHostMsg = pHostMsg2; + pHostMsg2 = NULL; + + int rc2 = pClient->Wakeup(); + LogFlowFunc(("Woke up client ID=%RU32 -> rc=%Rrc\n", pClient->m_idClient, rc2)); + RT_NOREF(rc2); + rc = VINF_SUCCESS; + } + else + { + LogFunc(("No client with session ID %u was found! (idMsg=%d %s)\n", + idSession, idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg))); + rc = !(fDestinations & VBOX_GUESTCTRL_DST_ROOT_SVC) ? VERR_NOT_FOUND : VWRN_NOT_FOUND; + } + } + + /* Does the message go to the root service? */ + if ( (fDestinations & VBOX_GUESTCTRL_DST_ROOT_SVC) + && RT_SUCCESS(rc)) + { + Assert(pHostMsg); + if (m_pMasterClient) + { + RTListAppend(&m_pMasterClient->m_HostMsgList, &pHostMsg->m_ListEntry); + pHostMsg = NULL; + + int rc2 = m_pMasterClient->Wakeup(); + LogFlowFunc(("Woke up client ID=%RU32 (master) -> rc=%Rrc\n", m_pMasterClient->m_idClient, rc2)); + NOREF(rc2); + } + else + rc = VERR_NOT_FOUND; + } + } + + /* Drop unset messages. */ + if (pHostMsg2) + pHostMsg2->Delete(); + } + if (pHostMsg) + pHostMsg->Delete(); + + if (RT_FAILURE(rc)) + LogFunc(("Failed %Rrc (idMsg=%u, cParms=%u)\n", rc, idMsg, cParms)); + return rc; +} + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnHostCall, + * Wraps to the hostProcessMessage() member function.} + */ +/*static*/ DECLCALLBACK(int) +GstCtrlService::svcHostCall(void *pvService, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + AssertLogRelReturn(RT_VALID_PTR(pvService), VERR_INVALID_PARAMETER); + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + LogFlowFunc(("u32Function=%RU32, cParms=%RU32, paParms=0x%p\n", u32Function, cParms, paParms)); + AssertReturn(u32Function != HOST_MSG_CANCEL_PENDING_WAITS, VERR_INVALID_FUNCTION); + return pThis->hostProcessMessage(u32Function, cParms, paParms); +} + + + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnSaveState} + */ +/*static*/ DECLCALLBACK(int) +GstCtrlService::svcSaveState(void *pvService, uint32_t idClient, void *pvClient, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ + RT_NOREF(pvClient); + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + /* Note! We don't need to save the idSession here because it's only used + for sessions and the sessions are not persistent across a state + save/restore. The Main objects aren't there. Clients shuts down. + Only the root service survives, so remember who that is and its mode. */ + + pVMM->pfnSSMR3PutU32(pSSM, 1); + pVMM->pfnSSMR3PutBool(pSSM, pThis->m_fLegacyMode); + return pVMM->pfnSSMR3PutBool(pSSM, idClient == pThis->m_idMasterClient); +} + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnLoadState} + */ +/*static*/ DECLCALLBACK(int) +GstCtrlService::svcLoadState(void *pvService, uint32_t idClient, void *pvClient, + PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + ClientState *pClient = reinterpret_cast<ClientState *>(pvClient); + AssertReturn(pClient, VERR_INVALID_CLIENT_ID); + Assert(pClient->m_idClient == idClient); + + if (uVersion >= HGCM_SAVED_STATE_VERSION) + { + uint32_t uSubVersion; + int rc = pVMM->pfnSSMR3GetU32(pSSM, &uSubVersion); + AssertRCReturn(rc, rc); + if (uSubVersion != 1) + return pVMM->pfnSSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + "sub version %u, expected 1\n", uSubVersion); + bool fLegacyMode; + rc = pVMM->pfnSSMR3GetBool(pSSM, &fLegacyMode); + AssertRCReturn(rc, rc); + pThis->m_fLegacyMode = fLegacyMode; + + bool fIsMaster; + rc = pVMM->pfnSSMR3GetBool(pSSM, &fIsMaster); + AssertRCReturn(rc, rc); + + pClient->m_fIsMaster = fIsMaster; + if (fIsMaster) + { + pThis->m_pMasterClient = pClient; + pThis->m_idMasterClient = idClient; + } + } + else + { + /* + * For old saved states we have to guess at who should be the master. + * Given how HGCMService::CreateAndConnectClient and associates manage + * and saves the client, the first client connecting will be restored + * first. The only time this might go wrong if the there are zombie + * VBoxService session processes in the restored guest, and I don't + * we need to care too much about that scenario. + * + * Given how HGCM first re-connects the clients before this function + * gets called, there isn't anything we need to do here it turns out. :-) + */ + } + pClient->m_fRestored = true; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnRegisterExtension, + * Installs a host callback for notifications of property changes.} + */ +/*static*/ DECLCALLBACK(int) GstCtrlService::svcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension) +{ + SELF *pThis = reinterpret_cast<SELF *>(pvService); + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrNullReturn(pfnExtension, VERR_INVALID_POINTER); + + pThis->mpfnHostCallback = pfnExtension; + pThis->mpvHostData = pvExtension; + return VINF_SUCCESS; +} + + +/** + * @copydoc FNVBOXHGCMSVCLOAD + */ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pTable=%p\n", pTable)); + + if (!RT_VALID_PTR(pTable)) + rc = VERR_INVALID_PARAMETER; + else + { + LogFlowFunc(("pTable->cbSize=%d, pTable->u32Version=0x%08X\n", pTable->cbSize, pTable->u32Version)); + + if ( pTable->cbSize != sizeof (VBOXHGCMSVCFNTABLE) + || pTable->u32Version != VBOX_HGCM_SVC_VERSION) + { + rc = VERR_VERSION_MISMATCH; + } + else + { + GstCtrlService *pService = NULL; + /* No exceptions may propagate outside. */ + try + { + pService = new GstCtrlService(pTable->pHelpers); + } + catch (int rcThrown) + { + rc = rcThrown; + } + catch(std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + /* + * We don't need an additional client data area on the host, + * because we're a class which can have members for that :-). + */ + pTable->cbClient = sizeof(ClientState); + + /* Limit pending calls to 8 pending per connection (doubt we need more than + one). Map legacy clients to the root and limit kernel to 1. Use defaults + for root and user clients. */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxCallsPerClient[i] = 8; + + pTable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_ROOT; + pTable->acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = 1; + + /* Register functions. */ + pTable->pfnUnload = GstCtrlService::svcUnload; + pTable->pfnConnect = GstCtrlService::svcConnect; + pTable->pfnDisconnect = GstCtrlService::svcDisconnect; + pTable->pfnCall = GstCtrlService::svcCall; + pTable->pfnHostCall = GstCtrlService::svcHostCall; + pTable->pfnSaveState = GstCtrlService::svcSaveState; + pTable->pfnLoadState = GstCtrlService::svcLoadState; + pTable->pfnRegisterExtension = GstCtrlService::svcRegisterExtension; + pTable->pfnNotify = NULL; + + /* Service specific initialization. */ + pTable->pvService = pService; + } + else + { + if (pService) + { + delete pService; + pService = NULL; + } + } + } + } + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + |